blob: 0f10207d3df6a7a0b6bbd27a546865022c290a26 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Peter D Phan72ce6b82021-06-03 06:18:26 -05002
George Keishinge635ddc2022-12-08 07:38:02 -06003import logging
Patrick Williams20f38712022-12-08 06:18:26 -06004import socket
5import time
George Keishinge635ddc2022-12-08 07:38:02 -06006from socket import timeout as SocketTimeout
Patrick Williams57318182022-12-08 06:18:26 -06007
Patrick Williams20f38712022-12-08 06:18:26 -06008import 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
Peter D Phan72ce6b82021-06-03 06:18:26 -050018
19class SSHRemoteclient:
20 r"""
21 Class to create ssh connection to remote host
22 for remote host command execution and scp.
23 """
24
George Keishing7a61aa22023-06-26 13:18:37 +053025 def __init__(self, hostname, username, password, port_ssh):
Peter D Phan72ce6b82021-06-03 06:18:26 -050026 r"""
George Keishinge5c44392025-05-17 21:14:09 +053027 Initialize the FFDCCollector object with the provided remote host
28 details.
Peter D Phan72ce6b82021-06-03 06:18:26 -050029
George Keishinge5c44392025-05-17 21:14:09 +053030 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
Peter D Phan72ce6b82021-06-03 06:18:26 -050043 """
Peter D Phan72ce6b82021-06-03 06:18:26 -050044 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
George Keishing7a61aa22023-06-26 13:18:37 +053051 self.port_ssh = port_ssh
Peter D Phan72ce6b82021-06-03 06:18:26 -050052
53 def ssh_remoteclient_login(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -050054 r"""
George Keishinge5c44392025-05-17 21:14:09 +053055 Connect to remote host using the SSH client.
Peter D Phan72ce6b82021-06-03 06:18:26 -050056
George Keishinge5c44392025-05-17 21:14:09 +053057 Returns:
58 bool: The method return True on success and False in failure.
59 """
Peter D Phan5963d632021-07-12 09:58:55 -050060 is_ssh_login = True
Peter D Phan72ce6b82021-06-03 06:18:26 -050061 try:
62 # SSHClient to make connections to the remote server
63 self.sshclient = paramiko.SSHClient()
Peter D Phan8462faf2021-06-16 12:24:15 -050064 # setting set_missing_host_key_policy() to allow any host
Patrick Williams20f38712022-12-08 06:18:26 -060065 self.sshclient.set_missing_host_key_policy(
66 paramiko.AutoAddPolicy()
67 )
Peter D Phan72ce6b82021-06-03 06:18:26 -050068 # Connect to the server
Patrick Williams20f38712022-12-08 06:18:26 -060069 self.sshclient.connect(
70 hostname=self.hostname,
George Keishing7a61aa22023-06-26 13:18:37 +053071 port=self.port_ssh,
Patrick Williams20f38712022-12-08 06:18:26 -060072 username=self.username,
73 password=self.password,
74 banner_timeout=120,
75 timeout=60,
76 look_for_keys=False,
77 )
Peter D Phan72ce6b82021-06-03 06:18:26 -050078
Patrick Williams20f38712022-12-08 06:18:26 -060079 except (
80 BadHostKeyException,
81 AuthenticationException,
82 SSHException,
83 NoValidConnectionsError,
84 socket.error,
85 ) as e:
Peter D Phan5963d632021-07-12 09:58:55 -050086 is_ssh_login = False
George Keishing15352052025-04-24 18:55:47 +053087 print("SSH Login: Exception: %s" % e)
Peter D Phan5963d632021-07-12 09:58:55 -050088
89 return is_ssh_login
Peter D Phan72ce6b82021-06-03 06:18:26 -050090
91 def ssh_remoteclient_disconnect(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -050092 r"""
George Keishinge5c44392025-05-17 21:14:09 +053093 Disconnect from the remote host using the SSH client.
Peter D Phan72ce6b82021-06-03 06:18:26 -050094
George Keishinge5c44392025-05-17 21:14:09 +053095 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 """
Peter D Phan72ce6b82021-06-03 06:18:26 -0500102 if self.sshclient:
103 self.sshclient.close()
104
105 if self.scpclient:
106 self.scpclient.close()
107
Patrick Williams20f38712022-12-08 06:18:26 -0600108 def execute_command(self, command, default_timeout=60):
George Keishinge5c44392025-05-17 21:14:09 +0530109 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.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500127 """
Patrick Williams20f38712022-12-08 06:18:26 -0600128 empty = ""
Peter D Phanbabf2962021-07-07 11:24:40 -0500129 cmd_start = time.time()
Peter D Phan72ce6b82021-06-03 06:18:26 -0500130 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600131 stdin, stdout, stderr = self.sshclient.exec_command(
132 command, timeout=default_timeout
133 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500134 start = time.time()
135 while time.time() < start + default_timeout:
Peter D Phanba48e9b2021-08-12 11:35:50 -0500136 # Need to do read/write operation to trigger
137 # paramiko exec_command timeout mechanism.
Peter D Phanfd631a12021-08-12 12:58:08 -0500138 xresults = stderr.readlines()
Patrick Williams20f38712022-12-08 06:18:26 -0600139 results = "".join(xresults)
Peter D Phanfd631a12021-08-12 12:58:08 -0500140 time.sleep(1)
Peter D Phanbabf2962021-07-07 11:24:40 -0500141 if stdout.channel.exit_status_ready():
142 break
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500143 cmd_exit_code = stdout.channel.recv_exit_status()
Peter D Phan8a7ec172021-08-03 13:20:26 -0500144
145 # Convert list of string to one string
Patrick Williams20f38712022-12-08 06:18:26 -0600146 err = ""
147 out = ""
Peter D Phanba48e9b2021-08-12 11:35:50 -0500148 for item in results:
Peter D Phanfd631a12021-08-12 12:58:08 -0500149 err += item
150 for item in stdout.readlines():
Peter D Phan8a7ec172021-08-03 13:20:26 -0500151 out += item
152
153 return cmd_exit_code, err, out
Peter D Phanbabf2962021-07-07 11:24:40 -0500154
Patrick Williams20f38712022-12-08 06:18:26 -0600155 except (
156 paramiko.AuthenticationException,
157 paramiko.SSHException,
158 paramiko.ChannelException,
159 SocketTimeout,
160 ) as e:
George Keishingc754b432025-04-24 14:27:14 +0530161 # Log command with error.
162 # Return to caller for next command, if any.
Patrick Williams20f38712022-12-08 06:18:26 -0600163 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 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500175 return 0, empty, empty
Peter D Phan72ce6b82021-06-03 06:18:26 -0500176
177 def scp_connection(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500178 r"""
George Keishinge5c44392025-05-17 21:14:09 +0530179 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
Peter D Phan72ce6b82021-06-03 06:18:26 -0500186 """
Peter D Phan733df632021-06-17 13:13:36 -0500187 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600188 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 )
Peter D Phan733df632021-06-17 13:13:36 -0500195 except (SCPException, SocketTimeout, PipeTimeout) as e:
196 self.scpclient = None
Patrick Williams20f38712022-12-08 06:18:26 -0600197 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 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500207
208 def scp_file_from_remote(self, remote_file, local_file):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500209 r"""
George Keishinge5c44392025-05-17 21:14:09 +0530210 SCP a file from the remote host to the local host with a filename.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500211
George Keishinge5c44392025-05-17 21:14:09 +0530212 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.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500216
Peter D Phan72ce6b82021-06-03 06:18:26 -0500217
George Keishinge5c44392025-05-17 21:14:09 +0530218 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.
Peter D Phan72ce6b82021-06-03 06:18:26 -0500224 """
Peter D Phan72ce6b82021-06-03 06:18:26 -0500225 try:
Peter D Phan56429a62021-06-23 08:38:29 -0500226 self.scpclient.get(remote_file, local_file, recursive=True)
Peter D Phancf352e52022-02-14 13:18:01 -0600227 except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
Peter D Phan8462faf2021-06-16 12:24:15 -0500228 # Log command with error. Return to caller for next file, if any.
Peter D Phane86d9a52021-07-15 10:42:25 -0500229 logging.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600230 "\n\tERROR: Fail scp %s from remotehost %s %s\n\n"
231 % (remote_file, e.__class__, e)
232 )
George Keishingc754b432025-04-24 14:27:14 +0530233 # 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.
Peter D Phancf352e52022-02-14 13:18:01 -0600237 time.sleep(2)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500238 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500239 # Return True for file accounting
240 return True