[ONOS-7904] Run CHO test with Segment Routing and H-AGG topology

Change-Id: I493d8762028dc61339e1a8d061e7ab331201917f
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')