Stratum Support for Segement Rounting Suite

- Add Stratum Bmv2 switch support for SRDhcpRelay and SRRouting tests
- Add Support for 0x1 topology on a Stratum Tofino HW switch
- Support for dockerized mininet with Stratum BMv2 switches
- Update scapy driver to work with newer versions of scapy
- Simple parsing for scapy ping output
- Add support for fetching and installing external onos apps
- Add support for onos-diagnostics profiles
- Move onos log levels to params file
- Add onos cfg settings to SR tests

Change-Id: I7c4a71484c8fd5735da9ef09b96d8990283b199b
(cherry picked from commit bef6d9bd943996483fed32130cb30ad26a06aac0)
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/Dockerfile b/TestON/tests/USECASE/SegmentRouting/dependencies/Dockerfile
index 1843055..4b714c5 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/Dockerfile
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/Dockerfile
@@ -1,6 +1,32 @@
 FROM opennetworking/mn-stratum
 
-RUN install_packages python-pip openvswitch-switch
-#RUN apt-get update && apt-get install -y python-pip
+ENV HOME /home/root
+WORKDIR $HOME
+RUN ln -s /root/* .
+RUN chmod 777 $HOME
+
+RUN install_packages python-pip openvswitch-switch vim quagga isc-dhcp-server isc-dhcp-client iptables vlan
 RUN pip install ipaddress
+
+RUN ln -s $HOME /var/run/quagga
+RUN ln -s /usr/sbin/zebra /usr/lib/quagga/zebra
+RUN ln -s /usr/sbin/bgpd /usr/lib/quagga/bgpd
+
+# try to ensure dhclient can write pid files
+RUN chmod 777 /run
+RUN ls -al $HOME
+# Issue with Uubuntu/Apparmour
+RUN mv /sbin/dhclient /usr/local/bin/ \
+&& touch /var/lib/dhcp/dhcpd.leases
+
+# Install custom mininet branch
+run install_packages git sudo lsb-release
+RUN git clone https://github.com/jhall11/mininet.git \
+&& cd mininet \
+&& git branch -v -a \
+&& git checkout -b dynamic_topo origin/dynamic_topo \
+&& cd util \
+&& alias sudo='' \
+&& apt-get update \
+&& ./install.sh -3fvn
 ENTRYPOINT bash
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
index efd8868..dd2b870 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
@@ -49,6 +49,7 @@
             main.cleanAndExit()
         from tests.dependencies.Network import Network
         main.Network = Network()
+        main.physicalNet = False
         main.testSetUp.envSetupDescription( False )
         stepResult = main.FALSE
         try:
@@ -63,6 +64,11 @@
                 main.useBmv2 = main.params[ 'DEPENDENCY' ][ 'useBmv2' ] == 'True'
             else:
                 main.useBmv2 = False
+            if main.useBmv2:
+                main.switchType = main.params[ 'DEPENDENCY' ].get( 'bmv2SwitchType', 'stratum' )
+            else:
+                main.switchType = "ovs"
+
             main.configPath = main.path + ( "/.." if main.useCommonConf else "" ) + "/dependencies/"
             main.bmv2Path = "/tools/dev/mininet/"
             main.forJson = "json/"
@@ -103,8 +109,16 @@
         - Connect to cli
         """
         # main.scale[ 0 ] determines the current number of ONOS controller
-        if not main.apps:
-            main.log.error( "App list is empty" )
+        try:
+            if main.params.get( 'EXTERNAL_APPS' ):
+                for app, url in main.params[ 'EXTERNAL_APPS' ].iteritems():
+                    main.log.info( "Downloading %s app from %s" )
+                    main.ONOSbench.onosFetchApp( url )
+            if not main.apps:
+                main.log.error( "App list is empty" )
+        except Exception as e:
+            main.log.debug( e )
+            main.cleanAndExit()
         main.log.info( "Cluster size: " + str( main.Cluster.numCtrls ) )
         main.log.info( "Cluster ips: " + ', '.join( main.Cluster.getIps() ) )
         main.dynamicHosts = [ 'in1', 'out1' ]
@@ -135,37 +149,35 @@
         if not appInstallResult:
             main.cleanAndExit()
 
-        for ctrl in main.Cluster.active():
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.segmentrouting" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.driver" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.net.flowobjective.impl" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.routeservice.impl" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.routeservice.store" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.routing.fpm" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.fpm" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.events" )
-            ctrl.CLI.logSet( "DEBUG", "org.onosproject.mcast" )
+        # FIXME: move to somewhere else?
+        switchPrefix = main.params[ 'DEPENDENCY' ].get( 'switchPrefix' )
+        # TODO: Support other pipeconfs/making this configurable
+        if switchPrefix == "tofino":
+            # It seems to take some time for the pipeconfs to be loaded
+            ctrl = main.Cluster.next()
+            for i in range( 120 ):
+                try:
+                    main.log.debug( "Checking to see if pipeconfs are loaded" )
+                    output = ctrl.CLI.sendline( "pipeconfs" )
+                    if "tofino" in output:
+                        main.log.debug( "Took around %s seconds for the pipeconf to be loaded" % i )
+                        break
+                    time.sleep( 1 )
+                except Exception as e:
+                    main.log.error( e )
 
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.p4runtime" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.protocols.p4runtime" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.drivers.p4runtime" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.protocols.grpc" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.protocols.gnmi" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.protocols.gnoi" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.drivers.gnoi" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.drivers.gmni" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.drivers.stratum" )
-            ctrl.CLI.logSet( "TRACE", "org.onosproject.bmv2" )
+        Testcaselib.setOnosLogLevels( main )
+        Testcaselib.setOnosConfig( main )
 
     @staticmethod
     def loadCount( main ):
-        with open("%s/count/%s.count" % (main.configPath, main.cfgName)) as count:
-                main.count = json.load(count)
+        with open( "%s/count/%s.count" % ( main.configPath, main.cfgName ) ) as count:
+                main.count = json.load( count )
 
     @staticmethod
-    def loadJson( main ):
-        with open( "%s%s.json" % ( main.configPath + main.forJson,
-                                   main.cfgName ) ) as cfg:
+    def loadJson( main, suffix='' ):
+        with open( "%s%s.json%s" % ( main.configPath + main.forJson,
+                                     main.cfgName, suffix ) ) as cfg:
             main.Cluster.active( 0 ).REST.setNetCfg( json.load( cfg ) )
 
     @staticmethod
@@ -173,7 +185,7 @@
         try:
             with open( "%s%s.chart" % ( main.configPath + main.forChart,
                                         main.cfgName ) ) as chart:
-                main.pingChart = json.load(chart)
+                main.pingChart = json.load( chart )
         except IOError:
             main.log.warn( "No chart file found." )
 
@@ -203,6 +215,7 @@
 
     @staticmethod
     def startMininet( main, topology, args="" ):
+        main.log.info( "Copying mininet topology file to mininet machine" )
         copyResult = main.ONOSbench.scp( main.Mininet1,
                                          main.topoPath + main.topology,
                                          main.Mininet1.home + "custom",
@@ -217,6 +230,22 @@
             import re
             controllerIPs = [ ctrl.ipAddress for ctrl in main.Cluster.runningNodes ]
             index = 0
+            destDir = "~/"
+            if 'MN_DOCKER' in main.params and main.params['MN_DOCKER']['args']:
+                destDir = "/tmp/mn_conf/"
+                # Try to ensure the destination exists
+                main.log.info( "Create folder for network config files" )
+                handle = main.Mininet1.handle
+                handle.sendline( "mkdir -p %s" % destDir )
+                handle.expect( [ main.Mininet1.prompt, main.Mininet1.dockerPrompt ] )
+                main.log.debug( handle.before + handle.after )
+                # Make sure permissions are correct
+                handle.sendline( "sudo chown %s:%s %s" % ( main.Mininet1.user_name, main.Mininet1.user_name, destDir ) )
+                handle.expect( [ main.Mininet1.prompt, main.Mininet1.dockerPrompt ] )
+                main.log.debug( handle.before + handle.after )
+                handle.sendline( "sudo chmod -R a+rwx %s" % ( destDir ) )
+                handle.expect( [ main.Mininet1.prompt, main.Mininet1.dockerPrompt ] )
+                main.log.debug( handle.before + handle.after )
             for conf in main.topologyConf.split(","):
                 # Update zebra configurations with correct ONOS instance IP
                 if conf in [ "zebradbgp1.conf", "zebradbgp2.conf" ]:
@@ -229,7 +258,7 @@
                         f.write( s )
                 copyResult = copyResult and main.ONOSbench.scp( main.Mininet1,
                                                                 main.configPath + main.forConfig + conf,
-                                                                "~/",
+                                                                destDir,
                                                                 direction="to" )
         copyResult = copyResult and main.ONOSbench.scp( main.Mininet1,
                                                         main.ONOSbench.home + main.bmv2Path + main.bmv2,
@@ -246,8 +275,6 @@
         main.step( "Starting Mininet Topology" )
         arg = "--onos-ip=%s %s" % (",".join([ctrl.ipAddress for ctrl in main.Cluster.runningNodes]), args)
         main.topology = topology
-        #switchType = " --switch=stratum"
-        #arg += switchType
         topoResult = main.Mininet1.startNet(
                 topoFile=main.Mininet1.home + "custom/" + main.topology, args=arg )
         stepResult = topoResult
@@ -258,25 +285,73 @@
         # Exit if topology did not load properly
         if not topoResult:
             main.cleanAndExit()
+        if main.useBmv2:
+            # Upload the net-cfg file created for each switch
+            filename = "onos-netcfg.json"
+            switchPrefix = main.params[ 'DEPENDENCY' ].get( 'switchPrefix', "bmv2" )
+            for switch in main.Mininet1.getSwitches( switchRegex=r"(StratumBmv2Switch)|(Bmv2Switch)" ).keys():
+                path = "/tmp/mn-stratum/%s/" % switch
+                dstPath = "/tmp/"
+                dstFileName = "%s-onos-netcfg.json" % switch
+                main.ONOSbench1.scp( main.Mininet1,
+                                     "%s%s" % ( path, filename ),
+                                     "%s%s" % ( dstPath, dstFileName ),
+                                     "from" )
+                main.ONOSbench1.handle.sendline( "sudo sed -i 's/localhost/%s/g' %s%s" % ( main.Mininet1.ip_address, dstPath, dstFileName ) )
+                # Configure managementAddress
+                main.ONOSbench1.handle.sendline( "sudo sed -i 's/localhost/%s/g' %s%s" % ( main.Mininet1.ip_address, dstPath, dstFileName ) )
+                main.ONOSbench1.handle.expect( main.ONOSbench1.prompt )
+                main.log.debug( main.ONOSbench1.handle.before + main.ONOSbench1.handle.after )
+                # Configure device id
+                main.ONOSbench1.handle.sendline( "sudo sed -i 's/device:%s/device:%s:%s/g' %s%s" % ( switch, switchPrefix, switch, dstPath, dstFileName ) )
+                main.ONOSbench1.handle.expect( main.ONOSbench1.prompt )
+                main.log.debug( main.ONOSbench1.handle.before + main.ONOSbench1.handle.after )
+                # Configure device name
+                main.ONOSbench1.handle.sendline( "sudo sed -i '/\"basic\"/a\        \"name\": \"%s\",' %s%s" % ( switch, dstPath, dstFileName ) )
+                main.ONOSbench1.handle.expect( main.ONOSbench1.prompt )
+                main.log.debug( main.ONOSbench1.handle.before + main.ONOSbench1.handle.after )
+                main.ONOSbench1.onosNetCfg( main.ONOSserver1.ip_address, dstPath, dstFileName )
+        # Make sure hosts make some noise
+        Testcaselib.discoverHosts( main )
+
+    @staticmethod
+    def discoverHosts( main ):
+        # TODO add option to only select specific hosts
+        if hasattr( main, "Mininet1" ):
+            network = main.Mininet1
+        elif hasattr( main, "NetworkBench" ):
+            network = main.NetworkBench
+        else:
+            main.log.warn( "Could not find component for test network, skipping host discovery" )
+            return
+        network.discoverHosts()
 
     @staticmethod
     def connectToPhysicalNetwork( main ):
         main.step( "Connecting to physical netowrk" )
+        main.physicalNet = True
         topoResult = main.NetworkBench.connectToNet()
         stepResult = topoResult
         utilities.assert_equals( expect=main.TRUE,
                                  actual=stepResult,
-                                 onpass="Successfully loaded topology",
-                                 onfail="Failed to load topology" )
+                                 onpass="Successfully connected to topology",
+                                 onfail="Failed to connect to topology" )
         # Exit if topology did not load properly
         if not topoResult:
             main.cleanAndExit()
 
+        # Perform any optional setup
+        for switch in main.NetworkBench.switches:
+            if hasattr( switch, "setup" ):
+                switch.setup()  # We might not need this
+
         main.step( "Assign switches to controllers." )
-        assignResult = main.TRUE
+        stepResult = main.TRUE
         switches = main.NetworkBench.getSwitches()
         pool = []
         for name in switches.keys():
+            # NOTE: although this terminology is ovsdb centric, we can use this function for other switches too
+            #       e.g. push onos net-cfg for stratum switches
             thread = main.Thread( target=main.NetworkBench.assignSwController,
                                   name="assignSwitchToController",
                                   args=[ name, main.Cluster.getIps(), '6653' ] )
@@ -293,7 +368,6 @@
 
         # Check devices
         Testcaselib.checkDevices( main, switches=int( main.params[ 'TOPO' ][ 'switchNum' ] ) )
-        time.sleep( float( main.params[ "timers" ][ "connectToNetSleep" ] ) )
         # Connecting to hosts that only have data plane connectivity
         main.step( "Connecting inband hosts" )
         stepResult = main.Network.connectInbandHosts()
@@ -301,6 +375,7 @@
                                  actual=stepResult,
                                  onpass="Successfully connected inband hosts",
                                  onfail="Failed to connect inband hosts" )
+        Testcaselib.discoverHosts( main )
 
     @staticmethod
     def saveOnosDiagnostics( main ):
@@ -408,8 +483,8 @@
         utilities.assertEquals(
                 expect=True,
                 actual=( count > minFlowCount ),
-                onpass="Flow count looks correct: " + str( count ),
-                onfail="Flow count looks wrong: " + str( count ) )
+                onpass="Flow count looks correct; found %s, expecting at least %s" % ( count, minFlowCount ),
+                onfail="Flow count looks wrong; found %s, expecting at least %s" % ( count, minFlowCount ) )
 
         main.step( "Check whether all flow status are ADDED" )
         flowCheck = utilities.retry( main.Cluster.active( 0 ).CLI.checkFlowsState,
@@ -450,7 +525,7 @@
     @staticmethod
     def checkFlowsByDpid( main, dpid, minFlowCount, sleep=10 ):
         main.step(
-            " Check whether the flow count of device %s is bigger than %s" % ( dpid, minFlowCount ) )
+            "Check whether the flow count of device %s is bigger than %s" % ( dpid, minFlowCount ) )
         count = utilities.retry( main.Cluster.active( 0 ).CLI.checkFlowAddedCount,
                                  main.FALSE,
                                  args=( dpid, minFlowCount ),
@@ -467,7 +542,7 @@
     @staticmethod
     def checkFlowEqualityByDpid( main, dpid, flowCount, sleep=10 ):
         main.step(
-                " Check whether the flow count of device %s is equal to %s" % ( dpid, flowCount ) )
+                "Check whether the flow count of device %s is equal to %s" % ( dpid, flowCount ) )
         count = utilities.retry( main.Cluster.active( 0 ).CLI.checkFlowAddedCount,
                                  main.FALSE,
                                  args=( dpid, flowCount, False, 1 ),
@@ -484,7 +559,7 @@
     @staticmethod
     def checkGroupEqualityByDpid( main, dpid, groupCount, sleep=10):
         main.step(
-                " Check whether the group count of device %s is equal to %s" % ( dpid, groupCount ) )
+                "Check whether the group count of device %s is equal to %s" % ( dpid, groupCount ) )
         count = utilities.retry( main.Cluster.active( 0 ).CLI.checkGroupAddedCount,
                                  main.FALSE,
                                  args=( dpid, groupCount, False, 1),
@@ -547,6 +622,12 @@
                         utilities.assert_equals( expect=expect, actual=pa,
                                                  onpass="IPv6 connectivity successfully tested",
                                                  onfail="IPv6 connectivity failed" )
+                elif main.physicalNet:
+                    pa = main.NetworkBench.pingallHostsUnidirectional( src, dst, acceptableFailed=acceptableFailed, useScapy=True )
+                    utilities.assert_equals( expect=expect, actual=pa,
+                                             onpass="IP connectivity successfully tested",
+                                             onfail="IP connectivity failed" )
+
                 else:
                     pa = main.Network.pingallHostsUnidirectional( src, dst, acceptableFailed=acceptableFailed )
                     utilities.assert_equals( expect=expect, actual=pa,
@@ -564,7 +645,8 @@
                     if ("v4" in hosts[0]):
                         pa = utilities.retry( main.Network.pingallHosts,
                                               main.FALSE if expect else main.TRUE,
-                                              args=(hosts,),
+                                              args=(hosts, ),
+                                              kwargs={ 'ipv6': False },
                                               attempts=retryAttempts,
                                               sleep=sleep )
                         utilities.assert_equals( expect=expect, actual=pa,
@@ -575,13 +657,19 @@
                         utilities.assert_equals( expect=expect, actual=pa,
                                                  onpass="IPv6 connectivity successfully tested",
                                                  onfail="IPv6 connectivity failed" )
+                elif main.physicalNet:
+                    pa = main.Network.pingallHosts( hosts, ipv6=True, useScapy=True )
+                    utilities.assert_equals( expect=expect, actual=pa,
+                                             onpass="IP connectivity successfully tested",
+                                             onfail="IP connectivity failed" )
                 else:
                     pa = main.Network.pingallHosts( hosts )
                     utilities.assert_equals( expect=expect, actual=pa,
                                              onpass="IP connectivity successfully tested",
                                              onfail="IP connectivity failed" )
-            if skipOnFail and pa != expect:
+            if pa != expect:
                 Testcaselib.saveOnosDiagnostics( main )
+            if skipOnFail and pa != expect:
                 Testcaselib.cleanup( main, copyKarafLog=False )
                 main.skipCase()
 
@@ -764,6 +852,8 @@
             if portUp:
                 ctrl.CLI.portstate( dpid=dpid1, port=port1, state='Enable' )
                 ctrl.CLI.portstate( dpid=dpid2, port=port2, state='Enable' )
+                main.log.info(
+                        "Waiting %s seconds for link up to be discovered" % sleep )
                 time.sleep( sleep )
 
             result = ctrl.CLI.checkStatus( numoswitch=switches,
@@ -919,6 +1009,7 @@
 
         for ctrl in main.Cluster.active():
             main.ONOSbench.onosStop( ctrl.ipAddress )
+        Testcaselib.mnDockerTeardown( main )
 
     @staticmethod
     def verifyNodes( main ):
@@ -985,6 +1076,7 @@
                                      onfail="Error killing ONOS instance" )
             main.Cluster.runningNodes[ i ].active = False
         main.Cluster.reset()
+        main.log.debug( "sleeping %i seconds" % ( sleep ) )
         time.sleep( sleep )
 
         if len( nodes ) < main.Cluster.numCtrls:
@@ -1004,6 +1096,7 @@
         else:
             sleep = float( sleep )
         [ main.ONOSbench.onosStart( main.Cluster.runningNodes[ i ].ipAddress ) for i in nodes ]
+        main.log.debug( "sleeping %i seconds" % ( sleep ) )
         time.sleep( sleep )
         for i in nodes:
             isUp = main.ONOSbench.isup( main.Cluster.runningNodes[ i ].ipAddress )
@@ -1135,7 +1228,7 @@
                                                      main.FALSE,
                                                      kwargs={ 'hostList': [ hostName ],
                                                               'prefix': ip,
-                                                              'update': False },
+                                                              'update': True },
                                                      attempts=attempts,
                                                      sleep=sleep )
         utilities.assert_equals( expect=main.TRUE, actual=ipResult,
@@ -1224,7 +1317,16 @@
                     mininetName = mininetNames[ scapyNames.index( scapyName ) ]
                 else:
                     mininetName = None
-                scapyHandle.startHostCli( mininetName )
+                if 'MN_DOCKER' in main.params and main.params['MN_DOCKER']['args']:
+                    scapyHandle.mExecDir = "/tmp"
+                    scapyHandle.hostHome = main.params[ "MN_DOCKER" ][ "home" ]
+                    main.log.debug( "start mn host component in docker" )
+                    scapyHandle.startHostCli( mininetName,
+                                              execDir="/tmp",
+                                              hostHome=main.params[ "MN_DOCKER" ][ "home" ] )
+                else:
+                    main.log.debug( "start mn host component" )
+                    scapyHandle.startHostCli( mininetName )
             else:
                 main.Network.createHostComponent( scapyName )
                 scapyHandle = getattr( main, scapyName )
@@ -1416,6 +1518,7 @@
             main.Cluster.active( 0 ).REST.setNetCfg( json.loads( cfg ),
                                                      subjectClass="ports" )
             # Wait for the host to get RA for setting up default gateway
+            main.log.debug( "sleeping %i seconds" % ( 5 ) )
             time.sleep( 5 )
 
         main.Mininet1.discoverHosts( [ hostName, ] )
@@ -1466,6 +1569,7 @@
             main.Cluster.active( 0 ).REST.setNetCfg( json.loads( cfg ),
                                                      subjectClass="ports" )
             # Wait for the host to get RA for setting up default gateway
+            main.log.debug( "sleeping %i seconds" % ( 5 ) )
             time.sleep( 5 )
 
         main.Mininet1.discoverHosts( [ hostName, ] )
@@ -1479,3 +1583,114 @@
                         vlan = hostName.split( "/" )[ -1 ]
                         del main.expectedHosts[ "onos" ][ hostName ]
                         main.expectedHosts[ "onos" ][ "{}/{}".format( macAddr.upper(), vlan ) ] = ip
+
+    @staticmethod
+    def mnDockerSetup( main ):
+        """
+        Optionally start and setup docker image for mininet
+        """
+        if 'MN_DOCKER' in main.params and main.params['MN_DOCKER']['args']:
+
+            main.log.info( "Creating Mininet Docker" )
+            handle = main.Mininet1.handle
+            main.Mininet1.dockerPrompt = '#'
+
+            confDir = "/tmp/mn_conf/"
+            # Try to ensure the destination exists
+            main.log.info( "Create folder for network config files" )
+            handle.sendline( "mkdir -p %s" % confDir )
+            handle.expect( main.Mininet1.prompt )
+            main.log.debug( handle.before + handle.after )
+            # Make sure permissions are correct
+            handle.sendline( "sudo chown %s:%s %s" % ( main.Mininet1.user_name, main.Mininet1.user_name, confDir ) )
+            handle.sendline( "sudo chmod -R a+rwx %s" % ( confDir ) )
+            handle.expect( main.Mininet1.prompt )
+            main.log.debug( handle.before + handle.after )
+            # Start docker container
+            handle.sendline( "docker run --name trellis_mininet %s %s" % ( main.params[ 'MN_DOCKER' ][ 'args' ], main.params[ 'MN_DOCKER' ][ 'name' ] ) )
+            handle.expect( main.Mininet1.bashPrompt )
+            output = handle.before + handle.after
+            main.log.debug( repr(output) )
+
+            handle.sendline( "docker attach trellis_mininet" )
+            handle.expect( main.Mininet1.dockerPrompt )
+            main.log.debug( handle.before + handle.after )
+            handle.sendline( "sysctl -w net.ipv4.ip_forward=0" )
+            handle.sendline( "sysctl -w net.ipv4.conf.all.forwarding=0" )
+            handle.expect( main.Mininet1.dockerPrompt )
+            main.log.debug( handle.before + handle.after )
+            # We should be good to go
+            main.Mininet1.prompt = main.Mininet1.dockerPrompt
+            main.Mininet1.sudoRequired = False
+
+            # Fow when we create component handles
+            main.Mininet1.mExecDir = "/tmp"
+            main.Mininet1.hostHome = main.params[ "MN_DOCKER" ][ "home" ]
+            main.Mininet1.hostPrompt = "/home/root#"
+
+    @staticmethod
+    def mnDockerTeardown( main ):
+        """
+        Optionally stop and cleanup docker image for mininet
+        """
+
+        if hasattr( main, 'Mininet1' ):
+            if 'MN_DOCKER' in main.params and main.params['MN_DOCKER']['args']:
+                main.log.info( "Deleting Mininet Docker" )
+
+                # Detach from container
+                handle = main.Mininet1.handle
+                try:
+                    handle.sendline( "exit" )  # ctrl-p ctrk-q  to detach from container
+                    main.log.debug( "sleeping %i seconds" % ( 5 ) )
+                    time.sleep(5)
+                    handle.expect( main.Mininet1.dockerPrompt )
+                    main.log.debug( handle.before + handle.after )
+                    main.Mininet1.prompt = main.Mininet1.bashPrompt
+                    handle.expect( main.Mininet1.prompt )
+                    main.log.debug( handle.before + handle.after )
+                    main.Mininet1.sudoRequired = True
+                except Exception as e:
+                    main.log.error( e )
+
+    @staticmethod
+    def setOnosConfig( main ):
+        """
+        Read and Set onos configurations from the params file
+        """
+        main.step( "Set ONOS configurations" )
+        config = main.params.get( 'ONOS_Configuration' )
+        if config:
+            main.log.debug( config )
+            checkResult = main.TRUE
+            for component in config:
+                for setting in config[ component ]:
+                    value = config[ component ][ setting ]
+                    check = main.Cluster.next().setCfg( component, setting, value )
+                    main.log.info( "Value was changed? {}".format( main.TRUE == check ) )
+                    checkResult = check and checkResult
+            utilities.assert_equals( expect=main.TRUE,
+                                     actual=checkResult,
+                                     onpass="Successfully set config",
+                                     onfail="Failed to set config" )
+        else:
+            main.log.warn( "No configurations were specified to be changed after startup" )
+
+    @staticmethod
+    def setOnosLogLevels( main ):
+        """
+        Read and Set onos log levels from the params file
+        """
+        main.step( 'Set logging levels' )
+        logging = True
+        try:
+            logs = main.params.get( 'ONOS_Logging', False )
+            if logs:
+                for namespace, level in logs.items():
+                    for ctrl in main.Cluster.active():
+                        ctrl.CLI.logSet( level, namespace )
+        except AttributeError:
+            logging = False
+        utilities.assert_equals( expect=True, actual=logging,
+                                 onpass="Set log levels",
+                                 onfail="Failed to set log levels" )
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py b/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py
index f38603e..5e95efe 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/cfgtranslator.py
@@ -28,6 +28,7 @@
 
 # Translate configuration JSON file from BMv2 driver to OFDPA-OVS driver.
 def bmv2ToOfdpa( main, cfgFile="" ):
+    didRE = r"device:(?P<swType>bmv2|tofino):(?P<swRole>leaf|spine)(?P<swNum>[1-9][0-9]*)(/(?P<portNum>[0-9]+))?"
     if not cfgFile:
         cfgFile = "%s%s.json" % ( main.configPath + main.forJson,
                                   main.cfgName )
@@ -36,9 +37,9 @@
 
     if 'ports' in netcfg.keys():
         for port in netcfg[ 'ports' ].keys():
-            searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", port )
+            searchObj = re.search( didRE, port )
             if searchObj:
-                new_port = 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 )
+                new_port = 'of:' + searchObj.group( 'swNum' ).zfill( 16 ) + '/' + searchObj.group( 'portNum' )
                 netcfg[ 'ports' ][ new_port ] = netcfg[ 'ports' ].pop( port )
 
     if 'hosts' in netcfg.keys():
@@ -46,32 +47,31 @@
             if type( hostCfg[ 'basic' ][ 'locations' ] ) is list:
                 new_locations = []
                 for location in hostCfg[ 'basic' ][ 'locations' ]:
-                    searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", location )
+                    searchObj = re.search( didRE, location )
                     if searchObj:
-                        new_locations.append( 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 ) )
+                        new_locations.append( 'of:' + searchObj.group( 'swNum' ).zfill( 16 ) + '/' + searchObj.group( 'portNum' ) )
                     else:
                         new_locations.append( location )
                 netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_locations
             else:
                 location = hostCfg[ 'basic' ][ 'locations' ]
-                searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)", location )
+                searchObj = re.search( didRE, location )
                 if searchObj:
-                    new_location = 'of:' + searchObj.group( 1 ).zfill( 16 ) + '/' + searchObj.group( 2 )
+                    new_location = 'of:' + searchObj.group( 'swNum' ).zfill( 16 ) + '/' + searchObj.group( 'portNum' )
                     netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_location
 
     if 'devices' in netcfg.keys():
         for device in netcfg[ 'devices' ].keys():
-            searchObj = re.search( "device:bmv2:(leaf|spine)([1-9][0-9]*)", device )
+            searchObj = re.search( didRE, device )
             new_device = device
             if searchObj:
-                new_device = 'of:' + searchObj.group( 2 ).zfill( 16 )
+                new_device = 'of:' + searchObj.group( 'swNum' ).zfill( 16 )
                 netcfg[ 'devices' ][ new_device ] = netcfg[ 'devices' ].pop( device )
             if 'pairDeviceId' in netcfg[ 'devices' ][ new_device ][ SR_APP ].keys():
-                searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)",
-                                       netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ])
+                searchObj = re.search( didRE, netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ])
                 if searchObj:
                     netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ] = 'of:' + \
-                                                                                    searchObj.group( 1 ).zfill( 16 )
+                                                                                    searchObj.group( 'swNum' ).zfill( 16 )
             if 'basic' in netcfg[ 'devices' ][ new_device ].keys():
                 netcfg[ 'devices' ][ new_device ][ 'basic' ].update( { 'driver': 'ofdpa-ovs' } )
 
@@ -79,17 +79,18 @@
         if DHCP_APP_ID in netcfg[ 'apps' ].keys():
             for i, dhcpcfg in enumerate( netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ] ):
                 if 'dhcpServerConnectPoint' in dhcpcfg.keys():
-                    searchObj = re.search( "device:bmv2:leaf([1-9][0-9]*)/([0-9]+)",
-                                           dhcpcfg[ 'dhcpServerConnectPoint' ] )
+                    searchObj = re.search( didRE, dhcpcfg[ 'dhcpServerConnectPoint' ] )
                     if searchObj:
                         netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ][ i ][ 'dhcpServerConnectPoint' ] = \
-                            'of:' + searchObj.group( 1 ).zfill(16) + '/' + searchObj.group( 2 )
+                            'of:' + searchObj.group( 'swNum' ).zfill(16) + '/' + searchObj.group( 'portNum' )
 
     with open( cfgFile, 'w' ) as cfg:
-        cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ) ) )
+        cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ), sort_keys=True ) )
 
 # Translate configuration JSON file from OFDPA-OVS driver to BMv2 driver.
-def ofdpaToBmv2( main, cfgFile="" ):
+def ofdpaToBmv2( main, switchPrefix="bmv2", cfgFile="" ):
+    didRE= r"device:(?P<swType>bmv2|tofino):(?P<swRole>leaf|spine)(?P<swNum>[1-9][0-9]*)(/(?P<portNum>[0-9]+))?"
+    didRE = r"of:0*(?P<swNum>[1-9][0-9]*)(/(?P<portNum>[0-9]+))?"
     if not cfgFile:
         cfgFile = "%s%s.json" % ( main.configPath + main.forJson,
                                   main.cfgName )
@@ -98,9 +99,9 @@
 
     if 'ports' in netcfg.keys():
         for port in netcfg[ 'ports' ].keys():
-            searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", port )
+            searchObj = re.search( didRE, port )
             if searchObj:
-                new_port = 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+                new_port = 'device:' + switchPrefix + ':leaf' + searchObj.group( 'swNum' ) + '/' + searchObj.group( 'portNum' )
                 netcfg[ 'ports' ][ new_port ] = netcfg[ 'ports' ].pop( port )
 
     if 'hosts' in netcfg.keys():
@@ -108,36 +109,36 @@
             if type( hostCfg[ 'basic' ][ 'locations' ] ) is list:
                 new_locations = []
                 for location in hostCfg[ 'basic' ][ 'locations' ]:
-                    searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", location )
+                    searchObj = re.search( didRE, location )
                     if searchObj:
-                        new_locations.append( 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 ) )
+                        new_locations.append( 'device:' + switchPrefix + ':leaf' + searchObj.group( 'swNum' ) + '/' + searchObj.group( 'portNum' ) )
                     else:
                         new_locations.append( location )
                 netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_locations
             else:
                 location = hostCfg[ 'basic' ][ 'locations' ]
-                searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)", location )
+                searchObj = re.search( didRE, location )
                 if searchObj:
-                    new_location = 'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+                    new_location = 'device:' + switchPrefix + ':leaf' + searchObj.group( 'swNum' ) + '/' + searchObj.group( 'portNum' )
                     netcfg[ 'hosts' ][ host ][ 'basic' ][ 'locations' ] = new_location
 
     if 'devices' in netcfg.keys():
         for device in netcfg[ 'devices' ].keys():
-            searchObj = re.search( "of:0*([1-9][0-9]*)", device )
+            searchObj = re.search( didRE, device )
             new_device = device
             if searchObj:
                 isLeaf = netcfg[ 'devices' ][ device ][ SR_APP ][ 'isEdgeRouter' ]
                 if isLeaf is True:
-                    new_device = 'device:bmv2:leaf' + searchObj.group( 1 )
+                    new_device = 'device:' + switchPrefix + ':leaf' + searchObj.group( 'swNum' )
                 else:
-                    new_device = 'device:bmv2:spine' + searchObj.group( 1 )
+                    new_device = 'device:' + switchPrefix + ':spine' + searchObj.group( 'swNum' )
                 netcfg[ 'devices' ][ new_device ] = netcfg[ 'devices' ].pop( device )
             if 'pairDeviceId' in netcfg[ 'devices' ][ new_device ][ SR_APP ].keys():
-                searchObj = re.search( "of:0*([1-9][0-9]*)",
+                searchObj = re.search( didRE,
                                        netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ])
                 if searchObj:
-                    netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ] = 'device:bmv2:leaf' + \
-                                                                                    searchObj.group( 1 )
+                    netcfg[ 'devices' ][ new_device ][ SR_APP ][ 'pairDeviceId' ] = 'device:' + switchPrefix + ':leaf' + \
+                                                                                    searchObj.group( 'swNum' )
             if 'basic' in netcfg[ 'devices' ][ new_device ].keys():
                 if 'driver' in netcfg[ 'devices' ][ new_device ][ 'basic' ].keys():
                     del netcfg[ 'devices' ][ new_device ][ 'basic' ][ 'driver' ]
@@ -146,11 +147,11 @@
         if DHCP_APP_ID in netcfg[ 'apps' ].keys():
             for i, dhcpcfg in enumerate( netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ] ):
                 if 'dhcpServerConnectPoint' in dhcpcfg.keys():
-                    searchObj = re.search( "of:0*([1-9][0-9]*)/([0-9]+)",
+                    searchObj = re.search( didRE,
                                            dhcpcfg[ 'dhcpServerConnectPoint' ] )
                     if searchObj:
                         netcfg[ 'apps' ][ DHCP_APP_ID ][ 'default' ][ i ][ 'dhcpServerConnectPoint' ] = \
-                            'device:bmv2:leaf' + searchObj.group( 1 ) + '/' + searchObj.group( 2 )
+                            'device:' + switchPrefix + ':leaf' + searchObj.group( 'swNum' ) + '/' + searchObj.group( 'portNum' )
 
     with open( cfgFile, 'w' ) as cfg:
-        cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ) ) )
+        cfg.write( json.dumps( netcfg, indent=4, separators=( ',', ':' ), sort_keys=True ) )
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py b/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
index 814b6fb..6e5626e 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/hagg_fabric.py
@@ -15,6 +15,7 @@
 from trellislib import DhcpServer, TaggedRoutedHost, DualHomedRoutedHost, DualHomedTaggedRoutedHost, DhcpClient, Dhcp6Client, DhcpServer, Dhcp6Server, TrellisHost
 
 from bmv2 import ONOSBmv2Switch
+from stratum import StratumBmv2Switch
 
 # Parse command line options and dump results
 def parseOptions():
@@ -31,7 +32,7 @@
     parser.add_option( '--onos-ip', dest='onosIp', type='str', default='',
                        help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
     parser.add_option( '--switch', dest='switch', type='str', default='ovs',
-                       help='Switch type: ovs, bmv2 (with fabric.p4)' )
+                       help='Switch type: ovs, bmv2 (with fabric.p4), stratum' )
 
     ( options, args ) = parser.parse_args()
     return options, args
@@ -42,7 +43,8 @@
 
 SWITCH_TO_PARAMS_DICT = {
     "ovs": dict(cls=OVSSwitch),
-    "bmv2": dict(cls=ONOSBmv2Switch, pipeconf=FABRIC_PIPECONF)
+    "bmv2": dict(cls=ONOSBmv2Switch, pipeconf=FABRIC_PIPECONF),
+    "stratum": dict(cls=StratumBmv2Switch, pipeconf=FABRIC_PIPECONF, loglevel='debug')
 }
 if opts.switch not in SWITCH_TO_PARAMS_DICT:
     raise Exception("Unknown switch type '%s'" % opts.switch)
@@ -261,7 +263,7 @@
                                              dpid = "00000000000%s" % (ls + 1),
                                              **SWITCH_PARAMS )
 
-        # connecting leaf and spines, leafs 1-5 have double links
+        # connecting leafs 2-5 and spines 101 and 102 with double links
         for s in range(2):
             spine_switch = self.spines[s]
 
@@ -275,7 +277,7 @@
         self.addLink(self.leafs[1], self.leafs[2], **linkopts)
         self.addLink(self.leafs[3], self.leafs[4], **linkopts)
 
-        # build second fabric with single links
+        # build second fabric with single links, spines 103 and 104 and leafs 1 and 6
         for s in range(2, 4):
             spine_switch = self.spines[s]
 
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py b/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
index 8668f83..7556d58 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/trellis_fabric.py
@@ -365,7 +365,7 @@
         self.addLink(r1, rh1)
 
         # External IPv6 Host behind r1
-        rh1v6 = self.addHost('rh1v6', cls=TrellisHost, ips=['2000::9902/120'], gateway='2000::9901')
+        rh1v6 = self.addHost('rh1v6', cls=TrellisHost, ips=['2000::9902/120'], gateway='2000::9901', ipv6=True )
         self.addLink(r1, rh1v6)
 
         # DHCP server
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/trellislib.py b/TestON/tests/USECASE/SegmentRouting/dependencies/trellislib.py
index ebf6910..e02df4c 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/trellislib.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/trellislib.py
@@ -9,6 +9,7 @@
 sys.path.append('..')
 from mininet.node import Host
 from routinglib import RoutedHost, RoutedHost6, Router
+from stratum import NoOffloadHost
 
 class TaggedRoutedHost(RoutedHost):
     """Host that can be configured with multiple IP addresses."""
@@ -24,6 +25,7 @@
         intf = self.defaultIntf()
         self.vlanIntf = "%s.%s" % (intf, self.vlan)
         self.cmd('ip -4 addr flush dev %s' % intf)
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('ip link add link %s name %s type vlan id %s' % (intf, self.vlanIntf, self.vlan))
         self.cmd('ip link set up %s' % self.vlanIntf)
 
@@ -38,7 +40,7 @@
         self.cmd('ip link remove link %s' % self.vlanIntf)
         super(TaggedRoutedHost, self).terminate()
 
-class DualHomedRoutedHost(Host):
+class DualHomedRoutedHost(NoOffloadHost):
     def __init__(self, name, ips, gateway, *args, **kwargs):
         super(DualHomedRoutedHost, self).__init__(name, **kwargs)
         self.bond0 = None
@@ -72,6 +74,7 @@
         self.cmd('ip link set %s master %s' % (intf2.name, self.bond0))
         self.cmd('ip addr flush dev %s' % intf1.name)
         self.cmd('ip addr flush dev %s' % intf2.name)
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('ip link set %s up' % self.bond0)
         # NOTE: Issues with bonded intfs in mn data structures. Either only show bonded intf
         #       or create a custom class to handle bonded infs??
@@ -100,6 +103,7 @@
         default_intf = self.defaultIntf()
         self.vlanIntf = "%s.%s" % (default_intf, self.vlan)
         self.cmd('ip -4 addr flush dev %s' % default_intf)
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('ip link add link %s name %s type vlan id %s' % (default_intf, self.vlanIntf, self.vlan))
         self.cmd('ip link set up %s' % self.vlanIntf)
 
@@ -123,6 +127,7 @@
     def config(self, **kwargs):
         super(DhcpClient, self).config(**kwargs)
         self.cmd('ip addr flush dev %s' % self.defaultIntf())
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('dhclient -q -4 -nw -pf %s -lf %s %s' % (self.pidFile, self.leaseFile, self.defaultIntf()))
 
     def terminate(self, **kwargs):
@@ -139,6 +144,7 @@
     def config(self, **kwargs):
         super(Dhcp6Client, self).config(**kwargs)
         self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('dhclient -q -6 -nw -pf %s -lf %s %s' % (self.pidFile, self.leaseFile, self.defaultIntf()))
 
     def terminate(self, **kwargs):
@@ -214,6 +220,7 @@
         super(TaggedDhcpClient, self).config(**kwargs)
         self.vlanIntf = "%s.%s" % (self.defaultIntf(), self.vlan)
         self.cmd('ip addr flush dev %s' % self.defaultIntf())
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('ip link add link %s name %s type vlan id %s' % (self.defaultIntf(), self.vlanIntf, self.vlan))
         self.cmd('ip link set up %s' % self.vlanIntf)
         self.cmd('dhclient -q -4 -nw -pf %s %s' % (self.pidFile, self.vlanIntf))
@@ -267,6 +274,7 @@
         self.cmd('ip link set %s master %s' % (intf2.name, self.bond0))
         self.cmd('ip addr flush dev %s' % intf1.name)
         self.cmd('ip addr flush dev %s' % intf2.name)
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
         self.cmd('ip link set %s up' % self.bond0)
         # NOTE: Issues with bonded intfs in mn data structures. Either only show bonded intf
         #       or create a custom class to handle bonded infs??
@@ -284,7 +292,7 @@
         self.cmd('rm -rf %s' % self.pidFile)
         super(DualHomedDhcpClient, self).terminate()
 
-class TrellisHost(Host):
+class TrellisHost(NoOffloadHost):
     def __init__(self, name, ips=[], gateway="", dualHomed=False, vlan=None, dhcpClient=False, dhcpServer=False, ipv6=False, *args, **kwargs):
         super(TrellisHost, self).__init__(name, *args, **kwargs)
         self.dualHomed = dualHomed
@@ -313,6 +321,10 @@
             self.bondIntfs( self.intfs[0], self.intfs[1] )
 
         self.cmd('ip %s addr flush dev %s' % ("-4" if self.ipv6 else "", self.defaultIntf()))
+        self.cmd('sysctl -w net.ipv4.ip_forward=0')
+
+        if not self.ipv6:
+            self.cmd('sysctl -w net.ipv6.conf.all.disable_ipv6=1')
 
         if self.vlan:
             # Setup vlan interface