2 """L2BD Multi-instance Test Case HLD:
5 - higher number of pg-l2 interfaces causes problems => only 15 pg-l2 \
6 interfaces in 5 bridge domains are tested
7 - jumbo packets in configuration with 14 l2-pg interfaces leads to \
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
17 - send L2 MAC frames between all pg-l2 interfaces of all BDs
20 - check BD data by parsing output of bridge_domain_dump API command
21 - all packets received correctly
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
32 - check BD data by parsing output of bridge_domain_dump API command
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 \
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
49 - add 3 pg-l2 interfaces per BD
52 - send L2 MAC frames between all pg-l2 interfaces of all BDs
55 - check BD data by parsing output of bridge_domain_dump API command
56 - all packets received correctly
62 - check BD data by parsing output of bridge_domain_dump API command
68 from scapy.packet import Raw
69 from scapy.layers.l2 import Ether
70 from scapy.layers.inet import IP, UDP
72 from framework import VppTestCase
73 from asfframework import VppTestRunner
74 from util import Host, ppp
77 class TestL2bdMultiInst(VppTestCase):
78 """L2BD Multi-instance Test Case"""
83 Perform standard class setup (defined by class method setUpClass in
84 class VppTestCase) before running the test case, set test case related
85 variables and configure VPP.
87 super(TestL2bdMultiInst, cls).setUpClass()
90 # Create pg interfaces
92 cls.ifs_per_bd = ifs_per_bd = 3
93 n_ifs = n_bd * ifs_per_bd
94 cls.create_pg_interfaces(range(n_ifs))
96 # Packet flows mapping pg0 -> pg1, pg2 etc.
99 bd_ifs = cls.bd_if_range(b + 1)
101 cls.flows[cls.pg_interfaces[j]] = [
102 cls.pg_interfaces[x] for x in bd_ifs if x != j
104 assert len(cls.flows[cls.pg_interfaces[j]]) == ifs_per_bd - 1
106 # Mapping between packet-generator index and lists of test hosts
107 cls.hosts_by_pg_idx = dict()
109 # Create test host entries
112 # Packet sizes - jumbo packet (9018 bytes) skipped
113 cls.pg_if_packet_sizes = [64, 512, 1518]
115 # Set up all interfaces
116 for i in cls.pg_interfaces:
120 super(TestL2bdMultiInst, cls).tearDownClass()
124 def tearDownClass(cls):
125 super(TestL2bdMultiInst, cls).tearDownClass()
129 Clear trace and packet infos before running each test.
131 self.reset_packet_infos()
132 super(TestL2bdMultiInst, self).setUp()
139 # Create list of deleted BDs
140 self.bd_deleted_list = []
142 # Create list of pg_interfaces in BDs
147 Show various debug prints after each test.
149 super(TestL2bdMultiInst, self).tearDown()
150 if not self.vpp_dead:
151 self.logger.info(self.vapi.ppcli("show l2fib verbose"))
152 self.logger.info(self.vapi.ppcli("show bridge-domain"))
155 def create_hosts(cls, hosts_per_if):
157 Create required number of host MAC addresses and distribute them
158 among interfaces. Create host IPv4 address for every host MAC
161 :param int hosts_per_if: Number of hosts per if to create MAC/IPv4
165 assert not cls.hosts_by_pg_idx
166 for i in range(len(cls.pg_interfaces)):
167 pg_idx = cls.pg_interfaces[i].sw_if_index
168 cls.hosts_by_pg_idx[pg_idx] = [
170 "00:00:00:ff:%02x:%02x" % (pg_idx, j + 1),
171 "172.17.1%02u.%u" % (pg_idx, j + 1),
177 def bd_if_range(cls, b):
180 return range(start, start + n)
182 def create_bd_and_mac_learn(self, count, start=1):
184 Create required number of bridge domains with MAC learning enabled,
185 put 3 l2-pg interfaces to every bridge domain and send MAC learning
188 :param int count: Number of bridge domains to be created.
189 :param int start: Starting number of the bridge domain ID.
192 for b in range(start, start + count):
194 self.vapi.bridge_domain_add_del_v2(
195 bd_id=b, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
199 ret = self.vapi.bridge_domain_add_del_v2(
200 bd_id=0xFFFFFFFF, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
203 self.logger.info("Bridge domain ID %d created" % bd_id)
204 if self.bd_list.count(bd_id) == 0:
205 self.bd_map[b] = bd_id
206 self.bd_list.append(bd_id)
207 if self.bd_deleted_list.count(bd_id) == 1:
208 self.bd_deleted_list.remove(bd_id)
209 for j in self.bd_if_range(b):
210 pg_if = self.pg_interfaces[j]
211 self.vapi.sw_interface_set_l2_bridge(
212 rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id
215 "pg-interface %s added to bridge domain ID %d" % (pg_if.name, bd_id)
217 self.pg_in_bd.append(pg_if)
218 hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
220 Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac) for host in hosts
222 pg_if.add_stream(packets)
223 self.logger.info("Sending broadcast eth frames for MAC learning")
225 self.logger.info(self.vapi.ppcli("show bridge-domain"))
226 self.logger.info(self.vapi.ppcli("show l2fib"))
228 def delete_bd(self, count, start=1):
230 Delete required number of bridge domains.
232 :param int count: Number of bridge domains to be created.
233 :param int start: Starting number of the bridge domain ID.
236 for b in range(start, start + count):
237 bd_id = self.bd_map[b]
238 for j in self.bd_if_range(b):
239 pg_if = self.pg_interfaces[j]
240 self.vapi.sw_interface_set_l2_bridge(
241 rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id, enable=0
243 self.pg_in_bd.remove(pg_if)
245 "pg-interface %s removed from bridge domain ID %d"
246 % (pg_if.name, bd_id)
248 self.vapi.bridge_domain_add_del_v2(bd_id=bd_id, is_add=0)
250 self.bd_list.remove(bd_id)
251 self.bd_deleted_list.append(bd_id)
252 self.logger.info("Bridge domain ID %d deleted" % bd_id)
254 def create_stream(self, src_if):
256 Create input packet stream for defined interface using hosts list.
258 :param object src_if: Interface to create packet stream for.
259 :param list packet_sizes: List of required packet sizes.
260 :return: Stream of packets.
262 packet_sizes = self.pg_if_packet_sizes
264 src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
265 for dst_if in self.flows[src_if]:
266 dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
267 for dst_host in dst_hosts:
268 pkt_info = self.create_packet_info(src_if, dst_if)
269 payload = self.info_to_payload(pkt_info)
270 src_host = random.choice(src_hosts)
272 Ether(dst=dst_host.mac, src=src_host.mac)
273 / IP(src=src_host.ip4, dst=dst_host.ip4)
274 / UDP(sport=1234, dport=1234)
277 pkt_info.data = p.copy()
278 size = random.choice(packet_sizes)
279 self.extend_packet(p, size)
282 "Input stream created for port %s. Length: %u pkt(s)"
283 % (src_if.name, len(pkts))
287 def verify_capture(self, dst_if):
289 Verify captured input packet stream for defined interface.
291 :param object dst_if: Interface to verify captured packet stream for.
294 for i in self.flows[dst_if]:
295 last_info[i.sw_if_index] = None
296 dst = dst_if.sw_if_index
297 for packet in dst_if.get_capture():
301 info = self.payload_to_info(packet[Raw])
302 self.assertEqual(info.dst, dst)
304 "Got packet on port %s: src=%u (id=%u)"
305 % (dst_if.name, info.src, info.index)
307 last_info[info.src] = self.get_next_packet_info_for_interface2(
308 info.src, dst, last_info[info.src]
310 pkt_info = last_info[info.src]
311 self.assertTrue(pkt_info is not None)
312 self.assertEqual(info.index, pkt_info.index)
313 # Check standard fields against saved data in pkt
314 saved = pkt_info.data
315 self.assertEqual(ip.src, saved[IP].src)
316 self.assertEqual(ip.dst, saved[IP].dst)
317 self.assertEqual(udp.sport, saved[UDP].sport)
318 self.assertEqual(udp.dport, saved[UDP].dport)
320 self.logger.error(ppp("Unexpected or invalid packet:", packet))
324 for src in self.flows[dst_if]:
325 remaining_packet = self.get_next_packet_info_for_interface2(
326 src.sw_if_index, dst, last_info[src.sw_if_index]
328 if remaining_packet is None:
329 s += "Port %u: Packet expected from source %u didn't arrive\n" % (
334 self.assertNotEqual(0, remaining, s)
336 def set_bd_flags(self, bd_id, **args):
338 Enable/disable defined feature(s) of the bridge domain.
340 :param int bd_id: Bridge domain ID.
341 :param list args: List of feature/status pairs. Allowed features: \
342 learn, forward, flood, uu_flood and arp_term. Status False means \
343 disable, status True means enable the feature.
344 :raise: ValueError in case of unknown feature in the input.
348 feature_bitmap = 1 << 0
349 elif flag == "forward":
350 feature_bitmap = 1 << 1
351 elif flag == "flood":
352 feature_bitmap = 1 << 2
353 elif flag == "uu_flood":
354 feature_bitmap = 1 << 3
355 elif flag == "arp_term":
356 feature_bitmap = 1 << 4
358 raise ValueError("Unknown feature used: %s" % flag)
359 is_set = 1 if args[flag] else 0
360 self.vapi.bridge_flags(bd_id=bd_id, is_set=is_set, flags=feature_bitmap)
361 self.logger.info("Bridge domain ID %d updated" % bd_id)
363 def verify_bd(self, bd_id, **args):
365 Check if the bridge domain is configured and verify expected status
368 :param int bd_id: Bridge domain ID.
369 :param list args: List of feature/status pairs. Allowed features: \
370 learn, forward, flood, uu_flood and arp_term. Status False means \
371 disable, status True means enable the feature.
372 :return: 1 if bridge domain is configured, otherwise return 0.
373 :raise: ValueError in case of unknown feature in the input.
375 bd_dump = self.vapi.bridge_domain_dump(bd_id)
376 if len(bd_dump) == 0:
377 self.logger.info("Bridge domain ID %d is not configured" % bd_id)
383 expected_status = 1 if args[flag] else 0
385 flag_status = bd_dump[6]
386 elif flag == "forward":
387 flag_status = bd_dump[5]
388 elif flag == "flood":
389 flag_status = bd_dump[3]
390 elif flag == "uu_flood":
391 flag_status = bd_dump[4]
392 elif flag == "arp_term":
393 flag_status = bd_dump[7]
395 raise ValueError("Unknown feature used: %s" % flag)
396 self.assertEqual(expected_status, flag_status)
399 def run_verify_test(self):
401 Create packet streams for all configured l2-pg interfaces, send all \
402 prepared packet streams and verify that:
403 - all packets received correctly on all pg-l2 interfaces assigned
405 - no packet received on all pg-l2 interfaces not assigned to
408 :raise RuntimeError: if no packet captured on l2-pg interface assigned
409 to the bridge domain or if any packet is captured
410 on l2-pg interface not assigned to the bridge
414 # Create incoming packet streams for packet-generator interfaces
415 # for pg_if in self.pg_interfaces:
416 assert len(self._packet_count_for_dst_if_idx) == 0
417 for pg_if in self.pg_in_bd:
418 pkts = self.create_stream(pg_if)
419 pg_if.add_stream(pkts)
421 # Enable packet capture and start packet sending
422 self.pg_enable_capture(self.pg_in_bd)
426 # Verify outgoing packet streams per packet-generator interface
427 for pg_if in self.pg_in_bd:
428 self.verify_capture(pg_if)
430 def test_l2bd_inst_01(self):
431 """L2BD Multi-instance test 1 - create 5 BDs"""
433 # Create 5 BDs, put interfaces to these BDs and send MAC learning
435 self.create_bd_and_mac_learn(5)
438 for bd_id in self.bd_list:
439 self.assertEqual(self.verify_bd(bd_id), 1)
442 # self.vapi.cli("clear trace")
443 self.run_verify_test()
446 def test_l2bd_inst_02(self):
447 """L2BD Multi-instance test 2 - update data of 5 BDs"""
449 # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
450 self.create_bd_and_mac_learn(5)
452 self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
454 self.set_bd_flags(self.bd_list[1], forward=False)
455 self.set_bd_flags(self.bd_list[2], flood=False)
456 self.set_bd_flags(self.bd_list[3], uu_flood=False)
457 self.set_bd_flags(self.bd_list[4], learn=False)
460 # Skipping check of uu_flood as it is not returned by
461 # bridge_domain_dump api command
463 self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
466 self.bd_list[1], learn=True, forward=False, flood=True, uu_flood=True
469 self.bd_list[2], learn=True, forward=True, flood=False, uu_flood=True
472 self.bd_list[3], learn=True, forward=True, flood=True, uu_flood=False
475 self.bd_list[4], learn=False, forward=True, flood=True, uu_flood=True
479 def test_l2bd_inst_03(self):
480 """L2BD Multi-instance test 3 - delete 2 BDs"""
483 self.create_bd_and_mac_learn(5)
487 for bd_id in self.bd_deleted_list:
488 self.assertEqual(self.verify_bd(bd_id), 0)
489 for bd_id in self.bd_list:
490 self.assertEqual(self.verify_bd(bd_id), 1)
493 self.run_verify_test()
496 def test_l2bd_inst_04(self):
497 """L2BD Multi-instance test 4 - add 2 BDs"""
499 # Create 5 BDs, put interfaces to these BDs and send MAC learning
501 self.create_bd_and_mac_learn(2)
504 for bd_id in self.bd_list:
505 self.assertEqual(self.verify_bd(bd_id), 1)
508 # self.vapi.cli("clear trace")
509 self.run_verify_test()
512 def test_l2bd_inst_05(self):
513 """L2BD Multi-instance test 5 - delete 5 BDs"""
516 self.create_bd_and_mac_learn(5)
520 for bd_id in self.bd_deleted_list:
521 self.assertEqual(self.verify_bd(bd_id), 0)
522 for bd_id in self.bd_list:
523 self.assertEqual(self.verify_bd(bd_id), 1)
526 if __name__ == "__main__":
527 unittest.main(testRunner=VppTestRunner)