Add disconnect() and wait_disconnect() methods to controller.
When current connection to the switch is disconnected,
allow controller to continue listening without killing controller thread.
Rework cxn tests to use this new controller model.
diff --git a/tests/cxn.py b/tests/cxn.py
index b658074..a4b3d8a 100644
--- a/tests/cxn.py
+++ b/tests/cxn.py
@@ -50,6 +50,9 @@
     Base handshake case to set up controller, but do not send hello.
     """
 
+    controllers = []
+    default_timeout = 2
+
     def sig_handler(self, v1, v2):
         cxn_logger.critical("Received interrupt signal; exiting")
         print "Received interrupt signal; exiting"
@@ -58,21 +61,15 @@
         sys.exit(1)
 
     def controllerSetup(self, host, port):
-        self.controller = controller.Controller(host=host,port=port)
+        con = controller.Controller(host=host,port=port)
 
         # clean_shutdown should be set to False to force quit app
         self.clean_shutdown = True
         # disable initial hello so hello is under control of test
-        self.controller.initial_hello = False
+        con.initial_hello = False
 
-        self.controller.start()
-        #@todo Add an option to wait for a pkt transaction to ensure version
-        # compatibilty?
-        self.controller.connect(timeout=10)
-        self.assertTrue(self.controller.active,
-                        "Controller startup failed, not active")
-        self.assertTrue(self.controller.switch_addr is not None,
-                        "Controller startup failed, no switch addr")
+        con.start()
+        self.controllers.append(con)
 
     def setUp(self):
         self.logger = cxn_logger
@@ -85,35 +82,15 @@
            cxn_logger.info("Could not set SIGINT handler: %s" % e)
         cxn_logger.info("** START TEST CASE " + str(self))
 
-        self.test_timeout = test_param_get(cxn_config,
-                                           'handshake_timeout') or 60
+        self.default_timeout = test_param_get(cxn_config,
+                                              'default_timeout') or 2
 
-    def inheritSetup(self, parent):
-        """
-        Inherit the setup of a parent
-
-        This allows running at test from within another test.  Do the
-        following:
-
-        sub_test = SomeTestClass()  # Create an instance of the test class
-        sub_test.inheritSetup(self) # Inherit setup of parent
-        sub_test.runTest()          # Run the test
-
-        Normally, only the parent's setUp and tearDown are called and
-        the state after the sub_test is run must be taken into account
-        by subsequent operations.
-        """
-        self.logger = parent.logger
-        self.config = parent.config
-        cxn_logger.info("** Setup " + str(self) + 
-                                    " inheriting from " + str(parent))
-        self.controller = parent.controller
-        
     def tearDown(self):
         cxn_logger.info("** END TEST CASE " + str(self))
-        self.controller.shutdown()
-        if self.clean_shutdown:
-            self.controller.join()
+        for con in self.controllers:
+            con.shutdown()
+            if self.clean_shutdown:
+                con.join()
 
     def runTest(self):
         # do nothing in the base case
@@ -134,18 +111,15 @@
     def runTest(self):
         self.controllerSetup(cxn_config["controller_host"],
                              cxn_config["controller_port"])
-
+        self.controllers[0].connect(self.default_timeout)
         cxn_logger.info("TCP Connected " + 
-                        str(self.controller.switch_addr))
+                        str(self.controllers[0].switch_addr))
         cxn_logger.info("Hello not sent, waiting for timeout")
 
         # wait for controller to die
         count = 0
-        while self.controller.active and count < self.test_timeout:
-            time.sleep(1)
-            count = count + 1
-        self.assertTrue(not self.controller.active, 
-                        "Expected controller disconnect, but still active")
+        self.assertTrue(self.controllers[0].wait_disconnected(timeout=10),
+                        "Not notified of controller disconnect")
 
 class HandshakeNoFeaturesRequest(BaseHandshake):
     """
@@ -155,21 +129,18 @@
     def runTest(self):
         self.controllerSetup(cxn_config["controller_host"],
                              cxn_config["controller_port"])
-
+        self.controllers[0].connect(self.default_timeout)
         cxn_logger.info("TCP Connected " + 
-                                    str(self.controller.switch_addr))
+                                    str(self.controllers[0].switch_addr))
         cxn_logger.info("Sending hello")
-        self.controller.message_send(message.hello())
+        self.controllers[0].message_send(message.hello())
 
         cxn_logger.info("Features request not sent, waiting for timeout")
 
         # wait for controller to die
         count = 0
-        while self.controller.active and count < self.test_timeout:
-            time.sleep(1)
-            count = count + 1
-        self.assertTrue(not self.controller.active, 
-                        "Expected controller disconnect, but still active")
+        self.assertTrue(self.controllers[0].wait_disconnected(timeout=10),
+                        "Not notified of controller disconnect")
 
 class HandshakeAndKeepalive(BaseHandshake):
     """
@@ -177,29 +148,55 @@
     Good for manual testing.
     """
     def runTest(self):
-        self.controllerSetup(cxn_config["controller_host"],
-                             cxn_config["controller_port"])
+        self.num_controllers = test_param_get(cxn_config, 
+                                              'num_controllers') or 1
 
-        cxn_logger.info("TCP Connected " + 
-                                    str(self.controller.switch_addr))
-        cxn_logger.info("Sending hello")
-        self.controller.message_send(message.hello())
+        for i in range(self.num_controllers):
+            self.controllerSetup(cxn_config["controller_host"],
+                                 cxn_config["controller_port"]+i)
+        for i in range(self.num_controllers):
+            self.controllers[i].handshake_done = False
 
-        request = message.features_request()
-        reply, pkt = self.controller.transact(request, timeout=20)
-        self.assertTrue(reply is not None,
-                        "Did not complete features_request for handshake")
-        cxn_logger.info("Handshake complete with " + 
-                        str(self.controller.switch_addr))
-
-        self.controller.keep_alive = True
-
-        # keep controller up forever
-        while self.controller.active:
-            time.sleep(1)
-
-        self.assertTrue(not self.controller.active, 
-                        "Expected controller disconnect, but still active")
+        # try to maintain switch connections forever
+        count = 0
+        while True:
+            for con in self.controllers:
+                if con.switch_socket and con.handshake_done:
+                    if count < 7:
+                        cxn_logger.info(con.host + ":" + str(con.port) + 
+                                        ": maintaining connection to " +
+                                        str(con.switch_addr))
+                        count = count + 1
+                    else:
+                        cxn_logger.info(con.host + ":" + str(con.port) + 
+                                        ": disconnecting from " +
+                                        str(con.switch_addr))
+                        con.disconnect()
+                        con.handshake_done = False
+                        count = 0
+                    time.sleep(1)
+                else:
+                    #@todo Add an option to wait for a pkt transaction to 
+                    # ensure version compatibilty?
+                    con.connect(self.default_timeout)
+                    if not con.switch_socket:
+                        cxn_logger.info("Did not connect to switch")
+                        continue
+                    cxn_logger.info("TCP Connected " + str(con.switch_addr))
+                    cxn_logger.info("Sending hello")
+                    con.message_send(message.hello())
+                    request = message.features_request()
+                    reply, pkt = con.transact(request, 
+                                              timeout=self.default_timeout)
+                    if reply:
+                        cxn_logger.info("Handshake complete with " + 
+                                        str(con.switch_addr))
+                        con.handshake_done = True
+                        con.keep_alive = True
+                    else:
+                        cxn_logger.info("Did not complete features_request for handshake")
+                        con.disconnect()
+                        con.handshake_done = False
 
 test_prio["HandshakeAndKeepalive"] = -1