blob: 6db832b7fbe0aa26483c39a41685d408dcc15cb5 [file] [log] [blame]
Maros Ondrejicka7943c902022-11-08 08:00:51 +01001Host stack test framework
2=========================
3
4Overview
5--------
6
7The goal of the Host stack test framework (**hs-test**) is to ease writing and running end-to-end tests for VPP's Host Stack.
8End-to-end tests often want multiple VPP instances, network namespaces, different types of interfaces
9and to execute external tools or commands. With such requirements the existing VPP test framework is not sufficient.
10For this, ``Go`` was chosen as a high level language, allowing rapid development, with ``Docker`` and ``ip`` being the tools for creating required topology.
11
12Go's package `testing`_ together with `go test`_ command form the base framework upon which the *hs-test* is built and run.
13
14Anatomy of a test case
15----------------------
16
17**Prerequisites**:
18
Filip Tehlar671cf512023-01-31 10:34:18 +010019* Install hs-test dependencies with ``make install-deps``
20* Tests use *hs-test*'s own docker image, so building it before starting tests is a prerequisite. Run ``make build[-debug]`` to do so
Maros Ondrejicka7943c902022-11-08 08:00:51 +010021* Docker has to be installed and Go has to be in path of both the running user and root
22* Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology
23
24**Action flow when running a test case**:
25
Filip Tehlar671cf512023-01-31 10:34:18 +010026#. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run),
27 and TEST=<test-name> to run specific test.
28#. ``make list-tests`` (or ``make help``) shows all test names.
Maros Ondrejicka7943c902022-11-08 08:00:51 +010029#. ``go test`` compiles package ``main`` along with any files with names matching the file pattern ``*_test.go``
30 and then runs the resulting test binaries
31#. The go test framework runs each function matching :ref:`naming convention<test-convention>`. Each of these corresponds to a `test suite`_
32#. Testify toolkit's ``suite.Run(t *testing.T, suite TestingSuite)`` function runs the suite and does the following:
33
34 #. Suite is initialized. The topology is loaded and configured in this step
35 #. Test suite runs all the tests attached to it
36 #. Execute tear-down functions, which currently consists of stopping running containers
37 and clean-up of test topology
38
39Adding a test case
40------------------
41
42This describes adding a new test case to an existing suite.
43For adding a new suite, please see `Modifying the framework`_ below.
44
45#. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
46#. Declare method whose name starts with ``Test`` and specifies its receiver as a pointer to the suite's struct (defined in ``framework_test.go``)
47#. Implement test behaviour inside the test method. This typically includes the following:
48
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010049 #. Retrieve a running container in which to run some action. Method ``getContainerByName``
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010050 from ``HstSuite`` struct serves this purpose
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010051 #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
52 #. Run arbitrary commands inside the containers with ``exec`` method
Maros Ondrejicka7943c902022-11-08 08:00:51 +010053 #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010054 For example, use ``wget`` with ``startWget`` function
Maros Ondrejicka7943c902022-11-08 08:00:51 +010055 #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010056 #. Verify results of your tests using ``assert`` methods provided by the test suite,
57 implemented by HstSuite struct
Maros Ondrejicka7943c902022-11-08 08:00:51 +010058
59**Example test case**
60
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010061Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
62This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./test -run TestMySuite/TestMyCase``.
Maros Ondrejicka7943c902022-11-08 08:00:51 +010063
64::
65
66 package main
67
68 import (
69 "fmt"
Maros Ondrejicka7943c902022-11-08 08:00:51 +010070 )
71
72 func (s *MySuite) TestMyCase() {
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010073 clientVpp := s.getContainerByName("client-vpp").vppInstance
Maros Ondrejicka7943c902022-11-08 08:00:51 +010074
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010075 serverVethAddress := s.netInterfaces["server-iface"].AddressString()
Maros Ondrejicka7943c902022-11-08 08:00:51 +010076
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010077 result := clientVpp.vppctl("ping " + serverVethAddress)
78 s.assertNotNil(result)
79 s.log(result)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010080 }
81
82Modifying the framework
83-----------------------
84
85**Adding a test suite**
86
87.. _test-convention:
88
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010089#. Adding a new suite takes place in ``framework_test.go`` and by creating a new file for the suite.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010090 Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010091 by the actual name
Maros Ondrejicka7943c902022-11-08 08:00:51 +010092
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010093#. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member.
Maros Ondrejicka7943c902022-11-08 08:00:51 +010094 HstSuite provides functionality that can be shared for all suites, like starting containers
95
96 ::
97
98 type MySuite struct {
99 HstSuite
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100100 }
101
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100102#. In suite file, implement ``SetupSuite`` method which testify runs once before starting any of the tests.
103 It's important here to call ``configureNetworkTopology`` method,
104 pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100105 Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
106 This will ensure network topology, such as network interfaces and namespaces, will be created.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100107 Another important method to call is ``loadContainerTopology()`` which will load
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100108 containers and shared volumes used by the suite. This time the name passed to method corresponds
109 to file in ``extras/hs-test/topo-containers`` folder
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100110
111 ::
112
113 func (s *MySuite) SetupSuite() {
114 // Add custom setup code here
115
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100116 s.configureNetworkTopology("myTopology")
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100117 s.loadContainerTopology("2peerVeth")
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100118 }
119
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100120#. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and
121 configuring VPP is usually placed here
122
123 ::
124
125 func (s *MySuite) SetupTest() {
126 s.SetupVolumes()
127 s.SetupContainers()
128 }
129
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100130#. In order for ``go test`` to run this suite, we need to create a normal test function and pass our suite to ``suite.Run``.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100131 These functions are placed at the end of ``framework_test.go``
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100132
133 ::
134
135 func TestMySuite(t *testing.T) {
136 var m MySuite
137 suite.Run(t, &m)
138 }
139
140#. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
141
142**Adding a topology element**
143
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100144Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and
145``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100146is started by the ``configureNetworkTopology`` method depending on which file's name is passed to it.
147Specified file is loaded and converted into internal data structures which represent various elements of the topology.
148After parsing the configuration, framework loops over the elements and configures them one by one on the host system.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100149
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100150These are currently supported types of network elements.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100151
152* ``netns`` - network namespace
153* ``veth`` - veth network interface, optionally with target network namespace or IPv4 address
154* ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace
155* ``tap`` - tap network interface with IP address
156
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100157Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed
158so that test suite retains map of defined containers and uses that to start them at the beginning
159of each test case and stop containers after the test finishes. Container configuration can specify
160also volumes which allow to share data between containers or between host system and containers.
161
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100162Supporting a new type of topology element requires adding code to recognize the new element type during loading.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100163And adding code to set up the element in the host system with some Linux tool, such as *ip*.
164This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100165
166**Communicating between containers**
167
168When two VPP instances or other applications, each in its own Docker container,
169want to communicate there are typically two ways this can be done within *hs-test*.
170
171* Network interfaces. Containers are being created with ``-d --network host`` options,
172 so they are connected with interfaces created in host system
173* Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers
174 or just between containers
175
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100176Host system connects to VPP instances running in containers using a shared folder
177where binary API socket is accessible by both sides.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100178
179**Adding an external tool**
180
181If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function.
182These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image,
183add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command.
184Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied.
185
Filip Tehlar94181432024-01-15 13:11:28 +0100186**Skipping tests**
187
188``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as:
189``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``.
190However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
191test run time it is not advisable to use aforementioned skip methods and instead prefix test name with ``Skip``:
192
193::
194
195 func (s *MySuite) SkipTest(){
196
197
Filip Tehlarf34b6802024-01-24 15:11:27 +0100198**Debugging a test**
199
200It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows:
201
202::
203
204 $ make test TEST=TestVeths/TestLDPreloadIperfVpp DEBUG=true
205 ...
206 run following command in different terminal:
207 docker exec -it server-vpp gdb -ex "attach $(docker exec server-vpp pidof vpp)"
208 Afterwards press CTRL+C to continue
209
210If a test consists of more VPP instances then this is done for each of them.
211
212
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100213**Eternal dependencies**
214
215* Linux tools ``ip``, ``brctl``
216* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
217 they are reasonably up-to-date automatically
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100218* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
219* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100220* Non-standard Go libraries - see ``extras/hs-test/go.mod``
221
222Generally, these will be updated on a per-need basis, for example when a bug is discovered
223or a new version incompatibility issue occurs.
224
225
226.. _testing: https://pkg.go.dev/testing
227.. _go test: https://pkg.go.dev/cmd/go#hdr-Test_packages
228.. _test suite: https://github.com/stretchr/testify#suite-package
229.. _volumes: https://docs.docker.com/storage/volumes/
230