make test: Use VXLAN built in scapy 2.3.3
[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
74
75 @unittest.skip("Crashes VPP")
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             cls.create_pg_interfaces(range(15))
91
92             # Packet flows mapping pg0 -> pg1, pg2 etc.
93             cls.flows = dict()
94             for i in range(0, len(cls.pg_interfaces), 3):
95                 cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i+1],
96                                                    cls.pg_interfaces[i+2]]
97                 cls.flows[cls.pg_interfaces[i+1]] = [cls.pg_interfaces[i],
98                                                      cls.pg_interfaces[i+2]]
99                 cls.flows[cls.pg_interfaces[i+2]] = [cls.pg_interfaces[i],
100                                                      cls.pg_interfaces[i+1]]
101
102             # Mapping between packet-generator index and lists of test hosts
103             cls.hosts_by_pg_idx = dict()
104             for pg_if in cls.pg_interfaces:
105                 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
106
107             # Create test host entries
108             cls.create_hosts(75)
109
110             # Packet sizes - jumbo packet (9018 bytes) skipped
111             cls.pg_if_packet_sizes = [64, 512, 1518]
112
113             # Set up all interfaces
114             for i in cls.pg_interfaces:
115                 i.admin_up()
116
117             # Create list of BDs
118             cls.bd_list = list()
119
120             # Create list of deleted BDs
121             cls.bd_deleted_list = list()
122
123             # Create list of pg_interfaces in BDs
124             cls.pg_in_bd = list()
125
126             # Create list of pg_interfaces not in BDs
127             cls.pg_not_in_bd = list()
128             for pg_if in cls.pg_interfaces:
129                 cls.pg_not_in_bd.append(pg_if)
130
131         except Exception:
132             super(TestL2bdMultiInst, cls).tearDownClass()
133             raise
134
135     def setUp(self):
136         """
137         Clear trace and packet infos before running each test.
138         """
139         super(TestL2bdMultiInst, self).setUp()
140         self.packet_infos = {}
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, count):
153         """
154         Create required number of host MAC addresses and distribute them among
155         interfaces. Create host IPv4 address for every host MAC address.
156
157         :param int count: Number of hosts to create MAC/IPv4 addresses for.
158         """
159         n_int = len(cls.pg_interfaces)
160         macs_per_if = count / n_int
161         i = -1
162         for pg_if in cls.pg_interfaces:
163             i += 1
164             start_nr = macs_per_if * i
165             end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
166             hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
167             for j in range(start_nr, end_nr):
168                 host = Host(
169                     "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
170                     "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
171                 hosts.append(host)
172
173     def create_bd_and_mac_learn(self, count, start=1):
174         """
175         Create required number of bridge domains with MAC learning enabled, put
176         3 l2-pg interfaces to every bridge domain and send MAC learning packets.
177
178         :param int count: Number of bridge domains to be created.
179         :param int start: Starting number of the bridge domain ID.
180             (Default value = 1)
181         """
182         for i in range(count):
183             bd_id = i + start
184             self.vapi.bridge_domain_add_del(bd_id=bd_id)
185             self.logger.info("Bridge domain ID %d created" % bd_id)
186             if self.bd_list.count(bd_id) == 0:
187                 self.bd_list.append(bd_id)
188             if self.bd_deleted_list.count(bd_id) == 1:
189                 self.bd_deleted_list.remove(bd_id)
190             for j in range(3):
191                 pg_if = self.pg_interfaces[(i+start-1)*3+j]
192                 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
193                                                      bd_id=bd_id)
194                 self.logger.info("pg-interface %s added to bridge domain ID %d"
195                                  % (pg_if.name, bd_id))
196                 self.pg_in_bd.append(pg_if)
197                 self.pg_not_in_bd.remove(pg_if)
198                 packets = []
199                 for host in self.hosts_by_pg_idx[pg_if.sw_if_index]:
200                     packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
201                     packets.append(packet)
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 i in range(count):
217             bd_id = i + start
218             self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=0)
219             if self.bd_list.count(bd_id) == 1:
220                 self.bd_list.remove(bd_id)
221             if self.bd_deleted_list.count(bd_id) == 0:
222                 self.bd_deleted_list.append(bd_id)
223             for j in range(3):
224                 pg_if = self.pg_interfaces[(i+start-1)*3+j]
225                 self.pg_in_bd.remove(pg_if)
226                 self.pg_not_in_bd.append(pg_if)
227             self.logger.info("Bridge domain ID %d deleted" % bd_id)
228
229     def create_stream(self, src_if, packet_sizes):
230         """
231         Create input packet stream for defined interface using hosts list.
232
233         :param object src_if: Interface to create packet stream for.
234         :param list packet_sizes: List of required packet sizes.
235         :return: Stream of packets.
236         """
237         pkts = []
238         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
239         for dst_if in self.flows[src_if]:
240             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
241             n_int = len(dst_hosts)
242             for i in range(0, n_int):
243                 dst_host = dst_hosts[i]
244                 src_host = random.choice(src_hosts)
245                 pkt_info = self.create_packet_info(
246                     src_if.sw_if_index, dst_if.sw_if_index)
247                 payload = self.info_to_payload(pkt_info)
248                 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
249                      IP(src=src_host.ip4, dst=dst_host.ip4) /
250                      UDP(sport=1234, dport=1234) /
251                      Raw(payload))
252                 pkt_info.data = p.copy()
253                 size = random.choice(packet_sizes)
254                 self.extend_packet(p, size)
255                 pkts.append(p)
256         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
257                           % (src_if.name, len(pkts)))
258         return pkts
259
260     def verify_capture(self, pg_if, capture):
261         """
262         Verify captured input packet stream for defined interface.
263
264         :param object pg_if: Interface to verify captured packet stream for.
265         :param list capture: Captured packet stream.
266         """
267         last_info = dict()
268         for i in self.pg_interfaces:
269             last_info[i.sw_if_index] = None
270         dst_sw_if_index = pg_if.sw_if_index
271         for packet in capture:
272             payload_info = self.payload_to_info(str(packet[Raw]))
273             try:
274                 ip = packet[IP]
275                 udp = packet[UDP]
276                 packet_index = payload_info.index
277                 self.assertEqual(payload_info.dst, dst_sw_if_index)
278                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
279                                   (pg_if.name, payload_info.src, packet_index))
280                 next_info = self.get_next_packet_info_for_interface2(
281                     payload_info.src, dst_sw_if_index,
282                     last_info[payload_info.src])
283                 last_info[payload_info.src] = next_info
284                 self.assertTrue(next_info is not None)
285                 self.assertEqual(packet_index, next_info.index)
286                 saved_packet = next_info.data
287                 # Check standard fields
288                 self.assertEqual(ip.src, saved_packet[IP].src)
289                 self.assertEqual(ip.dst, saved_packet[IP].dst)
290                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
291                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
292             except:
293                 self.logger.error("Unexpected or invalid packet:")
294                 self.logger.error(packet.show())
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)