tests: replace pycodestyle with black
[vpp.git] / test / test_l2bd_multi_instance.py
1 #!/usr/bin/env python3
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 class TestL2bdMultiInst(VppTestCase):
77     """L2BD Multi-instance Test Case"""
78
79     @classmethod
80     def setUpClass(cls):
81         """
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.
85         """
86         super(TestL2bdMultiInst, cls).setUpClass()
87
88         try:
89             # Create pg interfaces
90             n_bd = 5
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))
94
95             # Packet flows mapping pg0 -> pg1, pg2 etc.
96             cls.flows = dict()
97             for b in range(n_bd):
98                 bd_ifs = cls.bd_if_range(b + 1)
99                 for j in bd_ifs:
100                     cls.flows[cls.pg_interfaces[j]] = [
101                         cls.pg_interfaces[x] for x in bd_ifs if x != j
102                     ]
103                     assert len(cls.flows[cls.pg_interfaces[j]]) == ifs_per_bd - 1
104
105             # Mapping between packet-generator index and lists of test hosts
106             cls.hosts_by_pg_idx = dict()
107
108             # Create test host entries
109             cls.create_hosts(5)
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         except Exception:
119             super(TestL2bdMultiInst, cls).tearDownClass()
120             raise
121
122     @classmethod
123     def tearDownClass(cls):
124         super(TestL2bdMultiInst, cls).tearDownClass()
125
126     def setUp(self):
127         """
128         Clear trace and packet infos before running each test.
129         """
130         self.reset_packet_infos()
131         super(TestL2bdMultiInst, self).setUp()
132         # Create list of BDs
133         self.bd_list = []
134
135         # Create list of deleted BDs
136         self.bd_deleted_list = []
137
138         # Create list of pg_interfaces in BDs
139         self.pg_in_bd = []
140
141     def tearDown(self):
142         """
143         Show various debug prints after each test.
144         """
145         super(TestL2bdMultiInst, self).tearDown()
146         if not self.vpp_dead:
147             self.logger.info(self.vapi.ppcli("show l2fib verbose"))
148             self.logger.info(self.vapi.ppcli("show bridge-domain"))
149
150     @classmethod
151     def create_hosts(cls, hosts_per_if):
152         """
153         Create required number of host MAC addresses and distribute them
154         among interfaces. Create host IPv4 address for every host MAC
155         address.
156
157         :param int hosts_per_if: Number of hosts per if to create MAC/IPv4
158                                  addresses for.
159         """
160         c = hosts_per_if
161         assert not cls.hosts_by_pg_idx
162         for i in range(len(cls.pg_interfaces)):
163             pg_idx = cls.pg_interfaces[i].sw_if_index
164             cls.hosts_by_pg_idx[pg_idx] = [
165                 Host(
166                     "00:00:00:ff:%02x:%02x" % (pg_idx, j + 1),
167                     "172.17.1%02u.%u" % (pg_idx, j + 1),
168                 )
169                 for j in range(c)
170             ]
171
172     @classmethod
173     def bd_if_range(cls, b):
174         n = cls.ifs_per_bd
175         start = (b - 1) * n
176         return range(start, start + n)
177
178     def create_bd_and_mac_learn(self, count, start=1):
179         """
180         Create required number of bridge domains with MAC learning enabled,
181         put 3 l2-pg interfaces to every bridge domain and send MAC learning
182         packets.
183
184         :param int count: Number of bridge domains to be created.
185         :param int start: Starting number of the bridge domain ID.
186             (Default value = 1)
187         """
188         for b in range(start, start + count):
189             self.vapi.bridge_domain_add_del(bd_id=b)
190             self.logger.info("Bridge domain ID %d created" % b)
191             if self.bd_list.count(b) == 0:
192                 self.bd_list.append(b)
193             if self.bd_deleted_list.count(b) == 1:
194                 self.bd_deleted_list.remove(b)
195             for j in self.bd_if_range(b):
196                 pg_if = self.pg_interfaces[j]
197                 self.vapi.sw_interface_set_l2_bridge(
198                     rx_sw_if_index=pg_if.sw_if_index, bd_id=b
199                 )
200                 self.logger.info(
201                     "pg-interface %s added to bridge domain ID %d" % (pg_if.name, b)
202                 )
203                 self.pg_in_bd.append(pg_if)
204                 hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
205                 packets = [
206                     Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac) for host in hosts
207                 ]
208                 pg_if.add_stream(packets)
209         self.logger.info("Sending broadcast eth frames for MAC learning")
210         self.pg_start()
211         self.logger.info(self.vapi.ppcli("show bridge-domain"))
212         self.logger.info(self.vapi.ppcli("show l2fib"))
213
214     def delete_bd(self, count, start=1):
215         """
216         Delete required number of bridge domains.
217
218         :param int count: Number of bridge domains to be created.
219         :param int start: Starting number of the bridge domain ID.
220             (Default value = 1)
221         """
222         for b in range(start, start + count):
223             for j in self.bd_if_range(b):
224                 pg_if = self.pg_interfaces[j]
225                 self.vapi.sw_interface_set_l2_bridge(
226                     rx_sw_if_index=pg_if.sw_if_index, bd_id=b, enable=0
227                 )
228                 self.pg_in_bd.remove(pg_if)
229             self.vapi.bridge_domain_add_del(bd_id=b, is_add=0)
230             self.bd_list.remove(b)
231             self.bd_deleted_list.append(b)
232             self.logger.info("Bridge domain ID %d deleted" % b)
233
234     def create_stream(self, src_if):
235         """
236         Create input packet stream for defined interface using hosts list.
237
238         :param object src_if: Interface to create packet stream for.
239         :param list packet_sizes: List of required packet sizes.
240         :return: Stream of packets.
241         """
242         packet_sizes = self.pg_if_packet_sizes
243         pkts = []
244         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
245         for dst_if in self.flows[src_if]:
246             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
247             for dst_host in dst_hosts:
248                 pkt_info = self.create_packet_info(src_if, dst_if)
249                 payload = self.info_to_payload(pkt_info)
250                 src_host = random.choice(src_hosts)
251                 p = (
252                     Ether(dst=dst_host.mac, src=src_host.mac)
253                     / IP(src=src_host.ip4, dst=dst_host.ip4)
254                     / UDP(sport=1234, dport=1234)
255                     / Raw(payload)
256                 )
257                 pkt_info.data = p.copy()
258                 size = random.choice(packet_sizes)
259                 self.extend_packet(p, size)
260                 pkts.append(p)
261         self.logger.debug(
262             "Input stream created for port %s. Length: %u pkt(s)"
263             % (src_if.name, len(pkts))
264         )
265         return pkts
266
267     def verify_capture(self, dst_if):
268         """
269         Verify captured input packet stream for defined interface.
270
271         :param object dst_if: Interface to verify captured packet stream for.
272         """
273         last_info = dict()
274         for i in self.flows[dst_if]:
275             last_info[i.sw_if_index] = None
276         dst = dst_if.sw_if_index
277         for packet in dst_if.get_capture():
278             try:
279                 ip = packet[IP]
280                 udp = packet[UDP]
281                 info = self.payload_to_info(packet[Raw])
282                 self.assertEqual(info.dst, dst)
283                 self.logger.debug(
284                     "Got packet on port %s: src=%u (id=%u)"
285                     % (dst_if.name, info.src, info.index)
286                 )
287                 last_info[info.src] = self.get_next_packet_info_for_interface2(
288                     info.src, dst, last_info[info.src]
289                 )
290                 pkt_info = last_info[info.src]
291                 self.assertTrue(pkt_info is not None)
292                 self.assertEqual(info.index, pkt_info.index)
293                 # Check standard fields against saved data in pkt
294                 saved = pkt_info.data
295                 self.assertEqual(ip.src, saved[IP].src)
296                 self.assertEqual(ip.dst, saved[IP].dst)
297                 self.assertEqual(udp.sport, saved[UDP].sport)
298                 self.assertEqual(udp.dport, saved[UDP].dport)
299             except:
300                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
301                 raise
302         s = ""
303         remaining = 0
304         for src in self.flows[dst_if]:
305             remaining_packet = self.get_next_packet_info_for_interface2(
306                 src.sw_if_index, dst, last_info[src.sw_if_index]
307             )
308             if remaining_packet is None:
309                 s += "Port %u: Packet expected from source %u didn't arrive\n" % (
310                     dst,
311                     src.sw_if_index,
312                 )
313                 remaining += 1
314             self.assertNotEqual(0, remaining, s)
315
316     def set_bd_flags(self, bd_id, **args):
317         """
318         Enable/disable defined feature(s) of the bridge domain.
319
320         :param int bd_id: Bridge domain ID.
321         :param list args: List of feature/status pairs. Allowed features: \
322         learn, forward, flood, uu_flood and arp_term. Status False means \
323         disable, status True means enable the feature.
324         :raise: ValueError in case of unknown feature in the input.
325         """
326         for flag in args:
327             if flag == "learn":
328                 feature_bitmap = 1 << 0
329             elif flag == "forward":
330                 feature_bitmap = 1 << 1
331             elif flag == "flood":
332                 feature_bitmap = 1 << 2
333             elif flag == "uu_flood":
334                 feature_bitmap = 1 << 3
335             elif flag == "arp_term":
336                 feature_bitmap = 1 << 4
337             else:
338                 raise ValueError("Unknown feature used: %s" % flag)
339             is_set = 1 if args[flag] else 0
340             self.vapi.bridge_flags(bd_id=bd_id, is_set=is_set, flags=feature_bitmap)
341         self.logger.info("Bridge domain ID %d updated" % bd_id)
342
343     def verify_bd(self, bd_id, **args):
344         """
345         Check if the bridge domain is configured and verify expected status
346         of listed features.
347
348         :param int bd_id: Bridge domain ID.
349         :param list args: List of feature/status pairs. Allowed features: \
350         learn, forward, flood, uu_flood and arp_term. Status False means \
351         disable, status True means enable the feature.
352         :return: 1 if bridge domain is configured, otherwise return 0.
353         :raise: ValueError in case of unknown feature in the input.
354         """
355         bd_dump = self.vapi.bridge_domain_dump(bd_id)
356         if len(bd_dump) == 0:
357             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
358             return 0
359         else:
360             bd_dump = bd_dump[0]
361             if len(args) > 0:
362                 for flag in args:
363                     expected_status = 1 if args[flag] else 0
364                     if flag == "learn":
365                         flag_status = bd_dump[6]
366                     elif flag == "forward":
367                         flag_status = bd_dump[5]
368                     elif flag == "flood":
369                         flag_status = bd_dump[3]
370                     elif flag == "uu_flood":
371                         flag_status = bd_dump[4]
372                     elif flag == "arp_term":
373                         flag_status = bd_dump[7]
374                     else:
375                         raise ValueError("Unknown feature used: %s" % flag)
376                     self.assertEqual(expected_status, flag_status)
377             return 1
378
379     def run_verify_test(self):
380         """
381         Create packet streams for all configured l2-pg interfaces, send all \
382         prepared packet streams and verify that:
383             - all packets received correctly on all pg-l2 interfaces assigned
384               to bridge domains
385             - no packet received on all pg-l2 interfaces not assigned to
386               bridge domains
387
388         :raise RuntimeError: if no packet captured on l2-pg interface assigned
389                              to the bridge domain or if any packet is captured
390                              on l2-pg interface not assigned to the bridge
391                              domain.
392         """
393         # Test
394         # Create incoming packet streams for packet-generator interfaces
395         # for pg_if in self.pg_interfaces:
396         assert len(self._packet_count_for_dst_if_idx) == 0
397         for pg_if in self.pg_in_bd:
398             pkts = self.create_stream(pg_if)
399             pg_if.add_stream(pkts)
400
401         # Enable packet capture and start packet sending
402         self.pg_enable_capture(self.pg_in_bd)
403         self.pg_start()
404
405         # Verify
406         # Verify outgoing packet streams per packet-generator interface
407         for pg_if in self.pg_in_bd:
408             self.verify_capture(pg_if)
409
410     def test_l2bd_inst_01(self):
411         """L2BD Multi-instance test 1 - create 5 BDs"""
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         self.delete_bd(5)
425
426     def test_l2bd_inst_02(self):
427         """L2BD Multi-instance test 2 - update data of 5 BDs"""
428         # Config 2
429         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
430         self.create_bd_and_mac_learn(5)
431         self.set_bd_flags(
432             self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
433         )
434         self.set_bd_flags(self.bd_list[1], forward=False)
435         self.set_bd_flags(self.bd_list[2], flood=False)
436         self.set_bd_flags(self.bd_list[3], uu_flood=False)
437         self.set_bd_flags(self.bd_list[4], learn=False)
438
439         # Verify 2
440         # Skipping check of uu_flood as it is not returned by
441         # bridge_domain_dump api command
442         self.verify_bd(
443             self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
444         )
445         self.verify_bd(
446             self.bd_list[1], learn=True, forward=False, flood=True, uu_flood=True
447         )
448         self.verify_bd(
449             self.bd_list[2], learn=True, forward=True, flood=False, uu_flood=True
450         )
451         self.verify_bd(
452             self.bd_list[3], learn=True, forward=True, flood=True, uu_flood=False
453         )
454         self.verify_bd(
455             self.bd_list[4], learn=False, forward=True, flood=True, uu_flood=True
456         )
457         self.delete_bd(5)
458
459     def test_l2bd_inst_03(self):
460         """L2BD Multi-instance test 3 - delete 2 BDs"""
461         # Config 3
462         # Delete 2 BDs
463         self.create_bd_and_mac_learn(5)
464         self.delete_bd(2)
465
466         # Verify 3
467         for bd_id in self.bd_deleted_list:
468             self.assertEqual(self.verify_bd(bd_id), 0)
469         for bd_id in self.bd_list:
470             self.assertEqual(self.verify_bd(bd_id), 1)
471
472         # Test 3
473         self.run_verify_test()
474         self.delete_bd(3, 3)
475
476     def test_l2bd_inst_04(self):
477         """L2BD Multi-instance test 4 - add 2 BDs"""
478         # Config 4
479         # Create 5 BDs, put interfaces to these BDs and send MAC learning
480         # packets
481         self.create_bd_and_mac_learn(2)
482
483         # Verify 4
484         for bd_id in self.bd_list:
485             self.assertEqual(self.verify_bd(bd_id), 1)
486
487         # Test 4
488         # self.vapi.cli("clear trace")
489         self.run_verify_test()
490         self.delete_bd(2)
491
492     def test_l2bd_inst_05(self):
493         """L2BD Multi-instance test 5 - delete 5 BDs"""
494         # Config 5
495         # Delete 5 BDs
496         self.create_bd_and_mac_learn(5)
497         self.delete_bd(5)
498
499         # Verify 5
500         for bd_id in self.bd_deleted_list:
501             self.assertEqual(self.verify_bd(bd_id), 0)
502         for bd_id in self.bd_list:
503             self.assertEqual(self.verify_bd(bd_id), 1)
504
505
506 if __name__ == "__main__":
507     unittest.main(testRunner=VppTestRunner)