blob: 0f10207d3df6a7a0b6bbd27a546865022c290a26 [file] [log] [blame]
#!/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