Remove serial failover tests, which only maintain one controller at any point
in time.
Instead, cxn.py-based failover tests can maintain multiple controllers at the
same time.
diff --git a/tests/cxn.py b/tests/cxn.py
index e9059cc..db377be 100644
--- a/tests/cxn.py
+++ b/tests/cxn.py
@@ -100,32 +100,59 @@
         self.assertTrue(self.controllers[0].wait_disconnected(timeout=10),
                         "Not notified of controller disconnect")
 
-class HandshakeAndKeepalive(BaseHandshake):
+class CompleteHandshake(BaseHandshake):
     """
-    Complete handshake and respond to echo request, but otherwise do nothing.
-    Good for manual testing.
+    Set up multiple controllers and complete handshake, but otherwise do nothing.
     """
 
     priority = -1
 
-    def runTest(self):
-        self.num_controllers = test_param_get('num_controllers', default=1)
-        self.controller_timeout = test_param_get('controller_timeout',
-                                                 default=-1)
-        self.hello_timeout = test_param_get('hello_timeout',
-                                            default=5)
-        self.features_req_timeout = test_param_get('features_req_timeout',
-                                                   default=5)
+    def buildControllerList(self):                                             
+        # controller_list is a list of IP:port tuples
+        con_list = test_param_get('controller_list')
+        if con_list is not None:
+            self.controller_list = []
+            for controller in con_list:
+                ip,portstr = controller.split(':')
+                try:
+                    port = int(portstr)
+                except:
+                    self.assertTrue(0, "failure converting port " +
+                                    portstr + " to integer")
+                self.controller_list.append( (ip, int(port)) )
+        else:
+            self.controller_list = [(config["controller_host"],
+                                     config["controller_port"])]
 
-        for i in range(self.num_controllers):
-            self.controllerSetup(config["controller_host"],
-                                 config["controller_port"]+i)
-        for i in range(self.num_controllers):
+    def __init__(self, keep_alive=True, cxn_cycles=5,
+                 controller_timeout=-1, hello_timeout=5, 
+                 features_req_timeout=5, disconnected_timeout=3):
+        BaseHandshake.__init__(self)
+        self.buildControllerList()
+        self.keep_alive = keep_alive
+        self.cxn_cycles = test_param_get('cxn_cycles') \
+            or cxn_cycles
+        self.controller_timeout = test_param_get('controller_timeout') \
+            or controller_timeout
+        self.hello_timeout = test_param_get('hello_timeout') \
+            or hello_timeout
+        self.features_req_timeout = test_param_get('features_req_timeout') \
+            or features_req_timeout
+        self.disconnected_timeout = test_param_get('disconnected_timeout') \
+            or disconnected_timeout
+
+    def runTest(self):
+        for conspec in self.controller_list:
+            self.controllerSetup(conspec[0], conspec[1])
+        for i in range(len(self.controller_list)):
             self.controllers[i].cstate = 0
-            self.controllers[i].keep_alive = True
+            self.controllers[i].keep_alive = self.keep_alive
         tick = 0.1  # time period in seconds at which controllers are handled
 
+        disconnected_count = 0
+        cycle = 0
         while True:
+            states = []
             for con in self.controllers:
                 condesc = con.host + ":" + str(con.port) + ": "
                 logging.debug("Checking " + condesc)
@@ -168,6 +195,7 @@
                                          str(con.switch_addr))
                             con.cstate = 4
                             con.count = 0
+                            cycle = cycle + 1
                         else:
                             con.count = con.count + 1
                             # fall back to previous state on timeout
@@ -191,5 +219,56 @@
                             con.cstate = 0
                 else:
                     con.cstate = 0
+            
+                states.append(con.cstate)
 
+            logging.debug("Cycle " + str(cycle) +
+                          ", states " + str(states) +
+                          ", disconnected_count " + str(disconnected_count))
+            if 4 in states:
+                disconnected_count = 0
+            else:
+                disconnected_count = disconnected_count + 1
+            if cycle != 0:
+                self.assertTrue(disconnected_count < self.disconnected_timeout/tick,
+                                "Timeout expired connecting to controller")
+            else:
+               # on first cycle, allow more time for initial connect
+               self.assertTrue(disconnected_count < 2*self.disconnected_timeout/tick,
+                               "Timeout expired connecting to controller on init")
+
+            if cycle > self.cxn_cycles:
+               break
             time.sleep(tick)
+
+class HandshakeAndKeepalive(CompleteHandshake):
+    """
+    Complete handshake and respond to echo request, but otherwise do nothing.
+    Good for manual testing.
+    """
+
+    priority = -1
+
+    def __init__(self):
+       CompleteHandshake.__init__(self, keep_alive=True)
+
+class HandshakeNoEcho(CompleteHandshake):
+    """
+    Complete handshake, but otherwise do nothing, and do not respond to echo.
+    """
+
+    priority = -1
+
+    def __init__(self):
+       CompleteHandshake.__init__(self, keep_alive=False)
+
+class HandshakeAndDrop(CompleteHandshake):
+    """
+    Complete handshake, but otherwise do nothing, and drop connection after a while.
+    """
+
+    priority = -1
+
+    def __init__(self):
+       CompleteHandshake.__init__(self, keep_alive=True, controller_timeout=10)
+
diff --git a/tests/serial_failover.py b/tests/serial_failover.py
deleted file mode 100644
index ffbbf0a..0000000
--- a/tests/serial_failover.py
+++ /dev/null
@@ -1,179 +0,0 @@
-"""
-Serial failover test cases
-
-"""
-
-import time
-import sys
-import logging
-
-import unittest
-import random
-
-from oftest import config
-import oftest.controller as controller
-import oftest.cstruct as ofp
-import oftest.message as message
-import oftest.dataplane as dataplane
-import oftest.action as action
-
-from oftest.testutils import *
-
-class SerialFailover(unittest.TestCase):
-    """
-    Opens a connection that the switch should use as its only controller,
-    as specified by controller_host and controller_port.
-    Then cause the connection to fail [fail method should be configurable].
-    Ultimately, the switch should connect to the next controller port,
-    as specified by 
-    --test-params="controller_list=['ip2:port2','ip3:port3']".
-    Multiple test params are specified by
-    --test-params="param1=val1;param2=val2"
-    """
-
-    priority = -1
-
-    # populated by buildControllerList()
-    controller_list = []
-    controller_idx = 0
-    # populated by setUp()
-    test_timeout = 0
-    test_iterations = 0
-
-    def controllerSetup(self, host, port):
-        self.controller = controller.Controller(host=host,port=port)
-
-        # clean_shutdown should be set to False to force quit app
-        self.clean_shutdown = True
-
-        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")
-        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")
-        logging.info("Connected " + 
-                                    str(self.controller.switch_addr))
-
-        # send echo request and wait for reply
-        request = message.echo_request()
-        response, pkt = self.controller.transact(request)
-        self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
-                         'response is not echo_reply')
-        self.assertEqual(request.header.xid, response.header.xid,
-                         'response xid != request xid')
-        self.assertEqual(len(response.data), 0, 'response data non-empty')
-
-    def connectionKill(self, kill_method):
-        if kill_method == 'controller_shutdown':
-            logging.info("Shutting down controller")
-            self.controller.shutdown()
-        elif kill_method == 'no_echo':
-            logging.info("Disabling controller keep alive")
-            self.controller.keep_alive = False
-
-            # wait for controller to die
-            count = 0
-            while self.controller.active and count < self.test_timeout:
-                time.sleep(1)
-                count = count + 1
-        else:
-            self.assertTrue(False, "Unknown controller kill method")
-
-    def buildControllerList(self):
-        # controller_list is list of ip/port tuples
-        partial_list = test_param_get('controller_list')
-        logging.debug("ctrl list: " + str(partial_list))
-        self.controller_list = [(config["controller_host"],
-                                 config["controller_port"])]
-        if partial_list is not None:
-            for controller in partial_list:
-                ip,portstr = controller.split(':')
-                try:
-                    port = int(portstr)
-                except:
-                    self.assertTrue(0, "failure converting port " + 
-                                    portstr + " to integer")
-                self.controller_list.append( (ip, int(port)) )
-
-    def getController(self):
-        return self.controller_list[self.controller_idx]
-
-    def getNextController(self):
-        self.controller_idx = (self.controller_idx + 1) \
-            % len(self.controller_list)
-        return self.controller_list[self.controller_idx]
-
-    def setUp(self):
-        logging.info("** START TEST CASE " + str(self))
-
-        self.test_timeout = test_param_get('failover_timeout') or 60
-        self.test_iterations = test_param_get('failover_iterations') or 4
-
-        self.buildControllerList()
-        self.controller_idx = 0
-        controller = self.getController()
-        self.controllerSetup(controller[0], controller[1])
-
-    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.
-        """
-        logging.info("** Setup " + str(self) + 
-                                    " inheriting from " + str(parent))
-        self.controller = parent.controller
-        
-    def tearDown(self):
-        logging.info("** END TEST CASE " + str(self))
-        self.controller.shutdown()
-        if self.clean_shutdown:
-            self.controller.join()
-
-    def doFailover(self, killmethod):
-        logging.info("Starting serial failover test")
-        self.assertTrue(self.controller.switch_socket is not None,
-                        str(self) + 'No connection to switch')
-        # kill controller connection
-        self.connectionKill(killmethod)
-        # establish new controller connection
-        controller = self.getNextController()
-        logging.debug("** Next controller (%u/%u)%s:%u" % 
-                                     (self.controller_idx,
-                                      len(self.controller_list),
-                                      controller[0],
-                                      controller[1]))
-        self.controllerSetup(controller[0], controller[1])
-
-    def runTest(self):
-        for i in range(0,self.test_iterations):
-            self.doFailover('controller_shutdown')
-
-    def assertTrue(self, cond, msg):
-        if not cond:
-            logging.error("** FAILED ASSERTION: " + msg)
-        unittest.TestCase.assertTrue(self, cond, msg)
-
-
-class SerialFailoverNoEcho(SerialFailover):
-    priority = -1
-
-    def runTest(self):
-        for i in range(0,self.test_iterations):
-            self.doFailover('no_echo')