blob: f7a8ad29c60f9eaf6c6b4fde42425d77a05d320d [file] [log] [blame]
Dan Talayco89d57342010-06-07 16:24:59 -07001"""
2Flow stats test case.
3Similar to Flow stats test case in the perl test harness.
4
5"""
6
7import logging
8
9import unittest
10import random
11
12import oftest.controller as controller
13import oftest.cstruct as ofp
14import oftest.message as message
15import oftest.dataplane as dataplane
16import oftest.action as action
17import oftest.parse as parse
18import basic
19
20from testutils import *
21from time import sleep
22
23#@var port_map Local copy of the configuration map from OF port
24# numbers to OS interfaces
25pa_port_map = None
26#@var pa_logger Local logger object
27pa_logger = None
28#@var pa_config Local copy of global configuration data
29pa_config = None
30
Ken Chiang620bdcc2012-03-23 12:52:07 -070031# TODO: ovs has problems with VLAN id?
32WILDCARD_VALUES = [ofp.OFPFW_IN_PORT,
33 # ofp.OFPFW_DL_VLAN,
34 ofp.OFPFW_DL_SRC,
35 ofp.OFPFW_DL_DST,
36 ofp.OFPFW_DL_TYPE,
37 ofp.OFPFW_NW_PROTO,
38 ofp.OFPFW_TP_SRC,
39 ofp.OFPFW_TP_DST,
40 0x3F << ofp.OFPFW_NW_SRC_SHIFT,
41 0x3F << ofp.OFPFW_NW_DST_SHIFT,
42 ofp.OFPFW_DL_VLAN_PCP,
43 ofp.OFPFW_NW_TOS]
44
Dan Talayco89d57342010-06-07 16:24:59 -070045def test_set_init(config):
46 """
47 Set up function for packet action test classes
48
49 @param config The configuration dictionary; see oft
50 """
51
52 global pa_port_map
53 global pa_logger
54 global pa_config
55
56 pa_logger = logging.getLogger("pkt_act")
57 pa_logger.info("Initializing test set")
58 pa_port_map = config["port_map"]
59 pa_config = config
60
Ken Chiang620bdcc2012-03-23 12:52:07 -070061class SingleFlowStats(basic.SimpleDataPlane):
Dan Talayco89d57342010-06-07 16:24:59 -070062 """
63 Verify flow stats are properly retrieved.
64
65 Generate a packet
Ken Chiang620bdcc2012-03-23 12:52:07 -070066 Generate and install a matching flow
67 Send the packet
68 Send a flow stats request to match the flow and retrieve stats
69 Verify that the packet counter has incremented
Dan Talayco89d57342010-06-07 16:24:59 -070070 """
Ken Chiang620bdcc2012-03-23 12:52:07 -070071
72 def verifyStats(self, match, out_port, test_timeout, packet_count):
73 stat_req = message.flow_stats_request()
74 stat_req.match = match
75 stat_req.table_id = 0xff
76 stat_req.out_port = out_port
77
78 all_packets_received = 0
79 for i in range(0,test_timeout):
80 pa_logger.info("Sending stats request")
81 rv = self.controller.message_send(stat_req)
82 self.assertTrue(rv != -1, "Error sending flow stat req")
83 do_barrier(self.controller)
84
85 (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
86 self.assertTrue(len(response.stats) == 1, "Did not receive flow stats reply")
87 for obj in response.stats:
88 # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
89 # for now, just clear them so the assert is simpler
90 #obj.match.pad1 = 0
91 #obj.match.pad2 = [0, 0]
92 #self.assertEqual(match, obj.match,
93 # "Matches do not match")
94 pa_logger.info("Received " + str(obj.packet_count) + " packets")
95 if obj.packet_count == packet_count:
96 all_packets_received = 1
97
98 if all_packets_received:
99 break
100 sleep(1)
101
102 self.assertTrue(all_packets_received,
103 "Packet count does not match number sent")
104
Dan Talayco89d57342010-06-07 16:24:59 -0700105 def runTest(self):
106 global pa_port_map
107
Ken Chiang620bdcc2012-03-23 12:52:07 -0700108 # TODO: set from command-line parameter
109 test_timeout = 60
110
Dan Talayco89d57342010-06-07 16:24:59 -0700111 of_ports = pa_port_map.keys()
112 of_ports.sort()
113 self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
114
115 rc = delete_all_flows(self.controller, pa_logger)
116 self.assertEqual(rc, 0, "Failed to delete all flows")
117
Ken Chiang620bdcc2012-03-23 12:52:07 -0700118 # build packet
Dan Talayco89d57342010-06-07 16:24:59 -0700119 pkt = simple_tcp_packet()
120 match = parse.packet_to_flow_match(pkt)
121 match.wildcards &= ~ofp.OFPFW_IN_PORT
122 self.assertTrue(match is not None,
123 "Could not generate flow match from pkt")
124 act = action.action_output()
125
Ken Chiang620bdcc2012-03-23 12:52:07 -0700126 # build flow
Dan Talayco39bf6912010-07-08 14:17:52 -0700127 ingress_port = of_ports[0];
128 egress_port = of_ports[1];
Dan Talayco89d57342010-06-07 16:24:59 -0700129 pa_logger.info("Ingress " + str(ingress_port) +
130 " to egress " + str(egress_port))
Ken Chiang620bdcc2012-03-23 12:52:07 -0700131 match.in_port = ingress_port
132 flow_mod_msg = message.flow_mod()
133 flow_mod_msg.match = match
134 flow_mod_msg.cookie = random.randint(0,9007199254740992)
135 flow_mod_msg.buffer_id = 0xffffffff
136 flow_mod_msg.idle_timeout = 0
137 flow_mod_msg.hard_timeout = 0
138 act.port = egress_port
139 self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
140
141 # send flow
142 pa_logger.info("Inserting flow")
143 rv = self.controller.message_send(flow_mod_msg)
144 self.assertTrue(rv != -1, "Error installing flow mod")
145 do_barrier(self.controller)
146
147 # no packets sent, so zero packet count
148 self.verifyStats(match, ofp.OFPP_NONE, test_timeout, 0)
149
150 # send packet N times
151 num_sends = random.randint(10,20)
152 pa_logger.info("Sending " + str(num_sends) + " test packets")
153 for i in range(0,num_sends):
154 pa_logger.info("Sending packet to dp port " +
155 str(ingress_port))
156 self.dataplane.send(ingress_port, str(pkt))
Dan Talaycoaff26c82012-03-25 15:06:26 -0700157 (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
158 test_timeout)
Ken Chiang620bdcc2012-03-23 12:52:07 -0700159 self.assertTrue(rcv_pkt is not None, "Did not receive packet")
160 pa_logger.debug("Packet len " + str(len(pkt)) + " in on " +
161 str(rcv_port))
162 self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
163 for j in range(0,test_timeout):
164 if str(pkt) == str(rcv_pkt):
165 break
166 sleep(1)
167 self.assertTrue(j < test_timeout,
168 'Timeout sending packets for flow stats test')
169
170 self.verifyStats(match, ofp.OFPP_NONE, test_timeout, num_sends)
171 self.verifyStats(match, egress_port, test_timeout, num_sends)
172 for wc in WILDCARD_VALUES:
173 match.wildcards = wc
174 self.verifyStats(match, egress_port, test_timeout, num_sends)
175
176
177class TwoFlowStats(basic.SimpleDataPlane):
178 """
179 Verify flow stats are properly retrieved.
180
181 Generate two packets and install two matching flows
182 Send some number of packets
183 Send a flow stats request to match the flows and retrieve stats
184 Verify that the packet counter has incremented
185
186 TODO: add a third flow, and then configure the match to exclude
187 that flow?
188 """
189
190 def buildFlowModMsg(self, pkt, ingress_port, egress_port):
191 match = parse.packet_to_flow_match(pkt)
192 match.wildcards &= ~ofp.OFPFW_IN_PORT
193 self.assertTrue(match is not None,
194 "Could not generate flow match from pkt")
Dan Talayco89d57342010-06-07 16:24:59 -0700195 match.in_port = ingress_port
196
197 flow_mod_msg = message.flow_mod()
198 flow_mod_msg.match = match
199 flow_mod_msg.cookie = random.randint(0,9007199254740992)
200 flow_mod_msg.buffer_id = 0xffffffff
Ken Chiang620bdcc2012-03-23 12:52:07 -0700201 flow_mod_msg.idle_timeout = 0
202 flow_mod_msg.hard_timeout = 0
203 act = action.action_output()
Dan Talayco89d57342010-06-07 16:24:59 -0700204 act.port = egress_port
205 self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
Dan Talayco89d57342010-06-07 16:24:59 -0700206
Ken Chiang620bdcc2012-03-23 12:52:07 -0700207 pa_logger.info("Ingress " + str(ingress_port) +
208 " to egress " + str(egress_port))
Dan Talayco89d57342010-06-07 16:24:59 -0700209
Ken Chiang620bdcc2012-03-23 12:52:07 -0700210 return flow_mod_msg
Dan Talayco89d57342010-06-07 16:24:59 -0700211
Ken Chiang620bdcc2012-03-23 12:52:07 -0700212 def sendPacket(self, pkt, ingress_port, egress_port, test_timeout):
Dan Talayco89d57342010-06-07 16:24:59 -0700213 pa_logger.info("Sending packet to dp port " +
214 str(ingress_port))
215 self.dataplane.send(ingress_port, str(pkt))
Dan Talaycoaff26c82012-03-25 15:06:26 -0700216 (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
217 test_timeout)
Dan Talayco89d57342010-06-07 16:24:59 -0700218 self.assertTrue(rcv_pkt is not None, "Did not receive packet")
219 pa_logger.debug("Packet len " + str(len(pkt)) + " in on " +
220 str(rcv_port))
221 self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
Ken Chiang620bdcc2012-03-23 12:52:07 -0700222 for j in range(0,test_timeout):
223 if str(pkt) == str(rcv_pkt):
224 break
225 sleep(1)
226 self.assertTrue(j < test_timeout,
227 'Timeout sending packets for flow stats test')
228
229 def verifyStats(self, match, out_port, test_timeout, packet_count):
230 stat_req = message.flow_stats_request()
231 stat_req.match = match
232 stat_req.table_id = 0xff
233 stat_req.out_port = out_port
234
235 all_packets_received = 0
236 for i in range(0,test_timeout):
237 pa_logger.info("Sending stats request")
238 rv = self.controller.message_send(stat_req)
239 self.assertTrue(rv != -1, "Error sending flow stat req")
240 do_barrier(self.controller)
241
242 (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
Dan Talaycoaff26c82012-03-25 15:06:26 -0700243 self.assertTrue(len(response.stats) >= 1,
244 "Did not receive flow stats reply")
Ken Chiang620bdcc2012-03-23 12:52:07 -0700245 total_packets = 0
246 for obj in response.stats:
247 # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
248 # for now, just clear them so the assert is simpler
249 #obj.match.pad1 = 0
250 #obj.match.pad2 = [0, 0]
251 #self.assertEqual(match, obj.match,
252 # "Matches do not match")
253 pa_logger.info("Received " + str(obj.packet_count) + " packets")
254 total_packets += obj.packet_count
255 if total_packets == packet_count:
256 all_packets_received = 1
257 break
258 sleep(1)
259
260 self.assertTrue(all_packets_received,
261 "Packet count does not match number sent")
262
263 def runTest(self):
264 global pa_port_map
265
266 # TODO: set from command-line parameter
267 test_timeout = 60
268
269 of_ports = pa_port_map.keys()
270 of_ports.sort()
271 self.assertTrue(len(of_ports) >= 3, "Not enough ports for test")
272 ingress_port = of_ports[0];
273 egress_port1 = of_ports[1];
274 egress_port2 = of_ports[2];
275
276 rc = delete_all_flows(self.controller, pa_logger)
277 self.assertEqual(rc, 0, "Failed to delete all flows")
278
279 pkt1 = simple_tcp_packet()
280 flow_mod_msg1 = self.buildFlowModMsg(pkt1, ingress_port, egress_port1)
281
282 pkt2 = simple_tcp_packet(dl_src='0:7:7:7:7:7')
283 flow_mod_msg2 = self.buildFlowModMsg(pkt2, ingress_port, egress_port2)
284
285 pa_logger.info("Inserting flow1")
286 rv = self.controller.message_send(flow_mod_msg1)
287 self.assertTrue(rv != -1, "Error installing flow mod")
288 pa_logger.info("Inserting flow2")
289 rv = self.controller.message_send(flow_mod_msg2)
290 self.assertTrue(rv != -1, "Error installing flow mod")
Dan Talayco89d57342010-06-07 16:24:59 -0700291 do_barrier(self.controller)
292
Ken Chiang620bdcc2012-03-23 12:52:07 -0700293 num_pkt1s = random.randint(10,30)
294 pa_logger.info("Sending " + str(num_pkt1s) + " pkt1s")
295 num_pkt2s = random.randint(10,30)
296 pa_logger.info("Sending " + str(num_pkt2s) + " pkt2s")
297 for i in range(0,num_pkt1s):
298 self.sendPacket(pkt1, ingress_port, egress_port1, test_timeout)
299 for i in range(0,num_pkt2s):
300 self.sendPacket(pkt2, ingress_port, egress_port2, test_timeout)
301
302 match1 = parse.packet_to_flow_match(pkt1)
303 self.verifyStats(match1, ofp.OFPP_NONE, test_timeout, num_pkt1s)
304 match2 = parse.packet_to_flow_match(pkt2)
305 self.verifyStats(match2, ofp.OFPP_NONE, test_timeout, num_pkt2s)
306 match1.wildcards |= ofp.OFPFW_DL_SRC
307 self.verifyStats(match1, ofp.OFPP_NONE, test_timeout,
308 num_pkt1s+num_pkt2s)
309 # TODO: sweep through the wildcards to verify matching?
310
311
312class AggregateStats(basic.SimpleDataPlane):
313 """
314 Verify aggregate flow stats are properly retrieved.
315
316 Generate two packets
317 Generate and install two matching flows
318 Send an aggregate stats request
319 Verify that aggregate stats are correct
320 Also verify out_port filtering
321 """
322
323 def buildFlowModMsg(self, pkt, ingress_port, egress_port):
324 match = parse.packet_to_flow_match(pkt)
325 match.wildcards &= ~ofp.OFPFW_IN_PORT
326 self.assertTrue(match is not None,
327 "Could not generate flow match from pkt")
328 match.in_port = ingress_port
329
330 flow_mod_msg = message.flow_mod()
331 flow_mod_msg.match = match
332 flow_mod_msg.cookie = random.randint(0,9007199254740992)
333 flow_mod_msg.buffer_id = 0xffffffff
334 flow_mod_msg.idle_timeout = 0
335 flow_mod_msg.hard_timeout = 0
336 act = action.action_output()
337 act.port = egress_port
338 self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
339
340 pa_logger.info("Ingress " + str(ingress_port) +
341 " to egress " + str(egress_port))
342
343 return flow_mod_msg
344
345 def sendPacket(self, pkt, ingress_port, egress_port, test_timeout):
346 pa_logger.info("Sending packet to dp port " +
347 str(ingress_port))
348 self.dataplane.send(ingress_port, str(pkt))
Dan Talaycoaff26c82012-03-25 15:06:26 -0700349 (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
350 test_timeout)
Ken Chiang620bdcc2012-03-23 12:52:07 -0700351 self.assertTrue(rcv_pkt is not None, "Did not receive packet")
352 pa_logger.debug("Packet len " + str(len(pkt)) + " in on " +
353 str(rcv_port))
354 self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
355 for j in range(0,test_timeout):
356 if str(pkt) == str(rcv_pkt):
357 break
358 sleep(1)
359 self.assertTrue(j < test_timeout,
360 'Timeout sending packets for flow stats test')
361
362 def verifyAggFlowStats(self, match, out_port, test_timeout,
363 flow_count, packet_count):
364 stat_req = message.aggregate_stats_request()
365 stat_req.match = match
366 stat_req.table_id = 0xff
367 stat_req.out_port = out_port
368
369 all_packets_received = 0
370 for i in range(0,test_timeout):
371 pa_logger.info("Sending stats request")
372 rv = self.controller.message_send(stat_req)
373 self.assertTrue(rv != -1, "Error sending flow stat req")
374 do_barrier(self.controller)
375
376 (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
Dan Talaycoaff26c82012-03-25 15:06:26 -0700377 self.assertTrue(len(response.stats) == 1,
378 "Did not receive flow stats reply")
Ken Chiang620bdcc2012-03-23 12:52:07 -0700379 for obj in response.stats:
380 self.assertTrue(obj.flow_count == flow_count,
381 "Flow count " + str(obj.flow_count) +
382 " does not match expected " + str(flow_count))
383 pa_logger.info("Received " + str(obj.packet_count) + " packets")
384 if obj.packet_count == packet_count:
385 all_packets_received = 1
386
387 if all_packets_received:
388 break
389 sleep(1)
390
391 self.assertTrue(all_packets_received,
392 "Packet count does not match number sent")
393
394 def runTest(self):
395 global pa_port_map
396
397 # TODO: set from command-line parameter
398 test_timeout = 60
399
400 of_ports = pa_port_map.keys()
401 of_ports.sort()
402 self.assertTrue(len(of_ports) >= 3, "Not enough ports for test")
403 ingress_port = of_ports[0];
404 egress_port1 = of_ports[1];
405 egress_port2 = of_ports[2];
406
407 rc = delete_all_flows(self.controller, pa_logger)
408 self.assertEqual(rc, 0, "Failed to delete all flows")
409
410 pkt1 = simple_tcp_packet()
411 flow_mod_msg1 = self.buildFlowModMsg(pkt1, ingress_port, egress_port1)
412
413 pkt2 = simple_tcp_packet(dl_src='0:7:7:7:7:7')
414 flow_mod_msg2 = self.buildFlowModMsg(pkt2, ingress_port, egress_port2)
415
416 pa_logger.info("Inserting flow1")
417 rv = self.controller.message_send(flow_mod_msg1)
418 self.assertTrue(rv != -1, "Error installing flow mod")
419 pa_logger.info("Inserting flow2")
420 rv = self.controller.message_send(flow_mod_msg2)
421 self.assertTrue(rv != -1, "Error installing flow mod")
422 do_barrier(self.controller)
423
424 num_pkt1s = random.randint(10,30)
425 pa_logger.info("Sending " + str(num_pkt1s) + " pkt1s")
426 num_pkt2s = random.randint(10,30)
427 pa_logger.info("Sending " + str(num_pkt2s) + " pkt2s")
428 for i in range(0,num_pkt1s):
429 self.sendPacket(pkt1, ingress_port, egress_port1, test_timeout)
430 for i in range(0,num_pkt2s):
431 self.sendPacket(pkt2, ingress_port, egress_port2, test_timeout)
432
433 # loop on flow stats request until timeout
434 match = parse.packet_to_flow_match(pkt1)
435 match.wildcards |= ofp.OFPFW_DL_SRC
436 self.verifyAggFlowStats(match, ofp.OFPP_NONE, test_timeout,
437 2, num_pkt1s+num_pkt2s)
438
439 # out_port filter for egress_port1
440 self.verifyAggFlowStats(match, egress_port1, test_timeout,
441 1, num_pkt1s)
442
443 # out_port filter for egress_port1
444 self.verifyAggFlowStats(match, egress_port2, test_timeout,
445 1, num_pkt2s)