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