| #!/usr/bin/python |
| |
| import os |
| import sys |
| import json |
| import argparse |
| from collections import OrderedDict |
| |
| TEMP_NETCFG_FILE = '/tmp/bmv2-demo-cfg.json' |
| BASE_LONGITUDE = -115 |
| SWITCH_BASE_LATITUDE = 25 |
| HOST_BASE_LATITUDE = 28 |
| BASE_SHIFT = 8 |
| VLAN_NONE = -1 |
| DEFAULT_SW_BW = 50 |
| DEFAULT_HOST_BW = 25 |
| |
| if 'ONOS_ROOT' not in os.environ: |
| print "Environment var $ONOS_ROOT not set" |
| exit() |
| else: |
| ONOS_ROOT = os.environ["ONOS_ROOT"] |
| sys.path.append(ONOS_ROOT + "/tools/dev/mininet") |
| if 'RUN_PACK_PATH' not in os.environ: |
| print "Environment var $RUN_PACK_PATH not set" |
| exit() |
| else: |
| RUN_PACK_PATH = os.environ["RUN_PACK_PATH"] |
| |
| from onos import ONOSCluster, ONOSCLI |
| from bmv2 import ONOSBmv2Switch, ONOSHost |
| |
| from itertools import combinations |
| from time import sleep |
| from subprocess import call |
| |
| from mininet.cli import CLI |
| from mininet.link import TCLink |
| from mininet.log import setLogLevel |
| from mininet.net import Mininet |
| from mininet.node import RemoteController, Host |
| from mininet.topo import Topo |
| |
| |
| def getCmdBg(cmd, logfile="/dev/null"): |
| return "{} > {} 2>&1 &".format(cmd, logfile) |
| |
| |
| class ClosTopo(Topo): |
| """2 stage Clos topology""" |
| |
| def __init__(self, args, **opts): |
| # Initialize topology and default options |
| Topo.__init__(self, **opts) |
| |
| bmv2SwitchIds = [] |
| for row in (1, 2): |
| for col in range(1, args.size + 1): |
| bmv2SwitchIds.append("s%d%d" % (row, col)) |
| |
| bmv2Switches = {} |
| |
| for switchId in bmv2SwitchIds: |
| deviceId = int(switchId[1:]) |
| # Use first number in device id to calculate latitude (row number), |
| # use second to calculate longitude (column number) |
| latitude = SWITCH_BASE_LATITUDE + (deviceId // 10) * BASE_SHIFT |
| longitude = BASE_LONGITUDE + (deviceId % 10) * BASE_SHIFT |
| |
| bmv2Switches[switchId] = self.addSwitch(switchId, |
| cls=ONOSBmv2Switch, |
| loglevel=args.log_level, |
| deviceId=deviceId, |
| netcfg=True, |
| netcfgDelay=0.5, |
| longitude=longitude, |
| latitude=latitude, |
| pipeconfId=args.pipeconf_id) |
| |
| for i in range(1, args.size + 1): |
| for j in range(1, args.size + 1): |
| if i == j: |
| self.addLink(bmv2Switches["s1%d" % i], |
| bmv2Switches["s2%d" % j], |
| cls=TCLink, bw=DEFAULT_SW_BW) |
| if args.with_imbalanced_striping: |
| # 2 links |
| self.addLink(bmv2Switches["s1%d" % i], |
| bmv2Switches["s2%d" % j], |
| cls=TCLink, bw=DEFAULT_SW_BW) |
| else: |
| self.addLink(bmv2Switches["s1%d" % i], |
| bmv2Switches["s2%d" % j], |
| cls=TCLink, bw=DEFAULT_SW_BW) |
| |
| for hostId in range(1, args.size + 1): |
| host = self.addHost("h%d" % hostId, |
| cls=DemoHost, |
| ip="10.0.0.%d/24" % hostId, |
| mac='00:00:00:00:00:%02x' % hostId) |
| self.addLink(host, bmv2Switches["s1%d" % hostId], |
| cls=TCLink, bw=DEFAULT_HOST_BW) |
| |
| |
| class DemoHost(ONOSHost): |
| """Demo host""" |
| |
| def __init__(self, name, **params): |
| ONOSHost.__init__(self, name, **params) |
| self.exectoken = "/tmp/mn-exec-token-host-%s" % name |
| self.cmd("touch %s" % self.exectoken) |
| |
| def startPingBg(self, h): |
| self.cmd(self.getInfiniteCmdBg("ping -i0.5 %s" % h.IP())) |
| self.cmd(self.getInfiniteCmdBg("arping -w5000000 %s" % h.IP())) |
| |
| def startIperfServer(self): |
| self.cmd(self.getInfiniteCmdBg("iperf -s -u")) |
| |
| def startIperfClient(self, h, flowBw="512k", numFlows=5, duration=5): |
| iperfCmd = "iperf -c{} -u -b{} -P{} -t{}".format( |
| h.IP(), flowBw, numFlows, duration) |
| self.cmd(self.getInfiniteCmdBg(iperfCmd, delay=0)) |
| |
| def stop(self, **kwargs): |
| self.cmd("killall iperf") |
| self.cmd("killall ping") |
| self.cmd("killall arping") |
| |
| def describe(self): |
| print "**********" |
| print self.name |
| print "default interface: %s\t%s\t%s" % ( |
| self.defaultIntf().name, |
| self.defaultIntf().IP(), |
| self.defaultIntf().MAC() |
| ) |
| print "**********" |
| |
| def getInfiniteCmdBg(self, cmd, logfile="/dev/null", delay=1): |
| return "(while [ -e {} ]; " \ |
| "do {}; " \ |
| "sleep {}; " \ |
| "done;) > {} 2>&1 &".format(self.exectoken, cmd, delay, logfile) |
| |
| |
| def generateNetcfg(onosIp, net, args): |
| netcfg = OrderedDict() |
| |
| netcfg['hosts'] = {} |
| netcfg['devices'] = {} |
| netcfg['links'] = {} |
| |
| if args.full_netcfg: |
| # Device configs |
| for sw in net.switches: |
| srcIp = sw.getSourceIp(onosIp) |
| netcfg['devices'][sw.onosDeviceId] = sw.getDeviceConfig(srcIp) |
| |
| hostLocations = {} |
| for link in net.links: |
| switchPort = link.intf1.name.split('-') |
| sw1Name = switchPort[0] # s11 |
| port1Name = switchPort[1] # eth0 |
| port1 = port1Name[3:] |
| switchPort = link.intf2.name.split('-') |
| sw2Name = switchPort[0] |
| port2Name = switchPort[1] |
| port2 = port2Name[3:] |
| sw1 = net[sw1Name] |
| sw2 = net[sw2Name] |
| if isinstance(sw1, Host): |
| # record host location and ignore it |
| # e.g. {'h1': 'device:bmv2:11'} |
| hostLocations[sw1.name] = '%s/%s' % (sw2.onosDeviceId, port2) |
| continue |
| |
| if isinstance(sw2, Host): |
| # record host location and ignore it |
| # e.g. {'h1': 'device:bmv2:11'} |
| hostLocations[sw2.name] = '%s/%s' % (sw1.onosDeviceId, port1) |
| continue |
| |
| if args.full_netcfg: |
| # Link configs |
| for linkId in ('%s/%s-%s/%s' % (sw1.onosDeviceId, port1, |
| sw2.onosDeviceId, port2), |
| '%s/%s-%s/%s' % (sw2.onosDeviceId, port2, |
| sw1.onosDeviceId, port1)): |
| netcfg['links'][linkId] = { |
| 'basic': { |
| 'type': 'DIRECT', |
| 'bandwidth': DEFAULT_SW_BW |
| } |
| } |
| |
| # Host configs |
| longitude = BASE_LONGITUDE |
| for host in net.hosts: |
| longitude = longitude + BASE_SHIFT |
| hostDefaultIntf = host.defaultIntf() |
| hostMac = host.MAC(hostDefaultIntf) |
| hostIp = host.IP(hostDefaultIntf) |
| hostId = '%s/%d' % (hostMac, VLAN_NONE) |
| location = hostLocations[host.name] |
| |
| # use host Id to generate host location |
| hostConfig = { |
| 'basic': { |
| 'locations': [location], |
| 'ips': [hostIp], |
| 'name': host.name, |
| 'latitude': HOST_BASE_LATITUDE, |
| 'longitude': longitude |
| } |
| } |
| netcfg['hosts'][hostId] = hostConfig |
| |
| if args.full_netcfg: |
| netcfg["apps"] = { |
| "org.onosproject.core": { |
| "core": { |
| "linkDiscoveryMode": "STRICT" |
| } |
| } |
| } |
| |
| print "Writing network config to %s" % TEMP_NETCFG_FILE |
| with open(TEMP_NETCFG_FILE, 'w') as tempFile: |
| json.dump(netcfg, tempFile, indent=4) |
| |
| |
| def main(args): |
| if not args.onos_ip: |
| controller = ONOSCluster('c0', 3) |
| onosIp = controller.nodes()[0].IP() |
| else: |
| controller = RemoteController('c0', ip=args.onos_ip) |
| onosIp = args.onos_ip |
| |
| topo = ClosTopo(args) |
| |
| net = Mininet(topo=topo, build=False, controller=[controller]) |
| |
| net.build() |
| net.start() |
| |
| print "Network started" |
| |
| # Always generate background pings. |
| sleep(3) |
| for (h1, h2) in combinations(net.hosts, 2): |
| h1.startPingBg(h2) |
| h2.startPingBg(h1) |
| |
| print "Background ping started" |
| |
| for h in net.hosts: |
| h.startIperfServer() |
| |
| print "Iperf servers started" |
| |
| if args.bg_traffic: |
| sleep(4) |
| print "Starting iperf clients..." |
| net.hosts[0].startIperfClient(net.hosts[-1], flowBw="400k", |
| numFlows=50, duration=10) |
| |
| generateNetcfg(onosIp, net, args) |
| |
| if args.netcfg_sleep > 0: |
| print "Waiting %d seconds before pushing config to ONOS..." \ |
| % args.netcfg_sleep |
| sleep(args.netcfg_sleep) |
| |
| print "Pushing config to ONOS..." |
| call(("%s/onos-netcfg" % RUN_PACK_PATH, onosIp, TEMP_NETCFG_FILE)) |
| |
| if not args.onos_ip: |
| ONOSCLI(net) |
| else: |
| CLI(net) |
| |
| net.stop() |
| call(("rm", "-f", TEMP_NETCFG_FILE)) |
| |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser( |
| description='BMv2 mininet demo script (2-stage Clos topology)') |
| parser.add_argument('--onos-ip', help='ONOS-BMv2 controller IP address', |
| type=str, action="store", required=False) |
| parser.add_argument('--size', help='Number of leaf/spine switches', |
| type=int, action="store", required=False, default=2) |
| parser.add_argument('--with-imbalanced-striping', |
| help='Topology with imbalanced striping', |
| type=bool, action="store", required=False, |
| default=False) |
| parser.add_argument('--pipeconf-id', help='Pipeconf ID for switches', |
| type=str, action="store", required=False, default='') |
| parser.add_argument('--netcfg-sleep', |
| help='Seconds to wait before pushing config to ONOS', |
| type=int, action="store", required=False, default=5) |
| parser.add_argument('--log-level', help='BMv2 log level', |
| type=str, action="store", required=False, |
| default='warn') |
| parser.add_argument('--full-netcfg', |
| help='Generate full netcfg JSON with links and devices', |
| type=bool, action="store", required=False, |
| default=False) |
| parser.add_argument('--bg-traffic', |
| help='Starts background traffic', |
| type=bool, action="store", required=False, |
| default=False) |
| setLogLevel('info') |
| main(parser.parse_args()) |