blob: 8668f83d9f5678894ed8368cc6ad1304fb33239c [file] [log] [blame]
#!/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
from bmv2 import ONOSBmv2Switch
from stratum import StratumBmv2Switch
# 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' )
parser.add_option( '--switch', dest='switch', type='str', default='ovs',
help='Switch type: ovs, bmv2 (with fabric.p4), stratum' )
( options, args ) = parser.parse_args()
return options, args
opts, args = parseOptions()
IP6_SUBNET_CLASS = 120
IP4_SUBNET_CLASS = 24
FABRIC_PIPECONF = "org.onosproject.pipelines.fabric"
SWITCH_TO_PARAMS_DICT = {
"ovs": dict(cls=OVSSwitch),
"bmv2": dict(cls=ONOSBmv2Switch, pipeconf=FABRIC_PIPECONF),
"stratum": dict(cls=StratumBmv2Switch, pipeconf=FABRIC_PIPECONF, loglevel='debug')
}
if opts.switch not in SWITCH_TO_PARAMS_DICT:
raise Exception("Unknown switch type '%s'" % opts.switch)
SWITCH_PARAMS = SWITCH_TO_PARAMS_DICT[opts.switch]
# 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),
**SWITCH_PARAMS )
# Create leaf switches
for ls in range(leaf):
leafs[ls] = self.addSwitch( 'leaf%s' % (ls + 1),
dpid="00000000000%s" % (ls + 1),
**SWITCH_PARAMS )
# 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),
**SWITCH_PARAMS )
# Create leaf switches
for ls in range(leaf):
leafs[ls] = self.addSwitch( 'leaf%s' % (ls + 1),
dpid="00000000000%s" % (ls + 1),
**SWITCH_PARAMS )
# 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')