make test: fix missing log/packet messages
[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     - more then 1 host per pg-l2 interface in configuration with 15 l2-pg \
8      interfaces leads to 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
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(15)
109
110             # Packet sizes
111             cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
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,
311             - forward,
312             - flood,
313             - uu_flood and
314             - arp_term
315             Status False means disable, status True means enable the feature.
316         :raise: ValueError in case of unknown feature in the input.
317         """
318         for flag in args:
319             if flag == "learn":
320                 feature_bitmap = 1 << 0
321             elif flag == "forward":
322                 feature_bitmap = 1 << 1
323             elif flag == "flood":
324                 feature_bitmap = 1 << 2
325             elif flag == "uu_flood":
326                 feature_bitmap = 1 << 3
327             elif flag == "arp_term":
328                 feature_bitmap = 1 << 4
329             else:
330                 raise ValueError("Unknown feature used: %s" % flag)
331             is_set = 1 if args[flag] else 0
332             self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
333         self.logger.info("Bridge domain ID %d updated" % bd_id)
334
335     def verify_bd(self, bd_id, **args):
336         """
337         Check if the bridge domain is configured and verify expected status
338         of listed features.
339
340         :param int bd_id: Bridge domain ID.
341         :param list args: List of feature/status pairs. Allowed features:
342             - learn,
343             - forward,
344             - flood,
345             - uu_flood and
346             - arp_term
347             Status False means disable, status True means enable the feature.
348         :return: 1 if bridge domain is configured, otherwise return 0.
349         :raise: ValueError in case of unknown feature in the input.
350         """
351         bd_dump = self.vapi.bridge_domain_dump(bd_id)
352         if len(bd_dump) == 0:
353             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
354             return 0
355         else:
356             bd_dump = bd_dump[0]
357             if len(args) > 0:
358                 for flag in args:
359                     expected_status = 1 if args[flag] else 0
360                     if flag == "learn":
361                         flag_status = bd_dump[6]
362                     elif flag == "forward":
363                         flag_status = bd_dump[5]
364                     elif flag == "flood":
365                         flag_status = bd_dump[3]
366                     elif flag == "uu_flood":
367                         flag_status = bd_dump[4]
368                     elif flag == "arp_term":
369                         flag_status = bd_dump[7]
370                     else:
371                         raise ValueError("Unknown feature used: %s" % flag)
372                     self.assertEqual(expected_status, flag_status)
373             return 1
374
375     def run_verify_test(self):
376         """
377         Create packet streams for all configured l2-pg interfaces, send all
378         prepared packet streams and verify that:
379             - all packets received correctly on all pg-l2 interfaces assigned to
380                 bridge domains
381             - no packet received on all pg-l2 interfaces not assigned to bridge
382                 domains
383
384         :raise: RuntimeError if no packet captured on l2-pg interface assigned
385             to the bridge domain or if any packet is captured on l2-pg interface
386             not assigned to the bridge domain.
387         """
388         # Test
389         # Create incoming packet streams for packet-generator interfaces
390         for pg_if in self.pg_interfaces:
391             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
392             pg_if.add_stream(pkts)
393
394         # Enable packet capture and start packet sending
395         self.pg_enable_capture(self.pg_interfaces)
396         self.pg_start()
397
398         # Verify
399         # Verify outgoing packet streams per packet-generator interface
400         for pg_if in self.pg_interfaces:
401             capture = pg_if.get_capture()
402             if pg_if in self.pg_in_bd:
403                 if len(capture) == 0:
404                     raise RuntimeError("Interface %s is in BD but the capture "
405                                        "is empty!" % pg_if.name)
406                 self.verify_capture(pg_if, capture)
407             elif pg_if in self.pg_not_in_bd:
408                 try:
409                     self.assertEqual(len(capture), 0)
410                 except AssertionError:
411                     raise RuntimeError("Interface %s is not in BD but "
412                                        "the capture is not empty!" % pg_if.name)
413             else:
414                 self.logger.error("Unknown interface: %s" % pg_if.name)
415
416     def test_l2bd_inst_01(self):
417         """ L2BD Multi-instance test 1 - create 5 BDs
418         """
419         # Config 1
420         # Create 5 BDs, put interfaces to these BDs and send MAC learning
421         # packets
422         self.create_bd_and_mac_learn(5)
423
424         # Verify 1
425         for bd_id in self.bd_list:
426             self.assertEqual(self.verify_bd(bd_id), 1)
427
428         # Test 1
429         # self.vapi.cli("clear trace")
430         self.run_verify_test()
431
432     def test_l2bd_inst_02(self):
433         """ L2BD Multi-instance test 2 - update data of 5 BDs
434         """
435         # Config 2
436         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
437         self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
438                           flood=False, uu_flood=False)
439         self.set_bd_flags(self.bd_list[1], forward=False)
440         self.set_bd_flags(self.bd_list[2], flood=False)
441         self.set_bd_flags(self.bd_list[3], uu_flood=False)
442         self.set_bd_flags(self.bd_list[4], learn=False)
443
444         # Verify 2
445         # Skipping check of uu_flood as it is not returned by
446         # bridge_domain_dump api command
447         self.verify_bd(self.bd_list[0], learn=False, forward=False,
448                        flood=False, uu_flood=False)
449         self.verify_bd(self.bd_list[1], learn=True, forward=False,
450                        flood=True, uu_flood=True)
451         self.verify_bd(self.bd_list[2], learn=True, forward=True,
452                        flood=False, uu_flood=True)
453         self.verify_bd(self.bd_list[3], learn=True, forward=True,
454                        flood=True, uu_flood=False)
455         self.verify_bd(self.bd_list[4], learn=False, forward=True,
456                        flood=True, uu_flood=True)
457
458     def test_l2bd_inst_03(self):
459         """ L2BD Multi-instance 3 - delete 2 BDs
460         """
461         # Config 3
462         # Delete 2 BDs
463         self.delete_bd(2)
464
465         # Verify 3
466         for bd_id in self.bd_deleted_list:
467             self.assertEqual(self.verify_bd(bd_id), 0)
468         for bd_id in self.bd_list:
469             self.assertEqual(self.verify_bd(bd_id), 1)
470
471         # Test 3
472         self.run_verify_test()
473
474     def test_l2bd_inst_04(self):
475         """ L2BD Multi-instance test 4 - add 2 BDs
476         """
477         # Config 4
478         # Create 5 BDs, put interfaces to these BDs and send MAC learning
479         # packets
480         self.create_bd_and_mac_learn(2)
481
482         # Verify 4
483         for bd_id in self.bd_list:
484             self.assertEqual(self.verify_bd(bd_id), 1)
485
486         # Test 4
487         # self.vapi.cli("clear trace")
488         self.run_verify_test()
489
490     def test_l2bd_inst_05(self):
491         """ L2BD Multi-instance 5 - delete 5 BDs
492         """
493         # Config 5
494         # Delete 5 BDs
495         self.delete_bd(5)
496
497         # Verify 5
498         for bd_id in self.bd_deleted_list:
499             self.assertEqual(self.verify_bd(bd_id), 0)
500         for bd_id in self.bd_list:
501             self.assertEqual(self.verify_bd(bd_id), 1)
502
503
504 if __name__ == '__main__':
505     unittest.main(testRunner=VppTestRunner)