blob: 77ad8bbbf4348b059fd1a545ed0126e201445598 [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:
George Keishingc754b432025-04-24 14:27:14 +0530131 # Log command with error.
132 # Return to caller for next command, if any.
Patrick Williams20f38712022-12-08 06:18:26 -0600133 logging.error(
134 "\n\tERROR: Fail remote command %s %s" % (e.__class__, e)
135 )
136 logging.error(
137 "\tCommand '%s' Elapsed Time %s"
138 % (
139 command,
140 time.strftime(
141 "%H:%M:%S", time.gmtime(time.time() - cmd_start)
142 ),
143 )
144 )
Peter D Phan2b6cb3a2021-07-19 06:55:42 -0500145 return 0, empty, empty
Peter D Phan72ce6b82021-06-03 06:18:26 -0500146
147 def scp_connection(self):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500148 r"""
149 Create a scp connection for file transfer.
150 """
Peter D Phan733df632021-06-17 13:13:36 -0500151 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600152 self.scpclient = SCPClient(
153 self.sshclient.get_transport(), sanitize=lambda x: x
154 )
155 logging.info(
156 "\n\t[Check] %s SCP transport established.\t [OK]"
157 % self.hostname
158 )
Peter D Phan733df632021-06-17 13:13:36 -0500159 except (SCPException, SocketTimeout, PipeTimeout) as e:
160 self.scpclient = None
Patrick Williams20f38712022-12-08 06:18:26 -0600161 logging.error(
162 "\n\tERROR: SCP get_transport has failed. %s %s"
163 % (e.__class__, e)
164 )
165 logging.info(
166 "\tScript continues generating FFDC on %s." % self.hostname
167 )
168 logging.info(
169 "\tCollected data will need to be manually offloaded."
170 )
Peter D Phan72ce6b82021-06-03 06:18:26 -0500171
172 def scp_file_from_remote(self, remote_file, local_file):
Peter D Phan72ce6b82021-06-03 06:18:26 -0500173 r"""
174 scp file in remote system to local with date-prefixed filename.
175
176 Description of argument(s):
177 remote_file Full path filename on the remote host
178
179 local_file Full path filename on the local host
180 local filename = date-time_remote filename
181
182 """
183
184 try:
Peter D Phan56429a62021-06-23 08:38:29 -0500185 self.scpclient.get(remote_file, local_file, recursive=True)
Peter D Phancf352e52022-02-14 13:18:01 -0600186 except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
Peter D Phan8462faf2021-06-16 12:24:15 -0500187 # Log command with error. Return to caller for next file, if any.
Peter D Phane86d9a52021-07-15 10:42:25 -0500188 logging.error(
Patrick Williams20f38712022-12-08 06:18:26 -0600189 "\n\tERROR: Fail scp %s from remotehost %s %s\n\n"
190 % (remote_file, e.__class__, e)
191 )
George Keishingc754b432025-04-24 14:27:14 +0530192 # Pause for 2 seconds allowing Paramiko to finish error processing
193 # before next fetch. Without the delay after SCPException, next
194 # fetch will get 'paramiko.ssh_exception.SSHException'> Channel
195 # closed Error.
Peter D Phancf352e52022-02-14 13:18:01 -0600196 time.sleep(2)
Peter D Phan72ce6b82021-06-03 06:18:26 -0500197 return False
Peter D Phan72ce6b82021-06-03 06:18:26 -0500198 # Return True for file accounting
199 return True