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