[ONOS-7765] Run SRMulticast test case 1 on Flex POD

Change-Id: I854d0ddf55754fd89ae8b29709e1589674c0a5fc
diff --git a/TestON/drivers/common/cli/emulator/scapyclidriver.py b/TestON/drivers/common/cli/emulator/scapyclidriver.py
index 0dd3b6b..6a24aa2 100644
--- a/TestON/drivers/common/cli/emulator/scapyclidriver.py
+++ b/TestON/drivers/common/cli/emulator/scapyclidriver.py
@@ -86,7 +86,7 @@
                                self.user_name +
                                "@" +
                                self.ip_address )
-                return main.TRUE
+                return self.handle
             else:
                 main.log.error( "Connection failed to the host " +
                                 self.user_name +
@@ -135,7 +135,7 @@
                       'bind_layers(MPLS, IP)' ]
 
         try:
-            self.handle.sendline( "scapy" )
+            self.handle.sendline( "sudo scapy" )
             self.handle.expect( self.scapyPrompt )
             self.handle.sendline( "conf.color_theme = NoTheme()" )
             self.handle.expect( self.scapyPrompt )
diff --git a/TestON/drivers/common/cli/hostdriver.py b/TestON/drivers/common/cli/hostdriver.py
index 4156902..86c0c38 100644
--- a/TestON/drivers/common/cli/hostdriver.py
+++ b/TestON/drivers/common/cli/hostdriver.py
@@ -27,9 +27,9 @@
 import os
 import time
 from math import pow
-from drivers.common.clidriver import CLI
+from drivers.common.cli.emulator.scapyclidriver import ScapyCliDriver
 
-class HostDriver( CLI ):
+class HostDriver( ScapyCliDriver ):
     """
     This class is created as a standalone host driver.
     """
@@ -39,6 +39,9 @@
         self.name = None
         self.shortName = None
         self.home = None
+        self.inband = False
+        self.prompt = "\$"
+        self.scapyPrompt = ">>>"
 
     def connect( self, **connectargs ):
         """
@@ -111,9 +114,7 @@
                 self.handle.sendline( "exit" )
                 i = self.handle.expect( [ "closed", pexpect.TIMEOUT ], timeout=2 )
                 if i == 1:
-                    main.log.error(
-                        self.name +
-                        ": timeout when waiting for response" )
+                    main.log.error( self.name + ": timeout when waiting for response" )
                     main.log.error( "response: " + str( self.handle.before ) )
         except TypeError:
             main.log.exception( self.name + ": Object not as expected" )
@@ -129,6 +130,70 @@
             response = main.FALSE
         return response
 
+    def connectInband( self ):
+        """
+        ssh to the host using its data plane IP
+        """
+        try:
+            if not self.options[ 'inband' ] == 'True':
+                main.log.info( "Skip connecting the host via data plane" )
+                return main.TRUE
+            self.handle.sendline( "" )
+            self.handle.expect( self.prompt )
+            self.handle.sendline( "ssh {}@{}".format( self.options[ 'username' ],
+                                                      self.options[ 'ip' ] ) )
+            i = self.handle.expect( [ "password:|Password:", self.prompt, pexpect.TIMEOUT ], timeout=30 )
+            if i == 0:
+                self.handle.sendline( self.options[ 'password' ] )
+                j = self.handle.expect( [ "password:|Password:", self.prompt, pexpect.TIMEOUT ], timeout=10 )
+                if j != 1:
+                    main.log.error( "Incorrect password" )
+                    return main.FALSE
+            elif i == 1:
+                main.log.info( "Password not required logged in" )
+            else:
+                main.log.error( "Failed to connect to the host" )
+                return main.FALSE
+            self.inband = True
+            return main.TRUE
+        except KeyError:
+            main.log.error( self.name + ": host component not as expected" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            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!" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+
+    def disconnectInband( self ):
+        """
+        Terminate the ssh connection to the host's data plane IP
+        """
+        try:
+            if not self.options[ 'inband' ] == 'True':
+                main.log.info( "Skip disconnecting the host via data plane" )
+                return main.TRUE
+            self.handle.sendline( "" )
+            self.handle.expect( self.prompt )
+            self.handle.sendline( "exit" )
+            i = self.handle.expect( [ "closed", pexpect.TIMEOUT ], timeout=2 )
+            if i == 1:
+                main.log.error( self.name + ": timeout when waiting for response" )
+                main.log.error( "response: " + str( self.handle.before ) )
+            return main.TRUE
+        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!" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return main.FALSE
+
     def ping( self, dst, ipv6=False, wait=3 ):
         """
         Description:
@@ -185,8 +250,6 @@
                     self.name +
                     ": timeout when waiting for response" )
                 main.log.error( "response: " + str( self.handle.before ) )
-            self.handle.sendline( "" )
-            self.handle.expect( self.prompt )
             response = self.handle.before
             return response
         except pexpect.EOF:
@@ -194,5 +257,52 @@
             main.log.error( self.name + ":     " + self.handle.before )
             main.cleanAndExit()
         except Exception:
-            main.log.exception( self.name + ": Uncaught exception!" )
+            main.log.exception( self.name + ": uncaught exception!" )
+            main.cleanAndExit()
+
+    def ip( self, options="a", wait=3 ):
+        """
+        Run ip command on host and return output
+        """
+        try:
+            command = "ip {}".format( options )
+            main.log.info( self.name + ": Sending: " + command )
+            self.handle.sendline( command )
+            i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ],
+                                    timeout=wait + 5 )
+            if i == 1:
+                main.log.error( self.name + ": timeout when waiting for response" )
+                main.log.error( "response: " + str( self.handle.before ) )
+            response = self.handle.before
+            return response
+        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 command( self, cmd, wait=3 ):
+        """
+        Run shell command on host and return output
+        Required:
+            cmd: command to run on the host
+        """
+        try:
+            main.log.info( self.name + ": Sending: " + cmd )
+            self.handle.sendline( cmd )
+            i = self.handle.expect( [ self.prompt, pexpect.TIMEOUT ],
+                                    timeout=wait + 5 )
+            if i == 1:
+                main.log.error( self.name + ": timeout when waiting for response" )
+                main.log.error( "response: " + str( self.handle.before ) )
+            response = self.handle.before
+            return response
+        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()
diff --git a/TestON/drivers/common/cli/networkdriver.py b/TestON/drivers/common/cli/networkdriver.py
index 38d7531..f0d7f8b 100755
--- a/TestON/drivers/common/cli/networkdriver.py
+++ b/TestON/drivers/common/cli/networkdriver.py
@@ -29,6 +29,7 @@
 """
 import pexpect
 import os
+import re
 import types
 from drivers.common.clidriver import CLI
 
@@ -123,26 +124,154 @@
             for key, value in main.componentDictionary.items():
                 if hasattr( main, key ):
                     if value[ 'type' ] in [ 'MininetSwitchDriver', 'OFDPASwitchDriver' ]:
-                        self.switches[ key ] = getattr( main, key )
+                        component = getattr( main, key )
+                        shortName = component.options[ 'shortName' ]
+                        localName = self.name + "-" + shortName
+                        self.copyComponent( key, localName )
+                        self.switches[ shortName ] = getattr( main, localName )
                     elif value[ 'type' ] in [ 'MininetHostDriver', 'HostDriver' ]:
-                        self.hosts[ key ] = getattr( main, key )
+                        component = getattr( main, key )
+                        shortName = component.options[ 'shortName' ]
+                        localName = self.name + "-" + shortName
+                        self.copyComponent( key, localName )
+                        self.hosts[ shortName ] = getattr( main, localName )
+            main.log.debug( self.name + ": found switches: {}".format( self.switches ) )
+            main.log.debug( self.name + ": found hosts: {}".format( self.hosts ) )
             return main.TRUE
         except Exception:
             main.log.error( self.name + ": failed to connect to network" )
             return main.FALSE
 
-    def getHosts( self, verbose=False, updateTimeout=1000 ):
+    def disconnectFromNet( self ):
         """
-        Return a dictionary which maps short names to host data
+        Disconnect from the physical network connected
         """
-        hosts = {}
         try:
-            for hostComponent in self.hosts.values():
-                # TODO: return more host data
-                hosts[ hostComponent.options[ 'shortName' ] ] = {}
+            for key, value in main.componentDictionary.items():
+                if hasattr( main, key ) and key.startswith( self.name + "-" ):
+                    self.removeComponent( key )
+            self.switches = {}
+            self.hosts = {}
+            return main.TRUE
         except Exception:
-            main.log.error( self.name + ": host component not as expected" )
-        return hosts
+            main.log.error( self.name + ": failed to disconnect from network" )
+            return main.FALSE
+
+    def copyComponent( self, name, newName ):
+        """
+        Copy the component initialized from the .topo file
+        The copied components are only supposed to be called within this driver
+        Required:
+            name: name of the component to be copied
+            newName: name of the new component
+        """
+        try:
+            main.componentDictionary[ newName ] = main.componentDictionary[ name ].copy()
+            main.componentInit( newName )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def removeHostComponent( self, name ):
+        """
+        Remove host component
+        Required:
+            name: name of the component to be removed
+        """
+        try:
+            self.removeComponent( name )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def removeComponent( self, name ):
+        """
+        Remove host/switch component
+        Required:
+            name: name of the component to be removed
+        """
+        try:
+            component = getattr( main, name )
+        except AttributeError:
+            main.log.error( "Component " + name + " does not exist." )
+            return main.FALSE
+        try:
+            # Disconnect from component
+            component.disconnect()
+            # Delete component
+            delattr( main, name )
+            # Delete component from ComponentDictionary
+            del( main.componentDictionary[ name ] )
+            return main.TRUE
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def createComponent( self, name ):
+        """
+        Creates switch/host component with the same parameters as the one copied to local.
+        Arguments:
+            name - The string of the name of this component. The new component
+                   will be assigned to main.<name> .
+                   In addition, main.<name>.name = str( name )
+        """
+        try:
+            # look to see if this component already exists
+            getattr( main, name )
+        except AttributeError:
+            # namespace is clear, creating component
+            localName = self.name + "-" + name
+            main.componentDictionary[ name ] = main.componentDictionary[ localName ].copy()
+            main.componentInit( name )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+        else:
+            # namespace is not clear!
+            main.log.error( name + " component already exists!" )
+            main.cleanAndExit()
+
+    def connectInbandHosts( self ):
+        """
+        Connect to hosts using data plane IPs specified
+        """
+        result = main.TRUE
+        try:
+            for hostName, hostComponent in self.hosts.items():
+                if hostComponent.options[ 'inband' ] == 'True':
+                    main.log.info( self.name + ": connecting inband host " + hostName )
+                    result = hostComponent.connectInband() and result
+            return result
+        except Exception:
+            main.log.error( self.name + ": failed to connect to inband hosts" )
+            return main.FALSE
+
+    def disconnectInbandHosts( self ):
+        """
+        Terminate the connections to hosts using data plane IPs
+        """
+        result = main.TRUE
+        try:
+            for hostName, hostComponent in self.hosts.items():
+                if hostComponent.options[ 'inband' ] == 'True':
+                    main.log.info( self.name + ": disconnecting inband host " + hostName )
+                    result = hostComponent.disconnectInband() and result
+            return result
+        except Exception:
+            main.log.error( self.name + ": failed to disconnect inband hosts" )
+            return main.FALSE
+
+    def getSwitches( self ):
+        """
+        Return a dictionary which maps short names to switch component
+        """
+        return self.switches
+
+    def getHosts( self ):
+        """
+        Return a dictionary which maps short names to host component
+        """
+        return self.hosts
 
     def getMacAddress( self, host ):
         """
@@ -150,7 +279,8 @@
         """
         import re
         try:
-            hostComponent = self.getHostComponentByShortName( host )
+            hosts = self.getHosts()
+            hostComponent = hosts[ host ]
             response = hostComponent.ifconfig()
             pattern = r'HWaddr\s([0-9A-F]{2}[:-]){5}([0-9A-F]{2})'
             macAddressSearch = re.search( pattern, response, re.I )
@@ -160,24 +290,17 @@
         except Exception:
             main.log.error( self.name + ": failed to get host MAC address" )
 
-    def getSwitchComponentByShortName( self, shortName ):
+    def runCmdOnHost( self, hostName, cmd ):
         """
-        Get switch component by its short name i.e. "s1"
+        Run shell command on specified host and return output
+        Required:
+            hostName: name of the host e.g. "h1"
+            cmd: command to run on the host
         """
-        for switchComponent in self.switches.values():
-            if switchComponent.options[ 'shortName' ] == shortName:
-                return switchComponent
-        main.log.warn( self.name + ": failed to find switch component by name " + shortName )
-        return None
-
-    def getHostComponentByShortName( self, shortName ):
-        """
-        Get host component by its short name i.e. "h1"
-        """
-        for hostComponent in self.hosts.values():
-            if hostComponent.options[ 'shortName' ] == shortName:
-                return hostComponent
-        main.log.warn( self.name + ": failed to find host component by name " + shortName )
+        hosts = self.getHosts()
+        hostComponent = hosts[ hostName ]
+        if hostComponent:
+            return hostComponent.command( cmd )
         return None
 
     def assignSwController( self, sw, ip, port="6653", ptcp="" ):
@@ -243,7 +366,7 @@
             index = 0
             for switch in switchList:
                 assigned = False
-                switchComponent = self.getSwitchComponentByShortName( switch )
+                switchComponent = self.switches[ switch ]
                 if switchComponent:
                     ptcp = ptcpList[ index ] if ptcpList else ""
                     assignResult = assignResult and switchComponent.assignSwController( ip=ip, port=port, ptcp=ptcp )
@@ -281,7 +404,8 @@
             returnValue = main.TRUE
             ipv6 = True if protocol == "IPv6" else False
             startTime = time.time()
-            hostPairs = itertools.permutations( list( self.hosts.values() ), 2 )
+            hosts = self.getHosts()
+            hostPairs = itertools.permutations( list( hosts.values() ), 2 )
             for hostPair in list( hostPairs ):
                 ipDst = hostPair[ 1 ].options[ 'ip6' ] if ipv6 else hostPair[ 1 ].options[ 'ip' ]
                 pingResult = hostPair[ 0 ].ping( ipDst, ipv6=ipv6 )
@@ -322,8 +446,9 @@
         import time
         import itertools
         hostComponentList = []
+        hosts = self.getHosts()
         for hostName in hostList:
-            hostComponent = self.getHostComponentByShortName( hostName )
+            hostComponent = hosts[ hostName ]
             if hostComponent:
                 hostComponentList.append( hostComponent )
         try:
@@ -361,3 +486,80 @@
         main.log.info( self.name + ": Simple iperf TCP test between two hosts" )
         # TODO: complete this function
         return main.TRUE
+
+    def update( self ):
+        return main.TRUE
+
+    def verifyHostIp( self, hostList=[], prefix="", update=False ):
+        """
+        Description:
+            Verify that all hosts have IP address assigned to them
+        Optional:
+            hostList: If specified, verifications only happen to the hosts
+            in hostList
+            prefix: at least one of the ip address assigned to the host
+            needs to have the specified prefix
+        Returns:
+            main.TRUE if all hosts have specific IP address assigned;
+            main.FALSE otherwise
+        """
+        try:
+            hosts = self.getHosts()
+            if not hostList:
+                hostList = hosts.keys()
+            for hostName, hostComponent in hosts.items():
+                if hostName not in hostList:
+                    continue
+                ipList = []
+                ipa = hostComponent.ip()
+                ipv4Pattern = r'inet ((?:[0-9]{1,3}\.){3}[0-9]{1,3})/'
+                ipList += re.findall( ipv4Pattern, ipa )
+                # It's tricky to make regex for IPv6 addresses and this one is simplified
+                ipv6Pattern = r'inet6 ((?:[0-9a-fA-F]{1,4})?(?:[:0-9a-fA-F]{1,4}){1,7}(?:::)?(?:[:0-9a-fA-F]{1,4}){1,7})/'
+                ipList += re.findall( ipv6Pattern, ipa )
+                main.log.debug( self.name + ": IP list on host " + str( hostName ) + ": " + str( ipList ) )
+                if not ipList:
+                    main.log.warn( self.name + ": Failed to discover any IP addresses on host " + str( hostName ) )
+                else:
+                    if not any( ip.startswith( str( prefix ) ) for ip in ipList ):
+                        main.log.warn( self.name + ": None of the IPs on host " + str( hostName ) + " has prefix " + str( prefix ) )
+                    else:
+                        main.log.debug( self.name + ": Found matching IP on host " + str( hostName ) )
+                        hostList.remove( hostName )
+            return main.FALSE if hostList else main.TRUE
+        except KeyError:
+            main.log.exception( self.name + ": host data not as expected: " + hosts )
+            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" )
+            return None
+
+    def addRoute( self, host, dstIP, interface, ipv6=False ):
+        """
+        Add a route to host
+        Ex: h1 route add -host 224.2.0.1 h1-eth0
+        """
+        try:
+            if ipv6:
+                cmd = "sudo route -A inet6 add "
+            else:
+                cmd = "sudo route add -host "
+            cmd += str( dstIP ) + " " + str( interface )
+            response = self.runCmdOnHost( host, cmd )
+            main.log.debug( "response = " + response )
+            return main.TRUE
+        except pexpect.TIMEOUT:
+            main.log.error( self.name + ": TIMEOUT exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        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!" )
+            main.cleanAndExit()
diff --git a/TestON/drivers/common/cli/ofdpa/ofdpaswitchdriver.py b/TestON/drivers/common/cli/ofdpa/ofdpaswitchdriver.py
index b080c96..ac909ee 100644
--- a/TestON/drivers/common/cli/ofdpa/ofdpaswitchdriver.py
+++ b/TestON/drivers/common/cli/ofdpa/ofdpaswitchdriver.py
@@ -56,9 +56,12 @@
             for key in connectargs:
                 vars( self )[ key ] = connectargs[ key ]
             # Get the name
-            self.name = self.options['name']
+            self.name = self.options[ 'name' ]
             # Get the dpid
             self.dpid = self.options[ 'dpid' ]
+            # Get ofagent patch
+            if 'confDir' in self.options:
+                self.switchDirectory = self.options[ 'confDir' ]
             # Parse the IP address
             try:
                 if os.getenv( str( self.ip_address ) ) is not None:
@@ -135,7 +138,7 @@
             response = main.FALSE
             return response
 
-    def assignSwController( self, ip, port="6653", ptcp=""):
+    def assignSwController( self, ip, port="6653", ptcp="", updateConf=False ):
         """
         Description:
             The assignment is realized properly creating the agent.conf
@@ -145,6 +148,9 @@
         Optional:
             port - controller port is ignored
             ptcp - ptcp information is ignored
+            updateConf - create new ofagent conf file and push to the switch if
+                         set to True; otherwise will use the existing conf file
+                         on the switch.
         Return:
             Returns main.TRUE if the switch is correctly assigned to controllers,
             otherwise it will return main.FALSE or an appropriate exception(s)
@@ -165,22 +171,23 @@
                 return main.FALSE
             # Complete the arguments adding the dpid
             opt_args += onosIp + '-i %s' % self.dpid + '"'
-            # Create a copy of the cfg file using the template
-            self.createCfg()
-            # Load the cfg file and adds the missing option
-            self.updateCfg( opt_args )
-            # Backup the cfg on the switch
-            self.backupCfg()
-            # Push the new cfg on the device
-            self.pushCfg()
-            # Start the ofagent on the device
+            if updateConf:
+                # Create a copy of the cfg file using the template
+                self.createCfg()
+                # Load the cfg file and adds the missing option
+                self.updateCfg( opt_args )
+                # Backup the cfg on the switch
+                self.backupCfg()
+                # Push the new cfg on the device
+                self.pushCfg()
+                # Start the ofagent on the device
             self.startOfAgent()
             # Enable all the ports
             assignResult = utilities.retry(
                 self.enablePorts,
                 main.FALSE,
                 kwargs={},
-                attempts=5,
+                attempts=10,
                 sleep=10)
             # Done return true
             return assignResult
@@ -317,11 +324,13 @@
         self.handle.expect( self.prompt )
         response = self.handle.before
         if "Error from ofdpaClientInitialize()" in response:
-            main.log.warn(
-                self.name +
-                ": Not yet started" )
+            main.log.warn( self.name + ": Not yet started" )
             return main.FALSE
-        main.log.info( self.name + ": started" )
-        self.handle.sendline( "sh portspeed.sh" )
+        # Change port speed
+        self.handle.sendline( "sh portspeed" )
         self.handle.expect( self.prompt )
+        response = self.handle.before
+        if "Failure calling" in response:
+            main.log.warn( self.name + ": failed to change port speed" )
+            return main.FALSE
         return main.TRUE
diff --git a/TestON/drivers/common/cli/onosclusterdriver.py b/TestON/drivers/common/cli/onosclusterdriver.py
index 5983855..b8320ba 100755
--- a/TestON/drivers/common/cli/onosclusterdriver.py
+++ b/TestON/drivers/common/cli/onosclusterdriver.py
@@ -376,6 +376,9 @@
         main.componentDictionary[name]['host'] = ipAddress
         home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
         main.componentDictionary[name]['home'] = self.checkOptions( home, None )
+        # TODO: for now we use karaf user name and password also for logging to the onos nodes
+        main.componentDictionary[name]['user'] = self.karafUser
+        main.componentDictionary[name]['password'] = self.karafPass
         main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
         main.log.debug( main.componentDictionary[name] )