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, VppTestRunner
73 from util import Host, ppp
76 class TestL2bdMultiInst(VppTestCase):
77 """L2BD Multi-instance Test Case"""
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.
86 super(TestL2bdMultiInst, cls).setUpClass()
89 # Create pg interfaces
91 cls.ifs_per_bd = ifs_per_bd = 3
92 n_ifs = n_bd * ifs_per_bd
93 cls.create_pg_interfaces(range(n_ifs))
95 # Packet flows mapping pg0 -> pg1, pg2 etc.
98 bd_ifs = cls.bd_if_range(b + 1)
100 cls.flows[cls.pg_interfaces[j]] = [
101 cls.pg_interfaces[x] for x in bd_ifs if x != j
103 assert len(cls.flows[cls.pg_interfaces[j]]) == ifs_per_bd - 1
105 # Mapping between packet-generator index and lists of test hosts
106 cls.hosts_by_pg_idx = dict()
108 # Create test host entries
111 # Packet sizes - jumbo packet (9018 bytes) skipped
112 cls.pg_if_packet_sizes = [64, 512, 1518]
114 # Set up all interfaces
115 for i in cls.pg_interfaces:
119 super(TestL2bdMultiInst, cls).tearDownClass()
123 def tearDownClass(cls):
124 super(TestL2bdMultiInst, cls).tearDownClass()
128 Clear trace and packet infos before running each test.
130 self.reset_packet_infos()
131 super(TestL2bdMultiInst, self).setUp()
138 # Create list of deleted BDs
139 self.bd_deleted_list = []
141 # Create list of pg_interfaces in BDs
146 Show various debug prints after each test.
148 super(TestL2bdMultiInst, self).tearDown()
149 if not self.vpp_dead:
150 self.logger.info(self.vapi.ppcli("show l2fib verbose"))
151 self.logger.info(self.vapi.ppcli("show bridge-domain"))
154 def create_hosts(cls, hosts_per_if):
156 Create required number of host MAC addresses and distribute them
157 among interfaces. Create host IPv4 address for every host MAC
160 :param int hosts_per_if: Number of hosts per if to create MAC/IPv4
164 assert not cls.hosts_by_pg_idx
165 for i in range(len(cls.pg_interfaces)):
166 pg_idx = cls.pg_interfaces[i].sw_if_index
167 cls.hosts_by_pg_idx[pg_idx] = [
169 "00:00:00:ff:%02x:%02x" % (pg_idx, j + 1),
170 "172.17.1%02u.%u" % (pg_idx, j + 1),
176 def bd_if_range(cls, b):
179 return range(start, start + n)
181 def create_bd_and_mac_learn(self, count, start=1):
183 Create required number of bridge domains with MAC learning enabled,
184 put 3 l2-pg interfaces to every bridge domain and send MAC learning
187 :param int count: Number of bridge domains to be created.
188 :param int start: Starting number of the bridge domain ID.
191 for b in range(start, start + count):
193 self.vapi.bridge_domain_add_del_v2(
194 bd_id=b, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
198 ret = self.vapi.bridge_domain_add_del_v2(
199 bd_id=0xFFFFFFFF, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
202 self.logger.info("Bridge domain ID %d created" % bd_id)
203 if self.bd_list.count(bd_id) == 0:
204 self.bd_map[b] = bd_id
205 self.bd_list.append(bd_id)
206 if self.bd_deleted_list.count(bd_id) == 1:
207 self.bd_deleted_list.remove(bd_id)
208 for j in self.bd_if_range(b):
209 pg_if = self.pg_interfaces[j]
210 self.vapi.sw_interface_set_l2_bridge(
211 rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id
214 "pg-interface %s added to bridge domain ID %d" % (pg_if.name, bd_id)
216 self.pg_in_bd.append(pg_if)
217 hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
219 Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac) for host in hosts
221 pg_if.add_stream(packets)
222 self.logger.info("Sending broadcast eth frames for MAC learning")
224 self.logger.info(self.vapi.ppcli("show bridge-domain"))
225 self.logger.info(self.vapi.ppcli("show l2fib"))
227 def delete_bd(self, count, start=1):
229 Delete required number of bridge domains.
231 :param int count: Number of bridge domains to be created.
232 :param int start: Starting number of the bridge domain ID.
235 for b in range(start, start + count):
236 bd_id = self.bd_map[b]
237 for j in self.bd_if_range(b):
238 pg_if = self.pg_interfaces[j]
239 self.vapi.sw_interface_set_l2_bridge(
240 rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id, enable=0
242 self.pg_in_bd.remove(pg_if)
244 "pg-interface %s removed from bridge domain ID %d"
245 % (pg_if.name, bd_id)
247 self.vapi.bridge_domain_add_del_v2(bd_id=bd_id, is_add=0)
249 self.bd_list.remove(bd_id)
250 self.bd_deleted_list.append(bd_id)
251 self.logger.info("Bridge domain ID %d deleted" % bd_id)
253 def create_stream(self, src_if):
255 Create input packet stream for defined interface using hosts list.
257 :param object src_if: Interface to create packet stream for.
258 :param list packet_sizes: List of required packet sizes.
259 :return: Stream of packets.
261 packet_sizes = self.pg_if_packet_sizes
263 src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
264 for dst_if in self.flows[src_if]:
265 dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
266 for dst_host in dst_hosts:
267 pkt_info = self.create_packet_info(src_if, dst_if)
268 payload = self.info_to_payload(pkt_info)
269 src_host = random.choice(src_hosts)
271 Ether(dst=dst_host.mac, src=src_host.mac)
272 / IP(src=src_host.ip4, dst=dst_host.ip4)
273 / UDP(sport=1234, dport=1234)
276 pkt_info.data = p.copy()
277 size = random.choice(packet_sizes)
278 self.extend_packet(p, size)
281 "Input stream created for port %s. Length: %u pkt(s)"
282 % (src_if.name, len(pkts))
286 def verify_capture(self, dst_if):
288 Verify captured input packet stream for defined interface.
290 :param object dst_if: Interface to verify captured packet stream for.
293 for i in self.flows[dst_if]:
294 last_info[i.sw_if_index] = None
295 dst = dst_if.sw_if_index
296 for packet in dst_if.get_capture():
300 info = self.payload_to_info(packet[Raw])
301 self.assertEqual(info.dst, dst)
303 "Got packet on port %s: src=%u (id=%u)"
304 % (dst_if.name, info.src, info.index)
306 last_info[info.src] = self.get_next_packet_info_for_interface2(
307 info.src, dst, last_info[info.src]
309 pkt_info = last_info[info.src]
310 self.assertTrue(pkt_info is not None)
311 self.assertEqual(info.index, pkt_info.index)
312 # Check standard fields against saved data in pkt
313 saved = pkt_info.data
314 self.assertEqual(ip.src, saved[IP].src)
315 self.assertEqual(ip.dst, saved[IP].dst)
316 self.assertEqual(udp.sport, saved[UDP].sport)
317 self.assertEqual(udp.dport, saved[UDP].dport)
319 self.logger.error(ppp("Unexpected or invalid packet:", packet))
323 for src in self.flows[dst_if]:
324 remaining_packet = self.get_next_packet_info_for_interface2(
325 src.sw_if_index, dst, last_info[src.sw_if_index]
327 if remaining_packet is None:
328 s += "Port %u: Packet expected from source %u didn't arrive\n" % (
333 self.assertNotEqual(0, remaining, s)
335 def set_bd_flags(self, bd_id, **args):
337 Enable/disable defined feature(s) of the bridge domain.
339 :param int bd_id: Bridge domain ID.
340 :param list args: List of feature/status pairs. Allowed features: \
341 learn, forward, flood, uu_flood and arp_term. Status False means \
342 disable, status True means enable the feature.
343 :raise: ValueError in case of unknown feature in the input.
347 feature_bitmap = 1 << 0
348 elif flag == "forward":
349 feature_bitmap = 1 << 1
350 elif flag == "flood":
351 feature_bitmap = 1 << 2
352 elif flag == "uu_flood":
353 feature_bitmap = 1 << 3
354 elif flag == "arp_term":
355 feature_bitmap = 1 << 4
357 raise ValueError("Unknown feature used: %s" % flag)
358 is_set = 1 if args[flag] else 0
359 self.vapi.bridge_flags(bd_id=bd_id, is_set=is_set, flags=feature_bitmap)
360 self.logger.info("Bridge domain ID %d updated" % bd_id)
362 def verify_bd(self, bd_id, **args):
364 Check if the bridge domain is configured and verify expected status
367 :param int bd_id: Bridge domain ID.
368 :param list args: List of feature/status pairs. Allowed features: \
369 learn, forward, flood, uu_flood and arp_term. Status False means \
370 disable, status True means enable the feature.
371 :return: 1 if bridge domain is configured, otherwise return 0.
372 :raise: ValueError in case of unknown feature in the input.
374 bd_dump = self.vapi.bridge_domain_dump(bd_id)
375 if len(bd_dump) == 0:
376 self.logger.info("Bridge domain ID %d is not configured" % bd_id)
382 expected_status = 1 if args[flag] else 0
384 flag_status = bd_dump[6]
385 elif flag == "forward":
386 flag_status = bd_dump[5]
387 elif flag == "flood":
388 flag_status = bd_dump[3]
389 elif flag == "uu_flood":
390 flag_status = bd_dump[4]
391 elif flag == "arp_term":
392 flag_status = bd_dump[7]
394 raise ValueError("Unknown feature used: %s" % flag)
395 self.assertEqual(expected_status, flag_status)
398 def run_verify_test(self):
400 Create packet streams for all configured l2-pg interfaces, send all \
401 prepared packet streams and verify that:
402 - all packets received correctly on all pg-l2 interfaces assigned
404 - no packet received on all pg-l2 interfaces not assigned to
407 :raise RuntimeError: if no packet captured on l2-pg interface assigned
408 to the bridge domain or if any packet is captured
409 on l2-pg interface not assigned to the bridge
413 # Create incoming packet streams for packet-generator interfaces
414 # for pg_if in self.pg_interfaces:
415 assert len(self._packet_count_for_dst_if_idx) == 0
416 for pg_if in self.pg_in_bd:
417 pkts = self.create_stream(pg_if)
418 pg_if.add_stream(pkts)
420 # Enable packet capture and start packet sending
421 self.pg_enable_capture(self.pg_in_bd)
425 # Verify outgoing packet streams per packet-generator interface
426 for pg_if in self.pg_in_bd:
427 self.verify_capture(pg_if)
429 def test_l2bd_inst_01(self):
430 """L2BD Multi-instance test 1 - create 5 BDs"""
432 # Create 5 BDs, put interfaces to these BDs and send MAC learning
434 self.create_bd_and_mac_learn(5)
437 for bd_id in self.bd_list:
438 self.assertEqual(self.verify_bd(bd_id), 1)
441 # self.vapi.cli("clear trace")
442 self.run_verify_test()
445 def test_l2bd_inst_02(self):
446 """L2BD Multi-instance test 2 - update data of 5 BDs"""
448 # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
449 self.create_bd_and_mac_learn(5)
451 self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
453 self.set_bd_flags(self.bd_list[1], forward=False)
454 self.set_bd_flags(self.bd_list[2], flood=False)
455 self.set_bd_flags(self.bd_list[3], uu_flood=False)
456 self.set_bd_flags(self.bd_list[4], learn=False)
459 # Skipping check of uu_flood as it is not returned by
460 # bridge_domain_dump api command
462 self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
465 self.bd_list[1], learn=True, forward=False, flood=True, uu_flood=True
468 self.bd_list[2], learn=True, forward=True, flood=False, uu_flood=True
471 self.bd_list[3], learn=True, forward=True, flood=True, uu_flood=False
474 self.bd_list[4], learn=False, forward=True, flood=True, uu_flood=True
478 def test_l2bd_inst_03(self):
479 """L2BD Multi-instance test 3 - delete 2 BDs"""
482 self.create_bd_and_mac_learn(5)
486 for bd_id in self.bd_deleted_list:
487 self.assertEqual(self.verify_bd(bd_id), 0)
488 for bd_id in self.bd_list:
489 self.assertEqual(self.verify_bd(bd_id), 1)
492 self.run_verify_test()
495 def test_l2bd_inst_04(self):
496 """L2BD Multi-instance test 4 - add 2 BDs"""
498 # Create 5 BDs, put interfaces to these BDs and send MAC learning
500 self.create_bd_and_mac_learn(2)
503 for bd_id in self.bd_list:
504 self.assertEqual(self.verify_bd(bd_id), 1)
507 # self.vapi.cli("clear trace")
508 self.run_verify_test()
511 def test_l2bd_inst_05(self):
512 """L2BD Multi-instance test 5 - delete 5 BDs"""
515 self.create_bd_and_mac_learn(5)
519 for bd_id in self.bd_deleted_list:
520 self.assertEqual(self.verify_bd(bd_id), 0)
521 for bd_id in self.bd_list:
522 self.assertEqual(self.verify_bd(bd_id), 1)
525 if __name__ == "__main__":
526 unittest.main(testRunner=VppTestRunner)