blob: 417df9e18cd05dfbc6dab9cca0978af34e89c7f3 [file] [log] [blame]
Jan00dad122016-11-29 10:04:53 +01001#!/usr/bin/env python
2"""L2BD Multi-instance Test Case HLD:
3
4**NOTES:**
5 - higher number of pg-l2 interfaces causes problems => only 15 pg-l2 \
6 interfaces in 5 bridge domains are tested
Jan65209ed2016-12-05 23:29:17 +01007 - jumbo packets in configuration with 14 l2-pg interfaces leads to \
8 problems too
Jan00dad122016-11-29 10:04:53 +01009
10**config 1**
11 - add 15 pg-l2 interfaces
12 - configure one host per pg-l2 interface
13 - configure 5 bridge domains (BD)
14 - add 3 pg-l2 interfaces per BD
15
16**test 1**
17 - send L2 MAC frames between all pg-l2 interfaces of all BDs
18
19**verify 1**
20 - check BD data by parsing output of bridge_domain_dump API command
21 - all packets received correctly
22
23**config 2**
24 - update data of 5 BD
25 - disable learning, forwarding, flooding and uu_flooding for BD1
26 - disable forwarding for BD2
27 - disable flooding for BD3
28 - disable uu_flooding for BD4
29 - disable learning for BD5
30
31**verify 2**
32 - check BD data by parsing output of bridge_domain_dump API command
33
34**config 3**
35 - delete 2 BDs
36
37**test 3**
38 - send L2 MAC frames between all pg-l2 interfaces of all BDs
39 - send L2 MAC frames between all pg-l2 interfaces formerly assigned to \
40 deleted BDs
41
42**verify 3**
43 - check BD data by parsing output of bridge_domain_dump API command
44 - all packets received correctly on all 3 pg-l2 interfaces assigned to BDs
45 - no packet received on all 3 pg-l2 interfaces of all deleted BDs
46
47**config 4**
48 - add 2 BDs
49 - add 3 pg-l2 interfaces per BD
50
51**test 4**
52 - send L2 MAC frames between all pg-l2 interfaces of all BDs
53
54**verify 4**
55 - check BD data by parsing output of bridge_domain_dump API command
56 - all packets received correctly
57
58**config 5**
59 - delete 5 BDs
60
61**verify 5**
62 - check BD data by parsing output of bridge_domain_dump API command
63"""
64
65import unittest
66import random
67
68from scapy.packet import Raw
69from scapy.layers.l2 import Ether
70from scapy.layers.inet import IP, UDP
71
72from framework import VppTestCase, VppTestRunner
73from util import Host
74
Ole Troan399ca1c2016-12-06 23:00:38 +010075@unittest.skip("Crashes VPP")
Jan00dad122016-11-29 10:04:53 +010076class TestL2bdMultiInst(VppTestCase):
77 """ L2BD Multi-instance Test Case """
78
79 @classmethod
80 def setUpClass(cls):
81 """
82 Perform standard class setup (defined by class method setUpClass in
83 class VppTestCase) before running the test case, set test case related
84 variables and configure VPP.
85 """
86 super(TestL2bdMultiInst, cls).setUpClass()
87
88 try:
89 # Create pg interfaces
90 cls.create_pg_interfaces(range(15))
91
92 # Packet flows mapping pg0 -> pg1, pg2 etc.
93 cls.flows = dict()
94 for i in range(0, len(cls.pg_interfaces), 3):
95 cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i+1],
96 cls.pg_interfaces[i+2]]
97 cls.flows[cls.pg_interfaces[i+1]] = [cls.pg_interfaces[i],
98 cls.pg_interfaces[i+2]]
99 cls.flows[cls.pg_interfaces[i+2]] = [cls.pg_interfaces[i],
100 cls.pg_interfaces[i+1]]
101
102 # Mapping between packet-generator index and lists of test hosts
103 cls.hosts_by_pg_idx = dict()
104 for pg_if in cls.pg_interfaces:
105 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
106
107 # Create test host entries
Jan65209ed2016-12-05 23:29:17 +0100108 cls.create_hosts(75)
Jan00dad122016-11-29 10:04:53 +0100109
Jan65209ed2016-12-05 23:29:17 +0100110 # Packet sizes - jumbo packet (9018 bytes) skipped
111 cls.pg_if_packet_sizes = [64, 512, 1518]
Jan00dad122016-11-29 10:04:53 +0100112
113 # Set up all interfaces
114 for i in cls.pg_interfaces:
115 i.admin_up()
116
117 # Create list of BDs
118 cls.bd_list = list()
119
120 # Create list of deleted BDs
121 cls.bd_deleted_list = list()
122
123 # Create list of pg_interfaces in BDs
124 cls.pg_in_bd = list()
125
126 # Create list of pg_interfaces not in BDs
127 cls.pg_not_in_bd = list()
128 for pg_if in cls.pg_interfaces:
129 cls.pg_not_in_bd.append(pg_if)
130
131 except Exception:
132 super(TestL2bdMultiInst, cls).tearDownClass()
133 raise
134
135 def setUp(self):
136 """
137 Clear trace and packet infos before running each test.
138 """
139 super(TestL2bdMultiInst, self).setUp()
140 self.packet_infos = {}
141
142 def tearDown(self):
143 """
144 Show various debug prints after each test.
145 """
146 super(TestL2bdMultiInst, self).tearDown()
147 if not self.vpp_dead:
148 self.logger.info(self.vapi.ppcli("show l2fib verbose"))
149 self.logger.info(self.vapi.ppcli("show bridge-domain"))
150
151 @classmethod
152 def create_hosts(cls, count):
153 """
154 Create required number of host MAC addresses and distribute them among
155 interfaces. Create host IPv4 address for every host MAC address.
156
157 :param int count: Number of hosts to create MAC/IPv4 addresses for.
158 """
159 n_int = len(cls.pg_interfaces)
160 macs_per_if = count / n_int
161 i = -1
162 for pg_if in cls.pg_interfaces:
163 i += 1
164 start_nr = macs_per_if * i
165 end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
166 hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
167 for j in range(start_nr, end_nr):
168 host = Host(
169 "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
170 "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
171 hosts.append(host)
172
173 def create_bd_and_mac_learn(self, count, start=1):
Jan65209ed2016-12-05 23:29:17 +0100174 """
Jan00dad122016-11-29 10:04:53 +0100175 Create required number of bridge domains with MAC learning enabled, put
176 3 l2-pg interfaces to every bridge domain and send MAC learning packets.
177
178 :param int count: Number of bridge domains to be created.
179 :param int start: Starting number of the bridge domain ID.
180 (Default value = 1)
181 """
182 for i in range(count):
183 bd_id = i + start
184 self.vapi.bridge_domain_add_del(bd_id=bd_id)
185 self.logger.info("Bridge domain ID %d created" % bd_id)
186 if self.bd_list.count(bd_id) == 0:
187 self.bd_list.append(bd_id)
188 if self.bd_deleted_list.count(bd_id) == 1:
189 self.bd_deleted_list.remove(bd_id)
190 for j in range(3):
191 pg_if = self.pg_interfaces[(i+start-1)*3+j]
192 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
193 bd_id=bd_id)
194 self.logger.info("pg-interface %s added to bridge domain ID %d"
195 % (pg_if.name, bd_id))
196 self.pg_in_bd.append(pg_if)
197 self.pg_not_in_bd.remove(pg_if)
198 packets = []
199 for host in self.hosts_by_pg_idx[pg_if.sw_if_index]:
200 packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
201 packets.append(packet)
202 pg_if.add_stream(packets)
203 self.logger.info("Sending broadcast eth frames for MAC learning")
204 self.pg_start()
205 self.logger.info(self.vapi.ppcli("show bridge-domain"))
206 self.logger.info(self.vapi.ppcli("show l2fib"))
207
208 def delete_bd(self, count, start=1):
Jan65209ed2016-12-05 23:29:17 +0100209 """
Jan00dad122016-11-29 10:04:53 +0100210 Delete required number of bridge domains.
211
212 :param int count: Number of bridge domains to be created.
213 :param int start: Starting number of the bridge domain ID.
214 (Default value = 1)
215 """
216 for i in range(count):
217 bd_id = i + start
218 self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=0)
219 if self.bd_list.count(bd_id) == 1:
220 self.bd_list.remove(bd_id)
221 if self.bd_deleted_list.count(bd_id) == 0:
222 self.bd_deleted_list.append(bd_id)
223 for j in range(3):
224 pg_if = self.pg_interfaces[(i+start-1)*3+j]
225 self.pg_in_bd.remove(pg_if)
226 self.pg_not_in_bd.append(pg_if)
227 self.logger.info("Bridge domain ID %d deleted" % bd_id)
228
229 def create_stream(self, src_if, packet_sizes):
230 """
231 Create input packet stream for defined interface using hosts list.
232
233 :param object src_if: Interface to create packet stream for.
234 :param list packet_sizes: List of required packet sizes.
235 :return: Stream of packets.
236 """
237 pkts = []
238 src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
239 for dst_if in self.flows[src_if]:
240 dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
241 n_int = len(dst_hosts)
242 for i in range(0, n_int):
243 dst_host = dst_hosts[i]
244 src_host = random.choice(src_hosts)
245 pkt_info = self.create_packet_info(
246 src_if.sw_if_index, dst_if.sw_if_index)
247 payload = self.info_to_payload(pkt_info)
248 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
249 IP(src=src_host.ip4, dst=dst_host.ip4) /
250 UDP(sport=1234, dport=1234) /
251 Raw(payload))
252 pkt_info.data = p.copy()
253 size = random.choice(packet_sizes)
254 self.extend_packet(p, size)
255 pkts.append(p)
256 self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
257 % (src_if.name, len(pkts)))
258 return pkts
259
260 def verify_capture(self, pg_if, capture):
261 """
262 Verify captured input packet stream for defined interface.
263
264 :param object pg_if: Interface to verify captured packet stream for.
265 :param list capture: Captured packet stream.
266 """
267 last_info = dict()
268 for i in self.pg_interfaces:
269 last_info[i.sw_if_index] = None
270 dst_sw_if_index = pg_if.sw_if_index
271 for packet in capture:
272 payload_info = self.payload_to_info(str(packet[Raw]))
273 try:
274 ip = packet[IP]
275 udp = packet[UDP]
276 packet_index = payload_info.index
277 self.assertEqual(payload_info.dst, dst_sw_if_index)
278 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
279 (pg_if.name, payload_info.src, packet_index))
280 next_info = self.get_next_packet_info_for_interface2(
281 payload_info.src, dst_sw_if_index,
282 last_info[payload_info.src])
283 last_info[payload_info.src] = next_info
284 self.assertTrue(next_info is not None)
285 self.assertEqual(packet_index, next_info.index)
286 saved_packet = next_info.data
287 # Check standard fields
288 self.assertEqual(ip.src, saved_packet[IP].src)
289 self.assertEqual(ip.dst, saved_packet[IP].dst)
290 self.assertEqual(udp.sport, saved_packet[UDP].sport)
291 self.assertEqual(udp.dport, saved_packet[UDP].dport)
292 except:
293 self.logger.error("Unexpected or invalid packet:")
294 self.logger.error(packet.show())
295 raise
296 for i in self.pg_interfaces:
297 remaining_packet = self.get_next_packet_info_for_interface2(
298 i, dst_sw_if_index, last_info[i.sw_if_index])
299 self.assertTrue(
300 remaining_packet is None,
301 "Port %u: Packet expected from source %u didn't arrive" %
302 (dst_sw_if_index, i.sw_if_index))
303
304 def set_bd_flags(self, bd_id, **args):
305 """
306 Enable/disable defined feature(s) of the bridge domain.
307
308 :param int bd_id: Bridge domain ID.
Jan65209ed2016-12-05 23:29:17 +0100309 :param list args: List of feature/status pairs. Allowed features: \
310 learn, forward, flood, uu_flood and arp_term. Status False means \
311 disable, status True means enable the feature.
Jan00dad122016-11-29 10:04:53 +0100312 :raise: ValueError in case of unknown feature in the input.
313 """
314 for flag in args:
315 if flag == "learn":
316 feature_bitmap = 1 << 0
317 elif flag == "forward":
318 feature_bitmap = 1 << 1
319 elif flag == "flood":
320 feature_bitmap = 1 << 2
321 elif flag == "uu_flood":
322 feature_bitmap = 1 << 3
323 elif flag == "arp_term":
324 feature_bitmap = 1 << 4
325 else:
326 raise ValueError("Unknown feature used: %s" % flag)
327 is_set = 1 if args[flag] else 0
328 self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
329 self.logger.info("Bridge domain ID %d updated" % bd_id)
330
331 def verify_bd(self, bd_id, **args):
332 """
333 Check if the bridge domain is configured and verify expected status
334 of listed features.
335
336 :param int bd_id: Bridge domain ID.
Jan65209ed2016-12-05 23:29:17 +0100337 :param list args: List of feature/status pairs. Allowed features: \
338 learn, forward, flood, uu_flood and arp_term. Status False means \
339 disable, status True means enable the feature.
Jan00dad122016-11-29 10:04:53 +0100340 :return: 1 if bridge domain is configured, otherwise return 0.
341 :raise: ValueError in case of unknown feature in the input.
342 """
343 bd_dump = self.vapi.bridge_domain_dump(bd_id)
344 if len(bd_dump) == 0:
345 self.logger.info("Bridge domain ID %d is not configured" % bd_id)
346 return 0
347 else:
348 bd_dump = bd_dump[0]
349 if len(args) > 0:
350 for flag in args:
351 expected_status = 1 if args[flag] else 0
352 if flag == "learn":
353 flag_status = bd_dump[6]
354 elif flag == "forward":
355 flag_status = bd_dump[5]
356 elif flag == "flood":
357 flag_status = bd_dump[3]
358 elif flag == "uu_flood":
359 flag_status = bd_dump[4]
360 elif flag == "arp_term":
361 flag_status = bd_dump[7]
362 else:
363 raise ValueError("Unknown feature used: %s" % flag)
364 self.assertEqual(expected_status, flag_status)
365 return 1
366
367 def run_verify_test(self):
368 """
Matej Klottondeb69842016-12-09 15:05:46 +0100369 Create packet streams for all configured l2-pg interfaces, send all \
Jan00dad122016-11-29 10:04:53 +0100370 prepared packet streams and verify that:
Matej Klottondeb69842016-12-09 15:05:46 +0100371 - all packets received correctly on all pg-l2 interfaces assigned
372 to bridge domains
373 - no packet received on all pg-l2 interfaces not assigned to
374 bridge domains
Jan00dad122016-11-29 10:04:53 +0100375
Matej Klottondeb69842016-12-09 15:05:46 +0100376 :raise RuntimeError: if no packet captured on l2-pg interface assigned
377 to the bridge domain or if any packet is captured
378 on l2-pg interface not assigned to the bridge
379 domain.
Jan00dad122016-11-29 10:04:53 +0100380 """
381 # Test
382 # Create incoming packet streams for packet-generator interfaces
383 for pg_if in self.pg_interfaces:
384 pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
385 pg_if.add_stream(pkts)
386
387 # Enable packet capture and start packet sending
388 self.pg_enable_capture(self.pg_interfaces)
389 self.pg_start()
390
391 # Verify
392 # Verify outgoing packet streams per packet-generator interface
393 for pg_if in self.pg_interfaces:
394 capture = pg_if.get_capture()
395 if pg_if in self.pg_in_bd:
396 if len(capture) == 0:
397 raise RuntimeError("Interface %s is in BD but the capture "
398 "is empty!" % pg_if.name)
399 self.verify_capture(pg_if, capture)
400 elif pg_if in self.pg_not_in_bd:
401 try:
402 self.assertEqual(len(capture), 0)
403 except AssertionError:
404 raise RuntimeError("Interface %s is not in BD but "
405 "the capture is not empty!" % pg_if.name)
406 else:
407 self.logger.error("Unknown interface: %s" % pg_if.name)
408
409 def test_l2bd_inst_01(self):
410 """ L2BD Multi-instance test 1 - create 5 BDs
411 """
412 # Config 1
413 # Create 5 BDs, put interfaces to these BDs and send MAC learning
414 # packets
415 self.create_bd_and_mac_learn(5)
416
417 # Verify 1
418 for bd_id in self.bd_list:
419 self.assertEqual(self.verify_bd(bd_id), 1)
420
421 # Test 1
422 # self.vapi.cli("clear trace")
423 self.run_verify_test()
424
425 def test_l2bd_inst_02(self):
426 """ L2BD Multi-instance test 2 - update data of 5 BDs
427 """
428 # Config 2
429 # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
430 self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
431 flood=False, uu_flood=False)
432 self.set_bd_flags(self.bd_list[1], forward=False)
433 self.set_bd_flags(self.bd_list[2], flood=False)
434 self.set_bd_flags(self.bd_list[3], uu_flood=False)
435 self.set_bd_flags(self.bd_list[4], learn=False)
436
437 # Verify 2
438 # Skipping check of uu_flood as it is not returned by
439 # bridge_domain_dump api command
440 self.verify_bd(self.bd_list[0], learn=False, forward=False,
441 flood=False, uu_flood=False)
442 self.verify_bd(self.bd_list[1], learn=True, forward=False,
443 flood=True, uu_flood=True)
444 self.verify_bd(self.bd_list[2], learn=True, forward=True,
445 flood=False, uu_flood=True)
446 self.verify_bd(self.bd_list[3], learn=True, forward=True,
447 flood=True, uu_flood=False)
448 self.verify_bd(self.bd_list[4], learn=False, forward=True,
449 flood=True, uu_flood=True)
450
451 def test_l2bd_inst_03(self):
452 """ L2BD Multi-instance 3 - delete 2 BDs
453 """
454 # Config 3
455 # Delete 2 BDs
456 self.delete_bd(2)
457
458 # Verify 3
459 for bd_id in self.bd_deleted_list:
460 self.assertEqual(self.verify_bd(bd_id), 0)
461 for bd_id in self.bd_list:
462 self.assertEqual(self.verify_bd(bd_id), 1)
463
464 # Test 3
465 self.run_verify_test()
466
467 def test_l2bd_inst_04(self):
468 """ L2BD Multi-instance test 4 - add 2 BDs
469 """
470 # Config 4
471 # Create 5 BDs, put interfaces to these BDs and send MAC learning
472 # packets
473 self.create_bd_and_mac_learn(2)
474
475 # Verify 4
476 for bd_id in self.bd_list:
477 self.assertEqual(self.verify_bd(bd_id), 1)
478
479 # Test 4
480 # self.vapi.cli("clear trace")
481 self.run_verify_test()
482
Jan00dad122016-11-29 10:04:53 +0100483 def test_l2bd_inst_05(self):
484 """ L2BD Multi-instance 5 - delete 5 BDs
485 """
486 # Config 5
487 # Delete 5 BDs
488 self.delete_bd(5)
489
490 # Verify 5
491 for bd_id in self.bd_deleted_list:
492 self.assertEqual(self.verify_bd(bd_id), 0)
493 for bd_id in self.bd_list:
494 self.assertEqual(self.verify_bd(bd_id), 1)
495
496
497if __name__ == '__main__':
498 unittest.main(testRunner=VppTestRunner)