#!/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""" 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 self.port_ssh = port_ssh 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, 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 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