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 @unittest.skip("Crashes VPP")
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
91 cls.create_pg_interfaces(range(15))
93 # Packet flows mapping pg0 -> pg1, pg2 etc.
95 for i in range(0, len(cls.pg_interfaces), 3):
96 cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + 1],
97 cls.pg_interfaces[i + 2]]
98 cls.flows[cls.pg_interfaces[i + 1]] = [cls.pg_interfaces[i],
99 cls.pg_interfaces[i + 2]]
100 cls.flows[cls.pg_interfaces[i + 2]] = [cls.pg_interfaces[i],
101 cls.pg_interfaces[i + 1]]
103 # Mapping between packet-generator index and lists of test hosts
104 cls.hosts_by_pg_idx = dict()
105 for pg_if in cls.pg_interfaces:
106 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
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:
121 # Create list of deleted BDs
122 cls.bd_deleted_list = list()
124 # Create list of pg_interfaces in BDs
125 cls.pg_in_bd = list()
127 # Create list of pg_interfaces not in BDs
128 cls.pg_not_in_bd = list()
129 for pg_if in cls.pg_interfaces:
130 cls.pg_not_in_bd.append(pg_if)
133 super(TestL2bdMultiInst, cls).tearDownClass()
138 Clear trace and packet infos before running each test.
140 super(TestL2bdMultiInst, self).setUp()
141 self.packet_infos = {}
145 Show various debug prints after each test.
147 super(TestL2bdMultiInst, self).tearDown()
148 if not self.vpp_dead:
149 self.logger.info(self.vapi.ppcli("show l2fib verbose"))
150 self.logger.info(self.vapi.ppcli("show bridge-domain"))
153 def create_hosts(cls, count):
155 Create required number of host MAC addresses and distribute them among
156 interfaces. Create host IPv4 address for every host MAC address.
158 :param int count: Number of hosts to create MAC/IPv4 addresses for.
160 n_int = len(cls.pg_interfaces)
161 macs_per_if = count / n_int
163 for pg_if in cls.pg_interfaces:
165 start_nr = macs_per_if * i
166 end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
167 hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
168 for j in range(start_nr, end_nr):
170 "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
171 "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
174 def create_bd_and_mac_learn(self, count, start=1):
176 Create required number of bridge domains with MAC learning enabled, put
177 3 l2-pg interfaces to every bridge domain and send MAC learning packets.
179 :param int count: Number of bridge domains to be created.
180 :param int start: Starting number of the bridge domain ID.
183 for i in range(count):
185 self.vapi.bridge_domain_add_del(bd_id=bd_id)
186 self.logger.info("Bridge domain ID %d created" % bd_id)
187 if self.bd_list.count(bd_id) == 0:
188 self.bd_list.append(bd_id)
189 if self.bd_deleted_list.count(bd_id) == 1:
190 self.bd_deleted_list.remove(bd_id)
192 pg_if = self.pg_interfaces[(i + start - 1) * 3 + j]
193 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
195 self.logger.info("pg-interface %s added to bridge domain ID %d"
196 % (pg_if.name, bd_id))
197 self.pg_in_bd.append(pg_if)
198 self.pg_not_in_bd.remove(pg_if)
200 for host in self.hosts_by_pg_idx[pg_if.sw_if_index]:
201 packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
202 packets.append(packet)
203 pg_if.add_stream(packets)
204 self.logger.info("Sending broadcast eth frames for MAC learning")
206 self.logger.info(self.vapi.ppcli("show bridge-domain"))
207 self.logger.info(self.vapi.ppcli("show l2fib"))
209 def delete_bd(self, count, start=1):
211 Delete required number of bridge domains.
213 :param int count: Number of bridge domains to be created.
214 :param int start: Starting number of the bridge domain ID.
217 for i in range(count):
219 self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=0)
220 if self.bd_list.count(bd_id) == 1:
221 self.bd_list.remove(bd_id)
222 if self.bd_deleted_list.count(bd_id) == 0:
223 self.bd_deleted_list.append(bd_id)
225 pg_if = self.pg_interfaces[(i + start - 1) * 3 + j]
226 self.pg_in_bd.remove(pg_if)
227 self.pg_not_in_bd.append(pg_if)
228 self.logger.info("Bridge domain ID %d deleted" % bd_id)
230 def create_stream(self, src_if, packet_sizes):
232 Create input packet stream for defined interface using hosts list.
234 :param object src_if: Interface to create packet stream for.
235 :param list packet_sizes: List of required packet sizes.
236 :return: Stream of packets.
239 src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
240 for dst_if in self.flows[src_if]:
241 dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
242 n_int = len(dst_hosts)
243 for i in range(0, n_int):
244 dst_host = dst_hosts[i]
245 src_host = random.choice(src_hosts)
246 pkt_info = self.create_packet_info(
247 src_if.sw_if_index, dst_if.sw_if_index)
248 payload = self.info_to_payload(pkt_info)
249 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
250 IP(src=src_host.ip4, dst=dst_host.ip4) /
251 UDP(sport=1234, dport=1234) /
253 pkt_info.data = p.copy()
254 size = random.choice(packet_sizes)
255 self.extend_packet(p, size)
257 self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
258 % (src_if.name, len(pkts)))
261 def verify_capture(self, pg_if, capture):
263 Verify captured input packet stream for defined interface.
265 :param object pg_if: Interface to verify captured packet stream for.
266 :param list capture: Captured packet stream.
269 for i in self.pg_interfaces:
270 last_info[i.sw_if_index] = None
271 dst_sw_if_index = pg_if.sw_if_index
272 for packet in capture:
273 payload_info = self.payload_to_info(str(packet[Raw]))
277 packet_index = payload_info.index
278 self.assertEqual(payload_info.dst, dst_sw_if_index)
279 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
280 (pg_if.name, payload_info.src, packet_index))
281 next_info = self.get_next_packet_info_for_interface2(
282 payload_info.src, dst_sw_if_index,
283 last_info[payload_info.src])
284 last_info[payload_info.src] = next_info
285 self.assertTrue(next_info is not None)
286 self.assertEqual(packet_index, next_info.index)
287 saved_packet = next_info.data
288 # Check standard fields
289 self.assertEqual(ip.src, saved_packet[IP].src)
290 self.assertEqual(ip.dst, saved_packet[IP].dst)
291 self.assertEqual(udp.sport, saved_packet[UDP].sport)
292 self.assertEqual(udp.dport, saved_packet[UDP].dport)
294 self.logger.error(ppp("Unexpected or invalid packet:", packet))
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: \
310 learn, forward, flood, uu_flood and arp_term. Status False means \
311 disable, status True means enable the feature.
312 :raise: ValueError in case of unknown feature in the input.
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
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)
331 def verify_bd(self, bd_id, **args):
333 Check if the bridge domain is configured and verify expected status
336 :param int bd_id: Bridge domain ID.
337 :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.
340 :return: 1 if bridge domain is configured, otherwise return 0.
341 :raise: ValueError in case of unknown feature in the input.
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)
351 expected_status = 1 if args[flag] else 0
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]
363 raise ValueError("Unknown feature used: %s" % flag)
364 self.assertEqual(expected_status, flag_status)
367 def run_verify_test(self):
369 Create packet streams for all configured l2-pg interfaces, send all \
370 prepared packet streams and verify that:
371 - all packets received correctly on all pg-l2 interfaces assigned
373 - no packet received on all pg-l2 interfaces not assigned to
376 :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
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)
387 # Enable packet capture and start packet sending
388 self.pg_enable_capture(self.pg_interfaces)
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:
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)
407 self.logger.error("Unknown interface: %s" % pg_if.name)
409 def test_l2bd_inst_01(self):
410 """ L2BD Multi-instance test 1 - create 5 BDs
413 # Create 5 BDs, put interfaces to these BDs and send MAC learning
415 self.create_bd_and_mac_learn(5)
418 for bd_id in self.bd_list:
419 self.assertEqual(self.verify_bd(bd_id), 1)
422 # self.vapi.cli("clear trace")
423 self.run_verify_test()
425 def test_l2bd_inst_02(self):
426 """ L2BD Multi-instance test 2 - update data of 5 BDs
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)
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)
451 def test_l2bd_inst_03(self):
452 """ L2BD Multi-instance 3 - delete 2 BDs
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)
465 self.run_verify_test()
467 def test_l2bd_inst_04(self):
468 """ L2BD Multi-instance test 4 - add 2 BDs
471 # Create 5 BDs, put interfaces to these BDs and send MAC learning
473 self.create_bd_and_mac_learn(2)
476 for bd_id in self.bd_list:
477 self.assertEqual(self.verify_bd(bd_id), 1)
480 # self.vapi.cli("clear trace")
481 self.run_verify_test()
483 def test_l2bd_inst_05(self):
484 """ L2BD Multi-instance 5 - delete 5 BDs
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)
497 if __name__ == '__main__':
498 unittest.main(testRunner=VppTestRunner)