Merge "[ONOS-7736] Host movement tests for dual-homed hosts"
diff --git a/TestON/drivers/common/cli/emulator/mininetclidriver.py b/TestON/drivers/common/cli/emulator/mininetclidriver.py
index ddd72b3..5bf06fb 100644
--- a/TestON/drivers/common/cli/emulator/mininetclidriver.py
+++ b/TestON/drivers/common/cli/emulator/mininetclidriver.py
@@ -914,7 +914,7 @@
 
     def moveHost( self, host, oldSw, newSw, macAddr=None, prefixLen=24 ):
         """
-           Moves a host from one switch to another on the fly
+           Moves an IPv4 host from one switch to another on the fly
            If macAddr is specified, change MAC address of the host interface
            to specified MAC address.
            Note: The intf between host and oldSw when detached
@@ -1021,7 +1021,7 @@
 
     def moveHostv6( self, host, oldSw, newSw, macAddr=None, prefixLen=64 ):
         """
-           Moves a host from one switch to another on the fly
+           Moves an IPv6 host from one switch to another on the fly
            If macAddr is specified, change MAC address of the host interface
            to specified MAC address.
            Note: The intf between host and oldSw when detached
@@ -4056,7 +4056,7 @@
     def moveDualHomedHost( self, host, oldSw, oldPairSw, newSw, newPairSw,
                            macAddr=None, prefixLen=None, bondedName='bond1' ):
         """
-        Moves a dual-homed host from one switch-pair to another pair on the fly
+        Moves a dual-homed IPv4 host from one switch-pair to another pair on the fly
         If macAddr is specified, change MAC address of the bonded host interface
         to specified MAC address.
         Assumes that the host has two interfaces (eth0 and eth1) originally.
@@ -4139,5 +4139,93 @@
                 main.log.exception( self.name + ": Uncaught exception!" )
                 return main.FALSE
 
+    def moveDualHomedHostv6( self, host, oldSw, oldPairSw, newSw, newPairSw,
+                             macAddr=None, prefixLen=None, bondedName='bond1' ):
+        """
+        Moves a dual-homed IPv6 host from one switch-pair to another pair on the fly
+        If macAddr is specified, change MAC address of the bonded host interface
+        to specified MAC address.
+        Assumes that the host has two interfaces (eth0 and eth1) originally.
+        """
+
+        if self.handle:
+            try:
+                IP = str( self.getIPAddress( host, proto='IPV6' ) ) + "/" + str( prefixLen )
+                bond1 = "%s-%s" % ( host, bondedName )
+                newIntf = host + '-eth2'
+                newIntfPair = host + '-eth3'
+                commands = [
+                    # Bring link between oldSw-host down
+                    "py net.configLinkStatus('" + oldSw + "'," + "'" + host + "'," + "'down')",
+                    # Bring link between oldPairSw-host down
+                    "py net.configLinkStatus('" + oldPairSw + "'," + "'" + host + "'," + "'down')",
+                    # Determine hostintf and Oldswitchintf
+                    "px hintf,sintf = " + host + ".connectionsTo(" + oldSw + ")[0]",
+                    # Determine ip and mac address of the host-oldSw interface
+                    'px ipaddr = "{}"'.format( IP ),
+                    "px macaddr = hintf.MAC()" if macAddr is None else 'px macaddr = "%s"' % macAddr,
+                    # Detach interface between oldSw-host
+                    "px " + oldSw + ".detach( sintf )",
+                    # Determine hostintf and Oldpairswitchintf
+                    "px sintfpair,hintfpair = " + oldPairSw + ".connectionsTo(" + host + ")[0]",
+                    # Detach interface between oldPairSw-host
+                    "px " + oldPairSw + ".detach( sintfpair )",
+                    # Add link between host-newSw
+                    "py net.addLink(" + host + "," + newSw + ", 2)",
+                    # Add link between host-newPairSw
+                    "py net.addLink(" + host + "," + newPairSw + ")",
+                    # Determine hostintf and Newswitchintf
+                    "px hintf,sintf = " + host + ".connectionsTo(" + newSw + ")[-1]",
+                    # Determine hostintf and NewPairswitchintf
+                    "px hintfpair,sintfpair = " + host + ".connectionsTo(" + newPairSw + ")[-1]",
+                    # Attach interface between newSw-host
+                    "px " + newSw + ".attach( sintf )",
+                    # Attach interface between newPairSw-host
+                    "px " + newPairSw + ".attach( sintfpair )",
+                    # Bond two interfaces
+                    host + ' ip link add %s type bond' % bond1,
+                    host + ' ip link set %s down' % newIntf,
+                    host + ' ip link set %s down' % newIntfPair,
+                    host + ' ip link set %s master %s' % ( newIntf, bond1 ),
+                    host + ' ip link set %s master %s' % ( newIntfPair, bond1 ),
+                    host + ' ip addr flush dev %s' % newIntf,
+                    host + ' ip addr flush dev %s' % newIntfPair,
+                    host + ' ip link set %s up' % bond1,
+                    "px lowestIntf = min( [ hintf, hintfpair ] )",
+                    "px highestIntf = max( [ hintf, hintfpair ] )",
+                    "px lowestIntf.name = '" + bond1 + "'",
+                    "px " + host + ".nameToIntf['" + bond1 + "'] = lowestIntf",
+                    "px del " + host + ".intfs[ " + host + ".ports[ highestIntf ] ]",
+                    "px del " + host + ".ports[ highestIntf ]",
+                    # Set ipaddress of the host-newSw interface
+                    "px " + host + ".setIP( ip = ipaddr, intf = lowestIntf " +
+                    ( ", prefixLen = %s )" % str( prefixLen ) if prefixLen is not None else " )" ),
+                    # Set macaddress of the host-newSw interface
+                    "px " + host + ".setMAC( mac = macaddr, intf = lowestIntf)",
+                    host + " ip -6 addr add %s dev %s" % ( IP, bond1 ),
+                    "net",
+                    # Determine ipaddress of the bonded host interface
+                    host + " ifconfig",
+                ]
+
+                for cmd in commands:
+                    print "cmd= ", cmd
+                    self.handle.sendline( cmd )
+                    self.handle.expect( "mininet>" )
+                    main.log.info( "====> %s ", self.handle.before )
+                return main.TRUE
+
+            except pexpect.TIMEOUT:
+                main.log.error( self.name + ": TIMEOUT exception found" )
+                main.log.error( self.name + ":     " + self.handle.before )
+                main.cleanAndExit()
+            except pexpect.EOF:
+                main.log.error( self.name + ": EOF exception found" )
+                main.log.error( self.name + ":     " + self.handle.before )
+                return main.FALSE
+            except Exception:
+                main.log.exception( self.name + ": Uncaught exception!" )
+                return main.FALSE
+
 if __name__ != "__main__":
     sys.modules[ __name__ ] = MininetCliDriver()
diff --git a/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.params b/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.params
index 05fc856..027aeb1 100644
--- a/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.params
+++ b/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.params
@@ -1,5 +1,5 @@
 <PARAMS>
-    <testcases>101,102,103,104,105,106,107,108,109,201,202,203,204,205,206,207,208,209,301,302,303,304,305,306,307,308,309,601,602,603,604,605,606,620,621,622,630,640,641,642,643,651</testcases>
+    <testcases>101,102,103,104,105,106,107,108,109,201,202,203,204,205,206,207,208,209,301,302,303,304,305,306,307,308,309,601,602,603,604,605,606,620,621,622,630,640,641,642,643,651,652,653</testcases>
 
     <GRAPH>
         <nodeCluster>Fabric</nodeCluster>
diff --git a/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.py b/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.py
index ce3c666..deed4f6 100644
--- a/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.py
+++ b/TestON/tests/USECASE/SegmentRouting/SRRouting/SRRouting.py
@@ -1301,16 +1301,90 @@
 
         h1v4cfg = '{"of:0000000000000001/7" : { "interfaces" : [ { "ips" : [ "10.1.0.254/24" ], "vlan-untagged": 10 } ] } }'
         lib.moveHost( main, "h1v4", "leaf1", "leaf1", "10.1.0.254", prefixLen=24, cfg=h1v4cfg )
+        hostLocations = { "h1v4": "of:0000000000000001/7" }
+        lib.verifyHostLocations( main, hostLocations )
         verify( main )
 
         h1v6cfg = '{"of:0000000000000001/8" : { "interfaces" : [ { "ips" : [ "1000::3ff/120" ], "vlan-untagged": 21 } ] } }'
         lib.moveHost( main, "h1v6", "leaf1", "leaf1", "1000::3fe", prefixLen=128, cfg=h1v6cfg, ipv6=True )
+        hostLocations = { "h1v6": "of:0000000000000001/8" }
+        lib.verifyHostLocations( main, hostLocations )
         verify( main )
 
         h13v4cfg = '{"of:0000000000000006/7" : { "interfaces" : [ { "ips" : [ "10.5.20.254/24" ], "vlan-untagged": 20 } ] } }'
         lib.moveHost( main, "h13v4", "leaf6", "leaf6", "10.5.20.254", prefixLen=24, cfg=h13v4cfg )
+        hostLocations = { "h13v4": "of:0000000000000006/7" }
+        lib.verifyHostLocations( main, hostLocations )
         verify( main )
 
         h13v6cfg = '{"of:0000000000000006/8" : { "interfaces" : [ { "ips" : [ "1012::3ff/120" ], "vlan-untagged": 26 } ] } }'
         lib.moveHost( main, "h13v6", "leaf6", "leaf6", "1012::3fe", prefixLen=128, cfg=h13v6cfg, ipv6=True )
+        hostLocations = { "h13v6": "of:0000000000000006/8" }
+        lib.verifyHostLocations( main, hostLocations )
         verify( main )
+
+        # TODO: test vlan tagged hosts
+
+    def CASE652( self, main ):
+        """
+        Move a dual-homed host from porst 1A and 1B to ports 2A and 2B
+        Host retains the same MAC and IP address
+        Test connectivity (expect no failure)
+        """
+        import time
+        from tests.USECASE.SegmentRouting.SRRouting.dependencies.SRRoutingTest import *
+        from tests.USECASE.SegmentRouting.dependencies.Testcaselib import Testcaselib as lib
+        main.case( "Move a dual-homed host from porst 1A and 1B to ports 2A and 2B with the same MAC and IP" )
+        setupTest( main, test_idx=652, onosNodes=3 )
+        main.Cluster.active( 0 ).CLI.balanceMasters()
+        time.sleep( float( main.params[ 'timers' ][ 'balanceMasterSleep' ] ) )
+        verify( main )
+
+        h4v4cfg = '''{"of:0000000000000002/12" : { "interfaces" : [ { "ips" : [ "10.2.30.254/24" ], "vlan-untagged": 16 } ] },
+                      "of:0000000000000003/14" : { "interfaces" : [ { "ips" : [ "10.2.30.254/24" ], "vlan-untagged": 16 } ] } }'''
+        lib.moveDualHomedHost( main, "h4v4", "leaf2", "leaf3", "leaf2", "leaf3", "10.2.30.254", prefixLen=24, cfg=h4v4cfg )
+        hostLocations = { "h4v4": [ "of:0000000000000002/12", "of:0000000000000003/14" ] }
+        lib.verifyHostLocations( main, hostLocations )
+        verify( main )
+
+        h4v6cfg = '''{"of:0000000000000002/13" : { "interfaces" : [ { "ips" : [ "1003::3ff/120" ], "vlan-untagged": 24 } ] },
+                      "of:0000000000000003/15" : { "interfaces" : [ { "ips" : [ "1003::3ff/120" ], "vlan-untagged": 24 } ] } }'''
+        lib.moveDualHomedHost( main, "h4v6", "leaf2", "leaf3", "leaf2", "leaf3", "1003::3fe", prefixLen=128, cfg=h4v6cfg, ipv6=True )
+        hostLocations = { "h4v6": [ "of:0000000000000002/13", "of:0000000000000003/15" ] }
+        lib.verifyHostLocations( main, hostLocations )
+        verify( main )
+
+        # TODO: test static routes that point to the moved host
+        # TODO: test vlan tagged hosts
+
+    def CASE653( self, main ):
+        """
+        Move a dual-homed host from porst 1A and 1B to ports 2A and 2B
+        Host retains the same IP but MAC address changes
+        Test connectivity (expect no failure)
+        """
+        import time
+        from tests.USECASE.SegmentRouting.SRRouting.dependencies.SRRoutingTest import *
+        from tests.USECASE.SegmentRouting.dependencies.Testcaselib import Testcaselib as lib
+        main.case( "Move a dual-homed host from porst 1A and 1B to ports 2A and 2B with the same IP and different MAC" )
+        setupTest( main, test_idx=653, onosNodes=3 )
+        main.Cluster.active( 0 ).CLI.balanceMasters()
+        time.sleep( float( main.params[ 'timers' ][ 'balanceMasterSleep' ] ) )
+        verify( main )
+
+        h4v4cfg = '''{"of:0000000000000002/12" : { "interfaces" : [ { "ips" : [ "10.2.30.254/24" ], "vlan-untagged": 16 } ] },
+                      "of:0000000000000003/14" : { "interfaces" : [ { "ips" : [ "10.2.30.254/24" ], "vlan-untagged": 16 } ] } }'''
+        lib.moveDualHomedHost( main, "h4v4", "leaf2", "leaf3", "leaf2", "leaf3", "10.2.30.254", macAddr="00:aa:01:00:00:03", prefixLen=24, cfg=h4v4cfg )
+        hostLocations = { "h4v4": [ "of:0000000000000002/12", "of:0000000000000003/14" ] }
+        lib.verifyHostLocations( main, hostLocations )
+        verify( main )
+
+        h4v6cfg = '''{"of:0000000000000002/13" : { "interfaces" : [ { "ips" : [ "1003::3ff/120" ], "vlan-untagged": 24 } ] },
+                      "of:0000000000000003/15" : { "interfaces" : [ { "ips" : [ "1003::3ff/120" ], "vlan-untagged": 24 } ] } }'''
+        lib.moveDualHomedHost( main, "h4v6", "leaf2", "leaf3", "leaf2", "leaf3", "1003::3fe", macAddr="00:bb:01:00:00:03", prefixLen=128, cfg=h4v6cfg, ipv6=True )
+        hostLocations = { "h4v6": [ "of:0000000000000002/13", "of:0000000000000003/15" ] }
+        lib.verifyHostLocations( main, hostLocations )
+        verify( main )
+
+        # TODO: test static routes that point to the moved host
+        # TODO: test vlan tagged hosts
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
index 6c44c63..1838fc3 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
@@ -1277,7 +1277,7 @@
                     if ip == ipAddr:
                         vlan = hostName.split( "/" )[ -1 ]
                         del main.expectedHosts[ "onos" ][ hostName ]
-                        main.expectedHosts[ "onos" ][ "{}/{}".format( macAddr, vlan ) ] = ip
+                        main.expectedHosts[ "onos" ][ "{}/{}".format( macAddr.upper(), vlan ) ] = ip
                         break
 
     @staticmethod
@@ -1298,25 +1298,27 @@
             macAddr: if specified, change MAC address of the host to the specified MAC address.
             prefixLen: prefix length
             cfg: port configurations as JSON string
-            ipv6: Use True to move IPv6 host (IPv6 is not supported now.)
+            ipv6: Use True to move IPv6 host
         """
-        # TODO: support IPv6 hosts and vlan-tagged hosts.
+        # TODO: support vlan-tagged hosts.
         if not hasattr( main, 'Mininet1' ):
             main.log.warn( "moveDualHomedHost is supposed to be used only in Mininet." )
             return
 
+        main.step( "Moving host {} from {} and {} to {} and {}".format( hostName, srcSw, srcPairSw,
+                                                                        dstSw, dstPairSw ) )
         if ipv6:
-            main.log.warn( "Moving IPv6 host is not implemented yet." )
-            return
-
-        main.Mininet1.moveDualHomedHost( hostName, srcSw, srcPairSw, dstSw, dstPairSw,
-                                         macAddr=macAddr, prefixLen=prefixLen )
-
-        main.Mininet1.changeDefaultGateway( hostName, gw )
-
+            main.Mininet1.moveDualHomedHostv6( hostName, srcSw, srcPairSw, dstSw, dstPairSw,
+                                               macAddr=macAddr, prefixLen=prefixLen )
+        else:
+            main.Mininet1.moveDualHomedHost( hostName, srcSw, srcPairSw, dstSw, dstPairSw,
+                                             macAddr=macAddr, prefixLen=prefixLen )
+            main.Mininet1.changeDefaultGateway( hostName, gw )
         if cfg:
             main.Cluster.active( 0 ).REST.setNetCfg( json.loads( cfg ),
                                                      subjectClass="ports" )
+            # Wait for the host to get RA for setting up default gateway
+            time.sleep( 5 )
 
         main.Mininet1.discoverHosts( [ hostName, ] )
 
@@ -1328,4 +1330,4 @@
                     if ip == ipAddr:
                         vlan = hostName.split( "/" )[ -1 ]
                         del main.expectedHosts[ "onos" ][ hostName ]
-                        main.expectedHosts[ "onos" ][ "{}/{}".format( macAddr, vlan ) ] = ip
+                        main.expectedHosts[ "onos" ][ "{}/{}".format( macAddr.upper(), vlan ) ] = ip