061656a82705592a42caf264ce182a1ed8f46af3
[vpp.git] / test / test_l2xc_multi_instance.py
1 #!/usr/bin/env python
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]] =\
86                     [cls.pg_interfaces[i + delta]]
87
88             # Mapping between packet-generator index and lists of test hosts
89             cls.hosts_by_pg_idx = dict()
90             for pg_if in cls.pg_interfaces:
91                 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
92
93             # Create test host entries
94             cls.create_hosts(70)
95
96             # Packet sizes - jumbo packet (9018 bytes) skipped
97             cls.pg_if_packet_sizes = [64, 512, 1518]
98
99             # Set up all interfaces
100             for i in cls.pg_interfaces:
101                 i.admin_up()
102
103             # Create list of x-connected pg_interfaces
104             cls.pg_in_xc = list()
105
106             # Create list of not x-connected pg_interfaces
107             cls.pg_not_in_xc = list()
108             for pg_if in cls.pg_interfaces:
109                 cls.pg_not_in_xc.append(pg_if)
110
111         except Exception:
112             super(TestL2xcMultiInst, cls).tearDownClass()
113             raise
114
115     @classmethod
116     def tearDownClass(cls):
117         super(TestL2xcMultiInst, cls).tearDownClass()
118
119     def setUp(self):
120         """
121         Clear trace and packet infos before running each test.
122         """
123         super(TestL2xcMultiInst, self).setUp()
124         self.reset_packet_infos()
125
126     def tearDown(self):
127         """
128         Show various debug prints after each test.
129         """
130         super(TestL2xcMultiInst, self).tearDown()
131
132     def show_commands_at_teardown(self):
133         self.logger.info(self.vapi.ppcli("show l2patch"))
134
135     @classmethod
136     def create_hosts(cls, count):
137         """
138         Create required number of host MAC addresses and distribute them among
139         interfaces. Create host IPv4 address for every host MAC address.
140
141         :param int count: Number of hosts to create MAC/IPv4 addresses for.
142         """
143         n_int = len(cls.pg_interfaces)
144         macs_per_if = count // n_int
145         i = -1
146         for pg_if in cls.pg_interfaces:
147             i += 1
148             start_nr = macs_per_if * i
149             end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
150             hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
151             for j in range(start_nr, end_nr):
152                 host = Host(
153                     "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
154                     "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
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(rx_if.sw_if_index,
171                                                    tx_if.sw_if_index, 1)
172             self.logger.info("Cross-connect from %s to %s created"
173                              % (tx_if.name, rx_if.name))
174             if self.pg_in_xc.count(rx_if) == 0:
175                 self.pg_in_xc.append(rx_if)
176             if self.pg_not_in_xc.count(rx_if) == 1:
177                 self.pg_not_in_xc.remove(rx_if)
178
179     def delete_xconnects(self, count, start=0):
180         """
181         Delete required number of cross-connects (always two cross-connects per
182         pair of packet-generator interfaces).
183
184         :param int count: Number of cross-connects to be deleted.
185         :param int start: Starting index of packet-generator interfaces. \
186         (Default value = 0)
187         """
188         for i in range(count):
189             rx_if = self.pg_interfaces[i + start]
190             delta = 1 if i % 2 == 0 else -1
191             tx_if = self.pg_interfaces[i + start + delta]
192             self.vapi.sw_interface_set_l2_xconnect(rx_if.sw_if_index,
193                                                    tx_if.sw_if_index, 0)
194             self.logger.info("Cross-connect from %s to %s deleted"
195                              % (tx_if.name, rx_if.name))
196             if self.pg_not_in_xc.count(rx_if) == 0:
197                 self.pg_not_in_xc.append(rx_if)
198             if self.pg_in_xc.count(rx_if) == 1:
199                 self.pg_in_xc.remove(rx_if)
200
201     def create_stream(self, src_if, packet_sizes):
202         """
203         Create input packet stream for defined interface using hosts list.
204
205         :param object src_if: Interface to create packet stream for.
206         :param list packet_sizes: List of required packet sizes.
207         :return: Stream of packets.
208         """
209         pkts = []
210         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
211         for dst_if in self.flows[src_if]:
212             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
213             n_int = len(dst_hosts)
214             for i in range(0, n_int):
215                 dst_host = dst_hosts[i]
216                 src_host = random.choice(src_hosts)
217                 pkt_info = self.create_packet_info(src_if, dst_if)
218                 payload = self.info_to_payload(pkt_info)
219                 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
220                      IP(src=src_host.ip4, dst=dst_host.ip4) /
221                      UDP(sport=1234, dport=1234) /
222                      Raw(payload))
223                 pkt_info.data = p.copy()
224                 size = random.choice(packet_sizes)
225                 self.extend_packet(p, size)
226                 pkts.append(p)
227         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
228                           % (src_if.name, len(pkts)))
229         return pkts
230
231     def verify_capture(self, pg_if, capture):
232         """
233         Verify captured input packet stream for defined interface.
234
235         :param object pg_if: Interface to verify captured packet stream for.
236         :param list capture: Captured packet stream.
237         """
238         last_info = dict()
239         for i in self.pg_interfaces:
240             last_info[i.sw_if_index] = None
241         dst_sw_if_index = pg_if.sw_if_index
242         for packet in capture:
243             payload_info = self.payload_to_info(packet[Raw])
244             try:
245                 ip = packet[IP]
246                 udp = packet[UDP]
247                 packet_index = payload_info.index
248                 self.assertEqual(payload_info.dst, dst_sw_if_index)
249                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
250                                   (pg_if.name, payload_info.src, packet_index))
251                 next_info = self.get_next_packet_info_for_interface2(
252                     payload_info.src, dst_sw_if_index,
253                     last_info[payload_info.src])
254                 last_info[payload_info.src] = next_info
255                 self.assertTrue(next_info is not None)
256                 self.assertEqual(packet_index, next_info.index)
257                 saved_packet = next_info.data
258                 # Check standard fields
259                 self.assertEqual(ip.src, saved_packet[IP].src)
260                 self.assertEqual(ip.dst, saved_packet[IP].dst)
261                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
262                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
263             except:
264                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
265                 raise
266         for i in self.pg_interfaces:
267             remaining_packet = self.get_next_packet_info_for_interface2(
268                 i, dst_sw_if_index, last_info[i.sw_if_index])
269             self.assertTrue(
270                 remaining_packet is None,
271                 "Port %u: Packet expected from source %u didn't arrive" %
272                 (dst_sw_if_index, i.sw_if_index))
273
274     def run_verify_test(self):
275         """
276         Create packet streams for all configured l2-pg interfaces, send all \
277         prepared packet streams and verify that:
278             - all packets received correctly on all pg-l2 interfaces assigned
279               to cross-connects
280             - no packet received on all pg-l2 interfaces not assigned to
281               cross-connects
282
283         :raise RuntimeError: if no packet captured on l2-pg interface assigned
284                              to the cross-connect or if any packet is captured
285                              on l2-pg interface not assigned to the
286                              cross-connect.
287         """
288         # Test
289         # Create incoming packet streams for packet-generator interfaces
290         for pg_if in self.pg_interfaces:
291             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
292             pg_if.add_stream(pkts)
293
294         # Enable packet capture and start packet sending
295         self.pg_enable_capture(self.pg_interfaces)
296         self.pg_start()
297
298         # Verify
299         # Verify outgoing packet streams per packet-generator interface
300         for pg_if in self.pg_interfaces:
301             if pg_if in self.pg_in_xc:
302                 capture = pg_if.get_capture(
303                     remark="interface is a cross-connect sink")
304                 self.verify_capture(pg_if, capture)
305             elif pg_if in self.pg_not_in_xc:
306                 pg_if.assert_nothing_captured(
307                     remark="interface is not a cross-connect sink")
308             else:
309                 raise Exception("Unexpected interface: %s" % pg_if.name)
310
311     def test_l2xc_inst_01(self):
312         """ L2XC Multi-instance test 1 - create 10 cross-connects
313         """
314         # Config 1
315         # Create 10 cross-connects
316         self.create_xconnects(10)
317
318         # Test 1
319         self.run_verify_test()
320
321     def test_l2xc_inst_02(self):
322         """ L2XC Multi-instance test 2 - delete 4 cross-connects
323         """
324         # Config 2
325         # Delete 4 cross-connects
326         self.delete_xconnects(4)
327
328         # Test 2
329         self.run_verify_test()
330
331     def test_l2xc_inst_03(self):
332         """ L2BD Multi-instance 3 - add new 4 cross-connects
333         """
334         # Config 3
335         # Add new 4 cross-connects
336         self.create_xconnects(4, start=10)
337
338         # Test 3
339         self.run_verify_test()
340
341     def test_l2xc_inst_04(self):
342         """ L2XC Multi-instance test 4 - delete 10 cross-connects
343         """
344         # Config 4
345         # Delete 10 cross-connects
346         self.delete_xconnects(10, start=4)
347
348         # Test 4
349         self.run_verify_test()
350
351
352 if __name__ == '__main__':
353     unittest.main(testRunner=VppTestRunner)