Allow use of onos docker for existing tests

- Allow Cluster to pull/build onos docker
- Connect clidriver to cli runnning in docker
- Some changes for debugability in components
- To use, set the useDocker and diffCliHosts tags in the cluster
  component to True, then define parameters in the params file
- Update all SR Stratum tests to use the tost docker image
- NOTE: Since the tost-onos image doesn't have openflow installe, we are
  currently only using docker for the bmv2 and tofino switches

Change-Id: If900b0bdbf9a41b8885c692ccba18a3b1bc580cc
diff --git a/TestON/drivers/common/clidriver.py b/TestON/drivers/common/clidriver.py
index 2db3914..928db71 100644
--- a/TestON/drivers/common/clidriver.py
+++ b/TestON/drivers/common/clidriver.py
@@ -33,6 +33,7 @@
     """
     def __init__( self ):
         super( CLI, self ).__init__()
+        self.inDocker = False
 
     def checkPrompt( self ):
         for key in self.options:
@@ -92,7 +93,7 @@
                                       self.prompt ],
                                     120 )
             if i == 0:  # Accept key, then expect either a password prompt or access
-                main.log.info( "ssh key confirmation received, send yes" )
+                main.log.info( self.name + ": ssh key confirmation received, send yes" )
                 self.handle.sendline( 'yes' )
                 i = 5  # Run the loop again
                 continue
@@ -101,7 +102,7 @@
                     main.log.info(
                             "ssh connection asked for password, gave password" )
                 else:
-                    main.log.info( "Server asked for password, but none was "
+                    main.log.info( self.name + ": Server asked for password, but none was "
                                     "given in the .topo file. Trying "
                                     "no password." )
                     self.pwd = ""
@@ -114,10 +115,10 @@
                                         pexpect.TIMEOUT ],
                                         120 )
                 if j != 2:
-                    main.log.error( "Incorrect Password" )
+                    main.log.error( self.name + ": Incorrect Password" )
                     return main.FALSE
             elif i == 2:
-                main.log.error( "Connection timeout" )
+                main.log.error( self.name + ": Connection timeout" )
                 return main.FALSE
             elif i == 3:  # timeout
                 main.log.error(
@@ -133,10 +134,10 @@
                     " port 22: Connection refused" )
                 return main.FALSE
             elif i == 6:  # Incorrect Password
-                main.log.error( "Incorrect Password" )
+                main.log.error( self.name + ": Incorrect Password" )
                 return main.FALSE
             elif i == 7:  # Prompt
-                main.log.info( "Password not required logged in" )
+                main.log.info( self.name + ": Password not required logged in" )
 
         self.handle.sendline( "" )
         self.handle.expect( self.prompt )
@@ -147,7 +148,12 @@
     def disconnect( self ):
         result = super( CLI, self ).disconnect( self )
         result = main.TRUE
-        # self.execute( cmd="exit",timeout=120,prompt="(.*)" )
+
+    def Prompt( self ):
+        """
+        Returns the prompt to expect depending on what program we are in
+        """
+        return self.prompt if not self.inDocker else self.dockerPrompt
 
     def execute( self, **execparams ):
         """
@@ -191,7 +197,7 @@
             self.LASTRSP = self.LASTRSP + \
                 self.handle.before + self.handle.after
             if not args[ "LOGCMD" ] is False:
-                main.log.info( "Executed :" + str( cmd ) +
+                main.log.info( self.name + ": Executed :" + str( cmd ) +
                                " \t\t Expected Prompt '" + str( expectPrompt ) +
                                "' Found" )
         elif index == 1:
@@ -209,10 +215,10 @@
                     [ "--More--", expectPrompt ], timeout=timeoutVar )
                 self.LASTRSP = self.LASTRSP + self.handle.before
         elif index == 2:
-            main.log.error( "Command not found" )
+            main.log.error( self.name + ": Command not found" )
             self.LASTRSP = self.LASTRSP + self.handle.before
         elif index == 3:
-            main.log.error( "Expected Prompt not found, Time Out!!" )
+            main.log.error( self.name + ": Expected Prompt not found, Time Out!!" )
             main.log.error( expectPrompt )
             self.LASTRSP = self.LASTRSP + self.handle.before
             return self.LASTRSP
@@ -253,7 +259,7 @@
             handle.expect( default )
 
         if i == 2:
-            main.log.error( "Unable to run as Sudo user" )
+            main.log.error( self.name + ": Unable to run as Sudo user" )
 
         return handle
 
@@ -299,7 +305,7 @@
             main.log.debug( "Wrong direction using secure copy command!" )
             return main.FALSE
 
-        main.log.info( "Sending: " + cmd )
+        main.log.info( self.name + ": Sending: " + cmd )
         self.handle.sendline( cmd )
         i = 0
         while i < 2:
@@ -315,13 +321,13 @@
                                 pexpect.TIMEOUT ],
                                 120 )
             if i == 0:  # ask for ssh key confirmation
-                main.log.info( "ssh key confirmation received, sending yes" )
+                main.log.info( self.name + ": ssh key confirmation received, sending yes" )
                 self.handle.sendline( 'yes' )
             elif i == 1:  # Asked for ssh password
-                main.log.info( "ssh connection asked for password, gave password" )
+                main.log.info( self.name + ": ssh connection asked for password, gave password" )
                 self.handle.sendline( pwd )
             elif i == 2:  # File finished transfering
-                main.log.info( "Secure copy successful" )
+                main.log.info( self.name + ": Secure copy successful" )
                 returnVal = main.TRUE
             elif i == 3:  # Connection refused
                 main.log.error(
@@ -330,15 +336,15 @@
                     " port 22: Connection refused" )
                 returnVal = main.FALSE
             elif i == 4:  # File Not found
-                main.log.error( "No such file found" )
+                main.log.error( self.name + ": No such file found" )
                 returnVal = main.FALSE
             elif i == 5:  # Permission denied
-                main.log.error( "Permission denied. Check folder permissions" )
+                main.log.error( self.name + ": Permission denied. Check folder permissions" )
                 returnVal = main.FALSE
             elif i == 6:  # prompt returned
                 return returnVal
             elif i == 7:  # EOF
-                main.log.error( "Pexpect.EOF found!!!" )
+                main.log.error( self.name + ": Pexpect.EOF found!!!" )
                 main.cleanAndExit()
             elif i == 8:  # timeout
                 main.log.error(
@@ -396,7 +402,7 @@
                                  self.prompt ],
                                120 )
             if i == 0:  # Accept key, then expect either a password prompt or access
-                main.log.info( "ssh key confirmation received, send yes" )
+                main.log.info( self.name + ": ssh key confirmation received, send yes" )
                 handle.sendline( 'yes' )
                 i = 5  # Run the loop again
                 continue
@@ -405,7 +411,7 @@
                     main.log.info(
                             "ssh connection asked for password, gave password" )
                 else:
-                    main.log.info( "Server asked for password, but none was "
+                    main.log.info( self.name + ": Server asked for password, but none was "
                                     "given in the .topo file. Trying "
                                     "no password." )
                     pwd = ""
@@ -416,10 +422,10 @@
                                      pexpect.TIMEOUT ],
                                      120 )
                 if j != 0:
-                    main.log.error( "Incorrect Password" )
+                    main.log.error( self.name + ": Incorrect Password" )
                     main.cleanAndExit()
             elif i == 2:
-                main.log.error( "Connection timeout" )
+                main.log.error( self.name + ": Connection timeout" )
                 main.cleanAndExit()
             elif i == 3:  # timeout
                 main.log.error(
@@ -435,30 +441,30 @@
                     " port 22: Connection refused" )
                 main.cleanAndExit()
             elif i == 6:
-                main.log.info( "Password not required logged in" )
+                main.log.info( self.name + ": Password not required logged in" )
 
         handle.sendline( "" )
         handle.expect( self.prompt )
         handle.sendline( "cd" )
         handle.expect( self.prompt )
 
-        main.log.info( "Successfully ssh to " + ipAddress + "." )
+        main.log.info( self.name + ": Successfully ssh to " + ipAddress + "." )
         return handle
 
     def exitFromSsh( self, handle, ipAddress ):
         try:
             handle.sendline( "logout" )
             handle.expect( "closed." )
-            main.log.info( "Successfully closed ssh connection from " + ipAddress )
+            main.log.info( self.name + ": Successfully closed ssh connection from " + ipAddress )
         except pexpect.EOF:
-            main.log.error( "Failed to close the connection from " + ipAddress )
+            main.log.error( self.name + ": Failed to close the connection from " + ipAddress )
         try:
             # check that this component handle still works
             self.handle.sendline( "" )
             self.handle.expect( self.prompt )
         except pexpect.EOF:
             main.log.error( self.handle.before )
-            main.log.error( "EOF after closing ssh connection" )
+            main.log.error( self.name + ": EOF after closing ssh connection" )
 
     def folderSize( self, path, size='10', unit='M', ignoreRoot=True ):
         """
@@ -548,7 +554,8 @@
                 cmd = "unset {}".format( variable )
             self.handle.sendline( cmd )
             self.handle.expect( self.prompt )
-            main.log.debug( self.handle.before )
+            output = self.handle.before
+            main.log.debug( output )
             return True
         except AssertionError:
             main.log.error( self.name + ": Could not execute command: " + output )
@@ -604,3 +611,315 @@
             main.log.debug( self.name + ": cleanOutput:" )
             main.log.debug( self.name + ": " + repr( cleaned ) )
         return cleaned
+
+    def dockerPull( self, image, tag=None ):
+        """
+        Pull a docker image from a registry
+        """
+        try:
+            imgStr = "%s%s" % ( image, ":%s" % tag if tag else "" )
+            cmdStr = "docker pull %s" % imgStr
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.prompt,
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ], 120 )
+            if i == 0:
+                return main.TRUE
+            else:
+                main.log.error( self.name + ": Error pulling docker image " + imgStr  )
+                output = self.handle.before + str( self.handle.after )
+                if i == 1:
+                    self.handle.expect( self.prompt )
+                    output += self.handle.before + str( self.handle.after )
+                main.log.debug( self.name + ": " + output )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerBuild( self, path, imageTag, pull=False, options="", timeout=600 ):
+        """
+        Build a docker image
+        Required Arguments:
+         - path: Path to the dockerfile, it is recommended to avoid relative paths
+         - imageTag: Give a tag to the built docker image
+        Optional Arguments:
+         - pull: Whether to attempt to pull latest images before building
+         - options: A string containing any addition optional arguments
+                    for the docker build command
+         - timeout: How many seconds to wait for the build to complete
+        """
+        try:
+            response = main.TRUE
+            if pull:
+                options = "--pull " + options
+            cmdStr = "docker build -t %s %s %s" % ( imageTag, options, path )
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ "Successfully built",
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ], timeout=timeout )
+            output = self.handle.before
+            if i == 0:
+                output += self.handle.after
+                self.handle.expect( self.prompt )
+                output += self.handle.before + self.handle.after
+                return response
+            elif i == 1:
+                response = main.FALSE
+                output += self.handle.after
+                self.handle.expect( self.prompt )
+                output += self.handle.before + self.handle.after
+            elif i == 2:
+                response = main.FALSE
+            main.log.error( self.name + ": Error building docker image" )
+            main.log.debug( self.name + ": " + output )
+            return response
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerStop( self, containerName ):
+        """
+        Stop a docker container
+        Required Arguments:
+         - containerName: Name of the container to stop
+        """
+        try:
+            cmdStr = "docker stop %s" % ( containerName )
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.prompt,
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ], 120 )
+            output = self.handle.before
+            if i == 0:
+                return main.TRUE
+            elif i == 1:
+                output += self.handle.after
+                self.handle.expect( self.prompt )
+                output += self.handle.before
+            elif i == 2:
+                pass
+            main.log.debug( "%s: %s" % ( self.name, output ) )
+            if "No such container" in output:
+                return main.TRUE
+            main.log.error( self.name + ": Error stopping docker image" )
+            main.log.debug( self.name + ": " + output )
+            return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerRun( self, image, containerName, options="", imageArgs="" ):
+        """
+        Run a docker image
+        Required Arguments:
+         - containerName: Give a name to the container once its started
+         - image: Run the given image
+        Optional Arguments:
+         - options: A string containing any addition optional arguments
+                    for the docker run command
+        - imageArgs: A string containing command line arguments for the
+                     command run by docker
+        """
+        try:
+            cmdStr = "docker run --name %s %s %s %s" % ( containerName,
+                                                         options if options else "",
+                                                         image,
+                                                         imageArgs )
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.prompt,
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ], 120 )
+            if i == 0:
+                return main.TRUE
+            else:
+                output = self.handle.before
+                main.log.debug( self.name + ": " + output )
+                main.log.error( self.name + ": Error running docker image" )
+                if i == 1:
+                    output += self.handle.after
+                    self.handle.expect( self.prompt )
+                    output += self.handle.before + self.handle.after
+                main.log.debug( self.name + ": " + output )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerAttach( self, containerName, dockerPrompt="" ):
+        """
+        Attach to a docker image
+        Required Arguments:
+         - containerName: The name of the container to attach to
+        Optional Arguments:
+         - dockerPrompt: a regex for matching the docker shell prompt
+        """
+        try:
+            if dockerPrompt:
+                self.dockerPrompt = dockerPrompt
+            cmdStr = "docker attach %s" % containerName
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.dockerPrompt,
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ] )
+            if i == 0:
+                self.inDocker = True
+                return main.TRUE
+            else:
+                main.log.error( self.name + ": Error connecting to docker container" )
+                output = self.handle.before + str( self.handle.after )
+                if i == 1:
+                    self.handle.expect( self.prompt )
+                    output += self.handle.before + str( self.handle.after )
+                main.log.debug( self.name + ": " + output )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except AttributeError as e:
+            main.log.exception( self.name + ": AttributeError - " + str( e ) )
+            main.log.warn( self.name + ": Make sure dockerPrompt is set" )
+            main.cleanup()
+            main.exit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerExec( self, containerName, command="/bin/bash", options="-it", dockerPrompt="" ):
+        """
+        Attach to a docker image
+        Required Arguments:
+         - containerName: The name of the container to attach to
+        Optional Arguments:
+         - command: Command to run in the docker container
+         - options: Docker exec options
+         - dockerPrompt: a regex for matching the docker shell prompt
+        """
+        try:
+            if dockerPrompt:
+                self.dockerPrompt = dockerPrompt
+            cmdStr = "docker exec %s %s %s" % ( options, containerName, command )
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.dockerPrompt,
+                                      "Error response from daemon",
+                                      pexpect.TIMEOUT ] )
+            if i == 0:
+                self.inDocker = True
+                return main.TRUE
+            else:
+                main.log.error( self.name + ": Error connecting to docker container" )
+                output = self.handle.before + str( self.handle.after )
+                if i == 1:
+                    self.handle.expect( self.prompt )
+                    output += self.handle.before + str( self.handle.after )
+                main.log.debug( self.name + ": " + output )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except AttributeError as e:
+            main.log.exception( self.name + ": AttributeError - " + str( e ) )
+            main.log.warn( self.name + ": Make sure dockerPrompt is set" )
+            main.cleanup()
+            main.exit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerCp( self, containerName, dockerPath, hostPath, direction="from" ):
+        """
+        Copy a file from/to a docker container to the host
+        Required Arguments:
+         - containerName: The name of the container to copy from/to
+         - dockerPath: the path in the container to copy from/to
+         - hostPath: the path on the host to copy to/from
+        Optional Arguments:
+         - direction: Choose whether to copy "from" the container or "to" the container
+        """
+        try:
+            cmdStr = "docker cp "
+            if direction == "from":
+                cmdStr += "%s:%s %s" % ( containerName, dockerPath, hostPath )
+            elif direction == "to":
+                cmdStr += "%s %s:%s" % ( hostPath, containerName, dockerPath )
+            main.log.info( self.name + ": sending: " + cmdStr )
+            self.handle.sendline( cmdStr)
+            i = self.handle.expect( [ self.prompt,
+                                      "Error",
+                                      pexpect.TIMEOUT ] )
+            if i == 0:
+                retValue = main.TRUE
+            else:
+                main.log.error( self.name + ": Error in docker cp" )
+                output = self.handle.before + str( self.handle.after )
+                if i == 1:
+                    self.handle.expect( self.prompt )
+                    output += self.handle.before + str( self.handle.after )
+                main.log.debug( self.name + ": " + output )
+                retValue = main.FALSE
+            return retValue
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except AttributeError as e:
+            main.log.exception( self.name + ": AttributeError - " + str( e ) )
+            main.log.warn( self.name + ": Make sure dockerPrompt is set" )
+            main.cleanup()
+            main.exit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE
+
+    def dockerDisconnect( self ):
+        """
+        Send ctrl-c, ctrl-d to session, which should close and exit the
+        attached docker session. This will likely exit the running program
+        in the container and also stop the container.
+        """
+        try:
+            cmdStr = "\x03"
+            main.log.info( self.name + ": sending: " + repr( cmdStr ) )
+            self.handle.send( cmdStr)
+            cmdStr = "\x04"
+            main.log.info( self.name + ": sending: " + repr( cmdStr ) )
+            self.handle.send( cmdStr)
+            i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ] )
+            if i == 0:
+                self.inDocker = False
+                return main.TRUE
+            else:
+                main.log.error( self.name + ": Error disconnecting from docker image" )
+                main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return main.FALSE