blob: 47d3a53ecbf3c34e31ee1737018eb799ccbc2ff0 [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
19* Tests use *hs-test*'s own docker image, so building it before starting tests is a prerequisite. Run ``sudo make`` to do so
20* Docker has to be installed and Go has to be in path of both the running user and root
21* Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology
22
23**Action flow when running a test case**:
24
25#. It starts with running ``./test``. This script is basically a wrapper for ``go test`` and accepts its parameters,
26 for example following runs a specific test: ``./test -run TestNs/TestHttpTps``
27#. ``go test`` compiles package ``main`` along with any files with names matching the file pattern ``*_test.go``
28 and then runs the resulting test binaries
29#. The go test framework runs each function matching :ref:`naming convention<test-convention>`. Each of these corresponds to a `test suite`_
30#. Testify toolkit's ``suite.Run(t *testing.T, suite TestingSuite)`` function runs the suite and does the following:
31
32 #. Suite is initialized. The topology is loaded and configured in this step
33 #. Test suite runs all the tests attached to it
34 #. Execute tear-down functions, which currently consists of stopping running containers
35 and clean-up of test topology
36
37Adding a test case
38------------------
39
40This describes adding a new test case to an existing suite.
41For adding a new suite, please see `Modifying the framework`_ below.
42
43#. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
44#. Declare method whose name starts with ``Test`` and specifies its receiver as a pointer to the suite's struct (defined in ``framework_test.go``)
45#. Implement test behaviour inside the test method. This typically includes the following:
46
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010047 #. Retrieve a running container in which to run some action. Function ``getContainerByName(name string)``
48 from ``HstSuite`` struct serves this purpose
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010049 an object representing a container and start it with ``run()`` method
Maros Ondrejicka7943c902022-11-08 08:00:51 +010050 #. Execute *hs-test* action(s) inside any of the running containers.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010051 Function ``execAction(args string)`` from ``container.go`` does this by using ``docker exec`` command to run ``hs-test`` executable.
52 For starting an VPP instance inside a container, the ``VppInstance`` struct can be used instead
53 #. Run arbitrary commands inside the containers with ``exec(cmd string)``
Maros Ondrejicka7943c902022-11-08 08:00:51 +010054 #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
55 For example, use ``wget`` with ``startWget(..)`` function
56 #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010057 #. Verify results of your tests using ``assert`` methods provided by the test suite,
58 implemented by HstSuite struct
Maros Ondrejicka7943c902022-11-08 08:00:51 +010059
60**Example test case**
61
62Two docker containers, each with its own VPP instance running. One VPP then pings the other.
63This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./test -run TestMySuite``.
64
65::
66
67 package main
68
69 import (
70 "fmt"
Maros Ondrejicka7943c902022-11-08 08:00:51 +010071 )
72
73 func (s *MySuite) TestMyCase() {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010074 serverVppContainer := s.getContainerByName("server-vpp")
Maros Ondrejicka7943c902022-11-08 08:00:51 +010075
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010076 serverVpp := NewVppInstance(serverContainer)
77 serverVpp.set2VethsServer()
78 serverVpp.start()
Maros Ondrejicka7943c902022-11-08 08:00:51 +010079
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010080 clientVppContainer := s.getContainerByName("client-vpp")
Maros Ondrejicka7943c902022-11-08 08:00:51 +010081
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010082 clientVpp:= NewVppInstance(clientContainer)
83 serverVpp.set2VethsClient()
84 clientVpp.start()
Maros Ondrejicka7943c902022-11-08 08:00:51 +010085
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010086 result, err := clientVpp.vppctl("ping 10.10.10.2")
87 s.assertNil(err, "ping resulted in error")
88 fmt.Println(result)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010089 }
90
91Modifying the framework
92-----------------------
93
94**Adding a test suite**
95
96.. _test-convention:
97
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010098#. Adding a new suite takes place in ``framework_test.go`` and by creating a new file for the suite.
99 Naming convention for the suite files is ``suite-name-test.go`` where *name* will be replaced
100 by the actual name
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100101
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100102#. Make a ``struct`` with at least ``HstSuite`` struct as its member.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100103 HstSuite provides functionality that can be shared for all suites, like starting containers
104
105 ::
106
107 type MySuite struct {
108 HstSuite
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100109 }
110
111#. Implement SetupSuite method which testify runs before running the tests.
112 It's important here to call ``setupSuite(s *suite.Suite, topologyName string)`` and assign its result to the suite's ``teardownSuite`` member.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100113 Pass the topology name to the function in the form of file name of one of the *yaml* files in ``topo-network`` folder.
114 Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
115 This will ensure network topology, such as network interfaces and namespaces, will be created.
116 Another important method to call is ``loadContainerTopology(topologyName string)`` which will load
117 containers and shared volumes used by the suite. This time the name passed to method corresponds
118 to file in ``extras/hs-test/topo-containers`` folder
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100119
120 ::
121
122 func (s *MySuite) SetupSuite() {
123 // Add custom setup code here
124
125 s.teardownSuite = setupSuite(&s.Suite, "myTopology")
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100126 s.loadContainerTopology("2peerVeth")
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100127 }
128
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100129#. In order for ``go test`` to run this suite, we need to create a normal test function and pass our suite to ``suite.Run``.
130 This is being at the end of ``framework_test.go``
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100131
132 ::
133
134 func TestMySuite(t *testing.T) {
135 var m MySuite
136 suite.Run(t, &m)
137 }
138
139#. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
140
141**Adding a topology element**
142
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100143Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and
144``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite
145is started by the ``setupSuite`` function depending on which file's name is passed to it.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100146Specified file is loaded by ``LoadTopology()`` function and converted into internal data structures which represent various elements of the topology.
147After parsing the configuration, ``Configure()`` method loops over array of topology elements and configures them one by one.
148
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100149These are currently supported types of network elements.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100150
151* ``netns`` - network namespace
152* ``veth`` - veth network interface, optionally with target network namespace or IPv4 address
153* ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace
154* ``tap`` - tap network interface with IP address
155
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100156Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed
157so that test suite retains map of defined containers and uses that to start them at the beginning
158of each test case and stop containers after the test finishes. Container configuration can specify
159also volumes which allow to share data between containers or between host system and containers.
160
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100161Supporting a new type of topology element requires adding code to recognize the new element type during loading.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100162And adding code to set up the element in the host system with some Linux tool, such as *ip*.
163This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100164
165**Communicating between containers**
166
167When two VPP instances or other applications, each in its own Docker container,
168want to communicate there are typically two ways this can be done within *hs-test*.
169
170* Network interfaces. Containers are being created with ``-d --network host`` options,
171 so they are connected with interfaces created in host system
172* Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers
173 or just between containers
174
175**Adding a hs-test action**
176
177Executing more complex or long running jobs is made easier by *hs-test* actions.
178These are functions that compartmentalize configuration and execution together for a specific task.
179For example, starting up VPP or running VCL echo client.
180
181The actions are located in ``extras/hs-test/actions.go``. To add one, create a new method that has its receiver as a pointer to ``Actions`` struct.
182
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100183Run it from test case with container's method ``execAction(args)`` where ``args`` is the action method's name.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100184This then executes the ``hs-test`` binary inside of the container and it then runs selected action.
185Action is specified by its name as first argument for the binary.
186
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100187*Note*: When ``execAction(args)`` runs some action from a test case, the execution of ``hs-test`` inside the container
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100188is asynchronous. The action might take many seconds to finish, while the test case execution context continues to run.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100189To mitigate this, ``execAction(args)`` waits pre-defined arbitrary number of seconds for a *sync file* to be written by ``hs-test``
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100190at the end of its run. The test case context and container use Docker volume to share the file.
191
192**Adding an external tool**
193
194If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function.
195These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image,
196add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command.
197Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied.
198
199**Eternal dependencies**
200
201* Linux tools ``ip``, ``brctl``
202* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
203 they are reasonably up-to-date automatically
204* Programs in Docker images - see ``envoyproxy/envoy-contrib`` in ``utils.go``
205* ``http_server`` - homegrown application that listens on specified address and sends a test file in response
206* Non-standard Go libraries - see ``extras/hs-test/go.mod``
207
208Generally, these will be updated on a per-need basis, for example when a bug is discovered
209or a new version incompatibility issue occurs.
210
211
212.. _testing: https://pkg.go.dev/testing
213.. _go test: https://pkg.go.dev/cmd/go#hdr-Test_packages
214.. _test suite: https://github.com/stretchr/testify#suite-package
215.. _volumes: https://docs.docker.com/storage/volumes/
216