MAP: Convert from DPO to input feature.
[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(pg_if.sw_if_index,
191                                                      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(pg_if.sw_if_index,
216                                                      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(str(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, is_set, feature_bitmap)
320         self.logger.info("Bridge domain ID %d updated" % bd_id)
321
322     def verify_bd(self, bd_id, **args):
323         """
324         Check if the bridge domain is configured and verify expected status
325         of listed features.
326
327         :param int bd_id: Bridge domain ID.
328         :param list args: List of feature/status pairs. Allowed features: \
329         learn, forward, flood, uu_flood and arp_term. Status False means \
330         disable, status True means enable the feature.
331         :return: 1 if bridge domain is configured, otherwise return 0.
332         :raise: ValueError in case of unknown feature in the input.
333         """
334         bd_dump = self.vapi.bridge_domain_dump(bd_id)
335         if len(bd_dump) == 0:
336             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
337             return 0
338         else:
339             bd_dump = bd_dump[0]
340             if len(args) > 0:
341                 for flag in args:
342                     expected_status = 1 if args[flag] else 0
343                     if flag == "learn":
344                         flag_status = bd_dump[6]
345                     elif flag == "forward":
346                         flag_status = bd_dump[5]
347                     elif flag == "flood":
348                         flag_status = bd_dump[3]
349                     elif flag == "uu_flood":
350                         flag_status = bd_dump[4]
351                     elif flag == "arp_term":
352                         flag_status = bd_dump[7]
353                     else:
354                         raise ValueError("Unknown feature used: %s" % flag)
355                     self.assertEqual(expected_status, flag_status)
356             return 1
357
358     def run_verify_test(self):
359         """
360         Create packet streams for all configured l2-pg interfaces, send all \
361         prepared packet streams and verify that:
362             - all packets received correctly on all pg-l2 interfaces assigned
363               to bridge domains
364             - no packet received on all pg-l2 interfaces not assigned to
365               bridge domains
366
367         :raise RuntimeError: if no packet captured on l2-pg interface assigned
368                              to the bridge domain or if any packet is captured
369                              on l2-pg interface not assigned to the bridge
370                              domain.
371         """
372         # Test
373         # Create incoming packet streams for packet-generator interfaces
374         # for pg_if in self.pg_interfaces:
375         assert(len(self._packet_count_for_dst_if_idx) == 0)
376         for pg_if in self.pg_in_bd:
377             pkts = self.create_stream(pg_if)
378             pg_if.add_stream(pkts)
379
380         # Enable packet capture and start packet sending
381         self.pg_enable_capture(self.pg_in_bd)
382         self.pg_start()
383
384         # Verify
385         # Verify outgoing packet streams per packet-generator interface
386         for pg_if in self.pg_in_bd:
387             self.verify_capture(pg_if)
388
389     def test_l2bd_inst_01(self):
390         """ L2BD Multi-instance test 1 - create 5 BDs
391         """
392         # Config 1
393         # Create 5 BDs, put interfaces to these BDs and send MAC learning
394         # packets
395         self.create_bd_and_mac_learn(5)
396
397         # Verify 1
398         for bd_id in self.bd_list:
399             self.assertEqual(self.verify_bd(bd_id), 1)
400
401         # Test 1
402         # self.vapi.cli("clear trace")
403         self.run_verify_test()
404
405     def test_l2bd_inst_02(self):
406         """ L2BD Multi-instance test 2 - update data of 5 BDs
407         """
408         # Config 2
409         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
410         self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
411                           flood=False, uu_flood=False)
412         self.set_bd_flags(self.bd_list[1], forward=False)
413         self.set_bd_flags(self.bd_list[2], flood=False)
414         self.set_bd_flags(self.bd_list[3], uu_flood=False)
415         self.set_bd_flags(self.bd_list[4], learn=False)
416
417         # Verify 2
418         # Skipping check of uu_flood as it is not returned by
419         # bridge_domain_dump api command
420         self.verify_bd(self.bd_list[0], learn=False, forward=False,
421                        flood=False, uu_flood=False)
422         self.verify_bd(self.bd_list[1], learn=True, forward=False,
423                        flood=True, uu_flood=True)
424         self.verify_bd(self.bd_list[2], learn=True, forward=True,
425                        flood=False, uu_flood=True)
426         self.verify_bd(self.bd_list[3], learn=True, forward=True,
427                        flood=True, uu_flood=False)
428         self.verify_bd(self.bd_list[4], learn=False, forward=True,
429                        flood=True, uu_flood=True)
430
431     def test_l2bd_inst_03(self):
432         """ L2BD Multi-instance test 3 - delete 2 BDs
433         """
434         # Config 3
435         # Delete 2 BDs
436         self.delete_bd(2)
437
438         # Verify 3
439         for bd_id in self.bd_deleted_list:
440             self.assertEqual(self.verify_bd(bd_id), 0)
441         for bd_id in self.bd_list:
442             self.assertEqual(self.verify_bd(bd_id), 1)
443
444         # Test 3
445         self.run_verify_test()
446
447     def test_l2bd_inst_04(self):
448         """ L2BD Multi-instance test 4 - add 2 BDs
449         """
450         # Config 4
451         # Create 5 BDs, put interfaces to these BDs and send MAC learning
452         # packets
453         self.create_bd_and_mac_learn(2)
454
455         # Verify 4
456         for bd_id in self.bd_list:
457             self.assertEqual(self.verify_bd(bd_id), 1)
458
459         # Test 4
460         # self.vapi.cli("clear trace")
461         self.run_verify_test()
462
463     @unittest.skipUnless(running_extended_tests, "part of extended tests")
464     def test_l2bd_inst_05(self):
465         """ L2BD Multi-instance test 5 - delete 5 BDs
466         """
467         # Config 5
468         # Delete 5 BDs
469         self.delete_bd(5)
470
471         # Verify 5
472         for bd_id in self.bd_deleted_list:
473             self.assertEqual(self.verify_bd(bd_id), 0)
474         for bd_id in self.bd_list:
475             self.assertEqual(self.verify_bd(bd_id), 1)
476
477
478 if __name__ == '__main__':
479     unittest.main(testRunner=VppTestRunner)