| #!/usr/bin/env python3 |
| |
| import logging |
| import socket |
| import time |
| from socket import timeout as SocketTimeout |
| |
| import paramiko |
| from paramiko.buffered_pipe import PipeTimeout as PipeTimeout |
| from paramiko.ssh_exception import ( |
| AuthenticationException, |
| BadHostKeyException, |
| NoValidConnectionsError, |
| SSHException, |
| ) |
| from scp import SCPClient, SCPException |
| |
| |
| class SSHRemoteclient: |
| r""" |
| Class to create ssh connection to remote host |
| for remote host command execution and scp. |
| """ |
| |
| def __init__(self, hostname, username, password, port_ssh): |
| r""" |
| Initialize the FFDCCollector object with the provided remote host |
| details. |
| |
| This method initializes an FFDCCollector object with the given |
| attributes, which represent the details of the remote (targeting) |
| host. The attributes include the hostname, username, password, and |
| SSH port. |
| |
| Parameters: |
| hostname (str): Name or IP address of the remote (targeting) host. |
| username (str): User on the remote host with access to FFDC files. |
| password (str): Password for the user on the remote host. |
| port_ssh (int): SSH port value. By default, 22. |
| |
| Returns: |
| None |
| """ |
| self.ssh_output = None |
| self.ssh_error = None |
| self.sshclient = None |
| self.scpclient = None |
| self.hostname = hostname |
| self.username = username |
| self.password = password |
| self.port_ssh = port_ssh |
| |
| def ssh_remoteclient_login(self): |
| r""" |
| Connect to remote host using the SSH client. |
| |
| Returns: |
| bool: The method return True on success and False in failure. |
| """ |
| is_ssh_login = True |
| try: |
| # SSHClient to make connections to the remote server |
| self.sshclient = paramiko.SSHClient() |
| # setting set_missing_host_key_policy() to allow any host |
| self.sshclient.set_missing_host_key_policy( |
| paramiko.AutoAddPolicy() |
| ) |
| # Connect to the server |
| self.sshclient.connect( |
| hostname=self.hostname, |
| port=self.port_ssh, |
| username=self.username, |
| password=self.password, |
| banner_timeout=120, |
| timeout=60, |
| look_for_keys=False, |
| ) |
| |
| except ( |
| BadHostKeyException, |
| AuthenticationException, |
| SSHException, |
| NoValidConnectionsError, |
| socket.error, |
| ) as e: |
| is_ssh_login = False |
| print("SSH Login: Exception: %s" % e) |
| |
| return is_ssh_login |
| |
| def ssh_remoteclient_disconnect(self): |
| r""" |
| Disconnect from the remote host using the SSH client. |
| |
| This method disconnects from the remote host using the SSH client |
| established during the FFDC collection process. The method does not |
| return any value. |
| |
| Returns: |
| None |
| """ |
| if self.sshclient: |
| self.sshclient.close() |
| |
| if self.scpclient: |
| self.scpclient.close() |
| |
| def execute_command(self, command, default_timeout=60): |
| r""" |
| Execute a command on the remote host using the SSH client. |
| |
| This method executes a provided command on the remote host using the |
| SSH client. The method takes the command string as an argument and an |
| optional default_timeout parameter of 60 seconds, which specifies the |
| timeout for the command execution. |
| |
| The method returns the output of the executed command as a string. |
| |
| Parameters: |
| command (str): The command string to be executed |
| on the remote host. |
| default_timeout (int, optional): The timeout for the command |
| execution. Defaults to 60 seconds. |
| |
| Returns: |
| str: The output of the executed command as a string. |
| """ |
| empty = "" |
| cmd_start = time.time() |
| try: |
| stdin, stdout, stderr = self.sshclient.exec_command( |
| command, timeout=default_timeout |
| ) |
| start = time.time() |
| while time.time() < start + default_timeout: |
| # Need to do read/write operation to trigger |
| # paramiko exec_command timeout mechanism. |
| xresults = stderr.readlines() |
| results = "".join(xresults) |
| time.sleep(1) |
| if stdout.channel.exit_status_ready(): |
| break |
| cmd_exit_code = stdout.channel.recv_exit_status() |
| |
| # Convert list of string to one string |
| err = "" |
| out = "" |
| for item in results: |
| err += item |
| for item in stdout.readlines(): |
| out += item |
| |
| return cmd_exit_code, err, out |
| |
| except ( |
| paramiko.AuthenticationException, |
| paramiko.SSHException, |
| paramiko.ChannelException, |
| SocketTimeout, |
| ) as e: |
| # Log command with error. |
| # Return to caller for next command, if any. |
| logging.error( |
| "\n\tERROR: Fail remote command %s %s" % (e.__class__, e) |
| ) |
| logging.error( |
| "\tCommand '%s' Elapsed Time %s" |
| % ( |
| command, |
| time.strftime( |
| "%H:%M:%S", time.gmtime(time.time() - cmd_start) |
| ), |
| ) |
| ) |
| return 0, empty, empty |
| |
| def scp_connection(self): |
| r""" |
| Establish an SCP connection for file transfer. |
| |
| This method creates an SCP connection for file transfer using the SSH |
| client established during the FFDC collection process. |
| |
| Returns: |
| None |
| """ |
| try: |
| self.scpclient = SCPClient( |
| self.sshclient.get_transport(), sanitize=lambda x: x |
| ) |
| logging.info( |
| "\n\t[Check] %s SCP transport established.\t [OK]" |
| % self.hostname |
| ) |
| except (SCPException, SocketTimeout, PipeTimeout) as e: |
| self.scpclient = None |
| logging.error( |
| "\n\tERROR: SCP get_transport has failed. %s %s" |
| % (e.__class__, e) |
| ) |
| logging.info( |
| "\tScript continues generating FFDC on %s." % self.hostname |
| ) |
| logging.info( |
| "\tCollected data will need to be manually offloaded." |
| ) |
| |
| def scp_file_from_remote(self, remote_file, local_file): |
| r""" |
| SCP a file from the remote host to the local host with a filename. |
| |
| This method copies a file from the remote host to the local host using |
| the SCP protocol. The method takes the remote_file and local_file as |
| arguments, which represent the full paths of the files on the remote |
| and local hosts, respectively. |
| |
| |
| Parameters: |
| remote_file (str): The full path filename on the remote host. |
| local_file (str): The full path filename on the local host. |
| |
| Returns: |
| bool: The method return True on success and False in failure. |
| """ |
| try: |
| self.scpclient.get(remote_file, local_file, recursive=True) |
| except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e: |
| # Log command with error. Return to caller for next file, if any. |
| logging.error( |
| "\n\tERROR: Fail scp %s from remotehost %s %s\n\n" |
| % (remote_file, e.__class__, e) |
| ) |
| # Pause for 2 seconds allowing Paramiko to finish error processing |
| # before next fetch. Without the delay after SCPException, next |
| # fetch will get 'paramiko.ssh_exception.SSHException'> Channel |
| # closed Error. |
| time.sleep(2) |
| return False |
| # Return True for file accounting |
| return True |