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