blob: 5a2e26f06be85fda32cf5de4c99dde171a5adcfe [file] [log] [blame]
Dan Talaycodba244e2010-02-15 14:08:53 -08001"""
Dan Talayco79f36082010-03-11 16:53:53 -08002Basic protocol and dataplane test cases
Dan Talaycodba244e2010-02-15 14:08:53 -08003
Dan Talayco48370102010-03-03 15:17:33 -08004It is recommended that these definitions be kept in their own
5namespace as different groups of tests will likely define
6similar identifiers.
7
Dan Talaycodba244e2010-02-15 14:08:53 -08008Current Assumptions:
9
Dan Talayco41eae8b2010-03-10 13:57:06 -080010 The function test_set_init is called with a complete configuration
11dictionary prior to the invocation of any tests from this file.
12
Dan Talaycodba244e2010-02-15 14:08:53 -080013 The switch is actively attempting to contact the controller at the address
14indicated oin oft_config
15
16"""
17
Dan Talaycodba244e2010-02-15 14:08:53 -080018import time
Dan Talayco710438c2010-02-18 15:16:07 -080019import signal
Dan Talaycodba244e2010-02-15 14:08:53 -080020import sys
Dan Talayco48370102010-03-03 15:17:33 -080021import logging
Dan Talaycodba244e2010-02-15 14:08:53 -080022
Dan Talayco2c0dba32010-03-06 22:47:06 -080023import unittest
24
25import oftest.controller as controller
26import oftest.cstruct as ofp
27import oftest.message as message
28import oftest.dataplane as dataplane
29import oftest.action as action
30
Dan Talayco6ce963a2010-03-07 21:58:13 -080031from testutils import *
32
33#@var basic_port_map Local copy of the configuration map from OF port
34# numbers to OS interfaces
Dan Talayco48370102010-03-03 15:17:33 -080035basic_port_map = None
Dan Talayco6ce963a2010-03-07 21:58:13 -080036#@var basic_logger Local logger object
Dan Talayco48370102010-03-03 15:17:33 -080037basic_logger = None
Dan Talayco6ce963a2010-03-07 21:58:13 -080038#@var basic_config Local copy of global configuration data
Dan Talayco48370102010-03-03 15:17:33 -080039basic_config = None
40
Dan Talaycoc24aaae2010-07-08 14:05:24 -070041test_prio = {}
42
Dan Talayco48370102010-03-03 15:17:33 -080043def test_set_init(config):
44 """
45 Set up function for basic test classes
46
47 @param config The configuration dictionary; see oft
Dan Talayco48370102010-03-03 15:17:33 -080048 """
49
50 global basic_port_map
51 global basic_logger
52 global basic_config
53
54 basic_logger = logging.getLogger("basic")
55 basic_logger.info("Initializing test set")
56 basic_port_map = config["port_map"]
57 basic_config = config
Dan Talayco48370102010-03-03 15:17:33 -080058
Dan Talayco6ce963a2010-03-07 21:58:13 -080059class SimpleProtocol(unittest.TestCase):
Dan Talaycodba244e2010-02-15 14:08:53 -080060 """
61 Root class for setting up the controller
62 """
63
Dan Talaycoef701f42010-05-07 09:22:35 -070064 def sig_handler(self, v1, v2):
Dan Talayco48370102010-03-03 15:17:33 -080065 basic_logger.critical("Received interrupt signal; exiting")
Dan Talayco710438c2010-02-18 15:16:07 -080066 print "Received interrupt signal; exiting"
Dan Talayco2c0dba32010-03-06 22:47:06 -080067 self.clean_shutdown = False
68 self.tearDown()
Dan Talayco710438c2010-02-18 15:16:07 -080069 sys.exit(1)
70
Dan Talaycodba244e2010-02-15 14:08:53 -080071 def setUp(self):
Dan Talayco551befa2010-07-15 17:05:32 -070072 self.logger = basic_logger
Dan Talayco710438c2010-02-18 15:16:07 -080073 signal.signal(signal.SIGINT, self.sig_handler)
Dan Talayco9f47f4d2010-06-03 13:54:37 -070074 basic_logger.info("** START TEST CASE " + str(self))
Dan Talayco2c0dba32010-03-06 22:47:06 -080075 self.controller = controller.Controller(
76 host=basic_config["controller_host"],
77 port=basic_config["controller_port"])
Dan Talaycof8f41402010-03-12 22:17:39 -080078 # clean_shutdown should be set to False to force quit app
Dan Talayco2c0dba32010-03-06 22:47:06 -080079 self.clean_shutdown = True
Dan Talayco710438c2010-02-18 15:16:07 -080080 self.controller.start()
Dan Talaycoef701f42010-05-07 09:22:35 -070081 #@todo Add an option to wait for a pkt transaction to ensure version
82 # compatibilty?
Dan Talayco710438c2010-02-18 15:16:07 -080083 self.controller.connect(timeout=20)
Dan Talaycoef701f42010-05-07 09:22:35 -070084 if not self.controller.active:
85 print "Controller startup failed; exiting"
86 sys.exit(1)
Dan Talayco48370102010-03-03 15:17:33 -080087 basic_logger.info("Connected " + str(self.controller.switch_addr))
Dan Talaycodba244e2010-02-15 14:08:53 -080088
89 def tearDown(self):
Dan Talayco9f47f4d2010-06-03 13:54:37 -070090 basic_logger.info("** END TEST CASE " + str(self))
Dan Talaycodba244e2010-02-15 14:08:53 -080091 self.controller.shutdown()
Dan Talayco2c0dba32010-03-06 22:47:06 -080092 #@todo Review if join should be done on clean_shutdown
Dan Talaycof8f41402010-03-12 22:17:39 -080093 if self.clean_shutdown:
94 self.controller.join()
Dan Talaycodba244e2010-02-15 14:08:53 -080095
96 def runTest(self):
Dan Talayco710438c2010-02-18 15:16:07 -080097 # Just a simple sanity check as illustration
Dan Talayco48370102010-03-03 15:17:33 -080098 basic_logger.info("Running simple proto test")
Dan Talayco710438c2010-02-18 15:16:07 -080099 self.assertTrue(self.controller.switch_socket is not None,
Dan Talaycodba244e2010-02-15 14:08:53 -0800100 str(self) + 'No connection to switch')
101
Dan Talayco9f47f4d2010-06-03 13:54:37 -0700102 def assertTrue(self, cond, msg):
103 if not cond:
104 basic_logger.error("** FAILED ASSERTION: " + msg)
105 unittest.TestCase.assertTrue(self, cond, msg)
106
Dan Talaycoc24aaae2010-07-08 14:05:24 -0700107test_prio["SimpleProtocol"] = 1
108
Dan Talayco6ce963a2010-03-07 21:58:13 -0800109class SimpleDataPlane(SimpleProtocol):
Dan Talaycodba244e2010-02-15 14:08:53 -0800110 """
111 Root class that sets up the controller and dataplane
112 """
113 def setUp(self):
Dan Talayco6ce963a2010-03-07 21:58:13 -0800114 SimpleProtocol.setUp(self)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800115 self.dataplane = dataplane.DataPlane()
Dan Talayco48370102010-03-03 15:17:33 -0800116 for of_port, ifname in basic_port_map.items():
Dan Talaycodba244e2010-02-15 14:08:53 -0800117 self.dataplane.port_add(ifname, of_port)
118
119 def tearDown(self):
Dan Talayco48370102010-03-03 15:17:33 -0800120 basic_logger.info("Teardown for simple dataplane test")
Dan Talayco6ce963a2010-03-07 21:58:13 -0800121 SimpleProtocol.tearDown(self)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800122 self.dataplane.kill(join_threads=self.clean_shutdown)
Dan Talayco48370102010-03-03 15:17:33 -0800123 basic_logger.info("Teardown done")
Dan Talaycodba244e2010-02-15 14:08:53 -0800124
125 def runTest(self):
Dan Talayco710438c2010-02-18 15:16:07 -0800126 self.assertTrue(self.controller.switch_socket is not None,
Dan Talaycodba244e2010-02-15 14:08:53 -0800127 str(self) + 'No connection to switch')
128 # self.dataplane.show()
129 # Would like an assert that checks the data plane
130
Dan Talayco551befa2010-07-15 17:05:32 -0700131class DataPlaneOnly(unittest.TestCase):
132 """
133 Root class that sets up only the dataplane
134 """
135
136 def sig_handler(self, v1, v2):
137 basic_logger.critical("Received interrupt signal; exiting")
138 print "Received interrupt signal; exiting"
139 self.clean_shutdown = False
140 self.tearDown()
141 sys.exit(1)
142
143 def setUp(self):
144 self.clean_shutdown = False
145 self.logger = basic_logger
146 signal.signal(signal.SIGINT, self.sig_handler)
147 basic_logger.info("** START DataPlaneOnly CASE " + str(self))
148 self.dataplane = dataplane.DataPlane()
149 for of_port, ifname in basic_port_map.items():
150 self.dataplane.port_add(ifname, of_port)
151
152 def tearDown(self):
153 basic_logger.info("Teardown for simple dataplane test")
154 self.dataplane.kill(join_threads=self.clean_shutdown)
155 basic_logger.info("Teardown done")
156
157 def runTest(self):
158 self.dataplane.show()
159 # Would like an assert that checks the data plane
160
Dan Talayco6ce963a2010-03-07 21:58:13 -0800161class Echo(SimpleProtocol):
Dan Talaycodba244e2010-02-15 14:08:53 -0800162 """
163 Test echo response with no data
164 """
165 def runTest(self):
Dan Talayco2c0dba32010-03-06 22:47:06 -0800166 request = message.echo_request()
Dan Talaycoe226eb12010-02-18 23:06:30 -0800167 response, pkt = self.controller.transact(request)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800168 self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
Dan Talaycoa92e75b2010-02-16 20:53:56 -0800169 'response is not echo_reply')
Dan Talaycodba244e2010-02-15 14:08:53 -0800170 self.assertEqual(request.header.xid, response.header.xid,
171 'response xid != request xid')
172 self.assertEqual(len(response.data), 0, 'response data non-empty')
173
Dan Talayco6ce963a2010-03-07 21:58:13 -0800174class EchoWithData(SimpleProtocol):
Dan Talaycodba244e2010-02-15 14:08:53 -0800175 """
176 Test echo response with short string data
177 """
178 def runTest(self):
Dan Talayco2c0dba32010-03-06 22:47:06 -0800179 request = message.echo_request()
Dan Talaycodba244e2010-02-15 14:08:53 -0800180 request.data = 'OpenFlow Will Rule The World'
Dan Talaycoe226eb12010-02-18 23:06:30 -0800181 response, pkt = self.controller.transact(request)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800182 self.assertEqual(response.header.type, ofp.OFPT_ECHO_REPLY,
Dan Talaycoa92e75b2010-02-16 20:53:56 -0800183 'response is not echo_reply')
Dan Talaycodba244e2010-02-15 14:08:53 -0800184 self.assertEqual(request.header.xid, response.header.xid,
185 'response xid != request xid')
186 self.assertEqual(request.data, response.data,
187 'response data does not match request')
188
Dan Talayco6ce963a2010-03-07 21:58:13 -0800189class PacketIn(SimpleDataPlane):
Dan Talaycodba244e2010-02-15 14:08:53 -0800190 """
191 Test packet in function
Dan Talayco6ce963a2010-03-07 21:58:13 -0800192
193 Send a packet to each dataplane port and verify that a packet
194 in message is received from the controller for each
Dan Talaycodba244e2010-02-15 14:08:53 -0800195 """
196 def runTest(self):
197 # Construct packet to send to dataplane
Dan Talaycoe226eb12010-02-18 23:06:30 -0800198 # Send packet to dataplane, once to each port
Dan Talaycodba244e2010-02-15 14:08:53 -0800199 # Poll controller with expect message type packet in
Dan Talaycoe226eb12010-02-18 23:06:30 -0800200
Dan Talayco6ce963a2010-03-07 21:58:13 -0800201 rc = delete_all_flows(self.controller, basic_logger)
202 self.assertEqual(rc, 0, "Failed to delete all flows")
203
Dan Talayco48370102010-03-03 15:17:33 -0800204 for of_port in basic_port_map.keys():
205 basic_logger.info("PKT IN test, port " + str(of_port))
Dan Talayco41eae8b2010-03-10 13:57:06 -0800206 pkt = simple_tcp_packet()
Dan Talaycodba244e2010-02-15 14:08:53 -0800207 self.dataplane.send(of_port, str(pkt))
Dan Talaycoe226eb12010-02-18 23:06:30 -0800208 #@todo Check for unexpected messages?
Dan Talayco2c0dba32010-03-06 22:47:06 -0800209 (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
Dan Talaycodba244e2010-02-15 14:08:53 -0800210
Dan Talaycoe226eb12010-02-18 23:06:30 -0800211 self.assertTrue(response is not None,
Dan Talayco6ce963a2010-03-07 21:58:13 -0800212 'Packet in message not received on port ' +
213 str(of_port))
Dan Talayco48370102010-03-03 15:17:33 -0800214 if str(pkt) != response.data:
Dan Talaycoef701f42010-05-07 09:22:35 -0700215 basic_logger.debug("pkt len " + str(len(str(pkt))) +
216 ": " + str(pkt))
217 basic_logger.debug("resp len " +
218 str(len(str(response.data))) +
219 ": " + str(response.data))
Dan Talayco48370102010-03-03 15:17:33 -0800220
221 self.assertEqual(str(pkt), response.data,
Dan Talayco6ce963a2010-03-07 21:58:13 -0800222 'Response packet does not match send packet' +
223 ' for port ' + str(of_port))
Dan Talaycodba244e2010-02-15 14:08:53 -0800224
Dan Talayco6ce963a2010-03-07 21:58:13 -0800225class PacketOut(SimpleDataPlane):
Dan Talaycodba244e2010-02-15 14:08:53 -0800226 """
227 Test packet out function
Dan Talayco6ce963a2010-03-07 21:58:13 -0800228
229 Send packet out message to controller for each dataplane port and
230 verify the packet appears on the appropriate dataplane port
Dan Talaycodba244e2010-02-15 14:08:53 -0800231 """
232 def runTest(self):
233 # Construct packet to send to dataplane
234 # Send packet to dataplane
235 # Poll controller with expect message type packet in
Dan Talayco41eae8b2010-03-10 13:57:06 -0800236
237 rc = delete_all_flows(self.controller, basic_logger)
238 self.assertEqual(rc, 0, "Failed to delete all flows")
Dan Talaycodba244e2010-02-15 14:08:53 -0800239
240 # These will get put into function
Dan Talayco41eae8b2010-03-10 13:57:06 -0800241 outpkt = simple_tcp_packet()
Dan Talayco48370102010-03-03 15:17:33 -0800242 of_ports = basic_port_map.keys()
243 of_ports.sort()
244 for dp_port in of_ports:
Dan Talayco2c0dba32010-03-06 22:47:06 -0800245 msg = message.packet_out()
Dan Talayco48370102010-03-03 15:17:33 -0800246 msg.data = str(outpkt)
Dan Talayco2c0dba32010-03-06 22:47:06 -0800247 act = action.action_output()
Dan Talayco48370102010-03-03 15:17:33 -0800248 act.port = dp_port
249 self.assertTrue(msg.actions.add(act), 'Could not add action to msg')
Dan Talaycodba244e2010-02-15 14:08:53 -0800250
Dan Talayco48370102010-03-03 15:17:33 -0800251 basic_logger.info("PacketOut to: " + str(dp_port))
252 rv = self.controller.message_send(msg)
253 self.assertTrue(rv == 0, "Error sending out message")
Dan Talaycodba244e2010-02-15 14:08:53 -0800254
Dan Talayco48370102010-03-03 15:17:33 -0800255 (of_port, pkt, pkt_time) = self.dataplane.poll(timeout=1)
Dan Talaycodba244e2010-02-15 14:08:53 -0800256
Dan Talayco48370102010-03-03 15:17:33 -0800257 self.assertTrue(pkt is not None, 'Packet not received')
258 basic_logger.info("PacketOut: got pkt from " + str(of_port))
259 if of_port is not None:
260 self.assertEqual(of_port, dp_port, "Unexpected receive port")
261 self.assertEqual(str(outpkt), str(pkt),
262 'Response packet does not match send packet')
Dan Talaycodba244e2010-02-15 14:08:53 -0800263
Dan Talayco6ce963a2010-03-07 21:58:13 -0800264class FlowStatsGet(SimpleProtocol):
265 """
266 Get stats
Dan Talayco2c0dba32010-03-06 22:47:06 -0800267
Dan Talayco6ce963a2010-03-07 21:58:13 -0800268 Simply verify stats get transaction
269 """
270 def runTest(self):
271 basic_logger.info("Running StatsGet")
Dan Talayco41eae8b2010-03-10 13:57:06 -0800272 basic_logger.info("Inserting trial flow")
273 request = message.flow_mod()
274 request.match.wildcards = ofp.OFPFW_ALL
275 request.buffer_id = 0xffffffff
276 rv = self.controller.message_send(request)
277 self.assertTrue(rv != -1, "Failed to insert test flow")
278
279 basic_logger.info("Sending flow request")
Dan Talayco6ce963a2010-03-07 21:58:13 -0800280 request = message.flow_stats_request()
281 request.out_port = ofp.OFPP_NONE
Dan Talayco41eae8b2010-03-10 13:57:06 -0800282 request.table_id = 0xff
283 request.match.wildcards = 0 # ofp.OFPFW_ALL
Dan Talayco6ce963a2010-03-07 21:58:13 -0800284 response, pkt = self.controller.transact(request, timeout=2)
285 self.assertTrue(response is not None, "Did not get response")
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700286 basic_logger.debug(response.show())
Dan Talayco6ce963a2010-03-07 21:58:13 -0800287
Dan Talayco79c6c4d2010-06-08 14:01:53 -0700288class TableStatsGet(SimpleProtocol):
289 """
290 Get table stats
291
292 Simply verify table stats get transaction
293 """
294 def runTest(self):
295 basic_logger.info("Running TableStatsGet")
296 basic_logger.info("Inserting trial flow")
297 request = message.flow_mod()
298 request.match.wildcards = ofp.OFPFW_ALL
299 request.buffer_id = 0xffffffff
300 rv = self.controller.message_send(request)
301 self.assertTrue(rv != -1, "Failed to insert test flow")
302
303 basic_logger.info("Sending table stats request")
304 request = message.table_stats_request()
305 response, pkt = self.controller.transact(request, timeout=2)
306 self.assertTrue(response is not None, "Did not get response")
307 basic_logger.debug(response.show())
308
Dan Talayco6ce963a2010-03-07 21:58:13 -0800309class FlowMod(SimpleProtocol):
310 """
311 Insert a flow
312
313 Simple verification of a flow mod transaction
314 """
315
316 def runTest(self):
317 basic_logger.info("Running " + str(self))
318 request = message.flow_mod()
Dan Talayco6ce963a2010-03-07 21:58:13 -0800319 request.match.wildcards = ofp.OFPFW_ALL
320 request.buffer_id = 0xffffffff
321 rv = self.controller.message_send(request)
Dan Talayco41eae8b2010-03-10 13:57:06 -0800322 self.assertTrue(rv != -1, "Error installing flow mod")
323
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700324class PortConfigMod(SimpleProtocol):
325 """
326 Modify a bit in port config and verify changed
327
328 Get the switch configuration, modify the port configuration
329 and write it back; get the config again and verify changed.
330 Then set it back to the way it was.
331 """
332
333 def runTest(self):
334 basic_logger.info("Running " + str(self))
Dan Talayco9f47f4d2010-06-03 13:54:37 -0700335 for of_port, ifname in basic_port_map.items(): # Grab first port
336 break
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700337
Dan Talayco9f47f4d2010-06-03 13:54:37 -0700338 (hw_addr, config, advert) = \
339 port_config_get(self.controller, of_port, basic_logger)
340 self.assertTrue(config is not None, "Did not get port config")
341
342 basic_logger.debug("No flood bit port " + str(of_port) + " is now " +
343 str(config & ofp.OFPPC_NO_FLOOD))
344
345 rv = port_config_set(self.controller, of_port,
346 config ^ ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD,
347 basic_logger)
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700348 self.assertTrue(rv != -1, "Error sending port mod")
349
350 # Verify change took place with same feature request
Dan Talayco9f47f4d2010-06-03 13:54:37 -0700351 (hw_addr, config2, advert) = \
352 port_config_get(self.controller, of_port, basic_logger)
353 basic_logger.debug("No flood bit port " + str(of_port) + " is now " +
354 str(config2 & ofp.OFPPC_NO_FLOOD))
355 self.assertTrue(config2 is not None, "Did not get port config2")
356 self.assertTrue(config2 & ofp.OFPPC_NO_FLOOD !=
357 config & ofp.OFPPC_NO_FLOOD,
358 "Bit change did not take")
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700359 # Set it back
Dan Talayco9f47f4d2010-06-03 13:54:37 -0700360 rv = port_config_set(self.controller, of_port, config,
361 ofp.OFPPC_NO_FLOOD, basic_logger)
362 self.assertTrue(rv != -1, "Error sending port mod")
Dan Talaycob3f43fe2010-05-13 14:24:20 -0700363
Dan Talaycodba244e2010-02-15 14:08:53 -0800364if __name__ == "__main__":
Dan Talayco2c0dba32010-03-06 22:47:06 -0800365 print "Please run through oft script: ./oft --test_spec=basic"