1272d7656a2ecdca4463676dd53339331b287bc1
[vpp.git] / test / test_l2bd_multi_instance.py
1 #!/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
7     - jumbo packets in configuration with 14 l2-pg interfaces leads to \
8     problems too
9
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
65 import unittest
66 import random
67
68 from scapy.packet import Raw
69 from scapy.layers.l2 import Ether
70 from scapy.layers.inet import IP, UDP
71
72 from framework import VppTestCase, VppTestRunner
73 from util import Host, ppp
74
75
76 @unittest.skip("Crashes VPP")
77 class TestL2bdMultiInst(VppTestCase):
78     """ L2BD Multi-instance Test Case """
79
80     @classmethod
81     def setUpClass(cls):
82         """
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.
86         """
87         super(TestL2bdMultiInst, cls).setUpClass()
88
89         try:
90             # Create pg interfaces
91             cls.create_pg_interfaces(range(15))
92
93             # Packet flows mapping pg0 -> pg1, pg2 etc.
94             cls.flows = dict()
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]]
102
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] = []
107
108             # Create test host entries
109             cls.create_hosts(75)
110
111             # Packet sizes - jumbo packet (9018 bytes) skipped
112             cls.pg_if_packet_sizes = [64, 512, 1518]
113
114             # Set up all interfaces
115             for i in cls.pg_interfaces:
116                 i.admin_up()
117
118             # Create list of BDs
119             cls.bd_list = list()
120
121             # Create list of deleted BDs
122             cls.bd_deleted_list = list()
123
124             # Create list of pg_interfaces in BDs
125             cls.pg_in_bd = list()
126
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)
131
132         except Exception:
133             super(TestL2bdMultiInst, cls).tearDownClass()
134             raise
135
136     def setUp(self):
137         """
138         Clear trace and packet infos before running each test.
139         """
140         super(TestL2bdMultiInst, self).setUp()
141         self.packet_infos = {}
142
143     def tearDown(self):
144         """
145         Show various debug prints after each test.
146         """
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"))
151
152     @classmethod
153     def create_hosts(cls, count):
154         """
155         Create required number of host MAC addresses and distribute them among
156         interfaces. Create host IPv4 address for every host MAC address.
157
158         :param int count: Number of hosts to create MAC/IPv4 addresses for.
159         """
160         n_int = len(cls.pg_interfaces)
161         macs_per_if = count / n_int
162         i = -1
163         for pg_if in cls.pg_interfaces:
164             i += 1
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):
169                 host = Host(
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))
172                 hosts.append(host)
173
174     def create_bd_and_mac_learn(self, count, start=1):
175         """
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.
178
179         :param int count: Number of bridge domains to be created.
180         :param int start: Starting number of the bridge domain ID.
181             (Default value = 1)
182         """
183         for i in range(count):
184             bd_id = i + start
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)
191             for j in range(3):
192                 pg_if = self.pg_interfaces[(i + start - 1) * 3 + j]
193                 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
194                                                      bd_id=bd_id)
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)
199                 packets = []
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")
205         self.pg_start()
206         self.logger.info(self.vapi.ppcli("show bridge-domain"))
207         self.logger.info(self.vapi.ppcli("show l2fib"))
208
209     def delete_bd(self, count, start=1):
210         """
211         Delete required number of bridge domains.
212
213         :param int count: Number of bridge domains to be created.
214         :param int start: Starting number of the bridge domain ID.
215             (Default value = 1)
216         """
217         for i in range(count):
218             bd_id = i + start
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)
224             for j in range(3):
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)
229
230     def create_stream(self, src_if, packet_sizes):
231         """
232         Create input packet stream for defined interface using hosts list.
233
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.
237         """
238         pkts = []
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) /
252                      Raw(payload))
253                 pkt_info.data = p.copy()
254                 size = random.choice(packet_sizes)
255                 self.extend_packet(p, size)
256                 pkts.append(p)
257         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
258                           % (src_if.name, len(pkts)))
259         return pkts
260
261     def verify_capture(self, pg_if, capture):
262         """
263         Verify captured input packet stream for defined interface.
264
265         :param object pg_if: Interface to verify captured packet stream for.
266         :param list capture: Captured packet stream.
267         """
268         last_info = dict()
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]))
274             try:
275                 ip = packet[IP]
276                 udp = packet[UDP]
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)
293             except:
294                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
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.
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.
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.
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.
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         """
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
372               to bridge domains
373             - no packet received on all pg-l2 interfaces not assigned to
374               bridge domains
375
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
379                              domain.
380         """
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
483     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
497 if __name__ == '__main__':
498     unittest.main(testRunner=VppTestRunner)