tests: replace pycodestyle with black
[vpp.git] / test / test_l2xc_multi_instance.py
1 #!/usr/bin/env python3
2 """L2XC Multi-instance Test Case HLD:
3
4 **NOTES:**
5     - higher number (more than 15) of pg-l2 interfaces causes problems => only
6       14 pg-l2 interfaces and 10 cross-connects are tested
7     - jumbo packets in configuration with 14 l2-pg interfaces leads to
8       problems too
9
10 **config 1**
11     - add 14 pg-l2 interfaces
12     - add 10 cross-connects (two cross-connects per pair of l2-pg interfaces)
13
14 **test 1**
15     - send L2 MAC frames between all pairs of pg-l2 interfaces
16
17 **verify 1**
18     - all packets received correctly in case of cross-connected l2-pg
19       interfaces
20     - no packet received in case of not cross-connected l2-pg interfaces
21
22 **config 2**
23     - delete 4 cross-connects
24
25 **test 2**
26     - send L2 MAC frames between all pairs of pg-l2 interfaces
27
28 **verify 2**
29     - all packets received correctly in case of cross-connected l2-pg
30       interfaces
31     - no packet received in case of not cross-connected l2-pg interfaces
32
33 **config 3**
34     - add new 4 cross-connects
35
36 **test 3**
37     - send L2 MAC frames between all pairs of pg-l2 interfaces
38
39 **verify 3**
40     - all packets received correctly in case of cross-connected l2-pg
41       interfaces
42     - no packet received in case of not cross-connected l2-pg interfaces
43
44 **config 4**
45     - delete 10 cross-connects
46
47 **test 4**
48     - send L2 MAC frames between all pairs of pg-l2 interfaces
49
50 **verify 4**
51     - no packet received on all of l2-pg interfaces (no cross-connect created)
52 """
53
54 import unittest
55 import random
56
57 from scapy.packet import Raw
58 from scapy.layers.l2 import Ether
59 from scapy.layers.inet import IP, UDP
60
61 from framework import VppTestCase, VppTestRunner
62 from util import Host, ppp
63
64
65 class TestL2xcMultiInst(VppTestCase):
66     """L2XC Multi-instance Test Case"""
67
68     @classmethod
69     def setUpClass(cls):
70         """
71         Perform standard class setup (defined by class method setUpClass in
72         class VppTestCase) before running the test case, set test case related
73         variables and configure VPP.
74         """
75         super(TestL2xcMultiInst, cls).setUpClass()
76
77         try:
78             # Create pg interfaces
79             cls.create_pg_interfaces(range(14))
80
81             # Packet flows mapping pg0 -> pg1 etc.
82             cls.flows = dict()
83             for i in range(len(cls.pg_interfaces)):
84                 delta = 1 if i % 2 == 0 else -1
85                 cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + delta]]
86
87             # Mapping between packet-generator index and lists of test hosts
88             cls.hosts_by_pg_idx = dict()
89             for pg_if in cls.pg_interfaces:
90                 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
91
92             # Create test host entries
93             cls.create_hosts(70)
94
95             # Packet sizes - jumbo packet (9018 bytes) skipped
96             cls.pg_if_packet_sizes = [64, 512, 1518]
97
98             # Set up all interfaces
99             for i in cls.pg_interfaces:
100                 i.admin_up()
101
102             # Create list of x-connected pg_interfaces
103             cls.pg_in_xc = list()
104
105             # Create list of not x-connected pg_interfaces
106             cls.pg_not_in_xc = list()
107             for pg_if in cls.pg_interfaces:
108                 cls.pg_not_in_xc.append(pg_if)
109
110         except Exception:
111             super(TestL2xcMultiInst, cls).tearDownClass()
112             raise
113
114     @classmethod
115     def tearDownClass(cls):
116         super(TestL2xcMultiInst, cls).tearDownClass()
117
118     def setUp(self):
119         """
120         Clear trace and packet infos before running each test.
121         """
122         super(TestL2xcMultiInst, self).setUp()
123         self.reset_packet_infos()
124
125     def tearDown(self):
126         """
127         Show various debug prints after each test.
128         """
129         super(TestL2xcMultiInst, self).tearDown()
130
131     def show_commands_at_teardown(self):
132         self.logger.info(self.vapi.ppcli("show l2patch"))
133
134     @classmethod
135     def create_hosts(cls, count):
136         """
137         Create required number of host MAC addresses and distribute them among
138         interfaces. Create host IPv4 address for every host MAC address.
139
140         :param int count: Number of hosts to create MAC/IPv4 addresses for.
141         """
142         n_int = len(cls.pg_interfaces)
143         macs_per_if = count // n_int
144         i = -1
145         for pg_if in cls.pg_interfaces:
146             i += 1
147             start_nr = macs_per_if * i
148             end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
149             hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
150             for j in range(start_nr, end_nr):
151                 host = Host(
152                     "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
153                     "172.17.1%02u.%u" % (pg_if.sw_if_index, j),
154                 )
155                 hosts.append(host)
156
157     def create_xconnects(self, count, start=0):
158         """
159         Create required number of cross-connects (always two cross-connects per
160         pair of packet-generator interfaces).
161
162         :param int count: Number of cross-connects to be created.
163         :param int start: Starting index of packet-generator interfaces. \
164         (Default value = 0)
165         """
166         for i in range(count):
167             rx_if = self.pg_interfaces[i + start]
168             delta = 1 if i % 2 == 0 else -1
169             tx_if = self.pg_interfaces[i + start + delta]
170             self.vapi.sw_interface_set_l2_xconnect(
171                 rx_if.sw_if_index, tx_if.sw_if_index, 1
172             )
173             self.logger.info(
174                 "Cross-connect from %s to %s created" % (tx_if.name, rx_if.name)
175             )
176             if self.pg_in_xc.count(rx_if) == 0:
177                 self.pg_in_xc.append(rx_if)
178             if self.pg_not_in_xc.count(rx_if) == 1:
179                 self.pg_not_in_xc.remove(rx_if)
180
181     def delete_xconnects(self, count, start=0):
182         """
183         Delete required number of cross-connects (always two cross-connects per
184         pair of packet-generator interfaces).
185
186         :param int count: Number of cross-connects to be deleted.
187         :param int start: Starting index of packet-generator interfaces. \
188         (Default value = 0)
189         """
190         for i in range(count):
191             rx_if = self.pg_interfaces[i + start]
192             delta = 1 if i % 2 == 0 else -1
193             tx_if = self.pg_interfaces[i + start + delta]
194             self.vapi.sw_interface_set_l2_xconnect(
195                 rx_if.sw_if_index, tx_if.sw_if_index, 0
196             )
197             self.logger.info(
198                 "Cross-connect from %s to %s deleted" % (tx_if.name, rx_if.name)
199             )
200             if self.pg_not_in_xc.count(rx_if) == 0:
201                 self.pg_not_in_xc.append(rx_if)
202             if self.pg_in_xc.count(rx_if) == 1:
203                 self.pg_in_xc.remove(rx_if)
204
205     def create_stream(self, src_if, packet_sizes):
206         """
207         Create input packet stream for defined interface using hosts list.
208
209         :param object src_if: Interface to create packet stream for.
210         :param list packet_sizes: List of required packet sizes.
211         :return: Stream of packets.
212         """
213         pkts = []
214         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
215         for dst_if in self.flows[src_if]:
216             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
217             n_int = len(dst_hosts)
218             for i in range(0, n_int):
219                 dst_host = dst_hosts[i]
220                 src_host = random.choice(src_hosts)
221                 pkt_info = self.create_packet_info(src_if, dst_if)
222                 payload = self.info_to_payload(pkt_info)
223                 p = (
224                     Ether(dst=dst_host.mac, src=src_host.mac)
225                     / IP(src=src_host.ip4, dst=dst_host.ip4)
226                     / UDP(sport=1234, dport=1234)
227                     / Raw(payload)
228                 )
229                 pkt_info.data = p.copy()
230                 size = random.choice(packet_sizes)
231                 self.extend_packet(p, size)
232                 pkts.append(p)
233         self.logger.debug(
234             "Input stream created for port %s. Length: %u pkt(s)"
235             % (src_if.name, len(pkts))
236         )
237         return pkts
238
239     def verify_capture(self, pg_if, capture):
240         """
241         Verify captured input packet stream for defined interface.
242
243         :param object pg_if: Interface to verify captured packet stream for.
244         :param list capture: Captured packet stream.
245         """
246         last_info = dict()
247         for i in self.pg_interfaces:
248             last_info[i.sw_if_index] = None
249         dst_sw_if_index = pg_if.sw_if_index
250         for packet in capture:
251             payload_info = self.payload_to_info(packet[Raw])
252             try:
253                 ip = packet[IP]
254                 udp = packet[UDP]
255                 packet_index = payload_info.index
256                 self.assertEqual(payload_info.dst, dst_sw_if_index)
257                 self.logger.debug(
258                     "Got packet on port %s: src=%u (id=%u)"
259                     % (pg_if.name, payload_info.src, packet_index)
260                 )
261                 next_info = self.get_next_packet_info_for_interface2(
262                     payload_info.src, dst_sw_if_index, last_info[payload_info.src]
263                 )
264                 last_info[payload_info.src] = next_info
265                 self.assertTrue(next_info is not None)
266                 self.assertEqual(packet_index, next_info.index)
267                 saved_packet = next_info.data
268                 # Check standard fields
269                 self.assertEqual(ip.src, saved_packet[IP].src)
270                 self.assertEqual(ip.dst, saved_packet[IP].dst)
271                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
272                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
273             except:
274                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
275                 raise
276         for i in self.pg_interfaces:
277             remaining_packet = self.get_next_packet_info_for_interface2(
278                 i, dst_sw_if_index, last_info[i.sw_if_index]
279             )
280             self.assertTrue(
281                 remaining_packet is None,
282                 "Port %u: Packet expected from source %u didn't arrive"
283                 % (dst_sw_if_index, i.sw_if_index),
284             )
285
286     def run_verify_test(self):
287         """
288         Create packet streams for all configured l2-pg interfaces, send all \
289         prepared packet streams and verify that:
290             - all packets received correctly on all pg-l2 interfaces assigned
291               to cross-connects
292             - no packet received on all pg-l2 interfaces not assigned to
293               cross-connects
294
295         :raise RuntimeError: if no packet captured on l2-pg interface assigned
296                              to the cross-connect or if any packet is captured
297                              on l2-pg interface not assigned to the
298                              cross-connect.
299         """
300         # Test
301         # Create incoming packet streams for packet-generator interfaces
302         for pg_if in self.pg_interfaces:
303             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
304             pg_if.add_stream(pkts)
305
306         # Enable packet capture and start packet sending
307         self.pg_enable_capture(self.pg_interfaces)
308         self.pg_start()
309
310         # Verify
311         # Verify outgoing packet streams per packet-generator interface
312         for pg_if in self.pg_interfaces:
313             if pg_if in self.pg_in_xc:
314                 capture = pg_if.get_capture(remark="interface is a cross-connect sink")
315                 self.verify_capture(pg_if, capture)
316             elif pg_if in self.pg_not_in_xc:
317                 pg_if.assert_nothing_captured(
318                     remark="interface is not a cross-connect sink"
319                 )
320             else:
321                 raise Exception("Unexpected interface: %s" % pg_if.name)
322
323     def test_l2xc_inst_01(self):
324         """L2XC Multi-instance test 1 - create 10 cross-connects"""
325         # Config 1
326         # Create 10 cross-connects
327         self.create_xconnects(10)
328
329         # Test 1
330         self.run_verify_test()
331
332     def test_l2xc_inst_02(self):
333         """L2XC Multi-instance test 2 - delete 4 cross-connects"""
334         # Config 2
335         # Delete 4 cross-connects
336         self.delete_xconnects(4)
337
338         # Test 2
339         self.run_verify_test()
340
341     def test_l2xc_inst_03(self):
342         """L2BD Multi-instance 3 - add new 4 cross-connects"""
343         # Config 3
344         # Add new 4 cross-connects
345         self.create_xconnects(4, start=10)
346
347         # Test 3
348         self.run_verify_test()
349
350     def test_l2xc_inst_04(self):
351         """L2XC Multi-instance test 4 - delete 10 cross-connects"""
352         # Config 4
353         # Delete 10 cross-connects
354         self.delete_xconnects(10, start=4)
355
356         # Test 4
357         self.run_verify_test()
358
359
360 if __name__ == "__main__":
361     unittest.main(testRunner=VppTestRunner)