pldm_visualise_pdrs: add process abstraction

Wrap the paramiko process control and access methods in a process
abstraction to facilitate new ways of running pldmtool.

Change-Id: Ibacc45dd3a09022c420b76a5828583e84ffd63e1
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/tools/visualize-pdr/pldm_visualise_pdrs.py b/tools/visualize-pdr/pldm_visualise_pdrs.py
index 32f0222..4b474e0 100755
--- a/tools/visualize-pdr/pldm_visualise_pdrs.py
+++ b/tools/visualize-pdr/pldm_visualise_pdrs.py
@@ -13,6 +13,29 @@
 import os
 
 
+class Process:
+    """ Interface definition for interacting with a process created by an
+        Executor."""
+
+    def __init__(self, stdout, stderr):
+        """ Construct a Process object.  Process object clients can read the
+            process stdout and stderr with os.read(), and can wait for the
+            process to exit.
+
+            Parameters:
+                stdout: os.read()able stream representing stdout
+                stderr: os.read()able stream representing stderr
+        """
+
+        self.stdout = stdout
+        self.stderr = stderr
+
+    def wait(self):
+        """ Wait for the process to finish, and return its exit status."""
+
+        raise NotImplementedError
+
+
 class Executor:
     """ Interface definition for interacting with executors.  An executor is an
         object that can run a program."""
@@ -24,6 +47,17 @@
         pass
 
 
+class ParamikoProcess(Process):
+    """ Concrete implementation of the Process interface that adapts Paramiko
+        interfaces to the Process interface requirements."""
+
+    def __init__(self, stdout, stderr):
+        super(ParamikoProcess, self).__init__(stdout, stderr)
+
+    def wait(self):
+        return self.stderr.channel.recv_exit_status()
+
+
 class ParamikoExecutor(Executor):
     """ Concrete implementation of the Executor interface that uses
         Paramiko to connect to a remote BMC to run the program."""
@@ -46,7 +80,8 @@
             hostname, username=uname, password=passwd, port=port, **kw)
 
     def exec_command(self, cmd):
-        return self.client.exec_command(cmd)
+        _, stdout, stderr = self.client.exec_command(cmd)
+        return ParamikoProcess(stdout, stderr)
 
     def close(self):
         self.client.close()
@@ -163,7 +198,7 @@
         return self.status
 
 
-def process_pldmtool_output(stdout_channel, stderr_channel):
+def process_pldmtool_output(process):
     """ Ensure pldmtool runs without error and if it does fail, detect that and
         show the pldmtool exit status and it's stderr.
 
@@ -180,23 +215,24 @@
               This is a pldmtool bug - re-throw the decoder error.
 
         Parameters:
-            stdout_channel: file-like stdout channel
-            stderr_channel: file-like stderr channel
+            process: A Process object providing process control functions like
+                     wait, and access functions such as reading stdout and
+                     stderr.
 
     """
 
     status = 0
     try:
-        data = json.load(stdout_channel)
+        data = json.load(process.stdout)
         # it's unlikely, but possible, that pldmtool failed but still wrote a
         # valid json document - so check for that.
-        status = stderr_channel.channel.recv_exit_status()
+        status = process.wait()
         if status == 0:
             return data
     except json.decoder.JSONDecodeError:
         # pldmtool wrote an invalid json document.  Check to see if it had
         # non-zero exit status.
-        status = stderr_channel.channel.recv_exit_status()
+        status = process.wait()
         if status == 0:
             # pldmtool didn't have non zero exit status, so it wrote an invalid
             # json document and the JSONDecodeError is the correct error.
@@ -204,7 +240,7 @@
 
     # pldmtool had a non-zero exit status, so throw an error for that, possibly
     # discarding a spurious JSONDecodeError exception.
-    raise PLDMToolError(status, "".join(stderr_channel))
+    raise PLDMToolError(status, "".join(process.stderr))
 
 
 def get_pdrs_one_at_a_time(executor):
@@ -219,9 +255,8 @@
     command_fmt = 'pldmtool platform getpdr -d {}'
     record_handle = 0
     while True:
-        output = executor.exec_command(command_fmt.format(str(record_handle)))
-        _, stdout, stderr = output
-        pdr = process_pldmtool_output(stdout, stderr)
+        process = executor.exec_command(command_fmt.format(str(record_handle)))
+        pdr = process_pldmtool_output(process)
         yield record_handle, pdr
         record_handle = pdr["nextRecordHandle"]
         if record_handle == 0:
@@ -237,12 +272,12 @@
 
     """
 
-    _, stdout, stderr = executor.exec_command('pldmtool platform getpdr -a')
-    all_pdrs = process_pldmtool_output(stdout, stderr)
+    process = executor.exec_command('pldmtool platform getpdr -a')
+    all_pdrs = process_pldmtool_output(process)
 
     # Explicitly request record 0 to find out what the real first record is.
-    _, stdout, stderr = executor.exec_command('pldmtool platform getpdr -d 0')
-    pdr_0 = process_pldmtool_output(stdout, stderr)
+    process = executor.exec_command('pldmtool platform getpdr -d 0')
+    pdr_0 = process_pldmtool_output(process)
     record_handle = pdr_0["recordHandle"]
 
     while True: