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