make test: improve handling of packet captures
[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, ppp
74
75
76 @unittest.skip("Crashes VPP")
77 class TestL2bdMultiInst(VppTestCase):
78     """ L2BD Multi-instance Test Case """
79
80     @classmethod
81     def setUpClass(cls):
82         """
83         Perform standard class setup (defined by class method setUpClass in
84         class VppTestCase) before running the test case, set test case related
85         variables and configure VPP.
86         """
87         super(TestL2bdMultiInst, cls).setUpClass()
88
89         try:
90             # Create pg interfaces
91             cls.create_pg_interfaces(range(15))
92
93             # Packet flows mapping pg0 -> pg1, pg2 etc.
94             cls.flows = dict()
95             for i in range(0, len(cls.pg_interfaces), 3):
96                 cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + 1],
97                                                    cls.pg_interfaces[i + 2]]
98                 cls.flows[cls.pg_interfaces[i + 1]] = [cls.pg_interfaces[i],
99                                                        cls.pg_interfaces[i + 2]]
100                 cls.flows[cls.pg_interfaces[i + 2]] = [cls.pg_interfaces[i],
101                                                        cls.pg_interfaces[i + 1]]
102
103             # Mapping between packet-generator index and lists of test hosts
104             cls.hosts_by_pg_idx = dict()
105             for pg_if in cls.pg_interfaces:
106                 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
107
108             # Create test host entries
109             cls.create_hosts(75)
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             # Create list of pg_interfaces not in BDs
128             cls.pg_not_in_bd = list()
129             for pg_if in cls.pg_interfaces:
130                 cls.pg_not_in_bd.append(pg_if)
131
132         except Exception:
133             super(TestL2bdMultiInst, cls).tearDownClass()
134             raise
135
136     def setUp(self):
137         """
138         Clear trace and packet infos before running each test.
139         """
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, 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(src_if, dst_if)
246                 payload = self.info_to_payload(pkt_info)
247                 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
248                      IP(src=src_host.ip4, dst=dst_host.ip4) /
249                      UDP(sport=1234, dport=1234) /
250                      Raw(payload))
251                 pkt_info.data = p.copy()
252                 size = random.choice(packet_sizes)
253                 self.extend_packet(p, size)
254                 pkts.append(p)
255         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
256                           % (src_if.name, len(pkts)))
257         return pkts
258
259     def verify_capture(self, pg_if, capture):
260         """
261         Verify captured input packet stream for defined interface.
262
263         :param object pg_if: Interface to verify captured packet stream for.
264         :param list capture: Captured packet stream.
265         """
266         last_info = dict()
267         for i in self.pg_interfaces:
268             last_info[i.sw_if_index] = None
269         dst_sw_if_index = pg_if.sw_if_index
270         for packet in capture:
271             payload_info = self.payload_to_info(str(packet[Raw]))
272             try:
273                 ip = packet[IP]
274                 udp = packet[UDP]
275                 packet_index = payload_info.index
276                 self.assertEqual(payload_info.dst, dst_sw_if_index)
277                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
278                                   (pg_if.name, payload_info.src, packet_index))
279                 next_info = self.get_next_packet_info_for_interface2(
280                     payload_info.src, dst_sw_if_index,
281                     last_info[payload_info.src])
282                 last_info[payload_info.src] = next_info
283                 self.assertTrue(next_info is not None)
284                 self.assertEqual(packet_index, next_info.index)
285                 saved_packet = next_info.data
286                 # Check standard fields
287                 self.assertEqual(ip.src, saved_packet[IP].src)
288                 self.assertEqual(ip.dst, saved_packet[IP].dst)
289                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
290                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
291             except:
292                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
293                 raise
294         for i in self.pg_interfaces:
295             remaining_packet = self.get_next_packet_info_for_interface2(
296                 i, dst_sw_if_index, last_info[i.sw_if_index])
297             self.assertTrue(
298                 remaining_packet is None,
299                 "Port %u: Packet expected from source %u didn't arrive" %
300                 (dst_sw_if_index, i.sw_if_index))
301
302     def set_bd_flags(self, bd_id, **args):
303         """
304         Enable/disable defined feature(s) of the bridge domain.
305
306         :param int bd_id: Bridge domain ID.
307         :param list args: List of feature/status pairs. Allowed features: \
308         learn, forward, flood, uu_flood and arp_term. Status False means \
309         disable, status True means enable the feature.
310         :raise: ValueError in case of unknown feature in the input.
311         """
312         for flag in args:
313             if flag == "learn":
314                 feature_bitmap = 1 << 0
315             elif flag == "forward":
316                 feature_bitmap = 1 << 1
317             elif flag == "flood":
318                 feature_bitmap = 1 << 2
319             elif flag == "uu_flood":
320                 feature_bitmap = 1 << 3
321             elif flag == "arp_term":
322                 feature_bitmap = 1 << 4
323             else:
324                 raise ValueError("Unknown feature used: %s" % flag)
325             is_set = 1 if args[flag] else 0
326             self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
327         self.logger.info("Bridge domain ID %d updated" % bd_id)
328
329     def verify_bd(self, bd_id, **args):
330         """
331         Check if the bridge domain is configured and verify expected status
332         of listed features.
333
334         :param int bd_id: Bridge domain ID.
335         :param list args: List of feature/status pairs. Allowed features: \
336         learn, forward, flood, uu_flood and arp_term. Status False means \
337         disable, status True means enable the feature.
338         :return: 1 if bridge domain is configured, otherwise return 0.
339         :raise: ValueError in case of unknown feature in the input.
340         """
341         bd_dump = self.vapi.bridge_domain_dump(bd_id)
342         if len(bd_dump) == 0:
343             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
344             return 0
345         else:
346             bd_dump = bd_dump[0]
347             if len(args) > 0:
348                 for flag in args:
349                     expected_status = 1 if args[flag] else 0
350                     if flag == "learn":
351                         flag_status = bd_dump[6]
352                     elif flag == "forward":
353                         flag_status = bd_dump[5]
354                     elif flag == "flood":
355                         flag_status = bd_dump[3]
356                     elif flag == "uu_flood":
357                         flag_status = bd_dump[4]
358                     elif flag == "arp_term":
359                         flag_status = bd_dump[7]
360                     else:
361                         raise ValueError("Unknown feature used: %s" % flag)
362                     self.assertEqual(expected_status, flag_status)
363             return 1
364
365     def run_verify_test(self):
366         """
367         Create packet streams for all configured l2-pg interfaces, send all \
368         prepared packet streams and verify that:
369             - all packets received correctly on all pg-l2 interfaces assigned
370               to bridge domains
371             - no packet received on all pg-l2 interfaces not assigned to
372               bridge domains
373
374         :raise RuntimeError: if no packet captured on l2-pg interface assigned
375                              to the bridge domain or if any packet is captured
376                              on l2-pg interface not assigned to the bridge
377                              domain.
378         """
379         # Test
380         # Create incoming packet streams for packet-generator interfaces
381         for pg_if in self.pg_interfaces:
382             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
383             pg_if.add_stream(pkts)
384
385         # Enable packet capture and start packet sending
386         self.pg_enable_capture(self.pg_interfaces)
387         self.pg_start()
388
389         # Verify
390         # Verify outgoing packet streams per packet-generator interface
391         for pg_if in self.pg_interfaces:
392             capture = pg_if.get_capture()
393             if pg_if in self.pg_in_bd:
394                 self.verify_capture(pg_if, capture)
395             elif pg_if not in self.pg_not_in_bd:
396                 self.logger.error("Unknown interface: %s" % pg_if.name)
397
398     def test_l2bd_inst_01(self):
399         """ L2BD Multi-instance test 1 - create 5 BDs
400         """
401         # Config 1
402         # Create 5 BDs, put interfaces to these BDs and send MAC learning
403         # packets
404         self.create_bd_and_mac_learn(5)
405
406         # Verify 1
407         for bd_id in self.bd_list:
408             self.assertEqual(self.verify_bd(bd_id), 1)
409
410         # Test 1
411         # self.vapi.cli("clear trace")
412         self.run_verify_test()
413
414     def test_l2bd_inst_02(self):
415         """ L2BD Multi-instance test 2 - update data of 5 BDs
416         """
417         # Config 2
418         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
419         self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
420                           flood=False, uu_flood=False)
421         self.set_bd_flags(self.bd_list[1], forward=False)
422         self.set_bd_flags(self.bd_list[2], flood=False)
423         self.set_bd_flags(self.bd_list[3], uu_flood=False)
424         self.set_bd_flags(self.bd_list[4], learn=False)
425
426         # Verify 2
427         # Skipping check of uu_flood as it is not returned by
428         # bridge_domain_dump api command
429         self.verify_bd(self.bd_list[0], learn=False, forward=False,
430                        flood=False, uu_flood=False)
431         self.verify_bd(self.bd_list[1], learn=True, forward=False,
432                        flood=True, uu_flood=True)
433         self.verify_bd(self.bd_list[2], learn=True, forward=True,
434                        flood=False, uu_flood=True)
435         self.verify_bd(self.bd_list[3], learn=True, forward=True,
436                        flood=True, uu_flood=False)
437         self.verify_bd(self.bd_list[4], learn=False, forward=True,
438                        flood=True, uu_flood=True)
439
440     def test_l2bd_inst_03(self):
441         """ L2BD Multi-instance 3 - delete 2 BDs
442         """
443         # Config 3
444         # Delete 2 BDs
445         self.delete_bd(2)
446
447         # Verify 3
448         for bd_id in self.bd_deleted_list:
449             self.assertEqual(self.verify_bd(bd_id), 0)
450         for bd_id in self.bd_list:
451             self.assertEqual(self.verify_bd(bd_id), 1)
452
453         # Test 3
454         self.run_verify_test()
455
456     def test_l2bd_inst_04(self):
457         """ L2BD Multi-instance test 4 - add 2 BDs
458         """
459         # Config 4
460         # Create 5 BDs, put interfaces to these BDs and send MAC learning
461         # packets
462         self.create_bd_and_mac_learn(2)
463
464         # Verify 4
465         for bd_id in self.bd_list:
466             self.assertEqual(self.verify_bd(bd_id), 1)
467
468         # Test 4
469         # self.vapi.cli("clear trace")
470         self.run_verify_test()
471
472     def test_l2bd_inst_05(self):
473         """ L2BD Multi-instance 5 - delete 5 BDs
474         """
475         # Config 5
476         # Delete 5 BDs
477         self.delete_bd(5)
478
479         # Verify 5
480         for bd_id in self.bd_deleted_list:
481             self.assertEqual(self.verify_bd(bd_id), 0)
482         for bd_id in self.bd_list:
483             self.assertEqual(self.verify_bd(bd_id), 1)
484
485
486 if __name__ == '__main__':
487     unittest.main(testRunner=VppTestRunner)