|  | #!/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 |