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 - more then 1 host per pg-l2 interface in configuration with 15 l2-pg \
8 interfaces leads to problems too
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
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
90 cls.create_pg_interfaces(range(15))
92 # Packet flows mapping pg0 -> pg1, pg2 etc.
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]]
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] = []
107 # Create test host entries
111 cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
113 # Set up all interfaces
114 for i in cls.pg_interfaces:
120 # Create list of deleted BDs
121 cls.bd_deleted_list = list()
123 # Create list of pg_interfaces in BDs
124 cls.pg_in_bd = list()
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)
132 super(TestL2bdMultiInst, cls).tearDownClass()
137 Clear trace and packet infos before running each test.
139 super(TestL2bdMultiInst, self).setUp()
140 self.packet_infos = {}
144 Show various debug prints after each test.
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"))
152 def create_hosts(cls, count):
154 Create required number of host MAC addresses and distribute them among
155 interfaces. Create host IPv4 address for every host MAC address.
157 :param int count: Number of hosts to create MAC/IPv4 addresses for.
159 n_int = len(cls.pg_interfaces)
160 macs_per_if = count / n_int
162 for pg_if in cls.pg_interfaces:
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):
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))
173 def create_bd_and_mac_learn(self, count, start=1):
175 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.
178 :param int count: Number of bridge domains to be created.
179 :param int start: Starting number of the bridge domain ID.
182 for i in range(count):
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)
191 pg_if = self.pg_interfaces[(i+start-1)*3+j]
192 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
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)
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")
205 self.logger.info(self.vapi.ppcli("show bridge-domain"))
206 self.logger.info(self.vapi.ppcli("show l2fib"))
208 def delete_bd(self, count, start=1):
210 Delete required number of bridge domains.
212 :param int count: Number of bridge domains to be created.
213 :param int start: Starting number of the bridge domain ID.
216 for i in range(count):
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)
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)
229 def create_stream(self, src_if, packet_sizes):
231 Create input packet stream for defined interface using hosts list.
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.
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) /
252 pkt_info.data = p.copy()
253 size = random.choice(packet_sizes)
254 self.extend_packet(p, size)
256 self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
257 % (src_if.name, len(pkts)))
260 def verify_capture(self, pg_if, capture):
262 Verify captured input packet stream for defined interface.
264 :param object pg_if: Interface to verify captured packet stream for.
265 :param list capture: Captured packet stream.
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]))
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)
293 self.logger.error("Unexpected or invalid packet:")
294 self.logger.error(packet.show())
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])
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))
304 def set_bd_flags(self, bd_id, **args):
306 Enable/disable defined feature(s) of the bridge domain.
308 :param int bd_id: Bridge domain ID.
309 :param list args: List of feature/status pairs. Allowed features:
315 Status False means disable, status True means enable the feature.
316 :raise: ValueError in case of unknown feature in the input.
320 feature_bitmap = 1 << 0
321 elif flag == "forward":
322 feature_bitmap = 1 << 1
323 elif flag == "flood":
324 feature_bitmap = 1 << 2
325 elif flag == "uu_flood":
326 feature_bitmap = 1 << 3
327 elif flag == "arp_term":
328 feature_bitmap = 1 << 4
330 raise ValueError("Unknown feature used: %s" % flag)
331 is_set = 1 if args[flag] else 0
332 self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
333 self.logger.info("Bridge domain ID %d updated" % bd_id)
335 def verify_bd(self, bd_id, **args):
337 Check if the bridge domain is configured and verify expected status
340 :param int bd_id: Bridge domain ID.
341 :param list args: List of feature/status pairs. Allowed features:
347 Status False means disable, status True means enable the feature.
348 :return: 1 if bridge domain is configured, otherwise return 0.
349 :raise: ValueError in case of unknown feature in the input.
351 bd_dump = self.vapi.bridge_domain_dump(bd_id)
352 if len(bd_dump) == 0:
353 self.logger.info("Bridge domain ID %d is not configured" % bd_id)
359 expected_status = 1 if args[flag] else 0
361 flag_status = bd_dump[6]
362 elif flag == "forward":
363 flag_status = bd_dump[5]
364 elif flag == "flood":
365 flag_status = bd_dump[3]
366 elif flag == "uu_flood":
367 flag_status = bd_dump[4]
368 elif flag == "arp_term":
369 flag_status = bd_dump[7]
371 raise ValueError("Unknown feature used: %s" % flag)
372 self.assertEqual(expected_status, flag_status)
375 def run_verify_test(self):
377 Create packet streams for all configured l2-pg interfaces, send all
378 prepared packet streams and verify that:
379 - all packets received correctly on all pg-l2 interfaces assigned to
381 - no packet received on all pg-l2 interfaces not assigned to bridge
384 :raise: RuntimeError if no packet captured on l2-pg interface assigned
385 to the bridge domain or if any packet is captured on l2-pg interface
386 not assigned to the bridge domain.
389 # Create incoming packet streams for packet-generator interfaces
390 for pg_if in self.pg_interfaces:
391 pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
392 pg_if.add_stream(pkts)
394 # Enable packet capture and start packet sending
395 self.pg_enable_capture(self.pg_interfaces)
399 # Verify outgoing packet streams per packet-generator interface
400 for pg_if in self.pg_interfaces:
401 capture = pg_if.get_capture()
402 if pg_if in self.pg_in_bd:
403 if len(capture) == 0:
404 raise RuntimeError("Interface %s is in BD but the capture "
405 "is empty!" % pg_if.name)
406 self.verify_capture(pg_if, capture)
407 elif pg_if in self.pg_not_in_bd:
409 self.assertEqual(len(capture), 0)
410 except AssertionError:
411 raise RuntimeError("Interface %s is not in BD but "
412 "the capture is not empty!" % pg_if.name)
414 self.logger.error("Unknown interface: %s" % pg_if.name)
416 def test_l2bd_inst_01(self):
417 """ L2BD Multi-instance test 1 - create 5 BDs
420 # Create 5 BDs, put interfaces to these BDs and send MAC learning
422 self.create_bd_and_mac_learn(5)
425 for bd_id in self.bd_list:
426 self.assertEqual(self.verify_bd(bd_id), 1)
429 # self.vapi.cli("clear trace")
430 self.run_verify_test()
432 def test_l2bd_inst_02(self):
433 """ L2BD Multi-instance test 2 - update data of 5 BDs
436 # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
437 self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
438 flood=False, uu_flood=False)
439 self.set_bd_flags(self.bd_list[1], forward=False)
440 self.set_bd_flags(self.bd_list[2], flood=False)
441 self.set_bd_flags(self.bd_list[3], uu_flood=False)
442 self.set_bd_flags(self.bd_list[4], learn=False)
445 # Skipping check of uu_flood as it is not returned by
446 # bridge_domain_dump api command
447 self.verify_bd(self.bd_list[0], learn=False, forward=False,
448 flood=False, uu_flood=False)
449 self.verify_bd(self.bd_list[1], learn=True, forward=False,
450 flood=True, uu_flood=True)
451 self.verify_bd(self.bd_list[2], learn=True, forward=True,
452 flood=False, uu_flood=True)
453 self.verify_bd(self.bd_list[3], learn=True, forward=True,
454 flood=True, uu_flood=False)
455 self.verify_bd(self.bd_list[4], learn=False, forward=True,
456 flood=True, uu_flood=True)
458 def test_l2bd_inst_03(self):
459 """ L2BD Multi-instance 3 - delete 2 BDs
466 for bd_id in self.bd_deleted_list:
467 self.assertEqual(self.verify_bd(bd_id), 0)
468 for bd_id in self.bd_list:
469 self.assertEqual(self.verify_bd(bd_id), 1)
472 self.run_verify_test()
474 def test_l2bd_inst_04(self):
475 """ L2BD Multi-instance test 4 - add 2 BDs
478 # Create 5 BDs, put interfaces to these BDs and send MAC learning
480 self.create_bd_and_mac_learn(2)
483 for bd_id in self.bd_list:
484 self.assertEqual(self.verify_bd(bd_id), 1)
487 # self.vapi.cli("clear trace")
488 self.run_verify_test()
490 def test_l2bd_inst_05(self):
491 """ L2BD Multi-instance 5 - delete 5 BDs
498 for bd_id in self.bd_deleted_list:
499 self.assertEqual(self.verify_bd(bd_id), 0)
500 for bd_id in self.bd_list:
501 self.assertEqual(self.verify_bd(bd_id), 1)
504 if __name__ == '__main__':
505 unittest.main(testRunner=VppTestRunner)