Varius updates to BMv2 mininet scripts

Most notably:
- Added support for onos.py's ONOSCluster controller (it works with
	"mn --custom onos.py,bmv2.py --switch onosbmv2 ...")
- Randomly select an open port for the Thrift RPC server

Change-Id: Ifa974741dc4a3693777f01866b1f6203d0e7e75e
diff --git a/tools/dev/mininet/bmv2.py b/tools/dev/mininet/bmv2.py
index b2213d1..44f66c2 100644
--- a/tools/dev/mininet/bmv2.py
+++ b/tools/dev/mininet/bmv2.py
@@ -1,3 +1,5 @@
+import socket
+
 from mininet.log import error, info
 from mininet.node import Switch
 from os import environ
@@ -7,20 +9,18 @@
 class ONOSBmv2Switch(Switch):
     """BMv2 software switch """
 
-    thriftPort = 9090
     deviceId = 0
+    instanceCount = 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:
+        if thriftPort:
             self.thriftPort = thriftPort
-            ONOSBmv2Switch.thriftPort = max(thriftPort, ONOSBmv2Switch.thriftPort)
+        else:
+            self.thriftPort = ONOSBmv2Switch.pickUnusedPort()
         if not deviceId:
             if self.dpid:
                 self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
@@ -39,25 +39,32 @@
         if persistent:
             self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
             self.cmd("touch %s" % self.exectoken)
+        # Store thrift port for future uses.
+        self.cmd("echo %d > /tmp/bmv2-%d-thrift-port" % (self.thriftPort, self.deviceId))
+
+    @classmethod
+    def pickUnusedPort(cls):
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.bind(('localhost', 0))
+        addr, port = s.getsockname()
+        s.close()
+        return port
 
     @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)
 
@@ -75,17 +82,23 @@
             args.append('--debugger')
         args.append('--log-console -L%s' % self.loglevel)
         args.append(self.jsonPath)
-
-        assert controllers[0]
-        c = controllers[0]
+        try:  # onos.py
+            clist = controllers[0].nodes()
+        except AttributeError:
+            clist = controllers
+        assert len(clist) > 0
+        # BMv2 can't connect to multiple controllers.
+        # Uniformly balance connections among available ones.
+        cip = clist[ONOSBmv2Switch.instanceCount % len(clist)].IP()
+        ONOSBmv2Switch.instanceCount += 1
+        # BMv2 controler port is hardcoded here as it is hardcoded also in ONOS.
+        cport = 40123
         args.append('--')
-        args.append('--controller-ip %s' % c.IP())
-        args.append('--controller-port %d' % c.port)
+        args.append('--controller-ip %s' % cip)
+        args.append('--controller-port %d' % cport)
 
         bmv2cmd = " ".join(args)
-
         info("\nStarting BMv2 target: %s\n" % bmv2cmd)
-
         if self.persistent:
             # Re-exec the switch if it crashes.
             cmdStr = "(while [ -e {} ]; " \
@@ -94,18 +107,16 @@
                      "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("rm -f /tmp/bmv2-%d-*" % self.deviceId)
+        self.cmd("rm -f /tmp/bmv2-%d.log" % self.deviceId)
         self.cmd('kill %' + self.swPath)
         self.deleteIntfs()
 
 
 ### Exports for bin/mn
-
 switches = {'onosbmv2': ONOSBmv2Switch}