make test: improve documentation and PEP8 compliance
[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]] = \
99                     [cls.pg_interfaces[i], cls.pg_interfaces[i + 2]]
100                 cls.flows[cls.pg_interfaces[i + 2]] = \
101                     [cls.pg_interfaces[i], 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,
176         put 3 l2-pg interfaces to every bridge domain and send MAC learning
177         packets.
178
179         :param int count: Number of bridge domains to be created.
180         :param int start: Starting number of the bridge domain ID.
181             (Default value = 1)
182         """
183         for i in range(count):
184             bd_id = i + start
185             self.vapi.bridge_domain_add_del(bd_id=bd_id)
186             self.logger.info("Bridge domain ID %d created" % bd_id)
187             if self.bd_list.count(bd_id) == 0:
188                 self.bd_list.append(bd_id)
189             if self.bd_deleted_list.count(bd_id) == 1:
190                 self.bd_deleted_list.remove(bd_id)
191             for j in range(3):
192                 pg_if = self.pg_interfaces[(i + start - 1) * 3 + j]
193                 self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
194                                                      bd_id=bd_id)
195                 self.logger.info("pg-interface %s added to bridge domain ID %d"
196                                  % (pg_if.name, bd_id))
197                 self.pg_in_bd.append(pg_if)
198                 self.pg_not_in_bd.remove(pg_if)
199                 packets = []
200                 for host in self.hosts_by_pg_idx[pg_if.sw_if_index]:
201                     packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
202                     packets.append(packet)
203                 pg_if.add_stream(packets)
204         self.logger.info("Sending broadcast eth frames for MAC learning")
205         self.pg_start()
206         self.logger.info(self.vapi.ppcli("show bridge-domain"))
207         self.logger.info(self.vapi.ppcli("show l2fib"))
208
209     def delete_bd(self, count, start=1):
210         """
211         Delete required number of bridge domains.
212
213         :param int count: Number of bridge domains to be created.
214         :param int start: Starting number of the bridge domain ID.
215             (Default value = 1)
216         """
217         for i in range(count):
218             bd_id = i + start
219             self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=0)
220             if self.bd_list.count(bd_id) == 1:
221                 self.bd_list.remove(bd_id)
222             if self.bd_deleted_list.count(bd_id) == 0:
223                 self.bd_deleted_list.append(bd_id)
224             for j in range(3):
225                 pg_if = self.pg_interfaces[(i + start - 1) * 3 + j]
226                 self.pg_in_bd.remove(pg_if)
227                 self.pg_not_in_bd.append(pg_if)
228             self.logger.info("Bridge domain ID %d deleted" % bd_id)
229
230     def create_stream(self, src_if, packet_sizes):
231         """
232         Create input packet stream for defined interface using hosts list.
233
234         :param object src_if: Interface to create packet stream for.
235         :param list packet_sizes: List of required packet sizes.
236         :return: Stream of packets.
237         """
238         pkts = []
239         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
240         for dst_if in self.flows[src_if]:
241             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
242             n_int = len(dst_hosts)
243             for i in range(0, n_int):
244                 dst_host = dst_hosts[i]
245                 src_host = random.choice(src_hosts)
246                 pkt_info = self.create_packet_info(src_if, dst_if)
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(ppp("Unexpected or invalid packet:", packet))
294                 raise
295         for i in self.pg_interfaces:
296             remaining_packet = self.get_next_packet_info_for_interface2(
297                 i, dst_sw_if_index, last_info[i.sw_if_index])
298             self.assertTrue(
299                 remaining_packet is None,
300                 "Port %u: Packet expected from source %u didn't arrive" %
301                 (dst_sw_if_index, i.sw_if_index))
302
303     def set_bd_flags(self, bd_id, **args):
304         """
305         Enable/disable defined feature(s) of the bridge domain.
306
307         :param int bd_id: Bridge domain ID.
308         :param list args: List of feature/status pairs. Allowed features: \
309         learn, forward, flood, uu_flood and arp_term. Status False means \
310         disable, status True means enable the feature.
311         :raise: ValueError in case of unknown feature in the input.
312         """
313         for flag in args:
314             if flag == "learn":
315                 feature_bitmap = 1 << 0
316             elif flag == "forward":
317                 feature_bitmap = 1 << 1
318             elif flag == "flood":
319                 feature_bitmap = 1 << 2
320             elif flag == "uu_flood":
321                 feature_bitmap = 1 << 3
322             elif flag == "arp_term":
323                 feature_bitmap = 1 << 4
324             else:
325                 raise ValueError("Unknown feature used: %s" % flag)
326             is_set = 1 if args[flag] else 0
327             self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
328         self.logger.info("Bridge domain ID %d updated" % bd_id)
329
330     def verify_bd(self, bd_id, **args):
331         """
332         Check if the bridge domain is configured and verify expected status
333         of listed features.
334
335         :param int bd_id: Bridge domain ID.
336         :param list args: List of feature/status pairs. Allowed features: \
337         learn, forward, flood, uu_flood and arp_term. Status False means \
338         disable, status True means enable the feature.
339         :return: 1 if bridge domain is configured, otherwise return 0.
340         :raise: ValueError in case of unknown feature in the input.
341         """
342         bd_dump = self.vapi.bridge_domain_dump(bd_id)
343         if len(bd_dump) == 0:
344             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
345             return 0
346         else:
347             bd_dump = bd_dump[0]
348             if len(args) > 0:
349                 for flag in args:
350                     expected_status = 1 if args[flag] else 0
351                     if flag == "learn":
352                         flag_status = bd_dump[6]
353                     elif flag == "forward":
354                         flag_status = bd_dump[5]
355                     elif flag == "flood":
356                         flag_status = bd_dump[3]
357                     elif flag == "uu_flood":
358                         flag_status = bd_dump[4]
359                     elif flag == "arp_term":
360                         flag_status = bd_dump[7]
361                     else:
362                         raise ValueError("Unknown feature used: %s" % flag)
363                     self.assertEqual(expected_status, flag_status)
364             return 1
365
366     def run_verify_test(self):
367         """
368         Create packet streams for all configured l2-pg interfaces, send all \
369         prepared packet streams and verify that:
370             - all packets received correctly on all pg-l2 interfaces assigned
371               to bridge domains
372             - no packet received on all pg-l2 interfaces not assigned to
373               bridge domains
374
375         :raise RuntimeError: if no packet captured on l2-pg interface assigned
376                              to the bridge domain or if any packet is captured
377                              on l2-pg interface not assigned to the bridge
378                              domain.
379         """
380         # Test
381         # Create incoming packet streams for packet-generator interfaces
382         for pg_if in self.pg_interfaces:
383             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
384             pg_if.add_stream(pkts)
385
386         # Enable packet capture and start packet sending
387         self.pg_enable_capture(self.pg_interfaces)
388         self.pg_start()
389
390         # Verify
391         # Verify outgoing packet streams per packet-generator interface
392         for pg_if in self.pg_interfaces:
393             capture = pg_if.get_capture()
394             if pg_if in self.pg_in_bd:
395                 self.verify_capture(pg_if, capture)
396             elif pg_if not in self.pg_not_in_bd:
397                 self.logger.error("Unknown interface: %s" % pg_if.name)
398
399     def test_l2bd_inst_01(self):
400         """ L2BD Multi-instance test 1 - create 5 BDs
401         """
402         # Config 1
403         # Create 5 BDs, put interfaces to these BDs and send MAC learning
404         # packets
405         self.create_bd_and_mac_learn(5)
406
407         # Verify 1
408         for bd_id in self.bd_list:
409             self.assertEqual(self.verify_bd(bd_id), 1)
410
411         # Test 1
412         # self.vapi.cli("clear trace")
413         self.run_verify_test()
414
415     def test_l2bd_inst_02(self):
416         """ L2BD Multi-instance test 2 - update data of 5 BDs
417         """
418         # Config 2
419         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
420         self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
421                           flood=False, uu_flood=False)
422         self.set_bd_flags(self.bd_list[1], forward=False)
423         self.set_bd_flags(self.bd_list[2], flood=False)
424         self.set_bd_flags(self.bd_list[3], uu_flood=False)
425         self.set_bd_flags(self.bd_list[4], learn=False)
426
427         # Verify 2
428         # Skipping check of uu_flood as it is not returned by
429         # bridge_domain_dump api command
430         self.verify_bd(self.bd_list[0], learn=False, forward=False,
431                        flood=False, uu_flood=False)
432         self.verify_bd(self.bd_list[1], learn=True, forward=False,
433                        flood=True, uu_flood=True)
434         self.verify_bd(self.bd_list[2], learn=True, forward=True,
435                        flood=False, uu_flood=True)
436         self.verify_bd(self.bd_list[3], learn=True, forward=True,
437                        flood=True, uu_flood=False)
438         self.verify_bd(self.bd_list[4], learn=False, forward=True,
439                        flood=True, uu_flood=True)
440
441     def test_l2bd_inst_03(self):
442         """ L2BD Multi-instance 3 - delete 2 BDs
443         """
444         # Config 3
445         # Delete 2 BDs
446         self.delete_bd(2)
447
448         # Verify 3
449         for bd_id in self.bd_deleted_list:
450             self.assertEqual(self.verify_bd(bd_id), 0)
451         for bd_id in self.bd_list:
452             self.assertEqual(self.verify_bd(bd_id), 1)
453
454         # Test 3
455         self.run_verify_test()
456
457     def test_l2bd_inst_04(self):
458         """ L2BD Multi-instance test 4 - add 2 BDs
459         """
460         # Config 4
461         # Create 5 BDs, put interfaces to these BDs and send MAC learning
462         # packets
463         self.create_bd_and_mac_learn(2)
464
465         # Verify 4
466         for bd_id in self.bd_list:
467             self.assertEqual(self.verify_bd(bd_id), 1)
468
469         # Test 4
470         # self.vapi.cli("clear trace")
471         self.run_verify_test()
472
473     def test_l2bd_inst_05(self):
474         """ L2BD Multi-instance 5 - delete 5 BDs
475         """
476         # Config 5
477         # Delete 5 BDs
478         self.delete_bd(5)
479
480         # Verify 5
481         for bd_id in self.bd_deleted_list:
482             self.assertEqual(self.verify_bd(bd_id), 0)
483         for bd_id in self.bd_list:
484             self.assertEqual(self.verify_bd(bd_id), 1)
485
486
487 if __name__ == '__main__':
488     unittest.main(testRunner=VppTestRunner)