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