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