Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
Carmelo Cascone | 977ae3f | 2016-06-23 19:28:28 -0700 | [diff] [blame] | 3 | import os |
| 4 | import sys |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 5 | import json |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 6 | import argparse |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 7 | from collections import OrderedDict |
Carmelo Cascone | 977ae3f | 2016-06-23 19:28:28 -0700 | [diff] [blame] | 8 | |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 9 | TEMP_NETCFG_FILE = '/tmp/bmv2-demo-cfg.json' |
| 10 | BASE_LONGITUDE = -115 |
| 11 | SWITCH_BASE_LATITUDE = 25 |
| 12 | HOST_BASE_LATITUDE = 28 |
| 13 | BASE_SHIFT = 8 |
| 14 | VLAN_NONE = -1 |
| 15 | DEFAULT_SW_BW = 50 |
| 16 | DEFAULT_HOST_BW = 25 |
| 17 | |
Carmelo Cascone | 977ae3f | 2016-06-23 19:28:28 -0700 | [diff] [blame] | 18 | if 'ONOS_ROOT' not in os.environ: |
| 19 | print "Environment var $ONOS_ROOT not set" |
| 20 | exit() |
| 21 | else: |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 22 | ONOS_ROOT = os.environ["ONOS_ROOT"] |
| 23 | sys.path.append(ONOS_ROOT + "/tools/dev/mininet") |
Devin Lim | 0d944e2 | 2017-06-23 15:17:53 -0700 | [diff] [blame] | 24 | if 'RUN_PACK_PATH' not in os.environ: |
| 25 | print "Environment var $RUN_PACK_PATH not set" |
| 26 | exit() |
| 27 | else: |
| 28 | RUN_PACK_PATH = os.environ["RUN_PACK_PATH"] |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 29 | |
| 30 | from onos import ONOSCluster, ONOSCLI |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 31 | from bmv2 import ONOSBmv2Switch, ONOSHost |
Carmelo Cascone | 977ae3f | 2016-06-23 19:28:28 -0700 | [diff] [blame] | 32 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 33 | from itertools import combinations |
| 34 | from time import sleep |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 35 | from subprocess import call |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 36 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 37 | from mininet.cli import CLI |
| 38 | from mininet.link import TCLink |
| 39 | from mininet.log import setLogLevel |
| 40 | from mininet.net import Mininet |
| 41 | from mininet.node import RemoteController, Host |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 42 | from mininet.topo import Topo |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 43 | |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 44 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 45 | class ClosTopo(Topo): |
| 46 | "2 stage Clos topology" |
| 47 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 48 | def __init__(self, args, **opts): |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 49 | # Initialize topology and default options |
| 50 | Topo.__init__(self, **opts) |
| 51 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 52 | bmv2SwitchIds = [] |
| 53 | for row in (1, 2): |
| 54 | for col in range(1, args.size + 1): |
| 55 | bmv2SwitchIds.append("s%d%d" % (row, col)) |
| 56 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 57 | bmv2Switches = {} |
| 58 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 59 | for switchId in bmv2SwitchIds: |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 60 | deviceId = int(switchId[1:]) |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 61 | # Use first number in device id to calculate latitude (row number) |
| 62 | latitude = SWITCH_BASE_LATITUDE + (deviceId // 10) * BASE_SHIFT |
| 63 | |
| 64 | # Use second number in device id to calculate longitude (column number) |
| 65 | longitude = BASE_LONGITUDE + (deviceId % 10) * BASE_SHIFT |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 66 | bmv2Switches[switchId] = self.addSwitch(switchId, |
| 67 | cls=ONOSBmv2Switch, |
| 68 | loglevel="warn", |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 69 | deviceId=deviceId, |
| 70 | netcfg=False, |
| 71 | longitude=longitude, |
| 72 | latitude=latitude, |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 73 | pipeconfId=args.pipeconf_id) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 74 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 75 | for i in range(1, args.size + 1): |
| 76 | for j in range(1, args.size + 1): |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 77 | if i == j: |
| 78 | # 2 links |
| 79 | self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 80 | cls=TCLink, bw=DEFAULT_SW_BW) |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 81 | if args.with_imbalanced_striping: |
| 82 | self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], |
| 83 | cls=TCLink, bw=DEFAULT_SW_BW) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 84 | else: |
| 85 | self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j], |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 86 | cls=TCLink, bw=DEFAULT_SW_BW) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 87 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 88 | for hostId in range(1, args.size + 1): |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 89 | host = self.addHost("h%d" % hostId, |
| 90 | cls=DemoHost, |
| 91 | ip="10.0.0.%d/24" % hostId, |
| 92 | mac='00:00:00:00:00:%02x' % hostId) |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 93 | self.addLink(host, bmv2Switches["s1%d" % hostId], cls=TCLink, bw=DEFAULT_HOST_BW) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 94 | |
| 95 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 96 | class DemoHost(ONOSHost): |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 97 | "Demo host" |
| 98 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 99 | def __init__(self, name, **params): |
| 100 | ONOSHost.__init__(self, name, **params) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 101 | self.exectoken = "/tmp/mn-exec-token-host-%s" % name |
| 102 | self.cmd("touch %s" % self.exectoken) |
| 103 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 104 | def startPingBg(self, h): |
| 105 | self.cmd(self.getInfiniteCmdBg("ping -i0.5 %s" % h.IP())) |
| 106 | self.cmd(self.getInfiniteCmdBg("arping -w5000000 %s" % h.IP())) |
| 107 | |
| 108 | def startIperfServer(self): |
Carmelo Cascone | 6e85404 | 2017-09-11 21:37:53 +0200 | [diff] [blame] | 109 | self.cmd(self.getInfiniteCmdBg("iperf -s -u")) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 110 | |
| 111 | def startIperfClient(self, h, flowBw="512k", numFlows=5, duration=5): |
Carmelo Cascone | 6e85404 | 2017-09-11 21:37:53 +0200 | [diff] [blame] | 112 | iperfCmd = "iperf -c{} -u -b{} -P{} -t{}".format(h.IP(), flowBw, numFlows, duration) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 113 | self.cmd(self.getInfiniteCmdBg(iperfCmd, sleep=0)) |
| 114 | |
| 115 | def stop(self): |
Carmelo Cascone | 6e85404 | 2017-09-11 21:37:53 +0200 | [diff] [blame] | 116 | self.cmd("killall iperf") |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 117 | self.cmd("killall ping") |
| 118 | self.cmd("killall arping") |
| 119 | |
| 120 | def describe(self): |
| 121 | print "**********" |
| 122 | print self.name |
| 123 | print "default interface: %s\t%s\t%s" % ( |
| 124 | self.defaultIntf().name, |
| 125 | self.defaultIntf().IP(), |
| 126 | self.defaultIntf().MAC() |
| 127 | ) |
| 128 | print "**********" |
| 129 | |
| 130 | def getInfiniteCmdBg(self, cmd, logfile="/dev/null", sleep=1): |
| 131 | return "(while [ -e {} ]; " \ |
| 132 | "do {}; " \ |
| 133 | "sleep {}; " \ |
| 134 | "done;) > {} 2>&1 &".format(self.exectoken, cmd, sleep, logfile) |
| 135 | |
| 136 | def getCmdBg(self, cmd, logfile="/dev/null"): |
| 137 | return "{} > {} 2>&1 &".format(cmd, logfile) |
| 138 | |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 139 | |
| 140 | def generateNetcfg(onosIp, net, args): |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 141 | netcfg = OrderedDict() |
| 142 | netcfg['devices'] = {} |
| 143 | netcfg['links'] = {} |
| 144 | netcfg['hosts'] = {} |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 145 | # Device configs |
| 146 | for sw in net.switches: |
| 147 | srcIp = sw.getSourceIp(onosIp) |
| 148 | netcfg['devices'][sw.onosDeviceId] = sw.getDeviceConfig(srcIp) |
| 149 | |
| 150 | hostLocations = {} |
| 151 | # Link configs |
| 152 | for link in net.links: |
| 153 | switchPort = link.intf1.name.split('-') |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 154 | sw1Name = switchPort[0] # s11 |
| 155 | port1Name = switchPort[1] # eth0 |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 156 | port1 = port1Name[3:] |
| 157 | switchPort = link.intf2.name.split('-') |
| 158 | sw2Name = switchPort[0] |
| 159 | port2Name = switchPort[1] |
| 160 | port2 = port2Name[3:] |
| 161 | sw1 = net[sw1Name] |
| 162 | sw2 = net[sw2Name] |
| 163 | if isinstance(sw1, Host): |
| 164 | # record host location and ignore it |
| 165 | # e.g. {'h1': 'device:bmv2:11'} |
| 166 | hostLocations[sw1.name] = '%s/%s' % (sw2.onosDeviceId, port2) |
| 167 | continue |
| 168 | |
| 169 | if isinstance(sw2, Host): |
| 170 | # record host location and ignore it |
| 171 | # e.g. {'h1': 'device:bmv2:11'} |
| 172 | hostLocations[sw2.name] = '%s/%s' % (sw1.onosDeviceId, port1) |
| 173 | continue |
| 174 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 175 | for linkId in ('%s/%s-%s/%s' % (sw1.onosDeviceId, port1, sw2.onosDeviceId, port2), |
| 176 | '%s/%s-%s/%s' % (sw2.onosDeviceId, port2, sw1.onosDeviceId, port1)): |
| 177 | netcfg['links'][linkId] = { |
| 178 | 'basic': { |
| 179 | 'type': 'DIRECT', |
| 180 | 'bandwidth': DEFAULT_SW_BW |
| 181 | } |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 182 | } |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 183 | |
| 184 | # Host configs |
| 185 | longitude = BASE_LONGITUDE |
| 186 | for host in net.hosts: |
| 187 | longitude = longitude + BASE_SHIFT |
| 188 | hostDefaultIntf = host.defaultIntf() |
| 189 | hostMac = host.MAC(hostDefaultIntf) |
| 190 | hostIp = host.IP(hostDefaultIntf) |
| 191 | hostId = '%s/%d' % (hostMac, VLAN_NONE) |
| 192 | location = hostLocations[host.name] |
| 193 | |
| 194 | # use host Id to generate host location |
| 195 | hostConfig = { |
| 196 | 'basic': { |
| 197 | 'locations': [location], |
| 198 | 'ips': [hostIp], |
| 199 | 'name': host.name, |
| 200 | 'latitude': HOST_BASE_LATITUDE, |
| 201 | 'longitude': longitude |
| 202 | } |
| 203 | } |
| 204 | netcfg['hosts'][hostId] = hostConfig |
| 205 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 206 | netcfg["apps"] = { |
| 207 | "org.onosproject.core": { |
| 208 | "core": { |
| 209 | "linkDiscoveryMode": "STRICT" |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 214 | print "Writing network config to %s" % TEMP_NETCFG_FILE |
| 215 | with open(TEMP_NETCFG_FILE, 'w') as tempFile: |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 216 | json.dump(netcfg, tempFile, indent=4) |
| 217 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 218 | |
| 219 | def main(args): |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 220 | if not args.onos_ip: |
| 221 | controller = ONOSCluster('c0', 3) |
| 222 | onosIp = controller.nodes()[0].IP() |
| 223 | else: |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 224 | controller = RemoteController('c0', ip=args.onos_ip) |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 225 | onosIp = args.onos_ip |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 226 | |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 227 | topo = ClosTopo(args) |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 228 | |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 229 | net = Mininet(topo=topo, build=False, controller=[controller]) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 230 | |
| 231 | net.build() |
| 232 | net.start() |
| 233 | |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 234 | print "Network started" |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 235 | |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 236 | # Generate background traffic. |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 237 | sleep(3) |
| 238 | for (h1, h2) in combinations(net.hosts, 2): |
| 239 | h1.startPingBg(h2) |
| 240 | h2.startPingBg(h1) |
| 241 | |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 242 | print "Background ping started" |
| 243 | |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 244 | for h in net.hosts: |
| 245 | h.startIperfServer() |
| 246 | |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 247 | print "Iperf servers started" |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 248 | |
| 249 | # sleep(4) |
| 250 | # print "Starting traffic from h1 to h3..." |
| 251 | # net.hosts[0].startIperfClient(net.hosts[-1], flowBw="200k", numFlows=100, duration=10) |
| 252 | |
Carmelo Cascone | 73f6b6d | 2017-08-24 13:17:55 +0200 | [diff] [blame] | 253 | generateNetcfg(onosIp, net, args) |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 254 | |
Carmelo Cascone | 6e85404 | 2017-09-11 21:37:53 +0200 | [diff] [blame] | 255 | if args.netcfg_sleep > 0: |
| 256 | print "Waiting %d seconds before pushing config to ONOS..." % args.netcfg_sleep |
| 257 | sleep(args.netcfg_sleep) |
| 258 | |
| 259 | print "Pushing config to ONOS..." |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 260 | call(("%s/onos-netcfg" % RUN_PACK_PATH, onosIp, TEMP_NETCFG_FILE)) |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 261 | |
| 262 | if not args.onos_ip: |
| 263 | ONOSCLI(net) |
| 264 | else: |
| 265 | CLI(net) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 266 | |
| 267 | net.stop() |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 268 | call(("rm", "-f", TEMP_NETCFG_FILE)) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 269 | |
| 270 | |
| 271 | if __name__ == '__main__': |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 272 | parser = argparse.ArgumentParser( |
| 273 | description='BMv2 mininet demo script (2-stage Clos topology)') |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 274 | parser.add_argument('--onos-ip', help='ONOS-BMv2 controller IP address', |
Carmelo Cascone | 12e4d8d | 2016-07-05 15:55:15 -0700 | [diff] [blame] | 275 | type=str, action="store", required=False) |
Carmelo Cascone | 3443325 | 2017-08-25 20:27:18 +0200 | [diff] [blame] | 276 | parser.add_argument('--size', help='Number of leaf/spine switches', |
| 277 | type=int, action="store", required=False, default=2) |
| 278 | parser.add_argument('--with-imbalanced-striping', help='Topology with imbalanced striping', |
| 279 | type=bool, action="store", required=False, default=False) |
Yi Tseng | 7875cb7 | 2017-08-08 10:15:58 -0700 | [diff] [blame] | 280 | parser.add_argument('--pipeconf-id', help='Pipeconf ID for switches', |
| 281 | type=str, action="store", required=False, default='') |
Carmelo Cascone | 6e85404 | 2017-09-11 21:37:53 +0200 | [diff] [blame] | 282 | parser.add_argument('--netcfg-sleep', help='Seconds to wait before pushing config to ONOS', |
| 283 | type=int, action="store", required=False, default=5) |
Carmelo Cascone | 785fada | 2016-06-16 18:34:16 -0700 | [diff] [blame] | 284 | args = parser.parse_args() |
| 285 | setLogLevel('info') |
| 286 | main(args) |