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/src/python/oftest/controller.py b/src/python/oftest/controller.py
index cc8b97c..0769437 100644
--- a/src/python/oftest/controller.py
+++ b/src/python/oftest/controller.py
@@ -104,6 +104,7 @@
self.socs = []
self.connect_cv = Condition()
self.message_cv = Condition()
+ self.disconnect_cv = Condition()
# Counters
self.socket_errors = 0
@@ -218,8 +219,7 @@
% (hdr.version, OFP_VERSION))
print "Version %d does not match OFTest version %d" % \
(hdr.version, OFP_VERSION)
- self.active = False
- self.switch_socket = None
+ self.disconnect()
return
msg = of_message_parse(rawmsg)
@@ -359,25 +359,23 @@
self.logger.info("Waiting for switch connection")
self.socs = [self.listen_socket]
self.dbg_state = "running"
+
while self.active:
try:
sel_in, sel_out, sel_err = \
select.select(self.socs, [], self.socs, 1)
except:
print sys.exc_info()
- self.logger.error("Select error, exiting")
- self.active = False
- break
+ self.logger.error("Select error, disconnecting")
+ self.disconnect()
for s in sel_err:
- self.logger.error("Got socket error on: " + str(s))
- self.active = False
- break
+ self.logger.error("Got socket error on: " + str(s) + ", disconnecting")
+ self.disconnect()
for s in sel_in:
if self._socket_ready_handle(s) == -1:
- self.active = False
- break
+ self.disconnect()
# End of main loop
self.dbg_state = "closing"
@@ -396,6 +394,30 @@
timed_wait(self.connect_cv, lambda: self.switch_socket, timeout=timeout)
return self.switch_socket is not None
+ def disconnect(self, timeout=-1):
+ """
+ If connected to a switch, disconnect.
+ """
+ if self.switch_socket:
+ self.socs.remove(self.switch_socket)
+ self.switch_socket.close()
+ self.switch_socket = None
+ self.switch_addr = None
+ with self.disconnect_cv:
+ self.disconnect_cv.notifyAll()
+
+ def wait_disconnected(self, timeout=-1):
+ """
+ @param timeout Block for up to timeout seconds. Pass -1 for the default.
+ @return Boolean, True if disconnected
+ """
+
+ with self.disconnect_cv:
+ timed_wait(self.disconnect_cv,
+ lambda: True if not self.switch_socket else None,
+ timeout=timeout)
+ return self.switch_socket is None
+
def kill(self):
"""
Force the controller thread to quit
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