blob: 1dc1039b33f0d4fe80b32eee97ad731a8cfe2ef6 [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
Adrian Villincee15aa2024-03-14 11:42:55 -040012`Ginkgo`_ forms the base framework upon which the *hs-test* is built and run.
13All tests are technically in a single suite because we are only using ``package main``. We simulate suite behavior by grouping tests by the topology they require.
14This allows us to run those mentioned groups in parallel, but not individual tests in parallel.
15
Maros Ondrejicka7943c902022-11-08 08:00:51 +010016
17Anatomy of a test case
18----------------------
19
20**Prerequisites**:
21
Filip Tehlar671cf512023-01-31 10:34:18 +010022* Install hs-test dependencies with ``make install-deps``
23* 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 +010024* Docker has to be installed and Go has to be in path of both the running user and root
25* Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology
26
27**Action flow when running a test case**:
28
Filip Tehlar671cf512023-01-31 10:34:18 +010029#. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run),
Adrian Villincee15aa2024-03-14 11:42:55 -040030 TEST=<test-name> to run a specific test and PARALLEL=[n-cpus].
31#. ``make list-tests`` (or ``make help``) shows all tests. The current `list of tests`_ is at the bottom of this document.
32#. ``Ginkgo`` looks for a spec suite in the current directory and then compiles it to a .test binary
33#. The Ginkgo test framework runs each function that was registered manually using ``registerMySuiteTest(s *MySuite)``. Each of these functions correspond to a suite
34#. Ginkgo's ``RunSpecs(t, "Suite description")`` function is the entry point and does the following:
Maros Ondrejicka7943c902022-11-08 08:00:51 +010035
Adrian Villincee15aa2024-03-14 11:42:55 -040036 #. Ginkgo compiles the spec, builds a spec tree
37 #. ``Describe`` container nodes in suite\_\*_test.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus])
Maros Ondrejicka7943c902022-11-08 08:00:51 +010038 #. Suite is initialized. The topology is loaded and configured in this step
Adrian Villincee15aa2024-03-14 11:42:55 -040039 #. Registered tests are run in generated ``It`` subject nodes
Maros Ondrejicka7943c902022-11-08 08:00:51 +010040 #. Execute tear-down functions, which currently consists of stopping running containers
41 and clean-up of test topology
42
43Adding a test case
44------------------
45
46This describes adding a new test case to an existing suite.
47For adding a new suite, please see `Modifying the framework`_ below.
48
49#. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
Adrian Villincee15aa2024-03-14 11:42:55 -040050#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``suite_*_test.go``)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010051#. Implement test behaviour inside the test method. This typically includes the following:
52
Adrian Villincee15aa2024-03-14 11:42:55 -040053 #. Retrieve a running container in which to run some action. Method ``getContainerByName``
54 from ``HstSuite`` struct serves this purpose
55 #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
56 #. Run arbitrary commands inside the containers with ``exec`` method
57 #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
58 For example, use ``wget`` with ``startWget`` function
59 #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
60 #. Verify results of your tests using ``assert`` methods provided by the test suite, implemented by HstSuite struct or use ``Gomega`` assert functions.
61
62#. Create an ``init()`` function and register the test using ``register*SuiteTests(testCaseFunction)``
63
Maros Ondrejicka7943c902022-11-08 08:00:51 +010064
65**Example test case**
66
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010067Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
Adrian Villincee15aa2024-03-14 11:42:55 -040068This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest`` or ``ginkgo -v --trace --focus MyTest``.
Maros Ondrejicka7943c902022-11-08 08:00:51 +010069
70::
71
72 package main
73
74 import (
75 "fmt"
Maros Ondrejicka7943c902022-11-08 08:00:51 +010076 )
77
Adrian Villincee15aa2024-03-14 11:42:55 -040078 func init(){
79 registerMySuiteTest(MyTest)
80 }
81
82 func MyTest(s *MySuite) {
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010083 clientVpp := s.getContainerByName("client-vpp").vppInstance
Maros Ondrejicka7943c902022-11-08 08:00:51 +010084
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010085 serverVethAddress := s.netInterfaces["server-iface"].AddressString()
Maros Ondrejicka7943c902022-11-08 08:00:51 +010086
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010087 result := clientVpp.vppctl("ping " + serverVethAddress)
88 s.assertNotNil(result)
89 s.log(result)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010090 }
91
92Modifying the framework
93-----------------------
94
95**Adding a test suite**
96
97.. _test-convention:
98
Adrian Villincee15aa2024-03-14 11:42:55 -040099#. To add a new suite, create a new file. Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100100 by the actual name
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100101
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100102#. Make a ``struct``, in the suite file, 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
Adrian Villincee15aa2024-03-14 11:42:55 -0400111#. Create a new slice that will contain test functions with a pointer to the suite's struct: ``var myTests = []func(s *MySuite){}``
112
113#. Then create a new function that will append test functions to that slice:
114
115 ::
116
117 func registerMySuiteTests(tests ...func(s *MySuite)) {
118 nginxTests = append(myTests, tests...)
119 }
120
121#. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100122 It's important here to call ``configureNetworkTopology`` method,
123 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 +0100124 Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
125 This will ensure network topology, such as network interfaces and namespaces, will be created.
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100126 Another important method to call is ``loadContainerTopology()`` which will load
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100127 containers and shared volumes used by the suite. This time the name passed to method corresponds
128 to file in ``extras/hs-test/topo-containers`` folder
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100129
130 ::
131
132 func (s *MySuite) SetupSuite() {
Adrian Villincee15aa2024-03-14 11:42:55 -0400133 s.HstSuite.SetupSuite()
134
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100135 // Add custom setup code here
136
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100137 s.configureNetworkTopology("myTopology")
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100138 s.loadContainerTopology("2peerVeth")
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100139 }
140
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100141#. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and
142 configuring VPP is usually placed here
143
144 ::
145
146 func (s *MySuite) SetupTest() {
Adrian Villincee15aa2024-03-14 11:42:55 -0400147 s.HstSuite.setupTest()
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100148 s.SetupVolumes()
149 s.SetupContainers()
150 }
151
Adrian Villincee15aa2024-03-14 11:42:55 -0400152#. In order for ``Ginkgo`` to run this suite, we need to create a ``Describe`` container node with setup nodes and an ``It`` subject node.
153 Place them at the end of the suite file
154
155 * Declare a suite struct variable before anything else
156 * To use ``BeforeAll()`` and ``AfterAll()``, the container has to be marked as ``Ordered``
157 * Because the container is now marked as Ordered, if a test fails, all the subsequent tests are skipped.
158 To override this behavior, decorate the container node with ``ContinueOnFailure``
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100159
160 ::
161
Adrian Villincee15aa2024-03-14 11:42:55 -0400162 var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() {
163 var s MySuite
164 BeforeAll(func() {
165 s.SetupSuite()
166 })
167 BeforeEach(func() {
168 s.SetupTest()
169 })
170 AfterAll(func() {
171 s.TearDownSuite()
172 })
173 AfterEach(func() {
174 s.TearDownTest()
175 })
176 for _, test := range mySuiteTests {
177 test := test
178 pc := reflect.ValueOf(test).Pointer()
179 funcValue := runtime.FuncForPC(pc)
180 It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
181 test(&s)
182 }, SpecTimeout(time.Minute*5))
183 }
184 })
185
186#. Notice the loop - it will generate multiple ``It`` nodes, each running a different test.
187 ``test := test`` is necessary, otherwise only the last test in a suite will run.
188 For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\.
189
190#. ``funcValue.Name()`` returns the full name of a function (e.g. ``fd.io/hs-test.MyTest``), however, we only need the test name (``MyTest``).
191
192#. To run certain tests solo, create a new slice that will only contain tests that have to run solo and a new register function.
193 Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node:
194
195 ::
196
197 var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
198 ...
199 It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
200 test(&s)
201 }, SpecTimeout(time.Minute*5))
202 })
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100203
204#. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
205
206**Adding a topology element**
207
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100208Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and
209``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100210is started by the ``configureNetworkTopology`` method depending on which file's name is passed to it.
211Specified file is loaded and converted into internal data structures which represent various elements of the topology.
212After 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 +0100213
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100214These are currently supported types of network elements.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100215
216* ``netns`` - network namespace
217* ``veth`` - veth network interface, optionally with target network namespace or IPv4 address
218* ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace
219* ``tap`` - tap network interface with IP address
220
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100221Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed
222so that test suite retains map of defined containers and uses that to start them at the beginning
223of each test case and stop containers after the test finishes. Container configuration can specify
224also volumes which allow to share data between containers or between host system and containers.
225
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100226Supporting a new type of topology element requires adding code to recognize the new element type during loading.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100227And adding code to set up the element in the host system with some Linux tool, such as *ip*.
228This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100229
230**Communicating between containers**
231
232When two VPP instances or other applications, each in its own Docker container,
233want to communicate there are typically two ways this can be done within *hs-test*.
234
235* Network interfaces. Containers are being created with ``-d --network host`` options,
236 so they are connected with interfaces created in host system
237* Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers
238 or just between containers
239
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100240Host system connects to VPP instances running in containers using a shared folder
241where binary API socket is accessible by both sides.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100242
243**Adding an external tool**
244
245If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function.
246These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image,
247add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command.
248Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied.
249
Filip Tehlar94181432024-01-15 13:11:28 +0100250**Skipping tests**
251
252``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as:
Adrian Villincee15aa2024-03-14 11:42:55 -0400253``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. You can also use Ginkgo's ``Skip()``.
Filip Tehlar94181432024-01-15 13:11:28 +0100254However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
Adrian Villincee15aa2024-03-14 11:42:55 -0400255test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test.
Filip Tehlar94181432024-01-15 13:11:28 +0100256
Filip Tehlarf34b6802024-01-24 15:11:27 +0100257**Debugging a test**
258
259It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows:
260
261::
262
Adrian Villincee15aa2024-03-14 11:42:55 -0400263 $ make test TEST=LDPreloadIperfVppTest DEBUG=true
Filip Tehlarf34b6802024-01-24 15:11:27 +0100264 ...
265 run following command in different terminal:
Adrian Villincee15aa2024-03-14 11:42:55 -0400266 docker exec -it server-vpp2456109 gdb -ex "attach $(docker exec server-vpp2456109 pidof vpp)"
267 Afterwards press CTRL+\ to continue
Filip Tehlarf34b6802024-01-24 15:11:27 +0100268
269If a test consists of more VPP instances then this is done for each of them.
270
271
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100272**Eternal dependencies**
273
274* Linux tools ``ip``, ``brctl``
275* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
276 they are reasonably up-to-date automatically
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100277* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
278* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100279* Non-standard Go libraries - see ``extras/hs-test/go.mod``
280
281Generally, these will be updated on a per-need basis, for example when a bug is discovered
282or a new version incompatibility issue occurs.
283
284
Adrian Villincee15aa2024-03-14 11:42:55 -0400285.. _ginkgo: https://onsi.github.io/ginkgo/
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100286.. _volumes: https://docs.docker.com/storage/volumes/
287
Adrian Villincee15aa2024-03-14 11:42:55 -0400288**List of tests**
289
290.. _list of tests:
291
292Please update this list whenever you add a new test by pasting the output below.
293
294* NsSuite/HttpTpsTest
295* NsSuite/VppProxyHttpTcpTest
296* NsSuite/VppProxyHttpTlsTest
297* NsSuite/EnvoyProxyHttpTcpTest
298* NginxSuite/MirroringTest
299* VethsSuiteSolo TcpWithLossTest [SOLO]
300* NoTopoSuiteSolo HttpStaticPromTest [SOLO]
301* TapSuite/LinuxIperfTest
302* NoTopoSuite/NginxHttp3Test
303* NoTopoSuite/NginxAsServerTest
304* NoTopoSuite/NginxPerfCpsTest
305* NoTopoSuite/NginxPerfRpsTest
306* NoTopoSuite/NginxPerfWrkTest
307* VethsSuite/EchoBuiltinTest
308* VethsSuite/HttpCliTest
309* VethsSuite/LDPreloadIperfVppTest
310* VethsSuite/VppEchoQuicTest
311* VethsSuite/VppEchoTcpTest
312* VethsSuite/VppEchoUdpTest
313* VethsSuite/XEchoVclClientUdpTest
314* VethsSuite/XEchoVclClientTcpTest
315* VethsSuite/XEchoVclServerUdpTest
316* VethsSuite/XEchoVclServerTcpTest
317* VethsSuite/VclEchoTcpTest
318* VethsSuite/VclEchoUdpTest
319* VethsSuite/VclRetryAttachTest