blob: 17e1dea47bc95f269ae540a6fce4443c1dcc612c [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"""
27 Description of argument(s):
28
Peter D Phan8462faf2021-06-16 12:24:15 -050029 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
Peter D Phan72ce6b82021-06-03 06:18:26 -050032 """
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
George Keishing7a61aa22023-06-26 13:18:37 +053041 self.port_ssh = port_ssh
Peter D Phan72ce6b82021-06-03 06:18:26 -050042
43 def ssh_remoteclient_login(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -050044 r"""
45 Method to create a ssh connection to remote host.
46 """
47
Peter D Phan5963d632021-07-12 09:58:55 -050048 is_ssh_login = True
Peter D Phan72ce6b82021-06-03 06:18:26 -050049 try:
50 # SSHClient to make connections to the remote server
51 self.sshclient = paramiko.SSHClient()
Peter D Phan8462faf2021-06-16 12:24:15 -050052 # setting set_missing_host_key_policy() to allow any host
Patrick Williams20f38712022-12-08 06:18:26 -060053 self.sshclient.set_missing_host_key_policy(
54 paramiko.AutoAddPolicy()
55 )
Peter D Phan72ce6b82021-06-03 06:18:26 -050056 # Connect to the server
Patrick Williams20f38712022-12-08 06:18:26 -060057 self.sshclient.connect(
58 hostname=self.hostname,
George Keishing7a61aa22023-06-26 13:18:37 +053059 port=self.port_ssh,
Patrick Williams20f38712022-12-08 06:18:26 -060060 username=self.username,
61 password=self.password,
62 banner_timeout=120,
63 timeout=60,
64 look_for_keys=False,
65 )
Peter D Phan72ce6b82021-06-03 06:18:26 -050066
Patrick Williams20f38712022-12-08 06:18:26 -060067 except (
68 BadHostKeyException,
69 AuthenticationException,
70 SSHException,
71 NoValidConnectionsError,
72 socket.error,
73 ) as e:
Peter D Phan5963d632021-07-12 09:58:55 -050074 is_ssh_login = False
75
76 return is_ssh_login
Peter D Phan72ce6b82021-06-03 06:18:26 -050077
78 def ssh_remoteclient_disconnect(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -050079 r"""
80 Clean up.
81 """
82
83 if self.sshclient:
84 self.sshclient.close()
85
86 if self.scpclient:
87 self.scpclient.close()
88
Patrick Williams20f38712022-12-08 06:18:26 -060089 def execute_command(self, command, default_timeout=60):
Peter D Phan72ce6b82021-06-03 06:18:26 -050090 """
91 Execute command on the remote host.
92
93 Description of argument(s):
94 command Command string sent to remote host
95
96 """
97
Patrick Williams20f38712022-12-08 06:18:26 -060098 empty = ""
Peter D Phanbabf2962021-07-07 11:24:40 -050099 cmd_start = time.time()
Peter D Phan72ce6b82021-06-03 06:18:26 -0500100 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600101 stdin, stdout, stderr = self.sshclient.exec_command(
102 command, timeout=default_timeout
103 )
Peter D Phanbabf2962021-07-07 11:24:40 -0500104 start = time.time()
105 while time.time() < start + default_timeout:
Peter D Phanba48e9b2021-08-12 11:35:50 -0500106 # Need to do read/write operation to trigger
107 # paramiko exec_command timeout mechanism.
Peter D Phanfd631a12021-08-12 12:58:08 -0500108 xresults = stderr.readlines()
Patrick Williams20f38712022-12-08 06:18:26 -0600109 results = "".join(xresults)
Peter D Phanfd631a12021-08-12 12:58:08 -0500110 time.sleep(1)
Peter D Phanbabf2962021-07-07 11:24:40 -0500111 if stdout.channel.exit_status_ready():
112 break
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500113 cmd_exit_code = stdout.channel.recv_exit_status()
Peter D Phan8a7ec172021-08-03 13:20:26 -0500114
115 # Convert list of string to one string
Patrick Williams20f38712022-12-08 06:18:26 -0600116 err = ""
117 out = ""
Peter D Phanba48e9b2021-08-12 11:35:50 -0500118 for item in results:
Peter D Phanfd631a12021-08-12 12:58:08 -0500119 err += item
120 for item in stdout.readlines():
Peter D Phan8a7ec172021-08-03 13:20:26 -0500121 out += item
122
123 return cmd_exit_code, err, out
Peter D Phanbabf2962021-07-07 11:24:40 -0500124
Patrick Williams20f38712022-12-08 06:18:26 -0600125 except (
126 paramiko.AuthenticationException,
127 paramiko.SSHException,
128 paramiko.ChannelException,
129 SocketTimeout,
130 ) as e:
Peter D Phan8462faf2021-06-16 12:24:15 -0500131 # Log command with error. Return to caller for next command, if any.
Patrick Williams20f38712022-12-08 06:18:26 -0600132 logging.error(
133 "\n\tERROR: Fail remote command %s %s" % (e.__class__, e)
134 )
135 logging.error(
136 "\tCommand '%s' Elapsed Time %s"
137 % (
138 command,
139 time.strftime(
140 "%H:%M:%S", time.gmtime(time.time() - cmd_start)
141 ),
142 )
143 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500144 return 0, empty, empty
Peter D Phan72ce6b82021-06-03 06:18:26 -0500145
146 def scp_connection(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500147 r"""
148 Create a scp connection for file transfer.
149 """
Peter D Phan733df632021-06-17 13:13:36 -0500150 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600151 self.scpclient = SCPClient(
152 self.sshclient.get_transport(), sanitize=lambda x: x
153 )
154 logging.info(
155 "\n\t[Check] %s SCP transport established.\t [OK]"
156 % self.hostname
157 )
Peter D Phan733df632021-06-17 13:13:36 -0500158 except (SCPException, SocketTimeout, PipeTimeout) as e:
159 self.scpclient = None
Patrick Williams20f38712022-12-08 06:18:26 -0600160 logging.error(
161 "\n\tERROR: SCP get_transport has failed. %s %s"
162 % (e.__class__, e)
163 )
164 logging.info(
165 "\tScript continues generating FFDC on %s." % self.hostname
166 )
167 logging.info(
168 "\tCollected data will need to be manually offloaded."
169 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500170
171 def scp_file_from_remote(self, remote_file, local_file):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500172 r"""
173 scp file in remote system to local with date-prefixed filename.
174
175 Description of argument(s):
176 remote_file Full path filename on the remote host
177
178 local_file Full path filename on the local host
179 local filename = date-time_remote filename
180
181 """
182
183 try:
Peter D Phan56429a62021-06-23 08:38:29 -0500184 self.scpclient.get(remote_file, local_file, recursive=True)
Peter D Phancf352e52022-02-14 13:18:01 -0600185 except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
Peter D Phan8462faf2021-06-16 12:24:15 -0500186 # Log command with error. Return to caller for next file, if any.
Peter D Phane86d9a52021-07-15 10:42:25 -0500187 logging.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600188 "\n\tERROR: Fail scp %s from remotehost %s %s\n\n"
189 % (remote_file, e.__class__, e)
190 )
Peter D Phancf352e52022-02-14 13:18:01 -0600191 # Pause for 2 seconds allowing Paramiko to finish error processing before next fetch.
192 # Without the delay after SCPException,
193 # next fetch will get 'paramiko.ssh_exception.SSHException'> Channel closed Error.
194 time.sleep(2)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500195 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500196 # Return True for file accounting
197 return True