Added Bmv2 demo scripts (mininet and netcfg)

Change-Id: I7471a9ebe55f74dbe8c63faef9a8685c48621862
diff --git a/tools/dev/mininet/bmv2.py b/tools/dev/mininet/bmv2.py
new file mode 100644
index 0000000..b2213d1
--- /dev/null
+++ b/tools/dev/mininet/bmv2.py
@@ -0,0 +1,111 @@
+from mininet.log import error, info
+from mininet.node import Switch
+from os import environ
+from os.path import isfile
+
+
+class ONOSBmv2Switch(Switch):
+    """BMv2 software switch """
+
+    thriftPort = 9090
+    deviceId = 0
+
+    def __init__(self, name, thriftPort=None, deviceId=None, debugger=False,
+                 loglevel="warn", elogger=False, persistent=True, **kwargs):
+        Switch.__init__(self, name, **kwargs)
+        self.swPath = environ['BMV2_EXE']
+        self.jsonPath = environ['BMV2_JSON']
+        if not thriftPort:
+            self.thriftPort = ONOSBmv2Switch.thriftPort
+            ONOSBmv2Switch.thriftPort += 1
+        else:
+            self.thriftPort = thriftPort
+            ONOSBmv2Switch.thriftPort = max(thriftPort, ONOSBmv2Switch.thriftPort)
+        if not deviceId:
+            if self.dpid:
+                self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
+            else:
+                self.deviceId = ONOSBmv2Switch.deviceId
+                ONOSBmv2Switch.deviceId += 1
+        else:
+            self.deviceId = deviceId
+            ONOSBmv2Switch.deviceId = max(deviceId, ONOSBmv2Switch.deviceId)
+        self.debugger = debugger
+        self.loglevel = loglevel
+        self.logfile = '/tmp/bmv2-%d.log' % self.deviceId
+        self.output = open(self.logfile, 'w')
+        self.elogger = elogger
+        self.persistent = persistent
+        if persistent:
+            self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
+            self.cmd("touch %s" % self.exectoken)
+
+    @classmethod
+    def setup(cls):
+        err = False
+
+        if 'BMV2_EXE' not in environ:
+            error("ERROR! environment var $BMV2_EXE not set\n")
+            err = True
+        elif not isfile(environ['BMV2_EXE']):
+            error("ERROR! BMV2_EXE=%s: no such file\n" % environ['BMV2_EXE'])
+            err = True
+
+        if 'BMV2_JSON' not in environ:
+            error("ERROR! environment var $BMV2_JSON not set\n")
+            err = True
+        elif not isfile(environ['BMV2_JSON']):
+            error("ERROR! BMV2_JSON=%s: no such file\n" % environ['BMV2_JSON'])
+            err = True
+
+        if err:
+            exit(1)
+
+    def start(self, controllers):
+        args = [self.swPath, '--device-id %s' % str(self.deviceId)]
+        for port, intf in self.intfs.items():
+            if not intf.IP():
+                args.append('-i %d@%s' % (port, intf.name))
+        if self.thriftPort:
+            args.append('--thrift-port %d' % self.thriftPort)
+        if self.elogger:
+            nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId
+            args.append('--nanolog %s' % nanomsg)
+        if self.debugger:
+            args.append('--debugger')
+        args.append('--log-console -L%s' % self.loglevel)
+        args.append(self.jsonPath)
+
+        assert controllers[0]
+        c = controllers[0]
+        args.append('--')
+        args.append('--controller-ip %s' % c.IP())
+        args.append('--controller-port %d' % c.port)
+
+        bmv2cmd = " ".join(args)
+
+        info("\nStarting BMv2 target: %s\n" % bmv2cmd)
+
+        if self.persistent:
+            # Re-exec the switch if it crashes.
+            cmdStr = "(while [ -e {} ]; " \
+                     "do {} ; " \
+                     "sleep 1; " \
+                     "done;) > {} 2>&1 &".format(self.exectoken, bmv2cmd, self.logfile)
+        else:
+            cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile)
+
+        self.cmd(cmdStr)
+
+    def stop(self):
+        "Terminate switch."
+        self.output.flush()
+        if self.persistent:
+            self.cmd("rm -f %s" % self.exectoken)
+        self.cmd('kill %' + self.swPath)
+        self.deleteIntfs()
+
+
+### Exports for bin/mn
+
+switches = {'onosbmv2': ONOSBmv2Switch}
diff --git a/tools/test/topos/bmv2-demo-cfg.json b/tools/test/topos/bmv2-demo-cfg.json
new file mode 100644
index 0000000..4ff2e0d
--- /dev/null
+++ b/tools/test/topos/bmv2-demo-cfg.json
@@ -0,0 +1,162 @@
+{
+  "apps": {
+    "org.onosproject.core": {
+      "core": {
+        "linkDiscoveryMode": "STRICT"
+      }
+    }
+  },
+  "devices": {
+    "bmv2:192.168.57.100:9090#11": {
+      "basic": {
+        "name": "bmv2:11",
+        "latitude": 40,
+        "longitude": -107
+      }
+    },
+    "bmv2:192.168.57.100:9091#12": {
+      "basic": {
+        "name": "bmv2:12",
+        "latitude": 40,
+        "longitude": -99
+      }
+    },
+    "bmv2:192.168.57.100:9092#13": {
+      "basic": {
+        "name": "bmv2:13",
+        "latitude": 40,
+        "longitude": -91
+      }
+    },
+    "bmv2:192.168.57.100:9093#21": {
+      "basic": {
+        "name": "bmv2:21",
+        "latitude": 46,
+        "longitude": -107
+      }
+    },
+    "bmv2:192.168.57.100:9094#22": {
+      "basic": {
+        "name": "bmv2:22",
+        "latitude": 46,
+        "longitude": -99
+      }
+    },
+    "bmv2:192.168.57.100:9095#23": {
+      "basic": {
+        "name": "bmv2:23",
+        "latitude": 46,
+        "longitude": -91
+      }
+    }
+  },
+  "links": {
+    "bmv2:192.168.57.100:9090#11/1-bmv2:192.168.57.100:9093#21/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9093#21/1-bmv2:192.168.57.100:9090#11/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9090#11/2-bmv2:192.168.57.100:9093#21/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9093#21/2-bmv2:192.168.57.100:9090#11/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9090#11/3-bmv2:192.168.57.100:9094#22/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9094#22/1-bmv2:192.168.57.100:9090#11/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9090#11/4-bmv2:192.168.57.100:9095#23/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9095#23/1-bmv2:192.168.57.100:9090#11/4": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9091#12/1-bmv2:192.168.57.100:9093#21/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9093#21/3-bmv2:192.168.57.100:9091#12/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9091#12/2-bmv2:192.168.57.100:9094#22/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9094#22/2-bmv2:192.168.57.100:9091#12/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9091#12/3-bmv2:192.168.57.100:9094#22/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9094#22/3-bmv2:192.168.57.100:9091#12/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9091#12/4-bmv2:192.168.57.100:9095#23/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9095#23/2-bmv2:192.168.57.100:9091#12/4": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9092#13/1-bmv2:192.168.57.100:9093#21/4": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9093#21/4-bmv2:192.168.57.100:9092#13/1": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9092#13/2-bmv2:192.168.57.100:9094#22/4": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9094#22/4-bmv2:192.168.57.100:9092#13/2": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9092#13/3-bmv2:192.168.57.100:9095#23/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9095#23/3-bmv2:192.168.57.100:9092#13/3": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9092#13/4-bmv2:192.168.57.100:9095#23/4": {
+      "basic": {}
+    },
+    "bmv2:192.168.57.100:9095#23/4-bmv2:192.168.57.100:9092#13/4": {
+      "basic": {}
+    }
+  },
+  "hosts": {
+    "00:00:00:00:00:01/-1": {
+      "basic": {
+        "location": "bmv2:192.168.57.100:9090#11/5",
+        "ips": [
+          "10.0.0.1"
+        ],
+        "name": "h1",
+        "latitude": 36,
+        "longitude": -107
+      }
+    },
+    "00:00:00:00:00:02/-1": {
+      "basic": {
+        "location": "bmv2:192.168.57.100:9091#12/5",
+        "ips": [
+          "10.0.0.2"
+        ],
+        "name": "h2",
+        "latitude": 36,
+        "longitude": -99
+      }
+    },
+    "00:00:00:00:00:03/-1": {
+      "basic": {
+        "location": "bmv2:192.168.57.100:9092#13/5",
+        "ips": [
+          "10.0.0.3"
+        ],
+        "name": "h3",
+        "latitude": 36,
+        "longitude": -91
+      }
+    }
+  }
+}
diff --git a/tools/test/topos/bmv2-demo.py b/tools/test/topos/bmv2-demo.py
new file mode 100644
index 0000000..c833d08
--- /dev/null
+++ b/tools/test/topos/bmv2-demo.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+
+import argparse
+from itertools import combinations
+from time import sleep
+
+from bmv2 import ONOSBmv2Switch
+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
+
+
+class ClosTopo(Topo):
+    "2 stage Clos topology"
+
+    def __init__(self, **opts):
+        # Initialize topology and default options
+        Topo.__init__(self, **opts)
+
+        bmv2SwitchIds = ["s11", "s12", "s13", "s21", "s22", "s23"]
+
+        bmv2Switches = {}
+
+        tport = 9090
+        for switchId in bmv2SwitchIds:
+            bmv2Switches[switchId] = self.addSwitch(switchId,
+                                                    cls=ONOSBmv2Switch,
+                                                    loglevel="warn",
+                                                    device_id=int(switchId[1:]),
+                                                    thrift_port=tport)
+            tport += 1
+
+        for i in (1, 2, 3):
+            for j in (1, 2, 3):
+                if i == j:
+                    # 2 links
+                    self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
+                                 cls=TCLink, bw=50)
+                    self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
+                                 cls=TCLink, bw=50)
+                else:
+                    self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
+                                 cls=TCLink, bw=50)
+
+        for hostId in (1, 2, 3):
+            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=22)
+
+
+class DemoHost(Host):
+    "Demo host"
+
+    def __init__(self, name, inNamespace=True, **params):
+        Host.__init__(self, name, inNamespace=inNamespace, **params)
+        self.exectoken = "/tmp/mn-exec-token-host-%s" % name
+        self.cmd("touch %s" % self.exectoken)
+
+    def config(self, **params):
+        r = super(Host, self).config(**params)
+
+        self.defaultIntf().rename("eth0")
+
+        for off in ["rx", "tx", "sg"]:
+            cmd = "/sbin/ethtool --offload eth0 %s off" % off
+            self.cmd(cmd)
+
+        # disable IPv6
+        self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+        self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1")
+        self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1")
+
+        return r
+
+    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("iperf3 -s"))
+
+    def startIperfClient(self, h, flowBw="512k", numFlows=5, duration=5):
+        iperfCmd = "iperf3 -c{} -b{} -P{} -t{}".format(h.IP(), flowBw, numFlows, duration)
+        self.cmd(self.getInfiniteCmdBg(iperfCmd, sleep=0))
+
+    def stop(self):
+        self.cmd("killall iperf3")
+        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", sleep=1):
+        return "(while [ -e {} ]; " \
+               "do {}; " \
+               "sleep {}; " \
+               "done;) > {} 2>&1 &".format(self.exectoken, cmd, sleep, logfile)
+
+    def getCmdBg(self, cmd, logfile="/dev/null"):
+        return "{} > {} 2>&1 &".format(cmd, logfile)
+
+
+def main(args):
+    topo = ClosTopo()
+
+    net = Mininet(topo=topo, build=False)
+
+    net.addController('c0', controller=RemoteController, ip=args.onos_ip, port=args.onos_port)
+
+    net.build()
+    net.start()
+
+    print "Network started..."
+
+    # Generates background traffic (needed for host discovery and bmv2 config swap).
+    sleep(3)
+    for (h1, h2) in combinations(net.hosts, 2):
+        h1.startPingBg(h2)
+        h2.startPingBg(h1)
+
+    for h in net.hosts:
+        h.startIperfServer()
+
+    print "Background ping started..."
+
+    # sleep(4)
+    # print "Starting traffic from h1 to h3..."
+    # net.hosts[0].startIperfClient(net.hosts[-1], flowBw="200k", numFlows=100, duration=10)
+
+    CLI(net)
+
+    net.stop()
+
+
+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=True)
+    parser.add_argument('--onos-port', help='ONOS-BMv2 controller port',
+                        type=int, action="store", default=40123)
+    args = parser.parse_args()
+    setLogLevel('info')
+    main(args)