Merge "[ONOS-7904] Run CHO test with Segment Routing and H-AGG topology"
diff --git a/TestON/drivers/common/cli/emulator/mininetclidriver.py b/TestON/drivers/common/cli/emulator/mininetclidriver.py
index 8d474cb..27e1bf6 100644
--- a/TestON/drivers/common/cli/emulator/mininetclidriver.py
+++ b/TestON/drivers/common/cli/emulator/mininetclidriver.py
@@ -1211,7 +1211,8 @@
                 response = self.execute(
                     cmd=cmd,
                     prompt="mininet>",
-                    timeout=10 )
+                    timeout=10,
+                    logCmd=False )
             except pexpect.EOF:
                 main.log.error( self.name + ": EOF exception found" )
                 main.log.error( self.name + ":     " + self.handle.before )
@@ -1930,7 +1931,7 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
 
-    def getSwitchRandom( self, timeout=60, nonCut=True ):
+    def getSwitchRandom( self, timeout=60, nonCut=True, switchClasses=None, excludeNodes=[] ):
         """
         Randomly get a switch from Mininet topology.
         If nonCut is True, it gets a list of non-cut switches (the deletion
@@ -1938,18 +1939,21 @@
         components of a graph) and randomly returns one of them, otherwise
         it just randomly returns one switch from all current switches in
         Mininet.
+        excludeNodes will be pased to getGraphDict method
         Returns the name of the chosen switch.
         """
         import random
         candidateSwitches = []
         try:
             if not nonCut:
-                switches = self.getSwitches( timeout=timeout )
+                switches = self.getSwitches( timeout=timeout, switchClasses=switchClasses )
                 assert len( switches ) != 0
                 for switchName in switches.keys():
                     candidateSwitches.append( switchName )
             else:
-                graphDict = self.getGraphDict( timeout=timeout, useId=False )
+                graphDict = self.getGraphDict( timeout=timeout, useId=False,
+                                               switchClasses=switchClasses,
+                                               excludeNodes=excludeNodes )
                 if graphDict is None:
                     return None
                 self.graph.update( graphDict )
@@ -2066,7 +2070,7 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
 
-    def getLinkRandom( self, timeout=60, nonCut=True ):
+    def getLinkRandom( self, timeout=60, nonCut=True, switchClasses=None, excludeNodes=[] ):
         """
         Randomly get a link from Mininet topology.
         If nonCut is True, it gets a list of non-cut links (the deletion
@@ -2074,13 +2078,14 @@
         component of a graph) and randomly returns one of them, otherwise
         it just randomly returns one link from all current links in
         Mininet.
+        excludeNodes will be passed to getLinks method to exclude unexpected links.
         Returns the link as a list, e.g. [ 's1', 's2' ]
         """
         import random
         candidateLinks = []
         try:
             if not nonCut:
-                links = self.getLinks( timeout=timeout )
+                links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
                 assert len( links ) != 0
                 for link in links:
                     # Exclude host-switch link
@@ -2088,7 +2093,9 @@
                         continue
                     candidateLinks.append( [ link[ 'node1' ], link[ 'node2' ] ] )
             else:
-                graphDict = self.getGraphDict( timeout=timeout, useId=False )
+                graphDict = self.getGraphDict( timeout=timeout, useId=False,
+                                               switchClasses=switchClasses,
+                                               excludeNodes=excludeNodes )
                 if graphDict is None:
                     return None
                 self.graph.update( graphDict )
@@ -2779,7 +2786,8 @@
             response = self.execute(
                 cmd=command,
                 prompt="mininet>",
-                timeout=10 )
+                timeout=10,
+                logCmd=False )
             ports = []
             if response:
                 for line in response.split( "\n" ):
@@ -2807,7 +2815,7 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
 
-    def getSwitches( self, verbose=False, updateTimeout=1000 ):
+    def getSwitches( self, verbose=False, updateTimeout=1000, switchClasses=None ):
         """
         Read switches from Mininet.
 
@@ -2824,8 +2832,9 @@
         # <OVSSwitchNS s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None,s1-eth3:None pid=22550>
         # <OVSBridge s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None pid=26830>
         # <UserSwitch s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None pid=14737>
-        try:
+        if not switchClasses:
             switchClasses = r"(OVSSwitch)|(OVSBridge)|(OVSSwitchNS)|(IVSSwitch)|(LinuxBridge)|(UserSwitch)"
+        try:
             swRE = r"<(?P<class>" + switchClasses + r")" +\
                    r"(?P<options>\{.*\})?\s" +\
                    r"(?P<name>[^:]+)\:\s" +\
@@ -2939,7 +2948,7 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
 
-    def getLinks( self, timeout=20, updateTimeout=1000 ):
+    def getLinks( self, timeout=20, updateTimeout=1000, excludeNodes=[] ):
         """
         Gathers information about current Mininet links. These links may not
         be up if one of the ports is down.
@@ -2951,6 +2960,10 @@
               'node2': str( node2 name )
               'port1': str( port1 of_port )
               'port2': str( port2 of_port ) }
+
+        If either node1 or node2 name starts with any of the strings sepcified
+        in excludeNodes, the link will be excluded from the returned value
+
         Note: The port number returned is the eth#, not necessarily the of_port
               number. In Mininet, for OVS switch, these should be the same. For
               hosts, this is just the eth#.
@@ -2962,14 +2975,17 @@
             # Examples:
             # s1-eth3<->s2-eth1 (OK OK)
             # s13-eth3<->h27-eth0 (OK OK)
-            linkRE = "(?P<node1>[\w]+)\-eth(?P<port1>[\d]+)\<\-\>" +\
-                     "(?P<node2>[\w]+)\-eth(?P<port2>[\d]+)"
+            linkRE = "(?P<node1>[\w]+)\-eth(?P<port1>[\d\.]+)\<\-\>" +\
+                     "(?P<node2>[\w]+)\-eth(?P<port2>[\d\.]+)"
             links = []
             for line in response:
                 match = re.search( linkRE, line )
                 if match:
                     node1 = match.group( 'node1' )
                     node2 = match.group( 'node2' )
+                    if any( node1.startswith( node ) for node in excludeNodes ) or \
+                       any( node2.startswith( node ) for node in excludeNodes ):
+                        continue
                     port1 = match.group( 'port1' )
                     port2 = match.group( 'port2' )
                     links.append( { 'node1': node1,
@@ -3438,7 +3454,8 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             main.cleanAndExit()
 
-    def getGraphDict( self, timeout=60, useId=True, includeHost=False ):
+    def getGraphDict( self, timeout=60, useId=True, includeHost=False,
+                      switchClasses=None, excludeNodes=[] ):
         """
         Return a dictionary which describes the latest Mininet topology data as a
         graph.
@@ -3457,6 +3474,9 @@
         topology.
         If includeHost == True, all hosts (and host-switch links) will be included
         in topology data.
+        if switchClasses == None, default switchClasses will be used when calling
+        getSwitches method.
+        excludeNodes will be passed to getLinks method to exclude unexpected links.
         Note that link or switch that are brought down by 'link x x down' or 'switch
         x down' commands still show in the output of Mininet CLI commands such as
         'links', 'dump', etc. Thus, to ensure the correctness of this function, it is
@@ -3465,10 +3485,9 @@
         """
         graphDict = {}
         try:
-            links = self.getLinks( timeout=timeout )
+            links = self.getLinks( timeout=timeout, excludeNodes=excludeNodes )
             portDict = {}
-            if useId:
-                switches = self.getSwitches()
+            switches = self.getSwitches( switchClasses=switchClasses )
             if includeHost:
                 hosts = self.getHosts()
             for link in links:
@@ -3477,6 +3496,9 @@
                     continue
                 nodeName1 = link[ 'node1' ]
                 nodeName2 = link[ 'node2' ]
+                if not self.getOVSPorts( nodeName1 ) or not self.getOVSPorts( nodeName2 ):
+                    # The device is probably offline
+                    continue
                 port1 = link[ 'port1' ]
                 port2 = link[ 'port2' ]
                 # Loop for two nodes
@@ -3516,11 +3538,29 @@
                             graphDict[ node1 ] = { 'edges': {} }
                     else:
                         # Assert node2 is not connected to any current links of node1
-                        assert node2 not in graphDict[ node1 ][ 'edges' ].keys()
-                    graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': portIndex }
+                        # assert node2 not in graphDict[ node1 ][ 'edges' ].keys()
+                        pass
+                    for port in switches[ nodeName1 ][ 'ports' ]:
+                        if port[ 'of_port' ] == str( portIndex ):
+                            # Use -1 as index for disabled port
+                            if port[ 'enabled' ] == True:
+                                graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': portIndex }
+                            else:
+                                graphDict[ node1 ][ 'edges' ][ node2 ] = { 'port': -1 }
                     # Swap two nodes/ports
                     nodeName1, nodeName2 = nodeName2, nodeName1
                     port1, port2 = port2, port1
+            # Remove links with disabled ports
+            linksToRemove = []
+            for node, edges in graphDict.items():
+                for neighbor, port in edges[ 'edges' ].items():
+                    if port[ 'port' ] == -1:
+                        linksToRemove.append( ( node, neighbor ) )
+            for node1, node2 in linksToRemove:
+                for i in range( 2 ):
+                    if graphDict.get( node1 )[ 'edges' ].get( node2 ):
+                        graphDict[ node1 ][ 'edges' ].pop( node2 )
+                    node1, node2 = node2, node1
             return graphDict
         except KeyError:
             main.log.exception( self.name + ": KeyError exception found" )
diff --git a/TestON/drivers/common/cli/onosclidriver.py b/TestON/drivers/common/cli/onosclidriver.py
index 1966a63..80e0246 100755
--- a/TestON/drivers/common/cli/onosclidriver.py
+++ b/TestON/drivers/common/cli/onosclidriver.py
@@ -5337,7 +5337,8 @@
                                            'annotations': idToDevice[ nodeA ][ 'annotations' ]}
                 else:
                     # Assert nodeB is not connected to any current links of nodeA
-                    assert nodeB not in graphDict[ nodeA ][ 'edges' ].keys()
+                    # assert nodeB not in graphDict[ nodeA ][ 'edges' ].keys()
+                    pass
                 graphDict[ nodeA ][ 'edges' ][ nodeB ] = { 'port': link[ 'src' ][ 'port' ],
                                                            'type': link[ 'type' ],
                                                            'state': link[ 'state' ] }
diff --git a/TestON/drivers/common/clidriver.py b/TestON/drivers/common/clidriver.py
index 81b33b2..25986b3 100644
--- a/TestON/drivers/common/clidriver.py
+++ b/TestON/drivers/common/clidriver.py
@@ -156,6 +156,7 @@
         prompt => represents expect command prompt or output,
         timeout => timeout for command execution,
         more => to provide a key press if it is on.
+        logCmd => log the command executed if True
 
         It will return output of command exection.
         """
@@ -164,7 +165,8 @@
         args = utilities.parse_args( [ "CMD",
                                        "TIMEOUT",
                                        "PROMPT",
-                                       "MORE" ],
+                                       "MORE",
+                                       "LOGCMD" ],
                                      **execparams )
 
         expectPrompt = args[ "PROMPT" ] if args[ "PROMPT" ] else defaultPrompt
@@ -188,9 +190,10 @@
         if index == 0:
             self.LASTRSP = self.LASTRSP + \
                 self.handle.before + self.handle.after
-            main.log.info( "Executed :" + str( cmd ) +
-                           " \t\t Expected Prompt '" + str( expectPrompt ) +
-                           "' Found" )
+            if not args[ "LOGCMD" ] is False:
+                main.log.info( "Executed :" + str( cmd ) +
+                               " \t\t Expected Prompt '" + str( expectPrompt ) +
+                               "' Found" )
         elif index == 1:
             self.LASTRSP = self.LASTRSP + self.handle.before
             self.handle.send( args[ "MORE" ] )
diff --git a/TestON/tests/CHOTestMonkey/CHOTestMonkey.params b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
index ff85c56..80a0b0b 100644
--- a/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
+++ b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
@@ -1,8 +1,9 @@
 <PARAMS>
     # 0. Initialize CHOTestMonkey
-    # 1. Load topology and balances all switches
-    # 2. Collect and store device and link data from ONOS
-    # 3. Collect and store host data from ONOS
+    # 1. Set IPv6 configure
+    # 5. Load topology and balances all switches
+    # 6. Collect and store device and link data from ONOS
+    # 7. Collect and store host data from ONOS
     # 10. Run all enabled checks
     # 20. Bring down/up links and check topology and ping
     # 21. Bring down/up a group of links and check topology and ping
@@ -20,35 +21,40 @@
     # 90. Sleep for some time
     # 100. Do nothing
     # Sample sequence: 0,1,2,3,[10,30,21,31,10,32,21,33,50,10,30,21,31,10,32,21,33,51,40,60,10,30,21,31,10,32,21,33,50,10,30,21,31,10,32,21,33,51,41,60]*500,100
+
     <testcases>
-        0,1,2,3,70
+        0,1,5,6,7,70
     </testcases>
+
     <GIT>
         <pull>False</pull>
         <branch>master</branch>
     </GIT>
+
     <TEST>
         <topo>1</topo>
         <IPv6>on</IPv6>
+        <dataPlaneConnectivity>False</dataPlaneConnectivity>
         <numCtrl>3</numCtrl>
         <pauseTest>off</pauseTest>
         <caseSleep>0</caseSleep>
-        <setIPv6CfgSleep>5</setIPv6CfgSleep>
-        <loadTopoSleep>5</loadTopoSleep>
-        <ipv6Prefix>1000::</ipv6Prefix>
-        <ipv4Prefix>10.1.</ipv4Prefix>
+        <ipv6Regex>1000::[0-9]+</ipv6Regex>
+        <ipv4Regex>10\.1\.[0-9]+\.[0-9]+</ipv4Regex>
         <karafCliTimeout>7200000</karafCliTimeout>
         <testDuration>86400</testDuration>
         <package>on</package>
     </TEST>
+
     <GRAPH>
         <nodeCluster>CHO</nodeCluster>
         <builds>20</builds>
     </GRAPH>
+
     <ENV>
         <cellName>choCell</cellName>
         <cellApps>drivers,openflow,proxyarp,events</cellApps>
     </ENV>
+
     <EVENT>
         <Event>
             <status>on</status>
@@ -399,8 +405,14 @@
         <topo4>
             <fileName>topoRingIpv6.py</fileName>
         </topo4>
+        <mininetArgs></mininetArgs>
+        <loadTopoSleep>5</loadTopoSleep>
     </TOPO>
 
+    <CASE1>
+        <setIPv6CfgSleep>5</setIPv6CfgSleep>
+    </CASE1>
+
     <CASE20>
         <linkToggleNum>5</linkToggleNum>
         <linkDownUpInterval>1</linkDownUpInterval>
@@ -426,11 +438,13 @@
         <addPointIntentWeight>3</addPointIntentWeight>
         <linkDownWeight>3</linkDownWeight>
         <deviceDownWeight>2</deviceDownWeight>
+        <portDownWeight>0</portDownWeight>
+        <onosDownWeight>1</onosDownWeight>
         <toggleFlowObj>0</toggleFlowObj>
     </CASE70>
 
     <CASE80>
-        <filePath>/home/admin/log-for-replay</filePath>
+        <filePath>/home/sdn/log-for-replay</filePath>
         <sleepTime>0.1</sleepTime>
         <skipChecks>on</skipChecks>
     </CASE80>
diff --git a/TestON/tests/CHOTestMonkey/CHOTestMonkey.params.trellis b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params.trellis
new file mode 100644
index 0000000..fc839ec
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params.trellis
@@ -0,0 +1,337 @@
+<PARAMS>
+    # 0. Initialize CHOTestMonkey
+    # 1. Set IPv6 configure
+    # 2. Load network configuration files
+    # 4. Copy topology libs and config files to Mininet
+    # 5. Load topology and balances all switches
+    # 6. Collect and store device and link data from ONOS
+    # 7. Collect and store host data from ONOS
+    # 70. Run randomly generated events
+    # 100. Do nothing
+
+    <testcases>
+        0,2,4,5,6,7,70
+    </testcases>
+
+    <GIT>
+        <pull>False</pull>
+        <branch>master</branch>
+    </GIT>
+
+    <TEST>
+        <topo>10</topo>
+        <IPv6>on</IPv6>
+        <dataPlaneConnectivity>True</dataPlaneConnectivity>
+        <numCtrl>3</numCtrl>
+        <pauseTest>on</pauseTest>
+        <caseSleep>0</caseSleep>
+        <ipv6Regex>10[0-9]+::[0-9]+</ipv6Regex>
+        <ipv4Regex>10\.[0-9]+\.[0-9]+\.[0-9]+</ipv4Regex>
+        <karafCliTimeout>7200000</karafCliTimeout>
+        <testDuration>86400</testDuration>
+        <package>on</package>
+    </TEST>
+
+    <GRAPH>
+        <nodeCluster>CHO</nodeCluster>
+        <builds>20</builds>
+    </GRAPH>
+
+    <ENV>
+        <cellName>choTrellisCell</cellName>
+        <cellApps>drivers,openflow,segmentrouting,fpm,dhcprelay,netcfghostprovider,routeradvertisement,t3,hostprobingprovider</cellApps>
+    </ENV>
+
+    <EVENT>
+        <Event>
+            <status>on</status>
+            <typeIndex>0</typeIndex>
+            <typeString>NULL</typeString>
+            <CLI>null</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </Event>
+
+        <TestPause>
+            <status>on</status>
+            <typeIndex>1</typeIndex>
+            <typeString>TEST_PAUSE</typeString>
+            <CLI>pause-test</CLI>
+            <CLIParamNum>0</CLIParamNum>
+        </TestPause>
+
+        <TestResume>
+            <status>on</status>
+            <typeIndex>2</typeIndex>
+            <typeString>TEST_RESUME</typeString>
+            <CLI>resume-test</CLI>
+            <CLIParamNum>0</CLIParamNum>
+        </TestResume>
+
+        <TestSleep>
+            <status>on</status>
+            <typeIndex>3</typeIndex>
+            <typeString>TEST_SLEEP</typeString>
+            <CLI>sleep</CLI>
+            <CLIParamNum>1</CLIParamNum>
+        </TestSleep>
+
+        <TestDebug>
+            <status>on</status>
+            <typeIndex>4</typeIndex>
+            <typeString>TEST_DEBUG</typeString>
+            <CLI>debug-test</CLI>
+            <CLIParamNum>0</CLIParamNum>
+        </TestDebug>
+
+        <IntentCheck>
+            <status>off</status>
+            <typeIndex>10</typeIndex>
+            <typeString>CHECK_INTENT</typeString>
+            <CLI>check-intent</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </IntentCheck>
+
+        <FlowCheck>
+            <status>off</status>
+            <typeIndex>11</typeIndex>
+            <typeString>CHECK_FLOW</typeString>
+            <CLI>check-flow</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+            <coreFlowNum>3</coreFlowNum>
+            <coreFlowNum6>5</coreFlowNum6>
+        </FlowCheck>
+
+        <TrafficCheck>
+            <status>on</status>
+            <typeIndex>12</typeIndex>
+            <typeString>CHECK_TRAFFIC</typeString>
+            <CLI>check-traffic</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>10</rerunInterval>
+            <maxRerunNum>10</maxRerunNum>
+            <pingWait>1</pingWait>
+            <pingTimeout>10</pingTimeout>
+        </TrafficCheck>
+
+        <TopoCheck>
+            <status>on</status>
+            <typeIndex>13</typeIndex>
+            <typeString>CHECK_TOPO</typeString>
+            <CLI>check-topo</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </TopoCheck>
+
+        <ONOSCheck>
+            <status>on</status>
+            <typeIndex>14</typeIndex>
+            <typeString>CHECK_ONOS</typeString>
+            <CLI>check-onos</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>10</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </ONOSCheck>
+
+        <RaftLogSizeCheck>
+            <status>on</status>
+            <typeIndex>15</typeIndex>
+            <typeString>CHECK_RAFT_LOG_SIZE</typeString>
+            <CLI>check-raft-size</CLI>
+            <CLIParamNum>0</CLIParamNum>
+        </RaftLogSizeCheck>
+
+        <LinkDown>
+            <status>on</status>
+            <typeIndex>20</typeIndex>
+            <typeString>NETWORK_LINK_DOWN</typeString>
+            <CLI>link-down</CLI>
+            <CLIParamNum>2</CLIParamNum>
+        </LinkDown>
+
+        <LinkUp>
+            <status>on</status>
+            <typeIndex>21</typeIndex>
+            <typeString>NETWORK_LINK_UP</typeString>
+            <CLI>link-up</CLI>
+            <CLIParamNum>2</CLIParamNum>
+        </LinkUp>
+
+        <DeviceDown>
+            <status>on</status>
+            <typeIndex>22</typeIndex>
+            <typeString>NETWORK_DEVICE_DOWN</typeString>
+            <CLI>device-down</CLI>
+            <CLIParamNum>1</CLIParamNum>
+        </DeviceDown>
+
+        <DeviceUp>
+            <status>on</status>
+            <typeIndex>23</typeIndex>
+            <typeString>NETWORK_DEVICE_UP</typeString>
+            <CLI>device-up</CLI>
+            <CLIParamNum>1</CLIParamNum>
+        </DeviceUp>
+
+        <PortDown>
+            <status>on</status>
+            <typeIndex>24</typeIndex>
+            <typeString>NETWORK_PORT_DOWN</typeString>
+            <CLI>port-down</CLI>
+            <CLIParamNum>2</CLIParamNum>
+        </PortDown>
+
+        <PortUp>
+            <status>on</status>
+            <typeIndex>25</typeIndex>
+            <typeString>NETWORK_PORT_UP</typeString>
+            <CLI>port-up</CLI>
+            <CLIParamNum>2</CLIParamNum>
+        </PortUp>
+
+        <ONOSDown>
+            <status>on</status>
+            <typeIndex>40</typeIndex>
+            <typeString>ONOS_ONOS_DOWN</typeString>
+            <CLI>onos-down</CLI>
+            <CLIParamNum>1</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </ONOSDown>
+
+        <ONOSUp>
+            <status>on</status>
+            <typeIndex>41</typeIndex>
+            <typeString>ONOS_ONOS_UP</typeString>
+            <CLI>onos-up</CLI>
+            <CLIParamNum>1</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </ONOSUp>
+
+        <SetCfg>
+            <status>on</status>
+            <typeIndex>42</typeIndex>
+            <typeString>ONOS_SET_CFG</typeString>
+            <CLI>set-cfg</CLI>
+            <CLIParamNum>3</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </SetCfg>
+
+        <BalanceMasters>
+            <status>on</status>
+            <typeIndex>44</typeIndex>
+            <typeString>ONOS_BALANCE_MASTERS</typeString>
+            <CLI>balance-masters</CLI>
+            <CLIParamNum>0</CLIParamNum>
+            <rerunInterval>5</rerunInterval>
+            <maxRerunNum>5</maxRerunNum>
+        </BalanceMasters>
+
+        <addAllChecks>
+            <status>on</status>
+            <typeIndex>110</typeIndex>
+            <typeString>CHECK_ALL</typeString>
+            <CLI>check-all</CLI>
+            <CLIParamNum>0</CLIParamNum>
+        </addAllChecks>
+
+        <randomLinkToggle>
+            <status>on</status>
+            <typeIndex>120</typeIndex>
+            <typeString>NETWORK_LINK_RANDOM_TOGGLE</typeString>
+            <CLI>link-toggle-random</CLI>
+            <CLIParamNum>1</CLIParamNum>
+            <sleepBeforeCheck>10</sleepBeforeCheck>
+        </randomLinkToggle>
+
+        <randomLinkGroupToggle>
+            <status>on</status>
+            <typeIndex>121</typeIndex>
+            <typeString>NETWORK_LINK_GROUP_RANDOM_TOGGLE</typeString>
+            <CLI>link-group-toggle-random</CLI>
+            <CLIParamNum>3</CLIParamNum>
+            <sleepBeforeCheck>10</sleepBeforeCheck>
+        </randomLinkGroupToggle>
+
+        <randomDeviceToggle>
+            <status>on</status>
+            <typeIndex>122</typeIndex>
+            <typeString>NETWORK_DEVICE_RANDOM_TOGGLE</typeString>
+            <CLI>device-toggle-random</CLI>
+            <CLIParamNum>1</CLIParamNum>
+            <sleepBeforeCheck>10</sleepBeforeCheck>
+        </randomDeviceToggle>
+
+        <randomDeviceGroupToggle>
+            <status>on</status>
+            <typeIndex>123</typeIndex>
+            <typeString>NETWORK_DEVICE_GROUP_RANDOM_TOGGLE</typeString>
+            <CLI>device-group-toggle-random</CLI>
+            <CLIParamNum>3</CLIParamNum>
+            <sleepBeforeCheck>10</sleepBeforeCheck>
+        </randomDeviceGroupToggle>
+
+        <randomONOSToggle>
+            <status>on</status>
+            <typeIndex>140</typeIndex>
+            <typeString>ONOS_ONOS_RANDOM_TOGGLE</typeString>
+            <CLI>onos-toggle-random</CLI>
+            <CLIParamNum>1</CLIParamNum>
+            <sleepBeforeCheck>10</sleepBeforeCheck>
+        </randomONOSToggle>
+    </EVENT>
+
+    <SCHEDULER>
+        <pendingEventsCapacity>1</pendingEventsCapacity>
+        <runningEventsCapacity>10</runningEventsCapacity>
+        <scheduleLoopSleep>0.1</scheduleLoopSleep>
+    </SCHEDULER>
+
+    <GENERATOR>
+        <listenerPort>6000</listenerPort>
+        <insertEventRetryInterval>1</insertEventRetryInterval>
+    </GENERATOR>
+
+    <TOPO>
+        <topo10>
+            <fileName>hagg.py</fileName>
+        </topo10>
+        <mininetArgs>--dhcp=1 --routers=1 --ipv6=1 --ipv4=1</mininetArgs>
+        <loadTopoSleep>90</loadTopoSleep>
+    </TOPO>
+
+    <CASE2>
+        <fileName>hagg.json</fileName>
+        <hostFileName>hagg.host</hostFileName>
+    </CASE2>
+
+    <CASE4>
+        <lib>routinglib.py,trellislib.py,trellis_fabric.py</lib>
+        <conf>bgpdbgp1.conf,bgpdbgp2.conf,bgpdr1.conf,bgpdr2.conf,dhcpd6.conf,dhcpd.conf,zebradbgp1.conf,zebradbgp2.conf</conf>
+    </CASE4>
+
+    <CASE70>
+        <sleepSec>30</sleepSec>
+        <addHostIntentWeight>0</addHostIntentWeight>
+        <addPointIntentWeight>0</addPointIntentWeight>
+        <linkDownWeight>0</linkDownWeight>
+        <deviceDownWeight>2</deviceDownWeight>
+        <portDownWeight>3</portDownWeight>
+        <onosDownWeight>1</onosDownWeight>
+        <toggleFlowObj>0</toggleFlowObj>
+    </CASE70>
+
+    <CASE80>
+        <filePath>/home/sdn/log-for-replay</filePath>
+        <sleepTime>10</sleepTime>
+        <skipChecks>on</skipChecks>
+    </CASE80>
+</PARAMS>
diff --git a/TestON/tests/CHOTestMonkey/CHOTestMonkey.py b/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
index 9791388..000dfe3 100644
--- a/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
+++ b/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
@@ -67,8 +67,7 @@
         try:
             onosPackage = main.params[ 'TEST' ][ 'package' ]
             karafTimeout = main.params[ 'TEST' ][ 'karafCliTimeout' ]
-            main.enableIPv6 = main.params[ 'TEST' ][ 'IPv6' ]
-            main.enableIPv6 = True if main.enableIPv6 == "on" else False
+            main.enableIPv6 = main.params[ 'TEST' ][ 'IPv6' ].lower() == "on"
             main.caseSleep = int( main.params[ 'TEST' ][ 'caseSleep' ] )
             main.onosCell = main.params[ 'ENV' ][ 'cellName' ]
             main.apps = main.params[ 'ENV' ][ 'cellApps' ]
@@ -82,7 +81,6 @@
             for eventName in main.params[ 'EVENT' ].keys():
                 if main.params[ 'EVENT' ][ eventName ][ 'status' ] == 'on':
                     main.enabledEvents[ int( main.params[ 'EVENT' ][ eventName ][ 'typeIndex' ] ) ] = eventName
-            print main.enabledEvents
             main.graph = Graph()
             main.eventScheduler = EventScheduler()
             main.eventGenerator = EventGenerator()
@@ -102,30 +100,9 @@
                                                 cellName=main.onosCell )
         for i in range( 1, main.Cluster.numCtrls + 1 ):
             newController = Controller( i )
-            newController.setCLI( main.Cluster.active( i - 1 ).CLI )
+            newController.setCLI( main.Cluster.runningNodes[ i - 1 ].CLI )
             main.controllers.append( newController )
 
-        main.step( "Set IPv6 cfg parameters for Neighbor Discovery" )
-        setIPv6CfgSleep = int( main.params[ 'TEST' ][ 'setIPv6CfgSleep' ] )
-        if main.enableIPv6:
-            time.sleep( setIPv6CfgSleep )
-            cfgResult1 = main.controllers[ 0 ].CLI.setCfg( "org.onosproject.net.neighbour.impl.NeighbourResolutionManager",
-                                                           "ndpEnabled",
-                                                           "true" )
-            time.sleep( setIPv6CfgSleep )
-            cfgResult2 = main.controllers[ 0 ].CLI.setCfg( "org.onosproject.provider.host.impl.HostLocationProvider",
-                                                           "requestIpv6ND",
-                                                           "true" )
-        else:
-            main.log.info( "Skipped setting IPv6 cfg parameters as it is disabled in params file" )
-            cfgResult1 = main.TRUE
-            cfgResult2 = main.TRUE
-        cfgResult = cfgResult1 and cfgResult2
-        utilities.assert_equals( expect=main.TRUE,
-                                 actual=cfgResult,
-                                 onpass="ipv6NeighborDiscovery cfg is set to true",
-                                 onfail="Failed to cfg set ipv6NeighborDiscovery" )
-
         main.step( "Start a thread for the scheduler" )
         t = main.Thread( target=main.eventScheduler.startScheduler,
                          threadID=main.threadID,
@@ -150,7 +127,7 @@
         with main.variableLock:
             main.threadID = main.threadID + 1
 
-        caseResult = setupResult and cfgResult
+        caseResult = setupResult
         utilities.assert_equals( expect=main.TRUE,
                                  actual=caseResult,
                                  onpass="Set up test environment PASS",
@@ -158,28 +135,118 @@
 
     def CASE1( self, main ):
         """
-        Load Mininet topology and balances all switches
+        Set IPv6 cfg parameters for Neighbor Discovery
+        """
+        main.step( "Set IPv6 cfg parameters for Neighbor Discovery" )
+        setIPv6CfgSleep = int( main.params[ 'CASE1' ][ 'setIPv6CfgSleep' ] )
+        if main.enableIPv6:
+            time.sleep( setIPv6CfgSleep )
+            cfgResult1 = main.Cluster.active( 0 ).CLI.setCfg( "org.onosproject.net.neighbour.impl.NeighbourResolutionManager",
+                                                              "ndpEnabled",
+                                                              "true" )
+            time.sleep( setIPv6CfgSleep )
+            cfgResult2 = main.Cluster.active( 0 ).CLI.setCfg( "org.onosproject.provider.host.impl.HostLocationProvider",
+                                                              "requestIpv6ND",
+                                                              "true" )
+        else:
+            main.log.info( "Skipped setting IPv6 cfg parameters as it is disabled in params file" )
+            cfgResult1 = main.TRUE
+            cfgResult2 = main.TRUE
+        cfgResult = cfgResult1 and cfgResult2
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=cfgResult,
+                                 onpass="ipv6NeighborDiscovery cfg is set to true",
+                                 onfail="Failed to cfg set ipv6NeighborDiscovery" )
+
+    def CASE2( self, main ):
+        """
+        Load network configuration files
+        """
+        import json
+        main.case( "Load json files for configuring the network" )
+
+        main.step( "Load json files for configuring the network" )
+        cfgResult = main.TRUE
+        jsonFileName = main.params[ 'CASE2' ][ 'fileName' ]
+        jsonFile = main.testDir + "/dependencies/topologies/json/" + jsonFileName
+        with open( jsonFile ) as cfg:
+            main.Cluster.active( 0 ).REST.setNetCfg( json.load( cfg ) )
+
+        main.step( "Load host files" )
+        hostFileName = main.params[ 'CASE2' ][ 'hostFileName' ]
+        hostFile = main.testDir + "/dependencies/topologies/host/" + hostFileName
+        with open( hostFile ) as cfg:
+            main.expectedHosts = json.load( cfg )
+
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=cfgResult,
+                                 onpass="Correctly loaded network configurations",
+                                 onfail="Failed to load network configurations" )
+
+    def CASE4( self, main ):
+        """
+        Copy topology lib and config files to Mininet node
         """
         import re
+        main.case( "Copy topology lib and config files to Mininet node" )
+
+        copyResult = main.TRUE
+        main.step( "Copy topology lib files to Mininet node" )
+        for libFileName in main.params[ 'CASE4' ][ 'lib' ].split(","):
+            libFile = main.testDir + "/dependencies/topologies/lib/" + libFileName
+            copyResult = copyResult and main.ONOSbench.scp( main.Mininet1,
+                                                            libFile,
+                                                            main.Mininet1.home + "/custom",
+                                                            direction="to" )
+
+        main.step( "Copy topology config files to Mininet node" )
+        controllerIPs = [ ctrl.ipAddress for ctrl in main.Cluster.runningNodes ]
+        index = 0
+        for confFileName in main.params[ 'CASE4' ][ 'conf' ].split(","):
+            confFile = main.testDir + "/dependencies/topologies/conf/" + confFileName
+            # Update zebra configurations with correct ONOS instance IP
+            if confFileName in [ "zebradbgp1.conf", "zebradbgp2.conf" ]:
+                ip = controllerIPs[ index ]
+                index = ( index + 1 ) % len( controllerIPs )
+                with open( confFile ) as f:
+                    s = f.read()
+                s = re.sub( r"(fpm connection ip).*(port 2620)", r"\1 " + ip + r" \2", s )
+                with open( confFile, "w" ) as f:
+                    f.write( s )
+            copyResult = copyResult and main.ONOSbench.scp( main.Mininet1,
+                                                            confFile,
+                                                            "~/",
+                                                            direction="to" )
+
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=copyResult,
+                                 onpass="Successfully copied topo lib/conf files",
+                                 onfail="Failed to copy topo lib/conf files" )
+
+    def CASE5( self, main ):
+        """
+        Load Mininet topology and balances all switches
+        """
         import time
-        import copy
-
-        main.topoIndex = "topo" + str( main.params[ 'TEST' ][ 'topo' ] )
-
+        import re
         main.log.report( "Load Mininet topology and Balance all Mininet switches across controllers" )
         main.log.report( "________________________________________________________________________" )
         main.case( "Assign and Balance all Mininet switches across controllers" )
 
+        main.step( "Copy Mininet topology files" )
+        main.topoIndex = "topo" + str( main.params[ 'TEST' ][ 'topo' ] )
+        topoFileName = main.params[ 'TOPO' ][ main.topoIndex ][ 'fileName' ]
+        topoFile = main.testDir + "/dependencies/topologies/" + topoFileName
+        copyResult = main.ONOSbench.scp( main.Mininet1, topoFile, main.Mininet1.home + "/custom", direction="to" )
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=copyResult,
+                                 onpass="Successfully copied topo files",
+                                 onfail="Failed to copy topo files" )
+
         main.step( "Start Mininet topology" )
-        newTopo = main.params[ 'TOPO' ][ main.topoIndex ][ 'fileName' ]
-        mininetDir = main.Mininet1.home + "/custom/"
-        topoPath = main.testDir + "/dependencies/topologies/" + newTopo
-        main.ONOSbench.secureCopy( main.Mininet1.user_name, main.Mininet1.ip_address, topoPath, mininetDir, direction="to" )
-        topoPath = mininetDir + newTopo
-        startStatus = main.Mininet1.startNet( topoFile=topoPath )
-        main.mininetSwitches = main.Mininet1.getSwitches()
-        main.mininetHosts = main.Mininet1.getHosts()
-        main.mininetLinks = main.Mininet1.getLinks( timeout=60 )
+        startStatus = main.Mininet1.startNet( topoFile=main.Mininet1.home + "/custom/" + topoFileName,
+                                              args=main.params[ 'TOPO' ][ 'mininetArgs' ] )
+        main.mininetSwitches = main.Mininet1.getSwitches( switchClasses=r"(OVSSwitch)" )
         utilities.assert_equals( expect=main.TRUE,
                                  actual=startStatus,
                                  onpass="Start Mininet topology test PASS",
@@ -191,7 +258,7 @@
             ips = main.Cluster.getIps()
             main.Mininet1.assignSwController( sw=switchName, ip=ips )
             response = main.Mininet1.getSwController( switchName )
-            print( "Response is " + str( response ) )
+            main.log.debug( "Response is " + str( response ) )
             if re.search( "tcp:" + main.Cluster.active( 0 ).ipAddress, response ):
                 switchMastership = switchMastership and main.TRUE
             else:
@@ -201,13 +268,22 @@
                                  onpass="Assign switches to controllers test PASS",
                                  onfail="Assign switches to controllers test FAIL" )
         # Waiting here to make sure topology converges across all nodes
-        sleep = int( main.params[ 'TEST' ][ 'loadTopoSleep' ] )
+        sleep = int( main.params[ 'TOPO' ][ 'loadTopoSleep' ] )
         time.sleep( sleep )
 
         main.step( "Balance devices across controllers" )
         balanceResult = main.Cluster.active( 0 ).CLI.balanceMasters()
         # giving some breathing time for ONOS to complete re-balance
-        time.sleep( sleep )
+        time.sleep( 5 )
+
+        # Get mininet hosts and links
+        main.mininetHosts = main.Mininet1.getHosts()
+        if hasattr( main, "expectedHosts" ):
+            main.mininetHosts = { key: value for key, value in main.mininetHosts.items() if key in main.expectedHosts[ "network" ].keys() }
+        main.mininetLinks = main.Mininet1.getLinks( timeout=60 )
+        main.mininetLinks = [ link for link in main.mininetLinks if
+                              link[ 'node1' ] in main.mininetHosts.keys() + main.mininetSwitches.keys() and
+                              link[ 'node2' ] in main.mininetHosts.keys() + main.mininetSwitches.keys() ]
 
         caseResult = ( startStatus and switchMastership and balanceResult )
         utilities.assert_equals( expect=main.TRUE,
@@ -215,7 +291,7 @@
                                  onpass="Starting new Att topology test PASS",
                                  onfail="Starting new Att topology test FAIL" )
 
-    def CASE2( self, main ):
+    def CASE6( self, main ):
         """
         Collect and store device and link data from ONOS
         """
@@ -225,6 +301,7 @@
         main.log.report( "Collect and Store topology details from ONOS" )
         main.log.report( "____________________________________________________________________" )
         main.case( "Collect and Store Topology Details from ONOS" )
+
         topoResult = main.TRUE
         topologyOutput = main.Cluster.active( 0 ).CLI.topology()
         topologyResult = main.Cluster.active( 0 ).CLI.getTopology( topologyOutput )
@@ -238,13 +315,13 @@
             dpidToName = {}
             for key, value in main.mininetSwitches.items():
                 dpidToName[ 'of:' + str( value[ 'dpid' ] ) ] = key
-            devicesRaw = main.Cluster.active( 0 ).CLI.devices()
-            devices = json.loads( devicesRaw )
+            main.devicesRaw = main.Cluster.active( 0 ).CLI.devices()
+            devices = json.loads( main.devicesRaw )
             deviceInitIndex = 0
             for device in devices:
                 name = dpidToName[ device[ 'id' ] ]
                 newDevice = Device( deviceInitIndex, name, device[ 'id' ] )
-                print newDevice
+                main.log.info( 'New device: {}'.format( newDevice ) )
                 main.devices.append( newDevice )
                 deviceInitIndex += 1
             utilities.assert_equals( expect=main.TRUE,
@@ -254,8 +331,8 @@
 
             main.step( "Collect and store link data" )
             stepResult = main.TRUE
-            linksRaw = main.Cluster.active( 0 ).CLI.links()
-            links = json.loads( linksRaw )
+            main.linksRaw = main.Cluster.active( 0 ).CLI.links()
+            links = json.loads( main.linksRaw )
             linkInitIndex = 0
             for link in links:
                 for device in main.devices:
@@ -265,7 +342,7 @@
                         deviceB = device
                 assert deviceA is not None and deviceB is not None
                 newLink = Link( linkInitIndex, deviceA, link[ 'src' ][ 'port' ], deviceB, link[ 'dst' ][ 'port' ] )
-                print newLink
+                main.log.info( 'New link: {}'.format( newLink ) )
                 main.links.append( newLink )
                 linkInitIndex += 1
             # Set backward links and outgoing links of devices
@@ -301,7 +378,7 @@
             main.log.info( "Topology does not match, exiting test..." )
             main.cleanAndExit()
 
-    def CASE3( self, main ):
+    def CASE7( self, main ):
         """
         Collect and store host data from ONOS
         """
@@ -310,28 +387,41 @@
 
         main.log.report( "Collect and store host adta from ONOS" )
         main.log.report( "______________________________________________" )
-        main.case( "Use fwd app and pingall to discover all the hosts, then collect and store host data" )
+        main.case( "Use fwd app and pingall to discover all the hosts if necessary, then collect and store host data" )
 
-        main.step( "Enable Reactive forwarding" )
-        appResult = main.controllers[ 0 ].CLI.activateApp( "org.onosproject.fwd" )
-        cfgResult1 = main.TRUE
-        cfgResult2 = main.TRUE
-        if main.enableIPv6:
-            cfgResult1 = main.controllers[ 0 ].CLI.setCfg( "org.onosproject.fwd.ReactiveForwarding", "ipv6Forwarding", "true" )
-            cfgResult2 = main.controllers[ 0 ].CLI.setCfg( "org.onosproject.fwd.ReactiveForwarding", "matchIpv6Address", "true" )
-        stepResult = appResult and cfgResult1 and cfgResult2
-        utilities.assert_equals( expect=main.TRUE,
-                                 actual=stepResult,
-                                 onpass="Successfully enabled reactive forwarding",
-                                 onfail="Failed to enable reactive forwarding" )
+        if main.params[ 'TEST' ][ 'dataPlaneConnectivity' ] == 'False':
+            main.step( "Enable Reactive forwarding" )
+            appResult = main.Cluster.active( 0 ).CLI.activateApp( "org.onosproject.fwd" )
+            cfgResult1 = main.TRUE
+            cfgResult2 = main.TRUE
+            if main.enableIPv6:
+                cfgResult1 = main.Cluster.active( 0 ).CLI.setCfg( "org.onosproject.fwd.ReactiveForwarding", "ipv6Forwarding", "true" )
+                cfgResult2 = main.Cluster.active( 0 ).CLI.setCfg( "org.onosproject.fwd.ReactiveForwarding", "matchIpv6Address", "true" )
+            stepResult = appResult and cfgResult1 and cfgResult2
+            utilities.assert_equals( expect=main.TRUE,
+                                     actual=stepResult,
+                                     onpass="Successfully enabled reactive forwarding",
+                                     onfail="Failed to enable reactive forwarding" )
 
-        main.step( "Discover hosts using pingall" )
+            main.step( "Discover hosts using pingall" )
+            main.Mininet1.pingall()
+            if main.enableIPv6:
+                ping6Result = main.Mininet1.pingall( protocol="IPv6" )
+
+            main.step( "Disable Reactive forwarding" )
+            appResult = main.Cluster.active( 0 ).CLI.deactivateApp( "org.onosproject.fwd" )
+            stepResult = appResult
+            utilities.assert_equals( expect=main.TRUE,
+                                     actual=stepResult,
+                                     onpass="Successfully deactivated fwd app",
+                                     onfail="Failed to deactivate fwd app" )
+
+        main.step( "Verify host discovery" )
         stepResult = main.TRUE
-        main.Mininet1.pingall()
-        if main.enableIPv6:
-            ping6Result = main.Mininet1.pingall( protocol="IPv6" )
-        hosts = main.controllers[ 0 ].CLI.hosts()
-        hosts = json.loads( hosts )
+        main.hostsRaw = main.Cluster.active( 0 ).CLI.hosts()
+        hosts = json.loads( main.hostsRaw )
+        if hasattr( main, "expectedHosts" ):
+            hosts = [ host for host in hosts if host[ 'id' ] in main.expectedHosts[ 'onos' ].keys() ]
         if not len( hosts ) == len( main.mininetHosts ):
             stepResult = main.FALSE
         utilities.assert_equals( expect=main.TRUE,
@@ -342,14 +432,6 @@
             main.log.debug( hosts )
             main.cleanAndExit()
 
-        main.step( "Disable Reactive forwarding" )
-        appResult = main.controllers[ 0 ].CLI.deactivateApp( "org.onosproject.fwd" )
-        stepResult = appResult
-        utilities.assert_equals( expect=main.TRUE,
-                                 actual=stepResult,
-                                 onpass="Successfully deactivated fwd app",
-                                 onfail="Failed to deactivate fwd app" )
-
         main.step( "Collect and store host data" )
         stepResult = main.TRUE
         macToName = {}
@@ -361,33 +443,52 @@
         hostInitIndex = 0
         for host in hosts:
             name = macToName[ host[ 'mac' ] ]
-            dpid = host[ 'locations' ][ 0 ][ 'elementId' ]
-            device = dpidToDevice[ dpid ]
+            devices = {}
+            for location in host[ 'locations' ]:
+                device = dpidToDevice[ location[ 'elementId' ] ]
+                devices[ device ] = location[ 'port' ]
             newHost = Host( hostInitIndex,
                             name, host[ 'id' ], host[ 'mac' ],
-                            device, host[ 'locations' ][ 0 ][ 'port' ],
+                            devices,
                             host[ 'vlan' ], host[ 'ipAddresses' ] )
-            print newHost
+            main.log.info( 'New host: {}'.format( newHost ) )
             main.hosts.append( newHost )
-            main.devices[ device.index ].hosts.append( newHost )
             hostInitIndex += 1
+            for location in host[ 'locations' ]:
+                device = dpidToDevice[ location[ 'elementId' ] ]
+                main.devices[ device.index ].hosts[ newHost ] = location[ 'port' ]
+
+        # Collect IPv4 and IPv6 hosts
+        main.ipv4Hosts = []
+        main.ipv6Hosts = []
+        for host in main.hosts:
+            if any( re.match( str( main.params[ 'TEST' ][ 'ipv6Regex' ] ), ipAddress ) for ipAddress in host.ipAddresses ):
+                main.ipv6Hosts.append( host )
+            if any( re.match( str( main.params[ 'TEST' ][ 'ipv4Regex' ] ), ipAddress ) for ipAddress in host.ipAddresses ):
+                main.ipv4Hosts.append( host )
         utilities.assert_equals( expect=main.TRUE,
                                  actual=stepResult,
                                  onpass="Successfully collected and stored host data",
                                  onfail="Failed to collect and store host data" )
 
         main.step( "Create one host component for each host and then start host cli" )
+        startCLIResult = main.TRUE
         for host in main.hosts:
             main.Mininet1.createHostComponent( host.name )
             hostHandle = getattr( main, host.name )
             main.log.info( "Starting CLI on host " + str( host.name ) )
-            startCLIResult = hostHandle.startHostCli()
+            startCLIResult = startCLIResult and hostHandle.startHostCli()
             host.setHandle( hostHandle )
-            stepResult = startCLIResult
-            utilities.assert_equals( expect=main.TRUE,
-                                     actual=startCLIResult,
-                                     onpass="Host CLI started",
-                                     onfail="Failed to start host CLI" )
+            if main.params[ 'TEST' ][ 'dataPlaneConnectivity' ] == 'True':
+                # Hosts should already be able to ping each other
+                if host in main.ipv4Hosts:
+                    host.correspondents += main.ipv4Hosts
+                if host in main.ipv6Hosts:
+                    host.correspondents += main.ipv6Hosts
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=startCLIResult,
+                                 onpass="Host CLI started",
+                                 onfail="Failed to start host CLI" )
 
     def CASE10( self, main ):
         """
@@ -711,8 +812,10 @@
         pointIntentNum = 0
         downDeviceNum = 0
         downLinkNum = 0
+        downPortNum = 0
+        downOnosNum = 0
         flowObj = False
-        upControllers = [ 1, 2, 3 ]
+        upControllers = range( 1, int( main.params[ 'TEST' ][ 'numCtrl' ] ) + 1 )
         while True:
             events = []
             for i in range( int( main.params[ 'CASE70' ][ 'toggleFlowObj' ] ) ):
@@ -725,14 +828,23 @@
                 events.append( 'link-down' )
             for i in range( int( main.params[ 'CASE70' ][ 'deviceDownWeight' ] ) ):
                 events.append( 'device-down' )
+            for i in range( int( main.params[ 'CASE70' ][ 'portDownWeight' ] ) ):
+                events.append( 'port-down' )
+            if downOnosNum == 0:
+                for i in range( int( main.params[ 'CASE70' ][ 'onosDownWeight' ] ) ):
+                    events.append( 'onos-down' )
             for i in range( int( pow( hostIntentNum, 1.5 ) / 100 ) ):
                 events.append( 'del-host-intent' )
             for i in range( int( pow( pointIntentNum, 1.5 ) / 100 ) ):
                 events.append( 'del-point-intent' )
-            for i in range( pow( 2, downLinkNum ) - 1 ):
+            for i in range( pow( 4, downLinkNum ) - 1 ):
                 events.append( 'link-up' )
-            for i in range( pow( 5, downDeviceNum ) - 1 ):
+            for i in range( pow( 4, downDeviceNum ) - 1 ):
                 events.append( 'device-up' )
+            for i in range( pow( 4, downPortNum ) - 1 ):
+                events.append( 'port-up' )
+            for i in range( pow( 4, downOnosNum ) - 1 ):
+                events.append( 'onos-up' )
             main.log.debug( events )
             event = random.sample( events, 1 )[ 0 ]
             if event == 'add-host-intent':
@@ -771,6 +883,19 @@
             elif event == 'device-up':
                 main.eventGenerator.triggerEvent( EventType().NETWORK_DEVICE_UP, EventScheduleMethod().RUN_BLOCK, 'random' )
                 downDeviceNum -= 1
+            elif event == 'port-down':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_PORT_DOWN, EventScheduleMethod().RUN_BLOCK, 'random', 'random' )
+                downPortNum += 1
+            elif event == 'port-up':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_PORT_UP, EventScheduleMethod().RUN_BLOCK, 'random', 'random' )
+                downPortNum -= 1
+            elif event == 'onos-down':
+                main.eventGenerator.triggerEvent( EventType().ONOS_ONOS_DOWN, EventScheduleMethod().RUN_BLOCK, 1 )
+                downOnosNum += 1
+            elif event == 'onos-up':
+                main.eventGenerator.triggerEvent( EventType().ONOS_ONOS_UP, EventScheduleMethod().RUN_BLOCK, 1 )
+                downOnosNum -= 1
+                main.eventGenerator.triggerEvent( EventType().ONOS_BALANCE_MASTERS, EventScheduleMethod().RUN_BLOCK )
             elif event == 'toggle-flowobj':
                 if not flowObj:
                     main.eventGenerator.triggerEvent( EventType().ONOS_SET_FLOWOBJ, EventScheduleMethod().RUN_BLOCK, 'true' )
@@ -828,7 +953,7 @@
                     main.eventGenerator.triggerEvent( eventIndex, EventScheduleMethod().RUN_BLOCK, *args )
                     time.sleep( float( main.params[ 'CASE80' ][ 'sleepTime' ] ) )
         except Exception as e:
-            print e
+            main.log.error( "Uncaught exception: {}".format( e ) )
         utilities.assert_equals( expect=main.TRUE,
                                  actual=main.caseResult,
                                  onpass="Replay from log file passed",
diff --git a/TestON/tests/CHOTestMonkey/dependencies/EventScheduler.py b/TestON/tests/CHOTestMonkey/dependencies/EventScheduler.py
index 480fb9a..3685a70 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/EventScheduler.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/EventScheduler.py
@@ -60,7 +60,7 @@
         param:
             index: the position to insert into pendingEvents, default value -1 implies the tail of pendingEvents
         """
-        if not typeIndex in main.enabledEvents.keys():
+        if typeIndex not in main.enabledEvents.keys():
             main.log.warn( "Event Scheduler - event type %s not enabled" % ( typeIndex ) )
             return
         if main.enabledEvents[ typeIndex ] in main.params[ 'EVENT' ].keys():
@@ -84,7 +84,7 @@
             else:
                 main.log.warn( "Event Scheduler - invalid index when isnerting event: %s" % ( index ) )
             self.pendingEventsCondition.notify()
-        #self.printEvents()
+        # self.printEvents()
 
     def startScheduler( self ):
         """
@@ -133,7 +133,7 @@
 
         with self.runningEventsCondition:
             self.runningEvents.append( eventTuple )
-        #self.printEvents()
+        # self.printEvents()
         rerunNum = 0
         result = eventTuple.startEvent()
         while result == EventStates().FAIL and rerunNum < eventTuple.maxRerunNum:
@@ -145,9 +145,8 @@
             main.log.error( eventTuple.typeString + " failed" )
             main.caseResult = main.FALSE
             if main.params[ 'TEST' ][ 'pauseTest' ] == 'on':
-                #self.isRunning = False
-                #main.log.error( "Event Scheduler - Test paused. To resume test, run \'resume-test\' command in CLI debugging mode" )
-                main.stop()
+                self.isRunning = False
+                main.log.error( "Event Scheduler - Test paused. To resume test, run \'resume-test\' command in CLI debugging mode" )
         with self.runningEventsCondition:
             self.runningEvents.remove( eventTuple )
             if len( self.runningEvents ) == 0:
@@ -156,7 +155,7 @@
                     if len( self.pendingEvents ) == 0:
                         with self.idleCondition:
                             self.idleCondition.notify()
-        #self.printEvents()
+        # self.printEvents()
 
     def printEvents( self ):
         """
@@ -198,5 +197,5 @@
         self.pendingEventsCapacity = capacity
 
     def setRunningState( self, state ):
-        assert state or state == False
+        assert state or state is False
         self.isRunning = state
diff --git a/TestON/tests/CHOTestMonkey/dependencies/cli.py b/TestON/tests/CHOTestMonkey/dependencies/cli.py
index 6e67107..8985242 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/cli.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/cli.py
@@ -114,7 +114,7 @@
 if __name__ == '__main__':
     import xml.etree.ElementTree
     try:
-        root = xml.etree.ElementTree.parse( '../CHOTestMonkey.params' ).getroot()
+        root = xml.etree.ElementTree.parse( '../CHOTestMonkey.params.trellis' ).getroot()
     except Exception:
         print "Uncaught exception!"
     for child in root:
diff --git a/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py b/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
index 50d6159..97cc92c 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
@@ -54,7 +54,8 @@
         NetworkElement.__init__( self, index )
         self.name = name
         self.dpid = dpid
-        self.hosts = []
+        self.hosts = {}
+        self.downPorts = []
         # For each bidirectional link, we only store one direction here
         self.outgoingLinks = []
 
@@ -64,24 +65,27 @@
 
 class Host( NetworkElement ):
 
-    def __init__( self, index, name, id, mac, device, devicePort, vlan, ipAddresses ):
+    def __init__( self, index, name, id, mac, devices, vlan, ipAddresses ):
         NetworkElement.__init__( self, index )
         self.name = name
         self.id = id
         self.mac = mac
-        self.device = device
-        self.devicePort = devicePort
+        self.devices = devices
         self.vlan = vlan
         self.ipAddresses = ipAddresses
         self.correspondents = []
         self.handle = None
+        self.isDualHomed = True if len( self.devices ) == 2 else False
 
     def __str__( self ):
-        return "name: " + self.name + ", mac: " + self.mac + ", device: " + self.device.dpid + ", ipAddresses: " + str( self.ipAddresses ) + ", status: " + self.status
+        return "name: " + self.name + ", mac: " + self.mac + ", devices: " + str( [ device.dpid for device in self.devices ] ) + ", ipAddresses: " + str( self.ipAddresses ) + ", status: " + self.status
 
     def setHandle( self, handle ):
         self.handle = handle
 
+    def setRemoved( self ):
+        if all( device.isRemoved() for device in self.devices ):
+            self.status = 'removed'
 
 class Link( NetworkElement ):
 
diff --git a/TestON/tests/CHOTestMonkey/dependencies/elements/ONOSElement.py b/TestON/tests/CHOTestMonkey/dependencies/elements/ONOSElement.py
index 688324c..182ecf2 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/elements/ONOSElement.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/elements/ONOSElement.py
@@ -46,9 +46,13 @@
 
     def bringDown( self ):
         self.status = 'down'
+        main.Cluster.runningNodes[ self.index - 1 ].active = False
+        main.Cluster.reset()
 
     def bringUp( self ):
         self.status = 'up'
+        main.Cluster.runningNodes[ self.index - 1 ].active = True
+        main.Cluster.reset()
 
 
 class Intent:
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py
index 7361c8f..ccb5831 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py
@@ -147,20 +147,23 @@
                     upHostNum += 1
         clusterNum = 1
         with main.mininetLock:
-            graphDictMininet = main.Mininet1.getGraphDict( useId=True )
+            graphDictMininet = main.Mininet1.getGraphDict( useId=True, switchClasses=r"(OVSSwitch)",
+                                                           excludeNodes=[ 'bgp', 'cs', 'nat', 'dhcp', 'r' ] )
         for controller in main.controllers:
             if controller.isUp():
                 with controller.CLILock:
+                    '''
                     topoState = controller.CLI.checkStatus( upDeviceNum, upLinkNum )
-                    # if not topoState:
-                    #    main.log.warn( "Topo Check - link or device number discoverd by ONOS%s is incorrect" % ( controller.index ) )
-                    #    checkResult = EventStates().FAIL
+                    if not topoState:
+                        main.log.warn( "Topo Check - link or device number discoverd by ONOS%s is incorrect" % ( controller.index ) )
+                        checkResult = EventStates().FAIL
                     # Compare ONOS and Mininet topologies
                     graphDictONOS = controller.CLI.getGraphDict()
                     compareResult = main.graph.compareGraphs( graphDictONOS, graphDictMininet )
                     if not compareResult:
                         checkResult = EventStates().FAIL
                         main.log.warn( "Topo Check - ONOS and Mininet topologies do not match" )
+                    '''
                     try:
                         # Check links
                         links = controller.CLI.links()
@@ -181,8 +184,10 @@
                         # Check hosts
                         hosts = controller.CLI.hosts()
                         hosts = json.loads( hosts )
+                        if hasattr( main, "expectedHosts" ):
+                            hosts = [ host for host in hosts if host[ 'id' ] in main.expectedHosts[ 'onos' ].keys() ]
                         if not len( hosts ) == upHostNum:
-                            checkResult = EventStates().FAIL
+                            # checkResult = EventStates().FAIL
                             main.log.warn( "Topo Check - host number discoverd by ONOS%s is incorrect: %s expected and %s actual" % ( controller.index, upHostNum, len( hosts ) ) )
                         # Check clusters
                         clusters = controller.CLI.clusters()
@@ -288,6 +293,7 @@
         for host in main.hosts:
             if host.isUp():
                 upHosts.append( host )
+        import re
         for host in upHosts:
             dstIPv4List[ host.index ] = []
             dstIPv6List[ host.index ] = []
@@ -295,10 +301,12 @@
                 if correspondent not in upHosts:
                     continue
                 for ipAddress in correspondent.ipAddresses:
-                    if ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv6Prefix' ] ) ):
+                    if re.match( str( main.params[ 'TEST' ][ 'ipv6Regex' ] ), ipAddress ):
                         dstIPv6List[ host.index ].append( ipAddress )
-                    elif ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv4Prefix' ] ) ):
+                    elif re.match( str( main.params[ 'TEST' ][ 'ipv4Regex' ] ), ipAddress ):
                         dstIPv4List[ host.index ].append( ipAddress )
+            if dstIPv4List[ host.index ]:
+                main.log.debug( "Check ping from host {} to {}".format( host.name, dstIPv4List[ host.index ] ) )
             thread = main.Thread( target=host.handle.pingHostSetAlternative,
                                   threadID=main.threadID,
                                   name="pingHostSetAlternative",
@@ -317,6 +325,8 @@
             return checkResult
         # Check ipv6 ping
         for host in upHosts:
+            if dstIPv6List[ host.index ]:
+                main.log.debug( "Check ping from host {} to {}".format( host.name, dstIPv6List[ host.index ] ) )
             thread = main.Thread( target=host.handle.pingHostSetAlternative,
                                   threadID=main.threadID,
                                   name="pingHostSetAlternative",
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py
index 5632e12..935226c 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py
@@ -51,7 +51,8 @@
             if args[ 0 ] == 'random' or args[ 1 ] == 'random':
                 if self.typeIndex == EventType().NETWORK_LINK_DOWN:
                     with main.mininetLock:
-                        linkRandom = main.Mininet1.getLinkRandom()
+                        linkRandom = main.Mininet1.getLinkRandom( switchClasses=r"(OVSSwitch)",
+                                                                  excludeNodes=[ 'bgp', 'cs', 'nat', 'dhcp', 'r' ] )
                     if linkRandom is None:
                         main.log.warn( "No link available, aborting event" )
                         return EventStates().ABORT
@@ -188,7 +189,8 @@
                 import random
                 if self.typeIndex == EventType().NETWORK_DEVICE_DOWN:
                     with main.mininetLock:
-                        switchRandom = main.Mininet1.getSwitchRandom()
+                        switchRandom = main.Mininet1.getSwitchRandom( switchClasses=r"(OVSSwitch)",
+                                                                      excludeNodes=[ 'bgp', 'cs', 'nat', 'dhcp', 'r' ] )
                     if switchRandom is None:
                         main.log.warn( "No switch available, aborting event" )
                         return EventStates().ABORT
@@ -232,8 +234,15 @@
                 main.log.warn( "Device Down - device has been removed" )
                 return EventStates().ABORT
         main.log.info( "Event recorded: {} {} {}".format( self.typeIndex, self.typeString, self.device.name ) )
+        result = main.TRUE
         with main.mininetLock:
-            result = main.Mininet1.delSwitch( self.device.name )
+            # Disable ports toward dual-homed hosts
+            for host, port in self.device.hosts.items():
+                if host.isDualHomed:
+                    main.log.info( "Disable port {}/{} which connects to a dual-homed host before bringing down this device".format( self.device.dpid, port ) )
+                    result = result and main.Cluster.active( 0 ).CLI.portstate( dpid=self.device.dpid, port=port, state="disable" )
+            # result = main.Mininet1.delSwitch( self.device.name )
+            result = result and main.Mininet1.switch( SW=self.device.name, OPTION="stop" )
         if not result:
             main.log.warn( "%s - failed to bring down device" % ( self.typeString ) )
             return EventStates().FAIL
@@ -269,12 +278,14 @@
         # Re-add the device
         main.log.info( "Event recorded: {} {} {}".format( self.typeIndex, self.typeString, self.device.name ) )
         with main.mininetLock:
-            result = main.Mininet1.addSwitch( self.device.name, dpid=self.device.dpid[ 3: ] )
+            # result = main.Mininet1.addSwitch( self.device.name, dpid=self.device.dpid[ 3: ] )
+            result = main.Mininet1.switch( SW=self.device.name, OPTION='start' )
         if not result:
             main.log.warn( "%s - failed to re-add device" % ( self.typeString ) )
             return EventStates().FAIL
         with main.variableLock:
             self.device.bringUp()
+        '''
         # Re-add links
         # We add host-device links first since we did the same in mininet topology file
         # TODO: a more rubust way is to add links according to the port info of the device
@@ -303,35 +314,168 @@
                         if intent.deviceA == self.device and intent.deviceB.isUp() or\
                                 intent.deviceB == self.device and intent.deviceA.isUp():
                             intent.setInstalled()
+        '''
         # Re-assign mastership for the device
         with main.mininetLock:
             ips = main.Cluster.getIps()
             main.Mininet1.assignSwController( sw=self.device.name, ip=ips )
-        # Re-discover hosts
-        for host in self.device.hosts:
-            correspondent = None
-            for h in main.hosts:
-                if h.isUp() and h != host:
-                    correspondent = h
-                    break
-            if correspondent is None:
-                with main.mininetLock:
-                    main.Mininet1.pingall()
-                    if main.enableIPv6:
-                        main.Mininet1.pingall( protocol="IPv6" )
+        for link in self.device.outgoingLinks:
+            neighbor = link.deviceB
+            # Skip bringing up any link that connecting this device to a removed neighbor
+            if neighbor.isRemoved():
+                continue
+            # Bring down again any link that was brought down before the device was down
+            if int( link.portB ) in link.deviceB.downPorts:
+                with main.variableLock:
+                    link.bringDown()
+                    link.backwardLink.bringDown()
             else:
-                ipv4Addr = None
-                ipv6Addr = None
-                for ipAddress in correspondent.ipAddresses:
-                    if ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv6Prefix' ] ) ) and ipv6Addr is None:
-                        ipv6Addr = ipAddress
-                    elif ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv4Prefix' ] ) ) and ipv4Addr is None:
-                        ipv4Addr = ipAddress
-                assert ipv4Addr is not None
-                host.handle.pingHostSetAlternative( [ ipv4Addr ], 1 )
-                if main.enableIPv6:
-                    assert ipv6Addr is not None
-                    host.handle.pingHostSetAlternative( [ ipv6Addr ], 1, True )
+                with main.variableLock:
+                    link.bringUp()
+                    link.backwardLink.bringUp()
+        # Re-discover hosts
+        if self.device.hosts:
+            main.Mininet1.discoverHosts( hostList=[ host.name for host in self.device.hosts ] )
+        for host in self.device.hosts:
             with main.variableLock:
                 host.bringUp()
+        self.device.downPorts = []
+        return EventStates().PASS
+
+
+class PortEvent( Event ):
+
+    def __init__( self ):
+        Event.__init__( self )
+        self.device = None
+        self.port = None
+        self.link = None
+
+    def startPortEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        """
+        args are the device name and port number, e.g. [ 's1', '5' ]
+        """
+        with self.eventLock:
+            # main.log.info( "%s - starting event" % ( self.typeString ) )
+            if len( args ) < 2:
+                main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            elif len( args ) > 2:
+                main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            if args[ 0 ] == 'random' or args[ 1 ] == 'random':
+                if self.typeIndex == EventType().NETWORK_PORT_DOWN:
+                    with main.mininetLock:
+                        linkRandom = main.Mininet1.getLinkRandom( switchClasses=r"(OVSSwitch)",
+                                                                  excludeNodes=[ 'bgp', 'cs', 'nat', 'dhcp', 'r' ])
+                    if linkRandom is None:
+                        main.log.warn( "No link available, aborting event" )
+                        return EventStates().ABORT
+                    for link in main.links:
+                        if link.deviceA.name == linkRandom[ 0 ] and link.deviceB.name == linkRandom[ 1 ]:
+                            self.device = link.deviceA
+                            self.port = int( link.portA )
+                    if not self.device:
+                        main.log.warn( "Failed to get a radnom device port, aborting event" )
+                        return EventStates().ABORT
+                elif self.typeIndex == EventType().NETWORK_PORT_UP:
+                    import random
+                    with main.variableLock:
+                        downPorts = {}
+                        for link in main.links:
+                            if link.isDown():
+                                if int( link.portA ) in link.deviceA.downPorts:
+                                    downPorts[ link.deviceA ] = link.portA
+                        if len( downPorts ) == 0:
+                            main.log.warn( "None of the links are in 'down' state, aborting event" )
+                            return EventStates().ABORT
+                        deviceList = random.sample( downPorts, 1 )
+                        self.device = deviceList[ 0 ]
+                        self.port = int( downPorts[ self.device ] )
+            if self.device is None:
+                for device in main.devices:
+                    if device.name == args[ 0 ]:
+                        self.device = device
+                if self.device is None:
+                    main.log.warn( "Device %s does not exist: " % ( args[ 0 ] ) )
+                    return EventStates().ABORT
+            if self.port is None:
+                try:
+                    self.port = int( args[ 1 ] )
+                except Exception:
+                    main.log.warn( "Device port is not a number: {}".format( args[ 1 ] ) )
+                    return EventStates().ABORT
+            if self.link is None:
+                for link in main.links:
+                    if link.deviceA.name == self.device.name and int( link.portA ) == self.port:
+                        self.link = link
+                if self.link is None:
+                    main.log.warn( "There's no link on device {} port {}".format( self.device.name, self.port ) )
+                    return EventStates().ABORT
+            main.log.debug( "%s - %s:%s" % ( self.typeString, self.device, self.port ) )
+            return self.startPortEvent()
+
+
+class PortDown( PortEvent ):
+
+    """
+    Generate a port down event giving the device name and port number
+    """
+    def __init__( self ):
+        PortEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startPortEvent( self ):
+        assert self.device is not None and self.port is not None and self.link is not None
+        if self.link.isDown():
+            main.log.warn( "port Down - link already down" )
+            return EventStates().ABORT
+        elif self.link.isRemoved():
+            main.log.warn( "port Down - link has been removed" )
+            return EventStates().ABORT
+        main.log.info( "Event recorded: {} {} {} {}".format( self.typeIndex, self.typeString, self.device.name, self.port ) )
+        with main.mininetLock:
+            result = main.Cluster.active( 0 ).CLI.portstate( dpid=self.device.dpid, port=self.port, state="disable" )
+        if not result:
+            main.log.warn( "%s - failed to bring down port" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.device.downPorts.append( self.port )
+            self.link.bringDown()
+            self.link.backwardLink.bringDown()
+        return EventStates().PASS
+
+
+class PortUp( PortEvent ):
+
+    """
+    Generate a port up event giving the device name and port number
+    """
+    def __init__( self ):
+        PortEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startPortEvent( self ):
+        assert self.device is not None and self.port is not None and self.link is not None
+        if self.link.isUp():
+            main.log.warn( "port up - link already up" )
+            return EventStates().ABORT
+        elif self.link.isRemoved():
+            main.log.warn( "port up - link has been removed" )
+            return EventStates().ABORT
+        main.log.info( "Event recorded: {} {} {} {}".format( self.typeIndex, self.typeString, self.device.name, self.port ) )
+        with main.mininetLock:
+            result = main.Cluster.active( 0 ).CLI.portstate( dpid=self.device.dpid, port=self.port, state="enable" )
+        if not result:
+            main.log.warn( "%s - failed to bring up port " % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.device.downPorts.remove( self.port )
+            self.link.bringUp()
+            self.link.backwardLink.bringUp()
         return EventStates().PASS
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py
index 1630066..0fa7f25 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py
@@ -68,7 +68,7 @@
                 return EventStates().ABORT
         main.log.info( "Event recorded: {} {} {}".format( self.typeIndex, self.typeString, self.ONOSIndex ) )
         with main.ONOSbenchLock:
-            result = main.ONOSbench.onosStop( main.controllers[ self.ONOSIndex - 1 ].ip )
+            result = main.ONOSbench.onosDie( main.controllers[ self.ONOSIndex - 1 ].ip )
         if not result:
             main.log.warn( "%s - failed to bring down ONOS" % ( self.typeString ) )
             return EventStates().FAIL
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py
index 866c219..912487e 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py
@@ -85,3 +85,16 @@
         sleepTime = int( args[ 0 ] )
         time.sleep( sleepTime )
         return result
+
+
+class TestDebug( TestEvent ):
+
+    def __init__( self ):
+        TestEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startTestEvent( self, args=None ):
+        result = EventStates().PASS
+        main.stop()
+        return result
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp1.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp1.conf
new file mode 100644
index 0000000..474bf02
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp1.conf
@@ -0,0 +1,141 @@
+log file /var/log/quagga/bgpdbgp1.log
+hostname bgp1
+password quagga
+!
+! Different next hop for IPv4
+!
+ip prefix-list 1 seq 10 permit 10.0.2.0/24
+ip prefix-list 1 seq 20 permit 10.1.2.0/24
+ip prefix-list 1 seq 30 permit 10.0.3.0/24
+ip prefix-list 1 seq 40 permit 10.0.4.0/24
+
+ip prefix-list 1 seq 50 permit 10.1.0.0/24
+ip prefix-list 1 seq 70 permit 10.1.10.0/24
+ip prefix-list 1 seq 80 permit 10.2.0.0/24
+ip prefix-list 1 seq 90 permit 10.2.30.0/24
+ip prefix-list 1 seq 100 permit 10.2.20.0/24
+ip prefix-list 1 seq 110 permit 10.2.10.0/24
+ip prefix-list 1 seq 120 permit 10.2.40.0/24
+ip prefix-list 1 seq 130 permit 10.3.0.0/24
+ip prefix-list 1 seq 140 permit 10.3.30.0/24
+ip prefix-list 1 seq 150 permit 10.3.10.0/24
+ip prefix-list 1 seq 160 permit 10.3.20.0/24
+ip prefix-list 1 seq 170 permit 10.5.10.0/24
+ip prefix-list 1 seq 180 permit 10.5.20.0/24
+
+!
+route-map NEXTHOP41 permit 10
+match ip address prefix-list 1
+set ip next-hop 10.0.1.254
+!
+!
+route-map NEXTHOP47 permit 10
+match ip address prefix-list 1
+set ip next-hop 10.0.7.254
+!
+! Different next hop for IPv6
+!
+ipv6 prefix-list 2 seq 10 permit 2000::200/120
+ipv6 prefix-list 2 seq 20 permit 2000::300/120
+
+ipv6 prefix-list 2 seq 30 permit 1000::300/120
+ipv6 prefix-list 2 seq 40 permit 1001::300/120
+ipv6 prefix-list 2 seq 50 permit 1002::300/120
+ipv6 prefix-list 2 seq 60 permit 1003::300/120
+ipv6 prefix-list 2 seq 70 permit 1004::300/120
+ipv6 prefix-list 2 seq 80 permit 1005::300/120
+ipv6 prefix-list 2 seq 90 permit 1006::300/120
+ipv6 prefix-list 2 seq 100 permit 1007::300/120
+ipv6 prefix-list 2 seq 110 permit 1008::300/120
+ipv6 prefix-list 2 seq 120 permit 1009::300/120
+ipv6 prefix-list 2 seq 130 permit 1010::300/120
+ipv6 prefix-list 2 seq 140 permit 1011::300/120
+ipv6 prefix-list 2 seq 150 permit 1012::300/120
+!
+route-map NEXTHOP61 permit 10
+match ipv6 address prefix-list 2
+set ipv6 next-hop global 2000::1ff
+set ipv6 next-hop local 2000::1ff
+!
+!
+route-map NEXTHOP67 permit 10
+match ipv6 address prefix-list 2
+set ipv6 next-hop global 2000::7ff
+set ipv6 next-hop local 2000::7ff
+!
+! Basic router config
+!
+router bgp 65003
+bgp router-id 172.16.0.3
+timers bgp 3 9
+!
+! IPv4
+!
+neighbor 10.0.1.1 remote-as 65001
+neighbor 10.0.1.1 ebgp-multihop
+neighbor 10.0.1.1 timers connect 5
+neighbor 10.0.1.1 advertisement-interval 5
+neighbor 10.0.1.1 route-map NEXTHOP41 out
+!
+neighbor 2000::101 remote-as 65001
+neighbor 2000::101 timers connect 5
+neighbor 2000::101 advertisement-interval 1
+no neighbor 2000::101 activate
+!
+neighbor 10.0.7.1 remote-as 65002
+neighbor 10.0.7.1 ebgp-multihop
+neighbor 10.0.7.1 timers connect 5
+neighbor 10.0.7.1 advertisement-interval 5
+neighbor 10.0.7.1 route-map NEXTHOP47 out
+!
+neighbor 2000::701 remote-as 65002
+neighbor 2000::701 timers connect 5
+neighbor 2000::701 advertisement-interval 1
+no neighbor 2000::701 activate
+!
+
+network 10.1.0.0/24
+network 10.1.10.0/24
+network 10.2.0.0/24
+network 10.2.30.0/24
+network 10.2.20.0/24
+network 10.2.10.0/24
+network 10.2.40.0/24
+network 10.3.0.0/24
+network 10.3.30.0/24
+network 10.3.10.0/24
+network 10.3.20.0/24
+network 10.5.10.0/24
+network 10.5.20.0/24
+
+network 10.0.2.0/24
+network 10.1.2.0/24
+network 10.0.3.0/24
+network 10.0.4.0/24
+!
+! IPv6
+!
+address-family ipv6
+
+network 1000::300/120
+network 1001::300/120
+network 1002::300/120
+network 1003::300/120
+network 1004::300/120
+network 1005::300/120
+network 1006::300/120
+network 1007::300/120
+network 1008::300/120
+network 1009::300/120
+network 1010::300/120
+network 1011::300/120
+network 1012::300/120
+
+network 2000::200/120
+network 2000::300/120
+
+neighbor 2000::101 activate
+neighbor 2000::101 route-map NEXTHOP61 out
+neighbor 2000::701 activate
+neighbor 2000::701 route-map NEXTHOP67 out
+exit-address-family
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp2.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp2.conf
new file mode 100644
index 0000000..421d5c2
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdbgp2.conf
@@ -0,0 +1,143 @@
+log file /var/log/quagga/bgpdbgp2.log
+hostname bgp2
+password quagga
+!
+! Different next hop for IPv4
+!
+ip prefix-list 1 seq 10 permit 10.0.2.0/24
+ip prefix-list 1 seq 20 permit 10.1.2.0/24
+ip prefix-list 1 seq 30 permit 10.0.3.0/24
+ip prefix-list 1 seq 40 permit 10.0.4.0/24
+
+ip prefix-list 1 seq 50 permit 10.1.0.0/24
+ip prefix-list 1 seq 60 permit 10.1.10.0/24
+ip prefix-list 1 seq 70 permit 10.2.0.0/24
+ip prefix-list 1 seq 80 permit 10.2.30.0/24
+ip prefix-list 1 seq 90 permit 10.2.20.0/24
+ip prefix-list 1 seq 100 permit 10.2.10.0/24
+ip prefix-list 1 seq 110 permit 10.2.40.0/24
+ip prefix-list 1 seq 120 permit 10.3.0.0/24
+ip prefix-list 1 seq 130 permit 10.3.30.0/24
+ip prefix-list 1 seq 140 permit 10.3.10.0/24
+ip prefix-list 1 seq 150 permit 10.3.20.0/24
+ip prefix-list 1 seq 160 permit 10.5.10.0/24
+ip prefix-list 1 seq 170 permit 10.5.20.0/24
+
+!
+route-map NEXTHOP45 permit 10
+match ip address prefix-list 1
+set ip next-hop 10.0.5.254
+!
+!
+route-map NEXTHOP46 permit 10
+match ip address prefix-list 1
+set ip next-hop 10.0.6.254
+!
+! Different next hop for IPv6
+!
+ipv6 prefix-list 2 seq 10 permit 2000::200/120
+ipv6 prefix-list 2 seq 20 permit 2000::300/120
+
+ipv6 prefix-list 2 seq 30 permit 1000::300/120
+ipv6 prefix-list 2 seq 40 permit 1001::300/120
+ipv6 prefix-list 2 seq 50 permit 1002::300/120
+ipv6 prefix-list 2 seq 60 permit 1003::300/120
+ipv6 prefix-list 2 seq 70 permit 1004::300/120
+ipv6 prefix-list 2 seq 80 permit 1005::300/120
+ipv6 prefix-list 2 seq 90 permit 1006::300/120
+ipv6 prefix-list 2 seq 100 permit 1007::300/120
+ipv6 prefix-list 2 seq 110 permit 1008::300/120
+ipv6 prefix-list 2 seq 120 permit 1009::300/120
+ipv6 prefix-list 2 seq 130 permit 1010::300/120
+ipv6 prefix-list 2 seq 140 permit 1011::300/120
+ipv6 prefix-list 2 seq 150 permit 1012::300/120
+!
+
+!
+route-map NEXTHOP65 permit 10
+match ipv6 address prefix-list 2
+set ipv6 next-hop global 2000::5ff
+set ipv6 next-hop local 2000::5ff
+!
+!
+route-map NEXTHOP66 permit 10
+match ipv6 address prefix-list 2
+set ipv6 next-hop global 2000::6ff
+set ipv6 next-hop local 2000::6ff
+!
+! Basic router config
+!
+router bgp 65003
+bgp router-id 172.16.0.4
+timers bgp 3 9
+!
+! IPv4
+!
+neighbor 10.0.5.1 remote-as 65001
+neighbor 10.0.5.1 ebgp-multihop
+neighbor 10.0.5.1 timers connect 5
+neighbor 10.0.5.1 advertisement-interval 5
+neighbor 10.0.5.1 route-map NEXTHOP45 out
+!
+neighbor 2000::501 remote-as 65001
+neighbor 2000::501 timers connect 5
+neighbor 2000::501 advertisement-interval 1
+no neighbor 2000::501 activate
+!
+neighbor 10.0.6.1 remote-as 65002
+neighbor 10.0.6.1 ebgp-multihop
+neighbor 10.0.6.1 timers connect 5
+neighbor 10.0.6.1 advertisement-interval 5
+neighbor 10.0.6.1 route-map NEXTHOP46 out
+!
+neighbor 2000::601 remote-as 65002
+neighbor 2000::601 timers connect 5
+neighbor 2000::601 advertisement-interval 1
+no neighbor 2000::601 activate
+!
+
+network 10.1.0.0/24
+network 10.1.10.0/24
+network 10.2.0.0/24
+network 10.2.30.0/24
+network 10.2.20.0/24
+network 10.2.10.0/24
+network 10.2.40.0/24
+network 10.3.0.0/24
+network 10.3.30.0/24
+network 10.3.10.0/24
+network 10.3.20.0/24
+network 10.5.10.0/24
+network 10.5.20.0/24
+
+network 10.1.0.0/24
+network 10.0.2.0/24
+network 10.1.2.0/24
+network 10.0.3.0/24
+network 10.0.4.0/24
+!
+! IPv6
+!
+address-family ipv6
+
+network 1000::300/120
+network 1001::300/120
+network 1002::300/120
+network 1003::300/120
+network 1004::300/120
+network 1005::300/120
+network 1006::300/120
+network 1007::300/120
+network 1008::300/120
+network 1009::300/120
+network 1010::300/120
+network 1011::300/120
+network 1012::300/120
+
+network 2000::200/120
+network 2000::300/120
+neighbor 2000::501 activate
+neighbor 2000::501 route-map NEXTHOP65 out
+neighbor 2000::601 activate
+neighbor 2000::601 route-map NEXTHOP66 out
+exit-address-family
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr1.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr1.conf
new file mode 100644
index 0000000..9e526b8
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr1.conf
@@ -0,0 +1,42 @@
+log file /var/log/quagga/bgpdr1.log
+hostname r1
+password quagga
+!
+! Basic router config
+!
+router bgp 65001
+bgp router-id 10.0.1.1
+timers bgp 3 9
+!
+! IPv4
+!
+neighbor 10.0.1.2 remote-as 65003
+neighbor 10.0.1.2 ebgp-multihop
+neighbor 10.0.1.2 timers connect 5
+neighbor 10.0.1.2 advertisement-interval 5
+!
+neighbor 2000::102 remote-as 65003
+neighbor 2000::102 timers connect 5
+neighbor 2000::102 advertisement-interval 1
+no neighbor 2000::102 activate
+!
+neighbor 10.0.5.2 remote-as 65003
+neighbor 10.0.5.2 ebgp-multihop
+neighbor 10.0.5.2 timers connect 5
+neighbor 10.0.5.2 advertisement-interval 5
+!
+neighbor 2000::502 remote-as 65003
+neighbor 2000::502 timers connect 5
+neighbor 2000::502 advertisement-interval 1
+no neighbor 2000::502 activate
+!
+network 10.0.99.0/24
+!
+! IPv6
+!
+address-family ipv6
+network 2000::7700/120
+network 2000::9900/120
+neighbor 2000::102 activate
+neighbor 2000::502 activate
+exit-address-family
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr2.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr2.conf
new file mode 100644
index 0000000..49553e2
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/bgpdr2.conf
@@ -0,0 +1,42 @@
+log file /var/log/quagga/bgpdr2.log
+hostname r2
+password quagga
+!
+! Basic router config
+!
+router bgp 65002
+bgp router-id 10.0.6.1
+timers bgp 3 9
+!
+! IPv4
+!
+neighbor 10.0.6.2 remote-as 65003
+neighbor 10.0.6.2 ebgp-multihop
+neighbor 10.0.6.2 timers connect 5
+neighbor 10.0.6.2 advertisement-interval 5
+!
+neighbor 2000::602 remote-as 65003
+neighbor 2000::602 timers connect 5
+neighbor 2000::602 advertisement-interval 1
+no neighbor 2000::602 activate
+!
+neighbor 10.0.7.2 remote-as 65003
+neighbor 10.0.7.2 ebgp-multihop
+neighbor 10.0.7.2 timers connect 5
+neighbor 10.0.7.2 advertisement-interval 5
+!
+neighbor 2000::702 remote-as 65003
+neighbor 2000::702 timers connect 5
+neighbor 2000::702 advertisement-interval 1
+no neighbor 2000::702 activate
+!
+network 10.0.99.0/24
+!
+! IPv6
+!
+address-family ipv6
+network 2000::8800/120
+network 2000::9900/120
+neighbor 2000::602 activate
+neighbor 2000::702 activate
+exit-address-family
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd.conf
new file mode 100644
index 0000000..4208774
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd.conf
@@ -0,0 +1,142 @@
+ddns-update-style none;
+
+default-lease-time 600;
+max-lease-time 7200;
+
+option domain-name-servers 8.8.8.8, 8.8.4.4;
+option domain-name "trellis.local";
+
+subnet 10.0.3.0 netmask 255.255.255.0 {
+  range 10.0.3.100 10.0.3.240;
+  option routers 10.0.3.254;
+}
+
+subnet 10.1.0.0 netmask 255.255.255.0 {
+  range 10.1.0.1 10.1.0.100;
+  option routers 10.1.0.254;
+}
+
+subnet 10.1.10.0 netmask 255.255.255.0 {
+  range 10.1.10.1 10.1.10.100;
+  option routers 10.1.10.254;
+}
+
+subnet 10.2.0.0 netmask 255.255.255.0 {
+  range 10.2.0.1 10.2.0.100;
+  option routers 10.2.0.254;
+}
+
+subnet 10.2.30.0 netmask 255.255.255.0 {
+  range 10.2.30.1 10.2.30.100;
+  option routers 10.2.30.254;
+}
+
+subnet 10.2.20.0 netmask 255.255.255.0 {
+  range 10.2.20.1 10.2.20.100;
+  option routers 10.2.20.254;
+}
+
+subnet 10.2.10.0 netmask 255.255.255.0 {
+  range 10.2.10.1 10.2.10.100;
+  option routers 10.2.10.254;
+}
+
+subnet 10.2.40.0 netmask 255.255.255.0 {
+  range 10.2.40.1 10.2.40.100;
+  option routers 10.2.40.254;
+}
+
+subnet 10.3.0.0 netmask 255.255.255.0 {
+  range 10.3.0.1 10.3.0.100;
+  option routers 10.3.0.254;
+}
+
+subnet 10.3.10.0 netmask 255.255.255.0 {
+  range 10.3.10.1 10.3.10.100;
+  option routers 10.3.10.254;
+}
+
+subnet 10.3.30.0 netmask 255.255.255.0 {
+  range 10.3.30.1 10.3.30.100;
+  option routers 10.3.30.254;
+}
+
+subnet 10.3.20.0 netmask 255.255.255.0 {
+  range 10.3.20.1 10.3.20.100;
+  option routers 10.3.20.254;
+}
+
+subnet 10.5.10.0 netmask 255.255.255.0 {
+  range 10.5.10.1 10.5.10.100;
+  option routers 10.5.10.254;
+}
+
+subnet 10.5.20.0 netmask 255.255.255.0 {
+  range 10.5.20.1 10.5.20.100;
+  option routers 10.5.20.254;
+}
+
+host h1v4 {
+  hardware ethernet 00:aa:00:00:00:01;
+  fixed-address 10.1.0.1;
+}
+
+host h2v4 {
+  hardware ethernet 00:aa:00:00:01:01;
+  fixed-address 10.1.10.1;
+}
+
+host h3v4 {
+  hardware ethernet 00:aa:00:00:00:02;
+  fixed-address 10.2.0.1;
+}
+
+host h4v4 {
+  hardware ethernet 00:aa:00:00:00:03;
+  fixed-address 10.2.30.1;
+}
+
+host h5v4 {
+  hardware ethernet 00:aa:00:00:00:04;
+  fixed-address 10.2.20.1;
+}
+
+host h6v4 {
+  hardware ethernet 00:aa:00:00:00:05;
+  fixed-address 10.2.10.1;
+}
+
+host h7v4 {
+  hardware ethernet 00:aa:00:00:01:05;
+  fixed-address 10.2.40.1;
+}
+
+host h8v4 {
+  hardware ethernet 00:aa:00:00:00:06;
+  fixed-address 10.3.0.1;
+}
+
+host h9v4 {
+  hardware ethernet 00:aa:00:00:00:07;
+  fixed-address 10.3.10.1;
+}
+
+host h10v4 {
+  hardware ethernet 00:aa:00:00:00:08;
+  fixed-address 10.3.30.1;
+}
+
+host h11v4 {
+  hardware ethernet 00:aa:00:00:00:0a;
+  fixed-address 10.3.20.1;
+}
+
+host h12v4 {
+  hardware ethernet 00:aa:00:00:02:01;
+  fixed-address 10.5.10.1;
+}
+
+host h13v4 {
+  hardware ethernet 00:aa:00:00:02:02;
+  fixed-address 10.5.20.1;
+}
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd6.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd6.conf
new file mode 100644
index 0000000..6f7ec9e
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/dhcpd6.conf
@@ -0,0 +1,139 @@
+default-lease-time 600;
+max-lease-time 7200;
+
+option dhcp6.next-hop code 242 = ip6-address;
+
+subnet6 2000::300/120 {
+  range6 2000::360 2000::3fe;
+  option dhcp6.next-hop 2000::03ff;
+}
+
+subnet6 1000::300/120 {
+  range6 1000::360 1000::3fe;
+  option dhcp6.next-hop 1000::03ff;
+}
+
+subnet6 1001::300/120 {
+  range6 1001::360 1001::3fe;
+  option dhcp6.next-hop 1001::03ff;
+}
+
+subnet6 1002::300/120 {
+  range6 1002::360 1002::3fe;
+  option dhcp6.next-hop 1002::03ff;
+}
+
+subnet6 1003::300/120 {
+  range6 1003::360 1003::3fe;
+  option dhcp6.next-hop 1003::03ff;
+}
+
+subnet6 1004::300/120 {
+  range6 1004::360 1004::3fe;
+  option dhcp6.next-hop 1004::03ff;
+}
+
+subnet6 1005::300/120 {
+  range6 1005::360 1005::3fe;
+  option dhcp6.next-hop 1005::03ff;
+}
+
+subnet6 1006::300/120 {
+  range6 1006::360 1006::3fe;
+  option dhcp6.next-hop 1006::03ff;
+}
+
+subnet6 1007::300/120 {
+  range6 1007::360 1007::3fe;
+  option dhcp6.next-hop 1007::03ff;
+}
+
+subnet6 1008::300/120 {
+  range6 1008::360 1008::3fe;
+  option dhcp6.next-hop 1008::03ff;
+}
+
+subnet6 1009::300/120 {
+  range6 1009::360 1009::3fe;
+  option dhcp6.next-hop 1009::03ff;
+}
+
+subnet6 1010::300/120 {
+  range6 1010::360 1010::3fe;
+  option dhcp6.next-hop 1010::03ff;
+}
+
+subnet6 1011::300/120 {
+  range6 1011::360 1011::3fe;
+  option dhcp6.next-hop 1011::03ff;
+}
+
+subnet6 1012::300/120 {
+  range6 1012::360 1012::3fe;
+  option dhcp6.next-hop 1012::03ff;
+}
+
+host h1v6 {
+  hardware ethernet 00:bb:00:00:00:01;
+  fixed-address6 1000::3fe;
+}
+
+host h2v6 {
+  hardware ethernet 00:bb:00:00:01:01;
+  fixed-address6 1001::3fe;
+}
+
+host h3v6 {
+  hardware ethernet 00:bb:00:00:00:02;
+  fixed-address6 1002::3fe;
+}
+
+host h4v6 {
+  hardware ethernet 00:bb:00:00:00:03;
+  fixed-address6 1003::3fe;
+}
+
+host h5v6 {
+  hardware ethernet 00:bb:00:00:00:04;
+  fixed-address6 1004::3fe;
+}
+
+host h6v6 {
+  hardware ethernet 00:bb:00:00:00:05;
+  fixed-address6 1005::3fe;
+}
+
+host h7v6 {
+  hardware ethernet 00:bb:00:00:01:05;
+  fixed-address6 1006::3fe;
+}
+
+host h8v6 {
+  hardware ethernet 00:bb:00:00:00:06;
+  fixed-address6 1007::3fe;
+}
+
+host h9v6 {
+  hardware ethernet 00:bb:00:00:00:07;
+  fixed-address6 1008::3fe;
+}
+
+host h10v6 {
+  hardware ethernet 00:bb:00:00:00:08;
+  fixed-address6 1009::3fe;
+}
+
+host h11v6 {
+  hardware ethernet 00:bb:00:00:00:0a;
+  fixed-address6 1010::3fe;
+}
+
+host h12v6 {
+  hardware ethernet 00:bb:00:00:01:0a;
+  fixed-address6 1011::3fe;
+}
+
+host h13v6 {
+  hardware ethernet 00:bb:00:00:02:0a;
+  fixed-address6 1012::3fe;
+}
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp1.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp1.conf
new file mode 100644
index 0000000..8c93f7d
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp1.conf
@@ -0,0 +1,9 @@
+log file /var/log/quagga/zebradbgp1.log
+hostname zebra-bgp1
+password quagga
+!
+! Default route via virtual management switch
+!
+ip route 0.0.0.0/0 172.16.0.1
+!
+fpm connection ip 10.192.19.68 port 2620
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp2.conf b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp2.conf
new file mode 100644
index 0000000..6205880
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/conf/zebradbgp2.conf
@@ -0,0 +1,9 @@
+log file /var/log/quagga/zebradbgp2.log
+hostname zebra-bgp2
+password quagga
+!
+! Default route via virtual management switch
+!
+ip route 0.0.0.0/0 172.16.0.1
+!
+fpm connection ip 10.192.19.67 port 2620
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/hagg.py b/TestON/tests/CHOTestMonkey/dependencies/topologies/hagg.py
new file mode 100644
index 0000000..4afc9d5
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/hagg.py
@@ -0,0 +1,425 @@
+#!/usr/bin/python
+import os
+import re
+from optparse import OptionParser
+from ipaddress import ip_network
+from mininet.node import RemoteController, OVSBridge, Host
+from mininet.link import TCLink
+from mininet.log import setLogLevel
+from mininet.net import Mininet
+from mininet.topo import Topo
+from mininet.nodelib import NAT
+from mininet.cli import CLI
+
+from routinglib import BgpRouter, RoutedHost
+from trellislib import DhcpServer, TaggedRoutedHost, DualHomedRoutedHost, DualHomedTaggedRoutedHost, DhcpClient, Dhcp6Client, DhcpServer, Dhcp6Server, TrellisHost
+
+# Parse command line options and dump results
+def parseOptions():
+    "Parse command line options"
+    parser = OptionParser()
+    parser.add_option( '--dhcp', dest='dhcp', type='int', default=0,
+                       help='Configure hosts with dhcp or not' )
+    parser.add_option( '--routers', dest='routers', type='int', default=0,
+                       help='Configure external routers or not in the topology' )
+    parser.add_option( '--ipv6', dest='ipv6', type='int', default=0,
+                       help='Configure hosts with ipv6 or not' )
+    parser.add_option( '--ipv4', dest='ipv4', type='int', default=1,
+                       help='Configure hosts with ipv4 or not' )
+    parser.add_option( '--onos-ip', dest='onosIp', type='str', default='',
+                        help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
+
+    ( options, args ) = parser.parse_args()
+    return options, args
+
+opts, args = parseOptions()
+
+class ComcastLeafSpineFabric(Topo):
+
+    spines = dict()
+    leafs = dict()
+    hosts_dict = dict()
+
+    def createIpv4Hosts(self, dhcp):
+
+        h1 = self.addHost('h1v4', cls=TrellisHost,
+                           mac='00:aa:00:00:00:01', ips=['10.1.0.1/24'],
+                           gateway='10.1.0.254', dhcpClient=dhcp)
+        self.addLink(h1, self.leafs[0])
+        self.hosts_dict['h1v4'] = h1
+
+        h2 = self.addHost('h2v4', cls=TrellisHost,
+                          mac='00:aa:00:00:01:01', ips=['10.1.10.1/24'],
+                          gateway='10.1.10.254', dhcpClient=dhcp)
+        self.addLink(h2, self.leafs[0])
+        self.hosts_dict['h2v4'] = h2
+
+        h3 = self.addHost('h3v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:02', ips=['10.2.0.1/24'],
+                          gateway='10.2.0.254', dhcpClient=dhcp)
+        self.addLink(h3, self.leafs[1])
+        self.hosts_dict['h3v4'] = h3
+
+        h4 = self.addHost('h4v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:03', ips=['10.2.30.1/24'],
+                          gateway='10.2.30.254', dhcpClient=dhcp,
+                          dualHomed=True)
+        self.addLink(h4, self.leafs[1])
+        self.addLink(h4, self.leafs[2])
+        self.hosts_dict['h4v4'] = h4
+
+        h5 = self.addHost('h5v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:04', ips=['10.2.20.1/24'],
+                          gateway='10.2.20.254', dhcpClient=dhcp, vlan=30,
+                          dualHomed=True)
+        self.addLink(h5, self.leafs[1])
+        self.addLink(h5, self.leafs[2])
+        self.hosts_dict['h5v4'] = h5
+
+        h6 = self.addHost('h6v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:05', ips=['10.2.10.1/24'],
+                          gateway='10.2.10.254', dhcpClient=dhcp, vlan=20)
+        self.addLink(h6, self.leafs[2])
+        self.hosts_dict['h6v4'] = h6
+
+        h7 = self.addHost('h7v4', cls=TrellisHost,
+                          mac='00:aa:00:00:01:05', ips=['10.2.40.1/24'],
+                          gateway='10.2.40.254', dhcpClient=dhcp, vlan=40)
+        self.addLink(h7, self.leafs[2])
+        self.hosts_dict['h7v4'] = h7
+
+        h8 = self.addHost('h8v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:06', ips=['10.3.0.1/24'],
+                          gateway='10.3.0.254', dhcpClient=dhcp)
+        self.addLink(h8, self.leafs[3])
+        self.hosts_dict['h8v4'] = h8
+
+        h9 = self.addHost('h9v4', cls=TrellisHost,
+                          mac='00:aa:00:00:00:07', ips=['10.3.10.1/24'],
+                          gateway='10.3.10.254', dhcpClient=dhcp, vlan=50,
+                          dualHomed=True)
+        self.addLink(h9, self.leafs[3])
+        self.addLink(h9, self.leafs[4])
+        self.hosts_dict['h9v4'] = h9
+
+        h10 = self.addHost('h10v4', cls=TrellisHost,
+                           mac='00:aa:00:00:00:08', ips=['10.3.30.1/24'],
+                           gateway='10.3.30.254', dhcpClient=dhcp, vlan=60,
+                           dualHomed=True)
+        self.addLink(h10, self.leafs[3])
+        self.addLink(h10, self.leafs[4])
+        self.hosts_dict['h10v4'] = h10
+
+        h11 = self.addHost('h11v4', cls=TrellisHost,
+                           mac='00:aa:00:00:00:0a', ips=['10.3.20.1/24'],
+                           gateway='10.3.20.254', dhcpClient=dhcp, vlan=70)
+        self.addLink(h11, self.leafs[4])
+        self.hosts_dict['h11v4'] = h11
+
+        h12 = self.addHost('h12v4', cls=TrellisHost,
+                           mac='00:aa:00:00:02:01', ips=['10.5.10.1/24'],
+                           gateway='10.5.10.254', dhcpClient=dhcp, vlan=80)
+        self.addLink(h12, self.leafs[5])
+        self.hosts_dict['h12v4'] = h12
+
+        h13 = self.addHost('h13v4', cls=TrellisHost,
+                           mac='00:aa:00:00:02:02', ips=['10.5.20.1/24'],
+                           gateway='10.5.20.254', dhcpClient=dhcp)
+        self.addLink(h13, self.leafs[5])
+        self.hosts_dict['h13v4'] = h13
+        return
+
+    def createIpv6Hosts(self, dhcp):
+
+        h1 = self.addHost('h1v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:01', ips=["1000::3fe/120"],
+                          gateway='1000::3ff', dhcpClient=dhcp, ipv6=1)
+        self.addLink(h1, self.leafs[0])
+        self.hosts_dict['h1v6'] = h1
+
+        h2 = self.addHost('h2v6', cls=TrellisHost,
+                          mac='00:bb:00:00:01:01', ips=['1001::3fe/120'],
+                          gateway='1001::3ff', dhcpClient=dhcp, ipv6=1)
+        self.addLink(h2, self.leafs[0])
+        self.hosts_dict['h2v6'] = h2
+
+        h3 = self.addHost('h3v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:02', ips=['1002::3fe/120'],
+                          gateway='1002::3ff', dhcpClient=dhcp, ipv6=1)
+        self.addLink(h3, self.leafs[1])
+        self.hosts_dict['h3v6'] = h3
+
+        h4 = self.addHost('h4v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:03', ips=['1003::3fe/120'],
+                          gateway='1003::3ff', dhcpClient=dhcp, ipv6=1,
+                          dualHomed=True)
+        self.addLink(h4, self.leafs[1])
+        self.addLink(h4, self.leafs[2])
+        self.hosts_dict['h4v6'] = h4
+
+        h5 = self.addHost('h5v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:04', ips=['1004::3fe/120'],
+                          gateway='1004::3ff', dhcpClient=False, ipv6=1,
+                          vlan=121,
+                          dualHomed=True)
+        self.addLink(h5, self.leafs[1])
+        self.addLink(h5, self.leafs[2])
+        self.hosts_dict['h5v6'] = h5
+
+        h6 = self.addHost('h6v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:05', ips=['1005::3fe/120'],
+                          gateway='1005::3ff', dhcpClient=False, ipv6=1,
+                          vlan=122)
+        self.addLink(h6, self.leafs[2])
+        self.hosts_dict['h6v6'] = h6
+
+        h7 = self.addHost('h7v6', cls=TrellisHost,
+                          mac='00:bb:00:00:01:05', ips=['1006::3fe/120'],
+                          gateway='1006::3ff', dhcpClient=False, ipv6=1,
+                          vlan=123)
+        self.addLink(h7, self.leafs[2])
+        self.hosts_dict['h7v6'] = h7
+
+        h8 = self.addHost('h8v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:06', ips=['1007::3fe/120'],
+                          gateway='1007::3ff', dhcpClient=dhcp, ipv6=1)
+        self.addLink(h8, self.leafs[3])
+        self.hosts_dict['h8v6'] = h8
+
+        h9 = self.addHost('h9v6', cls=TrellisHost,
+                          mac='00:bb:00:00:00:07', ips=['1008::3fe/120'],
+                          gateway='1008::3ff', dhcpClient=False, vlan=124,
+                          dualHomed=True, ipv6=1)
+        self.addLink(h9, self.leafs[3])
+        self.addLink(h9, self.leafs[4])
+        self.hosts_dict['h9v6'] = h9
+
+        h10 = self.addHost('h10v6', cls=TrellisHost,
+                           mac='00:bb:00:00:00:08', ips=['1009::3fe/120'],
+                           gateway='1009::3ff', dhcpClient=False, vlan=125,
+                           dualHomed=True, ipv6=1)
+        self.addLink(h10, self.leafs[3])
+        self.addLink(h10, self.leafs[4])
+        self.hosts_dict['h10v6'] = h10
+
+        h11 = self.addHost('h11v6', cls=TrellisHost,
+                           mac='00:bb:00:00:00:0a', ips=['1010::3fe/120'],
+                           gateway='1010::3ff', dhcpClient=False, vlan=126,
+                           ipv6=1)
+        self.addLink(h11, self.leafs[4])
+        self.hosts_dict['h11v6'] = h11
+
+        h12 = self.addHost('h12v6', cls=TrellisHost,
+                           mac='00:bb:00:00:01:0a', ips=['1011::3fe/120'],
+                           gateway='1011::3ff', dhcpClient=False, vlan=127,
+                           ipv6=1)
+        self.addLink(h12, self.leafs[5])
+        self.hosts_dict['h12v6'] = h12
+
+        h13 = self.addHost('h13v6', cls=TrellisHost,
+                           mac='00:bb:00:00:02:0a', ips=['1012::3fe/120'],
+                           gateway='1012::3ff', dhcpClient=dhcp, ipv6=1)
+        self.addLink(h13, self.leafs[5])
+        self.hosts_dict['h13v6'] = h13
+
+        return
+
+    '''
+    Creates the HAGG topology employed by Comcast.
+    '''
+    def __init__(self, dhcp=False, routers=False, ipv4=False, ipv6=False, **opts):
+        Topo.__init__(self, **opts)
+
+        linkopts = dict( bw=10 )
+
+        spine = 4
+        leaf = 6
+
+        # Create spine switches
+        for s in range(spine):
+            self.spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+
+        # Create leaf switches
+        for ls in range(leaf):
+            self.leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+
+        # connecting leaf and spines, leafs 1-5 have double links
+        for s in range(2):
+            spine_switch = self.spines[s]
+
+            for ls in range(1, 5):
+                leaf_switch = self.leafs[ls]
+
+                self.addLink( spine_switch, leaf_switch, **linkopts )
+                self.addLink( spine_switch, leaf_switch, **linkopts )
+
+        # connect paired leafs
+        self.addLink(self.leafs[1], self.leafs[2], **linkopts)
+        self.addLink(self.leafs[3], self.leafs[4], **linkopts)
+
+        # build second fabric with single links
+        for s in range(2, 4):
+            spine_switch = self.spines[s]
+
+            for ls in [0, 5]:
+                leaf_switch = self.leafs[ls]
+                self.addLink( spine_switch, leaf_switch, **linkopts )
+
+        # connect spines together
+        self.addLink(self.spines[2], self.spines[0], **linkopts)
+        self.addLink(self.spines[3], self.spines[1], **linkopts)
+
+        # create hosts
+        if ipv6:
+            self.createIpv6Hosts(dhcp)
+
+        if ipv4:
+            self.createIpv4Hosts(dhcp)
+
+        if not ipv4 and not ipv6:
+            print("No hosts were created!")
+
+        # create quagga routers
+        # Note: Change "fpm connection ip" to $OC1 in zebradbgp1.conf and zebradbgp2.conf to make quagga work correctly
+        if routers:
+            last_ls = self.leafs[4]
+            last_paired_ls = self.leafs[3]
+
+            # Control plane switch (for quagga fpm)
+            cs0 = self.addSwitch('cs0', cls=OVSBridge)
+
+            # Control plane NAT (for quagga fpm)
+            nat = self.addHost('nat', cls=NAT,
+                               ip='172.16.0.1/12',
+                               subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
+            self.addLink(cs0, nat)
+
+            # Internal Quagga bgp1
+            intfs = {'bgp1-eth0': [{'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:03', 'vlan': '110'},
+                                   {'ipAddrs': ['10.0.7.2/24', '2000::702/120'], 'mac': '00:88:00:00:00:03', 'vlan': '170'}],
+                     'bgp1-eth1': {'ipAddrs': ['172.16.0.3/12']}}
+            bgp1 = self.addHost('bgp1', cls=BgpRouter,
+                                interfaces=intfs,
+                                quaggaConfFile='./bgpdbgp1.conf',
+                                zebraConfFile='./zebradbgp1.conf')
+            self.addLink(bgp1, last_paired_ls)
+            self.addLink(bgp1, cs0)
+
+            # Internal Quagga bgp2
+            intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
+                                   {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
+                     'bgp2-eth1': {'ipAddrs': ['172.16.0.4/12']}}
+            bgp2 = self.addHost('bgp2', cls=BgpRouter,
+                                interfaces=intfs,
+                                quaggaConfFile='./bgpdbgp2.conf',
+                                zebraConfFile='./zebradbgp2.conf')
+            self.addLink(bgp2, last_ls)
+            self.addLink(bgp2, cs0)
+
+            # External Quagga r1
+            intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
+                     'r1-eth1': {'ipAddrs': ['10.0.5.1/24', '2000::501/120'], 'mac': '00:88:00:00:00:11'},
+                     'r1-eth2': {'ipAddrs': ['10.0.99.1/16']},
+                     'r1-eth3': {'ipAddrs': ['2000::9901/120']},
+                     'r1-eth4': {'ipAddrs': ['2000::7701/120']},
+                     'r1-eth5': {'ipAddrs': ['10.0.88.1/24']},
+                     'r1-eth6': {'ipAddrs': ['2000::8701/120']}}
+            r1 = self.addHost('r1', cls=BgpRouter,
+                                interfaces=intfs,
+                                quaggaConfFile='./bgpdr1.conf')
+            self.addLink(r1, last_paired_ls)
+            self.addLink(r1, last_ls)
+
+            # External IPv4 Host behind r1
+            rh1v4 = self.addHost('rh1v4', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+            self.addLink(r1, rh1v4)
+
+            # External IPv6 Host behind r1
+            rh1v6 = self.addHost('rh1v6', cls=RoutedHost, ips=['2000::9902/120'], gateway='2000::9901')
+            self.addLink(r1, rh1v6)
+
+            # Another external IPv6 Host behind r1
+            rh11v6 = self.addHost('rh11v6', cls=RoutedHost, ips=['2000::7702/120'], gateway='2000::7701')
+            self.addLink(r1, rh11v6)
+
+            # Add an external ipv4 hosts that is not configured in the bgp conf
+            # files
+            rh5v4 = self.addHost('rh5v4', cls=RoutedHost, ips=['10.0.88.2/24'], gateway='10.0.88.1')
+            self.addLink(r1, rh5v4)
+
+            # Add an external ipv6 hosts that is not configured in the bgp conf
+            # files
+            rh5v6 = self.addHost('rh5v6', cls=RoutedHost, ips=['2000::8702/120'], gateway='2000::8701')
+            self.addLink(r1, rh5v6)
+
+            # External Quagga r2
+            intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24', '2000::601/120'], 'mac': '00:88:00:00:00:02'},
+                     'r2-eth1': {'ipAddrs': ['10.0.7.1/24', '2000::701/120'], 'mac': '00:88:00:00:00:22'},
+                     'r2-eth2': {'ipAddrs': ['10.0.99.1/16']},
+                     'r2-eth3': {'ipAddrs': ['2000::9901/120']},
+                     'r2-eth4': {'ipAddrs': ['2000::8801/120']}}
+            r2 = self.addHost('r2', cls=BgpRouter,
+                                interfaces=intfs,
+                                quaggaConfFile='./bgpdr2.conf')
+            self.addLink(r2, last_ls)
+            self.addLink(r2, last_paired_ls)
+
+            # External IPv4 Host behind r2
+            rh2v4 = self.addHost('rh2v4', cls=RoutedHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+            self.addLink(r2, rh2v4)
+
+            # External IPv6 Host behind r2
+            rh2v6 = self.addHost('rh2v6', cls=RoutedHost, ips=['2000::9902/120'], gateway='2000::9901')
+            self.addLink(r2, rh2v6)
+
+            # Another external IPv6 Host behind r1
+            rh22v6 = self.addHost('rh22v6', cls=RoutedHost, ips=['2000::8802/120'], gateway='2000::8801')
+            self.addLink(r2, rh22v6)
+
+        # create dhcp servers
+        if dhcp:
+            cs1 = self.addSwitch('cs1', cls=OVSBridge)
+            self.addLink(cs1, self.leafs[4])
+            if ipv4:
+                dhcp4 = self.addHost( 'dhcp', cls=TrellisHost,
+                                      mac="00:cc:00:00:00:01", ips=["10.0.3.253/24"],
+                                      gateway="10.0.3.254", dhcpServer=True)
+                self.addLink(dhcp4, cs1, **linkopts)
+            if ipv6:
+                dhcp6 = self.addHost( 'dhcp6', cls=TrellisHost,
+                                      mac="00:dd:00:00:00:01", ips=["2000::3fd/120"],
+                                      gateway="2000::3ff", dhcpServer=True, ipv6=True)
+                self.addLink(dhcp6, cs1, **linkopts)
+
+
+def config( opts ):
+
+    dhcp = bool(opts.dhcp)
+    routers = bool(opts.routers)
+    ipv6 = bool(opts.ipv6)
+    ipv4 = bool(opts.ipv4)
+
+    if opts.onosIp != '':
+        controllers = opts.onosIp.split( ',' )
+    else:
+        controllers = ['127.0.0.1']
+    topo = ComcastLeafSpineFabric(dhcp=dhcp, routers=routers, ipv6=ipv6,
+                                  ipv4=ipv4)
+
+    net = Mininet( topo=topo, link=TCLink, build=False,
+                   controller=None, autoSetMacs=True )
+    i = 0
+    for ip in controllers:
+        net.addController( "c%s" % ( i ), controller=RemoteController, ip=ip )
+        i += 1
+
+    net.build()
+    net.start()
+    CLI( net )
+    net.stop()
+
+
+if __name__ == '__main__':
+    setLogLevel('info')
+    config(opts)
+    os.system('sudo mn -c')
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/host/hagg.host b/TestON/tests/CHOTestMonkey/dependencies/topologies/host/hagg.host
new file mode 100644
index 0000000..91a1f0f
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/host/hagg.host
@@ -0,0 +1,60 @@
+{
+    "onos":
+    {
+        "00:AA:00:00:00:01/None": "10.1.0.1",
+        "00:AA:00:00:01:01/None": "10.1.10.1",
+        "00:AA:00:00:00:02/None": "10.2.0.1",
+        "00:AA:00:00:00:03/None": "10.2.30.1",
+        "00:AA:00:00:00:04/30": "10.2.20.1",
+        "00:AA:00:00:00:05/20": "10.2.10.1",
+        "00:AA:00:00:01:05/40": "10.2.40.1",
+        "00:AA:00:00:00:06/None": "10.3.0.1",
+        "00:AA:00:00:00:07/50": "10.3.10.1",
+        "00:AA:00:00:00:08/60": "10.3.30.1",
+        "00:AA:00:00:00:0A/70": "10.3.20.1",
+        "00:AA:00:00:02:01/80": "10.5.10.1",
+        "00:AA:00:00:02:02/None": "10.5.20.1",
+        "00:BB:00:00:00:01/None": "1000::3fe",
+        "00:BB:00:00:01:01/None": "1001::3fe",
+        "00:BB:00:00:00:02/None": "1002::3fe",
+        "00:BB:00:00:00:03/None": "1003::3fe",
+        "00:BB:00:00:00:04/121": "1004::3fe",
+        "00:BB:00:00:00:05/122": "1005::3fe",
+        "00:BB:00:00:01:05/123": "1006::3fe",
+        "00:BB:00:00:00:06/None": "1007::3fe",
+        "00:BB:00:00:00:07/124": "1008::3fe",
+        "00:BB:00:00:00:08/125": "1009::3fe",
+        "00:BB:00:00:00:0A/126": "1010::3fe",
+        "00:BB:00:00:01:0A/127": "1011::3fe",
+        "00:BB:00:00:02:0A/None": "1012::3fe"
+    },
+    "network":
+    {
+        "h1v4": "10.1.0.1",
+        "h2v4": "10.1.10.1",
+        "h3v4": "10.2.0.1",
+        "h4v4": "10.2.30.1",
+        "h5v4": "10.2.20.1",
+        "h6v4": "10.2.10.1",
+        "h7v4": "10.2.40.1",
+        "h8v4": "10.3.0.1",
+        "h9v4": "10.3.10.1",
+        "h10v4": "10.3.30.1",
+        "h11v4": "10.3.20.1",
+        "h12v4": "10.5.10.1",
+        "h13v4": "10.5.20.1",
+        "h1v6": "1000::3fe",
+        "h2v6": "1001::3fe",
+        "h3v6": "1002::3fe",
+        "h4v6": "1003::3fe",
+        "h5v6": "1004::3fe",
+        "h6v6": "1005::3fe",
+        "h7v6": "1006::3fe",
+        "h8v6": "1007::3fe",
+        "h9v6": "1008::3fe",
+        "h10v6": "1009::3fe",
+        "h11v6": "1010::3fe",
+        "h12v6": "1011::3fe",
+        "h13v6": "1012::3fe"
+    }
+}
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/json/hagg.json b/TestON/tests/CHOTestMonkey/dependencies/topologies/json/hagg.json
new file mode 100644
index 0000000..b1b9965
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/json/hagg.json
@@ -0,0 +1,611 @@
+{
+    "ports" : {
+	"of:0000000000000006/3" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1011::3ff/120" ],
+                    "vlan-tagged": [127]
+                }
+            ]
+        },
+        "of:0000000000000006/4" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1012::3ff/120" ],
+                    "vlan-untagged": 26
+                }
+            ]
+        },
+	"of:0000000000000006/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.5.10.254/24" ],
+                    "vlan-tagged": [80]
+                }
+            ]
+        },
+        "of:0000000000000006/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.5.20.254/24" ],
+                    "vlan-untagged": 35
+                }
+            ]
+        },
+        "of:0000000000000002/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1003::3ff/120" , "1004::3ff/120", "10.2.30.254/24", "10.2.20.254/24"],
+                    "vlan-tagged": [ 121 , 30 ]
+                }
+            ]
+        },
+        "of:0000000000000003/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1003::3ff/120" , "1004::3ff/120", "10.2.30.254/24", "10.2.20.254/24"],
+                    "vlan-tagged": [ 121, 30 ]
+                }
+            ]
+        },
+        "of:0000000000000004/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1008::3ff/120", "1009::3ff/120", "10.3.10.254/24", "10.3.30.254/24" ],
+                    "vlan-tagged": [ 124, 125, 50, 60 ]
+                }
+            ]
+        },
+        "of:0000000000000005/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1008::3ff/120", "1009::3ff/120", "10.3.10.254/24", "10.3.30.254/24" ],
+                    "vlan-tagged": [ 124, 125, 50, 60 ]
+                }
+            ]
+        },
+        "of:0000000000000001/3" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1000::3ff/120" ],
+                    "vlan-untagged": 21
+                }
+            ]
+        },
+        "of:0000000000000001/4" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1001::3ff/120" ],
+                    "vlan-untagged": 22
+                }
+            ]
+        },
+        "of:0000000000000001/5" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.1.0.254/24" ],
+                    "vlan-untagged": 10
+                }
+            ]
+        },
+        "of:0000000000000001/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.1.10.254/24" ],
+                    "vlan-untagged": 11
+                }
+            ]
+        },
+        "of:0000000000000002/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1002::3ff/120" ],
+                    "vlan-untagged": 23
+                }
+            ]
+        },
+        "of:0000000000000002/7" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1003::3ff/120" ],
+                    "vlan-untagged": 24
+                }
+            ]
+        },
+        "of:0000000000000002/8" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1004::3ff/120" ],
+                    "vlan-tagged": [121]
+                }
+            ]
+        },
+        "of:0000000000000002/9" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.0.254/24" ],
+                    "vlan-untagged": 12
+                }
+            ]
+        },
+        "of:0000000000000002/10" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.30.254/24" ],
+                    "vlan-untagged": 16
+                }
+            ]
+        },
+        "of:0000000000000002/11" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.20.254/24" ],
+                    "vlan-tagged": [30]
+                }
+            ]
+        },
+        "of:0000000000000003/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1003::3ff/120" ],
+                    "vlan-untagged": 24
+                }
+            ]
+        },
+        "of:0000000000000003/7" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1004::3ff/120" ],
+                    "vlan-tagged": [121]
+                }
+            ]
+        },
+        "of:0000000000000003/8" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1005::3ff/120" ],
+                    "vlan-tagged": [122]
+                }
+            ]
+        },
+	"of:0000000000000003/9" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1006::3ff/120" ],
+                    "vlan-tagged": [123]
+                }
+            ]
+        },
+        "of:0000000000000003/10" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.30.254/24" ],
+                    "vlan-untagged": 16
+                }
+            ]
+        },
+        "of:0000000000000003/11" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.20.254/24" ],
+                    "vlan-tagged": [30]
+                }
+            ]
+        },
+        "of:0000000000000003/12" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.10.254/24" ],
+                    "vlan-tagged": [20]
+                }
+            ]
+        },
+	"of:0000000000000003/13" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.2.40.254/24" ],
+                    "vlan-tagged": [40]
+                }
+            ]
+        },
+        "of:0000000000000004/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1007::3ff/120" ],
+                    "vlan-untagged": 25
+                }
+            ]
+        },
+        "of:0000000000000004/7" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1008::3ff/120" ],
+                    "vlan-tagged": [124]
+                }
+            ]
+        },
+        "of:0000000000000004/8" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1009::3ff/120" ],
+                    "vlan-tagged": [125]
+                }
+            ]
+        },
+        "of:0000000000000004/9" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.0.254/24" ],
+                    "vlan-untagged": 25
+                }
+            ]
+        },
+        "of:0000000000000004/10" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.10.254/24" ],
+                    "vlan-tagged": [50]
+                }
+            ]
+        },
+        "of:0000000000000004/11" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.30.254/24" ],
+                    "vlan-tagged": [60]
+                }
+            ]
+        },
+        "of:0000000000000004/12" : {
+            "interfaces" : [
+                {
+			"ips" : [ "10.0.1.254/24", "10.0.7.254/24", "2000::1ff/120", "2000::7ff/120" ],
+			"vlan-tagged": [110, 170]
+
+                }
+            ]
+        },
+        "of:0000000000000004/13" : {
+            "interfaces" : [
+                {
+			"ips" : [ "10.0.1.254/24", "2000::1ff/120"],
+			"vlan-untagged": 110
+
+                }
+            ]
+        },
+        "of:0000000000000004/14" : {
+            "interfaces" : [
+                {
+			"ips" : [ "10.0.7.254/24", "2000::7ff/120" ],
+			"vlan-untagged": 170
+
+                }
+            ]
+        },
+        "of:0000000000000005/6" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1008::3ff/120" ],
+                    "vlan-tagged": [124]
+                }
+            ]
+        },
+        "of:0000000000000005/7" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1009::3ff/120" ],
+                    "vlan-tagged": [125]
+                }
+            ]
+        },
+        "of:0000000000000005/8" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "1010::3ff/120" ],
+                    "vlan-tagged": [126]
+                }
+            ]
+        },
+        "of:0000000000000005/9" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.10.254/24" ],
+                    "vlan-tagged": [50]
+                }
+            ]
+        },
+        "of:0000000000000005/10" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.30.254/24" ],
+                    "vlan-tagged": [60]
+                }
+            ]
+        },
+        "of:0000000000000005/11" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.20.254/24" ],
+                    "vlan-tagged": [70]
+                }
+            ]
+        },
+        "of:0000000000000005/12" : {
+            "interfaces" : [
+                {
+                       "ips" : [ "10.0.5.254/24", "10.0.6.254/24", "2000::5ff/120", "2000::6ff/120" ],
+	               "vlan-tagged": [150, 160]
+                }
+            ]
+        },
+        "of:0000000000000005/13" : {
+            "interfaces" : [
+                {
+                       "ips" : [ "10.0.5.254/24", "2000::5ff/120"],
+	               "vlan-untagged": 150
+                }
+            ]
+        },
+        "of:0000000000000005/14" : {
+            "interfaces" : [
+                {
+                       "ips" : [ "10.0.6.254/24", "2000::6ff/120" ],
+	               "vlan-untagged": 160
+                }
+            ]
+        },
+        "of:0000000000000005/15" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.0.3.254/24", "2000::3ff/120" ],
+                    "vlan-untagged": 15
+                }
+            ]
+        }
+    },
+    "hosts" : {
+	"00:BB:00:00:01:0A/127" : {
+            "basic": {
+                "locations": ["of:0000000000000006/3"],
+                "ips": ["1011::3fe"]
+            }
+	},
+	"00:bb:00:00:00:04/121" : {
+            "basic": {
+                "locations": ["of:0000000000000003/7","of:0000000000000002/8"],
+                "ips": ["1004::3fe"]
+            }
+        },
+	"00:bb:00:00:00:05/122" : {
+            "basic": {
+                "locations": ["of:0000000000000003/8"],
+                "ips": ["1005::3fe"]
+            }
+        },
+	"00:bb:00:00:01:05/123" : {
+            "basic": {
+                "locations": ["of:0000000000000003/9"],
+                "ips": ["1006::3fe"]
+            }
+        },
+	"00:bb:00:00:00:07/124" : {
+            "basic": {
+                "locations": ["of:0000000000000005/6", "of:0000000000000004/7"],
+                "ips": ["1008::3fe"]
+            }
+        },
+	"00:bb:00:00:00:08/125" : {
+            "basic": {
+                "locations": ["of:0000000000000004/8","of:0000000000000005/7"],
+                "ips": ["1009::3fe"]
+            }
+        },
+	"00:bb:00:00:00:0A/126" : {
+            "basic": {
+                "locations": ["of:0000000000000005/8"],
+                "ips": ["1010::3fe"]
+            }
+        }
+    },
+    "devices" : {
+	 "of:0000000000000006" : {
+            "segmentrouting" : {
+                "name" : "s006",
+                "ipv4NodeSid" : 1006,
+		"ipv6NodeSid" : 2006,
+		"ipv6Loopback" : "2000::c0a8:0006",
+                "ipv4Loopback" : "192.168.0.6",
+                "routerMac" : "00:00:00:00:00:06",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s006",
+                "driver" : "ofdpa-ovs",
+		"latitude": 30,
+		"longitude": -110
+            }
+        },
+        "of:0000000000000103" : {
+            "segmentrouting" : {
+                "name" : "s103",
+                "ipv4NodeSid" : 1103,
+                "ipv4Loopback" : "192.168.0.103",
+		"ipv6NodeSid" : 2103,
+		"ipv6Loopback" : "2000::c0a8:0203",
+                "routerMac" : "00:00:00:00:01:03",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                 "name" : "s103",
+                 "driver" : "ofdpa-ovs",
+		 "latitude": 35,
+		 "longitude": -115
+            }
+        },
+        "of:0000000000000104" : {
+            "segmentrouting" : {
+                "name" : "s104",
+                "ipv4NodeSid" : 1104,
+                "ipv4Loopback" : "192.168.0.104",
+		"ipv6NodeSid" : 2104,
+		"ipv6Loopback" : "2000::c0a8:0204",
+                "routerMac" : "00:00:00:00:01:04",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                 "name" : "s104",
+                 "driver" : "ofdpa-ovs",
+		 "latitude": 35,
+		 "longitude": -110
+            }
+	},
+        "of:0000000000000001" : {
+            "segmentrouting" : {
+                "name" : "s001",
+                "ipv4NodeSid" : 1001,
+		"ipv6NodeSid" : 2001,
+		"ipv6Loopback" : "2000::c0a8:0001",
+                "ipv4Loopback" : "192.168.0.1",
+                "routerMac" : "00:00:00:00:00:01",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s001",
+                "driver" : "ofdpa-ovs",
+		"latitude": 30,
+		"longitude": -105
+            }
+        },
+        "of:0000000000000002" : {
+            "segmentrouting" : {
+                "name" : "s002",
+                "ipv4NodeSid" : 1002,
+                "ipv4Loopback" : "192.168.0.2",
+		"ipv6NodeSid" : 2002,
+		"ipv6Loopback" : "2000::c0a8:0002",
+                "routerMac" : "00:00:00:00:00:02",
+                "isEdgeRouter" : true,
+                "pairLocalPort" : 5,
+                "pairDeviceId": "of:0000000000000003",
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s002",
+                "driver" : "ofdpa-ovs",
+		"latitude": 34,
+		"longitude": -95
+            }
+        },
+        "of:0000000000000003" : {
+            "segmentrouting" : {
+                "name" : "s003",
+                "ipv4NodeSid" : 1003,
+                "ipv4Loopback" : "192.168.0.3",
+                "ipv6NodeSid" : 2003,
+		"ipv6Loopback" : "2000::c0a8:0003",
+                "routerMac" : "00:00:00:00:00:02",
+                "isEdgeRouter" : true,
+                "pairLocalPort" : 5,
+                "pairDeviceId": "of:0000000000000002",
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s003",
+                "driver" : "ofdpa-ovs",
+		"latitude": 34,
+		"longitude": -90
+            }
+        },
+        "of:0000000000000004" : {
+            "segmentrouting" : {
+                "name" : "s004",
+                "ipv4NodeSid" : 1004,
+                "ipv4Loopback" : "192.168.0.4",
+		"ipv6NodeSid" : 2004,
+		"ipv6Loopback" : "2000::c0a8:0004",
+                "routerMac" : "00:00:00:00:00:04",
+                "isEdgeRouter" : true,
+                "pairLocalPort" : 5,
+                "pairDeviceId": "of:0000000000000005",
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s004",
+                "driver" : "ofdpa-ovs",
+		"latitude": 34,
+		"longitude": -85
+            }
+        },
+        "of:0000000000000005" : {
+            "segmentrouting" : {
+                "name" : "s005",
+                "ipv4NodeSid" : 1005,
+                "ipv4Loopback" : "192.168.0.5",
+		"ipv6NodeSid" : 2005,
+		"ipv6Loopback" : "2000::c0a8:0005",
+                "routerMac" : "00:00:00:00:00:04",
+                "isEdgeRouter" : true,
+                "pairLocalPort" : 5,
+                "pairDeviceId": "of:0000000000000004",
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s005",
+                "driver" : "ofdpa-ovs",
+		"latitude": 34,
+		"longitude": -80
+            }
+        },
+        "of:0000000000000101" : {
+            "segmentrouting" : {
+                "name" : "s101",
+                "ipv4NodeSid" : 1101,
+                "ipv4Loopback" : "192.168.0.101",
+		"ipv6NodeSid" : 2101,
+		"ipv6Loopback" : "2000::c0a8:0101",
+                "routerMac" : "00:00:00:00:01:01",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s101",
+                "driver" : "ofdpa-ovs",
+		"latitude": 42,
+                "longitude": -100
+            }
+        },
+        "of:0000000000000102" : {
+            "segmentrouting" : {
+                "name" : "s102",
+                "ipv4NodeSid" : 1102,
+                "ipv4Loopback" : "192.168.0.102",
+		"ipv6NodeSid" : 2102,
+		"ipv6Loopback" : "2000::c0a8:0202",
+                "routerMac" : "00:00:00:00:01:02",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            },
+            "basic" : {
+                "name" : "s102",
+                 "driver" : "ofdpa-ovs",
+		 "latitude": 42,
+		 "longitude": -95
+            }
+        }
+    },
+    "apps" : {
+        "org.onosproject.dhcprelay" : {
+            "default": [
+                {
+                    "dhcpServerConnectPoint": "of:0000000000000005/15",
+                    "serverIps": ["10.0.3.253", "2000::3fd"]
+                }
+            ]
+        }
+    }
+}
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/routinglib.py b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/routinglib.py
new file mode 100644
index 0000000..13a75e2
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/routinglib.py
@@ -0,0 +1,702 @@
+#!/usr/bin/python
+
+"""
+Libraries for creating L3 topologies with routing protocols.
+"""
+
+from mininet.node import Host, OVSBridge
+from mininet.nodelib import NAT
+from mininet.log import info, debug, error
+from mininet.cli import CLI
+from ipaddress import ip_network, ip_address, ip_interface
+import os
+
+class RoutedHost(Host):
+    """Host that can be configured with multiple IP addresses."""
+    def __init__(self, name, ips, gateway, *args, **kwargs):
+        super(RoutedHost, self).__init__(name, *args, **kwargs)
+
+        self.ips = ips
+        self.gateway = gateway
+
+    def config(self, **kwargs):
+        Host.config(self, **kwargs)
+
+        self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
+        for ip in self.ips:
+            self.cmd('ip addr add %s dev %s' % (ip, self.defaultIntf()))
+
+        self.cmd('ip route add default via %s' % self.gateway)
+
+class RoutedHost6(Host):
+    """Host that can be configured with multiple IP addresses."""
+    def __init__(self, name, ips, gateway, *args, **kwargs):
+        super(RoutedHost6, self).__init__(name, *args, **kwargs)
+
+        self.ips = ips
+        self.gateway = gateway
+
+    def config(self, **kwargs):
+        Host.config(self, **kwargs)
+
+        self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
+        for ip in self.ips:
+            self.cmd('ip -6 addr add %s dev %s' % (ip, self.defaultIntf()))
+
+        self.cmd('ip -6 route add default via %s' % self.gateway)
+
+class Router(Host):
+
+    """An L3 router.
+    Configures the Linux kernel for L3 forwarding and supports rich interface
+    configuration of IP addresses, MAC addresses and VLANs."""
+
+    def __init__(self, name, interfaces, *args, **kwargs):
+        super(Router, self).__init__(name, **kwargs)
+
+        self.interfaces = interfaces
+
+    def config(self, **kwargs):
+        super(Host, self).config(**kwargs)
+
+        self.cmd('sysctl net.ipv4.ip_forward=1')
+        self.cmd('sysctl net.ipv4.conf.all.rp_filter=0')
+        self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
+
+        for intf, configs in self.interfaces.items():
+            self.cmd('ip -4 addr flush dev %s' % intf)
+            self.cmd( 'sysctl net.ipv4.conf.%s.rp_filter=0' % intf )
+
+            if not isinstance(configs, list):
+                configs = [configs]
+
+            for attrs in configs:
+                # Configure the vlan if there is one
+                if 'vlan' in attrs:
+                    vlanName = '%s.%s' % (intf, attrs['vlan'])
+                    self.cmd('ip link add link %s name %s type vlan id %s' %
+                             (intf, vlanName, attrs['vlan']))
+                    self.cmd('ip link set %s up' % vlanName)
+                    addrIntf = vlanName
+                else:
+                    addrIntf = intf
+
+                # Now configure the addresses on the vlan/native interface
+                if 'mac' in attrs:
+                    self.cmd('ip link set %s down' % addrIntf)
+                    self.cmd('ip link set %s address %s' % (addrIntf, attrs['mac']))
+                    self.cmd('ip link set %s up' % addrIntf)
+                for addr in attrs['ipAddrs']:
+                    self.cmd('ip addr add %s dev %s' % (addr, addrIntf))
+
+class QuaggaRouter(Router):
+
+    """Runs Quagga to create a router that can speak routing protocols."""
+
+    binDir = '/usr/lib/quagga'
+    logDir = '/var/log/quagga'
+
+    def __init__(self, name, interfaces,
+                 defaultRoute=None,
+                 zebraConfFile=None,
+                 protocols= [],
+                 fpm=None,
+                 runDir='/var/run/quagga', *args, **kwargs):
+        super(QuaggaRouter, self).__init__(name, interfaces, **kwargs)
+
+        self.protocols = protocols
+        self.fpm = fpm
+
+        for p in self.protocols:
+            p.setQuaggaRouter(self)
+
+        self.runDir = runDir
+        self.defaultRoute = defaultRoute
+
+        # Ensure required directories exist
+        try:
+            original_umask = os.umask(0)
+            if (not os.path.isdir(QuaggaRouter.logDir)):
+                os.makedirs(QuaggaRouter.logDir, 0777)
+            if (not os.path.isdir(self.runDir)):
+                os.makedirs(self.runDir, 0777)
+        finally:
+            os.umask(original_umask)
+
+        self.zebraConfFile = zebraConfFile
+        if (self.zebraConfFile is None):
+            self.zebraConfFile = '%s/zebrad%s.conf' % (self.runDir, self.name)
+            self.generateZebra()
+
+        self.socket = '%s/zebra%s.api' % (self.runDir, self.name)
+
+        self.zebraPidFile = '%s/zebra%s.pid' % (self.runDir, self.name)
+
+    def generateZebra(self):
+        configFile = open(self.zebraConfFile, 'w+')
+        configFile.write('log file %s/zebrad%s.log\n' % (QuaggaRouter.logDir, self.name))
+        configFile.write('hostname zebra-%s\n' % self.name)
+        configFile.write('password %s\n' % 'quagga')
+        if (self.fpm is not None):
+            configFile.write('fpm connection ip %s port 2620' % self.fpm)
+        configFile.close()
+
+    def config(self, **kwargs):
+        super(QuaggaRouter, self).config(**kwargs)
+
+        self.cmd('%s/zebra -d -f %s -z %s -i %s'
+                 % (QuaggaRouter.binDir, self.zebraConfFile, self.socket, self.zebraPidFile))
+        print("\n")
+        print('%s/zebra -d -f %s -z %s -i %s'
+                 % (QuaggaRouter.binDir, self.zebraConfFile, self.socket, self.zebraPidFile))
+
+        for p in self.protocols:
+            p.config(**kwargs)
+
+        if self.defaultRoute:
+            self.cmd('ip route add default via %s' % self.defaultRoute)
+
+    def stopProtocols(self, **kwargs):
+        for p in self.protocols:
+            p.stop(**kwargs)
+
+    def startProtocols(self, **kwargs):
+        for p in self.protocols:
+            p.start(**kwargs)
+
+    def terminate(self, **kwargs):
+        self.cmd("ps ax | grep '%s' | awk '{print $1}' | xargs kill"
+                 % (self.socket))
+
+        for p in self.protocols:
+            p.terminate(**kwargs)
+
+        super(QuaggaRouter, self).terminate()
+
+class Protocol(object):
+
+    """Base abstraction of a protocol that the QuaggaRouter can run."""
+
+    def setQuaggaRouter(self, qr):
+        self.qr = qr
+
+    def config(self, **kwargs):
+        pass
+
+    def stop(self, **kwargs):
+        pass
+
+    def start(self, **kwargs):
+        pass
+
+    def terminate(self, **kwargs):
+        pass
+
+class BgpProtocol(Protocol):
+
+    """Configures and runs the BGP protocol in Quagga."""
+
+    def __init__(self, configFile=None, asNum=None, neighbors=[], routes=[], *args, **kwargs):
+        self.configFile = configFile
+
+        self.asNum = asNum
+        self.neighbors = neighbors
+        self.routes = routes
+
+    def config(self, **kwargs):
+        if self.configFile is None:
+            self.configFile = '%s/bgpd%s.conf' % (self.qr.runDir, self.qr.name)
+            self.generateConfig()
+
+        bgpdPidFile = '%s/bgpd%s.pid' % (self.qr.runDir, self.qr.name)
+
+        self.qr.cmd('%s/bgpd -d -f %s -z %s -i %s'
+                     % (QuaggaRouter.binDir, self.configFile, self.qr.socket, bgpdPidFile))
+
+    def stop(self, **kwargs):
+        self.qr.cmd('pkill -f %s' % self.configFile)
+
+    def start(self, **kwargs):
+        bgpdPidFile = '%s/bgpd%s.pid' % (self.qr.runDir, self.qr.name)
+        self.qr.cmd('%s/bgpd -d -f %s -z %s -i %s'
+                     % (QuaggaRouter.binDir, self.configFile, self.qr.socket, bgpdPidFile))
+
+    def generateConfig(self):
+        conf = ConfigurationWriter(self.configFile)
+
+        def getRouterId(interfaces):
+            intfAttributes = interfaces.itervalues().next()
+            print intfAttributes
+            if isinstance(intfAttributes, list):
+                # Try use the first set of attributes, but if using vlans they might not have addresses
+                intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
+            return intfAttributes['ipAddrs'][0].split('/')[0]
+
+        conf.writeLine('log file %s/bgpd%s.log' % (QuaggaRouter.logDir, self.qr.name))
+        conf.writeLine('hostname bgp-%s' % self.qr.name)
+        conf.writeLine('password %s' % 'quagga')
+        conf.writeLine('!')
+        conf.writeLine('router bgp %s' % self.asNum)
+
+        conf.indent()
+
+        conf.writeLine('bgp router-id %s' % getRouterId(self.qr.interfaces))
+        conf.writeLine('timers bgp %s' % '3 9')
+        conf.writeLine('!')
+
+        for neighbor in self.neighbors:
+            conf.writeLine('neighbor %s remote-as %s' % (neighbor['address'], neighbor['as']))
+            conf.writeLine('neighbor %s ebgp-multihop' % neighbor['address'])
+            conf.writeLine('neighbor %s timers connect %s' % (neighbor['address'], '5'))
+            conf.writeLine('neighbor %s advertisement-interval %s' % (neighbor['address'], '5'))
+            if 'port' in neighbor:
+                conf.writeLine('neighbor %s port %s' % (neighbor['address'], neighbor['port']))
+            conf.writeLine('!')
+
+        for route in self.routes:
+            conf.writeLine('network %s' % route)
+
+        conf.close()
+
+class OspfProtocol(Protocol):
+
+    """Configures and runs the OSPF protocol in Quagga."""
+
+    def __init__(self, configFile=None, *args, **kwargs):
+        self.configFile = configFile
+
+    def config(self, **kwargs):
+        if self.configFile is None:
+            self.configFile = '%s/ospfd%s.conf' % (self.qr.runDir, self.qr.name)
+            self.generateConfig()
+
+        ospfPidFile = '%s/ospf%s.pid' % (self.qr.runDir, self.qr.name)
+
+        self.qr.cmd('%s/ospfd -d -f %s -z %s -i %s'
+                     % (QuaggaRouter.binDir, self.configFile, self.qr.socket, ospfPidFile))
+
+    def generateConfig(self):
+        conf = ConfigurationWriter(self.configFile)
+
+        def getRouterId(interfaces):
+            intfAttributes = interfaces.itervalues().next()
+            print intfAttributes
+            if isinstance(intfAttributes, list):
+                # Try use the first set of attributes, but if using vlans they might not have addresses
+                intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
+            return intfAttributes['ipAddrs'][0].split('/')[0]
+
+        conf.writeLine('hostname ospf-%s' % self.qr.name)
+        conf.writeLine('password %s' % 'hello')
+        conf.writeLine('!')
+        conf.writeLine('router ospf')
+
+        conf.indent()
+
+        conf.writeLine('ospf router-id %s' % getRouterId(self.qr.interfaces))
+        conf.writeLine('!')
+
+        for name, intf in self.qr.interfaces.items():
+            for ip in intf['ipAddrs']:
+                conf.writeLine('network %s area 0' % ip)
+            # if intf['ipAddrs'][0].startswith('192.168'):
+            #    writeLine(1, 'passive-interface %s' % name)
+
+        conf.close()
+
+class PimProtocol(Protocol):
+
+    """Configures and runs the PIM protcol in Quagga."""
+
+    def __init__(self, configFile=None, *args, **kwargs):
+        self.configFile = configFile
+
+    def config(self, **kwargs):
+        pimPidFile = '%s/pim%s.pid' % (self.qr.runDir, self.qr.name)
+
+        self.qr.cmd('%s/pimd -Z -d -f %s -z %s -i %s'
+                     % (QuaggaRouter.binDir, self.configFile, self.qr.socket, pimPidFile))
+
+class ConfigurationWriter(object):
+
+    """Utility class for writing a configuration file."""
+
+    def __init__(self, filename):
+        self.filename = filename
+        self.indentValue = 0
+
+        self.configFile = open(self.filename, 'w+')
+
+    def indent(self):
+        self.indentValue += 1
+
+    def unindent(self):
+        if (self.indentValue > 0):
+            self.indentValue -= 1
+
+    def write(self, string):
+        self.configFile.write(string)
+
+    def writeLine(self, string):
+        intentStr = ''
+        for _ in range(0, self.indentValue):
+            intentStr += '  '
+        self.write('%s%s\n' % (intentStr, string))
+
+    def close(self):
+        self.configFile.close()
+
+# Backward compatibility for BGP-only use case
+class BgpRouter(QuaggaRouter):
+
+    """Quagga router running the BGP protocol."""
+
+    def __init__(self, name, interfaces,
+                 asNum=0, neighbors=[], routes=[],
+                 defaultRoute=None,
+                 quaggaConfFile=None,
+                 zebraConfFile=None,
+                 *args, **kwargs):
+        bgp = BgpProtocol(configFile=quaggaConfFile, asNum=asNum, neighbors=neighbors, routes=routes)
+
+        super(BgpRouter, self).__init__(name, interfaces,
+                                        zebraConfFile=zebraConfFile,
+                                        defaultRoute=defaultRoute,
+                                        protocols=[bgp],
+                                        *args, **kwargs)
+
+class RouterData(object):
+
+    """Internal data structure storing information about a router."""
+
+    def __init__(self, index):
+        self.index = index
+        self.neighbors = []
+        self.interfaces = {}
+        self.switches = []
+
+    def addNeighbor(self, theirAddress, theirAsNum):
+        self.neighbors.append({'address': theirAddress.ip, 'as': theirAsNum})
+
+    def addInterface(self, intf, vlan, address):
+        if intf not in self.interfaces:
+            self.interfaces[intf] = InterfaceData(intf)
+
+        self.interfaces[intf].addAddress(vlan, address)
+
+    def setSwitch(self, switch):
+        self.switches.append(switch)
+
+class InterfaceData(object):
+
+    """Internal data structure storing information about an interface."""
+
+    def __init__(self, number):
+        self.number = number
+        self.addressesByVlan = {}
+
+    def addAddress(self, vlan, address):
+        if vlan not in self.addressesByVlan:
+            self.addressesByVlan[vlan] = []
+
+        self.addressesByVlan[vlan].append(address.with_prefixlen)
+
+class RoutedNetwork(object):
+
+    """Creates a host behind a router. This is common boilerplate topology
+    segment in routed networks."""
+
+    @staticmethod
+    def build(topology, router, hostName, networks):
+        # There's a convention that the router's addresses are already set up,
+        # and it has the last address in the network.
+
+        def getFirstAddress(network):
+            return '%s/%s' % (network[1], network.prefixlen)
+
+        defaultRoute = AutonomousSystem.getLastAddress(networks[0]).ip
+
+        host = topology.addHost(hostName, cls=RoutedHost,
+                                ips=[getFirstAddress(network) for network in networks],
+                                gateway=defaultRoute)
+
+        topology.addLink(router, host)
+
+class AutonomousSystem(object):
+
+    """Base abstraction of an autonomous system, which implies some internal
+    topology and connections to other topology elements (switches/other ASes)."""
+
+    psIdx = 1
+
+    def __init__(self, asNum, numRouters):
+        self.asNum = asNum
+        self.numRouters = numRouters
+        self.routers = {}
+        for i in range(1, numRouters + 1):
+            self.routers[i] = RouterData(i)
+
+        self.routerNodes = {}
+
+        self.neighbors = []
+        self.vlanAddresses = {}
+
+    def peerWith(self, myRouter, myAddress, theirAddress, theirAsNum, intf=1, vlan=None):
+        router = self.routers[myRouter]
+
+        router.addInterface(intf, vlan, myAddress)
+        router.addNeighbor(theirAddress, theirAsNum)
+
+    def getRouter(self, i):
+        return self.routerNodes[i]
+
+    @staticmethod
+    def generatePeeringAddresses():
+        network = ip_network(u'10.0.%s.0/24' % AutonomousSystem.psIdx)
+        AutonomousSystem.psIdx += 1
+
+        return ip_interface('%s/%s' % (network[1], network.prefixlen)), \
+            ip_interface('%s/%s' % (network[2], network.prefixlen))
+
+    @staticmethod
+    def addPeering(as1, as2, router1=1, router2=1, intf1=1, intf2=1, address1=None, address2=None, useVlans=False):
+        vlan = AutonomousSystem.psIdx if useVlans else None
+
+        if address1 is None or address2 is None:
+            (address1, address2) = AutonomousSystem.generatePeeringAddresses()
+
+        as1.peerWith(router1, address1, address2, as2.asNum, intf=intf1, vlan=vlan)
+        as2.peerWith(router2, address2, address1, as1.asNum, intf=intf2, vlan=vlan)
+
+    @staticmethod
+    def getLastAddress(network):
+        return ip_interface(network.network_address + network.num_addresses - 2)
+
+    @staticmethod
+    def getIthAddress(network, i):
+        return ip_interface('%s/%s' % (network[i], network.prefixlen))
+
+class BasicAutonomousSystem(AutonomousSystem):
+
+    """Basic autonomous system containing one host and one or more routers
+    which peer with other ASes."""
+
+    def __init__(self, num, routes, numRouters=1):
+        super(BasicAutonomousSystem, self).__init__(65000+num, numRouters)
+        self.num = num
+        self.routes = routes
+
+    def addLink(self, switch, router=1):
+        self.routers[router].setSwitch(switch)
+
+    def build(self, topology):
+        self.addRouterAndHost(topology)
+
+    def addRouterAndHost(self, topology):
+
+        # TODO implementation is messy and needs to be cleaned up
+
+        intfs = {}
+
+        router = self.routers[1]
+        for i, router in self.routers.items():
+
+            # routerName = 'r%i%i' % (self.num, i)
+            routerName = 'r%i' % self.num
+            if not i == 1:
+                routerName += ('%i' % i)
+
+            hostName = 'h%i' % self.num
+
+            for j, interface in router.interfaces.items():
+                nativeAddresses = interface.addressesByVlan.pop(None, [])
+                peeringIntf = [{'mac' : '00:00:%02x:00:%02x:%02x' % (self.num, i, j),
+                               'ipAddrs' : nativeAddresses}]
+
+                for vlan, addresses in interface.addressesByVlan.items():
+                    peeringIntf.append({'vlan': vlan,
+                                        'mac': '00:00:%02x:%02x:%02x:%02x' % (self.num, vlan, i, j),
+                                        'ipAddrs': addresses})
+
+                intfs.update({'%s-eth%s' % (routerName, j-1) : peeringIntf})
+
+            # Only add the host to the first router for now
+            if i == 1:
+                internalAddresses = []
+                for route in self.routes:
+                    internalAddresses.append('%s/%s' % (AutonomousSystem.getLastAddress(route).ip, route.prefixlen))
+
+                internalIntf = {'ipAddrs' : internalAddresses}
+
+                # This is the configuration of the next interface after all the peering interfaces
+                intfs.update({'%s-eth%s' % (routerName, len(router.interfaces.keys())) : internalIntf})
+
+            routerNode = topology.addHost(routerName,
+                                          asNum=self.asNum, neighbors=router.neighbors,
+                                          routes=self.routes,
+                                          cls=BgpRouter, interfaces=intfs)
+
+            self.routerNodes[i] = routerNode
+
+            for switch in router.switches:
+                topology.addLink(switch, routerNode)
+
+            # Only add the host to the first router for now
+            if i == 1:
+                defaultRoute = internalAddresses[0].split('/')[0]
+
+                host = topology.addHost(hostName, cls=RoutedHost,
+                                        ips=[self.getFirstAddress(route) for route in self.routes],
+                                        gateway=defaultRoute)
+
+                topology.addLink(routerNode, host)
+
+    # def getLastAddress(self, network):
+    #    return ip_address(network.network_address + network.num_addresses - 2)
+
+    def getFirstAddress(self, network):
+        return '%s/%s' % (network[1], network.prefixlen)
+
+# TODO fix this AS - doesn't currently work
+class RouteServerAutonomousSystem(BasicAutonomousSystem):
+
+    def __init__(self, routerAddress, *args, **kwargs):
+        BasicAutonomousSystem.__init__(self, *args, **kwargs)
+
+        self.routerAddress = routerAddress
+
+    def build(self, topology, connectAtSwitch):
+
+        switch = topology.addSwitch('as%isw' % self.num, cls=OVSBridge)
+
+        self.addRouterAndHost(topology, self.routerAddress, switch)
+
+        rsName = 'rs%i' % self.num
+        routeServer = topology.addHost(rsName,
+                                       self.asnum, self.neighbors,
+                                       cls=BgpRouter,
+                                       interfaces={'%s-eth0' % rsName : {'ipAddrs': [self.peeringAddress]}})
+
+        topology.addLink(routeServer, switch)
+        topology.addLink(switch, connectAtSwitch)
+
+class SdnAutonomousSystem(AutonomousSystem):
+
+    """Runs the internal BGP speakers needed for ONOS routing apps like
+    SDN-IP."""
+
+    routerIdx = 1
+
+    def __init__(self, onosIps, num=1, numBgpSpeakers=1, asNum=65000, externalOnos=True,
+                 peerIntfConfig=None, withFpm=False):
+        super(SdnAutonomousSystem, self).__init__(asNum, numBgpSpeakers)
+        self.onosIps = onosIps
+        self.num = num
+        self.numBgpSpeakers = numBgpSpeakers
+        self.peerIntfConfig = peerIntfConfig
+        self.withFpm = withFpm
+        self.externalOnos = externalOnos
+        self.internalPeeringSubnet = ip_network(u'1.1.1.0/24')
+
+        for router in self.routers.values():
+            # Add iBGP sessions to ONOS nodes
+            for onosIp in onosIps:
+                router.neighbors.append({'address': onosIp, 'as': asNum, 'port': 2000})
+
+            # Add iBGP sessions to other BGP speakers
+            for i, router2 in self.routers.items():
+                if router == router2:
+                    continue
+                cpIpBase = self.num*10
+                ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
+                router.neighbors.append({'address': ip.ip, 'as': asNum})
+
+    def build(self, topology, connectAtSwitch, controlSwitch):
+
+        natIp = AutonomousSystem.getLastAddress(self.internalPeeringSubnet)
+
+        for i, router in self.routers.items():
+            num = SdnAutonomousSystem.routerIdx
+            SdnAutonomousSystem.routerIdx += 1
+            name = 'bgp%s' % num
+
+            cpIpBase = self.num*10
+            ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
+
+            eth0 = { 'ipAddrs' : [ str(ip) ] }
+            if self.peerIntfConfig is not None:
+                eth1 = self.peerIntfConfig
+            else:
+                nativeAddresses = router.interfaces[1].addressesByVlan.pop(None, [])
+                eth1 = [{ 'mac': '00:00:00:00:00:%02x' % num,
+                          'ipAddrs' : nativeAddresses }]
+
+                for vlan, addresses in router.interfaces[1].addressesByVlan.items():
+                    eth1.append({'vlan': vlan,
+                                 'mac': '00:00:00:%02x:%02x:00' % (num, vlan),
+                                 'ipAddrs': addresses})
+
+            intfs = { '%s-eth0' % name : eth0,
+                      '%s-eth1' % name : eth1 }
+
+            bgp = topology.addHost( name, cls=BgpRouter, asNum=self.asNum,
+                                    neighbors=router.neighbors,
+                                    interfaces=intfs,
+                                    defaultRoute=str(natIp.ip),
+                                    fpm=self.onosIps[0] if self.withFpm else None )
+
+            topology.addLink( bgp, controlSwitch )
+            topology.addLink( bgp, connectAtSwitch )
+
+        if self.externalOnos:
+            nat = topology.addHost('nat', cls=NAT,
+                                   ip='%s/%s' % (natIp.ip, self.internalPeeringSubnet.prefixlen),
+                                   subnet=str(self.internalPeeringSubnet), inNamespace=False)
+            topology.addLink(controlSwitch, nat)
+
+def generateRoutes(baseRange, numRoutes, subnetSize=None):
+    baseNetwork = ip_network(baseRange)
+
+    # We need to get at least 2 addresses out of each subnet, so the biggest
+    # prefix length we can have is /30
+    maxPrefixLength = baseNetwork.max_prefixlen - 2
+
+    if subnetSize is not None:
+        return list(baseNetwork.subnets(new_prefix=subnetSize))
+
+    trySubnetSize = baseNetwork.prefixlen + 1
+    while trySubnetSize <= maxPrefixLength and \
+            len(list(baseNetwork.subnets(new_prefix=trySubnetSize))) < numRoutes:
+        trySubnetSize += 1
+
+    if trySubnetSize > maxPrefixLength:
+        raise Exception("Can't get enough routes from input parameters")
+
+    return list(baseNetwork.subnets(new_prefix=trySubnetSize))[:numRoutes]
+
+class RoutingCli( CLI ):
+
+    """CLI command that can bring a host up or down. Useful for simulating router failure."""
+
+    def do_host( self, line ):
+        args = line.split()
+        if len(args) != 2:
+            error( 'invalid number of args: host <host name> {up, down}\n' )
+            return
+
+        host = args[ 0 ]
+        command = args[ 1 ]
+        if host not in self.mn or self.mn.get( host ) not in self.mn.hosts:
+            error( 'invalid host: %s\n' % args[ 1 ] )
+        else:
+            if command == 'up':
+                op = 'up'
+            elif command == 'down':
+                op = 'down'
+            else:
+                error( 'invalid command: host <host name> {up, down}\n' )
+                return
+
+            for intf in self.mn.get( host ).intfList( ):
+                intf.link.intf1.ifconfig( op )
+                intf.link.intf2.ifconfig( op )
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellis_fabric.py b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellis_fabric.py
new file mode 100644
index 0000000..73bfda3
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellis_fabric.py
@@ -0,0 +1,434 @@
+#!/usr/bin/python
+import os
+import re
+from optparse import OptionParser
+
+from ipaddress import ip_network
+from mininet.node import RemoteController, OVSBridge, Host, OVSSwitch
+from mininet.link import TCLink
+from mininet.log import setLogLevel
+from mininet.net import Mininet
+from mininet.topo import Topo
+from mininet.nodelib import NAT
+from mininet.cli import CLI
+
+from routinglib import BgpRouter
+from trellislib import TrellisHost, DhcpRelay
+from functools import partial
+
+# Parse command line options and dump results
+def parseOptions():
+    "Parse command line options"
+    parser = OptionParser()
+    parser.add_option( '--spine', dest='spine', type='int', default=2,
+                       help='number of spine switches, default=2' )
+    parser.add_option( '--leaf', dest='leaf', type='int', default=2,
+                       help='number of leaf switches, default=2' )
+    parser.add_option( '--fanout', dest='fanout', type='int', default=2,
+                       help='number of hosts per leaf switch, default=2' )
+    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( '--ipv6', action="store_true", dest='ipv6',
+                       help='hosts are capable to use also ipv6' )
+    parser.add_option( '--dual-homed', action="store_true", dest='dualhomed', default=False,
+                       help='True if the topology is dual-homed, default=False' )
+    parser.add_option( '--vlan', dest='vlan', type='str', default='',
+                       help='list of vlan id for hosts, separated by comma(,).'
+                            'Empty or id with 0 will be unconfigured.' )
+    parser.add_option( '--dhcp-client', action="store_true", dest='dhcpClient', default=False,
+                       help='Set hosts as DhcpClient if True' )
+    parser.add_option( '--dhcp-relay', action="store_true", dest='dhcpRelay', default=False,
+                       help='Connect half of the hosts to switch indirectly (via DHCP relay) if True' )
+    parser.add_option( '--multiple-dhcp-server', action="store_true", dest='multipleServer', default=False,
+                       help='Use another DHCP server for indirectly connected DHCP clients if True' )
+    parser.add_option( '--remote-dhcp-server', action="store_true", dest='remoteServer', default=False,
+                       help='Connect DHCP server indirectly (via gateway) if True' )
+    ( options, args ) = parser.parse_args()
+    return options, args
+
+
+opts, args = parseOptions()
+
+IP6_SUBNET_CLASS = 120
+IP4_SUBNET_CLASS = 24
+
+# TODO: DHCP support
+class IpHost( Host ):
+
+    def __init__( self, name, *args, **kwargs ):
+        super( IpHost, self ).__init__( name, *args, **kwargs )
+        gateway = re.split( '\.|/', kwargs[ 'ip' ] )
+        gateway[ 3 ] = '254'
+        self.gateway = '.'.join( gateway[ 0:4 ] )
+
+    def config( self, **kwargs ):
+        Host.config( self, **kwargs )
+        mtu = "ifconfig " + self.name + "-eth0 mtu 1490"
+        self.cmd( mtu )
+        self.cmd( 'ip route add default via %s' % self.gateway )
+
+class DualHomedIpHost(IpHost):
+    def __init__(self, name, *args, **kwargs):
+        super(DualHomedIpHost, self).__init__(name, **kwargs)
+        self.bond0 = None
+
+    def config(self, **kwargs):
+        super(DualHomedIpHost, self).config(**kwargs)
+        intf0 = self.intfs[0].name
+        intf1 = self.intfs[1].name
+        self.bond0 = "%s-bond0" % self.name
+        self.cmd('modprobe bonding')
+        self.cmd('ip link add %s type bond' % self.bond0)
+        self.cmd('ip link set %s down' % intf0)
+        self.cmd('ip link set %s down' % intf1)
+        self.cmd('ip link set %s master %s' % (intf0, self.bond0))
+        self.cmd('ip link set %s master %s' % (intf1, self.bond0))
+        self.cmd('ip addr flush dev %s' % intf0)
+        self.cmd('ip addr flush dev %s' % intf1)
+        self.cmd('ip link set %s up' % self.bond0)
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link set %s down' % self.bond0)
+        self.cmd('ip link delete %s' % self.bond0)
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(DualHomedIpHost, self).terminate()
+
+
+# TODO: Implement IPv6 support
+class DualHomedLeafSpineFabric (Topo) :
+    def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], ipv6 = False,
+                 dhcp_client = False, dhcp_relay = False,
+                 multiple_server = False, remote_server = False, **opts):
+        # TODO: add support to dhcp_relay, multiple_server and remote_server
+        Topo.__init__(self, **opts)
+        spines = dict()
+        leafs = dict()
+
+        # leaf should be 2 or 4
+
+        # calculate the subnets to use and set options
+        linkopts = dict( bw=100 )
+        # Create spine switches
+        for s in range(spine):
+            spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+
+        # Create leaf switches
+        for ls in range(leaf):
+            leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+
+            # Connect leaf to all spines with dual link
+            for s in range( spine ):
+                switch = spines[ s ]
+                self.addLink(leafs[ls], switch, **linkopts)
+                self.addLink(leafs[ls], switch, **linkopts)
+
+            # Add hosts after paired ToR switches are added.
+            if ls % 2 == 0:
+                continue
+
+            # Add leaf-leaf link
+            self.addLink(leafs[ls], leafs[ls-1])
+
+            dual_ls = ls / 2
+            # Add hosts
+            for f in range(fanout):
+                name = 'h%s%s' % (dual_ls * fanout + f + 1, "v6" if ipv6 else "")
+                if ipv6:
+                    ips = ['2000::%d0%d/%d' % (dual_ls+2, f+1, IP6_SUBNET_CLASS)]
+                    gateway = '2000::%dff' % (dual_ls+2)
+                    mac = '00:bb:00:00:00:%02x' % (dual_ls * fanout + f + 1)
+                else:
+                    ips = ['10.0.%d.%d/%d' % (dual_ls+2, f+1, IP4_SUBNET_CLASS)]
+                    gateway = '10.0.%d.254' % (dual_ls+2)
+                    mac = '00:aa:00:00:00:%02x' % (dual_ls * fanout + f + 1)
+                host = self.addHost( name=name, cls=TrellisHost, ips=ips, gateway=gateway, mac=mac,
+                                     vlan=vlan_id[ dual_ls*fanout + f ] if vlan_id[dual_ls * fanout + f] != 0 else None,
+                                     dhcpClient=dhcp_client, ipv6=ipv6, dualHomed=True )
+                self.addLink(host, leafs[ls], **linkopts)
+                self.addLink(host, leafs[ls-1], **linkopts)
+
+        last_ls = leafs[leaf-2]
+        last_paired_ls = leafs[leaf-1]
+        # Create common components
+        # Control plane switch (for DHCP servers)
+        cs1 = self.addSwitch('cs1', cls=OVSBridge)
+        self.addLink(cs1, last_ls)
+
+        # Control plane switch (for quagga fpm)
+        cs0 = self.addSwitch('cs0', cls=OVSBridge)
+
+        # Control plane NAT (for quagga fpm)
+        nat = self.addHost('nat', cls=NAT,
+                           ip='172.16.0.1/12',
+                           subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
+        self.addLink(cs0, nat)
+
+        # Internal Quagga bgp1
+        intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:03'},
+                 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
+        bgp1 = self.addHost('bgp1', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdbgp1.conf',
+                            zebraConfFile='./zebradbgp1.conf')
+        self.addLink(bgp1, last_ls)
+        self.addLink(bgp1, cs0)
+
+        # Internal Quagga bgp2
+        intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
+                               {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
+                 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/12']}}
+        bgp2 = self.addHost('bgp2', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdbgp2.conf',
+                            zebraConfFile='./zebradbgp2.conf')
+        self.addLink(bgp2, last_paired_ls)
+        self.addLink(bgp2, cs0)
+
+        # External Quagga r1
+        intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
+                 'r1-eth1': {'ipAddrs': ['10.0.5.1/24', '2000::501/120'], 'mac': '00:88:00:00:00:11'},
+                 'r1-eth2': {'ipAddrs': ['10.0.99.1/16']}}
+        r1 = self.addHost('r1', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdr1.conf')
+        self.addLink(r1, last_ls)
+        self.addLink(r1, last_paired_ls)
+
+        # External IPv4 Host behind r1
+        rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+        self.addLink(r1, rh1)
+
+        # External Quagga r2
+        intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24', '2000::601/120'], 'mac': '00:88:00:00:00:02'},
+                 'r2-eth1': {'ipAddrs': ['10.0.7.1/24', '2000::701/120'], 'mac': '00:88:00:00:00:22'},
+                 'r2-eth2': {'ipAddrs': ['10.0.99.1/16']}}
+        r2 = self.addHost('r2', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdr2.conf')
+        self.addLink(r2, last_ls)
+        self.addLink(r2, last_paired_ls)
+
+        # External IPv4 Host behind r2
+        rh2 = self.addHost('rh2', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+        self.addLink(r2, rh2)
+
+        # DHCP server
+        if ipv6:
+            dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                ips=['2000::3fd/120'], gateway='2000::3ff',
+                                dhcpServer=True, ipv6=True)
+            self.addLink(dhcp, cs1)
+        else:
+            dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                ips=['10.0.3.253/24'], gateway='10.0.3.254',
+                                dhcpServer=True)
+            self.addLink(dhcp, cs1)
+
+
+class LeafSpineFabric (Topo) :
+    def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], ipv6 = False,
+                 dhcp_client = False, dhcp_relay = False,
+                 multiple_server = False, remote_server = False, **opts):
+        Topo.__init__(self, **opts)
+        spines = dict()
+        leafs = dict()
+
+        # TODO: support IPv6 hosts
+        linkopts = dict( bw=100 )
+
+        # Create spine switches
+        for s in range(spine):
+            spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
+
+        # Create leaf switches
+        for ls in range(leaf):
+            leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
+
+            # Connect leaf to all spines
+            for s in range( spine ):
+                switch = spines[ s ]
+                self.addLink( leafs[ ls ], switch, **linkopts )
+
+            # If dual-homed ToR, add hosts only when adding second switch at each edge-pair
+            # When the number of leaf switches is odd, leave the last switch as a single ToR
+
+            # Add hosts
+            for f in range(fanout):
+                name = 'h%s%s' % (ls * fanout + f + 1, "v6" if ipv6 else "")
+                if ipv6:
+                    ips = ['2000::%d0%d/%d' % (ls+2, f+1, IP6_SUBNET_CLASS)]
+                    gateway = '2000::%dff' % (ls+2)
+                    mac = '00:bb:00:00:00:%02x' % (ls * fanout + f + 1)
+                else:
+                    ips = ['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)]
+                    gateway = '10.0.%d.254' % (ls+2)
+                    mac = '00:aa:00:00:00:%02x' % (ls * fanout + f + 1)
+                host = self.addHost( name=name, cls=TrellisHost, ips=ips, gateway=gateway, mac=mac,
+                                     vlan=vlan_id[ ls*fanout + f ] if vlan_id[ls * fanout + f] != 0 else None,
+                                     dhcpClient=dhcp_client, ipv6=ipv6 )
+                if dhcp_relay and f % 2:
+                    relayIndex = ls * fanout + f + 1
+                    if ipv6:
+                        intfs = {
+                            'relay%s-eth0' % relayIndex: { 'ipAddrs': ['2000::%dff/%d' % (leaf + ls + 2, IP6_SUBNET_CLASS)] },
+                            'relay%s-eth1' % relayIndex: { 'ipAddrs': ['2000::%d5%d/%d' % (ls + 2, f, IP6_SUBNET_CLASS)] }
+                        }
+                        if remote_server:
+                            serverIp = '2000::99fd'
+                        elif multiple_server:
+                            serverIp = '2000::3fc'
+                        else:
+                            serverIp = '2000::3fd'
+                        dhcpRelay = self.addHost(name='relay%s' % relayIndex, cls=DhcpRelay, serverIp=serverIp,
+                                                 gateway='2000::%dff' % (ls+2), interfaces=intfs)
+                    else:
+                        intfs = {
+                            'relay%s-eth0' % relayIndex: { 'ipAddrs': ['10.0.%d.254/%d' % (leaf + ls + 2, IP4_SUBNET_CLASS)] },
+                            'relay%s-eth1' % relayIndex: { 'ipAddrs': ['10.0.%d.%d/%d' % (ls + 2, f + 99, IP4_SUBNET_CLASS)] }
+                        }
+                        if remote_server:
+                            serverIp = '10.0.99.3'
+                        elif multiple_server:
+                            serverIp = '10.0.3.252'
+                        else:
+                            serverIp = '10.0.3.253'
+                        dhcpRelay = self.addHost(name='relay%s' % relayIndex, cls=DhcpRelay, serverIp=serverIp,
+                                                 gateway='10.0.%d.254' % (ls+2), interfaces=intfs)
+                    self.addLink(host, dhcpRelay, **linkopts)
+                    self.addLink(dhcpRelay, leafs[ls], **linkopts)
+                else:
+                    self.addLink(host, leafs[ls], **linkopts)
+
+        last_ls = leafs[leaf-1]
+        # Create common components
+        # Control plane switch (for DHCP servers)
+        cs1 = self.addSwitch('cs1', cls=OVSBridge)
+        self.addLink(cs1, last_ls)
+
+        # Control plane switch (for quagga fpm)
+        cs0 = self.addSwitch('cs0', cls=OVSBridge)
+
+        # Control plane NAT (for quagga fpm)
+        nat = self.addHost('nat', cls=NAT,
+                           ip='172.16.0.1/12',
+                           subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
+        self.addLink(cs0, nat)
+
+        # Internal Quagga bgp1
+        intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
+                 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
+        bgp1 = self.addHost('bgp1', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdbgp1.conf',
+                            zebraConfFile='./zebradbgp1.conf')
+        self.addLink(bgp1, last_ls)
+        self.addLink(bgp1, cs0)
+
+        # External Quagga r1
+        intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
+                 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']},
+                 'r1-eth2': {'ipAddrs': ['2000::9901/120']}}
+        r1 = self.addHost('r1', cls=BgpRouter,
+                            interfaces=intfs,
+                            quaggaConfFile='./bgpdr1.conf')
+        self.addLink(r1, last_ls)
+
+        # External switch behind r1
+        rs0 = self.addSwitch('rs0', cls=OVSBridge)
+        self.addLink(r1, rs0)
+
+        # External IPv4 Host behind r1
+        rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
+        self.addLink(r1, rh1)
+
+        # External IPv6 Host behind r1
+        rh1v6 = self.addHost('rh1v6', cls=TrellisHost, ips=['2000::9902/120'], gateway='2000::9901')
+        self.addLink(r1, rh1v6)
+
+        # DHCP server
+        if ipv6:
+            if remote_server:
+                dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                    ips=['2000::99fd/120'], gateway='2000::9901',
+                                    dhcpServer=True, ipv6=True)
+                self.addLink(rs0, dhcp)
+            else:
+                dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                    ips=['2000::3fd/120'], gateway='2000::3ff',
+                                    dhcpServer=True, ipv6=True)
+                self.addLink(dhcp, cs1)
+                if multiple_server:
+                    dhcp2 = self.addHost('dhcp2', cls=TrellisHost, mac='00:99:00:00:00:02',
+                                         ips=['2000::3fc/120'], gateway='2000::3ff',
+                                         dhcpServer=True, ipv6=True)
+                    self.addLink(dhcp2, cs1)
+        else:
+            if remote_server:
+                dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                    ips=['10.0.99.3/24'], gateway='10.0.99.1',
+                                    dhcpServer=True)
+                self.addLink(rs0, dhcp)
+            else:
+                dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
+                                    ips=['10.0.3.253/24'], gateway='10.0.3.254',
+                                    dhcpServer=True)
+                self.addLink(dhcp, cs1)
+                if multiple_server:
+                    dhcp2 = self.addHost('dhcp2', cls=TrellisHost, mac='00:99:00:00:00:02',
+                                         ips=['10.0.3.252/24'], gateway='10.0.3.254',
+                                         dhcpServer=True)
+                    self.addLink(dhcp2, cs1)
+
+def config( opts ):
+    spine = opts.spine
+    leaf = opts.leaf
+    fanout = opts.fanout
+    dualhomed = opts.dualhomed
+    if opts.vlan == '':
+        vlan = [0] * (((leaf / 2) if dualhomed else leaf) * fanout)
+    else:
+        vlan = [int(vlan_id) if vlan_id != '' else 0 for vlan_id in opts.vlan.split(',')]
+
+    if opts.onosIp != '':
+        controllers = opts.onosIp.split( ',' )
+    else:
+        controllers = ['127.0.0.1']
+
+    if len(vlan) != ((leaf / 2) if dualhomed else leaf ) * fanout:
+        print "Invalid vlan configuration is given."
+        return
+
+    if dualhomed:
+        if leaf % 2 == 1 or leaf == 0:
+            print "Even number of leaf switches (at least two) are needed to build dual-homed topology."
+            return
+        else:
+            topo = DualHomedLeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan,
+                                            ipv6=opts.ipv6,
+                                            dhcp_client=opts.dhcpClient,
+                                            dhcp_relay=opts.dhcpRelay,
+                                            multiple_server=opts.multipleServer,
+                                            remote_server=opts.remoteServer)
+    else:
+        topo = LeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan, ipv6=opts.ipv6,
+                               dhcp_client=opts.dhcpClient,
+                               dhcp_relay=opts.dhcpRelay,
+                               multiple_server=opts.multipleServer,
+                               remote_server=opts.remoteServer)
+
+    net = Mininet( topo=topo, link=TCLink, build=False,
+                   controller=None, autoSetMacs=True )
+    i = 0
+    for ip in controllers:
+        net.addController( "c%s" % ( i ), controller=RemoteController, ip=ip )
+        i += 1
+    net.build()
+    net.start()
+    CLI( net )
+    net.stop()
+
+if __name__ == '__main__':
+    setLogLevel('info')
+    config(opts)
+    os.system('sudo mn -c')
diff --git a/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellislib.py b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellislib.py
new file mode 100644
index 0000000..ebf6910
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/topologies/lib/trellislib.py
@@ -0,0 +1,401 @@
+#!/usr/bin/python
+
+"""
+Libraries for Trellis hosts.
+"""
+
+import time
+import sys
+sys.path.append('..')
+from mininet.node import Host
+from routinglib import RoutedHost, RoutedHost6, Router
+
+class TaggedRoutedHost(RoutedHost):
+    """Host that can be configured with multiple IP addresses."""
+    def __init__(self, name, ips, gateway, vlan, *args, **kwargs):
+        super(RoutedHost, self).__init__(name, *args, **kwargs)
+        self.ips = ips
+        self.gateway = gateway
+        self.vlan = vlan
+        self.vlanIntf = None
+
+    def config(self, **kwargs):
+        Host.config(self, **kwargs)
+        intf = self.defaultIntf()
+        self.vlanIntf = "%s.%s" % (intf, self.vlan)
+        self.cmd('ip -4 addr flush dev %s' % intf)
+        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)
+
+        for ip in self.ips:
+            self.cmd('ip addr add %s dev %s' % (ip, self.vlanIntf))
+
+        self.cmd('ip route add default via %s' % self.gateway)
+        intf.name = self.vlanIntf
+        self.nameToIntf[self.vlanIntf] = intf
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link remove link %s' % self.vlanIntf)
+        super(TaggedRoutedHost, self).terminate()
+
+class DualHomedRoutedHost(Host):
+    def __init__(self, name, ips, gateway, *args, **kwargs):
+        super(DualHomedRoutedHost, self).__init__(name, **kwargs)
+        self.bond0 = None
+        self.ips = ips
+        self.gateway = gateway
+
+    def config(self, **kwargs):
+        super(DualHomedRoutedHost, self).config(**kwargs)
+        self.bondIntfs( self.intfs[0], self.intfs[1] )
+
+        for ip in self.ips:
+            self.cmd('ip addr add %s dev %s' % (ip, self.bond0))
+
+        self.cmd('ip route add default via %s' % self.gateway)
+
+    def bondIntfs( self, intf1, intf2, bondedName="bond0" ):
+        '''
+        Bond two interfaces together
+        intf1 - the first interface to bond
+        intf2 - the second interface to bond
+        bondedName - the prefix of the new interface name
+        '''
+        # Setup bonded interface
+        # TODO: support multiple bonded interfaces. Maybe just changed self.bond0 to a list of bonded intf names?
+        self.bond0 = "%s-%s" % ( self.name, bondedName )
+        self.cmd('modprobe bonding')
+        self.cmd('ip link add %s type bond' % self.bond0)
+        self.cmd('ip link set %s down' % intf1.name)
+        self.cmd('ip link set %s down' % intf2.name)
+        self.cmd('ip link set %s master %s' % (intf1.name, self.bond0))
+        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('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??
+        lowestIntf = min( [ intf1, intf2 ] )
+        highestIntf = max( [ intf1, intf2 ] )
+        lowestIntf.name = self.bond0
+        self.nameToIntf[self.bond0] = lowestIntf
+        del self.intfs[ self.ports[ highestIntf ] ]
+        del self.ports[ highestIntf ]
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link set %s down' % self.bond0)
+        self.cmd('ip link delete %s' % self.bond0)
+        super(DualHomedRoutedHost, self).terminate()
+
+class DualHomedTaggedRoutedHost(DualHomedRoutedHost):
+    def __init__(self, name, ips, gateway, vlan, *args, **kwargs):
+        super(DualHomedTaggedRoutedHost, self).__init__(name, ips, gateway, *args, **kwargs)
+        self.ips = ips
+        self.gateway = gateway
+        self.vlan = vlan
+        self.vlanIntf = None
+
+    def config(self, **kwargs):
+        super(DualHomedTaggedRoutedHost, self).config(**kwargs)
+        default_intf = self.defaultIntf()
+        self.vlanIntf = "%s.%s" % (default_intf, self.vlan)
+        self.cmd('ip -4 addr flush dev %s' % default_intf)
+        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)
+
+        for ip in self.ips:
+            self.cmd('ip addr add %s dev %s' % (ip, self.vlanIntf))
+
+        self.cmd('ip route add default via %s' % self.gateway)
+        default_intf.name = self.vlanIntf
+        self.nameToIntf[self.vlanIntf] = default_intf
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link remove link %s' % self.vlanIntf)
+        super(DualHomedTaggedRoutedHost, self).terminate()
+
+class DhcpClient(Host):
+    def __init__(self, name, *args, **kwargs):
+        super(DhcpClient, self).__init__(name, **kwargs)
+        self.pidFile = '/run/dhclient-%s.pid' % self.name
+        self.leaseFile = '/var/lib/dhcp/dhcpclient-%s.lease' % (self.name, )
+
+    def config(self, **kwargs):
+        super(DhcpClient, self).config(**kwargs)
+        self.cmd('ip addr flush dev %s' % self.defaultIntf())
+        self.cmd('dhclient -q -4 -nw -pf %s -lf %s %s' % (self.pidFile, self.leaseFile, self.defaultIntf()))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(DhcpClient, self).terminate()
+
+class Dhcp6Client(Host):
+    def __init__(self, name, *args, **kwargs):
+        super(Dhcp6Client, self).__init__(name, **kwargs)
+        self.pidFile = '/run/dhclient-%s.pid' % self.name
+        self.leaseFile = '/var/lib/dhcp/dhcpclient6-%s.lease' % (self.name, )
+
+    def config(self, **kwargs):
+        super(Dhcp6Client, self).config(**kwargs)
+        self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
+        self.cmd('dhclient -q -6 -nw -pf %s -lf %s %s' % (self.pidFile, self.leaseFile, self.defaultIntf()))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(Dhcp6Client, self).terminate()
+
+class DhcpServer(RoutedHost):
+    binFile = '/usr/sbin/dhcpd'
+    pidFile = '/run/dhcp-server-dhcpd.pid'
+    configFile = './dhcpd.conf'
+    leasesFile = '/var/lib/dhcp/dhcpd.leases'
+
+    def config(self, **kwargs):
+        super(DhcpServer, self).config(**kwargs)
+        self.cmd('touch %s' % self.leasesFile)
+        self.cmd('%s -q -4 -pf %s -cf %s %s' % (self.binFile, self.pidFile, self.configFile, self.defaultIntf()))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(DhcpServer, self).terminate()
+
+class Dhcp6Server(RoutedHost6):
+    binFile = '/usr/sbin/dhcpd'
+    pidFile = '/run/dhcp-server-dhcpd6.pid'
+    configFile = './dhcpd6.conf'
+    leasesFile = '/var/lib/dhcp/dhcpd6.leases'
+
+    def config(self, **kwargs):
+        super(Dhcp6Server, self).config(**kwargs)
+        linkLocalAddr = mac_to_ipv6_linklocal(kwargs['mac'])
+        self.cmd('ip -6 addr add dev %s scope link %s' % (self.defaultIntf(), linkLocalAddr))
+        self.cmd('touch %s' % self.leasesFile)
+        self.cmd('%s -q -6 -pf %s -cf %s %s' % (self.binFile, self.pidFile, self.configFile, self.defaultIntf()))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        self.cmd('rm -rf  %s' % self.leasesFile)
+        super(Dhcp6Server, self).terminate()
+
+class DhcpRelay(Router):
+    binFile = '/usr/sbin/dhcrelay'
+    pidFile = '/run/dhcp-relay.pid'
+    serverIp = None
+    gateway = None
+
+    def __init__(self, name, serverIp, gateway, *args, **kwargs):
+        super(DhcpRelay, self).__init__(name, **kwargs)
+        self.serverIp = serverIp
+        self.gateway = gateway
+
+    def config(self, **kwargs):
+        super(DhcpRelay, self).config(**kwargs)
+        ifacesStr = ' '.join(["-i " + ifaceName for ifaceName in self.interfaces.keys()])
+        self.cmd('route add default gw %s' % self.gateway)
+        self.cmd('%s -4 -a -pf %s %s %s' % (self.binFile, self.pidFile, ifacesStr, self.serverIp))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`', self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(DhcpRelay, self).terminate()
+
+class TaggedDhcpClient(Host):
+    def __init__(self, name, vlan, *args, **kwargs):
+        super(TaggedDhcpClient, self).__init__(name, **kwargs)
+        self.pidFile = '/run/dhclient-%s.pid' % self.name
+        self.vlan = vlan
+        self.vlanIntf = None
+
+    def config(self, **kwargs):
+        super(TaggedDhcpClient, self).config(**kwargs)
+        self.vlanIntf = "%s.%s" % (self.defaultIntf(), self.vlan)
+        self.cmd('ip addr flush dev %s' % self.defaultIntf())
+        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))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        self.cmd('ip link remove link %s' % self.vlanIntf)
+        super(TaggedDhcpClient, self).terminate()
+
+class TaggedDhcpServer(TaggedRoutedHost):
+    binFile = '/usr/sbin/dhcpd'
+    pidFile = '/run/dhcp-server/dhcpd.pid'
+    configFile = './dhcpd.conf'
+
+    def config(self, **kwargs):
+        super(TaggedDhcpServer, self).config(**kwargs)
+        self.cmd('%s -q -4 -pf %s -cf %s %s' % (self.binFile, self.pidFile, self.configFile, self.vlanIntf))
+
+    def terminate(self, **kwargs):
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(TaggedDhcpServer, self).terminate()
+
+class DualHomedDhcpClient(Host):
+    def __init__(self, name, *args, **kwargs):
+        super(DualHomedDhcpClient, self).__init__(name, **kwargs)
+        self.pidFile = '/run/dhclient-%s.pid' % self.name
+        self.bond0 = None
+
+    def config(self, **kwargs):
+        super(DualHomedDhcpClient, self).config(**kwargs)
+        self.bondIntfs( self.intfs[0], self.intfs[1] )
+        self.cmd('dhclient -q -4 -nw -pf %s %s' % (self.pidFile, self.bond0))
+
+    def bondIntfs( self, intf1, intf2, bondedName="bond0" ):
+        '''
+        Bond two interfaces together
+        intf1 - the first interface to bond
+        intf2 - the second interface to bond
+        bondedName - the prefix of the new interface name
+        '''
+        # Setup bonded interface
+        # TODO: support multiple bonded interfaces. Maybe just changed self.bond0 to a list of bonded intf names?
+        self.bond0 = "%s-%s" % ( self.name, bondedName )
+        self.cmd('modprobe bonding')
+        self.cmd('ip link add %s type bond' % self.bond0)
+        self.cmd('ip link set %s down' % intf1.name)
+        self.cmd('ip link set %s down' % intf2.name)
+        self.cmd('ip link set %s master %s' % (intf1.name, self.bond0))
+        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('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??
+        lowestIntf = min( [ intf1, intf2 ] )
+        highestIntf = max( [ intf1, intf2 ] )
+        lowestIntf.name = self.bond0
+        self.nameToIntf[self.bond0] = lowestIntf
+        del self.intfs[ self.ports[ highestIntf ] ]
+        del self.ports[ highestIntf ]
+
+    def terminate(self, **kwargs):
+        self.cmd('ip link set %s down' % self.bond0)
+        self.cmd('ip link delete %s' % self.bond0)
+        self.cmd('kill -9 `cat %s`' % self.pidFile)
+        self.cmd('rm -rf %s' % self.pidFile)
+        super(DualHomedDhcpClient, self).terminate()
+
+class TrellisHost(Host):
+    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
+        self.bond0 = None
+        self.vlan = vlan
+        self.vlanIntf = None
+        self.dhcpClient = dhcpClient
+        self.dhcpServer = dhcpServer
+        if dhcpClient:
+            self.pidFile = '/run/dhclient-%s.pid' % self.name
+            self.leaseFile = '/var/lib/dhcp/dhcpclient%s-%s.lease' % ("6" if ipv6 else "", self.name)
+        else:
+            self.ips = ips
+            self.gateway = gateway
+            if dhcpServer:
+                self.binFile = '/usr/sbin/dhcpd'
+                self.pidFile = '/run/dhcp-server-dhcpd%s.pid' % ("6" if ipv6 else "")
+                self.configFile = './dhcpd%s.conf' % ("6" if ipv6 else "")
+                self.leasesFile = '/var/lib/dhcp/dhcpd%s.leases' % ("6" if ipv6 else "")
+        self.ipv6 = ipv6
+
+    def config(self, **kwargs):
+        super(TrellisHost, self).config(**kwargs)
+
+        if self.dualHomed:
+            self.bondIntfs( self.intfs[0], self.intfs[1] )
+
+        self.cmd('ip %s addr flush dev %s' % ("-4" if self.ipv6 else "", self.defaultIntf()))
+
+        if self.vlan:
+            # Setup vlan interface
+            defaultIntf = self.defaultIntf()
+            self.vlanIntf = "%s.%s" % (defaultIntf, self.vlan)
+            self.cmd('ip link add link %s name %s type vlan id %s' % (defaultIntf, self.vlanIntf, self.vlan))
+            self.cmd('ip link set up %s' % self.vlanIntf)
+            defaultIntf.name = self.vlanIntf
+            self.nameToIntf[self.vlanIntf] = defaultIntf
+
+        if self.dhcpClient:
+            time.sleep(3)
+            self.cmd('dhclient -q -%s -nw -pf %s -lf %s %s' % (6 if self.ipv6 else 4, self.pidFile, self.leaseFile, self.defaultIntf()))
+        else:
+            # Setup IP addresses
+            for ip in self.ips:
+                self.cmd('ip addr add %s dev %s' % (ip, self.defaultIntf()))
+            self.cmd('ip route add default via %s' % self.gateway)
+
+            if self.dhcpServer:
+                if self.ipv6:
+                    linkLocalAddr = mac_to_ipv6_linklocal(kwargs['mac'])
+                    self.cmd('ip -6 addr add dev %s scope link %s' % (self.defaultIntf(), linkLocalAddr))
+                self.cmd('touch %s' % self.leasesFile)
+                self.cmd('%s -q -%s -pf %s -cf %s %s' % (self.binFile, 6 if self.ipv6 else 4, self.pidFile, self.configFile, self.defaultIntf()))
+
+    def bondIntfs( self, intf1, intf2, bondedName="bond0" ):
+        '''
+        Bond two interfaces together
+        intf1 - the first interface to bond
+        intf2 - the second interface to bond
+        bondedName - the prefix of the new interface name
+        '''
+        # Setup bonded interface
+        # TODO: support multiple bonded interfaces. Maybe just changed self.bond0 to a list of bonded intf names?
+        self.bond0 = "%s-%s" % ( self.name, bondedName )
+        self.cmd('modprobe bonding')
+        self.cmd('ip link add %s type bond' % self.bond0)
+        self.cmd('ip link set %s down' % intf1.name)
+        self.cmd('ip link set %s down' % intf2.name)
+        self.cmd('ip link set %s master %s' % (intf1.name, self.bond0))
+        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('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??
+        lowestIntf = min( [ intf1, intf2 ] )
+        highestIntf = max( [ intf1, intf2 ] )
+        lowestIntf.name = self.bond0
+        self.nameToIntf[self.bond0] = lowestIntf
+        del self.intfs[ self.ports[ highestIntf ] ]
+        del self.ports[ highestIntf ]
+
+    def terminate(self, **kwargs):
+        if self.vlan:
+            self.cmd('ip link remove link %s' % self.vlanIntf)
+        if self.dualHomed:
+            self.cmd('ip link set %s down' % self.bond0)
+            self.cmd('ip link delete %s' % self.bond0)
+        if self.dhcpClient:
+            self.cmd('kill -9 `cat %s`' % self.pidFile)
+            self.cmd('rm -rf %s' % self.pidFile)
+            self.cmd('rm -rf %s' % self.leaseFile)
+        if self.dhcpServer:
+            self.cmd('kill -9 `cat %s`' % self.pidFile)
+            self.cmd('rm -rf %s' % self.pidFile)
+        super(TrellisHost, self).terminate()
+
+# Utility for IPv6
+def mac_to_ipv6_linklocal(mac):
+    '''
+    Convert mac address to link-local IPv6 address
+    '''
+    # Remove the most common delimiters; dots, dashes, etc.
+    mac_value = int(mac.translate(None, ' .:-'), 16)
+
+    # Split out the bytes that slot into the IPv6 address
+    # XOR the most significant byte with 0x02, inverting the
+    # Universal / Local bit
+    high2 = mac_value >> 32 & 0xffff ^ 0x0200
+    high1 = mac_value >> 24 & 0xff
+    low1 = mac_value >> 16 & 0xff
+    low2 = mac_value & 0xffff
+
+    return 'fe80::{:04x}:{:02x}ff:fe{:02x}:{:04x}'.format(high2, high1, low1, low2)