| #!/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): | 
 |         r""" | 
 |         Description of argument(s): | 
 |  | 
 |         hostname        Name/IP of the remote (targeting) host | 
 |         username        User on the remote host with access to FFCD files | 
 |         password        Password for user on remote host | 
 |         """ | 
 |  | 
 |         self.ssh_output = None | 
 |         self.ssh_error = None | 
 |         self.sshclient = None | 
 |         self.scpclient = None | 
 |         self.hostname = hostname | 
 |         self.username = username | 
 |         self.password = password | 
 |  | 
 |     def ssh_remoteclient_login(self): | 
 |         r""" | 
 |         Method to create a ssh connection to remote host. | 
 |         """ | 
 |  | 
 |         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, | 
 |                 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 | 
 |  | 
 |         return is_ssh_login | 
 |  | 
 |     def ssh_remoteclient_disconnect(self): | 
 |         r""" | 
 |         Clean up. | 
 |         """ | 
 |  | 
 |         if self.sshclient: | 
 |             self.sshclient.close() | 
 |  | 
 |         if self.scpclient: | 
 |             self.scpclient.close() | 
 |  | 
 |     def execute_command(self, command, default_timeout=60): | 
 |         """ | 
 |         Execute command on the remote host. | 
 |  | 
 |         Description of argument(s): | 
 |         command                Command string sent to remote host | 
 |  | 
 |         """ | 
 |  | 
 |         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""" | 
 |         Create a scp connection for file transfer. | 
 |         """ | 
 |         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 file in remote system to local with date-prefixed filename. | 
 |  | 
 |         Description of argument(s): | 
 |         remote_file            Full path filename on the remote host | 
 |  | 
 |         local_file             Full path filename on the local host | 
 |                                local filename = date-time_remote filename | 
 |  | 
 |         """ | 
 |  | 
 |         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 |