ffdc: Add standalone FFDC collector script

- Set 1: 1st Pass: default to OpenBMC and SSH.
- Set 2: Remove trailing spaces.
- Set 3: Remove trailing spaces - part 2.
- Set 4: Address pycodestyle,
openbmc Python Coding Guidelines and review feedback issues.
- Set 5: more correction to meet pycodestyle.
- Set 6: Address review comments. Test scenarios were (re)executed.
- Set 7: Relocate all FFDC Collector files in ffdc/ dir.
Test scenarios were (re)executed with newly relocated files.
- Set 8: Make script progress, status more informative.
Add error handling, making error message more informative.
Add test for error scenarios.

Tested: 7 cli scenarios
(1) python3  <sandbox>/openbmc-test-automation/ffdc/collect_ffdc.py \
-h <> -u <> -p <> -f <sandbox>/openbmc-test-automation/ffdc/ffdc_config.yaml

(2) cd <sandbox>/openbmc-test-automation/ffdc
python3 collect_ffdc.py -h <> -u <> -p <>

(3) export OPENBMC_HOST=<>
export OPENBMC_USERNAME=<>
export OPENBMC_PASSWORD=<>
cd <sandbox>/openbmc-test-automation/ffdc
python3 collect_ffdc.py

(4) python3 collect_ffdc.py -h <invalid host>
(5) python3 collect_ffdc.py -u <invalid userid>
(6) python3 collect_ffdc.py -p <invalid password>
(7) python3 collect_ffdc.py -l <path with no access (/var, /opt)>

Change-Id: Ia26630168856341e749ce73b5cced831fc470219
Signed-off-by: Peter D  Phan <peterp@us.ibm.com>
diff --git a/ffdc/ssh_utility.py b/ffdc/ssh_utility.py
new file mode 100644
index 0000000..51e8209
--- /dev/null
+++ b/ffdc/ssh_utility.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+
+import paramiko
+from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
+from paramiko.buffered_pipe import PipeTimeout as PipeTimeout
+import scp
+import socket
+from socket import timeout as SocketTimeout
+
+
+class SSHRemoteclient:
+    r"""
+    Class to create ssh connection to remote host
+    for remote host command execution and scp.
+    """
+
+    def __init__(self, hostname, username, password):
+
+        r"""
+        Description of argument(s):
+
+        hostname                name/ip of the remote (targetting) host
+        username                user on the remote host with access to FFCD files
+        password                password for user on remote host
+        """
+
+        self.ssh_output = None
+        self.ssh_error = None
+        self.sshclient = None
+        self.scpclient = None
+        self.hostname = hostname
+        self.username = username
+        self.password = password
+
+    def ssh_remoteclient_login(self):
+
+        r"""
+        Method to create a ssh connection to remote host.
+        """
+
+        try:
+            # SSHClient to make connections to the remote server
+            self.sshclient = paramiko.SSHClient()
+            # setting set_missing_host_key_policy() to allow any host.
+            self.sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+            # Connect to the server
+            self.sshclient.connect(hostname=self.hostname,
+                                   username=self.username,
+                                   password=self.password)
+
+        except AuthenticationException:
+            print("\n>>>>>\tERROR: Authentication failed, please verify your login credentials")
+        except SSHException:
+            print("\n>>>>>\tERROR: Failures in SSH2 protocol negotiation or logic errors.")
+        except NoValidConnectionsError:
+            print('\n>>>>>\tERROR: No Valid SSH Connection after multiple attempts.')
+        except socket.error:
+            print("\n>>>>>\tERROR: SSH Connection refused.")
+        except Exception:
+            raise Exception("\n>>>>>\tERROR: Unexpected Exception.")
+
+    def ssh_remoteclient_disconnect(self):
+
+        r"""
+        Clean up.
+        """
+
+        if self.sshclient:
+            self.sshclient.close()
+
+        if self.scpclient:
+            self.scpclient.close()
+
+    def execute_command(self, command):
+        """
+        Execute command on the remote host.
+
+        Description of argument(s):
+        command                Command string sent to remote host
+
+        """
+
+        try:
+            stdin, stdout, stderr = self.sshclient.exec_command(command)
+            stdout.channel.recv_exit_status()
+            response = stdout.readlines()
+            return response
+        except (paramiko.AuthenticationException, paramiko.SSHException,
+                paramiko.ChannelException) as ex:
+            print("\n>>>>>\tERROR: Remote command execution fails.")
+
+    def scp_connection(self):
+
+        r"""
+        Create a scp connection for file transfer.
+        """
+        self.scpclient = scp.SCPClient(self.sshclient.get_transport())
+
+    def scp_file_from_remote(self, remote_file, local_file):
+
+        r"""
+        scp file in remote system to local with date-prefixed filename.
+
+        Description of argument(s):
+        remote_file            Full path filename on the remote host
+
+        local_file             Full path filename on the local host
+                               local filename = date-time_remote filename
+
+        """
+
+        try:
+            self.scpclient.get(remote_file, local_file)
+        except scp.SCPException:
+            print("scp.SCPException scp %s from remotehost" % remote_file)
+            return False
+        except (SocketTimeout, PipeTimeout) as ex:
+            # Future enhancement: multiple retries on these exceptions due to bad ssh connection
+            print("Timeout scp %s from remotehost" % remote_file)
+            return False
+
+        # Return True for file accounting
+        return True