1#!/usr/bin/env python3 2 3import logging 4import socket 5import time 6from socket import timeout as SocketTimeout 7 8import paramiko 9from paramiko.buffered_pipe import PipeTimeout as PipeTimeout 10from paramiko.ssh_exception import ( 11 AuthenticationException, 12 BadHostKeyException, 13 NoValidConnectionsError, 14 SSHException, 15) 16from scp import SCPClient, SCPException 17 18 19class SSHRemoteclient: 20 r""" 21 Class to create ssh connection to remote host 22 for remote host command execution and scp. 23 """ 24 25 def __init__(self, hostname, username, password): 26 r""" 27 Description of argument(s): 28 29 hostname Name/IP of the remote (targeting) host 30 username User on the remote host with access to FFCD files 31 password Password for user on remote host 32 """ 33 34 self.ssh_output = None 35 self.ssh_error = None 36 self.sshclient = None 37 self.scpclient = None 38 self.hostname = hostname 39 self.username = username 40 self.password = password 41 42 def ssh_remoteclient_login(self): 43 r""" 44 Method to create a ssh connection to remote host. 45 """ 46 47 is_ssh_login = True 48 try: 49 # SSHClient to make connections to the remote server 50 self.sshclient = paramiko.SSHClient() 51 # setting set_missing_host_key_policy() to allow any host 52 self.sshclient.set_missing_host_key_policy( 53 paramiko.AutoAddPolicy() 54 ) 55 # Connect to the server 56 self.sshclient.connect( 57 hostname=self.hostname, 58 username=self.username, 59 password=self.password, 60 banner_timeout=120, 61 timeout=60, 62 look_for_keys=False, 63 ) 64 65 except ( 66 BadHostKeyException, 67 AuthenticationException, 68 SSHException, 69 NoValidConnectionsError, 70 socket.error, 71 ) as e: 72 is_ssh_login = False 73 74 return is_ssh_login 75 76 def ssh_remoteclient_disconnect(self): 77 r""" 78 Clean up. 79 """ 80 81 if self.sshclient: 82 self.sshclient.close() 83 84 if self.scpclient: 85 self.scpclient.close() 86 87 def execute_command(self, command, default_timeout=60): 88 """ 89 Execute command on the remote host. 90 91 Description of argument(s): 92 command Command string sent to remote host 93 94 """ 95 96 empty = "" 97 cmd_start = time.time() 98 try: 99 stdin, stdout, stderr = self.sshclient.exec_command( 100 command, timeout=default_timeout 101 ) 102 start = time.time() 103 while time.time() < start + default_timeout: 104 # Need to do read/write operation to trigger 105 # paramiko exec_command timeout mechanism. 106 xresults = stderr.readlines() 107 results = "".join(xresults) 108 time.sleep(1) 109 if stdout.channel.exit_status_ready(): 110 break 111 cmd_exit_code = stdout.channel.recv_exit_status() 112 113 # Convert list of string to one string 114 err = "" 115 out = "" 116 for item in results: 117 err += item 118 for item in stdout.readlines(): 119 out += item 120 121 return cmd_exit_code, err, out 122 123 except ( 124 paramiko.AuthenticationException, 125 paramiko.SSHException, 126 paramiko.ChannelException, 127 SocketTimeout, 128 ) as e: 129 # Log command with error. Return to caller for next command, if any. 130 logging.error( 131 "\n\tERROR: Fail remote command %s %s" % (e.__class__, e) 132 ) 133 logging.error( 134 "\tCommand '%s' Elapsed Time %s" 135 % ( 136 command, 137 time.strftime( 138 "%H:%M:%S", time.gmtime(time.time() - cmd_start) 139 ), 140 ) 141 ) 142 return 0, empty, empty 143 144 def scp_connection(self): 145 r""" 146 Create a scp connection for file transfer. 147 """ 148 try: 149 self.scpclient = SCPClient( 150 self.sshclient.get_transport(), sanitize=lambda x: x 151 ) 152 logging.info( 153 "\n\t[Check] %s SCP transport established.\t [OK]" 154 % self.hostname 155 ) 156 except (SCPException, SocketTimeout, PipeTimeout) as e: 157 self.scpclient = None 158 logging.error( 159 "\n\tERROR: SCP get_transport has failed. %s %s" 160 % (e.__class__, e) 161 ) 162 logging.info( 163 "\tScript continues generating FFDC on %s." % self.hostname 164 ) 165 logging.info( 166 "\tCollected data will need to be manually offloaded." 167 ) 168 169 def scp_file_from_remote(self, remote_file, local_file): 170 r""" 171 scp file in remote system to local with date-prefixed filename. 172 173 Description of argument(s): 174 remote_file Full path filename on the remote host 175 176 local_file Full path filename on the local host 177 local filename = date-time_remote filename 178 179 """ 180 181 try: 182 self.scpclient.get(remote_file, local_file, recursive=True) 183 except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e: 184 # Log command with error. Return to caller for next file, if any. 185 logging.error( 186 "\n\tERROR: Fail scp %s from remotehost %s %s\n\n" 187 % (remote_file, e.__class__, e) 188 ) 189 # Pause for 2 seconds allowing Paramiko to finish error processing before next fetch. 190 # Without the delay after SCPException, 191 # next fetch will get 'paramiko.ssh_exception.SSHException'> Channel closed Error. 192 time.sleep(2) 193 return False 194 # Return True for file accounting 195 return True 196