blob: 8a49ac6e7e2b7a2f8618cd78628aeb0514000e73 [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
Adrian Villin4677d922024-06-14 09:32:39 +020029#. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run, use ``make cleanup-hst`` to clean up),
Adrian Villincee15aa2024-03-14 11:42:55 -040030 TEST=<test-name> to run a specific test and PARALLEL=[n-cpus].
Adrian Villin4677d922024-06-14 09:32:39 +020031#. ``make list-tests`` (or ``make help``) shows all tests.
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 ``Register[SuiteName]Test()``. Each of these functions correspond to a suite.
Adrian Villincee15aa2024-03-14 11:42:55 -040034#. 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
Adrian Villin4677d922024-06-14 09:32:39 +020037 #. ``Describe`` container nodes in suite\_\*.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 Villin4677d922024-06-14 09:32:39 +020050#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``infra/suite_*.go``)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010051#. Implement test behaviour inside the test method. This typically includes the following:
52
Adrian Villin4677d922024-06-14 09:32:39 +020053 #. Import ``. "fd.io/hs-test/infra"``
54 #. Retrieve a running container in which to run some action. Method ``GetContainerByName``
Adrian Villincee15aa2024-03-14 11:42:55 -040055 from ``HstSuite`` struct serves this purpose
Adrian Villin4677d922024-06-14 09:32:39 +020056 #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``Vppctl`` method to access debug CLI
57 #. Run arbitrary commands inside the containers with ``Exec`` method
58 #. Run other external tool with one of the preexisting functions in the ``infra/utils.go`` file.
59 For example, use ``wget`` with ``StartWget`` function
Adrian Villincee15aa2024-03-14 11:42:55 -040060 #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
Adrian Villin4677d922024-06-14 09:32:39 +020061 #. Verify results of your tests using ``Assert`` methods provided by the test suite.
Adrian Villincee15aa2024-03-14 11:42:55 -040062
Adrian Villin4677d922024-06-14 09:32:39 +020063#. Create an ``init()`` function and register the test using ``Register[SuiteName]Tests(testCaseFunction)``
Adrian Villincee15aa2024-03-14 11:42:55 -040064
Maros Ondrejicka7943c902022-11-08 08:00:51 +010065
66**Example test case**
67
Maros Ondrejicka56bfc632023-02-21 13:42:35 +010068Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
Adrian Villin4677d922024-06-14 09:32:39 +020069This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest``.
Maros Ondrejicka7943c902022-11-08 08:00:51 +010070
71::
72
73 package main
74
75 import (
Adrian Villin4677d922024-06-14 09:32:39 +020076 . "fd.io/hs-test/infra"
Maros Ondrejicka7943c902022-11-08 08:00:51 +010077 )
78
Adrian Villincee15aa2024-03-14 11:42:55 -040079 func init(){
Adrian Villin4677d922024-06-14 09:32:39 +020080 RegisterMySuiteTest(MyTest)
Adrian Villincee15aa2024-03-14 11:42:55 -040081 }
82
83 func MyTest(s *MySuite) {
Adrian Villin4677d922024-06-14 09:32:39 +020084 clientVpp := s.GetContainerByName("client-vpp").VppInstance
Maros Ondrejicka7943c902022-11-08 08:00:51 +010085
Adrian Villin4677d922024-06-14 09:32:39 +020086 serverVethAddress := s.NetInterfaces["server-iface"].Ip4AddressString()
Maros Ondrejicka7943c902022-11-08 08:00:51 +010087
Adrian Villin4677d922024-06-14 09:32:39 +020088 result := clientVpp.Vppctl("ping " + serverVethAddress)
89 s.Log(result)
90 s.AssertNotNil(result)
Maros Ondrejicka7943c902022-11-08 08:00:51 +010091 }
92
Adrian Villin681ff3a2024-06-07 06:45:48 -040093
94Filtering test cases
95--------------------
96
97The framework allows us to filter test cases in a few different ways, using ``make test TEST=``:
Adrian Villin4677d922024-06-14 09:32:39 +020098
99 * Suite name
100 * File name
101 * Test name
102 * All of the above as long as they are ordered properly, e.g. ``make test TEST=VethsSuite.http_test.go.HeaderServerTest``
Adrian Villin681ff3a2024-06-07 06:45:48 -0400103
104**Names are case sensitive!**
105
106Names don't have to be complete, as long as they are last:
107This is valid and will run all tests in every ``http`` file (if there is more than one):
Adrian Villin4677d922024-06-14 09:32:39 +0200108
109* ``make test TEST=VethsSuite.http``
110
Adrian Villin681ff3a2024-06-07 06:45:48 -0400111This is not valid:
Adrian Villin4677d922024-06-14 09:32:39 +0200112
113* ``make test TEST=Veths.http``
Adrian Villin681ff3a2024-06-07 06:45:48 -0400114
115They can also be left out:
Adrian Villin4677d922024-06-14 09:32:39 +0200116
117* ``make test TEST=http_test.go`` will run every test in ``http_test.go``
118* ``make test TEST=Nginx`` will run everything that has 'Nginx' in its name - suites, files and tests.
119* ``make test TEST=HeaderServerTest`` will only run the header server test
Adrian Villin681ff3a2024-06-07 06:45:48 -0400120
121
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100122Modifying the framework
123-----------------------
124
125**Adding a test suite**
126
127.. _test-convention:
128
Adrian Villin4677d922024-06-14 09:32:39 +0200129#. To add a new suite, create a new file in the ``infra/`` folder. Naming convention for the suite files is ``suite_[name].go``.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100130
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100131#. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100132 HstSuite provides functionality that can be shared for all suites, like starting containers
133
Adrian Villin4677d922024-06-14 09:32:39 +0200134#. Create a new map that will contain a file name where a test is located and test functions with a pointer to the suite's struct: ``var myTests = map[string][]func(s *MySuite){}``
135
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100136 ::
137
Adrian Villin4677d922024-06-14 09:32:39 +0200138 var myTests = map[string][]func(s *MySuite){}
139
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100140 type MySuite struct {
141 HstSuite
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100142 }
143
Adrian Villincee15aa2024-03-14 11:42:55 -0400144
Adrian Villin4677d922024-06-14 09:32:39 +0200145#. Then create a new function that will add tests to that map:
Adrian Villincee15aa2024-03-14 11:42:55 -0400146
147 ::
148
Adrian Villin4677d922024-06-14 09:32:39 +0200149 func RegisterMyTests(tests ...func(s *MySuite)) {
150 myTests[getTestFilename()] = tests
Adrian Villincee15aa2024-03-14 11:42:55 -0400151 }
152
Adrian Villin4677d922024-06-14 09:32:39 +0200153
Adrian Villincee15aa2024-03-14 11:42:55 -0400154#. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests.
Adrian Villin4677d922024-06-14 09:32:39 +0200155 It's important here to call ``ConfigureNetworkTopology()`` method,
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100156 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 +0100157 Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
158 This will ensure network topology, such as network interfaces and namespaces, will be created.
Adrian Villin4677d922024-06-14 09:32:39 +0200159 Another important method to call is ``LoadContainerTopology()`` which will load
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100160 containers and shared volumes used by the suite. This time the name passed to method corresponds
161 to file in ``extras/hs-test/topo-containers`` folder
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100162
163 ::
164
165 func (s *MySuite) SetupSuite() {
Adrian Villincee15aa2024-03-14 11:42:55 -0400166 s.HstSuite.SetupSuite()
167
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100168 // Add custom setup code here
169
Adrian Villin4677d922024-06-14 09:32:39 +0200170 s.ConfigureNetworkTopology("myTopology")
171 s.LoadContainerTopology("2peerVeth")
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100172 }
173
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100174#. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and
175 configuring VPP is usually placed here
176
177 ::
178
179 func (s *MySuite) SetupTest() {
Adrian Villincee15aa2024-03-14 11:42:55 -0400180 s.HstSuite.setupTest()
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100181 s.SetupVolumes()
182 s.SetupContainers()
183 }
184
Adrian Villincee15aa2024-03-14 11:42:55 -0400185#. In order for ``Ginkgo`` to run this suite, we need to create a ``Describe`` container node with setup nodes and an ``It`` subject node.
186 Place them at the end of the suite file
187
188 * Declare a suite struct variable before anything else
189 * To use ``BeforeAll()`` and ``AfterAll()``, the container has to be marked as ``Ordered``
190 * Because the container is now marked as Ordered, if a test fails, all the subsequent tests are skipped.
191 To override this behavior, decorate the container node with ``ContinueOnFailure``
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100192
193 ::
194
Adrian Villincee15aa2024-03-14 11:42:55 -0400195 var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() {
Adrian Villin4677d922024-06-14 09:32:39 +0200196 var s MySuite
197 BeforeAll(func() {
198 s.SetupSuite()
Adrian Villincee15aa2024-03-14 11:42:55 -0400199 })
Adrian Villin4677d922024-06-14 09:32:39 +0200200 BeforeEach(func() {
201 s.SetupTest()
202 })
203 AfterAll(func() {
204 s.TearDownSuite()
205 })
206 AfterEach(func() {
207 s.TearDownTest()
208 })
209
210 for filename, tests := range myTests {
211 for _, test := range tests {
212 test := test
213 pc := reflect.ValueOf(test).Pointer()
214 funcValue := runtime.FuncForPC(pc)
215 testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
216 It(testName, func(ctx SpecContext) {
217 s.Log(testName + ": BEGIN")
218 test(&s)
219 }, SpecTimeout(SuiteTimeout))
220 }
221 }
Adrian Villincee15aa2024-03-14 11:42:55 -0400222 })
223
224#. Notice the loop - it will generate multiple ``It`` nodes, each running a different test.
225 ``test := test`` is necessary, otherwise only the last test in a suite will run.
226 For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\.
227
Adrian Villin4677d922024-06-14 09:32:39 +0200228#. ``testName`` contains the test name in the following format: ``[name]_test.go/MyTest``.
Adrian Villincee15aa2024-03-14 11:42:55 -0400229
Adrian Villin4677d922024-06-14 09:32:39 +0200230#. To run certain tests solo, create a register function and a map that will only contain tests that have to run solo.
Adrian Villincee15aa2024-03-14 11:42:55 -0400231 Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node:
232
233 ::
234
235 var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
236 ...
Adrian Villin4677d922024-06-14 09:32:39 +0200237 It(testName, Label("SOLO"), func(ctx SpecContext) {
238 s.Log(testName + ": BEGIN")
239 test(&s)
Adrian Villincee15aa2024-03-14 11:42:55 -0400240 }, SpecTimeout(time.Minute*5))
241 })
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100242
243#. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above
244
245**Adding a topology element**
246
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100247Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and
248``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100249is started by the ``configureNetworkTopology`` method depending on which file's name is passed to it.
250Specified file is loaded and converted into internal data structures which represent various elements of the topology.
251After 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 +0100252
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100253These are currently supported types of network elements.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100254
255* ``netns`` - network namespace
256* ``veth`` - veth network interface, optionally with target network namespace or IPv4 address
257* ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace
258* ``tap`` - tap network interface with IP address
259
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100260Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed
261so that test suite retains map of defined containers and uses that to start them at the beginning
262of each test case and stop containers after the test finishes. Container configuration can specify
263also volumes which allow to share data between containers or between host system and containers.
264
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100265Supporting a new type of topology element requires adding code to recognize the new element type during loading.
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100266And adding code to set up the element in the host system with some Linux tool, such as *ip*.
267This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100268
269**Communicating between containers**
270
271When two VPP instances or other applications, each in its own Docker container,
272want to communicate there are typically two ways this can be done within *hs-test*.
273
274* Network interfaces. Containers are being created with ``-d --network host`` options,
275 so they are connected with interfaces created in host system
276* Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers
277 or just between containers
278
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100279Host system connects to VPP instances running in containers using a shared folder
280where binary API socket is accessible by both sides.
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100281
282**Adding an external tool**
283
284If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function.
285These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image,
286add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command.
287Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied.
288
Filip Tehlar94181432024-01-15 13:11:28 +0100289**Skipping tests**
290
291``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 -0400292``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. You can also use Ginkgo's ``Skip()``.
Filip Tehlar94181432024-01-15 13:11:28 +0100293However 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 -0400294test 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 +0100295
Filip Tehlarf34b6802024-01-24 15:11:27 +0100296**Debugging a test**
297
298It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows:
299
300::
301
Adrian Villincee15aa2024-03-14 11:42:55 -0400302 $ make test TEST=LDPreloadIperfVppTest DEBUG=true
Filip Tehlarf34b6802024-01-24 15:11:27 +0100303 ...
304 run following command in different terminal:
Adrian Villincee15aa2024-03-14 11:42:55 -0400305 docker exec -it server-vpp2456109 gdb -ex "attach $(docker exec server-vpp2456109 pidof vpp)"
306 Afterwards press CTRL+\ to continue
Filip Tehlarf34b6802024-01-24 15:11:27 +0100307
308If a test consists of more VPP instances then this is done for each of them.
309
Matus Fabiane99d2662024-07-19 16:04:09 +0200310**Memory leak testing**
Filip Tehlarf34b6802024-01-24 15:11:27 +0100311
Matus Fabiane99d2662024-07-19 16:04:09 +0200312It is possible to use VPP memory traces to diagnose if and where memory leaks happen by comparing of two traces at different point in time.
313You can do it by test like following:
314
315::
316
317 func MemLeakTest(s *NoTopoSuite) {
318 s.SkipUnlessLeakCheck() // test is excluded from usual test run
319 vpp := s.GetContainerByName("vpp").VppInstance
320 /* do your configuration here */
321 vpp.Disconnect() // no goVPP less noise
322 vpp.EnableMemoryTrace() // enable memory traces
323 traces1, err := vpp.GetMemoryTrace() // get first sample
324 s.AssertNil(err, fmt.Sprint(err))
325 vpp.Vppctl("test mem-leak") // execute some action
326 traces2, err := vpp.GetMemoryTrace() // get second sample
327 s.AssertNil(err, fmt.Sprint(err))
328 vpp.MemLeakCheck(traces1, traces2) // compare samples and generate report
329 }
330
331To get your memory leak report run following command:
332
333::
334
335 $ make test-leak TEST=MemLeakTest
336 ...
337 NoTopoSuiteSolo mem_leak_test.go/MemLeakTest [SOLO]
338 /home/matus/vpp/extras/hs-test/infra/suite_no_topo.go:113
339
340 Report Entries >>
341
342 SUMMARY: 112 byte(s) leaked in 1 allocation(s)
343 - /home/matus/vpp/extras/hs-test/infra/vppinstance.go:624 @ 07/19/24 15:53:33.539
344
345 leak of 112 byte(s) in 1 allocation(s) from:
346 #0 clib_mem_heap_alloc_aligned + 0x31
347 #1 _vec_alloc_internal + 0x113
348 #2 _vec_validate + 0x81
349 #3 leak_memory_fn + 0x4f
350 #4 0x7fc167815ac3
351 #5 0x7fc1678a7850
352 << Report Entries
353 ------------------------------
354
355**External dependencies**
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100356
357* Linux tools ``ip``, ``brctl``
358* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
359 they are reasonably up-to-date automatically
Maros Ondrejicka56bfc632023-02-21 13:42:35 +0100360* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
361* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100362* Non-standard Go libraries - see ``extras/hs-test/go.mod``
363
364Generally, these will be updated on a per-need basis, for example when a bug is discovered
365or a new version incompatibility issue occurs.
366
367
Adrian Villincee15aa2024-03-14 11:42:55 -0400368.. _ginkgo: https://onsi.github.io/ginkgo/
Maros Ondrejicka7943c902022-11-08 08:00:51 +0100369.. _volumes: https://docs.docker.com/storage/volumes/