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, port_ssh): 26 r""" 27 Initialize the FFDCCollector object with the provided remote host 28 details. 29 30 This method initializes an FFDCCollector object with the given 31 attributes, which represent the details of the remote (targeting) 32 host. The attributes include the hostname, username, password, and 33 SSH port. 34 35 Parameters: 36 hostname (str): Name or IP address of the remote (targeting) host. 37 username (str): User on the remote host with access to FFDC files. 38 password (str): Password for the user on the remote host. 39 port_ssh (int): SSH port value. By default, 22. 40 41 Returns: 42 None 43 """ 44 self.ssh_output = None 45 self.ssh_error = None 46 self.sshclient = None 47 self.scpclient = None 48 self.hostname = hostname 49 self.username = username 50 self.password = password 51 self.port_ssh = port_ssh 52 53 def ssh_remoteclient_login(self): 54 r""" 55 Connect to remote host using the SSH client. 56 57 Returns: 58 bool: The method return True on success and False in failure. 59 """ 60 is_ssh_login = True 61 try: 62 # SSHClient to make connections to the remote server 63 self.sshclient = paramiko.SSHClient() 64 # setting set_missing_host_key_policy() to allow any host 65 self.sshclient.set_missing_host_key_policy( 66 paramiko.AutoAddPolicy() 67 ) 68 # Connect to the server 69 self.sshclient.connect( 70 hostname=self.hostname, 71 port=self.port_ssh, 72 username=self.username, 73 password=self.password, 74 banner_timeout=120, 75 timeout=60, 76 look_for_keys=False, 77 ) 78 79 except ( 80 BadHostKeyException, 81 AuthenticationException, 82 SSHException, 83 NoValidConnectionsError, 84 socket.error, 85 ) as e: 86 is_ssh_login = False 87 print("SSH Login: Exception: %s" % e) 88 89 return is_ssh_login 90 91 def ssh_remoteclient_disconnect(self): 92 r""" 93 Disconnect from the remote host using the SSH client. 94 95 This method disconnects from the remote host using the SSH client 96 established during the FFDC collection process. The method does not 97 return any value. 98 99 Returns: 100 None 101 """ 102 if self.sshclient: 103 self.sshclient.close() 104 105 if self.scpclient: 106 self.scpclient.close() 107 108 def execute_command(self, command, default_timeout=60): 109 r""" 110 Execute a command on the remote host using the SSH client. 111 112 This method executes a provided command on the remote host using the 113 SSH client. The method takes the command string as an argument and an 114 optional default_timeout parameter of 60 seconds, which specifies the 115 timeout for the command execution. 116 117 The method returns the output of the executed command as a string. 118 119 Parameters: 120 command (str): The command string to be executed 121 on the remote host. 122 default_timeout (int, optional): The timeout for the command 123 execution. Defaults to 60 seconds. 124 125 Returns: 126 str: The output of the executed command as a string. 127 """ 128 empty = "" 129 cmd_start = time.time() 130 try: 131 stdin, stdout, stderr = self.sshclient.exec_command( 132 command, timeout=default_timeout 133 ) 134 start = time.time() 135 while time.time() < start + default_timeout: 136 # Need to do read/write operation to trigger 137 # paramiko exec_command timeout mechanism. 138 xresults = stderr.readlines() 139 results = "".join(xresults) 140 time.sleep(1) 141 if stdout.channel.exit_status_ready(): 142 break 143 cmd_exit_code = stdout.channel.recv_exit_status() 144 145 # Convert list of string to one string 146 err = "" 147 out = "" 148 for item in results: 149 err += item 150 for item in stdout.readlines(): 151 out += item 152 153 return cmd_exit_code, err, out 154 155 except ( 156 paramiko.AuthenticationException, 157 paramiko.SSHException, 158 paramiko.ChannelException, 159 SocketTimeout, 160 ) as e: 161 # Log command with error. 162 # Return to caller for next command, if any. 163 logging.error( 164 "\n\tERROR: Fail remote command %s %s" % (e.__class__, e) 165 ) 166 logging.error( 167 "\tCommand '%s' Elapsed Time %s" 168 % ( 169 command, 170 time.strftime( 171 "%H:%M:%S", time.gmtime(time.time() - cmd_start) 172 ), 173 ) 174 ) 175 return 0, empty, empty 176 177 def scp_connection(self): 178 r""" 179 Establish an SCP connection for file transfer. 180 181 This method creates an SCP connection for file transfer using the SSH 182 client established during the FFDC collection process. 183 184 Returns: 185 None 186 """ 187 try: 188 self.scpclient = SCPClient( 189 self.sshclient.get_transport(), sanitize=lambda x: x 190 ) 191 logging.info( 192 "\n\t[Check] %s SCP transport established.\t [OK]" 193 % self.hostname 194 ) 195 except (SCPException, SocketTimeout, PipeTimeout) as e: 196 self.scpclient = None 197 logging.error( 198 "\n\tERROR: SCP get_transport has failed. %s %s" 199 % (e.__class__, e) 200 ) 201 logging.info( 202 "\tScript continues generating FFDC on %s." % self.hostname 203 ) 204 logging.info( 205 "\tCollected data will need to be manually offloaded." 206 ) 207 208 def scp_file_from_remote(self, remote_file, local_file): 209 r""" 210 SCP a file from the remote host to the local host with a filename. 211 212 This method copies a file from the remote host to the local host using 213 the SCP protocol. The method takes the remote_file and local_file as 214 arguments, which represent the full paths of the files on the remote 215 and local hosts, respectively. 216 217 218 Parameters: 219 remote_file (str): The full path filename on the remote host. 220 local_file (str): The full path filename on the local host. 221 222 Returns: 223 bool: The method return True on success and False in failure. 224 """ 225 try: 226 self.scpclient.get(remote_file, local_file, recursive=True) 227 except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e: 228 # Log command with error. Return to caller for next file, if any. 229 logging.error( 230 "\n\tERROR: Fail scp %s from remotehost %s %s\n\n" 231 % (remote_file, e.__class__, e) 232 ) 233 # Pause for 2 seconds allowing Paramiko to finish error processing 234 # before next fetch. Without the delay after SCPException, next 235 # fetch will get 'paramiko.ssh_exception.SSHException'> Channel 236 # closed Error. 237 time.sleep(2) 238 return False 239 # Return True for file accounting 240 return True 241