Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 1 | """ |
| 2 | Basic protocol and dataplane test cases |
| 3 | |
| 4 | It is recommended that these definitions be kept in their own |
| 5 | namespace as different groups of tests will likely define |
| 6 | similar identifiers. |
| 7 | """ |
| 8 | |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 9 | import logging |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 10 | |
| 11 | from oftest import config |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 12 | import ofp |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 13 | import oftest.base_tests as base_tests |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 14 | import oftest.oft12.testutils as testutils |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 15 | |
| 16 | class Echo(base_tests.SimpleProtocol): |
| 17 | """ |
| 18 | Test echo response with no data |
| 19 | """ |
| 20 | def runTest(self): |
| 21 | testutils.do_echo_request_reply_test(self, self.controller) |
| 22 | |
| 23 | class EchoWithData(base_tests.SimpleProtocol): |
| 24 | """ |
| 25 | Test echo response with short string data |
| 26 | """ |
| 27 | def runTest(self): |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 28 | request = ofp.message.echo_request() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 29 | request.data = 'OpenFlow Will Rule The World' |
| 30 | response, _ = self.controller.transact(request) |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 31 | self.assertEqual(response.type, ofp.OFPT_ECHO_REPLY, |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 32 | 'response is not echo_reply') |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 33 | self.assertEqual(request.xid, response.xid, |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 34 | 'response xid != request xid') |
| 35 | self.assertEqual(request.data, response.data, |
| 36 | 'response data does not match request') |
| 37 | |
| 38 | class FeaturesRequest(base_tests.SimpleProtocol): |
| 39 | """ |
| 40 | Test features_request to make sure we get a response |
| 41 | |
| 42 | Does NOT test the contents; just that we get a response |
| 43 | """ |
| 44 | def runTest(self): |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 45 | request = ofp.message.features_request() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 46 | response,_ = self.controller.transact(request) |
| 47 | self.assertTrue(response,"Got no features_reply to features_request") |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 48 | self.assertEqual(response.type, ofp.OFPT_FEATURES_REPLY, |
| 49 | 'response is not features_reply') |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 50 | |
| 51 | class PacketIn(base_tests.SimpleDataPlane): |
| 52 | """ |
| 53 | Test packet in function |
| 54 | |
| 55 | Send a packet to each dataplane port and verify that a packet |
| 56 | in message is received from the controller for each |
| 57 | """ |
| 58 | def runTest(self): |
| 59 | # Construct packet to send to dataplane |
| 60 | # Send packet to dataplane, once to each port |
| 61 | # Poll controller with expect message type packet in |
| 62 | |
| 63 | rc = testutils.delete_all_flows(self.controller, logging) |
| 64 | self.assertEqual(rc, 0, "Failed to delete all flows") |
| 65 | |
| 66 | for of_port in config["port_map"].keys(): |
| 67 | logging.info("PKT IN test, port " + str(of_port)) |
| 68 | pkt = testutils.simple_tcp_packet() |
| 69 | self.dataplane.send(of_port, str(pkt)) |
| 70 | #@todo Check for unexpected messages? |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 71 | (response, _) = self.controller.poll(ofp.OFPT_PACKET_IN) |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 72 | |
| 73 | self.assertTrue(response is not None, |
| 74 | 'Packet in message not received on port ' + |
| 75 | str(of_port)) |
| 76 | if str(pkt) != response.data: |
| 77 | logging.debug("pkt len " + str(len(str(pkt))) + |
| 78 | ": " + str(pkt)) |
| 79 | logging.debug("resp len " + |
| 80 | str(len(str(response.data))) + |
| 81 | ": " + str(response.data)) |
| 82 | |
| 83 | self.assertEqual(str(pkt), response.data, |
| 84 | 'Response packet does not match send packet' + |
| 85 | ' for port ' + str(of_port)) |
| 86 | |
| 87 | class PacketOut(base_tests.SimpleDataPlane): |
| 88 | """ |
| 89 | Test packet out function |
| 90 | |
| 91 | Send packet out message to controller for each dataplane port and |
| 92 | verify the packet appears on the appropriate dataplane port |
| 93 | """ |
| 94 | def runTest(self): |
| 95 | # Construct packet to send to dataplane |
| 96 | # Send packet to dataplane |
| 97 | # Poll controller with expect message type packet in |
| 98 | |
| 99 | rc = testutils.delete_all_flows(self.controller, logging) |
| 100 | self.assertEqual(rc, 0, "Failed to delete all flows") |
| 101 | |
| 102 | # These will get put into function |
| 103 | outpkt = testutils.simple_tcp_packet() |
| 104 | of_ports = config["port_map"].keys() |
| 105 | of_ports.sort() |
| 106 | for dp_port in of_ports: |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 107 | msg = ofp.message.packet_out() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 108 | msg.in_port = ofp.OFPP_CONTROLLER |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 109 | msg.buffer_id = 0xffffffff |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 110 | msg.data = str(outpkt) |
Rich Lane | 6339349 | 2013-01-11 09:21:12 -0800 | [diff] [blame] | 111 | act = ofp.action.output() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 112 | act.port = dp_port |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 113 | msg.actions.append(act) |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 114 | |
| 115 | logging.info("PacketOut to: " + str(dp_port)) |
| 116 | rv = self.controller.message_send(msg) |
| 117 | self.assertTrue(rv == 0, "Error sending out message") |
| 118 | |
| 119 | (of_port, pkt, _) = self.dataplane.poll(timeout=1) |
| 120 | |
| 121 | self.assertTrue(pkt is not None, 'Packet not received') |
| 122 | logging.info("PacketOut: got pkt from " + str(of_port)) |
| 123 | if of_port is not None: |
| 124 | self.assertEqual(of_port, dp_port, "Unexpected receive port") |
| 125 | self.assertEqual(str(outpkt), str(pkt), |
| 126 | 'Response packet does not match send packet') |
| 127 | |
| 128 | class FlowRemoveAll(base_tests.SimpleProtocol): |
| 129 | """ |
| 130 | Remove all flows; required for almost all tests |
| 131 | |
| 132 | Add a bunch of flows, remove them, and then make sure there are no flows left |
| 133 | This is an intentionally naive test to see if the baseline functionality works |
| 134 | and should be a precondition to any more complicated deletion test (e.g., |
| 135 | delete_strict vs. delete) |
| 136 | """ |
| 137 | def runTest(self): |
| 138 | logging.info("Running StatsGet") |
| 139 | logging.info("Inserting trial flow") |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 140 | request = ofp.message.flow_add() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 141 | request.buffer_id = 0xffffffff |
| 142 | for i in range(1,5): |
| 143 | request.priority = i*1000 |
| 144 | logging.debug("Adding flow %d" % i) |
| 145 | rv = self.controller.message_send(request) |
| 146 | self.assertTrue(rv != -1, "Failed to insert test flow %d" % i) |
| 147 | logging.info("Removing all flows") |
| 148 | testutils.delete_all_flows(self.controller, logging) |
| 149 | logging.info("Sending flow request") |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 150 | request = ofp.message.flow_stats_request() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 151 | request.out_port = ofp.OFPP_ANY |
| 152 | request.out_group = ofp.OFPG_ANY |
| 153 | request.table_id = 0xff |
| 154 | response, _ = self.controller.transact(request, timeout=2) |
| 155 | self.assertTrue(response is not None, "Did not get response") |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 156 | self.assertTrue(isinstance(response,ofp.message.flow_stats_reply),"Not a flow_stats_reply") |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 157 | self.assertEqual(len(response.entries),0) |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 158 | logging.debug(response.show()) |
| 159 | |
| 160 | |
| 161 | |
| 162 | class FlowStatsGet(base_tests.SimpleProtocol): |
| 163 | """ |
| 164 | Get stats |
| 165 | |
| 166 | Simply verify stats get transaction |
| 167 | """ |
| 168 | def runTest(self): |
| 169 | logging.info("Running StatsGet") |
| 170 | logging.info("Inserting trial flow") |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 171 | request = ofp.message.flow_add() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 172 | request.buffer_id = 0xffffffff |
| 173 | rv = self.controller.message_send(request) |
| 174 | self.assertTrue(rv != -1, "Failed to insert test flow") |
| 175 | |
| 176 | logging.info("Sending flow request") |
| 177 | response = testutils.flow_stats_get(self) |
| 178 | logging.debug(response.show()) |
| 179 | |
| 180 | class TableStatsGet(base_tests.SimpleProtocol): |
| 181 | """ |
| 182 | Get table stats |
| 183 | |
| 184 | Naively verify that we get a reply |
| 185 | do better sanity check of data in stats.TableStats test |
| 186 | """ |
| 187 | def runTest(self): |
| 188 | logging.info("Running TableStatsGet") |
| 189 | logging.info("Sending table stats request") |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 190 | request = ofp.message.table_stats_request() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 191 | response, _ = self.controller.transact(request, timeout=2) |
| 192 | self.assertTrue(response is not None, "Did not get response") |
| 193 | logging.debug(response.show()) |
| 194 | |
| 195 | class FlowMod(base_tests.SimpleProtocol): |
| 196 | """ |
| 197 | Insert a flow |
| 198 | |
| 199 | Simple verification of a flow mod transaction |
| 200 | """ |
| 201 | |
| 202 | def runTest(self): |
| 203 | logging.info("Running " + str(self)) |
Rich Lane | 4b5537f | 2013-05-07 15:23:26 -0700 | [diff] [blame^] | 204 | request = ofp.message.flow_add() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 205 | request.buffer_id = 0xffffffff |
| 206 | rv = self.controller.message_send(request) |
| 207 | self.assertTrue(rv != -1, "Error installing flow mod") |
| 208 | |
| 209 | class PortConfigMod(base_tests.SimpleProtocol): |
| 210 | """ |
| 211 | Modify a bit in port config and verify changed |
| 212 | |
| 213 | Get the switch configuration, modify the port configuration |
| 214 | and write it back; get the config again and verify changed. |
| 215 | Then set it back to the way it was. |
| 216 | """ |
| 217 | |
| 218 | def runTest(self): |
| 219 | logging.info("Running " + str(self)) |
| 220 | for of_port, _ in config["port_map"].items(): # Grab first port |
| 221 | break |
| 222 | |
| 223 | (_, port_config, _) = \ |
| 224 | testutils.port_config_get(self.controller, of_port, logging) |
| 225 | self.assertTrue(port_config is not None, "Did not get port config") |
| 226 | |
| 227 | logging.debug("No flood bit port " + str(of_port) + " is now " + |
| 228 | str(port_config & ofp.OFPPC_NO_PACKET_IN)) |
| 229 | |
| 230 | rv = testutils.port_config_set(self.controller, of_port, |
| 231 | port_config ^ ofp.OFPPC_NO_PACKET_IN, ofp.OFPPC_NO_PACKET_IN, |
| 232 | logging) |
| 233 | self.assertTrue(rv != -1, "Error sending port mod") |
| 234 | |
| 235 | # Verify change took place with same feature request |
| 236 | (_, port_config2, _) = \ |
| 237 | testutils.port_config_get(self.controller, of_port, logging) |
| 238 | logging.debug("No packet_in bit port " + str(of_port) + " is now " + |
| 239 | str(port_config2 & ofp.OFPPC_NO_PACKET_IN)) |
| 240 | self.assertTrue(port_config2 is not None, "Did not get port config2") |
| 241 | self.assertTrue(port_config2 & ofp.OFPPC_NO_PACKET_IN != |
| 242 | port_config & ofp.OFPPC_NO_PACKET_IN, |
| 243 | "Bit change did not take") |
| 244 | # Set it back |
| 245 | rv = testutils.port_config_set(self.controller, of_port, port_config, |
| 246 | ofp.OFPPC_NO_PACKET_IN, logging) |
| 247 | self.assertTrue(rv != -1, "Error sending port mod") |
| 248 | |
| 249 | class TableModConfig(base_tests.SimpleProtocol): |
| 250 | """ Simple table modification |
| 251 | |
| 252 | Mostly to make sure the switch correctly responds to these messages. |
| 253 | More complicated tests in the multi-tables.py tests |
| 254 | """ |
| 255 | def runTest(self): |
| 256 | logging.info("Running " + str(self)) |
Rich Lane | 02eb6b0 | 2013-01-11 08:08:37 -0800 | [diff] [blame] | 257 | table_mod = ofp.message.table_mod() |
Rich Lane | 629393f | 2013-01-10 15:37:33 -0800 | [diff] [blame] | 258 | table_mod.table_id = 0 # first table should always exist |
| 259 | table_mod.config = ofp.OFPTC_TABLE_MISS_CONTROLLER |
| 260 | |
| 261 | rv = self.controller.message_send(table_mod) |
| 262 | self.assertTrue(rv != -1, "Error sending table_mod") |
| 263 | testutils.do_echo_request_reply_test(self, self.controller) |
| 264 | |
| 265 | |
| 266 | if __name__ == "__main__": |
| 267 | print "Please run through oft script: ./oft --test_spec=basic" |