Fixed bmv2.py assigning gRPC/Thrift port already in use

Also handle Mininet exceptions by shutting down all BMv2 instances

Change-Id: I5a8d1a06441daa57c5dd3b0861f1b7484828174a
diff --git a/tools/dev/mininet/bmv2.py b/tools/dev/mininet/bmv2.py
index 08effd0..b6ac2f6 100644
--- a/tools/dev/mininet/bmv2.py
+++ b/tools/dev/mininet/bmv2.py
@@ -1,3 +1,4 @@
+import multiprocessing
 import os
 import socket
 import re
@@ -41,6 +42,9 @@
 
 def watchDog(sw):
     while True:
+        if ONOSBmv2Switch.mininet_exception == 1:
+            sw.killBmv2(log=False)
+            return
         if sw.stopped:
             return
         with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
@@ -72,6 +76,11 @@
 
 class ONOSBmv2Switch(Switch):
     """BMv2 software switch with gRPC server"""
+    # Shared value used to notify to all instances of this class that a Mininet
+    # exception occurred. Mininet exception handling doesn't call the stop()
+    # method, so the mn process would hang after clean-up since Bmv2 would still
+    # be running.
+    mininet_exception = multiprocessing.Value('i', 0)
 
     def __init__(self, name, json=None, debugger=False, loglevel="warn",
                  elogger=False, grpcport=None, cpuport=255,
@@ -79,8 +88,8 @@
                  pktdump=False, valgrind=False, gnmi=False,
                  portcfg=True, onosdevid=None, **kwargs):
         Switch.__init__(self, name, **kwargs)
-        self.grpcPort = pickUnusedPort() if not grpcport else grpcport
-        self.thriftPort = pickUnusedPort() if not thriftport else thriftport
+        self.grpcPort = grpcport
+        self.thriftPort = thriftport
         self.cpuPort = cpuport
         self.json = json
         self.debugger = parseBoolean(debugger)
@@ -111,9 +120,6 @@
         # Remove files from previous executions
         self.cleanupTmpFiles()
 
-        writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
-        writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
-
     def getSourceIp(self, dstIP):
         """
         Queries the Linux routing table to get the source IP that can talk with
@@ -226,6 +232,9 @@
 
         info("\nStarting BMv2 target: %s\n" % cmdString)
 
+        writeToFile("/tmp/bmv2-%s-grpc-port" % self.name, self.grpcPort)
+        writeToFile("/tmp/bmv2-%s-thrift-port" % self.name, self.thriftPort)
+
         try:
             if not self.dryrun:
                 # Start the switch
@@ -238,12 +247,17 @@
                 threading.Thread(target=watchDog, args=[self]).start()
 
             self.doOnosNetcfg(self.controllerIp(controllers))
-        except Exception as ex:
+        except Exception:
+            ONOSBmv2Switch.mininet_exception = 1
             self.killBmv2()
             self.printBmv2Log()
-            raise ex
+            raise
 
     def grpcTargetArgs(self):
+        if self.grpcPort is None:
+            self.grpcPort = pickUnusedPort()
+        if self.thriftPort is None:
+            self.thriftPort = pickUnusedPort()
         args = ['--device-id %s' % str(BMV2_DEFAULT_DEVICE_ID)]
         for port, intf in self.intfs.items():
             if not intf.IP():
@@ -284,7 +298,7 @@
                 break
             # Port is not open yet. If there is time, we wait a bit.
             if endtime > time.time():
-                time.sleep(0.2)
+                time.sleep(0.1)
             else:
                 # Time's up.
                 raise Exception("Switch did not start before timeout")