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/cli/onosclidriver.py b/TestON/drivers/common/cli/onosclidriver.py
index c317fb0..7a0641e 100755
--- a/TestON/drivers/common/cli/onosclidriver.py
+++ b/TestON/drivers/common/cli/onosclidriver.py
@@ -58,6 +58,7 @@
         self.handle = None
         self.karafUser = None
         self.karafPass = None
+        self.dockerPrompt = None
         self.graph = Graph()
         super( OnosCliDriver, self ).__init__()
 
@@ -82,9 +83,12 @@
                     self.karafUser = self.options[ key ]
                 elif key == "karaf_password":
                     self.karafPass = self.options[ key ]
+                elif key == "docker_prompt":
+                    self.dockerPrompt = self.options[ key ]
             self.home = self.checkOptions( self.home, "~/onos" )
             self.karafUser = self.checkOptions( self.karafUser, self.user_name )
             self.karafPass = self.checkOptions( self.karafPass, self.pwd )
+            self.dockerPrompt = self.checkOptions( self.dockerPrompt, "~/onos#" )
 
             for key in self.options:
                 if key == 'onosIp':
@@ -143,9 +147,15 @@
                 i = self.logout()
                 if i == main.TRUE:
                     self.handle.sendline( "" )
-                    self.handle.expect( self.prompt )
-                    self.handle.sendline( "exit" )
-                    self.handle.expect( "closed" )
+                    for l in range( 3 ):
+                        p = self.handle.expect( [ self.prompt, self.dockerPrompt ] )
+                        if p == 1:
+                            self.inDocker = False
+                        self.handle.sendline( "exit" )
+                        j = self.handle.expect( [ "closed", pexpect.TIMEOUT ], timeout=3 )
+                        if j == 0:
+                            return response
+                    response = main.FALSE
         except TypeError:
             main.log.exception( self.name + ": Object not as expected" )
             response = main.FALSE
@@ -156,7 +166,7 @@
             main.log.exception( "Exception in disconnect of " + self.name )
             response = main.TRUE
         except Exception:
-            main.log.exception( self.name + ": Connection failed to the host" )
+            main.log.exception( self.name + ": disconnection failed from the host" )
             response = main.FALSE
         return response
 
@@ -171,11 +181,14 @@
         try:
             if self.handle:
                 self.handle.sendline( "" )
-                i = self.handle.expect( [ self.karafPrompt, self.prompt, pexpect.TIMEOUT ],
+                i = self.handle.expect( [ self.karafPrompt,
+                                          self.Prompt(),
+                                          pexpect.TIMEOUT ],
+
                                         timeout=10 )
                 if i == 0:  # In ONOS CLI
                     self.handle.sendline( "logout" )
-                    j = self.handle.expect( [ self.prompt,
+                    j = self.handle.expect( [ self.Prompt(),
                                               "Command not found:",
                                               pexpect.TIMEOUT ] )
                     if j == 0:  # Successfully logged out
@@ -185,7 +198,7 @@
                         # or the command timed out
                         self.handle.send( "\x04" )  # send ctrl-d
                         try:
-                            self.handle.expect( self.prompt )
+                            self.handle.expect( self.Prompt() )
                         except pexpect.TIMEOUT:
                             main.log.error( "ONOS did not respond to 'logout' or CTRL-d" )
                         return main.TRUE
@@ -272,59 +285,49 @@
         try:
             # Check if we are already in the cli
             self.handle.sendline( "" )
-            x = self.handle.expect( [
-                self.prompt, self.karafPrompt ], commandlineTimeout )
+            x = self.handle.expect( [ self.Prompt(), self.karafPrompt ], commandlineTimeout )
             if x == 1:
                 main.log.info( "ONOS cli is already running" )
                 return main.TRUE
 
             # Not in CLI so login
-            if waitForStart:
+            if self.inDocker:
+                # The Docker does not have all the wrapper scripts
+                startCliCommand = "ssh -p 8101 -o StrictHostKeyChecking=no %s@localhost" % self.karafUser
+            elif waitForStart:
                 # Wait for onos start ( onos-wait-for-start ) and enter onos cli
-                startCliCommand = "onos-wait-for-start "
+                startCliCommand = "onos-wait-for-start " + str( ONOSIp )
             else:
-                startCliCommand = "onos "
-            self.handle.sendline( startCliCommand + str( ONOSIp ) )
-            i = self.handle.expect( [
-                self.karafPrompt,
-                pexpect.TIMEOUT ], onosStartTimeout )
+                startCliCommand = "onos " + str( ONOSIp )
+            self.handle.sendline( startCliCommand )
+            tries = 0
+            while tries < 5:
+                i = self.handle.expect( [
+                    self.karafPrompt,
+                    "Password:",
+                    pexpect.TIMEOUT ], onosStartTimeout )
 
-            if i == 0:
-                main.log.info( str( ONOSIp ) + " CLI Started successfully" )
-                if karafTimeout:  # FIXME: This doesn't look right
-                    self.handle.sendline(
-                        "config:property-set -p org.apache.karaf.shell\
-                                 sshIdleTimeout " +
-                        karafTimeout )
-                    self.handle.expect( self.prompt )
-                    self.handle.sendline( startCliCommand + str( ONOSIp ) )
-                    self.handle.expect( self.karafPrompt )
-                main.log.debug( self.handle.before )
-                return main.TRUE
-            else:
-                # If failed, send ctrl+c to process and try again
-                main.log.info( "Starting CLI failed. Retrying..." )
-                self.handle.send( "\x03" )
-                self.handle.sendline( startCliCommand + str( ONOSIp ) )
-                i = self.handle.expect( [ self.karafPrompt, pexpect.TIMEOUT ],
-                                        timeout=30 )
                 if i == 0:
-                    main.log.info( str( ONOSIp ) + " CLI Started " +
-                                   "successfully after retry attempt" )
+                    main.log.info( str( ONOSIp ) + " CLI Started successfully" )
                     if karafTimeout:
                         self.handle.sendline(
                             "config:property-set -p org.apache.karaf.shell\
-                                    sshIdleTimeout " +
+                                     sshIdleTimeout " +
                             karafTimeout )
-                        self.handle.expect( self.prompt )
-                        self.handle.sendline( startCliCommand + str( ONOSIp ) )
                         self.handle.expect( self.karafPrompt )
                     return main.TRUE
+                elif i == 1:
+                    main.log.info( str( ONOSIp ) + " CLI asking for password" )
+                    main.log.debug( "Sending %s" % self.karafPass )
+                    self.handle.sendline( self.karafPass )
                 else:
-                    main.log.error( "Connection to CLI " +
-                                    str( ONOSIp ) + " timeout" )
-                    return main.FALSE
-
+                    # If failed, send ctrl+c to process and try again
+                    main.log.info( "Starting CLI failed. Retrying..." )
+                    self.handle.send( "\x03" )
+                    self.handle.sendline( startCliCommand )
+                tries += 1
+            main.log.error( "Connection to CLI " + str( ONOSIp ) + " timeout" )
+            return main.FALSE
         except TypeError:
             main.log.exception( self.name + ": Object not as expected" )
             return None
@@ -339,7 +342,7 @@
     def startCellCli( self, karafTimeout="",
                       commandlineTimeout=10, onosStartTimeout=60 ):
         """
-        Start CLI on onos ecll handle.
+        Start CLI on onos cell handle.
 
         karafTimeout is an optional argument. karafTimeout value passed
         by user would be used to set the current karaf shell idle timeout.
@@ -357,7 +360,7 @@
         try:
             self.handle.sendline( "" )
             x = self.handle.expect( [
-                self.prompt, self.karafPrompt ], commandlineTimeout )
+                self.Prompt(), self.karafPrompt ], commandlineTimeout )
 
             if x == 1:
                 main.log.info( "ONOS cli is already running" )
@@ -376,10 +379,9 @@
                         "config:property-set -p org.apache.karaf.shell\
                                  sshIdleTimeout " +
                         karafTimeout )
-                    self.handle.expect( self.prompt )
+                    self.handle.expect( self.Prompt() )
                     self.handle.sendline( "/opt/onos/bin/onos" )
                     self.handle.expect( self.karafPrompt )
-                main.log.debug( self.handle.before )
                 return main.TRUE
             else:
                 # If failed, send ctrl+c to process and try again
@@ -396,7 +398,7 @@
                             "config:property-set -p org.apache.karaf.shell\
                                     sshIdleTimeout " +
                             karafTimeout )
-                        self.handle.expect( self.prompt )
+                        self.handle.expect( self.Prompt() )
                         self.handle.sendline( "/opt/onos/bin/onos" )
                         self.handle.expect( self.karafPrompt )
                     return main.TRUE
@@ -476,7 +478,9 @@
         try:
             # Try to reconnect if disconnected from cli
             self.handle.sendline( "" )
-            i = self.handle.expect( [ self.karafPrompt, self.prompt, pexpect.TIMEOUT ] )
+            i = self.handle.expect( [ self.karafPrompt,
+                                      self.Prompt(),
+                                      pexpect.TIMEOUT ] )
             response = self.handle.before
             if i == 1:
                 main.log.error( self.name + ": onos cli session closed. " )
@@ -495,7 +499,7 @@
                     main.cleanAndExit()
             if i == 2:
                 main.log.warn( "Timeout when testing cli responsiveness" )
-                main.log.debug( self.handle.before )
+                main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
                 self.handle.send( "\x03" )  # Send ctrl-c to clear previous output
                 self.handle.expect( self.karafPrompt )
 
@@ -505,7 +509,7 @@
                 main.log.debug( self.name + ": " + repr( response ) )
         except pexpect.TIMEOUT:
             main.log.error( self.name + ": ONOS timeout" )
-            main.log.debug( self.handle.before )
+            main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
             self.handle.send( "\x03" )
             self.handle.expect( self.karafPrompt )
             return None
@@ -558,8 +562,9 @@
                 main.log.debug( self.name + ": Raw output" )
                 main.log.debug( self.name + ": " + repr( response ) )
 
+            response = self.cleanOutput( response, debug=debug )
             # Remove control codes from karaf 4.2.1
-            karafEscape = re.compile( r"('(0|1)~\'|\r\r\r\n\x1b\[A\x1b\[79C(x|\s)?|\x1b(>|=)|\x1b\[90m~)" )
+            karafEscape = re.compile( r"('(0|1)~\'|\r\r\r\n\x1b\[A\x1b\[79C(x|\s)?|\x1b(>|=~?)|\x1b\[90m~)" )
             response = karafEscape.sub( '', response )
             if debug:
                 main.log.debug( self.name + ": karafEscape output" )
@@ -570,11 +575,15 @@
             # Remove ANSI color control strings from output
             # NOTE: karaf is sometimes adding a single character then two
             #       backspaces and sometimes adding 2 characters with 2 backspaces??
-            backspaceEscape = re.compile( r'((..\x08\x08)|(.|\s)\x08)' )
-            response = backspaceEscape.sub( '', response )
-            if debug:
-                main.log.debug( self.name + ": backspaceEscape output" )
-                main.log.debug( self.name + ": " + repr( response ) )
+            backspaceEscape = re.compile( r'((.|\s)\x08)' )
+            unchanged = False
+            while not unchanged:
+                old = response
+                response = backspaceEscape.sub( '', response, count=1 )
+                if debug:
+                    main.log.debug( self.name + ": backspaceEscape output" )
+                    main.log.debug( self.name + ": " + repr( response ) )
+                unchanged = old == response
 
             # Remove extra return chars that get added
             response = re.sub(  r"\s\r", "", response )
@@ -632,7 +641,7 @@
         except pexpect.TIMEOUT:
             main.log.error( self.name + ": ONOS timeout" )
             if debug:
-                main.log.debug( self.handle.before )
+                main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
             self.exitFromCmd( self.karafPrompt, 100 )
             return None
         except IndexError:
@@ -6713,3 +6722,100 @@
         except Exception:
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
+
+    def prepareForCLI( self, debug=True, maxRetries=120 ):
+        """
+        Prepare docker container to connect to onos cli
+        """
+        try:
+            # Wait for log files to be created
+            ready = 0
+            retries = 0
+            while not ready and retries < maxRetries:
+                retries += 1
+                self.handle.sendline( "ls -al /root/onos/apache-karaf-*/data/log" )
+                ready = self.handle.expect( [ "No such file or directory", self.dockerPrompt ] )
+                main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
+                if not ready:
+                    self.handle.expect( self.dockerPrompt )
+                time.sleep( 1 )
+            #main.log.debug( "%s: It took %s tries for onos log folder to %sbe created" %
+            #                    ( self.name, retries, "" if ready else "NOT " ) )
+
+            cmdList = []
+            cmdList.append( "apt-get update" )
+            cmdList.append( "apt-get install -y openssh-server" )
+            # Some built in scripts are hardcoded
+            cmdList.append( "ln -s /root/onos /opt/onos" )
+            cmdList.append( "ln -s /root/onos/apache-karaf-*/data/log /opt/onos/log" )
+            cmdList.append( "ls -al /opt/onos" )
+            output = ""
+            for cmdStr in cmdList:
+                self.handle.sendline( cmdStr )
+                self.handle.expect( self.dockerPrompt )
+                self.handle.sendline( "" )
+                self.handle.expect( self.dockerPrompt )
+                handle = self.handle.before
+                assert "command not found" not in handle, handle
+                assert "No such file or directory" not in handle, handle
+                output += handle
+            if debug:
+                main.log.debug( "%s: %s" % ( self.name, output ) )
+            return output
+        except AssertionError:
+            main.log.exception( "" )
+            return None
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":    " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def onosSecureSSH( self, userName="onos", userPWD="rocks" ):
+        """
+        Enables secure access to ONOS console
+        by removing default users & keys.
+
+        bin/onos-user-password onos rocks
+
+        Returns: main.TRUE on success and main.FALSE on failure
+        """
+
+        try:
+            self.handle.sendline( "" )
+            self.handle.expect( self.dockerPrompt )
+
+            self.handle.sendline( "[ ! -f ~/.ssh/id_rsa.pub ] && ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' -q" )
+            self.handle.expect( self.dockerPrompt )
+            main.log.debug( "%s: %s%s" % ( self.name, self.handle.before, self.handle.after ) )
+
+            self.handle.sendline( "bin/onos-user-key $(id -un) $(cut -d\\\\ -f2 ~/.ssh/id_rsa.pub)" )
+            self.handle.expect( pexpect.TIMEOUT, timeout=10 )
+            main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
+
+            self.handle.sendline( "bin/onos-user-password %s %s" % ( userName, userPWD ) )
+            i = self.handle.expect( [ "usage",
+                                      self.dockerPrompt,
+                                      pexpect.TIMEOUT ] )
+            if i == 0:
+                # malformed command
+                main.log.warn( self.name + ": Could not parse onos-user-password command" )
+                self.handle.expect( self.dockerPrompt )
+                return main.FALSE
+            elif i == 1:
+                # Process started
+                main.log.info( self.name + ": SSH password added for user " + userName )
+                return main.TRUE
+            elif i == 2:
+                # timeout
+                main.log.error( self.name + ": Failed to secure onos ssh " )
+                main.log.debug( "%s: %s" % ( self.name, self.handle.before ) )
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":    " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()