Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 1 | Host stack test framework |
| 2 | ========================= |
| 3 | |
| 4 | Overview |
| 5 | -------- |
| 6 | |
| 7 | The goal of the Host stack test framework (**hs-test**) is to ease writing and running end-to-end tests for VPP's Host Stack. |
| 8 | End-to-end tests often want multiple VPP instances, network namespaces, different types of interfaces |
| 9 | and to execute external tools or commands. With such requirements the existing VPP test framework is not sufficient. |
| 10 | For 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 Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 12 | `Ginkgo`_ forms the base framework upon which the *hs-test* is built and run. |
| 13 | All 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. |
| 14 | This allows us to run those mentioned groups in parallel, but not individual tests in parallel. |
| 15 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 16 | |
| 17 | Anatomy of a test case |
| 18 | ---------------------- |
| 19 | |
| 20 | **Prerequisites**: |
| 21 | |
Filip Tehlar | 671cf51 | 2023-01-31 10:34:18 +0100 | [diff] [blame] | 22 | * 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 Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 24 | * 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 Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 29 | #. 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 Villin | f151640 | 2024-10-09 11:03:35 +0200 | [diff] [blame] | 30 | TEST=<test-name> to run a specific test and PARALLEL=[n-cpus]. If you want to run multiple specific tests, separate their names with a comma. |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 31 | #. ``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 Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 34 | #. Ginkgo's ``RunSpecs(t, "Suite description")`` function is the entry point and does the following: |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 35 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 36 | #. Ginkgo compiles the spec, builds a spec tree |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 37 | #. ``Describe`` container nodes in suite\_\*.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus]) |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 38 | #. Suite is initialized. The topology is loaded and configured in this step |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 39 | #. Registered tests are run in generated ``It`` subject nodes |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 40 | #. Execute tear-down functions, which currently consists of stopping running containers |
| 41 | and clean-up of test topology |
| 42 | |
| 43 | Adding a test case |
| 44 | ------------------ |
| 45 | |
| 46 | This describes adding a new test case to an existing suite. |
| 47 | For 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 Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 50 | #. 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 Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 51 | #. Implement test behaviour inside the test method. This typically includes the following: |
| 52 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 53 | #. Import ``. "fd.io/hs-test/infra"`` |
| 54 | #. Retrieve a running container in which to run some action. Method ``GetContainerByName`` |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 55 | from ``HstSuite`` struct serves this purpose |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 56 | #. 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 Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 60 | #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 61 | #. Verify results of your tests using ``Assert`` methods provided by the test suite. |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 62 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 63 | #. Create an ``init()`` function and register the test using ``Register[SuiteName]Tests(testCaseFunction)`` |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 64 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 65 | |
| 66 | **Example test case** |
| 67 | |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 68 | Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other. |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 69 | This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest``. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 70 | |
Adrian Villin | d05f16d | 2024-11-20 11:11:35 +0100 | [diff] [blame] | 71 | To add a multi-worker test, name it ``[name]MTTest``. Doing this, the framework will allocate 3 CPUs to a VPP container, no matter what ``CPUS`` is set to. |
| 72 | Only a single multi-worker VPP container is supported for now. Please register multi-worker tests as Solo tests to avoid reusing the same cores |
| 73 | when running in parallel. |
| 74 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 75 | :: |
| 76 | |
| 77 | package main |
| 78 | |
| 79 | import ( |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 80 | . "fd.io/hs-test/infra" |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 81 | ) |
| 82 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 83 | func init(){ |
Adrian Villin | d05f16d | 2024-11-20 11:11:35 +0100 | [diff] [blame] | 84 | RegisterMySuiteTests(MyTest) |
| 85 | RegisterSoloMySuiteTests(MyMTTest) |
| 86 | } |
| 87 | |
| 88 | func MyMTTest(s *MySuite){ |
| 89 | MyTest(s) |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 90 | } |
| 91 | |
| 92 | func MyTest(s *MySuite) { |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 93 | clientVpp := s.GetContainerByName("client-vpp").VppInstance |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 94 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 95 | serverVethAddress := s.NetInterfaces["server-iface"].Ip4AddressString() |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 96 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 97 | result := clientVpp.Vppctl("ping " + serverVethAddress) |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 98 | s.AssertNotNil(result) |
Adrian Villin | d05f16d | 2024-11-20 11:11:35 +0100 | [diff] [blame] | 99 | s.Log(result) |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 100 | } |
| 101 | |
Adrian Villin | 681ff3a | 2024-06-07 06:45:48 -0400 | [diff] [blame] | 102 | |
| 103 | Filtering test cases |
| 104 | -------------------- |
| 105 | |
| 106 | The framework allows us to filter test cases in a few different ways, using ``make test TEST=``: |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 107 | |
| 108 | * Suite name |
| 109 | * File name |
| 110 | * Test name |
| 111 | * All of the above as long as they are ordered properly, e.g. ``make test TEST=VethsSuite.http_test.go.HeaderServerTest`` |
Adrian Villin | d05f16d | 2024-11-20 11:11:35 +0100 | [diff] [blame] | 112 | * Multiple tests/suites: ``make test TEST=HttpClient,LdpSuite`` |
Adrian Villin | 681ff3a | 2024-06-07 06:45:48 -0400 | [diff] [blame] | 113 | |
| 114 | **Names are case sensitive!** |
| 115 | |
| 116 | Names don't have to be complete, as long as they are last: |
| 117 | This is valid and will run all tests in every ``http`` file (if there is more than one): |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 118 | |
| 119 | * ``make test TEST=VethsSuite.http`` |
| 120 | |
Adrian Villin | 681ff3a | 2024-06-07 06:45:48 -0400 | [diff] [blame] | 121 | This is not valid: |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 122 | |
| 123 | * ``make test TEST=Veths.http`` |
Adrian Villin | 681ff3a | 2024-06-07 06:45:48 -0400 | [diff] [blame] | 124 | |
| 125 | They can also be left out: |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 126 | |
| 127 | * ``make test TEST=http_test.go`` will run every test in ``http_test.go`` |
| 128 | * ``make test TEST=Nginx`` will run everything that has 'Nginx' in its name - suites, files and tests. |
| 129 | * ``make test TEST=HeaderServerTest`` will only run the header server test |
Adrian Villin | 681ff3a | 2024-06-07 06:45:48 -0400 | [diff] [blame] | 130 | |
| 131 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 132 | Modifying the framework |
| 133 | ----------------------- |
| 134 | |
| 135 | **Adding a test suite** |
| 136 | |
| 137 | .. _test-convention: |
| 138 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 139 | #. To add a new suite, create a new file in the ``infra/`` folder. Naming convention for the suite files is ``suite_[name].go``. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 140 | |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 141 | #. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 142 | HstSuite provides functionality that can be shared for all suites, like starting containers |
| 143 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 144 | #. 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){}`` |
| 145 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 146 | :: |
| 147 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 148 | var myTests = map[string][]func(s *MySuite){} |
| 149 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 150 | type MySuite struct { |
| 151 | HstSuite |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 152 | } |
| 153 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 154 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 155 | #. Then create a new function that will add tests to that map: |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 156 | |
| 157 | :: |
| 158 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 159 | func RegisterMyTests(tests ...func(s *MySuite)) { |
| 160 | myTests[getTestFilename()] = tests |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 161 | } |
| 162 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 163 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 164 | #. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests. |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 165 | It's important here to call ``ConfigureNetworkTopology()`` method, |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 166 | pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder. |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 167 | Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml`` |
| 168 | This will ensure network topology, such as network interfaces and namespaces, will be created. |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 169 | Another important method to call is ``LoadContainerTopology()`` which will load |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 170 | containers and shared volumes used by the suite. This time the name passed to method corresponds |
| 171 | to file in ``extras/hs-test/topo-containers`` folder |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 172 | |
| 173 | :: |
| 174 | |
| 175 | func (s *MySuite) SetupSuite() { |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 176 | s.HstSuite.SetupSuite() |
| 177 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 178 | // Add custom setup code here |
| 179 | |
Adrian Villin | 514098e | 2024-10-15 14:56:16 +0200 | [diff] [blame] | 180 | s.ConfigureNetworkTopology("myNetworkTopology") |
| 181 | s.LoadContainerTopology("myContainerTopology") |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 182 | } |
| 183 | |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 184 | #. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and |
| 185 | configuring VPP is usually placed here |
| 186 | |
| 187 | :: |
| 188 | |
| 189 | func (s *MySuite) SetupTest() { |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 190 | s.HstSuite.setupTest() |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 191 | s.SetupVolumes() |
| 192 | s.SetupContainers() |
| 193 | } |
| 194 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 195 | #. In order for ``Ginkgo`` to run this suite, we need to create a ``Describe`` container node with setup nodes and an ``It`` subject node. |
| 196 | Place them at the end of the suite file |
| 197 | |
| 198 | * Declare a suite struct variable before anything else |
| 199 | * To use ``BeforeAll()`` and ``AfterAll()``, the container has to be marked as ``Ordered`` |
| 200 | * Because the container is now marked as Ordered, if a test fails, all the subsequent tests are skipped. |
| 201 | To override this behavior, decorate the container node with ``ContinueOnFailure`` |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 202 | |
| 203 | :: |
| 204 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 205 | var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() { |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 206 | var s MySuite |
| 207 | BeforeAll(func() { |
| 208 | s.SetupSuite() |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 209 | }) |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 210 | BeforeEach(func() { |
| 211 | s.SetupTest() |
| 212 | }) |
| 213 | AfterAll(func() { |
| 214 | s.TearDownSuite() |
| 215 | }) |
| 216 | AfterEach(func() { |
| 217 | s.TearDownTest() |
| 218 | }) |
| 219 | |
| 220 | for filename, tests := range myTests { |
| 221 | for _, test := range tests { |
| 222 | test := test |
| 223 | pc := reflect.ValueOf(test).Pointer() |
| 224 | funcValue := runtime.FuncForPC(pc) |
| 225 | testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] |
| 226 | It(testName, func(ctx SpecContext) { |
| 227 | s.Log(testName + ": BEGIN") |
| 228 | test(&s) |
Adrian Villin | 514098e | 2024-10-15 14:56:16 +0200 | [diff] [blame] | 229 | }, SpecTimeout(TestTimeout)) |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 230 | } |
| 231 | } |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 232 | }) |
| 233 | |
| 234 | #. Notice the loop - it will generate multiple ``It`` nodes, each running a different test. |
| 235 | ``test := test`` is necessary, otherwise only the last test in a suite will run. |
| 236 | For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\. |
| 237 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 238 | #. ``testName`` contains the test name in the following format: ``[name]_test.go/MyTest``. |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 239 | |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 240 | #. To run certain tests solo, create a register function and a map that will only contain tests that have to run solo. |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 241 | Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node: |
| 242 | |
| 243 | :: |
| 244 | |
| 245 | var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() { |
| 246 | ... |
Adrian Villin | 4677d92 | 2024-06-14 09:32:39 +0200 | [diff] [blame] | 247 | It(testName, Label("SOLO"), func(ctx SpecContext) { |
| 248 | s.Log(testName + ": BEGIN") |
| 249 | test(&s) |
Adrian Villin | 514098e | 2024-10-15 14:56:16 +0200 | [diff] [blame] | 250 | }, SpecTimeout(TestTimeout)) |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 251 | }) |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 252 | |
| 253 | #. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above |
| 254 | |
| 255 | **Adding a topology element** |
| 256 | |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 257 | Topology configuration exists as ``yaml`` files in the ``extras/hs-test/topo-network`` and |
| 258 | ``extras/hs-test/topo-containers`` folders. Processing of a network topology file for a particular test suite |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 259 | is started by the ``configureNetworkTopology`` method depending on which file's name is passed to it. |
| 260 | Specified file is loaded and converted into internal data structures which represent various elements of the topology. |
| 261 | After parsing the configuration, framework loops over the elements and configures them one by one on the host system. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 262 | |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 263 | These are currently supported types of network elements. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 264 | |
| 265 | * ``netns`` - network namespace |
| 266 | * ``veth`` - veth network interface, optionally with target network namespace or IPv4 address |
| 267 | * ``bridge`` - ethernet bridge to connect created interfaces, optionally with target network namespace |
| 268 | * ``tap`` - tap network interface with IP address |
| 269 | |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 270 | Similarly, container topology is started by ``loadContainerTopology()``, configuration file is processed |
| 271 | so that test suite retains map of defined containers and uses that to start them at the beginning |
| 272 | of each test case and stop containers after the test finishes. Container configuration can specify |
| 273 | also volumes which allow to share data between containers or between host system and containers. |
| 274 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 275 | Supporting a new type of topology element requires adding code to recognize the new element type during loading. |
Maros Ondrejicka | db823ed | 2022-12-14 16:30:04 +0100 | [diff] [blame] | 276 | And adding code to set up the element in the host system with some Linux tool, such as *ip*. |
| 277 | This should be implemented in ``netconfig.go`` for network and in ``container.go`` for containers and volumes. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 278 | |
| 279 | **Communicating between containers** |
| 280 | |
| 281 | When two VPP instances or other applications, each in its own Docker container, |
| 282 | want to communicate there are typically two ways this can be done within *hs-test*. |
| 283 | |
| 284 | * Network interfaces. Containers are being created with ``-d --network host`` options, |
| 285 | so they are connected with interfaces created in host system |
| 286 | * Shared folders. Containers are being created with ``-v`` option to create shared `volumes`_ between host system and containers |
| 287 | or just between containers |
| 288 | |
Maros Ondrejicka | 56bfc63 | 2023-02-21 13:42:35 +0100 | [diff] [blame] | 289 | Host system connects to VPP instances running in containers using a shared folder |
| 290 | where binary API socket is accessible by both sides. |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 291 | |
| 292 | **Adding an external tool** |
| 293 | |
| 294 | If an external program should be executed as part of a test case, it might be useful to wrap its execution in its own function. |
| 295 | These types of functions are placed in the ``utils.go`` file. If the external program is not available by default in Docker image, |
| 296 | add its installation to ``extras/hs-test/Dockerfile.vpp`` in ``apt-get install`` command. |
| 297 | Alternatively copy the executable from host system to the Docker image, similarly how the VPP executables and libraries are being copied. |
| 298 | |
Filip Tehlar | 9418143 | 2024-01-15 13:11:28 +0100 | [diff] [blame] | 299 | **Skipping tests** |
| 300 | |
| 301 | ``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as: |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 302 | ``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. You can also use Ginkgo's ``Skip()``. |
Filip Tehlar | 9418143 | 2024-01-15 13:11:28 +0100 | [diff] [blame] | 303 | However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 304 | test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test. |
Filip Tehlar | 9418143 | 2024-01-15 13:11:28 +0100 | [diff] [blame] | 305 | |
Matus Fabian | 147585e | 2024-09-20 10:44:08 +0200 | [diff] [blame] | 306 | **External dependencies** |
| 307 | |
| 308 | * Linux tools ``ip``, ``brctl`` |
| 309 | * Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made, |
| 310 | they are reasonably up-to-date automatically |
| 311 | * Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx`` |
| 312 | * ``http_server`` - homegrown application that listens on specified port and sends a test file in response |
| 313 | * Non-standard Go libraries - see ``extras/hs-test/go.mod`` |
| 314 | |
| 315 | Generally, these will be updated on a per-need basis, for example when a bug is discovered |
| 316 | or a new version incompatibility issue occurs. |
| 317 | |
| 318 | Debugging a test |
| 319 | ---------------- |
| 320 | |
Adrian Villin | d05f16d | 2024-11-20 11:11:35 +0100 | [diff] [blame] | 321 | DRYRUN |
| 322 | ^^^^^^ |
| 323 | |
| 324 | ``make test TEST=[name] DRYRUN=true`` will setup and start most of the containers, but won't run any tests or start VPP. VPP and interfaces will be |
| 325 | configured automatically once you start VPP with the generated startup.conf file. |
| 326 | |
Matus Fabian | 147585e | 2024-09-20 10:44:08 +0200 | [diff] [blame] | 327 | GDB |
| 328 | ^^^ |
Filip Tehlar | f34b680 | 2024-01-24 15:11:27 +0100 | [diff] [blame] | 329 | |
| 330 | It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows: |
| 331 | |
| 332 | :: |
| 333 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 334 | $ make test TEST=LDPreloadIperfVppTest DEBUG=true |
Filip Tehlar | f34b680 | 2024-01-24 15:11:27 +0100 | [diff] [blame] | 335 | ... |
| 336 | run following command in different terminal: |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 337 | docker exec -it server-vpp2456109 gdb -ex "attach $(docker exec server-vpp2456109 pidof vpp)" |
| 338 | Afterwards press CTRL+\ to continue |
Filip Tehlar | f34b680 | 2024-01-24 15:11:27 +0100 | [diff] [blame] | 339 | |
| 340 | If a test consists of more VPP instances then this is done for each of them. |
| 341 | |
Matus Fabian | 147585e | 2024-09-20 10:44:08 +0200 | [diff] [blame] | 342 | Utility methods |
| 343 | ^^^^^^^^^^^^^^^ |
| 344 | |
| 345 | **Packet Capture** |
| 346 | |
| 347 | It is possible to use VPP pcap trace to capture received and sent packets. |
| 348 | You just need to add ``EnablePcapTrace`` to ``SetupTest`` method in test suite and ``CollectPcapTrace`` to ``TearDownTest``. |
| 349 | This way pcap trace is enabled on all interfaces and to capture maximum 10000 packets. |
| 350 | Your pcap file will be located in the test execution directory. |
| 351 | |
| 352 | **Event Logger** |
| 353 | |
| 354 | ``clib_warning`` is a handy way to add debugging output, but in some cases it's not appropriate for per-packet use in data plane code. |
| 355 | In this case VPP event logger is better option, for example you can enable it for TCP or session layer in build time. |
| 356 | To collect traces when test ends you just need to add ``CollectEventLogs`` method to ``TearDownTest`` in the test suite. |
| 357 | Your event logger file will be located in the test execution directory. |
| 358 | To view events you can use :ref:`G2 graphical event viewer <eventviewer>` or ``convert_evt`` tool, located in ``src/scripts/host-stack/``, |
| 359 | which convert event logs to human readable text. |
| 360 | |
| 361 | Memory leak testing |
| 362 | ^^^^^^^^^^^^^^^^^^^ |
Filip Tehlar | f34b680 | 2024-01-24 15:11:27 +0100 | [diff] [blame] | 363 | |
Matus Fabian | e99d266 | 2024-07-19 16:04:09 +0200 | [diff] [blame] | 364 | It 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. |
| 365 | You can do it by test like following: |
| 366 | |
| 367 | :: |
| 368 | |
| 369 | func MemLeakTest(s *NoTopoSuite) { |
| 370 | s.SkipUnlessLeakCheck() // test is excluded from usual test run |
| 371 | vpp := s.GetContainerByName("vpp").VppInstance |
| 372 | /* do your configuration here */ |
| 373 | vpp.Disconnect() // no goVPP less noise |
| 374 | vpp.EnableMemoryTrace() // enable memory traces |
| 375 | traces1, err := vpp.GetMemoryTrace() // get first sample |
| 376 | s.AssertNil(err, fmt.Sprint(err)) |
| 377 | vpp.Vppctl("test mem-leak") // execute some action |
| 378 | traces2, err := vpp.GetMemoryTrace() // get second sample |
| 379 | s.AssertNil(err, fmt.Sprint(err)) |
| 380 | vpp.MemLeakCheck(traces1, traces2) // compare samples and generate report |
| 381 | } |
| 382 | |
| 383 | To get your memory leak report run following command: |
| 384 | |
| 385 | :: |
| 386 | |
| 387 | $ make test-leak TEST=MemLeakTest |
| 388 | ... |
| 389 | NoTopoSuiteSolo mem_leak_test.go/MemLeakTest [SOLO] |
| 390 | /home/matus/vpp/extras/hs-test/infra/suite_no_topo.go:113 |
| 391 | |
| 392 | Report Entries >> |
| 393 | |
| 394 | SUMMARY: 112 byte(s) leaked in 1 allocation(s) |
| 395 | - /home/matus/vpp/extras/hs-test/infra/vppinstance.go:624 @ 07/19/24 15:53:33.539 |
| 396 | |
| 397 | leak of 112 byte(s) in 1 allocation(s) from: |
| 398 | #0 clib_mem_heap_alloc_aligned + 0x31 |
| 399 | #1 _vec_alloc_internal + 0x113 |
| 400 | #2 _vec_validate + 0x81 |
| 401 | #3 leak_memory_fn + 0x4f |
| 402 | #4 0x7fc167815ac3 |
| 403 | #5 0x7fc1678a7850 |
| 404 | << Report Entries |
| 405 | ------------------------------ |
| 406 | |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 407 | |
Adrian Villin | cee15aa | 2024-03-14 11:42:55 -0400 | [diff] [blame] | 408 | .. _ginkgo: https://onsi.github.io/ginkgo/ |
Maros Ondrejicka | 7943c90 | 2022-11-08 08:00:51 +0100 | [diff] [blame] | 409 | .. _volumes: https://docs.docker.com/storage/volumes/ |