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