Committed by
Gerrit Code Review
Added Bmv2 demo scripts (mininet and netcfg)
Change-Id: I7471a9ebe55f74dbe8c63faef9a8685c48621862
Showing
3 changed files
with
429 additions
and
0 deletions
tools/dev/mininet/bmv2.py
0 → 100644
| 1 | +from mininet.log import error, info | ||
| 2 | +from mininet.node import Switch | ||
| 3 | +from os import environ | ||
| 4 | +from os.path import isfile | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +class ONOSBmv2Switch(Switch): | ||
| 8 | + """BMv2 software switch """ | ||
| 9 | + | ||
| 10 | + thriftPort = 9090 | ||
| 11 | + deviceId = 0 | ||
| 12 | + | ||
| 13 | + def __init__(self, name, thriftPort=None, deviceId=None, debugger=False, | ||
| 14 | + loglevel="warn", elogger=False, persistent=True, **kwargs): | ||
| 15 | + Switch.__init__(self, name, **kwargs) | ||
| 16 | + self.swPath = environ['BMV2_EXE'] | ||
| 17 | + self.jsonPath = environ['BMV2_JSON'] | ||
| 18 | + if not thriftPort: | ||
| 19 | + self.thriftPort = ONOSBmv2Switch.thriftPort | ||
| 20 | + ONOSBmv2Switch.thriftPort += 1 | ||
| 21 | + else: | ||
| 22 | + self.thriftPort = thriftPort | ||
| 23 | + ONOSBmv2Switch.thriftPort = max(thriftPort, ONOSBmv2Switch.thriftPort) | ||
| 24 | + if not deviceId: | ||
| 25 | + if self.dpid: | ||
| 26 | + self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16) | ||
| 27 | + else: | ||
| 28 | + self.deviceId = ONOSBmv2Switch.deviceId | ||
| 29 | + ONOSBmv2Switch.deviceId += 1 | ||
| 30 | + else: | ||
| 31 | + self.deviceId = deviceId | ||
| 32 | + ONOSBmv2Switch.deviceId = max(deviceId, ONOSBmv2Switch.deviceId) | ||
| 33 | + self.debugger = debugger | ||
| 34 | + self.loglevel = loglevel | ||
| 35 | + self.logfile = '/tmp/bmv2-%d.log' % self.deviceId | ||
| 36 | + self.output = open(self.logfile, 'w') | ||
| 37 | + self.elogger = elogger | ||
| 38 | + self.persistent = persistent | ||
| 39 | + if persistent: | ||
| 40 | + self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId | ||
| 41 | + self.cmd("touch %s" % self.exectoken) | ||
| 42 | + | ||
| 43 | + @classmethod | ||
| 44 | + def setup(cls): | ||
| 45 | + err = False | ||
| 46 | + | ||
| 47 | + if 'BMV2_EXE' not in environ: | ||
| 48 | + error("ERROR! environment var $BMV2_EXE not set\n") | ||
| 49 | + err = True | ||
| 50 | + elif not isfile(environ['BMV2_EXE']): | ||
| 51 | + error("ERROR! BMV2_EXE=%s: no such file\n" % environ['BMV2_EXE']) | ||
| 52 | + err = True | ||
| 53 | + | ||
| 54 | + if 'BMV2_JSON' not in environ: | ||
| 55 | + error("ERROR! environment var $BMV2_JSON not set\n") | ||
| 56 | + err = True | ||
| 57 | + elif not isfile(environ['BMV2_JSON']): | ||
| 58 | + error("ERROR! BMV2_JSON=%s: no such file\n" % environ['BMV2_JSON']) | ||
| 59 | + err = True | ||
| 60 | + | ||
| 61 | + if err: | ||
| 62 | + exit(1) | ||
| 63 | + | ||
| 64 | + def start(self, controllers): | ||
| 65 | + args = [self.swPath, '--device-id %s' % str(self.deviceId)] | ||
| 66 | + for port, intf in self.intfs.items(): | ||
| 67 | + if not intf.IP(): | ||
| 68 | + args.append('-i %d@%s' % (port, intf.name)) | ||
| 69 | + if self.thriftPort: | ||
| 70 | + args.append('--thrift-port %d' % self.thriftPort) | ||
| 71 | + if self.elogger: | ||
| 72 | + nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId | ||
| 73 | + args.append('--nanolog %s' % nanomsg) | ||
| 74 | + if self.debugger: | ||
| 75 | + args.append('--debugger') | ||
| 76 | + args.append('--log-console -L%s' % self.loglevel) | ||
| 77 | + args.append(self.jsonPath) | ||
| 78 | + | ||
| 79 | + assert controllers[0] | ||
| 80 | + c = controllers[0] | ||
| 81 | + args.append('--') | ||
| 82 | + args.append('--controller-ip %s' % c.IP()) | ||
| 83 | + args.append('--controller-port %d' % c.port) | ||
| 84 | + | ||
| 85 | + bmv2cmd = " ".join(args) | ||
| 86 | + | ||
| 87 | + info("\nStarting BMv2 target: %s\n" % bmv2cmd) | ||
| 88 | + | ||
| 89 | + if self.persistent: | ||
| 90 | + # Re-exec the switch if it crashes. | ||
| 91 | + cmdStr = "(while [ -e {} ]; " \ | ||
| 92 | + "do {} ; " \ | ||
| 93 | + "sleep 1; " \ | ||
| 94 | + "done;) > {} 2>&1 &".format(self.exectoken, bmv2cmd, self.logfile) | ||
| 95 | + else: | ||
| 96 | + cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile) | ||
| 97 | + | ||
| 98 | + self.cmd(cmdStr) | ||
| 99 | + | ||
| 100 | + def stop(self): | ||
| 101 | + "Terminate switch." | ||
| 102 | + self.output.flush() | ||
| 103 | + if self.persistent: | ||
| 104 | + self.cmd("rm -f %s" % self.exectoken) | ||
| 105 | + self.cmd('kill %' + self.swPath) | ||
| 106 | + self.deleteIntfs() | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +### Exports for bin/mn | ||
| 110 | + | ||
| 111 | +switches = {'onosbmv2': ONOSBmv2Switch} |
tools/test/topos/bmv2-demo-cfg.json
0 → 100644
| 1 | +{ | ||
| 2 | + "apps": { | ||
| 3 | + "org.onosproject.core": { | ||
| 4 | + "core": { | ||
| 5 | + "linkDiscoveryMode": "STRICT" | ||
| 6 | + } | ||
| 7 | + } | ||
| 8 | + }, | ||
| 9 | + "devices": { | ||
| 10 | + "bmv2:192.168.57.100:9090#11": { | ||
| 11 | + "basic": { | ||
| 12 | + "name": "bmv2:11", | ||
| 13 | + "latitude": 40, | ||
| 14 | + "longitude": -107 | ||
| 15 | + } | ||
| 16 | + }, | ||
| 17 | + "bmv2:192.168.57.100:9091#12": { | ||
| 18 | + "basic": { | ||
| 19 | + "name": "bmv2:12", | ||
| 20 | + "latitude": 40, | ||
| 21 | + "longitude": -99 | ||
| 22 | + } | ||
| 23 | + }, | ||
| 24 | + "bmv2:192.168.57.100:9092#13": { | ||
| 25 | + "basic": { | ||
| 26 | + "name": "bmv2:13", | ||
| 27 | + "latitude": 40, | ||
| 28 | + "longitude": -91 | ||
| 29 | + } | ||
| 30 | + }, | ||
| 31 | + "bmv2:192.168.57.100:9093#21": { | ||
| 32 | + "basic": { | ||
| 33 | + "name": "bmv2:21", | ||
| 34 | + "latitude": 46, | ||
| 35 | + "longitude": -107 | ||
| 36 | + } | ||
| 37 | + }, | ||
| 38 | + "bmv2:192.168.57.100:9094#22": { | ||
| 39 | + "basic": { | ||
| 40 | + "name": "bmv2:22", | ||
| 41 | + "latitude": 46, | ||
| 42 | + "longitude": -99 | ||
| 43 | + } | ||
| 44 | + }, | ||
| 45 | + "bmv2:192.168.57.100:9095#23": { | ||
| 46 | + "basic": { | ||
| 47 | + "name": "bmv2:23", | ||
| 48 | + "latitude": 46, | ||
| 49 | + "longitude": -91 | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + }, | ||
| 53 | + "links": { | ||
| 54 | + "bmv2:192.168.57.100:9090#11/1-bmv2:192.168.57.100:9093#21/1": { | ||
| 55 | + "basic": {} | ||
| 56 | + }, | ||
| 57 | + "bmv2:192.168.57.100:9093#21/1-bmv2:192.168.57.100:9090#11/1": { | ||
| 58 | + "basic": {} | ||
| 59 | + }, | ||
| 60 | + "bmv2:192.168.57.100:9090#11/2-bmv2:192.168.57.100:9093#21/2": { | ||
| 61 | + "basic": {} | ||
| 62 | + }, | ||
| 63 | + "bmv2:192.168.57.100:9093#21/2-bmv2:192.168.57.100:9090#11/2": { | ||
| 64 | + "basic": {} | ||
| 65 | + }, | ||
| 66 | + "bmv2:192.168.57.100:9090#11/3-bmv2:192.168.57.100:9094#22/1": { | ||
| 67 | + "basic": {} | ||
| 68 | + }, | ||
| 69 | + "bmv2:192.168.57.100:9094#22/1-bmv2:192.168.57.100:9090#11/3": { | ||
| 70 | + "basic": {} | ||
| 71 | + }, | ||
| 72 | + "bmv2:192.168.57.100:9090#11/4-bmv2:192.168.57.100:9095#23/1": { | ||
| 73 | + "basic": {} | ||
| 74 | + }, | ||
| 75 | + "bmv2:192.168.57.100:9095#23/1-bmv2:192.168.57.100:9090#11/4": { | ||
| 76 | + "basic": {} | ||
| 77 | + }, | ||
| 78 | + "bmv2:192.168.57.100:9091#12/1-bmv2:192.168.57.100:9093#21/3": { | ||
| 79 | + "basic": {} | ||
| 80 | + }, | ||
| 81 | + "bmv2:192.168.57.100:9093#21/3-bmv2:192.168.57.100:9091#12/1": { | ||
| 82 | + "basic": {} | ||
| 83 | + }, | ||
| 84 | + "bmv2:192.168.57.100:9091#12/2-bmv2:192.168.57.100:9094#22/2": { | ||
| 85 | + "basic": {} | ||
| 86 | + }, | ||
| 87 | + "bmv2:192.168.57.100:9094#22/2-bmv2:192.168.57.100:9091#12/2": { | ||
| 88 | + "basic": {} | ||
| 89 | + }, | ||
| 90 | + "bmv2:192.168.57.100:9091#12/3-bmv2:192.168.57.100:9094#22/3": { | ||
| 91 | + "basic": {} | ||
| 92 | + }, | ||
| 93 | + "bmv2:192.168.57.100:9094#22/3-bmv2:192.168.57.100:9091#12/3": { | ||
| 94 | + "basic": {} | ||
| 95 | + }, | ||
| 96 | + "bmv2:192.168.57.100:9091#12/4-bmv2:192.168.57.100:9095#23/2": { | ||
| 97 | + "basic": {} | ||
| 98 | + }, | ||
| 99 | + "bmv2:192.168.57.100:9095#23/2-bmv2:192.168.57.100:9091#12/4": { | ||
| 100 | + "basic": {} | ||
| 101 | + }, | ||
| 102 | + "bmv2:192.168.57.100:9092#13/1-bmv2:192.168.57.100:9093#21/4": { | ||
| 103 | + "basic": {} | ||
| 104 | + }, | ||
| 105 | + "bmv2:192.168.57.100:9093#21/4-bmv2:192.168.57.100:9092#13/1": { | ||
| 106 | + "basic": {} | ||
| 107 | + }, | ||
| 108 | + "bmv2:192.168.57.100:9092#13/2-bmv2:192.168.57.100:9094#22/4": { | ||
| 109 | + "basic": {} | ||
| 110 | + }, | ||
| 111 | + "bmv2:192.168.57.100:9094#22/4-bmv2:192.168.57.100:9092#13/2": { | ||
| 112 | + "basic": {} | ||
| 113 | + }, | ||
| 114 | + "bmv2:192.168.57.100:9092#13/3-bmv2:192.168.57.100:9095#23/3": { | ||
| 115 | + "basic": {} | ||
| 116 | + }, | ||
| 117 | + "bmv2:192.168.57.100:9095#23/3-bmv2:192.168.57.100:9092#13/3": { | ||
| 118 | + "basic": {} | ||
| 119 | + }, | ||
| 120 | + "bmv2:192.168.57.100:9092#13/4-bmv2:192.168.57.100:9095#23/4": { | ||
| 121 | + "basic": {} | ||
| 122 | + }, | ||
| 123 | + "bmv2:192.168.57.100:9095#23/4-bmv2:192.168.57.100:9092#13/4": { | ||
| 124 | + "basic": {} | ||
| 125 | + } | ||
| 126 | + }, | ||
| 127 | + "hosts": { | ||
| 128 | + "00:00:00:00:00:01/-1": { | ||
| 129 | + "basic": { | ||
| 130 | + "location": "bmv2:192.168.57.100:9090#11/5", | ||
| 131 | + "ips": [ | ||
| 132 | + "10.0.0.1" | ||
| 133 | + ], | ||
| 134 | + "name": "h1", | ||
| 135 | + "latitude": 36, | ||
| 136 | + "longitude": -107 | ||
| 137 | + } | ||
| 138 | + }, | ||
| 139 | + "00:00:00:00:00:02/-1": { | ||
| 140 | + "basic": { | ||
| 141 | + "location": "bmv2:192.168.57.100:9091#12/5", | ||
| 142 | + "ips": [ | ||
| 143 | + "10.0.0.2" | ||
| 144 | + ], | ||
| 145 | + "name": "h2", | ||
| 146 | + "latitude": 36, | ||
| 147 | + "longitude": -99 | ||
| 148 | + } | ||
| 149 | + }, | ||
| 150 | + "00:00:00:00:00:03/-1": { | ||
| 151 | + "basic": { | ||
| 152 | + "location": "bmv2:192.168.57.100:9092#13/5", | ||
| 153 | + "ips": [ | ||
| 154 | + "10.0.0.3" | ||
| 155 | + ], | ||
| 156 | + "name": "h3", | ||
| 157 | + "latitude": 36, | ||
| 158 | + "longitude": -91 | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | +} |
tools/test/topos/bmv2-demo.py
0 → 100644
| 1 | +#!/usr/bin/python | ||
| 2 | + | ||
| 3 | +import argparse | ||
| 4 | +from itertools import combinations | ||
| 5 | +from time import sleep | ||
| 6 | + | ||
| 7 | +from bmv2 import ONOSBmv2Switch | ||
| 8 | +from mininet.cli import CLI | ||
| 9 | +from mininet.link import TCLink | ||
| 10 | +from mininet.log import setLogLevel | ||
| 11 | +from mininet.net import Mininet | ||
| 12 | +from mininet.node import RemoteController, Host | ||
| 13 | +from mininet.topo import Topo | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +class ClosTopo(Topo): | ||
| 17 | + "2 stage Clos topology" | ||
| 18 | + | ||
| 19 | + def __init__(self, **opts): | ||
| 20 | + # Initialize topology and default options | ||
| 21 | + Topo.__init__(self, **opts) | ||
| 22 | + | ||
| 23 | + bmv2SwitchIds = ["s11", "s12", "s13", "s21", "s22", "s23"] | ||
| 24 | + | ||
| 25 | + bmv2Switches = {} | ||
| 26 | + | ||
| 27 | + tport = 9090 | ||
| 28 | + for switchId in bmv2SwitchIds: | ||
| 29 | + bmv2Switches[switchId] = self.addSwitch(switchId, | ||
| 30 | + cls=ONOSBmv2Switch, | ||
| 31 | + loglevel="warn", | ||
| 32 | + device_id=int(switchId[1:]), | ||
| 33 | + thrift_port=tport) | ||
| 34 | + tport += 1 | ||
| 35 | + | ||
| 36 | + for i in (1, 2, 3): | ||
| 37 | + for j in (1, 2, 3): | ||
| 38 | + if i == j: | ||
| 39 | + # 2 links | ||
| 40 | + self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], | ||
| 41 | + cls=TCLink, bw=50) | ||
| 42 | + self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], | ||
| 43 | + cls=TCLink, bw=50) | ||
| 44 | + else: | ||
| 45 | + self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], | ||
| 46 | + cls=TCLink, bw=50) | ||
| 47 | + | ||
| 48 | + for hostId in (1, 2, 3): | ||
| 49 | + host = self.addHost("h%d" % hostId, | ||
| 50 | + cls=DemoHost, | ||
| 51 | + ip="10.0.0.%d/24" % hostId, | ||
| 52 | + mac='00:00:00:00:00:%02x' % hostId) | ||
| 53 | + self.addLink(host, bmv2Switches["s1%d" % hostId], cls=TCLink, bw=22) | ||
| 54 | + | ||
| 55 | + | ||
| 56 | +class DemoHost(Host): | ||
| 57 | + "Demo host" | ||
| 58 | + | ||
| 59 | + def __init__(self, name, inNamespace=True, **params): | ||
| 60 | + Host.__init__(self, name, inNamespace=inNamespace, **params) | ||
| 61 | + self.exectoken = "/tmp/mn-exec-token-host-%s" % name | ||
| 62 | + self.cmd("touch %s" % self.exectoken) | ||
| 63 | + | ||
| 64 | + def config(self, **params): | ||
| 65 | + r = super(Host, self).config(**params) | ||
| 66 | + | ||
| 67 | + self.defaultIntf().rename("eth0") | ||
| 68 | + | ||
| 69 | + for off in ["rx", "tx", "sg"]: | ||
| 70 | + cmd = "/sbin/ethtool --offload eth0 %s off" % off | ||
| 71 | + self.cmd(cmd) | ||
| 72 | + | ||
| 73 | + # disable IPv6 | ||
| 74 | + self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") | ||
| 75 | + self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") | ||
| 76 | + self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") | ||
| 77 | + | ||
| 78 | + return r | ||
| 79 | + | ||
| 80 | + def startPingBg(self, h): | ||
| 81 | + self.cmd(self.getInfiniteCmdBg("ping -i0.5 %s" % h.IP())) | ||
| 82 | + self.cmd(self.getInfiniteCmdBg("arping -w5000000 %s" % h.IP())) | ||
| 83 | + | ||
| 84 | + def startIperfServer(self): | ||
| 85 | + self.cmd(self.getInfiniteCmdBg("iperf3 -s")) | ||
| 86 | + | ||
| 87 | + def startIperfClient(self, h, flowBw="512k", numFlows=5, duration=5): | ||
| 88 | + iperfCmd = "iperf3 -c{} -b{} -P{} -t{}".format(h.IP(), flowBw, numFlows, duration) | ||
| 89 | + self.cmd(self.getInfiniteCmdBg(iperfCmd, sleep=0)) | ||
| 90 | + | ||
| 91 | + def stop(self): | ||
| 92 | + self.cmd("killall iperf3") | ||
| 93 | + self.cmd("killall ping") | ||
| 94 | + self.cmd("killall arping") | ||
| 95 | + | ||
| 96 | + def describe(self): | ||
| 97 | + print "**********" | ||
| 98 | + print self.name | ||
| 99 | + print "default interface: %s\t%s\t%s" % ( | ||
| 100 | + self.defaultIntf().name, | ||
| 101 | + self.defaultIntf().IP(), | ||
| 102 | + self.defaultIntf().MAC() | ||
| 103 | + ) | ||
| 104 | + print "**********" | ||
| 105 | + | ||
| 106 | + def getInfiniteCmdBg(self, cmd, logfile="/dev/null", sleep=1): | ||
| 107 | + return "(while [ -e {} ]; " \ | ||
| 108 | + "do {}; " \ | ||
| 109 | + "sleep {}; " \ | ||
| 110 | + "done;) > {} 2>&1 &".format(self.exectoken, cmd, sleep, logfile) | ||
| 111 | + | ||
| 112 | + def getCmdBg(self, cmd, logfile="/dev/null"): | ||
| 113 | + return "{} > {} 2>&1 &".format(cmd, logfile) | ||
| 114 | + | ||
| 115 | + | ||
| 116 | +def main(args): | ||
| 117 | + topo = ClosTopo() | ||
| 118 | + | ||
| 119 | + net = Mininet(topo=topo, build=False) | ||
| 120 | + | ||
| 121 | + net.addController('c0', controller=RemoteController, ip=args.onos_ip, port=args.onos_port) | ||
| 122 | + | ||
| 123 | + net.build() | ||
| 124 | + net.start() | ||
| 125 | + | ||
| 126 | + print "Network started..." | ||
| 127 | + | ||
| 128 | + # Generates background traffic (needed for host discovery and bmv2 config swap). | ||
| 129 | + sleep(3) | ||
| 130 | + for (h1, h2) in combinations(net.hosts, 2): | ||
| 131 | + h1.startPingBg(h2) | ||
| 132 | + h2.startPingBg(h1) | ||
| 133 | + | ||
| 134 | + for h in net.hosts: | ||
| 135 | + h.startIperfServer() | ||
| 136 | + | ||
| 137 | + print "Background ping started..." | ||
| 138 | + | ||
| 139 | + # sleep(4) | ||
| 140 | + # print "Starting traffic from h1 to h3..." | ||
| 141 | + # net.hosts[0].startIperfClient(net.hosts[-1], flowBw="200k", numFlows=100, duration=10) | ||
| 142 | + | ||
| 143 | + CLI(net) | ||
| 144 | + | ||
| 145 | + net.stop() | ||
| 146 | + | ||
| 147 | + | ||
| 148 | +if __name__ == '__main__': | ||
| 149 | + parser = argparse.ArgumentParser(description='BMv2 mininet demo script (2-stage Clos topology)') | ||
| 150 | + parser.add_argument('--onos-ip', help='ONOS-BMv2 controller IP address', | ||
| 151 | + type=str, action="store", required=True) | ||
| 152 | + parser.add_argument('--onos-port', help='ONOS-BMv2 controller port', | ||
| 153 | + type=int, action="store", default=40123) | ||
| 154 | + args = parser.parse_args() | ||
| 155 | + setLogLevel('info') | ||
| 156 | + main(args) |
-
Please register or login to post a comment