1#!/usr/bin/env python3
2
3import paramiko
4from paramiko.ssh_exception import AuthenticationException
5from paramiko.ssh_exception import NoValidConnectionsError
6from paramiko.ssh_exception import SSHException
7from paramiko.ssh_exception import BadHostKeyException
8from paramiko.buffered_pipe import PipeTimeout as PipeTimeout
9from scp import SCPClient, SCPException
10import time
11import socket
12import logging
13from socket import timeout as SocketTimeout
14
15
16class SSHRemoteclient:
17    r"""
18    Class to create ssh connection to remote host
19    for remote host command execution and scp.
20    """
21
22    def __init__(self, hostname, username, password):
23
24        r"""
25        Description of argument(s):
26
27        hostname        Name/IP of the remote (targeting) host
28        username        User on the remote host with access to FFCD files
29        password        Password for user on remote host
30        """
31
32        self.ssh_output = None
33        self.ssh_error = None
34        self.sshclient = None
35        self.scpclient = None
36        self.hostname = hostname
37        self.username = username
38        self.password = password
39
40    def ssh_remoteclient_login(self):
41
42        r"""
43        Method to create a ssh connection to remote host.
44        """
45
46        is_ssh_login = True
47        try:
48            # SSHClient to make connections to the remote server
49            self.sshclient = paramiko.SSHClient()
50            # setting set_missing_host_key_policy() to allow any host
51            self.sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy())
52            # Connect to the server
53            self.sshclient.connect(hostname=self.hostname,
54                                   username=self.username,
55                                   password=self.password,
56                                   banner_timeout=120,
57                                   timeout=60,
58                                   look_for_keys=False)
59
60        except (BadHostKeyException, AuthenticationException,
61                SSHException, NoValidConnectionsError, socket.error) as e:
62            is_ssh_login = False
63
64        return is_ssh_login
65
66    def ssh_remoteclient_disconnect(self):
67
68        r"""
69        Clean up.
70        """
71
72        if self.sshclient:
73            self.sshclient.close()
74
75        if self.scpclient:
76            self.scpclient.close()
77
78    def execute_command(self, command,
79                        default_timeout=60):
80        """
81        Execute command on the remote host.
82
83        Description of argument(s):
84        command                Command string sent to remote host
85
86        """
87
88        empty = ''
89        cmd_start = time.time()
90        try:
91            stdin, stdout, stderr = \
92                self.sshclient.exec_command(command, timeout=default_timeout)
93            start = time.time()
94            while time.time() < start + default_timeout:
95                # Need to do read/write operation to trigger
96                # paramiko exec_command timeout mechanism.
97                xresults = stderr.readlines()
98                results = ''.join(xresults)
99                time.sleep(1)
100                if stdout.channel.exit_status_ready():
101                    break
102            cmd_exit_code = stdout.channel.recv_exit_status()
103
104            # Convert list of string to one string
105            err = ''
106            out = ''
107            for item in results:
108                err += item
109            for item in stdout.readlines():
110                out += item
111
112            return cmd_exit_code, err, out
113
114        except (paramiko.AuthenticationException, paramiko.SSHException,
115                paramiko.ChannelException, SocketTimeout) as e:
116            # Log command with error. Return to caller for next command, if any.
117            logging.error("\n\tERROR: Fail remote command %s %s" % (e.__class__, e))
118            logging.error("\tCommand '%s' Elapsed Time %s" %
119                          (command, time.strftime("%H:%M:%S", time.gmtime(time.time() - cmd_start))))
120            return 0, empty, empty
121
122    def scp_connection(self):
123
124        r"""
125        Create a scp connection for file transfer.
126        """
127        try:
128            self.scpclient = SCPClient(self.sshclient.get_transport(), sanitize=lambda x: x)
129            logging.info("\n\t[Check] %s SCP transport established.\t [OK]" % self.hostname)
130        except (SCPException, SocketTimeout, PipeTimeout) as e:
131            self.scpclient = None
132            logging.error("\n\tERROR: SCP get_transport has failed. %s %s" % (e.__class__, e))
133            logging.info("\tScript continues generating FFDC on %s." % self.hostname)
134            logging.info("\tCollected data will need to be manually offloaded.")
135
136    def scp_file_from_remote(self, remote_file, local_file):
137
138        r"""
139        scp file in remote system to local with date-prefixed filename.
140
141        Description of argument(s):
142        remote_file            Full path filename on the remote host
143
144        local_file             Full path filename on the local host
145                               local filename = date-time_remote filename
146
147        """
148
149        try:
150            self.scpclient.get(remote_file, local_file, recursive=True)
151        except (SCPException, SocketTimeout, PipeTimeout, SSHException) as e:
152            # Log command with error. Return to caller for next file, if any.
153            logging.error(
154                "\n\tERROR: Fail scp %s from remotehost %s %s\n\n" % (remote_file, e.__class__, e))
155            # Pause for 2 seconds allowing Paramiko to finish error processing before next fetch.
156            # Without the delay after SCPException,
157            #    next fetch will get 'paramiko.ssh_exception.SSHException'> Channel closed Error.
158            time.sleep(2)
159            return False
160        # Return True for file accounting
161        return True
162