xref: /openbmc/openbmc-test-automation/ffdc/lib/ssh_utility.py (revision 62b0c90e54c41276e2d41aff217f93c91d9df4a7)
1#!/usr/bin/env python3
2
3import logging
4import socket
5import time
6from socket import timeout as SocketTimeout
7
8import 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
18
19class SSHRemoteclient:
20    r"""
21    Class to create ssh connection to remote host
22    for remote host command execution and scp.
23    """
24
25    def __init__(self, hostname, username, password, port_ssh):
26        r"""
27        Description of argument(s):
28
29        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
32        """
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
41        self.port_ssh = port_ssh
42
43    def ssh_remoteclient_login(self):
44        r"""
45        Method to create a ssh connection to remote host.
46        """
47
48        is_ssh_login = True
49        try:
50            # SSHClient to make connections to the remote server
51            self.sshclient = paramiko.SSHClient()
52            # setting set_missing_host_key_policy() to allow any host
53            self.sshclient.set_missing_host_key_policy(
54                paramiko.AutoAddPolicy()
55            )
56            # Connect to the server
57            self.sshclient.connect(
58                hostname=self.hostname,
59                port=self.port_ssh,
60                username=self.username,
61                password=self.password,
62                banner_timeout=120,
63                timeout=60,
64                look_for_keys=False,
65            )
66
67        except (
68            BadHostKeyException,
69            AuthenticationException,
70            SSHException,
71            NoValidConnectionsError,
72            socket.error,
73        ) as e:
74            is_ssh_login = False
75            print("SSH Login: Exception: %s" % e)
76
77        return is_ssh_login
78
79    def ssh_remoteclient_disconnect(self):
80        r"""
81        Clean up.
82        """
83
84        if self.sshclient:
85            self.sshclient.close()
86
87        if self.scpclient:
88            self.scpclient.close()
89
90    def execute_command(self, command, default_timeout=60):
91        """
92        Execute command on the remote host.
93
94        Description of argument(s):
95        command                Command string sent to remote host
96
97        """
98
99        empty = ""
100        cmd_start = time.time()
101        try:
102            stdin, stdout, stderr = self.sshclient.exec_command(
103                command, timeout=default_timeout
104            )
105            start = time.time()
106            while time.time() < start + default_timeout:
107                # Need to do read/write operation to trigger
108                # paramiko exec_command timeout mechanism.
109                xresults = stderr.readlines()
110                results = "".join(xresults)
111                time.sleep(1)
112                if stdout.channel.exit_status_ready():
113                    break
114            cmd_exit_code = stdout.channel.recv_exit_status()
115
116            # Convert list of string to one string
117            err = ""
118            out = ""
119            for item in results:
120                err += item
121            for item in stdout.readlines():
122                out += item
123
124            return cmd_exit_code, err, out
125
126        except (
127            paramiko.AuthenticationException,
128            paramiko.SSHException,
129            paramiko.ChannelException,
130            SocketTimeout,
131        ) as e:
132            # Log command with error.
133            # Return to caller for next command, if any.
134            logging.error(
135                "\n\tERROR: Fail remote command %s %s" % (e.__class__, e)
136            )
137            logging.error(
138                "\tCommand '%s' Elapsed Time %s"
139                % (
140                    command,
141                    time.strftime(
142                        "%H:%M:%S", time.gmtime(time.time() - cmd_start)
143                    ),
144                )
145            )
146            return 0, empty, empty
147
148    def scp_connection(self):
149        r"""
150        Create a scp connection for file transfer.
151        """
152        try:
153            self.scpclient = SCPClient(
154                self.sshclient.get_transport(), sanitize=lambda x: x
155            )
156            logging.info(
157                "\n\t[Check] %s SCP transport established.\t [OK]"
158                % self.hostname
159            )
160        except (SCPException, SocketTimeout, PipeTimeout) as e:
161            self.scpclient = None
162            logging.error(
163                "\n\tERROR: SCP get_transport has failed. %s %s"
164                % (e.__class__, e)
165            )
166            logging.info(
167                "\tScript continues generating FFDC on %s." % self.hostname
168            )
169            logging.info(
170                "\tCollected data will need to be manually offloaded."
171            )
172
173    def scp_file_from_remote(self, remote_file, local_file):
174        r"""
175        scp file in remote system to local with date-prefixed filename.
176
177        Description of argument(s):
178        remote_file            Full path filename on the remote host
179
180        local_file             Full path filename on the local host
181                               local filename = date-time_remote filename
182
183        """
184
185        try:
186            self.scpclient.get(remote_file, local_file, recursive=True)
187        except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
188            # Log command with error. Return to caller for next file, if any.
189            logging.error(
190                "\n\tERROR: Fail scp %s from remotehost %s %s\n\n"
191                % (remote_file, e.__class__, e)
192            )
193            # Pause for 2 seconds allowing Paramiko to finish error processing
194            # before next fetch. Without the delay after SCPException, next
195            # fetch will get 'paramiko.ssh_exception.SSHException'> Channel
196            # closed Error.
197            time.sleep(2)
198            return False
199        # Return True for file accounting
200        return True
201