xref: /openbmc/openbmc-test-automation/ffdc/lib/ssh_utility.py (revision c754b43f563d43399575778a28217af52a7f8993)
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
76        return is_ssh_login
77
78    def ssh_remoteclient_disconnect(self):
79        r"""
80        Clean up.
81        """
82
83        if self.sshclient:
84            self.sshclient.close()
85
86        if self.scpclient:
87            self.scpclient.close()
88
89    def execute_command(self, command, default_timeout=60):
90        """
91        Execute command on the remote host.
92
93        Description of argument(s):
94        command                Command string sent to remote host
95
96        """
97
98        empty = ""
99        cmd_start = time.time()
100        try:
101            stdin, stdout, stderr = self.sshclient.exec_command(
102                command, timeout=default_timeout
103            )
104            start = time.time()
105            while time.time() < start + default_timeout:
106                # Need to do read/write operation to trigger
107                # paramiko exec_command timeout mechanism.
108                xresults = stderr.readlines()
109                results = "".join(xresults)
110                time.sleep(1)
111                if stdout.channel.exit_status_ready():
112                    break
113            cmd_exit_code = stdout.channel.recv_exit_status()
114
115            # Convert list of string to one string
116            err = ""
117            out = ""
118            for item in results:
119                err += item
120            for item in stdout.readlines():
121                out += item
122
123            return cmd_exit_code, err, out
124
125        except (
126            paramiko.AuthenticationException,
127            paramiko.SSHException,
128            paramiko.ChannelException,
129            SocketTimeout,
130        ) as e:
131            # Log command with error.
132            # Return to caller for next command, if any.
133            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            )
145            return 0, empty, empty
146
147    def scp_connection(self):
148        r"""
149        Create a scp connection for file transfer.
150        """
151        try:
152            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            )
159        except (SCPException, SocketTimeout, PipeTimeout) as e:
160            self.scpclient = None
161            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            )
171
172    def scp_file_from_remote(self, remote_file, local_file):
173        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:
185            self.scpclient.get(remote_file, local_file, recursive=True)
186        except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
187            # Log command with error. Return to caller for next file, if any.
188            logging.error(
189                "\n\tERROR: Fail scp %s from remotehost %s %s\n\n"
190                % (remote_file, e.__class__, e)
191            )
192            # 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.
196            time.sleep(2)
197            return False
198        # Return True for file accounting
199        return True
200