blob: 0c07cb6111fce6e60471fc1c83e4d4701c70f8d4 [file] [log] [blame]
Klement Sekerae7c034b2017-01-26 14:54:47 +01001.. _unittest: https://docs.python.org/2/library/unittest.html
2.. _TestCase: https://docs.python.org/2/library/unittest.html#unittest.TestCase
3.. _AssertionError: https://docs.python.org/2/library/exceptions.html#exceptions.AssertionError
4.. _SkipTest: https://docs.python.org/2/library/unittest.html#unittest.SkipTest
5.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
6.. _scapy: http://www.secdev.org/projects/scapy/
7.. _logging: https://docs.python.org/2/library/logging.html
juraj.linkes565409b2018-10-11 10:06:44 +02008.. _process: https://docs.python.org/2/library/multiprocessing.html#the-process-class
9.. _pipes: https://docs.python.org/2/library/multiprocessing.html#multiprocessing.Pipe
10.. _managed: https://docs.python.org/2/library/multiprocessing.html#managers
Klement Sekerae7c034b2017-01-26 14:54:47 +010011
12.. |vtf| replace:: VPP Test Framework
13
14|vtf|
15=====
16
17.. contents::
18 :local:
Tibored6814b2017-01-27 12:59:02 +010019 :depth: 1
Klement Sekerae7c034b2017-01-26 14:54:47 +010020
21Overview
22########
23
24The goal of the |vtf| is to ease writing, running and debugging
25unit tests for the VPP. For this, python was chosen as a high level language
26allowing rapid development with scapy_ providing the necessary tool for creating
27and dissecting packets.
28
29Anatomy of a test case
30######################
31
32Python's unittest_ is used as the base framework upon which the VPP test
33framework is built. A test suite in the |vtf| consists of multiple classes
34derived from `VppTestCase`, which is itself derived from TestCase_.
35The test class defines one or more test functions, which act as test cases.
36
37Function flow when running a test case is:
38
391. `setUpClass <VppTestCase.setUpClass>`:
40 This function is called once for each test class, allowing a one-time test
41 setup to be executed. If this functions throws an exception,
42 none of the test functions are executed.
432. `setUp <VppTestCase.setUp>`:
44 The setUp function runs before each of the test functions. If this function
45 throws an exception other than AssertionError_ or SkipTest_, then this is
46 considered an error, not a test failure.
473. *test_<name>*:
48 This is the guts of the test case. It should execute the test scenario
49 and use the various assert functions from the unittest framework to check
50 necessary. Multiple test_<name> methods can exist in a test case.
514. `tearDown <VppTestCase.tearDown>`:
52 The tearDown function is called after each test function with the purpose
53 of doing partial cleanup.
545. `tearDownClass <VppTestCase.tearDownClass>`:
55 Method called once after running all of the test functions to perform
56 the final cleanup.
57
58Logging
59#######
60
61Each test case has a logger automatically created for it, stored in
62'logger' property, based on logging_. Use the logger's standard methods
63debug(), info(), error(), ... to emit log messages to the logger.
64
65All the log messages go always into a log file in temporary directory
66(see below).
67
68To control the messages printed to console, specify the V= parameter.
69
70.. code-block:: shell
71
72 make test # minimum verbosity
73 make test V=1 # moderate verbosity
74 make test V=2 # maximum verbosity
75
juraj.linkes565409b2018-10-11 10:06:44 +020076Parallel test execution
77#######################
78
79|vtf| test suites can be run in parallel. Each test suite is executed
80in a separate process spawned by Python multiprocessing process_.
81
82The results from child test suites are sent to parent through pipes_, which are
83aggregated and summarized at the end of the run.
84
85Stdout, stderr and logs logged in child processes are redirected to individual
86parent managed_ queues. The data from these queues are then emitted to stdout
87of the parent process in the order the test suites have finished. In case there
88are no finished test suites (such as at the beginning of the run), the data
89from last started test suite are emitted in real time.
90
91To enable parallel test run, specify the number of parallel processes:
92
93.. code-block:: shell
94
95 make test TEST_JOBS=n # at most n processes will be spawned
96 make test TEST_JOBS=auto # chosen based on the number of cores
97 # and the size of shared memory
98
Klement Sekerae7c034b2017-01-26 14:54:47 +010099Test temporary directory and VPP life cycle
100###########################################
101
102Test separation is achieved by separating the test files and vpp instances.
103Each test creates a temporary directory and it's name is used to create
104a shared memory prefix which is used to run a VPP instance.
105The temporary directory name contains the testcase class name for easy
106reference, so for testcase named 'TestVxlan' the directory could be named
107e.g. vpp-unittest-TestVxlan-UNUP3j.
108This way, there is no conflict between any other VPP instances running
109on the box and the test VPP. Any temporary files created by the test case
110are stored in this temporary test directory.
111
112The test temporary directory holds the following interesting files:
113
114* log.txt - this contains the logger output on max verbosity
115* pg*_in.pcap - last injected packet stream into VPP, named after the interface,
116 so for pg0, the file will be named pg0_in.pcap
117* pg*_out.pcap - last capture file created by VPP for interface, similarly,
118 named after the interface, so for e.g. pg1, the file will be named
119 pg1_out.pcap
120* history files - whenever the capture is restarted or a new stream is added,
121 the existing files are rotated and renamed, soo all the pcap files
122 are always saved for later debugging if needed
123* core - if vpp dumps a core, it'll be stored in the temporary directory
124* vpp_stdout.txt - file containing output which vpp printed to stdout
125* vpp_stderr.txt - file containing output which vpp printed to stderr
126
127*NOTE*: existing temporary directories named vpp-unittest-* are automatically
128removed when invoking 'make test*' or 'make retest*' to keep the temporary
129directory clean.
130
131Virtual environment
132###################
133
134Virtualenv_ is a python module which provides a means to create an environment
135containing the dependencies required by the |vtf|, allowing a separation
136from any existing system-wide packages. |vtf|'s Makefile automatically
137creates a virtualenv_ inside build-root and installs the required packages
138in that environment. The environment is entered whenever executing a test
139via one of the make test targets.
140
141Naming conventions
142##################
143
144Most unit tests do some kind of packet manipulation - sending and receiving
145packets between VPP and virtual hosts connected to the VPP. Referring
146to the sides, addresses, etc. is always done as if looking from the VPP side,
147thus:
148
149* *local_* prefix is used for the VPP side.
150 So e.g. `local_ip4 <VppInterface.local_ip4>` address is the IPv4 address
151 assigned to the VPP interface.
152* *remote_* prefix is used for the virtual host side.
153 So e.g. `remote_mac <VppInterface.remote_mac>` address is the MAC address
154 assigned to the virtual host connected to the VPP.
155
156Automatically generated addresses
157#################################
158
159To send packets, one needs to typically provide some addresses, otherwise
160the packets will be dropped. The interface objects in |vtf| automatically
161provide addresses based on (typically) their indexes, which ensures
162there are no conflicts and eases debugging by making the addressing scheme
163consistent.
164
165The developer of a test case typically doesn't need to work with the actual
166numbers, rather using the properties of the objects. The addresses typically
167come in two flavors: '<address>' and '<address>n' - note the 'n' suffix.
168The former address is a Python string, while the latter is translated using
169socket.inet_pton to raw format in network byte order - this format is suitable
170for passing as an argument to VPP APIs.
171
172e.g. for the IPv4 address assigned to the VPP interface:
173
174* local_ip4 - Local IPv4 address on VPP interface (string)
175* local_ip4n - Local IPv4 address - raw, suitable as API parameter.
176
177These addresses need to be configured in VPP to be usable using e.g.
Dave Wallaced1706812021-08-12 18:36:02 -0400178`VppInterface.config_ip4` API. Please see the documentation to
179`VppInterface` for more details.
Klement Sekerae7c034b2017-01-26 14:54:47 +0100180
181By default, there is one remote address of each kind created for L3:
182remote_ip4 and remote_ip6. If the test needs more addresses, because it's
183simulating more remote hosts, they can be generated using
184`generate_remote_hosts` API and the entries for them inserted into the ARP
185table using `configure_ipv4_neighbors` API.
186
187Packet flow in the |vtf|
188########################
189
190Test framework -> VPP
191~~~~~~~~~~~~~~~~~~~~~
192
193|vtf| doesn't send any packets to VPP directly. Traffic is instead injected
194using packet-generator interfaces, represented by the `VppPGInterface` class.
195Packets are written into a temporary .pcap file, which is then read by the VPP
196and the packets are injected into the VPP world.
197
Dave Wallaced1706812021-08-12 18:36:02 -0400198To add a list of packets to an interface, call the `VppPGInterface.add_stream`
199method on that interface. Once everything is prepared, call `pg_start` method to
200start the packet generator on the VPP side.
Klement Sekerae7c034b2017-01-26 14:54:47 +0100201
202VPP -> test framework
203~~~~~~~~~~~~~~~~~~~~~
204
205Similarly, VPP doesn't send any packets to |vtf| directly. Instead, packet
206capture feature is used to capture and write traffic to a temporary .pcap file,
207which is then read and analyzed by the |vtf|.
208
209The following APIs are available to the test case for reading pcap files.
210
Dave Wallaced1706812021-08-12 18:36:02 -0400211* `VppPGInterface.get_capture`: this API is suitable for bulk & batch
212 style of test, where a list of packets is prepared & sent, then the
213 received packets are read and verified. The API needs the number of
214 packets which are expected to be captured (ignoring filtered
215 packets - see below) to know when the pcap file is completely
216 written by the VPP. If using packet infos for verifying packets,
217 then the counts of the packet infos can be automatically used by
218 `VppPGInterface.get_capture` to get the proper count (in this case
219 the default value None can be supplied as expected_count or ommitted
220 altogether).
221* `VppPGInterface.wait_for_packet`: this API is suitable for
222 interactive style of test, e.g. when doing session management,
223 three-way handshakes, etc. This API waits for and returns a single
224 packet, keeping the capture file in place and remembering
225 context. Repeated invocations return following packets (or raise
226 Exception if timeout is reached) from the same capture file (=
227 packets arriving on the same interface).
Klement Sekerae7c034b2017-01-26 14:54:47 +0100228
Dave Wallaced1706812021-08-12 18:36:02 -0400229*NOTE*: it is not recommended to mix these APIs unless you understand
230how they work internally. None of these APIs rotate the pcap capture
231file, so calling e.g. `VppPGInterface.get_capture` after
232`VppPGInterface.wait_for_packet` will return already read packets. It
233is safe to switch from one API to another after calling
234`VppPGInterface.enable_capture` as that API rotates the capture file.
Klement Sekerae7c034b2017-01-26 14:54:47 +0100235
236Automatic filtering of packets:
237~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238
Dave Wallaced1706812021-08-12 18:36:02 -0400239Both APIs (`VppPGInterface.get_capture` and
240`VppPGInterface.wait_for_packet`) by default filter the packet
241capture, removing known uninteresting packets from it - these are IPv6
242Router Advertisments and IPv6 Router Alerts. These packets are
243unsolicitated and from the point of |vtf| are random. If a test wants
244to receive these packets, it should specify either None or a custom
245filtering function as the value to the 'filter_out_fn' argument.
Klement Sekerae7c034b2017-01-26 14:54:47 +0100246
247Common API flow for sending/receiving packets:
juraj.linkes565409b2018-10-11 10:06:44 +0200248~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Klement Sekerae7c034b2017-01-26 14:54:47 +0100249
250We will describe a simple scenario, where packets are sent from pg0 to pg1
251interface, assuming that the interfaces were created using
252`create_pg_interfaces` API.
253
2541. Create a list of packets for pg0::
255
256 packet_count = 10
257 packets = create_packets(src=self.pg0, dst=self.pg1,
258 count=packet_count)
259
2602. Add that list of packets to the source interface::
261
262 self.pg0.add_stream(packets)
263
2643. Enable capture on the destination interface::
265
266 self.pg1.enable_capture()
267
2684. Start the packet generator::
269
270 self.pg_start()
271
2725. Wait for capture file to appear and read it::
273
274 capture = self.pg1.get_capture(expected_count=packet_count)
275
2766. Verify packets match sent packets::
277
278 self.verify_capture(send=packets, captured=capture)
279
280Test framework objects
281######################
282
283The following objects provide VPP abstraction and provide a means to do
284common tasks easily in the test cases.
285
286* `VppInterface`: abstract class representing generic VPP interface
287 and contains some common functionality, which is then used by derived classes
288* `VppPGInterface`: class representing VPP packet-generator interface.
289 The interface is created/destroyed when the object is created/destroyed.
290* `VppSubInterface`: VPP sub-interface abstract class, containing common
291 functionality for e.g. `VppDot1QSubint` and `VppDot1ADSubint` classes
292
293How VPP APIs/CLIs are called
294############################
295
296Vpp provides python bindings in a python module called vpp-papi, which the test
297framework installs in the virtual environment. A shim layer represented by
298the `VppPapiProvider` class is built on top of the vpp-papi, serving these
299purposes:
300
3011. Automatic return value checks:
302 After each API is called, the return value is checked against the expected
303 return value (by default 0, but can be overridden) and an exception
304 is raised if the check fails.
3052. Automatic call of hooks:
306
307 a. `before_cli <Hook.before_cli>` and `before_api <Hook.before_api>` hooks
308 are used for debug logging and stepping through the test
309 b. `after_cli <Hook.after_cli>` and `after_api <Hook.after_api>` hooks
310 are used for monitoring the vpp process for crashes
3113. Simplification of API calls:
312 Many of the VPP APIs take a lot of parameters and by providing sane defaults
313 for these, the API is much easier to use in the common case and the code is
314 more readable. E.g. ip_add_del_route API takes ~25 parameters, of which
315 in the common case, only 3 are needed.
316
317Utility methods
318###############
319
320Some interesting utility methods are:
321
322* `ppp`: 'Pretty Print Packet' - returns a string containing the same output
323 as Scapy's packet.show() would print
324* `ppc`: 'Pretty Print Capture' - returns a string containing printout of
325 a capture (with configurable limit on the number of packets printed from it)
326 using `ppp`
327
328*NOTE*: Do not use Scapy's packet.show() in the tests, because it prints
329the output to stdout. All output should go to the logger associated with
330the test case.
331
332Example: how to add a new test
333##############################
334
335In this example, we will describe how to add a new test case which tests
336basic IPv4 forwarding.
337
3381. Add a new file called test_ip4_fwd.py in the test directory, starting
339 with a few imports::
340
341 from framework import VppTestCase
342 from scapy.layers.l2 import Ether
343 from scapy.packet import Raw
344 from scapy.layers.inet import IP, UDP
345 from random import randint
346
3472. Create a class inherited from the VppTestCase::
348
349 class IP4FwdTestCase(VppTestCase):
350 """ IPv4 simple forwarding test case """
351
Elias Rudberg12b1b562020-12-03 13:29:38 +01003523. Add a setUpClass function containing the setup needed for our test to run::
Klement Sekerae7c034b2017-01-26 14:54:47 +0100353
354 @classmethod
355 def setUpClass(self):
356 super(IP4FwdTestCase, self).setUpClass()
357 self.create_pg_interfaces(range(2)) # create pg0 and pg1
358 for i in self.pg_interfaces:
359 i.admin_up() # put the interface up
360 i.config_ip4() # configure IPv4 address on the interface
361 i.resolve_arp() # resolve ARP, so that we know VPP MAC
362
Elias Rudberg12b1b562020-12-03 13:29:38 +01003634. Create a helper method to create the packets to send::
Klement Sekerae7c034b2017-01-26 14:54:47 +0100364
365 def create_stream(self, src_if, dst_if, count):
366 packets = []
367 for i in range(count):
368 # create packet info stored in the test case instance
369 info = self.create_packet_info(src_if, dst_if)
370 # convert the info into packet payload
371 payload = self.info_to_payload(info)
372 # create the packet itself
373 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
374 IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
375 UDP(sport=randint(1000, 2000), dport=5678) /
376 Raw(payload))
377 # store a copy of the packet in the packet info
378 info.data = p.copy()
379 # append the packet to the list
380 packets.append(p)
381
382 # return the created packet list
383 return packets
384
Elias Rudberg12b1b562020-12-03 13:29:38 +01003855. Create a helper method to verify the capture::
Klement Sekerae7c034b2017-01-26 14:54:47 +0100386
387 def verify_capture(self, src_if, dst_if, capture):
388 packet_info = None
389 for packet in capture:
390 try:
391 ip = packet[IP]
392 udp = packet[UDP]
393 # convert the payload to packet info object
Paul Vinciguerraeaea4212019-03-06 11:58:06 -0800394 payload_info = self.payload_to_info(packet[Raw])
Klement Sekerae7c034b2017-01-26 14:54:47 +0100395 # make sure the indexes match
396 self.assert_equal(payload_info.src, src_if.sw_if_index,
397 "source sw_if_index")
398 self.assert_equal(payload_info.dst, dst_if.sw_if_index,
399 "destination sw_if_index")
400 packet_info = self.get_next_packet_info_for_interface2(
401 src_if.sw_if_index,
402 dst_if.sw_if_index,
403 packet_info)
404 # make sure we didn't run out of saved packets
405 self.assertIsNotNone(packet_info)
406 self.assert_equal(payload_info.index, packet_info.index,
407 "packet info index")
408 saved_packet = packet_info.data # fetch the saved packet
409 # assert the values match
410 self.assert_equal(ip.src, saved_packet[IP].src,
411 "IP source address")
412 # ... more assertions here
413 self.assert_equal(udp.sport, saved_packet[UDP].sport,
414 "UDP source port")
415 except:
416 self.logger.error(ppp("Unexpected or invalid packet:",
417 packet))
418 raise
419 remaining_packet = self.get_next_packet_info_for_interface2(
420 src_if.sw_if_index,
421 dst_if.sw_if_index,
422 packet_info)
423 self.assertIsNone(remaining_packet,
424 "Interface %s: Packet expected from interface "
425 "%s didn't arrive" % (dst_if.name, src_if.name))
426
Elias Rudberg12b1b562020-12-03 13:29:38 +01004276. Add the test code to test_basic function::
Klement Sekerae7c034b2017-01-26 14:54:47 +0100428
429 def test_basic(self):
430 count = 10
431 # create the packet stream
432 packets = self.create_stream(self.pg0, self.pg1, count)
433 # add the stream to the source interface
434 self.pg0.add_stream(packets)
435 # enable capture on both interfaces
436 self.pg0.enable_capture()
437 self.pg1.enable_capture()
438 # start the packet generator
439 self.pg_start()
440 # get capture - the proper count of packets was saved by
441 # create_packet_info() based on dst_if parameter
442 capture = self.pg1.get_capture()
443 # assert nothing captured on pg0 (always do this last, so that
444 # some time has already passed since pg_start())
445 self.pg0.assert_nothing_captured()
446 # verify capture
447 self.verify_capture(self.pg0, self.pg1, capture)
448
Elias Rudberg12b1b562020-12-03 13:29:38 +01004497. Run the test by issuing 'make test' or, to run only this specific
450 test, issue 'make test TEST=test_ip4_fwd'.