Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 1 | """ |
| 2 | Serial failover test cases |
| 3 | |
| 4 | """ |
| 5 | |
| 6 | import time |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 7 | import sys |
| 8 | import logging |
| 9 | |
| 10 | import unittest |
| 11 | import random |
| 12 | |
Rich Lane | 477f481 | 2012-10-04 22:49:00 -0700 | [diff] [blame] | 13 | from oftest import config |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 14 | import oftest.controller as controller |
| 15 | import oftest.cstruct as ofp |
| 16 | import oftest.message as message |
| 17 | import oftest.dataplane as dataplane |
| 18 | import oftest.action as action |
| 19 | |
Rich Lane | da3b5ad | 2012-10-03 09:05:32 -0700 | [diff] [blame] | 20 | from oftest.testutils import * |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 21 | |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 22 | class SerialFailover(unittest.TestCase): |
| 23 | """ |
| 24 | Opens a connection that the switch should use as its only controller, |
| 25 | as specified by controller_host and controller_port. |
| 26 | Then cause the connection to fail [fail method should be configurable]. |
| 27 | Ultimately, the switch should connect to the next controller port, |
| 28 | as specified by |
| 29 | --test-params="controller_list=['ip2:port2','ip3:port3']". |
| 30 | Multiple test params are specified by |
| 31 | --test-params="param1=val1;param2=val2" |
| 32 | """ |
| 33 | |
Rich Lane | d1d9c28 | 2012-10-04 22:07:10 -0700 | [diff] [blame] | 34 | priority = -1 |
| 35 | |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 36 | # populated by buildControllerList() |
| 37 | controller_list = [] |
| 38 | controller_idx = 0 |
| 39 | # populated by setUp() |
| 40 | test_timeout = 0 |
| 41 | test_iterations = 0 |
| 42 | |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 43 | def controllerSetup(self, host, port): |
| 44 | self.controller = controller.Controller(host=host,port=port) |
| 45 | |
| 46 | # clean_shutdown should be set to False to force quit app |
| 47 | self.clean_shutdown = True |
| 48 | |
| 49 | self.controller.start() |
| 50 | #@todo Add an option to wait for a pkt transaction to ensure version |
| 51 | # compatibilty? |
Dan Talayco | 907efa2 | 2012-09-19 11:30:06 -0700 | [diff] [blame] | 52 | self.controller.connect(timeout=10) |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 53 | self.assertTrue(self.controller.active, |
| 54 | "Controller startup failed, not active") |
| 55 | self.assertTrue(self.controller.switch_addr is not None, |
| 56 | "Controller startup failed, no switch addr") |
Dan Talayco | 907efa2 | 2012-09-19 11:30:06 -0700 | [diff] [blame] | 57 | request = message.features_request() |
Dan Talayco | c689a79 | 2012-09-28 14:22:53 -0700 | [diff] [blame] | 58 | reply, pkt = self.controller.transact(request, timeout=20) |
Dan Talayco | 907efa2 | 2012-09-19 11:30:06 -0700 | [diff] [blame] | 59 | self.assertTrue(reply is not None, |
| 60 | "Did not complete features_request for handshake") |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 61 | logging.info("Connected " + |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 62 | str(self.controller.switch_addr)) |
| 63 | |
| 64 | # send echo request and wait for reply |
| 65 | request = message.echo_request() |
| 66 | response, pkt = self.controller.transact(request) |
| 67 | self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY, |
| 68 | 'response is not echo_reply') |
| 69 | self.assertEqual(request.header.xid, response.header.xid, |
| 70 | 'response xid != request xid') |
| 71 | self.assertEqual(len(response.data), 0, 'response data non-empty') |
| 72 | |
| 73 | def connectionKill(self, kill_method): |
| 74 | if kill_method == 'controller_shutdown': |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 75 | logging.info("Shutting down controller") |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 76 | self.controller.shutdown() |
| 77 | elif kill_method == 'no_echo': |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 78 | logging.info("Disabling controller keep alive") |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 79 | self.controller.keep_alive = False |
| 80 | |
| 81 | # wait for controller to die |
| 82 | count = 0 |
| 83 | while self.controller.active and count < self.test_timeout: |
| 84 | time.sleep(1) |
| 85 | count = count + 1 |
| 86 | else: |
| 87 | self.assertTrue(false, "Unknown controller kill method") |
| 88 | |
| 89 | def buildControllerList(self): |
| 90 | # controller_list is list of ip/port tuples |
Rich Lane | 2014f9b | 2012-10-05 15:29:40 -0700 | [diff] [blame^] | 91 | partial_list = test_param_get('controller_list') |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 92 | logging.debug("ctrl list: " + str(partial_list)) |
Rich Lane | 477f481 | 2012-10-04 22:49:00 -0700 | [diff] [blame] | 93 | self.controller_list = [(config["controller_host"], |
| 94 | config["controller_port"])] |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 95 | if partial_list is not None: |
| 96 | for controller in partial_list: |
| 97 | ip,portstr = controller.split(':') |
| 98 | try: |
| 99 | port = int(portstr) |
| 100 | except: |
| 101 | self.assertTrue(0, "failure converting port " + |
| 102 | portstr + " to integer") |
| 103 | self.controller_list.append( (ip, int(port)) ) |
| 104 | |
| 105 | def getController(self): |
| 106 | return self.controller_list[self.controller_idx] |
| 107 | |
| 108 | def getNextController(self): |
| 109 | self.controller_idx = (self.controller_idx + 1) \ |
| 110 | % len(self.controller_list) |
| 111 | return self.controller_list[self.controller_idx] |
| 112 | |
| 113 | def setUp(self): |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 114 | logging.info("** START TEST CASE " + str(self)) |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 115 | |
Rich Lane | 2014f9b | 2012-10-05 15:29:40 -0700 | [diff] [blame^] | 116 | self.test_timeout = test_param_get('failover_timeout') or 60 |
| 117 | self.test_iterations = test_param_get('failover_iterations') or 4 |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 118 | |
| 119 | self.buildControllerList() |
| 120 | self.controller_idx = 0 |
| 121 | controller = self.getController() |
| 122 | self.controllerSetup(controller[0], controller[1]) |
| 123 | |
| 124 | def inheritSetup(self, parent): |
| 125 | """ |
| 126 | Inherit the setup of a parent |
| 127 | |
| 128 | This allows running at test from within another test. Do the |
| 129 | following: |
| 130 | |
| 131 | sub_test = SomeTestClass() # Create an instance of the test class |
| 132 | sub_test.inheritSetup(self) # Inherit setup of parent |
| 133 | sub_test.runTest() # Run the test |
| 134 | |
| 135 | Normally, only the parent's setUp and tearDown are called and |
| 136 | the state after the sub_test is run must be taken into account |
| 137 | by subsequent operations. |
| 138 | """ |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 139 | logging.info("** Setup " + str(self) + |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 140 | " inheriting from " + str(parent)) |
| 141 | self.controller = parent.controller |
| 142 | |
| 143 | def tearDown(self): |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 144 | logging.info("** END TEST CASE " + str(self)) |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 145 | self.controller.shutdown() |
| 146 | if self.clean_shutdown: |
| 147 | self.controller.join() |
| 148 | |
| 149 | def doFailover(self, killmethod): |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 150 | logging.info("Starting serial failover test") |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 151 | self.assertTrue(self.controller.switch_socket is not None, |
| 152 | str(self) + 'No connection to switch') |
| 153 | # kill controller connection |
| 154 | self.connectionKill(killmethod) |
| 155 | # establish new controller connection |
| 156 | controller = self.getNextController() |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 157 | logging.debug("** Next controller (%u/%u)%s:%u" % |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 158 | (self.controller_idx, |
| 159 | len(self.controller_list), |
| 160 | controller[0], |
| 161 | controller[1])) |
| 162 | self.controllerSetup(controller[0], controller[1]) |
| 163 | |
| 164 | def runTest(self): |
| 165 | for i in range(0,self.test_iterations): |
| 166 | self.doFailover('controller_shutdown') |
| 167 | |
| 168 | def assertTrue(self, cond, msg): |
| 169 | if not cond: |
Rich Lane | 9a00381 | 2012-10-04 17:17:59 -0700 | [diff] [blame] | 170 | logging.error("** FAILED ASSERTION: " + msg) |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 171 | unittest.TestCase.assertTrue(self, cond, msg) |
| 172 | |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 173 | |
| 174 | class SerialFailoverNoEcho(SerialFailover): |
Rich Lane | d1d9c28 | 2012-10-04 22:07:10 -0700 | [diff] [blame] | 175 | priority = -1 |
Ken Chiang | 20a3da5 | 2012-07-11 09:13:57 -0700 | [diff] [blame] | 176 | |
| 177 | def runTest(self): |
| 178 | for i in range(0,self.test_iterations): |
| 179 | self.doFailover('no_echo') |