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