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