aboutsummaryrefslogtreecommitdiffstats
path: root/extras
diff options
context:
space:
mode:
Diffstat (limited to 'extras')
-rw-r--r--extras/configs/http/setup.http3
-rw-r--r--extras/hs-test/Makefile157
-rw-r--r--extras/hs-test/README.rst270
-rw-r--r--extras/hs-test/container.go374
-rw-r--r--extras/hs-test/cpu.go96
-rw-r--r--extras/hs-test/cpu_pinning_test.go30
-rw-r--r--extras/hs-test/docker/Dockerfile.build8
-rw-r--r--extras/hs-test/docker/Dockerfile.curl10
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx2
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx-http38
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx-server6
-rw-r--r--extras/hs-test/docker/Dockerfile.vpp3
-rw-r--r--extras/hs-test/echo_test.go53
-rw-r--r--extras/hs-test/framework_test.go23
-rw-r--r--extras/hs-test/go.mod68
-rw-r--r--extras/hs-test/go.sum264
-rw-r--r--extras/hs-test/hs_test.sh147
-rw-r--r--extras/hs-test/hst_suite.go502
-rw-r--r--extras/hs-test/http_test.go1260
-rw-r--r--extras/hs-test/infra/address_allocator.go (renamed from extras/hs-test/address_allocator.go)4
-rw-r--r--extras/hs-test/infra/container.go538
-rw-r--r--extras/hs-test/infra/cpu.go210
-rw-r--r--extras/hs-test/infra/hst_suite.go643
-rw-r--r--extras/hs-test/infra/netconfig.go (renamed from extras/hs-test/netconfig.go)82
-rw-r--r--extras/hs-test/infra/suite_cpu_pinning.go113
-rw-r--r--extras/hs-test/infra/suite_envoy_proxy.go213
-rw-r--r--extras/hs-test/infra/suite_iperf_linux.go96
-rw-r--r--extras/hs-test/infra/suite_ldp.go203
-rw-r--r--extras/hs-test/infra/suite_nginx_proxy.go191
-rw-r--r--extras/hs-test/infra/suite_no_topo.go131
-rw-r--r--extras/hs-test/infra/suite_ns.go127
-rw-r--r--extras/hs-test/infra/suite_veth.go153
-rw-r--r--extras/hs-test/infra/suite_vpp_proxy.go212
-rw-r--r--extras/hs-test/infra/topo.go (renamed from extras/hs-test/topo.go)2
-rw-r--r--extras/hs-test/infra/utils.go316
-rw-r--r--extras/hs-test/infra/vppinstance.go647
-rw-r--r--extras/hs-test/iperf_linux_test.go48
-rw-r--r--extras/hs-test/ldp_test.go110
-rw-r--r--extras/hs-test/linux_iperf_test.go40
-rw-r--r--extras/hs-test/mem_leak_test.go25
-rw-r--r--extras/hs-test/mirroring_test.go26
-rw-r--r--extras/hs-test/nginx_test.go137
-rw-r--r--extras/hs-test/proxy_test.go140
-rw-r--r--extras/hs-test/raw_session_test.go32
-rw-r--r--extras/hs-test/resources/curl/write_out_download1
-rw-r--r--extras/hs-test/resources/curl/write_out_download_connect1
-rw-r--r--extras/hs-test/resources/curl/write_out_upload1
-rw-r--r--extras/hs-test/resources/curl/write_out_upload_connect1
-rw-r--r--extras/hs-test/resources/envoy/proxy.yaml16
-rw-r--r--extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf5
-rw-r--r--extras/hs-test/resources/nginx/nginx_server.conf43
-rw-r--r--extras/hs-test/resources/nginx/nginx_server_mirroring.conf13
-rwxr-xr-xextras/hs-test/script/build_boringssl.sh2
-rwxr-xr-xextras/hs-test/script/build_hst.sh50
-rwxr-xr-xextras/hs-test/script/build_nginx.sh2
-rwxr-xr-x[-rw-r--r--]extras/hs-test/script/compress.sh8
-rwxr-xr-xextras/hs-test/script/nginx_ldp.sh3
-rwxr-xr-xextras/hs-test/script/nginx_server_entrypoint.sh3
-rw-r--r--extras/hs-test/suite_nginx_test.go134
-rw-r--r--extras/hs-test/suite_no_topo_test.go110
-rw-r--r--extras/hs-test/suite_ns_test.go119
-rw-r--r--extras/hs-test/suite_tap_test.go84
-rw-r--r--extras/hs-test/suite_veth_test.go144
-rw-r--r--extras/hs-test/test94
-rw-r--r--extras/hs-test/tools/http_server/http_server.go4
-rw-r--r--extras/hs-test/topo-containers/2containers.yaml4
-rw-r--r--extras/hs-test/topo-containers/2peerVethLdp.yaml18
-rw-r--r--extras/hs-test/topo-containers/envoyProxy.yaml40
-rw-r--r--extras/hs-test/topo-containers/nginxProxy.yaml32
-rw-r--r--extras/hs-test/topo-containers/ns.yaml2
-rw-r--r--extras/hs-test/topo-containers/singleCpuPinning.yaml11
-rw-r--r--extras/hs-test/topo-containers/vppProxy.yaml (renamed from extras/hs-test/topo-containers/nginxProxyAndServer.yaml)21
-rw-r--r--extras/hs-test/utils.go80
-rw-r--r--extras/hs-test/vcl_test.go162
-rw-r--r--extras/hs-test/vppinstance.go473
-rwxr-xr-xextras/scripts/checkstyle.sh19
-rw-r--r--extras/strongswan/vpp_sswan/Makefile4
-rwxr-xr-xextras/vpp_if_stats/apimock.go5
-rwxr-xr-xextras/vpp_if_stats/json_structs.go1
-rwxr-xr-xextras/vpp_if_stats/statsmock.go3
-rw-r--r--extras/vpp_if_stats/vpp_if_stats.go3
-rw-r--r--extras/vpp_if_stats/vpp_if_stats_test.go7
-rw-r--r--extras/vpp_stats_fs/stats_fs.go8
83 files changed, 6416 insertions, 3038 deletions
diff --git a/extras/configs/http/setup.http b/extras/configs/http/setup.http
index 78b7a2f19e8..c4d4d1e6b70 100644
--- a/extras/configs/http/setup.http
+++ b/extras/configs/http/setup.http
@@ -3,5 +3,4 @@ create tap host-if-name lstack host-ip4-addr 192.168.10.2/24
set int ip address tap0 192.168.10.1/24
set int state tap0 up
-http static server www-root <path> uri tcp://0.0.0.0/1234 cache-size 10m fifo-size 2048
-builtinurl enable
+http static server url-handlers www-root <path> uri tcp://0.0.0.0/1234 cache-size 10m fifo-size 2048
diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile
index e247bf44160..b72aca1d7dd 100644
--- a/extras/hs-test/Makefile
+++ b/extras/hs-test/Makefile
@@ -1,5 +1,10 @@
export HS_ROOT=$(CURDIR)
+# sets WS_ROOT if called from extras/hs-test
+ifeq ($(WS_ROOT),)
+export WS_ROOT=$(HS_ROOT)/../..
+endif
+
ifeq ($(VERBOSE),)
VERBOSE=false
endif
@@ -16,6 +21,10 @@ ifeq ($(TEST),)
TEST=all
endif
+ifeq ($(TEST-HS),)
+TEST-HS=all
+endif
+
ifeq ($(DEBUG),)
DEBUG=false
endif
@@ -32,6 +41,10 @@ ifeq ($(REPEAT),)
REPEAT=0
endif
+ifeq ($(CPU0),)
+CPU0=false
+endif
+
ifeq ($(VPPSRC),)
VPPSRC=$(shell pwd)/../..
endif
@@ -44,73 +57,92 @@ ifeq ($(ARCH),)
ARCH=$(shell dpkg --print-architecture)
endif
-list_tests = @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'Test' | \
- sed 's/^/* /; s/\(Suite\) /\1\//g'
-
.PHONY: help
help:
@echo "Make targets:"
- @echo " test - run tests"
- @echo " test-debug - run tests (vpp debug image)"
- @echo " build - build test infra"
- @echo " build-debug - build test infra (vpp debug image)"
- @echo " build-go - just build golang files"
- @echo " fixstyle - format .go source files"
- @echo " list-tests - list all tests"
+ @echo " test - run tests"
+ @echo " test-debug - run tests (vpp debug image)"
+ @echo " test-leak - run memory leak tests (vpp debug image)"
+ @echo " build - build test infra"
+ @echo " build-cov - coverage build of VPP and Docker images"
+ @echo " build-debug - build test infra (vpp debug image)"
+ @echo " build-go - just build golang files"
+ @echo " checkstyle-go - check style of .go source files"
+ @echo " fixstyle-go - format .go source files"
+ @echo " cleanup-hst - stops and removes all docker contaiers and namespaces"
+ @echo " list-tests - list all tests"
@echo
- @echo "make build arguments:"
+ @echo "'make build' arguments:"
@echo " UBUNTU_VERSION - ubuntu version for docker image"
- @echo " HST_EXTENDED_TESTS - build extended tests"
+ @echo " HST_EXTENDED_TESTS - build extended tests"
@echo
- @echo "make test arguments:"
+ @echo "'make test' arguments:"
@echo " PERSIST=[true|false] - whether clean up topology and dockers after test"
@echo " VERBOSE=[true|false] - verbose output"
@echo " UNCONFIGURE=[true|false] - unconfigure selected test"
@echo " DEBUG=[true|false] - attach VPP to GDB"
@echo " TEST=[test-name] - specific test to run"
- @echo " CPUS=[n-cpus] - number of cpus to run with vpp"
+ @echo " CPUS=[n-cpus] - number of cpus to allocate to VPP and containers"
@echo " VPPSRC=[path-to-vpp-src] - path to vpp source files (for gdb)"
@echo " PARALLEL=[n-cpus] - number of test processes to spawn to run in parallel"
@echo " REPEAT=[n] - repeat tests up to N times or until a failure occurs"
+ @echo " CPU0=[true|false] - use cpu0"
@echo
@echo "List of all tests:"
- $(call list_tests)
+ @$(MAKE) list-tests
.PHONY: list-tests
list-tests:
- $(call list_tests)
+ @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'Test' | \
+ sed 's/^/* /; s/\(Suite\) /\1\//g'
.PHONY: build-vpp-release
build-vpp-release:
- @make -C ../.. build-release
+ @$(MAKE) -C ../.. build-release
.PHONY: build-vpp-debug
build-vpp-debug:
- @make -C ../.. build
+ @$(MAKE) -C ../.. build
+
+.PHONY: build-vpp-gcov
+build-vpp-gcov:
+ @$(MAKE) -C ../.. build-vpp-gcov
.build.ok: build
@touch .build.ok
+.build.cov.ok: build-vpp-gcov
+ @touch .build.cov.ok
+
.build_debug.ok: build-debug
@touch .build.ok
.PHONY: test
test: .deps.ok .build.ok
- # '-' ignores the exit status, it is set in compress.sh
- # necessary so gmake won't skip executing the bash script
- -bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
- --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT)
- @bash ./script/compress.sh
+ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
+
.PHONY: test-debug
test-debug: .deps.ok .build_debug.ok
- # '-' ignores the exit status, it is set in compress.sh
- # necessary so gmake won't skip executing the bash script
- -bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
- --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT)
- @bash ./script/compress.sh
+ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true \
+ --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
+
+.PHONY: test-cov
+test-cov: .deps.ok .build.cov.ok
+ @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST-HS) --cpus=$(CPUS) \
+ --vppsrc=$(VPPSRC) --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
+
+.PHONY: test-leak
+test-leak: .deps.ok .build_debug.ok
+ @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC)
.PHONY: build-go
build-go:
@@ -122,6 +154,12 @@ build: .deps.ok build-vpp-release build-go
bash ./script/build_hst.sh release
@touch .build.ok
+.PHONY: build-cov
+build-cov: .deps.ok build-vpp-gcov build-go
+ @rm -f .build.cov.ok
+ bash ./script/build_hst.sh gcov
+ @touch .build.cov.ok
+
.PHONY: build-debug
build-debug: .deps.ok build-vpp-debug build-go
@rm -f .build.ok
@@ -129,23 +167,76 @@ build-debug: .deps.ok build-vpp-debug build-go
@touch .build.ok
.deps.ok:
- @sudo make install-deps
+ @sudo $(MAKE) install-deps
.PHONY: install-deps
install-deps:
@rm -f .deps.ok
@apt-get update \
&& apt-get install -y apt-transport-https ca-certificates curl software-properties-common \
- apache2-utils wrk bridge-utils
+ apache2-utils wrk bridge-utils gpg
@if [ ! -f /usr/share/keyrings/docker-archive-keyring.gpg ] ; then \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; \
echo "deb [arch=$(ARCH) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(UBUNTU_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ; \
apt-get update; \
fi
+ @apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
@touch .deps.ok
-.PHONY: fixstyle
-fixstyle:
- @gofmt -w .
+.goimports.ok:
+ @rm -f .goimports.ok
+ go install golang.org/x/tools/cmd/goimports@v0.25.0
+ @touch .goimports.ok
+
+.PHONY: checkstyle-go
+checkstyle-go: .goimports.ok
+ $(eval GOPATH := $(shell go env GOPATH))
+ @output=$$($(GOPATH)/bin/goimports -d $${WS_ROOT}); \
+ status=$$?; \
+ if [ $$status -ne 0 ]; then \
+ exit $$status; \
+ elif [ -z "$$output" ]; then \
+ echo "*******************************************************************"; \
+ echo "Checkstyle OK."; \
+ echo "*******************************************************************"; \
+ else \
+ echo "$$output"; \
+ echo "*******************************************************************"; \
+ echo "Checkstyle failed. Use 'make fixstyle-go' or fix errors manually."; \
+ echo "*******************************************************************"; \
+ exit 1; \
+ fi
+
+.PHONY: fixstyle-go
+fixstyle-go: .goimports.ok
+ $(eval GOPATH := $(shell go env GOPATH))
+ @echo "Modified files:"
+ @$(GOPATH)/bin/goimports -w -l $(WS_ROOT)
@go mod tidy
+ @echo "*******************************************************************"
+ @echo "Fixstyle done."
+ @echo "*******************************************************************"
+
+.PHONY: cleanup-hst
+cleanup-hst:
+ @if [ ! -f ".last_hst_ppid" ]; then \
+ echo "'.last_hst_ppid' file does not exist."; \
+ exit 1; \
+ fi
+ @echo "****************************"
+ @echo "Removing docker containers:"
+ @# "-" ignores errors
+ @-sudo docker rm $$(sudo docker stop $$(sudo docker ps -a -q --filter "name=$$(cat .last_hst_ppid)") -t 0)
+ @echo "****************************"
+ @echo "Removing IP address files:"
+ @find . -type f -regextype egrep -regex '.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' -exec sudo rm -v {} \;
+ @echo "****************************"
+ @echo "Removing network namespaces:"
+ @for ns in $$(ip netns list | grep $$(cat .last_hst_ppid) | awk '{print $$1}'); do \
+ echo $$ns; \
+ sudo ip netns delete $$ns; \
+ done
+ @echo "****************************"
+ @echo "Done."
+ @echo "****************************"
diff --git a/extras/hs-test/README.rst b/extras/hs-test/README.rst
index 1dc1039b33f..6a4cea187c4 100644
--- a/extras/hs-test/README.rst
+++ b/extras/hs-test/README.rst
@@ -26,15 +26,15 @@ Anatomy of a test case
**Action flow when running a test case**:
-#. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run),
+#. 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),
TEST=<test-name> to run a specific test and PARALLEL=[n-cpus].
-#. ``make list-tests`` (or ``make help``) shows all tests. The current `list of tests`_ is at the bottom of this document.
-#. ``Ginkgo`` looks for a spec suite in the current directory and then compiles it to a .test binary
-#. The Ginkgo test framework runs each function that was registered manually using ``registerMySuiteTest(s *MySuite)``. Each of these functions correspond to a suite
+#. ``make list-tests`` (or ``make help``) shows all tests.
+#. ``Ginkgo`` looks for a spec suite in the current directory and then compiles it to a .test binary.
+#. The Ginkgo test framework runs each function that was registered manually using ``Register[SuiteName]Test()``. Each of these functions correspond to a suite.
#. Ginkgo's ``RunSpecs(t, "Suite description")`` function is the entry point and does the following:
#. Ginkgo compiles the spec, builds a spec tree
- #. ``Describe`` container nodes in suite\_\*_test.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus])
+ #. ``Describe`` container nodes in suite\_\*.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus])
#. Suite is initialized. The topology is loaded and configured in this step
#. Registered tests are run in generated ``It`` subject nodes
#. Execute tear-down functions, which currently consists of stopping running containers
@@ -47,48 +47,78 @@ This describes adding a new test case to an existing suite.
For adding a new suite, please see `Modifying the framework`_ below.
#. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists
-#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``suite_*_test.go``)
+#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``infra/suite_*.go``)
#. Implement test behaviour inside the test method. This typically includes the following:
- #. Retrieve a running container in which to run some action. Method ``getContainerByName``
+ #. Import ``. "fd.io/hs-test/infra"``
+ #. Retrieve a running container in which to run some action. Method ``GetContainerByName``
from ``HstSuite`` struct serves this purpose
- #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI
- #. Run arbitrary commands inside the containers with ``exec`` method
- #. Run other external tool with one of the preexisting functions in the ``utils.go`` file.
- For example, use ``wget`` with ``startWget`` function
+ #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``Vppctl`` method to access debug CLI
+ #. Run arbitrary commands inside the containers with ``Exec`` method
+ #. Run other external tool with one of the preexisting functions in the ``infra/utils.go`` file.
+ For example, use ``wget`` with ``StartWget`` function
#. Use ``exechelper`` or just plain ``exec`` packages to run whatever else
- #. Verify results of your tests using ``assert`` methods provided by the test suite, implemented by HstSuite struct or use ``Gomega`` assert functions.
+ #. Verify results of your tests using ``Assert`` methods provided by the test suite.
-#. Create an ``init()`` function and register the test using ``register*SuiteTests(testCaseFunction)``
+#. Create an ``init()`` function and register the test using ``Register[SuiteName]Tests(testCaseFunction)``
**Example test case**
Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other.
-This 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``.
+This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest``.
::
package main
import (
- "fmt"
+ . "fd.io/hs-test/infra"
)
func init(){
- registerMySuiteTest(MyTest)
+ RegisterMySuiteTest(MyTest)
}
func MyTest(s *MySuite) {
- clientVpp := s.getContainerByName("client-vpp").vppInstance
+ clientVpp := s.GetContainerByName("client-vpp").VppInstance
- serverVethAddress := s.netInterfaces["server-iface"].AddressString()
+ serverVethAddress := s.NetInterfaces["server-iface"].Ip4AddressString()
- result := clientVpp.vppctl("ping " + serverVethAddress)
- s.assertNotNil(result)
- s.log(result)
+ result := clientVpp.Vppctl("ping " + serverVethAddress)
+ s.Log(result)
+ s.AssertNotNil(result)
}
+
+Filtering test cases
+--------------------
+
+The framework allows us to filter test cases in a few different ways, using ``make test TEST=``:
+
+ * Suite name
+ * File name
+ * Test name
+ * All of the above as long as they are ordered properly, e.g. ``make test TEST=VethsSuite.http_test.go.HeaderServerTest``
+
+**Names are case sensitive!**
+
+Names don't have to be complete, as long as they are last:
+This is valid and will run all tests in every ``http`` file (if there is more than one):
+
+* ``make test TEST=VethsSuite.http``
+
+This is not valid:
+
+* ``make test TEST=Veths.http``
+
+They can also be left out:
+
+* ``make test TEST=http_test.go`` will run every test in ``http_test.go``
+* ``make test TEST=Nginx`` will run everything that has 'Nginx' in its name - suites, files and tests.
+* ``make test TEST=HeaderServerTest`` will only run the header server test
+
+
Modifying the framework
-----------------------
@@ -96,34 +126,37 @@ Modifying the framework
.. _test-convention:
-#. 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
- by the actual name
+#. To add a new suite, create a new file in the ``infra/`` folder. Naming convention for the suite files is ``suite_[name].go``.
#. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member.
HstSuite provides functionality that can be shared for all suites, like starting containers
+#. 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){}``
+
::
+ var myTests = map[string][]func(s *MySuite){}
+
type MySuite struct {
HstSuite
}
-#. Create a new slice that will contain test functions with a pointer to the suite's struct: ``var myTests = []func(s *MySuite){}``
-#. Then create a new function that will append test functions to that slice:
+#. Then create a new function that will add tests to that map:
::
- func registerMySuiteTests(tests ...func(s *MySuite)) {
- nginxTests = append(myTests, tests...)
+ func RegisterMyTests(tests ...func(s *MySuite)) {
+ myTests[getTestFilename()] = tests
}
+
#. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests.
- It's important here to call ``configureNetworkTopology`` method,
+ It's important here to call ``ConfigureNetworkTopology()`` method,
pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder.
Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml``
This will ensure network topology, such as network interfaces and namespaces, will be created.
- Another important method to call is ``loadContainerTopology()`` which will load
+ Another important method to call is ``LoadContainerTopology()`` which will load
containers and shared volumes used by the suite. This time the name passed to method corresponds
to file in ``extras/hs-test/topo-containers`` folder
@@ -134,8 +167,8 @@ Modifying the framework
// Add custom setup code here
- s.configureNetworkTopology("myTopology")
- s.loadContainerTopology("2peerVeth")
+ s.ConfigureNetworkTopology("myTopology")
+ s.LoadContainerTopology("2peerVeth")
}
#. In suite file, implement ``SetupTest`` method which gets executed before each test. Starting containers and
@@ -160,44 +193,50 @@ Modifying the framework
::
var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() {
- var s MySuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
+ var s MySuite
+ BeforeAll(func() {
+ s.SetupSuite()
})
- for _, test := range mySuiteTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) {
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range myTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
})
#. Notice the loop - it will generate multiple ``It`` nodes, each running a different test.
``test := test`` is necessary, otherwise only the last test in a suite will run.
For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\.
-#. ``funcValue.Name()`` returns the full name of a function (e.g. ``fd.io/hs-test.MyTest``), however, we only need the test name (``MyTest``).
+#. ``testName`` contains the test name in the following format: ``[name]_test.go/MyTest``.
-#. To run certain tests solo, create a new slice that will only contain tests that have to run solo and a new register function.
+#. To run certain tests solo, create a register function and a map that will only contain tests that have to run solo.
Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node:
::
var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
...
- It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) {
- test(&s)
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
}, SpecTimeout(time.Minute*5))
})
@@ -254,7 +293,23 @@ Alternatively copy the executable from host system to the Docker image, similarl
However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test.
-**Debugging a test**
+**External dependencies**
+
+* Linux tools ``ip``, ``brctl``
+* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
+ they are reasonably up-to-date automatically
+* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
+* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
+* Non-standard Go libraries - see ``extras/hs-test/go.mod``
+
+Generally, these will be updated on a per-need basis, for example when a bug is discovered
+or a new version incompatibility issue occurs.
+
+Debugging a test
+----------------
+
+GDB
+^^^
It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows:
@@ -268,52 +323,71 @@ It is possible to debug VPP by attaching ``gdb`` before test execution by adding
If a test consists of more VPP instances then this is done for each of them.
+Utility methods
+^^^^^^^^^^^^^^^
-**Eternal dependencies**
+**Packet Capture**
-* Linux tools ``ip``, ``brctl``
-* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
- they are reasonably up-to-date automatically
-* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
-* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
-* Non-standard Go libraries - see ``extras/hs-test/go.mod``
+It is possible to use VPP pcap trace to capture received and sent packets.
+You just need to add ``EnablePcapTrace`` to ``SetupTest`` method in test suite and ``CollectPcapTrace`` to ``TearDownTest``.
+This way pcap trace is enabled on all interfaces and to capture maximum 10000 packets.
+Your pcap file will be located in the test execution directory.
-Generally, these will be updated on a per-need basis, for example when a bug is discovered
-or a new version incompatibility issue occurs.
+**Event Logger**
+
+``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.
+In this case VPP event logger is better option, for example you can enable it for TCP or session layer in build time.
+To collect traces when test ends you just need to add ``CollectEventLogs`` method to ``TearDownTest`` in the test suite.
+Your event logger file will be located in the test execution directory.
+To view events you can use :ref:`G2 graphical event viewer <eventviewer>` or ``convert_evt`` tool, located in ``src/scripts/host-stack/``,
+which convert event logs to human readable text.
+
+Memory leak testing
+^^^^^^^^^^^^^^^^^^^
+
+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.
+You can do it by test like following:
+
+::
+
+ func MemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck() // test is excluded from usual test run
+ vpp := s.GetContainerByName("vpp").VppInstance
+ /* do your configuration here */
+ vpp.Disconnect() // no goVPP less noise
+ vpp.EnableMemoryTrace() // enable memory traces
+ traces1, err := vpp.GetMemoryTrace() // get first sample
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.Vppctl("test mem-leak") // execute some action
+ traces2, err := vpp.GetMemoryTrace() // get second sample
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2) // compare samples and generate report
+ }
+
+To get your memory leak report run following command:
+
+::
+
+ $ make test-leak TEST=MemLeakTest
+ ...
+ NoTopoSuiteSolo mem_leak_test.go/MemLeakTest [SOLO]
+ /home/matus/vpp/extras/hs-test/infra/suite_no_topo.go:113
+
+ Report Entries >>
+
+ SUMMARY: 112 byte(s) leaked in 1 allocation(s)
+ - /home/matus/vpp/extras/hs-test/infra/vppinstance.go:624 @ 07/19/24 15:53:33.539
+
+ leak of 112 byte(s) in 1 allocation(s) from:
+ #0 clib_mem_heap_alloc_aligned + 0x31
+ #1 _vec_alloc_internal + 0x113
+ #2 _vec_validate + 0x81
+ #3 leak_memory_fn + 0x4f
+ #4 0x7fc167815ac3
+ #5 0x7fc1678a7850
+ << Report Entries
+ ------------------------------
.. _ginkgo: https://onsi.github.io/ginkgo/
.. _volumes: https://docs.docker.com/storage/volumes/
-
-**List of tests**
-
-.. _list of tests:
-
-Please update this list whenever you add a new test by pasting the output below.
-
-* NsSuite/HttpTpsTest
-* NsSuite/VppProxyHttpTcpTest
-* NsSuite/VppProxyHttpTlsTest
-* NsSuite/EnvoyProxyHttpTcpTest
-* NginxSuite/MirroringTest
-* VethsSuiteSolo TcpWithLossTest [SOLO]
-* NoTopoSuiteSolo HttpStaticPromTest [SOLO]
-* TapSuite/LinuxIperfTest
-* NoTopoSuite/NginxHttp3Test
-* NoTopoSuite/NginxAsServerTest
-* NoTopoSuite/NginxPerfCpsTest
-* NoTopoSuite/NginxPerfRpsTest
-* NoTopoSuite/NginxPerfWrkTest
-* VethsSuite/EchoBuiltinTest
-* VethsSuite/HttpCliTest
-* VethsSuite/LDPreloadIperfVppTest
-* VethsSuite/VppEchoQuicTest
-* VethsSuite/VppEchoTcpTest
-* VethsSuite/VppEchoUdpTest
-* VethsSuite/XEchoVclClientUdpTest
-* VethsSuite/XEchoVclClientTcpTest
-* VethsSuite/XEchoVclServerUdpTest
-* VethsSuite/XEchoVclServerTcpTest
-* VethsSuite/VclEchoTcpTest
-* VethsSuite/VclEchoUdpTest
-* VethsSuite/VclRetryAttachTest
diff --git a/extras/hs-test/container.go b/extras/hs-test/container.go
deleted file mode 100644
index 0bdc3a24996..00000000000
--- a/extras/hs-test/container.go
+++ /dev/null
@@ -1,374 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strings"
- "text/template"
- "time"
-
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
-)
-
-const (
- logDir string = "/tmp/hs-test/"
- volumeDir string = "/volumes"
-)
-
-var (
- workDir, _ = os.Getwd()
-)
-
-type Volume struct {
- hostDir string
- containerDir string
- isDefaultWorkDir bool
-}
-
-type Container struct {
- suite *HstSuite
- isOptional bool
- runDetached bool
- name string
- image string
- extraRunningArgs string
- volumes map[string]Volume
- envVars map[string]string
- vppInstance *VppInstance
-}
-
-func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
- containerName := yamlInput["name"].(string)
- if len(containerName) == 0 {
- err := fmt.Errorf("container name must not be blank")
- return nil, err
- }
-
- var container = new(Container)
- container.volumes = make(map[string]Volume)
- container.envVars = make(map[string]string)
- container.name = containerName
- container.suite = suite
-
- if image, ok := yamlInput["image"]; ok {
- container.image = image.(string)
- } else {
- container.image = "hs-test/vpp"
- }
-
- if args, ok := yamlInput["extra-args"]; ok {
- container.extraRunningArgs = args.(string)
- } else {
- container.extraRunningArgs = ""
- }
-
- if isOptional, ok := yamlInput["is-optional"]; ok {
- container.isOptional = isOptional.(bool)
- } else {
- container.isOptional = false
- }
-
- if runDetached, ok := yamlInput["run-detached"]; ok {
- container.runDetached = runDetached.(bool)
- } else {
- container.runDetached = true
- }
-
- if _, ok := yamlInput["volumes"]; ok {
- workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + volumeDir
- workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
- volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
- for _, volu := range yamlInput["volumes"].([]interface{}) {
- volumeMap := volu.(ContainerConfig)
- hostDir := workDirReplacer.Replace(volumeMap["host-dir"].(string))
- hostDir = volDirReplacer.Replace(hostDir)
- containerDir := volumeMap["container-dir"].(string)
- isDefaultWorkDir := false
-
- if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
- isDefaultWorkDir = isDefault.(bool)
- }
- container.addVolume(hostDir, containerDir, isDefaultWorkDir)
- }
- }
-
- if _, ok := yamlInput["vars"]; ok {
- for _, envVar := range yamlInput["vars"].([]interface{}) {
- envVarMap := envVar.(ContainerConfig)
- name := envVarMap["name"].(string)
- value := envVarMap["value"].(string)
- container.addEnvVar(name, value)
- }
- }
- return container, nil
-}
-
-func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
- for _, v := range c.volumes {
- if v.isDefaultWorkDir {
- res = v
- exists = true
- return
- }
- }
- return
-}
-
-func (c *Container) getHostWorkDir() (res string) {
- if v, ok := c.getWorkDirVolume(); ok {
- res = v.hostDir
- }
- return
-}
-
-func (c *Container) getContainerWorkDir() (res string) {
- if v, ok := c.getWorkDirVolume(); ok {
- res = v.containerDir
- }
- return
-}
-
-func (c *Container) getContainerArguments() string {
- args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host --rm"
- args += c.getVolumesAsCliOption()
- args += c.getEnvVarsAsCliOption()
- if *vppSourceFileDir != "" {
- args += fmt.Sprintf(" -v %s:%s", *vppSourceFileDir, *vppSourceFileDir)
- }
- args += " --name " + c.name + " " + c.image
- args += " " + c.extraRunningArgs
- return args
-}
-
-func (c *Container) runWithRetry(cmd string) error {
- nTries := 5
- for i := 0; i < nTries; i++ {
- err := exechelper.Run(cmd)
- if err == nil {
- return nil
- }
- time.Sleep(1 * time.Second)
- }
- return fmt.Errorf("failed to run container command")
-}
-
-func (c *Container) create() error {
- cmd := "docker create " + c.getContainerArguments()
- c.suite.log(cmd)
- return exechelper.Run(cmd)
-}
-
-func (c *Container) start() error {
- cmd := "docker start " + c.name
- c.suite.log(cmd)
- return c.runWithRetry(cmd)
-}
-
-func (c *Container) prepareCommand() (string, error) {
- if c.name == "" {
- return "", fmt.Errorf("run container failed: name is blank")
- }
-
- cmd := "docker run "
- if c.runDetached {
- cmd += " -d"
- }
- cmd += " " + c.getContainerArguments()
-
- c.suite.log(cmd)
- return cmd, nil
-}
-
-func (c *Container) combinedOutput() (string, error) {
- cmd, err := c.prepareCommand()
- if err != nil {
- return "", err
- }
-
- byteOutput, err := exechelper.CombinedOutput(cmd)
- return string(byteOutput), err
-}
-
-func (c *Container) run() error {
- cmd, err := c.prepareCommand()
- if err != nil {
- return err
- }
- return c.runWithRetry(cmd)
-}
-
-func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
- var volume Volume
- volume.hostDir = hostDir
- volume.containerDir = containerDir
- volume.isDefaultWorkDir = isDefaultWorkDir
- c.volumes[hostDir] = volume
-}
-
-func (c *Container) getVolumesAsCliOption() string {
- cliOption := ""
-
- if len(c.volumes) > 0 {
- for _, volume := range c.volumes {
- cliOption += fmt.Sprintf(" -v %s:%s", volume.hostDir, volume.containerDir)
- }
- }
-
- return cliOption
-}
-
-func (c *Container) addEnvVar(name string, value string) {
- c.envVars[name] = value
-}
-
-func (c *Container) getEnvVarsAsCliOption() string {
- cliOption := ""
- if len(c.envVars) == 0 {
- return cliOption
- }
-
- for name, value := range c.envVars {
- cliOption += fmt.Sprintf(" -e %s=%s", name, value)
- }
- return cliOption
-}
-
-func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) {
- vpp := new(VppInstance)
- vpp.container = c
- vpp.cpus = cpus
- c.suite.vppContainerCount += 1
- vpp.additionalConfig = append(vpp.additionalConfig, additionalConfigs...)
- c.vppInstance = vpp
- return vpp, nil
-}
-
-func (c *Container) copy(sourceFileName string, targetFileName string) error {
- cmd := exec.Command("docker", "cp", sourceFileName, c.name+":"+targetFileName)
- return cmd.Run()
-}
-
-func (c *Container) createFile(destFileName string, content string) error {
- f, err := os.CreateTemp("/tmp", "hst-config"+c.suite.pid)
- if err != nil {
- return err
- }
- defer os.Remove(f.Name())
-
- if _, err := f.Write([]byte(content)); err != nil {
- return err
- }
- if err := f.Close(); err != nil {
- return err
- }
- c.copy(f.Name(), destFileName)
- return nil
-}
-
-/*
- * Executes in detached mode so that the started application can continue to run
- * without blocking execution of test
- */
-func (c *Container) execServer(command string, arguments ...any) {
- serverCommand := fmt.Sprintf(command, arguments...)
- containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
- " " + c.name + " " + serverCommand
- GinkgoHelper()
- c.suite.log(containerExecCommand)
- c.suite.assertNil(exechelper.Run(containerExecCommand))
-}
-
-func (c *Container) exec(command string, arguments ...any) string {
- cliCommand := fmt.Sprintf(command, arguments...)
- containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
- " " + c.name + " " + cliCommand
- GinkgoHelper()
- c.suite.log(containerExecCommand)
- byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
- c.suite.assertNil(err, err)
- return string(byteOutput)
-}
-
-func (c *Container) getLogDirPath() string {
- testId := c.suite.getTestId()
- testName := CurrentSpecReport().LeafNodeText
- logDirPath := logDir + testName + "/" + testId + "/"
-
- cmd := exec.Command("mkdir", "-p", logDirPath)
- if err := cmd.Run(); err != nil {
- Fail("mkdir error: " + fmt.Sprint(err))
- }
-
- return logDirPath
-}
-
-func (c *Container) saveLogs() {
- cmd := exec.Command("docker", "inspect", "--format='{{.State.Status}}'", c.name)
- if output, _ := cmd.CombinedOutput(); !strings.Contains(string(output), "running") {
- return
- }
-
- testLogFilePath := c.getLogDirPath() + "container-" + c.name + ".log"
-
- cmd = exec.Command("docker", "logs", "--details", "-t", c.name)
- output, err := cmd.CombinedOutput()
- if err != nil {
- Fail("fetching logs error: " + fmt.Sprint(err))
- }
-
- f, err := os.Create(testLogFilePath)
- if err != nil {
- Fail("file create error: " + fmt.Sprint(err))
- }
- fmt.Fprint(f, string(output))
- f.Close()
-}
-
-// Outputs logs from docker containers. Set 'maxLines' to 0 to output the full log.
-func (c *Container) log(maxLines int) (string, error) {
- var cmd string
- if maxLines == 0 {
- cmd = "docker logs " + c.name
- } else {
- cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.name)
- }
-
- c.suite.log(cmd)
- o, err := exechelper.CombinedOutput(cmd)
- return string(o), err
-}
-
-func (c *Container) stop() error {
- if c.vppInstance != nil && c.vppInstance.apiStream != nil {
- c.vppInstance.saveLogs()
- c.vppInstance.disconnect()
- }
- c.vppInstance = nil
- c.saveLogs()
- return exechelper.Run("docker stop " + c.name + " -t 0")
-}
-
-func (c *Container) createConfig(targetConfigName string, templateName string, values any) {
- template := template.Must(template.ParseFiles(templateName))
-
- f, err := os.CreateTemp(logDir, "hst-config")
- c.suite.assertNil(err, err)
- defer os.Remove(f.Name())
-
- err = template.Execute(f, values)
- c.suite.assertNil(err, err)
-
- err = f.Close()
- c.suite.assertNil(err, err)
-
- c.copy(f.Name(), targetConfigName)
-}
-
-func init() {
- cmd := exec.Command("mkdir", "-p", logDir)
- if err := cmd.Run(); err != nil {
- panic(err)
- }
-}
diff --git a/extras/hs-test/cpu.go b/extras/hs-test/cpu.go
deleted file mode 100644
index 69b4cabd4e3..00000000000
--- a/extras/hs-test/cpu.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package main
-
-import (
- "bufio"
- "errors"
- "fmt"
- . "github.com/onsi/ginkgo/v2"
- "os"
- "os/exec"
- "strings"
-)
-
-var CgroupPath = "/sys/fs/cgroup/"
-
-type CpuContext struct {
- cpuAllocator *CpuAllocatorT
- cpus []int
-}
-
-type CpuAllocatorT struct {
- cpus []int
-}
-
-var cpuAllocator *CpuAllocatorT = nil
-
-func (c *CpuAllocatorT) Allocate(vppContainerCount int, nCpus int) (*CpuContext, error) {
- var cpuCtx CpuContext
- maxCpu := GinkgoParallelProcess() * 2 * nCpus
- minCpu := (GinkgoParallelProcess() - 1) * 2 * nCpus
- if len(c.cpus) < maxCpu {
- vppContainerCount += 1
- err := fmt.Errorf("could not allocate %d CPUs; available: %d; attempted to allocate cores %d-%d",
- nCpus*vppContainerCount, len(c.cpus), minCpu, minCpu+nCpus*vppContainerCount)
- return nil, err
- }
- if vppContainerCount == 0 {
- cpuCtx.cpus = c.cpus[minCpu : maxCpu-nCpus]
- } else if vppContainerCount == 1 {
- cpuCtx.cpus = c.cpus[minCpu+nCpus : maxCpu]
- } else {
- return nil, fmt.Errorf("too many VPP containers; CPU allocation for >2 VPP containers is not implemented yet")
- }
-
- cpuCtx.cpuAllocator = c
- return &cpuCtx, nil
-}
-
-func (c *CpuAllocatorT) readCpus() error {
- var first, last int
-
- // Path depends on cgroup version. We need to check which version is in use.
- // For that following command can be used: 'stat -fc %T /sys/fs/cgroup/'
- // In case the output states 'cgroup2fs' then cgroups v2 is used, 'tmpfs' in case cgroups v1.
- cmd := exec.Command("stat", "-fc", "%T", "/sys/fs/cgroup/")
- byteOutput, err := cmd.CombinedOutput()
- if err != nil {
- return err
- }
- CpuPath := CgroupPath
- if strings.Contains(string(byteOutput), "tmpfs") {
- CpuPath += "cpuset/cpuset.effective_cpus"
- } else if strings.Contains(string(byteOutput), "cgroup2fs") {
- CpuPath += "cpuset.cpus.effective"
- } else {
- return errors.New("cgroup unknown fs: " + string(byteOutput))
- }
-
- file, err := os.Open(CpuPath)
- if err != nil {
- return err
- }
- defer file.Close()
-
- sc := bufio.NewScanner(file)
- sc.Scan()
- line := sc.Text()
- _, err = fmt.Sscanf(line, "%d-%d", &first, &last)
- if err != nil {
- return err
- }
- for i := first; i <= last; i++ {
- c.cpus = append(c.cpus, i)
- }
- return nil
-}
-
-func CpuAllocator() (*CpuAllocatorT, error) {
- if cpuAllocator == nil {
- cpuAllocator = new(CpuAllocatorT)
- err := cpuAllocator.readCpus()
- if err != nil {
- return nil, err
- }
- }
- return cpuAllocator, nil
-}
diff --git a/extras/hs-test/cpu_pinning_test.go b/extras/hs-test/cpu_pinning_test.go
new file mode 100644
index 00000000000..b8dec65937e
--- /dev/null
+++ b/extras/hs-test/cpu_pinning_test.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterCpuPinningSoloTests(DefaultCpuConfigurationTest, SkipCoresTest)
+}
+
+// TODO: Add more CPU configuration tests
+
+func DefaultCpuConfigurationTest(s *CpuPinningSuite) {
+ vpp := s.GetContainerByName(SingleTopoContainerVpp).VppInstance
+ s.AssertNil(vpp.Start())
+}
+
+func SkipCoresTest(s *CpuPinningSuite) {
+
+ skipCoresConfiguration := VppCpuConfig{
+ PinMainCpu: true,
+ PinWorkersCorelist: true,
+ SkipCores: 1,
+ }
+
+ vpp := s.GetContainerByName(SingleTopoContainerVpp).VppInstance
+ vpp.CpuConfig = skipCoresConfiguration
+
+ s.AssertNil(vpp.Start())
+}
diff --git a/extras/hs-test/docker/Dockerfile.build b/extras/hs-test/docker/Dockerfile.build
deleted file mode 100644
index 8b2652e93fc..00000000000
--- a/extras/hs-test/docker/Dockerfile.build
+++ /dev/null
@@ -1,8 +0,0 @@
-ARG UBUNTU_VERSION
-
-FROM ubuntu:${UBUNTU_VERSION}
-
-RUN apt-get update \
- && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
- && rm -rf /var/lib/apt/lists/*
-
diff --git a/extras/hs-test/docker/Dockerfile.curl b/extras/hs-test/docker/Dockerfile.curl
index 81d15e86c82..cbb0bbe734f 100644
--- a/extras/hs-test/docker/Dockerfile.curl
+++ b/extras/hs-test/docker/Dockerfile.curl
@@ -1,6 +1,14 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
COPY script/build_curl.sh /build_curl.sh
+COPY resources/curl/* /tmp/
+RUN fallocate -l 10MB /tmp/testFile
RUN apt-get update && apt-get install wget
RUN /build_curl.sh
diff --git a/extras/hs-test/docker/Dockerfile.nginx b/extras/hs-test/docker/Dockerfile.nginx
index 11ec6af156d..386f8e90016 100644
--- a/extras/hs-test/docker/Dockerfile.nginx
+++ b/extras/hs-test/docker/Dockerfile.nginx
@@ -3,7 +3,7 @@ ARG UBUNTU_VERSION
FROM ubuntu:${UBUNTU_VERSION}
RUN apt-get update \
- && apt-get install -y nginx gdb less \
+ && apt-get install -y nginx gdb less libunwind-dev \
&& rm -rf /var/lib/apt/lists/*
COPY vpp-data/lib/* /usr/lib/
diff --git a/extras/hs-test/docker/Dockerfile.nginx-http3 b/extras/hs-test/docker/Dockerfile.nginx-http3
index 5d66a2528a6..8b2f8406d38 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-http3
+++ b/extras/hs-test/docker/Dockerfile.nginx-http3
@@ -1,4 +1,10 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
COPY script/build_boringssl.sh /build_boringssl.sh
RUN git clone https://boringssl.googlesource.com/boringssl
diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server
index 1971158131b..ecb8f590f89 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-server
+++ b/extras/hs-test/docker/Dockerfile.nginx-server
@@ -7,6 +7,10 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
COPY resources/nginx/nginx_server_mirroring.conf /nginx.conf
+COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh
+COPY resources/nginx/html/index.html /usr/share/nginx/index.html
+RUN fallocate -l 10MB /usr/share/nginx/httpTestFile
+RUN mkdir /usr/share/nginx/upload && chmod 777 /usr/share/nginx/upload
-ENTRYPOINT ["nginx", "-c", "/nginx.conf"]
+ENTRYPOINT ["nginx_server_entrypoint.sh"]
diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp
index ace83c593c6..82a1a1a73d3 100644
--- a/extras/hs-test/docker/Dockerfile.vpp
+++ b/extras/hs-test/docker/Dockerfile.vpp
@@ -5,7 +5,7 @@ FROM ubuntu:${UBUNTU_VERSION}
RUN apt-get update \
&& apt-get install -y openssl libapr1 libnuma1 libsubunit0 \
iproute2 libnl-3-dev libnl-route-3-dev python3 iputils-ping \
- vim gdb \
+ vim gdb libunwind-dev redis redis-tools iperf3 \
&& rm -rf /var/lib/apt/lists/*
ENV DIR=vpp-data/lib/vpp_plugins
@@ -20,6 +20,7 @@ COPY \
$DIR/nsim_plugin.so \
$DIR/prom_plugin.so \
$DIR/tlsopenssl_plugin.so \
+ $DIR/mactime_plugin.so \
/usr/lib/x86_64-linux-gnu/vpp_plugins/
COPY vpp-data/bin/vpp /usr/bin/
diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go
index ce852bea3e0..6b4739a5457 100644
--- a/extras/hs-test/echo_test.go
+++ b/extras/hs-test/echo_test.go
@@ -1,51 +1,56 @@
package main
+import (
+ . "fd.io/hs-test/infra"
+)
+
func init() {
- registerVethTests(EchoBuiltinTest)
- registerSoloVethTests(TcpWithLossTest)
+ RegisterVethTests(EchoBuiltinTest)
+ RegisterSoloVethTests(TcpWithLossTest)
}
func EchoBuiltinTest(s *VethsSuite) {
- serverVpp := s.getContainerByName("server-vpp").vppInstance
- serverVeth := s.getInterfaceByName(serverInterfaceName)
+ serverVpp := s.GetContainerByName("server-vpp").VppInstance
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
- serverVpp.vppctl("test echo server " +
- " uri tcp://" + serverVeth.ip4AddressString() + "/1234")
+ serverVpp.Vppctl("test echo server " +
+ " uri tcp://" + serverVeth.Ip4AddressString() + "/1234")
- clientVpp := s.getContainerByName("client-vpp").vppInstance
+ clientVpp := s.GetContainerByName("client-vpp").VppInstance
- o := clientVpp.vppctl("test echo client nclients 100 bytes 1 verbose" +
+ o := clientVpp.Vppctl("test echo client nclients 100 bytes 1 verbose" +
" syn-timeout 100 test-timeout 100" +
- " uri tcp://" + serverVeth.ip4AddressString() + "/1234")
- s.log(o)
- s.assertNotContains(o, "failed:")
+ " uri tcp://" + serverVeth.Ip4AddressString() + "/1234")
+ s.Log(o)
+ s.AssertNotContains(o, "failed:")
}
// unstable with multiple workers
func TcpWithLossTest(s *VethsSuite) {
s.SkipIfMultiWorker()
- serverVpp := s.getContainerByName("server-vpp").vppInstance
+ serverVpp := s.GetContainerByName("server-vpp").VppInstance
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- serverVpp.vppctl("test echo server uri tcp://%s/20022",
- serverVeth.ip4AddressString())
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ serverVpp.Vppctl("test echo server uri tcp://%s/20022",
+ serverVeth.Ip4AddressString())
- clientVpp := s.getContainerByName("client-vpp").vppInstance
+ clientVpp := s.GetContainerByName("client-vpp").VppInstance
// Ensure that VPP doesn't abort itself with NSIM enabled
// Warning: Removing this ping will make VPP crash!
- clientVpp.vppctl("ping %s", serverVeth.ip4AddressString())
+ clientVpp.Vppctl("ping %s", serverVeth.Ip4AddressString())
// Add loss of packets with Network Delay Simulator
- clientVpp.vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" +
+ clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" +
" packet-size 1400 packets-per-drop 1000")
- clientVpp.vppctl("nsim output-feature enable-disable host-" + s.getInterfaceByName(clientInterfaceName).name)
+ name := s.GetInterfaceByName(ClientInterfaceName).Name()
+ clientVpp.Vppctl("nsim output-feature enable-disable host-" + name)
// Do echo test from client-vpp container
- output := clientVpp.vppctl("test echo client uri tcp://%s/20022 verbose echo-bytes mbytes 50",
- serverVeth.ip4AddressString())
- s.log(output)
- s.assertNotEqual(len(output), 0)
- s.assertNotContains(output, "failed", output)
+ output := clientVpp.Vppctl("test echo client uri tcp://%s/20022 verbose echo-bytes mbytes 50",
+ serverVeth.Ip4AddressString())
+ s.Log(output)
+ s.AssertNotEqual(len(output), 0)
+ s.AssertNotContains(output, "failed", output)
}
diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go
index 8773fa2417d..91cb1032bba 100644
--- a/extras/hs-test/framework_test.go
+++ b/extras/hs-test/framework_test.go
@@ -1,13 +1,36 @@
package main
import (
+ "fmt"
+ "os"
+ "strings"
"testing"
+ "time"
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestHst(t *testing.T) {
+ if *IsVppDebug {
+ // 30 minute timeout so that the framework won't timeout while debugging
+ SuiteTimeout = time.Minute * 30
+ } else {
+ SuiteTimeout = time.Minute * 5
+ }
+
+ output, err := os.ReadFile("/sys/devices/system/node/online")
+ if err == nil && strings.Contains(string(output), "-") {
+ NumaAwareCpuAlloc = true
+ }
+ // creates a file with PPID, used for 'make cleanup-hst'
+ ppid := fmt.Sprint(os.Getppid())
+ ppid = ppid[:len(ppid)-1]
+ f, _ := os.Create(".last_hst_ppid")
+ f.Write([]byte(ppid))
+ f.Close()
+
RegisterFailHandler(Fail)
RunSpecs(t, "HST")
}
diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod
index 3be9ba20a86..01cb9000bdc 100644
--- a/extras/hs-test/go.mod
+++ b/extras/hs-test/go.mod
@@ -1,33 +1,67 @@
module fd.io/hs-test
-go 1.21
+go 1.22.5
require (
+ github.com/cilium/cilium v1.15.7
+ github.com/docker/docker v27.1.1+incompatible
+ github.com/docker/go-units v0.5.0
github.com/edwarnicke/exechelper v1.0.3
+ github.com/onsi/ginkgo/v2 v2.17.2
+ github.com/onsi/gomega v1.33.1
+ github.com/sirupsen/logrus v1.9.3
go.fd.io/govpp v0.10.0
gopkg.in/yaml.v3 v3.0.1
)
require (
- github.com/go-logr/logr v1.4.1 // indirect
- github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
- golang.org/x/net v0.20.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- golang.org/x/tools v0.17.0 // indirect
-)
-
-require (
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/errors v0.22.0 // indirect
+ github.com/go-openapi/strfmt v0.23.0 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/go-openapi/validate v0.24.0 // indirect
+ github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
- github.com/onsi/ginkgo/v2 v2.16.0
- github.com/onsi/gomega v1.32.0
+ github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
+ github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
+ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/sirupsen/logrus v1.9.3
+ github.com/sasha-s/go-deadlock v0.3.1 // indirect
+ github.com/spf13/cobra v1.8.1 // indirect
+ github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect
+ github.com/spf13/viper v1.19.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
- golang.org/x/sys v0.16.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
+ go.opentelemetry.io/otel v1.28.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
+ go.opentelemetry.io/otel/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.28.0 // indirect
+ go.opentelemetry.io/otel/trace v1.28.0 // indirect
+ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
+ golang.org/x/net v0.26.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.21.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ golang.org/x/tools v0.22.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gotest.tools/v3 v3.5.1 // indirect
+ k8s.io/apimachinery v0.30.2 // indirect
+ k8s.io/client-go v0.30.2 // indirect
+ k8s.io/klog/v2 v2.120.1 // indirect
+ k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
)
diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum
index 479b0289814..fb555ad7abf 100644
--- a/extras/hs-test/go.sum
+++ b/extras/hs-test/go.sum
@@ -1,70 +1,268 @@
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
+github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cilium/checkmate v1.0.3 h1:CQC5eOmlAZeEjPrVZY3ZwEBH64lHlx9mXYdUehEwI5w=
+github.com/cilium/checkmate v1.0.3/go.mod h1:KiBTasf39/F2hf2yAmHw21YFl3hcEyP4Yk6filxc12A=
+github.com/cilium/cilium v1.15.7 h1:7LwGfAW/fR/VFcm6zlESjE2Ut5vJWe+kdWq3RNJrNRc=
+github.com/cilium/cilium v1.15.7/go.mod h1:6Ml8eeyWjMJKDeadutWhn5NibMps0H+yLOgfKBoHTUs=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
+github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edwarnicke/exechelper v1.0.3 h1:OY2ocGAITTqnEDvZk0dRQSeMIQvyH0SyL/4ncz+5GeQ=
github.com/edwarnicke/exechelper v1.0.3/go.mod h1:R65OUPKns4bgeHkCmfSHbmqLBU8aHZxTgLmEyUBUk4U=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
+github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
+github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
+github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
+github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
+github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
+github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
-github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
-github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
-github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
-github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
+github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
+github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
+github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
+github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
+github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
+github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
+github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
+github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
+github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
+github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21 h1:tcHUxOT8j/R+0S+A1j8D2InqguXFNxAiij+8QFOlX7Y=
+github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21/go.mod h1:whJevzBpTrid75eZy99s3DqCmy05NfibNaF2Ol5Ox5A=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.fd.io/govpp v0.10.0 h1:lL93SbqOILjON2pMvazrlHRekGYTRy0Qmj57RuAkxR0=
go.fd.io/govpp v0.10.0/go.mod h1:5m3bZM9ck+2EGC2O3ASmSSJAaoouyOlVWtiwj5BdCv0=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
-golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
-google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
-google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
+k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
+k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
+k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh
new file mode 100644
index 00000000000..85c0dd72705
--- /dev/null
+++ b/extras/hs-test/hs_test.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+
+source vars
+
+args=
+single_test=0
+persist_set=0
+unconfigure_set=0
+debug_set=0
+leak_check_set=0
+debug_build=
+ginkgo_args=
+tc_name=
+
+for i in "$@"
+do
+case "${i}" in
+ --persist=*)
+ persist="${i#*=}"
+ if [ "$persist" = "true" ]; then
+ args="$args -persist"
+ persist_set=1
+ fi
+ ;;
+ --debug=*)
+ debug="${i#*=}"
+ if [ "$debug" = "true" ]; then
+ args="$args -debug"
+ debug_set=1
+ fi
+ ;;
+ --debug_build=*)
+ debug_build="${i#*=}"
+ if [ "$debug_build" = "true" ]; then
+ args="$args -debug_build"
+ fi
+ ;;
+ --verbose=*)
+ verbose="${i#*=}"
+ if [ "$verbose" = "true" ]; then
+ args="$args -verbose"
+ fi
+ ;;
+ --unconfigure=*)
+ unconfigure="${i#*=}"
+ if [ "$unconfigure" = "true" ]; then
+ args="$args -unconfigure"
+ unconfigure_set=1
+ fi
+ ;;
+ --cpus=*)
+ args="$args -cpus ${i#*=}"
+ ;;
+ --vppsrc=*)
+ args="$args -vppsrc ${i#*=}"
+ ;;
+ --test=*)
+ tc_name="${i#*=}"
+ if [ "$tc_name" != "all" ]; then
+ single_test=1
+ ginkgo_args="$ginkgo_args --focus $tc_name -vv"
+ args="$args -verbose"
+ else
+ ginkgo_args="$ginkgo_args -v"
+ fi
+ ;;
+ --parallel=*)
+ ginkgo_args="$ginkgo_args -procs=${i#*=}"
+ ;;
+ --repeat=*)
+ ginkgo_args="$ginkgo_args --repeat=${i#*=}"
+ ;;
+ --cpu0=*)
+ cpu0="${i#*=}"
+ if [ "$cpu0" = "true" ]; then
+ args="$args -cpu0"
+ fi
+ ;;
+ --leak_check=*)
+ leak_check="${i#*=}"
+ if [ "$leak_check" = "true" ]; then
+ args="$args -leak_check"
+ leak_check_set=1
+ fi
+ ;;
+esac
+done
+
+if [ $single_test -eq 0 ] && [ $persist_set -eq 1 ]; then
+ echo "persist flag is not supported while running all tests!"
+ exit 1
+fi
+
+if [ $unconfigure_set -eq 1 ] && [ $single_test -eq 0 ]; then
+ echo "a single test has to be specified when unconfigure is set"
+ exit 1
+fi
+
+if [ $persist_set -eq 1 ] && [ $unconfigure_set -eq 1 ]; then
+ echo "setting persist flag and unconfigure flag is not allowed"
+ exit 1
+fi
+
+if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then
+ echo "VPP debug flag is not supported while running all tests!"
+ exit 1
+fi
+
+if [ $leak_check_set -eq 1 ]; then
+ if [ $single_test -eq 0 ]; then
+ echo "a single test has to be specified when leak_check is set"
+ exit 1
+ fi
+ ginkgo_args="--focus $tc_name"
+ sudo -E go run github.com/onsi/ginkgo/v2/ginkgo $ginkgo_args -- $args
+ exit 0
+fi
+
+if [ -n "${BUILD_NUMBER}" ]; then
+ ginkgo_args="$ginkgo_args --no-color"
+fi
+
+mkdir -p summary
+# shellcheck disable=SC2086
+sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --json-report=summary/report.json $ginkgo_args -- $args
+
+if [ $? != 0 ]; then
+ jq -r '.[0] | .SpecReports[] | select((.State == "failed") or (.State == "timedout") or (.State == "panicked")) | select(.Failure != null) |
+"TestName:
+ \(.LeafNodeText)
+Suite:
+ \(.Failure.FailureNodeLocation.FileName)
+Message:\n"
++(if .ReportEntries? then .ReportEntries[] | select(.Name == "VPP Backtrace") |
+"\tVPP crashed
+Full Back Trace:
+\(.Value.Representation | ltrimstr("{{red}}") | rtrimstr("{{/}}"))" else
+ "\(.Failure.Message)"
+ + (if .Failure.Message == "A spec timeout occurred" then "\n" else
+"\nFull Stack Trace:
+\(.Failure.Location.FullStackTrace)\n" end) end)' summary/report.json > summary/failed-summary.log \
+ && echo "Summary generated -> summary/failed-summary.log"
+else
+ if [ -e "summary/failed-summary.log" ]; then
+ rm summary/failed-summary.log
+ fi
+fi
diff --git a/extras/hs-test/hst_suite.go b/extras/hs-test/hst_suite.go
deleted file mode 100644
index 9cb79c563fa..00000000000
--- a/extras/hs-test/hst_suite.go
+++ /dev/null
@@ -1,502 +0,0 @@
-package main
-
-import (
- "bufio"
- "errors"
- "flag"
- "fmt"
- "io"
- "log"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
- "gopkg.in/yaml.v3"
-)
-
-const (
- DEFAULT_NETWORK_NUM int = 1
-)
-
-var isPersistent = flag.Bool("persist", false, "persists topology config")
-var isVerbose = flag.Bool("verbose", false, "verbose test output")
-var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
-var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
-var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
-var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
-
-type HstSuite struct {
- containers map[string]*Container
- vppContainerCount int
- volumes []string
- netConfigs []NetConfig
- netInterfaces map[string]*NetInterface
- ip4AddrAllocator *Ip4AddressAllocator
- testIds map[string]string
- cpuAllocator *CpuAllocatorT
- cpuContexts []*CpuContext
- cpuPerVpp int
- pid string
- logger *log.Logger
- logFile *os.File
-}
-
-func (s *HstSuite) SetupSuite() {
- s.createLogger()
- s.log("Suite Setup")
- RegisterFailHandler(func(message string, callerSkip ...int) {
- s.hstFail()
- Fail(message, callerSkip...)
- })
- var err error
- s.pid = fmt.Sprint(os.Getpid())
- s.cpuAllocator, err = CpuAllocator()
- if err != nil {
- Fail("failed to init cpu allocator: " + fmt.Sprint(err))
- }
- s.cpuPerVpp = *nConfiguredCpus
-}
-
-func (s *HstSuite) AllocateCpus() []int {
- cpuCtx, err := s.cpuAllocator.Allocate(s.vppContainerCount, s.cpuPerVpp)
- s.assertNil(err)
- s.AddCpuContext(cpuCtx)
- return cpuCtx.cpus
-}
-
-func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
- s.cpuContexts = append(s.cpuContexts, cpuCtx)
-}
-
-func (s *HstSuite) TearDownSuite() {
- defer s.logFile.Close()
- s.log("Suite Teardown")
- s.unconfigureNetworkTopology()
-}
-
-func (s *HstSuite) TearDownTest() {
- s.log("Test Teardown")
- if *isPersistent {
- return
- }
- s.resetContainers()
- s.removeVolumes()
- s.ip4AddrAllocator.deleteIpAddresses()
-}
-
-func (s *HstSuite) skipIfUnconfiguring() {
- if *isUnconfiguring {
- s.skip("skipping to unconfigure")
- }
-}
-
-func (s *HstSuite) SetupTest() {
- s.log("Test Setup")
- s.vppContainerCount = 0
- s.skipIfUnconfiguring()
- s.setupVolumes()
- s.setupContainers()
-}
-
-func (s *HstSuite) setupVolumes() {
- for _, volume := range s.volumes {
- cmd := "docker volume create --name=" + volume
- s.log(cmd)
- exechelper.Run(cmd)
- }
-}
-
-func (s *HstSuite) setupContainers() {
- for _, container := range s.containers {
- if !container.isOptional {
- container.run()
- }
- }
-}
-
-func (s *HstSuite) logVppInstance(container *Container, maxLines int) {
- if container.vppInstance == nil {
- return
- }
-
- logSource := container.getHostWorkDir() + defaultLogFilePath
- file, err := os.Open(logSource)
-
- if err != nil {
- return
- }
- defer file.Close()
-
- scanner := bufio.NewScanner(file)
- var lines []string
- var counter int
-
- for scanner.Scan() {
- lines = append(lines, scanner.Text())
- counter++
- if counter > maxLines {
- lines = lines[1:]
- counter--
- }
- }
-
- s.log("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:")
- for _, line := range lines {
- s.log(line)
- }
- s.log("^^^^^^^^^^^^^^^\n\n")
-}
-
-func (s *HstSuite) hstFail() {
- s.log("Containers: " + fmt.Sprint(s.containers))
- for _, container := range s.containers {
- out, err := container.log(20)
- if err != nil {
- s.log("An error occured while obtaining '" + container.name + "' container logs: " + fmt.Sprint(err))
- continue
- }
- s.log("\nvvvvvvvvvvvvvvv " +
- container.name + ":\n" +
- out +
- "^^^^^^^^^^^^^^^\n\n")
- s.logVppInstance(container, 20)
- }
-}
-
-func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).To(BeNil(), msgAndArgs...)
-}
-
-func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).ToNot(BeNil(), msgAndArgs...)
-}
-
-func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- Expect(actual).To(Equal(expected), msgAndArgs...)
-}
-
-func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- Expect(actual).ToNot(Equal(expected), msgAndArgs...)
-}
-
-func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- Expect(testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
-}
-
-func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- Expect(testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
-}
-
-func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).ToNot(BeEmpty(), msgAndArgs...)
-}
-
-func (s *HstSuite) createLogger() {
- suiteName := CurrentSpecReport().ContainerHierarchyTexts[0]
- var err error
- s.logFile, err = os.Create("summary/" + suiteName + ".log")
- if err != nil {
- Fail("Unable to create log file.")
- }
- s.logger = log.New(io.Writer(s.logFile), "", log.LstdFlags)
-}
-
-// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
-// to keep console tidy
-func (s *HstSuite) log(arg any) {
- logs := strings.Split(fmt.Sprint(arg), "\n")
- for _, line := range logs {
- s.logger.Println(line)
- }
- if *isVerbose {
- GinkgoWriter.Println(arg)
- }
-}
-
-func (s *HstSuite) skip(args string) {
- Skip(args)
-}
-
-func (s *HstSuite) SkipIfMultiWorker(args ...any) {
- if *nConfiguredCpus > 1 {
- s.skip("test case not supported with multiple vpp workers")
- }
-}
-
-func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
- imageName := "hs-test/nginx-http3"
-
- cmd := exec.Command("docker", "images", imageName)
- byteOutput, err := cmd.CombinedOutput()
- if err != nil {
- s.log("error while searching for docker image")
- return
- }
- if !strings.Contains(string(byteOutput), imageName) {
- s.skip("extended tests not built")
- }
-}
-
-func (s *HstSuite) resetContainers() {
- for _, container := range s.containers {
- container.stop()
- }
-}
-
-func (s *HstSuite) removeVolumes() {
- for _, volumeName := range s.volumes {
- cmd := "docker volume rm " + volumeName
- exechelper.Run(cmd)
- os.RemoveAll(volumeName)
- }
-}
-
-func (s *HstSuite) getNetNamespaceByName(name string) string {
- return name + s.pid
-}
-
-func (s *HstSuite) getInterfaceByName(name string) *NetInterface {
- return s.netInterfaces[name+s.pid]
-}
-
-func (s *HstSuite) getContainerByName(name string) *Container {
- return s.containers[name+s.pid]
-}
-
-/*
- * Create a copy and return its address, so that individial tests which call this
- * are not able to modify the original container and affect other tests by doing that
- */
-func (s *HstSuite) getTransientContainerByName(name string) *Container {
- containerCopy := *s.containers[name+s.pid]
- return &containerCopy
-}
-
-func (s *HstSuite) loadContainerTopology(topologyName string) {
- data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
- if err != nil {
- Fail("read error: " + fmt.Sprint(err))
- }
- var yamlTopo YamlTopology
- err = yaml.Unmarshal(data, &yamlTopo)
- if err != nil {
- Fail("unmarshal error: " + fmt.Sprint(err))
- }
-
- for _, elem := range yamlTopo.Volumes {
- volumeMap := elem["volume"].(VolumeConfig)
- hostDir := volumeMap["host-dir"].(string)
- workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + volumeDir
- volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
- hostDir = volDirReplacer.Replace(hostDir)
- s.volumes = append(s.volumes, hostDir)
- }
-
- s.containers = make(map[string]*Container)
- for _, elem := range yamlTopo.Containers {
- newContainer, err := newContainer(s, elem)
- newContainer.suite = s
- newContainer.name += newContainer.suite.pid
- if err != nil {
- Fail("container config error: " + fmt.Sprint(err))
- }
- s.containers[newContainer.name] = newContainer
- }
-}
-
-func (s *HstSuite) loadNetworkTopology(topologyName string) {
- data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
- if err != nil {
- Fail("read error: " + fmt.Sprint(err))
- }
- var yamlTopo YamlTopology
- err = yaml.Unmarshal(data, &yamlTopo)
- if err != nil {
- Fail("unmarshal error: " + fmt.Sprint(err))
- }
-
- s.ip4AddrAllocator = NewIp4AddressAllocator()
- s.netInterfaces = make(map[string]*NetInterface)
-
- for _, elem := range yamlTopo.Devices {
- if _, ok := elem["name"]; ok {
- elem["name"] = elem["name"].(string) + s.pid
- }
-
- if peer, ok := elem["peer"].(NetDevConfig); ok {
- if peer["name"].(string) != "" {
- peer["name"] = peer["name"].(string) + s.pid
- }
- if _, ok := peer["netns"]; ok {
- peer["netns"] = peer["netns"].(string) + s.pid
- }
- }
-
- if _, ok := elem["netns"]; ok {
- elem["netns"] = elem["netns"].(string) + s.pid
- }
-
- if _, ok := elem["interfaces"]; ok {
- interfaceCount := len(elem["interfaces"].([]interface{}))
- for i := 0; i < interfaceCount; i++ {
- elem["interfaces"].([]interface{})[i] = elem["interfaces"].([]interface{})[i].(string) + s.pid
- }
- }
-
- switch elem["type"].(string) {
- case NetNs:
- {
- if namespace, err := newNetNamespace(elem); err == nil {
- s.netConfigs = append(s.netConfigs, &namespace)
- } else {
- Fail("network config error: " + fmt.Sprint(err))
- }
- }
- case Veth, Tap:
- {
- if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
- s.netConfigs = append(s.netConfigs, netIf)
- s.netInterfaces[netIf.Name()] = netIf
- } else {
- Fail("network config error: " + fmt.Sprint(err))
- }
- }
- case Bridge:
- {
- if bridge, err := newBridge(elem); err == nil {
- s.netConfigs = append(s.netConfigs, &bridge)
- } else {
- Fail("network config error: " + fmt.Sprint(err))
- }
- }
- }
- }
-}
-
-func (s *HstSuite) configureNetworkTopology(topologyName string) {
- s.loadNetworkTopology(topologyName)
-
- if *isUnconfiguring {
- return
- }
-
- for _, nc := range s.netConfigs {
- if err := nc.configure(); err != nil {
- Fail("Network config error: " + fmt.Sprint(err))
- }
- }
-}
-
-func (s *HstSuite) unconfigureNetworkTopology() {
- if *isPersistent {
- return
- }
- for _, nc := range s.netConfigs {
- nc.unconfigure()
- }
-}
-
-func (s *HstSuite) getTestId() string {
- testName := CurrentSpecReport().LeafNodeText
-
- if s.testIds == nil {
- s.testIds = map[string]string{}
- }
-
- if _, ok := s.testIds[testName]; !ok {
- s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
- }
-
- return s.testIds[testName]
-}
-
-// Returns last 4 digits of PID
-func (s *HstSuite) getPortFromPid() string {
- port := s.pid
- for len(port) < 4 {
- port += "0"
- }
- return port[len(port)-4:]
-}
-
-func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) {
- cmd := exec.Command("iperf3", "-4", "-s", "-p", s.getPortFromPid())
- if env != nil {
- cmd.Env = env
- }
- s.log(cmd)
- err := cmd.Start()
- if err != nil {
- msg := fmt.Errorf("failed to start iperf server: %v", err)
- running <- msg
- return
- }
- running <- nil
- <-done
- cmd.Process.Kill()
-}
-
-func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
- defer func() {
- clnCh <- nil
- }()
-
- nTries := 0
-
- for {
- cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.getPortFromPid())
- if env != nil {
- cmd.Env = env
- }
- s.log(cmd)
- o, err := cmd.CombinedOutput()
- if err != nil {
- if nTries > 5 {
- clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
- return
- }
- time.Sleep(1 * time.Second)
- nTries++
- continue
- } else {
- clnRes <- fmt.Sprintf("Client output: %s", o)
- }
- break
- }
-}
-
-func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
- cmd := newCommand([]string{"./http_server", addressPort, s.pid}, netNs)
- err := cmd.Start()
- s.log(cmd)
- if err != nil {
- s.log("Failed to start http server: " + fmt.Sprint(err))
- return
- }
- running <- struct{}{}
- <-done
- cmd.Process.Kill()
-}
-
-func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) {
- defer func() {
- finished <- errors.New("wget error")
- }()
-
- cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
- netNs)
- s.log(cmd)
- o, err := cmd.CombinedOutput()
- if err != nil {
- finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
- return
- } else if !strings.Contains(string(o), "200 OK") {
- finished <- fmt.Errorf("wget error: response not 200 OK")
- return
- }
- finished <- nil
-}
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index 94cb0cad064..d1d28fb893c 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -1,287 +1,1157 @@
package main
import (
+ "bytes"
"fmt"
+ "io"
+ "math/rand"
+ "net"
"net/http"
+ "net/http/httptrace"
"os"
- "strings"
+ "strconv"
+ "sync"
"time"
+ "github.com/onsi/gomega/ghttp"
+ "github.com/onsi/gomega/gmeasure"
+
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
- registerNsTests(HttpTpsTest)
- registerVethTests(HttpCliTest, HttpCliConnectErrorTest)
- registerNoTopoTests(NginxHttp3Test, NginxAsServerTest,
- NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest, HeaderServerTest,
+ RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest)
+ RegisterSoloVethTests(HttpClientGetMemLeakTest)
+ RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest,
HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest,
- HttpCliBadRequestTest)
- registerNoTopoSoloTests(HttpStaticPromTest)
+ HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest,
+ HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest,
+ HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
+ HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
+ HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
+ HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
+ HttpClientPostFileTest, HttpClientPostFilePtrTest, AuthorityFormTargetTest, HttpRequestLineTest)
+ RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
+ PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest)
+}
+
+const wwwRootPath = "/tmp/www_root"
+
+func httpDownloadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data interface{}) {
+ url, isValid := data.(string)
+ s.AssertEqual(true, isValid)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", url, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ t := time.Now()
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertHttpStatus(resp, 200)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ duration := time.Since(t)
+ experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
+}
+
+func HttpGetTpsInterruptModeTest(s *NoTopoSuite) {
+ HttpGetTpsTest(s)
+}
+
+func HttpGetTpsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":8080/test_file_10M"
+
+ vpp.Vppctl("http tps uri tcp://0.0.0.0/8080")
+
+ s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url)
+}
+
+func httpUploadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data interface{}) {
+ url, isValid := data.(string)
+ s.AssertEqual(true, isValid)
+ body := make([]byte, 10485760)
+ _, err := rand.Read(body)
+ client := NewHttpClient()
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
+ s.AssertNil(err, fmt.Sprint(err))
+ t := time.Now()
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertHttpStatus(resp, 200)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ duration := time.Since(t)
+ experiment.RecordValue("Upload Speed", (float64(req.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
}
-func HttpTpsTest(s *NsSuite) {
- iface := s.getInterfaceByName(clientInterface)
- client_ip := iface.ip4AddressString()
- port := "8080"
- finished := make(chan error, 1)
- clientNetns := s.getNetNamespaceByName("cln")
+func HttpPostTpsInterruptModeTest(s *NoTopoSuite) {
+ HttpPostTpsTest(s)
+}
+
+func HttpPostTpsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":8080/test_file_10M"
+
+ vpp.Vppctl("http tps uri tcp://0.0.0.0/8080")
+
+ s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url)
+}
+
+func HttpPersistentConnectionTest(s *NoTopoSuite) {
+ // testing url handler app do not support multi-thread
+ s.SkipIfMultiWorker()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("test-url-handler enable"))
+
+ transport := http.DefaultTransport
+ transport.(*http.Transport).Proxy = nil
+ transport.(*http.Transport).DisableKeepAlives = false
+ client := &http.Client{
+ Transport: transport,
+ Timeout: time.Second * 30,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+ }}
+
+ body := []byte("{\"sandwich\": {\"spam\": 2, \"eggs\": 1}}")
+ req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test3", bytes.NewBuffer(body))
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpContentLength(resp, int64(0))
+ o1 := vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o1)
+ s.AssertContains(o1, "ESTABLISHED")
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test1", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ clientTrace := &httptrace.ClientTrace{
+ GotConn: func(info httptrace.GotConnInfo) {
+ s.AssertEqual(true, info.Reused, "connection not reused")
+ },
+ }
+ req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpBody(resp, "hello")
+ o2 := vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o2)
+ s.AssertContains(o2, "ESTABLISHED")
+ s.AssertEqual(o1, o2)
- container := s.getContainerByName("vpp")
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test2", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpBody(resp, "some data")
+ o2 = vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o2)
+ s.AssertContains(o2, "ESTABLISHED")
+ s.AssertEqual(o1, o2)
+
+}
+
+func HttpPipeliningTest(s *NoTopoSuite) {
+ // testing url handler app do not support multi-thread
+ s.SkipIfMultiWorker()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ s.Log(vpp.Vppctl("test-url-handler enable"))
- // configure vpp in the container
- container.vppInstance.vppctl("http tps uri tcp://0.0.0.0/8080")
+ req1 := "GET /test_delayed HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n"
+ req2 := "GET /test1 HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n"
- go func() {
- defer GinkgoRecover()
- s.startWget(finished, client_ip, port, "test_file_10M", clientNetns)
- }()
- // wait for client
- err := <-finished
- s.assertNil(err, fmt.Sprint(err))
+ conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer conn.Close()
+ err = conn.SetDeadline(time.Now().Add(time.Second * 15))
+ s.AssertNil(err, fmt.Sprint(err))
+ n, err := conn.Write([]byte(req1))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertEqual(n, len([]rune(req1)))
+ // send second request a bit later so first is already in progress
+ time.Sleep(500 * time.Millisecond)
+ n, err = conn.Write([]byte(req2))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertEqual(n, len([]rune(req2)))
+ reply := make([]byte, 1024)
+ n, err = conn.Read(reply)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(string(reply))
+ s.AssertContains(string(reply), "delayed data", "first request response not received")
+ s.AssertNotContains(string(reply), "hello", "second request response received")
+ // make sure response for second request is not received later
+ _, err = conn.Read(reply)
+ s.AssertMatchError(err, os.ErrDeadlineExceeded, "second request response received")
}
func HttpCliTest(s *VethsSuite) {
- serverContainer := s.getContainerByName("server-vpp")
- clientContainer := s.getContainerByName("client-vpp")
+ serverContainer := s.GetContainerByName("server-vpp")
+ clientContainer := s.GetContainerByName("client-vpp")
- serverVeth := s.getInterfaceByName(serverInterfaceName)
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
- serverContainer.vppInstance.vppctl("http cli server")
+ serverContainer.VppInstance.Vppctl("http cli server")
- uri := "http://" + serverVeth.ip4AddressString() + "/80"
+ uri := "http://" + serverVeth.Ip4AddressString() + "/80"
- o := clientContainer.vppInstance.vppctl("http cli client" +
+ o := clientContainer.VppInstance.Vppctl("http cli client" +
" uri " + uri + " query /show/vlib/graph")
- s.log(o)
- s.assertContains(o, "<html>", "<html> not found in the result!")
+ s.Log(o)
+ s.AssertContains(o, "<html>", "<html> not found in the result!")
+ s.AssertContains(o, "</html>", "</html> not found in the result!")
}
func HttpCliConnectErrorTest(s *VethsSuite) {
- clientContainer := s.getContainerByName("client-vpp")
+ clientContainer := s.GetContainerByName("client-vpp")
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
- serverVeth := s.getInterfaceByName(serverInterfaceName)
+ uri := "http://" + serverVeth.Ip4AddressString() + "/80"
- uri := "http://" + serverVeth.ip4AddressString() + "/80"
-
- o := clientContainer.vppInstance.vppctl("http cli client" +
+ o := clientContainer.VppInstance.Vppctl("http cli client" +
" uri " + uri + " query /show/vlib/graph")
- s.log(o)
- s.assertContains(o, "failed to connect")
+ s.Log(o)
+ s.AssertContains(o, "failed to connect")
+}
+
+func HttpClientTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("GET", "/test"),
+ ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}),
+ ghttp.VerifyHeader(http.Header{"Accept": []string{"text/html"}}),
+ ghttp.RespondWith(http.StatusOK, "<html><body><p>Hello</p></body></html>"),
+ ))
+ server.Start()
+ defer server.Close()
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+ s.Log(o)
+ s.AssertContains(o, "<html>", "<html> not found in the result!")
+ s.AssertContains(o, "</html>", "</html> not found in the result!")
}
-func NginxHttp3Test(s *NoTopoSuite) {
- s.SkipUnlessExtendedTestsBuilt()
+func HttpClientErrRespTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("GET", "/test"),
+ ghttp.RespondWith(http.StatusNotFound, "404: Not Found"),
+ ))
+ server.Start()
+ defer server.Close()
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+ s.Log(o)
+ s.AssertContains(o, "404: Not Found", "error not found in the result!")
+}
+
+func HttpClientPostFormTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ body := "field1=value1&field2=value2"
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.VerifyContentType("application/x-www-form-urlencoded"),
+ ghttp.VerifyBody([]byte(body)),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ))
+ server.Start()
+ defer server.Close()
- query := "index.html"
- nginxCont := s.getContainerByName("nginx-http3")
- s.assertNil(nginxCont.run())
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http post uri " + uri + " target /test data " + body)
- vpp := s.getContainerByName("vpp").vppInstance
- vpp.waitForApp("nginx-", 5)
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
+ s.Log(o)
+ s.AssertNotContains(o, "error")
+}
+
+func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) {
+ serverAddress := s.HostAddr()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ fileName := "/tmp/test_file.txt"
+ s.Log(vpp.Container.Exec("fallocate -l " + strconv.Itoa(fileSize) + " " + fileName))
+ s.Log(vpp.Container.Exec("ls -la " + fileName))
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(false),
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.VerifyHeader(http.Header{"Content-Length": []string{strconv.Itoa(fileSize)}}),
+ ghttp.VerifyContentType("application/octet-stream"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ))
+ server.Start()
+ defer server.Close()
+
+ uri := "http://" + serverAddress + "/80"
+ cmd := "http post uri " + uri + " target /test file " + fileName
+ if usePtr {
+ cmd += " use-ptr"
+ }
+ o := vpp.Vppctl(cmd)
+
+ s.Log(o)
+ s.AssertNotContains(o, "error")
+}
+
+func HttpClientPostFileTest(s *NoTopoSuite) {
+ httpClientPostFile(s, false, 32768)
+}
+
+func HttpClientPostFilePtrTest(s *NoTopoSuite) {
+ httpClientPostFile(s, true, 131072)
+}
+
+func cliTestAuthority(s *NoTopoSuite, authority string) {
+ o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
+ s.AssertNotContains(o, "error")
+ s.AssertContains(o, authority)
+}
+
+func cliTestAuthorityError(s *NoTopoSuite, authority string) {
+ o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
+ s.AssertContains(o, "error")
+}
- defer func() { os.Remove(query) }()
- curlCont := s.getContainerByName("curl")
- args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query)
- curlCont.extraRunningArgs = args
- o, err := curlCont.combinedOutput()
- s.assertNil(err, fmt.Sprint(err))
- s.assertContains(o, "<http>", "<http> not found in the result!")
+func AuthorityFormTargetTest(s *NoTopoSuite) {
+ cliTestAuthority(s, "10.10.2.45:20")
+ cliTestAuthority(s, "[dead:beef::1234]:443")
+ cliTestAuthorityError(s, "example.com:80")
+ cliTestAuthorityError(s, "10.10.2.45")
+ cliTestAuthorityError(s, "1000.10.2.45:20")
+ cliTestAuthorityError(s, "[xyz0::1234]:443")
}
func HttpStaticPromTest(s *NoTopoSuite) {
- finished := make(chan error, 1)
query := "stats.prom"
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- s.log(vpp.vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
- s.log(vpp.vppctl("prom enable"))
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("prom enable"))
time.Sleep(time.Second * 5)
- go func() {
- defer GinkgoRecover()
- s.startWget(finished, serverAddress, "80", query, "")
- }()
- err := <-finished
- s.assertNil(err)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, false))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain")
+ s.AssertGreaterThan(resp.ContentLength, 0)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
}
-func HttpStaticMovedTest(s *NoTopoSuite) {
- vpp := s.getContainerByName("vpp").vppInstance
- vpp.container.exec("mkdir -p /tmp/tmp.aaa")
- vpp.container.createFile("/tmp/tmp.aaa/index.html", "<http><body><p>Hello</p></body></http>")
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- s.log(vpp.vppctl("http static server www-root /tmp uri tcp://" + serverAddress + "/80 debug"))
+func promReq(s *NoTopoSuite, url string) {
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", url, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertHttpStatus(resp, 200)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+}
- transport := http.DefaultTransport
- transport.(*http.Transport).Proxy = nil
- client := &http.Client{
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- },
- Transport: transport,
+func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) {
+ defer GinkgoRecover()
+ defer wg.Done()
+ promReq(s, url)
+}
+
+func PromConcurrentConnectionsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":80/stats.prom"
+
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("prom enable"))
+ time.Sleep(time.Second * 5)
+
+ var wg sync.WaitGroup
+ for i := 0; i < 20; i++ {
+ wg.Add(1)
+ go promReqWg(s, url, &wg)
+ }
+ wg.Wait()
+ s.Log(vpp.Vppctl("show session verbose proto http"))
+}
+
+func PromMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":80/stats.prom"
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("prom enable"))
+ time.Sleep(time.Second * 3)
+
+ /* warmup request (FIB) */
+ promReq(s, url)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* collect stats couple of times */
+ for i := 0; i < 5; i++ {
+ time.Sleep(time.Second * 1)
+ promReq(s, url)
+ }
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 5)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
+
+func HttpClientGetMemLeakTest(s *VethsSuite) {
+ s.SkipUnlessLeakCheck()
+
+ serverContainer := s.GetContainerByName("server-vpp").VppInstance
+ clientContainer := s.GetContainerByName("client-vpp").VppInstance
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+
+ /* no goVPP less noise */
+ clientContainer.Disconnect()
+
+ serverContainer.Vppctl("http cli server")
+
+ uri := "http://" + serverVeth.Ip4AddressString() + "/80"
+
+ /* warmup request (FIB) */
+ clientContainer.Vppctl("http cli client uri " + uri + " query /show/version")
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ clientContainer.EnableMemoryTrace()
+ traces1, err := clientContainer.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ clientContainer.Vppctl("http cli client uri " + uri + " query /show/vlib/graph")
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := clientContainer.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ clientContainer.MemLeakCheck(traces1, traces2)
+}
+
+func HttpClientPostMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ serverAddress := s.HostAddr()
+ body := "field1=value1&field2=value2"
+
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ),
+ ghttp.CombineHandlers(
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ),
+ )
+ server.Start()
+ defer server.Close()
+
+ /* warmup request (FIB) */
+ vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
+
+func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
+ vpp.Vppctl("http cli server")
+
+ /* warmup request (FIB) */
+ _, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ _, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+
+}
+
+func HttpStaticFileHandlerDefaultMaxAgeTest(s *NoTopoSuite) {
+ HttpStaticFileHandlerTestFunction(s, "default")
+}
+
+func HttpStaticFileHandlerTest(s *NoTopoSuite) {
+ HttpStaticFileHandlerTestFunction(s, "123")
+}
+
+func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) {
+ var maxAgeFormatted string
+ if max_age == "default" {
+ maxAgeFormatted = ""
+ max_age = "600"
+ } else {
+ maxAgeFormatted = "max-age " + max_age
}
+
+ content := "<html><body><p>Hello</p></body></html>"
+ content2 := "<html><body><p>Page</p></body></html>"
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ err := vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
+ s.AssertNil(err, fmt.Sprint(err))
+ err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m " + maxAgeFormatted))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5)
+ s.AssertEqual(len(resp.Header.Get("Last-Modified")), 29)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content))))
+ s.AssertHttpBody(resp, content)
+ o := vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertNotContains(o, "page.html")
+
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content))))
+ s.AssertHttpBody(resp, content)
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content2))))
+ s.AssertHttpBody(resp, content2)
+ o = vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertContains(o, "page.html")
+}
+
+func HttpStaticPathTraversalTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ vpp.Container.Exec("mkdir -p " + "/tmp/secret_folder")
+ err := vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/../secret_folder/secret_file.txt", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 404)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
+}
+
+func HttpStaticMovedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath + "/tmp.aaa")
+ err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<html><body><p>Hello</p></body></html>")
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
+
+ client := NewHttpClient()
req, err := http.NewRequest("GET", "http://"+serverAddress+":80/tmp.aaa", nil)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
resp, err := client.Do(req)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.assertEqual(301, resp.StatusCode)
- s.assertNotEqual("", resp.Header.Get("Location"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 301)
+ s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/tmp.aaa/index.html")
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpStaticNotFoundTest(s *NoTopoSuite) {
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- s.log(vpp.vppctl("http static server www-root /tmp uri tcp://" + serverAddress + "/80 debug"))
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
- transport := http.DefaultTransport
- transport.(*http.Transport).Proxy = nil
- client := &http.Client{Transport: transport}
+ client := NewHttpClient()
req, err := http.NewRequest("GET", "http://"+serverAddress+":80/notfound.html", nil)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
resp, err := client.Do(req)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.assertEqual(404, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 404)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- vpp.vppctl("http cli server")
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
- transport := http.DefaultTransport
- transport.(*http.Transport).Proxy = nil
- client := &http.Client{Transport: transport}
+ client := NewHttpClient()
req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test", nil)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
resp, err := client.Do(req)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.assertEqual(405, resp.StatusCode)
- // TODO: need to be fixed in http code
- //s.assertNotEqual("", resp.Header.Get("Allow"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 405)
+ s.AssertHttpHeaderWithValue(resp, "Allow", "GET", "server MUST generate an Allow header")
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpCliBadRequestTest(s *NoTopoSuite) {
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- vpp.vppctl("http cli server")
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
- transport := http.DefaultTransport
- transport.(*http.Transport).Proxy = nil
- client := &http.Client{Transport: transport}
+ client := NewHttpClient()
req, err := http.NewRequest("GET", "http://"+serverAddress+":80", nil)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
resp, err := client.Do(req)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.assertEqual(400, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 400)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
}
-func HeaderServerTest(s *NoTopoSuite) {
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- vpp.vppctl("http cli server")
+func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- transport := http.DefaultTransport
- transport.(*http.Transport).Proxy = nil
- client := &http.Client{Transport: transport}
- req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
- s.assertNil(err, fmt.Sprint(err))
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json", nil)
+ s.AssertNil(err, fmt.Sprint(err))
resp, err := client.Do(req)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.assertEqual("http_cli_server", resp.Header.Get("Server"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "vpp_details")
+ s.AssertContains(string(data), "version")
+ s.AssertContains(string(data), "build_date")
+ s.AssertNotContains(string(data), "build_by")
+ s.AssertNotContains(string(data), "build_host")
+ s.AssertNotContains(string(data), "build_dir")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
-func NginxAsServerTest(s *NoTopoSuite) {
- query := "return_ok"
- finished := make(chan error, 1)
+func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json?verbose=true", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "vpp_details")
+ s.AssertContains(string(data), "version")
+ s.AssertContains(string(data), "build_date")
+ s.AssertContains(string(data), "build_by")
+ s.AssertContains(string(data), "build_host")
+ s.AssertContains(string(data), "build_dir")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
+}
- nginxCont := s.getContainerByName("nginx")
- s.assertNil(nginxCont.run())
+func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- vpp := s.getContainerByName("vpp").vppInstance
- vpp.waitForApp("nginx-", 5)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_list.json", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "interface_list")
+ s.AssertContains(string(data), s.VppIfName())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
+}
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
+func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- defer func() { os.Remove(query) }()
- go func() {
- defer GinkgoRecover()
- s.startWget(finished, serverAddress, "80", query, "")
- }()
- s.assertNil(<-finished)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_stats.json", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "interface_stats")
+ s.AssertContains(string(data), "local0")
+ s.AssertContains(string(data), s.VppIfName())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
-func parseString(s, pattern string) string {
- temp := strings.Split(s, "\n")
- for _, item := range temp {
- if strings.Contains(item, pattern) {
- return item
- }
- }
- return ""
-}
-
-func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
- nRequests := 1000000
- nClients := 1000
-
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
-
- vpp := s.getContainerByName("vpp").vppInstance
-
- nginxCont := s.getContainerByName(singleTopoContainerNginx)
- s.assertNil(nginxCont.run())
- vpp.waitForApp("nginx-", 5)
-
- if ab_or_wrk == "ab" {
- abCont := s.getContainerByName("ab")
- args := fmt.Sprintf("-n %d -c %d", nRequests, nClients)
- if mode == "rps" {
- args += " -k"
- } else if mode != "cps" {
- return fmt.Errorf("invalid mode %s; expected cps/rps", mode)
- }
- // don't exit on socket receive errors
- args += " -r"
- args += " http://" + serverAddress + ":80/64B.json"
- abCont.extraRunningArgs = args
- time.Sleep(time.Second * 10)
- o, err := abCont.combinedOutput()
- rps := parseString(o, "Requests per second:")
- s.log(rps)
- s.log(err)
- s.assertNil(err, "err: '%s', output: '%s'", err, o)
- } else {
- wrkCont := s.getContainerByName("wrk")
- args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s:80/64B.json", nClients,
- serverAddress)
- wrkCont.extraRunningArgs = args
- o, err := wrkCont.combinedOutput()
- rps := parseString(o, "requests")
- s.log(rps)
- s.log(err)
- s.assertNil(err, "err: '%s', output: '%s'", err, o)
- }
- return nil
+func validatePostInterfaceStats(s *NoTopoSuite, data string) {
+ s.AssertContains(data, "interface_stats")
+ s.AssertContains(data, s.VppIfName())
+ s.AssertNotContains(data, "error")
+ s.AssertNotContains(data, "local0")
}
-// unstable with multiple workers
-func NginxPerfCpsTest(s *NoTopoSuite) {
- s.SkipIfMultiWorker()
- s.assertNil(runNginxPerf(s, "cps", "ab"))
+func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ body := []byte(s.VppIfName())
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("POST",
+ "http://"+serverAddress+":80/interface_stats.json", bytes.NewBuffer(body))
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ validatePostInterfaceStats(s, string(data))
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
+}
+
+func HttpStaticMacTimeTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ s.Log(vpp.Vppctl("mactime enable-disable " + s.VppIfName()))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/mactime.json", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "mactime")
+ s.AssertContains(string(data), s.HostAddr())
+ s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
+ parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Date"))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5)
+ s.AssertEqual(len(resp.Header.Get("Date")), 29)
+}
+
+func HttpInvalidRequestLineTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", " GET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "\rGET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "\nGET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "HTTP-version must be present")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/x\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP/x' invalid http version not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed")
+}
+
+func HttpRequestLineTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", "\r\nGET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 200 OK")
+ s.AssertContains(resp, "<html>", "html content not found")
+}
+
+func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+
+ resp, err := TcpSendReceive(serverAddress+":80", "GET /interface|stats.json HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'|' not allowed in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /interface#stats.json HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'#' not allowed in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%1stats.json HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%Bstats.json HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json%B HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target path")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose>true HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'>' not allowed in target query")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose%true HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target query")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose=%1 HTTP/1.1\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "after '%' there must be two hex-digit characters in target query")
+}
+
+func HttpInvalidContentLengthTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length:\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: \r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: a\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request",
+ "Content-Length value other than digit not allowed")
+}
+
+func HttpContentLengthTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ ifName := s.VppIfName()
+
+ resp, err := TcpSendReceive(serverAddress+":80",
+ "POST /interface_stats.json HTTP/1.1\r\nContent-Length:4\r\n\r\n"+ifName)
+ s.AssertNil(err, fmt.Sprint(err))
+ validatePostInterfaceStats(s, resp)
+
+ resp, err = TcpSendReceive(serverAddress+":80",
+ "POST /interface_stats.json HTTP/1.1\r\n Content-Length: 4 \r\n\r\n"+ifName)
+ s.AssertNil(err, fmt.Sprint(err))
+ validatePostInterfaceStats(s, resp)
+
+ resp, err = TcpSendReceive(serverAddress+":80",
+ "POST /interface_stats.json HTTP/1.1\r\n\tContent-Length:\t\t4\r\n\r\n"+ifName)
+ s.AssertNil(err, fmt.Sprint(err))
+ validatePostInterfaceStats(s, resp)
+}
+
+func HttpMethodNotImplementedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("OPTIONS", "http://"+serverAddress+":80/show/version", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 501)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
+}
+
+func HttpVersionNotSupportedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/2\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 505 HTTP Version Not Supported")
+}
+
+func HttpUriDecodeTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/sh%6fw%20versio%6E%20verbose", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotContains(string(data), "unknown input")
+ s.AssertContains(string(data), "Compiler")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
}
-func NginxPerfRpsTest(s *NoTopoSuite) {
- s.assertNil(runNginxPerf(s, "rps", "ab"))
+func HttpHeadersTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(
+ serverAddress+":80",
+ "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\nAccept:text/xml\r\nAccept:\ttext/plain\t \r\nAccept:text/html\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 200 OK")
+ s.AssertContains(resp, "Content-Type: text/plain")
+ s.AssertNotContains(resp, "<html>", "html content received instead of plain text")
}
-func NginxPerfWrkTest(s *NoTopoSuite) {
- s.assertNil(runNginxPerf(s, "", "wrk"))
+func HttpInvalidHeadersTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nUser-Agent: test\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Header section must end with CRLF CRLF")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser@Agent:test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'@' not allowed in field name")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "incomplete field line not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\n: test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field name not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\rUser-Agent:test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\nUser-Agent:test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent: \r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed")
+}
+
+func HeaderServerTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Server", "http_cli_server")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
}
diff --git a/extras/hs-test/address_allocator.go b/extras/hs-test/infra/address_allocator.go
index e05ea76b9bb..cb647024412 100644
--- a/extras/hs-test/address_allocator.go
+++ b/extras/hs-test/infra/address_allocator.go
@@ -1,4 +1,4 @@
-package main
+package hst
import (
"errors"
@@ -84,7 +84,7 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress
return address, nil
}
-func (a *Ip4AddressAllocator) deleteIpAddresses() {
+func (a *Ip4AddressAllocator) DeleteIpAddresses() {
for ip := range a.assignedIps {
os.Remove(a.assignedIps[ip])
}
diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go
new file mode 100644
index 00000000000..8ec9b8cd02c
--- /dev/null
+++ b/extras/hs-test/infra/container.go
@@ -0,0 +1,538 @@
+package hst
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "regexp"
+ "slices"
+ "strconv"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/docker/go-units"
+
+ "github.com/cilium/cilium/pkg/sysctl"
+ containerTypes "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/image"
+ "github.com/docker/docker/pkg/stdcopy"
+ "github.com/edwarnicke/exechelper"
+ . "github.com/onsi/ginkgo/v2"
+)
+
+const (
+ logDir string = "/tmp/hs-test/"
+ volumeDir string = "/volumes"
+)
+
+var (
+ workDir, _ = os.Getwd()
+)
+
+type Volume struct {
+ HostDir string
+ ContainerDir string
+ IsDefaultWorkDir bool
+}
+
+type Container struct {
+ Suite *HstSuite
+ IsOptional bool
+ RunDetached bool
+ Name string
+ ID string
+ Image string
+ ExtraRunningArgs string
+ Volumes map[string]Volume
+ EnvVars map[string]string
+ VppInstance *VppInstance
+ AllocatedCpus []int
+ ctx context.Context
+}
+
+func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
+ containerName := yamlInput["name"].(string)
+ if len(containerName) == 0 {
+ err := fmt.Errorf("container name must not be blank")
+ return nil, err
+ }
+
+ var container = new(Container)
+ container.Volumes = make(map[string]Volume)
+ container.EnvVars = make(map[string]string)
+ container.Name = containerName
+ container.Suite = suite
+ container.ctx = context.Background()
+
+ if Image, ok := yamlInput["image"]; ok {
+ container.Image = Image.(string)
+ } else {
+ container.Image = "hs-test/vpp"
+ }
+
+ if args, ok := yamlInput["extra-args"]; ok {
+ container.ExtraRunningArgs = args.(string)
+ } else {
+ container.ExtraRunningArgs = ""
+ }
+
+ if isOptional, ok := yamlInput["is-optional"]; ok {
+ container.IsOptional = isOptional.(bool)
+ } else {
+ container.IsOptional = false
+ }
+
+ if runDetached, ok := yamlInput["run-detached"]; ok {
+ container.RunDetached = runDetached.(bool)
+ } else {
+ container.RunDetached = true
+ }
+
+ if _, ok := yamlInput["volumes"]; ok {
+ workingVolumeDir := logDir + suite.GetCurrentTestName() + volumeDir
+ workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
+ volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
+ for _, volu := range yamlInput["volumes"].([]interface{}) {
+ volumeMap := volu.(ContainerConfig)
+ hostDir := workDirReplacer.Replace(volumeMap["host-dir"].(string))
+ hostDir = volDirReplacer.Replace(hostDir)
+ containerDir := volumeMap["container-dir"].(string)
+ isDefaultWorkDir := false
+
+ if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
+ isDefaultWorkDir = isDefault.(bool)
+ }
+ container.addVolume(hostDir, containerDir, isDefaultWorkDir)
+ }
+ }
+
+ if _, ok := yamlInput["vars"]; ok {
+ for _, envVar := range yamlInput["vars"].([]interface{}) {
+ envVarMap := envVar.(ContainerConfig)
+ name := envVarMap["name"].(string)
+ value := envVarMap["value"].(string)
+ container.AddEnvVar(name, value)
+ }
+ }
+ return container, nil
+}
+
+func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
+ for _, v := range c.Volumes {
+ if v.IsDefaultWorkDir {
+ res = v
+ exists = true
+ return
+ }
+ }
+ return
+}
+
+func (c *Container) GetHostWorkDir() (res string) {
+ if v, ok := c.getWorkDirVolume(); ok {
+ res = v.HostDir
+ }
+ return
+}
+
+func (c *Container) GetContainerWorkDir() (res string) {
+ if v, ok := c.getWorkDirVolume(); ok {
+ res = v.ContainerDir
+ }
+ return
+}
+
+func (c *Container) getContainerArguments() string {
+ args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host"
+ args += c.getVolumesAsCliOption()
+ args += c.getEnvVarsAsCliOption()
+ if *VppSourceFileDir != "" {
+ args += fmt.Sprintf(" -v %s:%s", *VppSourceFileDir, *VppSourceFileDir)
+ }
+ args += " --name " + c.Name + " " + c.Image
+ args += " " + c.ExtraRunningArgs
+ return args
+}
+
+func (c *Container) PullDockerImage(name string, ctx context.Context) {
+ // "func (*Client) ImagePull" doesn't work, returns "No such image"
+ c.Suite.Log("Pulling image: " + name)
+ _, err := exechelper.CombinedOutput("docker pull " + name)
+ c.Suite.AssertNil(err)
+}
+
+// Creates a container
+func (c *Container) Create() error {
+ var sliceOfImageNames []string
+ images, err := c.Suite.Docker.ImageList(c.ctx, image.ListOptions{})
+ c.Suite.AssertNil(err)
+
+ for _, image := range images {
+ sliceOfImageNames = append(sliceOfImageNames, strings.Split(image.RepoTags[0], ":")[0])
+ }
+ if !slices.Contains(sliceOfImageNames, c.Image) {
+ c.PullDockerImage(c.Image, c.ctx)
+ }
+
+ c.allocateCpus()
+ cpuSet := fmt.Sprintf("%d-%d", c.AllocatedCpus[0], c.AllocatedCpus[len(c.AllocatedCpus)-1])
+ resp, err := c.Suite.Docker.ContainerCreate(
+ c.ctx,
+ &containerTypes.Config{
+ Hostname: c.Name,
+ Image: c.Image,
+ Env: c.getEnvVars(),
+ Cmd: strings.Split(c.ExtraRunningArgs, " "),
+ },
+ &containerTypes.HostConfig{
+ Resources: containerTypes.Resources{
+ Ulimits: []*units.Ulimit{
+ {
+ Name: "nofile",
+ Soft: 90000,
+ Hard: 90000,
+ },
+ },
+ CpusetCpus: cpuSet,
+ },
+ CapAdd: []string{"ALL"},
+ Privileged: true,
+ NetworkMode: "host",
+ Binds: c.getVolumesAsSlice(),
+ },
+ nil,
+ nil,
+ c.Name,
+ )
+ c.ID = resp.ID
+ return err
+}
+
+func (c *Container) allocateCpus() {
+ c.Suite.StartedContainers = append(c.Suite.StartedContainers, c)
+ c.AllocatedCpus = c.Suite.AllocateCpus()
+ c.Suite.Log("Allocated CPUs " + fmt.Sprint(c.AllocatedCpus) + " to container " + c.Name)
+}
+
+// Starts a container
+func (c *Container) Start() error {
+ var err error
+ var nTries int
+
+ for nTries = 0; nTries < 5; nTries++ {
+ err = c.Suite.Docker.ContainerStart(c.ctx, c.ID, containerTypes.StartOptions{})
+ if err == nil {
+ continue
+ }
+ c.Suite.Log("Error while starting " + c.Name + ". Retrying...")
+ time.Sleep(1 * time.Second)
+ }
+ if nTries >= 5 {
+ return err
+ }
+
+ // wait for container to start
+ time.Sleep(1 * time.Second)
+
+ // check if container exited right after startup
+ containers, err := c.Suite.Docker.ContainerList(c.ctx, containerTypes.ListOptions{
+ All: true,
+ Filters: filters.NewArgs(filters.Arg("name", c.Name)),
+ })
+ if err != nil {
+ return err
+ }
+ if containers[0].State == "exited" {
+ c.Suite.Log("Container details: " + fmt.Sprint(containers[0]))
+ return fmt.Errorf("Container %s exited: '%s'", c.Name, containers[0].Status)
+ }
+
+ return err
+}
+
+func (c *Container) GetOutput() (string, string) {
+ // Wait for the container to finish executing
+ statusCh, errCh := c.Suite.Docker.ContainerWait(c.ctx, c.ID, containerTypes.WaitConditionNotRunning)
+ select {
+ case err := <-errCh:
+ c.Suite.AssertNil(err)
+ case <-statusCh:
+ }
+
+ // Get the logs from the container
+ logOptions := containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true}
+ logReader, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions)
+ c.Suite.AssertNil(err)
+ defer logReader.Close()
+
+ var stdoutBuf, stderrBuf bytes.Buffer
+
+ // Use stdcopy.StdCopy to demultiplex the multiplexed stream
+ _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader)
+ c.Suite.AssertNil(err)
+
+ stdout := stdoutBuf.String()
+ stderr := stderrBuf.String()
+ return stdout, stderr
+}
+
+func (c *Container) prepareCommand() (string, error) {
+ if c.Name == "" {
+ return "", fmt.Errorf("run container failed: name is blank")
+ }
+
+ cmd := "docker exec "
+ if c.RunDetached {
+ cmd += " -d"
+ }
+
+ cmd += " " + c.getContainerArguments()
+
+ c.Suite.Log(cmd)
+ return cmd, nil
+}
+
+func (c *Container) CombinedOutput() (string, error) {
+ cmd, err := c.prepareCommand()
+ if err != nil {
+ return "", err
+ }
+
+ byteOutput, err := exechelper.CombinedOutput(cmd)
+ return string(byteOutput), err
+}
+
+// Creates and starts a container
+func (c *Container) Run() {
+ c.Suite.AssertNil(c.Create())
+ c.Suite.AssertNil(c.Start())
+}
+
+func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
+ var volume Volume
+ volume.HostDir = strings.Replace(hostDir, "volumes", c.Suite.GetTestId()+"/"+"volumes", 1)
+ volume.ContainerDir = containerDir
+ volume.IsDefaultWorkDir = isDefaultWorkDir
+ c.Volumes[hostDir] = volume
+}
+
+func (c *Container) getVolumesAsSlice() []string {
+ var volumeSlice []string
+
+ if *VppSourceFileDir != "" {
+ volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", *VppSourceFileDir, *VppSourceFileDir))
+ }
+
+ core_pattern, err := sysctl.Read("kernel.core_pattern")
+ if err == nil {
+ index := strings.LastIndex(core_pattern, "/")
+ core_pattern = core_pattern[:index]
+ volumeSlice = append(volumeSlice, c.Suite.getLogDirPath()+":"+core_pattern)
+ } else {
+ c.Suite.Log(err)
+ }
+
+ if len(c.Volumes) > 0 {
+ for _, volume := range c.Volumes {
+ volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", volume.HostDir, volume.ContainerDir))
+ }
+ }
+ return volumeSlice
+}
+
+func (c *Container) getVolumesAsCliOption() string {
+ cliOption := ""
+
+ if len(c.Volumes) > 0 {
+ for _, volume := range c.Volumes {
+ cliOption += fmt.Sprintf(" -v %s:%s", volume.HostDir, volume.ContainerDir)
+ }
+ }
+
+ return cliOption
+}
+
+func (c *Container) AddEnvVar(name string, value string) {
+ c.EnvVars[name] = value
+}
+
+func (c *Container) getEnvVarsAsCliOption() string {
+ cliOption := ""
+ if len(c.EnvVars) == 0 {
+ return cliOption
+ }
+
+ for name, value := range c.EnvVars {
+ cliOption += fmt.Sprintf(" -e %s=%s", name, value)
+ }
+ return cliOption
+}
+
+func (c *Container) getEnvVars() []string {
+ var envVars []string
+ if len(c.EnvVars) == 0 {
+ return envVars
+ }
+
+ for name, value := range c.EnvVars {
+ envVars = append(envVars, fmt.Sprintf("%s=%s", name, value))
+ }
+ return envVars
+}
+
+func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) {
+ vpp := new(VppInstance)
+ vpp.Container = c
+ vpp.Cpus = cpus
+ vpp.setDefaultCpuConfig()
+ vpp.AdditionalConfig = append(vpp.AdditionalConfig, additionalConfigs...)
+ c.VppInstance = vpp
+ return vpp, nil
+}
+
+func (c *Container) copy(sourceFileName string, targetFileName string) error {
+ cmd := exec.Command("docker", "cp", sourceFileName, c.Name+":"+targetFileName)
+ return cmd.Run()
+}
+
+func (c *Container) CreateFile(destFileName string, content string) error {
+ f, err := os.CreateTemp("/tmp", "hst-config"+c.Suite.Ppid)
+ if err != nil {
+ return err
+ }
+ defer os.Remove(f.Name())
+
+ if _, err := f.Write([]byte(content)); err != nil {
+ return err
+ }
+ if err := f.Close(); err != nil {
+ return err
+ }
+ c.copy(f.Name(), destFileName)
+ return nil
+}
+
+func (c *Container) GetFile(sourceFileName, targetFileName string) error {
+ cmd := exec.Command("docker", "cp", c.Name+":"+sourceFileName, targetFileName)
+ return cmd.Run()
+}
+
+/*
+ * Executes in detached mode so that the started application can continue to run
+ * without blocking execution of test
+ */
+func (c *Container) ExecServer(command string, arguments ...any) {
+ serverCommand := fmt.Sprintf(command, arguments...)
+ containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
+ " " + c.Name + " " + serverCommand
+ GinkgoHelper()
+ c.Suite.Log(containerExecCommand)
+ c.Suite.AssertNil(exechelper.Run(containerExecCommand))
+}
+
+func (c *Container) Exec(command string, arguments ...any) string {
+ cliCommand := fmt.Sprintf(command, arguments...)
+ containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
+ " " + c.Name + " " + cliCommand
+ GinkgoHelper()
+ c.Suite.Log(containerExecCommand)
+ byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
+ c.Suite.AssertNil(err, fmt.Sprint(err))
+ return string(byteOutput)
+}
+
+func (c *Container) saveLogs() {
+ testLogFilePath := c.Suite.getLogDirPath() + "container-" + c.Name + ".log"
+
+ logs, err := c.log(0)
+ if err != nil {
+ c.Suite.Log(err)
+ return
+ }
+
+ f, err := os.Create(testLogFilePath)
+ if err != nil {
+ c.Suite.Log(err)
+ return
+ }
+ defer f.Close()
+ fmt.Fprint(f, logs)
+}
+
+// Returns logs from docker containers. Set 'maxLines' to 0 to output the full log.
+func (c *Container) log(maxLines int) (string, error) {
+ var logOptions containerTypes.LogsOptions
+ if maxLines == 0 {
+ logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true, Timestamps: true}
+ } else {
+ logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true, Tail: strconv.Itoa(maxLines)}
+ }
+
+ out, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions)
+ if err != nil {
+ c.Suite.Log(err)
+ return "", err
+ }
+ defer out.Close()
+
+ var stdoutBuf, stderrBuf bytes.Buffer
+
+ _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out)
+ if err != nil {
+ c.Suite.Log(err)
+ }
+
+ stdout := stdoutBuf.String()
+ stderr := stderrBuf.String()
+
+ re := regexp.MustCompile("(?m)^.*==> /dev/null <==.*$[\r\n]+")
+ stdout = re.ReplaceAllString(stdout, "")
+
+ re = regexp.MustCompile("(?m)^.*tail: cannot open '' for reading: No such file or directory.*$[\r\n]+")
+ stderr = re.ReplaceAllString(stderr, "")
+
+ return stdout + stderr, err
+}
+
+func (c *Container) stop() error {
+ if c.VppInstance != nil && c.VppInstance.ApiStream != nil {
+ c.VppInstance.saveLogs()
+ c.VppInstance.Disconnect()
+ }
+ c.VppInstance = nil
+ c.saveLogs()
+
+ c.Suite.Log("Stopping container " + c.Name)
+ timeout := 0
+ if err := c.Suite.Docker.ContainerStop(c.ctx, c.ID, containerTypes.StopOptions{Timeout: &timeout}); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (c *Container) CreateConfig(targetConfigName string, templateName string, values any) {
+ template := template.Must(template.ParseFiles(templateName))
+
+ f, err := os.CreateTemp(logDir, "hst-config")
+ c.Suite.AssertNil(err, err)
+ defer os.Remove(f.Name())
+
+ err = template.Execute(f, values)
+ c.Suite.AssertNil(err, err)
+
+ err = f.Close()
+ c.Suite.AssertNil(err, err)
+
+ c.copy(f.Name(), targetConfigName)
+}
+
+func init() {
+ cmd := exec.Command("mkdir", "-p", logDir)
+ if err := cmd.Run(); err != nil {
+ panic(err)
+ }
+}
diff --git a/extras/hs-test/infra/cpu.go b/extras/hs-test/infra/cpu.go
new file mode 100644
index 00000000000..615f8a3f87d
--- /dev/null
+++ b/extras/hs-test/infra/cpu.go
@@ -0,0 +1,210 @@
+package hst
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+var CgroupPath = "/sys/fs/cgroup/"
+
+type CpuContext struct {
+ cpuAllocator *CpuAllocatorT
+ cpus []int
+}
+
+type CpuAllocatorT struct {
+ cpus []int
+ runningInCi bool
+ buildNumber int
+ maxContainerCount int
+}
+
+func iterateAndAppend(start int, end int, slice []int) []int {
+ for i := start; i <= end; i++ {
+ slice = append(slice, i)
+ }
+ return slice
+}
+
+var cpuAllocator *CpuAllocatorT = nil
+
+func (c *CpuAllocatorT) Allocate(containerCount int, nCpus int) (*CpuContext, error) {
+ var cpuCtx CpuContext
+ // indexes, not actual cores
+ var minCpu, maxCpu int
+
+ if c.runningInCi {
+ minCpu = ((c.buildNumber) * c.maxContainerCount * nCpus)
+ maxCpu = ((c.buildNumber + 1) * c.maxContainerCount * nCpus) - 1
+ } else {
+ minCpu = ((GinkgoParallelProcess() - 1) * c.maxContainerCount * nCpus)
+ maxCpu = (GinkgoParallelProcess() * c.maxContainerCount * nCpus) - 1
+ }
+
+ if len(c.cpus)-1 < maxCpu {
+ err := fmt.Errorf("could not allocate %d CPUs; available count: %d; attempted to allocate cores with index %d-%d; max index: %d;\n"+
+ "available cores: %v", nCpus*containerCount, len(c.cpus), minCpu, maxCpu, len(c.cpus)-1, c.cpus)
+ return nil, err
+ }
+
+ if containerCount == 1 {
+ cpuCtx.cpus = c.cpus[minCpu : minCpu+nCpus]
+ } else if containerCount > 1 && containerCount <= c.maxContainerCount {
+ cpuCtx.cpus = c.cpus[minCpu+(nCpus*(containerCount-1)) : minCpu+(nCpus*containerCount)]
+ } else {
+ return nil, fmt.Errorf("too many containers; CPU allocation for >%d containers is not implemented", c.maxContainerCount)
+ }
+ cpuCtx.cpuAllocator = c
+ return &cpuCtx, nil
+}
+
+func (c *CpuAllocatorT) readCpus() error {
+ var first, second, third, fourth int
+ var file *os.File
+ var err error
+
+ if c.runningInCi {
+ // non-debug build runs on node0, debug on node1
+ if *IsDebugBuild {
+ file, err = os.Open("/sys/devices/system/node/node1/cpulist")
+ } else {
+ file, err = os.Open("/sys/devices/system/node/node0/cpulist")
+ }
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d,%d-%d", &first, &second, &third, &fourth)
+ if err != nil {
+ return err
+ }
+
+ c.cpus = iterateAndAppend(first, second, c.cpus)
+ c.cpus = iterateAndAppend(third, fourth, c.cpus)
+ } else if NumaAwareCpuAlloc {
+ var fifth, sixth int
+ var tmpCpus []int
+
+ file, err := os.Open("/sys/devices/system/node/online")
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ // get numa node range
+ _, err = fmt.Sscanf(line, "%d-%d", &first, &second)
+ if err != nil {
+ return err
+ }
+
+ for i := first; i <= second; i++ {
+ file, err := os.Open("/sys/devices/system/node/node" + fmt.Sprint(i) + "/cpulist")
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ // get numa node cores
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d,%d-%d", &third, &fourth, &fifth, &sixth)
+ if err != nil {
+ return err
+ }
+
+ // get numa node cores from first range
+ tmpCpus = iterateAndAppend(third, fourth, tmpCpus)
+
+ // discard cpu 0
+ if tmpCpus[0] == 0 && !*UseCpu0 {
+ tmpCpus = tmpCpus[1:]
+ }
+
+ // get numa node cores from second range
+ tmpCpus = iterateAndAppend(fifth, sixth, tmpCpus)
+
+ // make c.cpus divisible by maxContainerCount * nCpus, so we don't have to check which numa will be used
+ // and we can use offsets
+ count_to_remove := len(tmpCpus) % (c.maxContainerCount * *NConfiguredCpus)
+ c.cpus = append(c.cpus, tmpCpus[:len(tmpCpus)-count_to_remove]...)
+ tmpCpus = tmpCpus[:0]
+ }
+ } else {
+ // Path depends on cgroup version. We need to check which version is in use.
+ // For that following command can be used: 'stat -fc %T /sys/fs/cgroup/'
+ // In case the output states 'cgroup2fs' then cgroups v2 is used, 'tmpfs' in case cgroups v1.
+ cmd := exec.Command("stat", "-fc", "%T", "/sys/fs/cgroup/")
+ byteOutput, err := cmd.CombinedOutput()
+ if err != nil {
+ return err
+ }
+
+ CpuPath := CgroupPath
+ if strings.Contains(string(byteOutput), "tmpfs") {
+ CpuPath += "cpuset/cpuset.effective_cpus"
+ } else if strings.Contains(string(byteOutput), "cgroup2fs") {
+ CpuPath += "cpuset.cpus.effective"
+ } else {
+ return errors.New("cgroup unknown fs: " + string(byteOutput))
+ }
+
+ file, err := os.Open(CpuPath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d", &first, &second)
+ if err != nil {
+ return err
+ }
+ c.cpus = iterateAndAppend(first, second, c.cpus)
+ }
+
+ // discard cpu 0
+ if c.cpus[0] == 0 && !*UseCpu0 {
+ c.cpus = c.cpus[1:]
+ }
+ return nil
+}
+
+func CpuAllocator() (*CpuAllocatorT, error) {
+ if cpuAllocator == nil {
+ var err error
+ cpuAllocator = new(CpuAllocatorT)
+ cpuAllocator.maxContainerCount = 4
+ buildNumberStr := os.Getenv("BUILD_NUMBER")
+
+ if buildNumberStr != "" {
+ cpuAllocator.runningInCi = true
+ // get last digit of build number
+ cpuAllocator.buildNumber, err = strconv.Atoi(buildNumberStr[len(buildNumberStr)-1:])
+ if err != nil {
+ return nil, err
+ }
+ }
+ err = cpuAllocator.readCpus()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return cpuAllocator, nil
+}
diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go
new file mode 100644
index 00000000000..234a8409ea0
--- /dev/null
+++ b/extras/hs-test/infra/hst_suite.go
@@ -0,0 +1,643 @@
+package hst
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/edwarnicke/exechelper"
+
+ containerTypes "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/client"
+ "github.com/onsi/gomega/gmeasure"
+ "gopkg.in/yaml.v3"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+const (
+ DEFAULT_NETWORK_NUM int = 1
+)
+
+var IsPersistent = flag.Bool("persist", false, "persists topology config")
+var IsVerbose = flag.Bool("verbose", false, "verbose test output")
+var IsUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
+var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
+var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
+var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
+var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build")
+var UseCpu0 = flag.Bool("cpu0", false, "use cpu0")
+var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests")
+var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
+var NumaAwareCpuAlloc bool
+var SuiteTimeout time.Duration
+
+type HstSuite struct {
+ Containers map[string]*Container
+ StartedContainers []*Container
+ Volumes []string
+ NetConfigs []NetConfig
+ NetInterfaces map[string]*NetInterface
+ Ip4AddrAllocator *Ip4AddressAllocator
+ TestIds map[string]string
+ CpuAllocator *CpuAllocatorT
+ CpuContexts []*CpuContext
+ CpuCount int
+ Ppid string
+ ProcessIndex string
+ Logger *log.Logger
+ LogFile *os.File
+ Docker *client.Client
+}
+
+// used for colorful ReportEntry
+type StringerStruct struct {
+ Label string
+}
+
+// ColorableString for ReportEntry to use
+func (s StringerStruct) ColorableString() string {
+ return fmt.Sprintf("{{red}}%s{{/}}", s.Label)
+}
+
+// non-colorable String() is used by go's string formatting support but ignored by ReportEntry
+func (s StringerStruct) String() string {
+ return s.Label
+}
+
+func getTestFilename() string {
+ _, filename, _, _ := runtime.Caller(2)
+ return filepath.Base(filename)
+}
+
+func (s *HstSuite) getLogDirPath() string {
+ testId := s.GetTestId()
+ testName := s.GetCurrentTestName()
+ logDirPath := logDir + testName + "/" + testId + "/"
+
+ cmd := exec.Command("mkdir", "-p", logDirPath)
+ if err := cmd.Run(); err != nil {
+ Fail("mkdir error: " + fmt.Sprint(err))
+ }
+
+ return logDirPath
+}
+
+func (s *HstSuite) newDockerClient() {
+ var err error
+ s.Docker, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+ s.AssertNil(err)
+ s.Log("docker client created")
+}
+
+func (s *HstSuite) SetupSuite() {
+ s.CreateLogger()
+ s.newDockerClient()
+ s.Log("Suite Setup")
+ RegisterFailHandler(func(message string, callerSkip ...int) {
+ s.HstFail()
+ Fail(message, callerSkip...)
+ })
+ var err error
+ s.Ppid = fmt.Sprint(os.Getppid())
+ // remove last number so we have space to prepend a process index (interfaces have a char limit)
+ s.Ppid = s.Ppid[:len(s.Ppid)-1]
+ s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
+ s.CpuAllocator, err = CpuAllocator()
+ if err != nil {
+ Fail("failed to init cpu allocator: " + fmt.Sprint(err))
+ }
+ s.CpuCount = *NConfiguredCpus
+}
+
+func (s *HstSuite) AllocateCpus() []int {
+ cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount)
+ // using Fail instead of AssertNil to make error message more readable
+ if err != nil {
+ Fail(fmt.Sprint(err))
+ }
+ s.AddCpuContext(cpuCtx)
+ return cpuCtx.cpus
+}
+
+func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
+ s.CpuContexts = append(s.CpuContexts, cpuCtx)
+}
+
+func (s *HstSuite) TearDownSuite() {
+ defer s.LogFile.Close()
+ defer s.Docker.Close()
+ s.Log("Suite Teardown")
+ s.UnconfigureNetworkTopology()
+}
+
+func (s *HstSuite) TearDownTest() {
+ s.Log("Test Teardown")
+ if *IsPersistent {
+ return
+ }
+ s.WaitForCoreDump()
+ s.ResetContainers()
+
+ if s.Ip4AddrAllocator != nil {
+ s.Ip4AddrAllocator.DeleteIpAddresses()
+ }
+}
+
+func (s *HstSuite) SkipIfUnconfiguring() {
+ if *IsUnconfiguring {
+ s.Skip("skipping to unconfigure")
+ }
+}
+
+func (s *HstSuite) SetupTest() {
+ s.Log("Test Setup")
+ s.StartedContainers = s.StartedContainers[:0]
+ s.SkipIfUnconfiguring()
+ s.SetupContainers()
+}
+
+func (s *HstSuite) SetupContainers() {
+ for _, container := range s.Containers {
+ if !container.IsOptional {
+ container.Run()
+ }
+ }
+}
+
+func (s *HstSuite) LogVppInstance(container *Container, maxLines int) {
+ if container.VppInstance == nil {
+ return
+ }
+
+ logSource := container.GetHostWorkDir() + defaultLogFilePath
+ file, err := os.Open(logSource)
+
+ if err != nil {
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ var lines []string
+ var counter int
+
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ counter++
+ if counter > maxLines {
+ lines = lines[1:]
+ counter--
+ }
+ }
+
+ s.Log("vvvvvvvvvvvvvvv " + container.Name + " [VPP instance]:")
+ for _, line := range lines {
+ s.Log(line)
+ }
+ s.Log("^^^^^^^^^^^^^^^\n\n")
+}
+
+func (s *HstSuite) HstFail() {
+ for _, container := range s.StartedContainers {
+ out, err := container.log(20)
+ if err != nil {
+ s.Log("An error occured while obtaining '" + container.Name + "' container logs: " + fmt.Sprint(err))
+ s.Log("The container might not be running - check logs in " + s.getLogDirPath())
+ continue
+ }
+ s.Log("\nvvvvvvvvvvvvvvv " +
+ container.Name + ":\n" +
+ out +
+ "^^^^^^^^^^^^^^^\n\n")
+ s.LogVppInstance(container, 20)
+ }
+}
+
+func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
+}
+
+func (s *HstSuite) CreateLogger() {
+ suiteName := s.GetCurrentSuiteName()
+ var err error
+ s.LogFile, err = os.Create("summary/" + suiteName + ".log")
+ if err != nil {
+ Fail("Unable to create log file.")
+ }
+ s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
+}
+
+// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
+// to keep console tidy
+func (s *HstSuite) Log(arg any) {
+ logs := strings.Split(fmt.Sprint(arg), "\n")
+ for _, line := range logs {
+ s.Logger.Println(line)
+ }
+ if *IsVerbose {
+ GinkgoWriter.Println(arg)
+ }
+}
+
+func (s *HstSuite) Skip(args string) {
+ Skip(args)
+}
+
+func (s *HstSuite) SkipIfMultiWorker(args ...any) {
+ if *NConfiguredCpus > 1 {
+ s.Skip("test case not supported with multiple vpp workers")
+ }
+}
+
+func (s *HstSuite) SkipIfNotEnoughAvailableCpus() bool {
+ var MaxRequestedCpu int
+
+ if s.CpuAllocator.runningInCi {
+ MaxRequestedCpu = ((s.CpuAllocator.buildNumber + 1) * s.CpuAllocator.maxContainerCount * s.CpuCount)
+ } else {
+ MaxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount)
+ }
+
+ if len(s.CpuAllocator.cpus)-1 < MaxRequestedCpu {
+ s.Skip(fmt.Sprintf("test case cannot allocate requested cpus (%d cpus * %d containers)", s.CpuCount, s.CpuAllocator.maxContainerCount))
+ }
+
+ return true
+}
+
+func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
+ imageName := "hs-test/nginx-http3"
+
+ cmd := exec.Command("docker", "images", imageName)
+ byteOutput, err := cmd.CombinedOutput()
+ if err != nil {
+ s.Log("error while searching for docker image")
+ return
+ }
+ if !strings.Contains(string(byteOutput), imageName) {
+ s.Skip("extended tests not built")
+ }
+}
+
+func (s *HstSuite) SkipUnlessLeakCheck() {
+ if !*IsLeakCheck {
+ s.Skip("leak-check tests excluded")
+ }
+}
+
+func (s *HstSuite) WaitForCoreDump() {
+ var filename string
+ dir, err := os.Open(s.getLogDirPath())
+ if err != nil {
+ s.Log(err)
+ return
+ }
+ defer dir.Close()
+
+ files, err := dir.Readdirnames(0)
+ if err != nil {
+ s.Log(err)
+ return
+ }
+ for _, file := range files {
+ if strings.Contains(file, "core") {
+ filename = file
+ }
+ }
+ timeout := 60
+ waitTime := 5
+
+ if filename != "" {
+ corePath := s.getLogDirPath() + filename
+ s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", corePath))
+ for i := waitTime; i <= timeout; i += waitTime {
+ fileInfo, err := os.Stat(corePath)
+ if err != nil {
+ s.Log("Error while reading file info: " + fmt.Sprint(err))
+ return
+ }
+ currSize := fileInfo.Size()
+ s.Log(fmt.Sprintf("Waiting %ds/%ds...", i, timeout))
+ time.Sleep(time.Duration(waitTime) * time.Second)
+ fileInfo, _ = os.Stat(corePath)
+
+ if currSize == fileInfo.Size() {
+ debug := ""
+ if *IsDebugBuild {
+ debug = "_debug"
+ }
+ vppBinPath := fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug)
+ pluginsLibPath := fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/x86_64-linux-gnu/vpp_plugins", debug)
+ cmd := fmt.Sprintf("sudo gdb %s -c %s -ex 'set solib-search-path %s/%s' -ex 'bt full' -batch", vppBinPath, corePath, *VppSourceFileDir, pluginsLibPath)
+ s.Log(cmd)
+ output, _ := exechelper.Output(cmd)
+ AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)})
+ os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644))
+ if s.CpuAllocator.runningInCi {
+ err = os.Remove(corePath)
+ if err == nil {
+ s.Log("removed " + corePath)
+ } else {
+ s.Log(err)
+ }
+ }
+ return
+ }
+ }
+ }
+}
+
+func (s *HstSuite) ResetContainers() {
+ for _, container := range s.StartedContainers {
+ container.stop()
+ s.Log("Removing container " + container.Name)
+ if err := s.Docker.ContainerRemove(container.ctx, container.ID, containerTypes.RemoveOptions{RemoveVolumes: true}); err != nil {
+ s.Log(err)
+ }
+ }
+}
+
+func (s *HstSuite) GetNetNamespaceByName(name string) string {
+ return s.ProcessIndex + name + s.Ppid
+}
+
+func (s *HstSuite) GetInterfaceByName(name string) *NetInterface {
+ return s.NetInterfaces[s.ProcessIndex+name+s.Ppid]
+}
+
+func (s *HstSuite) GetContainerByName(name string) *Container {
+ return s.Containers[s.ProcessIndex+name+s.Ppid]
+}
+
+/*
+ * Create a copy and return its address, so that individial tests which call this
+ * are not able to modify the original container and affect other tests by doing that
+ */
+func (s *HstSuite) GetTransientContainerByName(name string) *Container {
+ containerCopy := *s.Containers[s.ProcessIndex+name+s.Ppid]
+ return &containerCopy
+}
+
+func (s *HstSuite) LoadContainerTopology(topologyName string) {
+ data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
+ if err != nil {
+ Fail("read error: " + fmt.Sprint(err))
+ }
+ var yamlTopo YamlTopology
+ err = yaml.Unmarshal(data, &yamlTopo)
+ if err != nil {
+ Fail("unmarshal error: " + fmt.Sprint(err))
+ }
+
+ for _, elem := range yamlTopo.Volumes {
+ volumeMap := elem["volume"].(VolumeConfig)
+ hostDir := volumeMap["host-dir"].(string)
+ workingVolumeDir := logDir + s.GetCurrentTestName() + volumeDir
+ volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
+ hostDir = volDirReplacer.Replace(hostDir)
+ s.Volumes = append(s.Volumes, hostDir)
+ }
+
+ s.Containers = make(map[string]*Container)
+ for _, elem := range yamlTopo.Containers {
+ newContainer, err := newContainer(s, elem)
+ newContainer.Suite = s
+ newContainer.Name = newContainer.Suite.ProcessIndex + newContainer.Name + newContainer.Suite.Ppid
+ if err != nil {
+ Fail("container config error: " + fmt.Sprint(err))
+ }
+ s.Containers[newContainer.Name] = newContainer
+ }
+}
+
+func (s *HstSuite) LoadNetworkTopology(topologyName string) {
+ data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
+ if err != nil {
+ Fail("read error: " + fmt.Sprint(err))
+ }
+ var yamlTopo YamlTopology
+ err = yaml.Unmarshal(data, &yamlTopo)
+ if err != nil {
+ Fail("unmarshal error: " + fmt.Sprint(err))
+ }
+
+ s.Ip4AddrAllocator = NewIp4AddressAllocator()
+ s.NetInterfaces = make(map[string]*NetInterface)
+
+ for _, elem := range yamlTopo.Devices {
+ if _, ok := elem["name"]; ok {
+ elem["name"] = s.ProcessIndex + elem["name"].(string) + s.Ppid
+ }
+
+ if peer, ok := elem["peer"].(NetDevConfig); ok {
+ if peer["name"].(string) != "" {
+ peer["name"] = s.ProcessIndex + peer["name"].(string) + s.Ppid
+ }
+ if _, ok := peer["netns"]; ok {
+ peer["netns"] = s.ProcessIndex + peer["netns"].(string) + s.Ppid
+ }
+ }
+
+ if _, ok := elem["netns"]; ok {
+ elem["netns"] = s.ProcessIndex + elem["netns"].(string) + s.Ppid
+ }
+
+ if _, ok := elem["interfaces"]; ok {
+ interfaceCount := len(elem["interfaces"].([]interface{}))
+ for i := 0; i < interfaceCount; i++ {
+ elem["interfaces"].([]interface{})[i] = s.ProcessIndex + elem["interfaces"].([]interface{})[i].(string) + s.Ppid
+ }
+ }
+
+ switch elem["type"].(string) {
+ case NetNs:
+ {
+ if namespace, err := newNetNamespace(elem); err == nil {
+ s.NetConfigs = append(s.NetConfigs, &namespace)
+ } else {
+ Fail("network config error: " + fmt.Sprint(err))
+ }
+ }
+ case Veth, Tap:
+ {
+ if netIf, err := newNetworkInterface(elem, s.Ip4AddrAllocator); err == nil {
+ s.NetConfigs = append(s.NetConfigs, netIf)
+ s.NetInterfaces[netIf.Name()] = netIf
+ } else {
+ Fail("network config error: " + fmt.Sprint(err))
+ }
+ }
+ case Bridge:
+ {
+ if bridge, err := newBridge(elem); err == nil {
+ s.NetConfigs = append(s.NetConfigs, &bridge)
+ } else {
+ Fail("network config error: " + fmt.Sprint(err))
+ }
+ }
+ }
+ }
+}
+
+func (s *HstSuite) ConfigureNetworkTopology(topologyName string) {
+ s.LoadNetworkTopology(topologyName)
+
+ if *IsUnconfiguring {
+ return
+ }
+
+ for _, nc := range s.NetConfigs {
+ s.Log(nc.Name())
+ if err := nc.configure(); err != nil {
+ Fail("Network config error: " + fmt.Sprint(err))
+ }
+ }
+}
+
+func (s *HstSuite) UnconfigureNetworkTopology() {
+ if *IsPersistent {
+ return
+ }
+ for _, nc := range s.NetConfigs {
+ nc.unconfigure()
+ }
+}
+
+func (s *HstSuite) GetTestId() string {
+ testName := s.GetCurrentTestName()
+
+ if s.TestIds == nil {
+ s.TestIds = map[string]string{}
+ }
+
+ if _, ok := s.TestIds[testName]; !ok {
+ s.TestIds[testName] = time.Now().Format("2006-01-02_15-04-05")
+ }
+
+ return s.TestIds[testName]
+}
+
+func (s *HstSuite) GetCurrentTestName() string {
+ return strings.Split(CurrentSpecReport().LeafNodeText, "/")[1]
+}
+
+func (s *HstSuite) GetCurrentSuiteName() string {
+ return CurrentSpecReport().ContainerHierarchyTexts[0]
+}
+
+// Returns last 3 digits of PID + Ginkgo process index as the 4th digit
+func (s *HstSuite) GetPortFromPpid() string {
+ port := s.Ppid
+ for len(port) < 3 {
+ port += "0"
+ }
+ return port[len(port)-3:] + s.ProcessIndex
+}
+
+/*
+RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
+passing in suite context, experiment and your data.
+
+You can also instruct runBenchmark to run with multiple concurrent workers.
+Note that if running in parallel Gomega returns from Sample when spins up all samples and does not wait until all finished.
+You can record multiple named measurements (float64 or duration) within passed-in callback.
+runBenchmark then produces report to show statistical distribution of measurements.
+*/
+func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callback func(s *HstSuite, e *gmeasure.Experiment, data interface{}), data interface{}) {
+ experiment := gmeasure.NewExperiment(name)
+
+ experiment.Sample(func(idx int) {
+ defer GinkgoRecover()
+ callback(s, experiment, data)
+ }, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
+ AddReportEntry(experiment.Name, experiment)
+}
+
+/*
+LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
+
+You should put it at the first place, so request is logged always.
+*/
+func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ dump, err := httputil.DumpRequest(req, body)
+ if err == nil {
+ s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
+ string(dump) +
+ "\n------------------------------\n")
+ }
+ }
+}
diff --git a/extras/hs-test/netconfig.go b/extras/hs-test/infra/netconfig.go
index c76a0fda5f5..3f3d3e3e84c 100644
--- a/extras/hs-test/netconfig.go
+++ b/extras/hs-test/infra/netconfig.go
@@ -1,4 +1,4 @@
-package main
+package hst
import (
"errors"
@@ -32,13 +32,13 @@ type (
NetInterface struct {
NetConfigBase
- ip4AddrAllocator *Ip4AddressAllocator
- ip4Address string
- index InterfaceIndex
- hwAddress MacAddress
- networkNamespace string
- networkNumber int
- peer *NetInterface
+ Ip4AddrAllocator *Ip4AddressAllocator
+ Ip4Address string
+ Index InterfaceIndex
+ HwAddress MacAddress
+ NetworkNamespace string
+ NetworkNumber int
+ Peer *NetInterface
}
NetworkNamespace struct {
@@ -47,8 +47,8 @@ type (
NetworkBridge struct {
NetConfigBase
- networkNamespace string
- interfaces []string
+ NetworkNamespace string
+ Interfaces []string
}
)
@@ -64,7 +64,7 @@ type InterfaceAdder func(n *NetInterface) *Cmd
var (
ipCommandMap = map[string]InterfaceAdder{
Veth: func(n *NetInterface) *Cmd {
- return exec.Command("ip", "link", "add", n.name, "type", "veth", "peer", "name", n.peer.name)
+ return exec.Command("ip", "link", "add", n.name, "type", "veth", "peer", "name", n.Peer.name)
},
Tap: func(n *NetInterface) *Cmd {
return exec.Command("ip", "tuntap", "add", n.name, "mode", "tap")
@@ -75,31 +75,31 @@ var (
func newNetworkInterface(cfg NetDevConfig, a *Ip4AddressAllocator) (*NetInterface, error) {
var newInterface *NetInterface = &NetInterface{}
var err error
- newInterface.ip4AddrAllocator = a
+ newInterface.Ip4AddrAllocator = a
newInterface.name = cfg["name"].(string)
- newInterface.networkNumber = DEFAULT_NETWORK_NUM
+ newInterface.NetworkNumber = DEFAULT_NETWORK_NUM
if interfaceType, ok := cfg["type"]; ok {
newInterface.category = interfaceType.(string)
}
if presetHwAddress, ok := cfg["preset-hw-address"]; ok {
- newInterface.hwAddress, err = ethernet_types.ParseMacAddress(presetHwAddress.(string))
+ newInterface.HwAddress, err = ethernet_types.ParseMacAddress(presetHwAddress.(string))
if err != nil {
return &NetInterface{}, err
}
}
if netns, ok := cfg["netns"]; ok {
- newInterface.networkNamespace = netns.(string)
+ newInterface.NetworkNamespace = netns.(string)
}
if ip, ok := cfg["ip4"]; ok {
if n, ok := ip.(NetDevConfig)["network"]; ok {
- newInterface.networkNumber = n.(int)
+ newInterface.NetworkNumber = n.(int)
}
- newInterface.ip4Address, err = newInterface.ip4AddrAllocator.NewIp4InterfaceAddress(
- newInterface.networkNumber,
+ newInterface.Ip4Address, err = newInterface.Ip4AddrAllocator.NewIp4InterfaceAddress(
+ newInterface.NetworkNumber,
)
if err != nil {
return &NetInterface{}, err
@@ -112,7 +112,7 @@ func newNetworkInterface(cfg NetDevConfig, a *Ip4AddressAllocator) (*NetInterfac
peer := cfg["peer"].(NetDevConfig)
- if newInterface.peer, err = newNetworkInterface(peer, a); err != nil {
+ if newInterface.Peer, err = newNetworkInterface(peer, a); err != nil {
return &NetInterface{}, err
}
@@ -128,8 +128,8 @@ func (n *NetInterface) configureUpState() error {
}
func (n *NetInterface) configureNetworkNamespace() error {
- if n.networkNamespace != "" {
- err := linkSetNetns(n.name, n.networkNamespace)
+ if n.NetworkNamespace != "" {
+ err := linkSetNetns(n.name, n.NetworkNamespace)
if err != nil {
return err
}
@@ -138,11 +138,11 @@ func (n *NetInterface) configureNetworkNamespace() error {
}
func (n *NetInterface) configureAddress() error {
- if n.ip4Address != "" {
+ if n.Ip4Address != "" {
if err := addAddress(
n.Name(),
- n.ip4Address,
- n.networkNamespace,
+ n.Ip4Address,
+ n.NetworkNamespace,
); err != nil {
return err
}
@@ -170,16 +170,16 @@ func (n *NetInterface) configure() error {
return err
}
- if n.peer != nil && n.peer.name != "" {
- if err := n.peer.configureUpState(); err != nil {
+ if n.Peer != nil && n.Peer.name != "" {
+ if err := n.Peer.configureUpState(); err != nil {
return err
}
- if err := n.peer.configureNetworkNamespace(); err != nil {
+ if err := n.Peer.configureNetworkNamespace(); err != nil {
return err
}
- if err := n.peer.configureAddress(); err != nil {
+ if err := n.Peer.configureAddress(); err != nil {
return err
}
}
@@ -199,19 +199,19 @@ func (n *NetInterface) Type() string {
return n.category
}
-func (n *NetInterface) addressWithPrefix() AddressWithPrefix {
- address, _ := ip_types.ParseAddressWithPrefix(n.ip4Address)
+func (n *NetInterface) AddressWithPrefix() AddressWithPrefix {
+ address, _ := ip_types.ParseAddressWithPrefix(n.Ip4Address)
return address
}
-func (n *NetInterface) ip4AddressWithPrefix() IP4AddressWithPrefix {
- ip4Prefix, _ := ip_types.ParseIP4Prefix(n.ip4Address)
- ip4AddressWithPrefix := ip_types.IP4AddressWithPrefix(ip4Prefix)
- return ip4AddressWithPrefix
+func (n *NetInterface) Ip4AddressWithPrefix() IP4AddressWithPrefix {
+ ip4Prefix, _ := ip_types.ParseIP4Prefix(n.Ip4Address)
+ Ip4AddressWithPrefix := ip_types.IP4AddressWithPrefix(ip4Prefix)
+ return Ip4AddressWithPrefix
}
-func (n *NetInterface) ip4AddressString() string {
- return strings.Split(n.ip4Address, "/")[0]
+func (n *NetInterface) Ip4AddressString() string {
+ return strings.Split(n.Ip4Address, "/")[0]
}
func (b *NetConfigBase) Name() string {
@@ -242,22 +242,22 @@ func newBridge(cfg NetDevConfig) (NetworkBridge, error) {
bridge.name = cfg["name"].(string)
bridge.category = Bridge
for _, v := range cfg["interfaces"].([]interface{}) {
- bridge.interfaces = append(bridge.interfaces, v.(string))
+ bridge.Interfaces = append(bridge.Interfaces, v.(string))
}
- bridge.networkNamespace = ""
+ bridge.NetworkNamespace = ""
if netns, ok := cfg["netns"]; ok {
- bridge.networkNamespace = netns.(string)
+ bridge.NetworkNamespace = netns.(string)
}
return bridge, nil
}
func (b *NetworkBridge) configure() error {
- return addBridge(b.name, b.interfaces, b.networkNamespace)
+ return addBridge(b.name, b.Interfaces, b.NetworkNamespace)
}
func (b *NetworkBridge) unconfigure() {
- delBridge(b.name, b.networkNamespace)
+ delBridge(b.name, b.NetworkNamespace)
}
func delBridge(brName, ns string) error {
diff --git a/extras/hs-test/infra/suite_cpu_pinning.go b/extras/hs-test/infra/suite_cpu_pinning.go
new file mode 100644
index 00000000000..355fdf96604
--- /dev/null
+++ b/extras/hs-test/infra/suite_cpu_pinning.go
@@ -0,0 +1,113 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+var cpuPinningTests = map[string][]func(s *CpuPinningSuite){}
+var cpuPinningSoloTests = map[string][]func(s *CpuPinningSuite){}
+
+type CpuPinningSuite struct {
+ HstSuite
+ previousMaxContainerCount int
+}
+
+func RegisterCpuPinningTests(tests ...func(s *CpuPinningSuite)) {
+ cpuPinningTests[getTestFilename()] = tests
+}
+
+func RegisterCpuPinningSoloTests(tests ...func(s *CpuPinningSuite)) {
+ cpuPinningSoloTests[getTestFilename()] = tests
+}
+
+func (s *CpuPinningSuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("tap")
+ s.LoadContainerTopology("singleCpuPinning")
+}
+
+func (s *CpuPinningSuite) SetupTest() {
+ // Skip if we cannot allocate 3 CPUs for test container
+ s.previousMaxContainerCount = s.CpuAllocator.maxContainerCount
+ s.CpuCount = 3
+ s.CpuAllocator.maxContainerCount = 1
+ s.SkipIfNotEnoughAvailableCpus()
+
+ s.HstSuite.SetupTest()
+ container := s.GetContainerByName(SingleTopoContainerVpp)
+ vpp, err := container.newVppInstance(container.AllocatedCpus)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+}
+
+func (s *CpuPinningSuite) TearDownTest() {
+ // reset vars
+ s.CpuCount = *NConfiguredCpus
+ s.CpuAllocator.maxContainerCount = s.previousMaxContainerCount
+ s.HstSuite.TearDownTest()
+
+}
+
+var _ = Describe("CpuPinningSuite", Ordered, ContinueOnFailure, func() {
+ var s CpuPinningSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range cpuPinningTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("CpuPinningSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s CpuPinningSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range cpuPinningSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go
new file mode 100644
index 00000000000..e34a7d74225
--- /dev/null
+++ b/extras/hs-test/infra/suite_envoy_proxy.go
@@ -0,0 +1,213 @@
+// Suite for Envoy proxy testing
+//
+// The topology consists of 4 containers: curl (client), VPP (session layer), Envoy (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+const (
+ VppContainerName = "vpp"
+ EnvoyProxyContainerName = "envoy-vcl"
+)
+
+type EnvoyProxySuite struct {
+ HstSuite
+ nginxPort uint16
+ proxyPort uint16
+ maxTimeout int
+}
+
+var envoyProxyTests = map[string][]func(s *EnvoyProxySuite){}
+var envoyProxySoloTests = map[string][]func(s *EnvoyProxySuite){}
+
+func RegisterEnvoyProxyTests(tests ...func(s *EnvoyProxySuite)) {
+ envoyProxyTests[getTestFilename()] = tests
+}
+
+func RegisterEnvoyProxySoloTests(tests ...func(s *EnvoyProxySuite)) {
+ envoyProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *EnvoyProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("envoyProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *EnvoyProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api").
+ Append("evt_qs_memfd_seg").
+ Append("event-queue-length 100000")
+
+ vppContainer := s.GetContainerByName(VppContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+ vppContainer.Exec("chmod 777 -R %s", vppContainer.GetContainerWorkDir())
+
+ // nginx HTTP server
+ nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxContainer.Create())
+ s.nginxPort = 80
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Port uint16
+ Timeout int
+ }{
+ LogPrefix: nginxContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Port: s.nginxPort,
+ Timeout: s.maxTimeout,
+ }
+ nginxContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxContainer.Start())
+
+ // Envoy
+ envoyContainer := s.GetContainerByName(EnvoyProxyContainerName)
+ s.AssertNil(envoyContainer.Create())
+ s.proxyPort = 8080
+ envoySettings := struct {
+ LogPrefix string
+ ServerAddress string
+ ServerPort uint16
+ ProxyPort uint16
+ }{
+ LogPrefix: envoyContainer.Name,
+ ServerAddress: serverInterface.Ip4AddressString(),
+ ServerPort: s.nginxPort,
+ ProxyPort: s.proxyPort,
+ }
+ envoyContainer.CreateConfig(
+ "/etc/envoy/envoy.yaml",
+ "resources/envoy/proxy.yaml",
+ envoySettings,
+ )
+ s.AssertNil(envoyContainer.Start())
+
+ // Add Ipv4 ARP entry for nginx HTTP server, otherwise first request fail (HTTP error 503)
+ arp := fmt.Sprintf("set ip neighbor %s %s %s",
+ serverInterface.Peer.Name(),
+ serverInterface.Ip4AddressString(),
+ serverInterface.HwAddress)
+ vppContainer.VppInstance.Vppctl(arp)
+}
+
+func (s *EnvoyProxySuite) TearDownTest() {
+ if CurrentSpecReport().Failed() {
+ s.CollectNginxLogs(NginxServerContainerName)
+ s.CollectEnvoyLogs(EnvoyProxyContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *EnvoyProxySuite) ProxyPort() uint16 {
+ return s.proxyPort
+}
+
+func (s *EnvoyProxySuite) ProxyAddr() string {
+ return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *EnvoyProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *EnvoyProxySuite) CurlUploadResource(uri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload --max-time %d --insecure --noproxy '*' -T %s %s", s.maxTimeout, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("EnvoyProxySuite", Ordered, ContinueOnFailure, func() {
+ var s EnvoyProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range envoyProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("EnvoyProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+ var s EnvoyProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range envoyProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_iperf_linux.go b/extras/hs-test/infra/suite_iperf_linux.go
new file mode 100644
index 00000000000..728429b505f
--- /dev/null
+++ b/extras/hs-test/infra/suite_iperf_linux.go
@@ -0,0 +1,96 @@
+package hst
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+type IperfSuite struct {
+ HstSuite
+}
+
+const (
+ ServerIperfContainerName string = "server"
+ ServerIperfInterfaceName string = "hstsrv"
+ ClientIperfContainerName string = "client"
+ ClientIperfInterfaceName string = "hstcln"
+)
+
+var iperfTests = map[string][]func(s *IperfSuite){}
+var iperfSoloTests = map[string][]func(s *IperfSuite){}
+
+func RegisterIperfTests(tests ...func(s *IperfSuite)) {
+ iperfTests[getTestFilename()] = tests
+}
+func RegisterIperfSoloTests(tests ...func(s *IperfSuite)) {
+ iperfSoloTests[getTestFilename()] = tests
+}
+
+func (s *IperfSuite) SetupSuite() {
+ time.Sleep(1 * time.Second)
+ s.HstSuite.SetupSuite()
+ s.ConfigureNetworkTopology("2taps")
+ s.LoadContainerTopology("2containers")
+}
+
+var _ = Describe("IperfSuite", Ordered, ContinueOnFailure, func() {
+ var s IperfSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range iperfTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("IperfSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s IperfSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range iperfSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go
new file mode 100644
index 00000000000..15b45f710ef
--- /dev/null
+++ b/extras/hs-test/infra/suite_ldp.go
@@ -0,0 +1,203 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ ServerLdpInterfaceName = "srv"
+ ClientLdpInterfaceName = "cln"
+)
+
+var ldpTests = map[string][]func(s *LdpSuite){}
+var ldpSoloTests = map[string][]func(s *LdpSuite){}
+
+type LdpSuite struct {
+ HstSuite
+}
+
+func RegisterLdpTests(tests ...func(s *LdpSuite)) {
+ ldpTests[getTestFilename()] = tests
+}
+func RegisterSoloLdpTests(tests ...func(s *LdpSuite)) {
+ ldpSoloTests[getTestFilename()] = tests
+}
+
+func (s *LdpSuite) SetupSuite() {
+ time.Sleep(1 * time.Second)
+ s.HstSuite.SetupSuite()
+ s.ConfigureNetworkTopology("2peerVeth")
+ s.LoadContainerTopology("2peerVethLdp")
+}
+
+func (s *LdpSuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ s.Log("**********************INTERRUPT MODE**********************")
+ } else {
+ sessionConfig.Close()
+ }
+
+ // ... For server
+ serverContainer := s.GetContainerByName("server-vpp")
+
+ serverVpp, err := serverContainer.newVppInstance(serverContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(serverVpp, fmt.Sprint(err))
+
+ s.SetupServerVpp()
+
+ // ... For client
+ clientContainer := s.GetContainerByName("client-vpp")
+
+ clientVpp, err := clientContainer.newVppInstance(clientContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(clientVpp, fmt.Sprint(err))
+
+ s.setupClientVpp()
+
+ serverContainer.AddEnvVar("VCL_CONFIG", serverContainer.GetContainerWorkDir()+"/vcl_srv.conf")
+ clientContainer.AddEnvVar("VCL_CONFIG", clientContainer.GetContainerWorkDir()+"/vcl_cln.conf")
+
+ for _, container := range s.StartedContainers {
+ container.AddEnvVar("LD_PRELOAD", "/usr/lib/libvcl_ldpreload.so")
+ container.AddEnvVar("LDP_DEBUG", "0")
+ container.AddEnvVar("VCL_DEBUG", "0")
+ }
+}
+
+func (s *LdpSuite) TearDownTest() {
+ for _, container := range s.StartedContainers {
+ delete(container.EnvVars, "LD_PRELOAD")
+ delete(container.EnvVars, "VCL_CONFIG")
+ }
+ s.HstSuite.TearDownTest()
+
+}
+
+func (s *LdpSuite) SetupServerVpp() {
+ var srvVclConf Stanza
+ serverContainer := s.GetContainerByName("server-vpp")
+ serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
+ serverVpp := serverContainer.VppInstance
+ s.AssertNil(serverVpp.Start())
+
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ idx, err := serverVpp.createAfPacket(serverVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+ serverContainer.GetContainerWorkDir())
+ err = srvVclConf.
+ NewStanza("vcl").
+ Append("rx-fifo-size 4000000").
+ Append("tx-fifo-size 4000000").
+ Append("app-scope-local").
+ Append("app-scope-global").
+ Append("use-mq-eventfd").
+ Append(serverAppSocketApi).Close().
+ SaveToFile(serverVclFileName)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+func (s *LdpSuite) setupClientVpp() {
+ var clnVclConf Stanza
+ clientContainer := s.GetContainerByName("client-vpp")
+ clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
+ clientVpp := clientContainer.VppInstance
+ s.AssertNil(clientVpp.Start())
+
+ clientVeth := s.GetInterfaceByName(ClientInterfaceName)
+ idx, err := clientVpp.createAfPacket(clientVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+ clientContainer.GetContainerWorkDir())
+ err = clnVclConf.
+ NewStanza("vcl").
+ Append("rx-fifo-size 4000000").
+ Append("tx-fifo-size 4000000").
+ Append("app-scope-local").
+ Append("app-scope-global").
+ Append("use-mq-eventfd").
+ Append(clientAppSocketApi).Close().
+ SaveToFile(clientVclFileName)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+var _ = Describe("LdpSuite", Ordered, ContinueOnFailure, func() {
+ var s LdpSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range ldpTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("LdpSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s LdpSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range ldpSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_nginx_proxy.go b/extras/hs-test/infra/suite_nginx_proxy.go
new file mode 100644
index 00000000000..75215cfc78d
--- /dev/null
+++ b/extras/hs-test/infra/suite_nginx_proxy.go
@@ -0,0 +1,191 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ NginxProxyContainerName = "nginx-proxy"
+ NginxServerContainerName = "nginx-server"
+ MirroringClientInterfaceName = "hstcln"
+ MirroringServerInterfaceName = "hstsrv"
+)
+
+var nginxProxyTests = map[string][]func(s *NginxProxySuite){}
+var nginxProxySoloTests = map[string][]func(s *NginxProxySuite){}
+
+type NginxProxySuite struct {
+ HstSuite
+ proxyPort uint16
+ maxTimeout int
+}
+
+func RegisterNginxProxyTests(tests ...func(s *NginxProxySuite)) {
+ nginxProxyTests[getTestFilename()] = tests
+}
+func RegisterNginxProxySoloTests(tests ...func(s *NginxProxySuite)) {
+ nginxProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *NginxProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("nginxProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *NginxProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ vppContainer := s.GetContainerByName(VppContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(MirroringClientInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(MirroringServerInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+
+ // nginx proxy
+ nginxProxyContainer := s.GetTransientContainerByName(NginxProxyContainerName)
+ s.AssertNil(nginxProxyContainer.Create())
+ s.proxyPort = 80
+ values := struct {
+ LogPrefix string
+ Proxy string
+ Server string
+ Port uint16
+ }{
+ LogPrefix: nginxProxyContainer.Name,
+ Proxy: clientInterface.Peer.Ip4AddressString(),
+ Server: serverInterface.Ip4AddressString(),
+ Port: s.proxyPort,
+ }
+ nginxProxyContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_proxy_mirroring.conf",
+ values,
+ )
+ s.AssertNil(nginxProxyContainer.Start())
+
+ // nginx HTTP server
+ nginxServerContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxServerContainer.Create())
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Timeout int
+ }{
+ LogPrefix: nginxServerContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Timeout: s.maxTimeout,
+ }
+ nginxServerContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server_mirroring.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxServerContainer.Start())
+
+ vpp.WaitForApp("nginx-", 5)
+}
+
+func (s *NginxProxySuite) TearDownTest() {
+ if CurrentSpecReport().Failed() {
+ s.CollectNginxLogs(NginxServerContainerName)
+ s.CollectNginxLogs(NginxProxyContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *NginxProxySuite) ProxyPort() uint16 {
+ return s.proxyPort
+}
+
+func (s *NginxProxySuite) ProxyAddr() string {
+ return s.GetInterfaceByName(MirroringClientInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *NginxProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("NginxProxySuite", Ordered, ContinueOnFailure, func() {
+ var s NginxProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nginxProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("NginxProxySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s NginxProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nginxProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go
new file mode 100644
index 00000000000..9b4998a77a1
--- /dev/null
+++ b/extras/hs-test/infra/suite_no_topo.go
@@ -0,0 +1,131 @@
+package hst
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+const (
+ SingleTopoContainerVpp = "vpp"
+ SingleTopoContainerNginx = "nginx"
+ TapInterfaceName = "htaphost"
+)
+
+var noTopoTests = map[string][]func(s *NoTopoSuite){}
+var noTopoSoloTests = map[string][]func(s *NoTopoSuite){}
+
+type NoTopoSuite struct {
+ HstSuite
+}
+
+func RegisterNoTopoTests(tests ...func(s *NoTopoSuite)) {
+ noTopoTests[getTestFilename()] = tests
+}
+func RegisterNoTopoSoloTests(tests ...func(s *NoTopoSuite)) {
+ noTopoSoloTests[getTestFilename()] = tests
+}
+
+func (s *NoTopoSuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("tap")
+ s.LoadContainerTopology("single")
+}
+
+func (s *NoTopoSuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ s.Log("**********************INTERRUPT MODE**********************")
+ } else {
+ sessionConfig.Close()
+ }
+
+ container := s.GetContainerByName(SingleTopoContainerVpp)
+ vpp, _ := container.newVppInstance(container.AllocatedCpus, sessionConfig)
+ s.AssertNil(vpp.Start())
+
+ tapInterface := s.GetInterfaceByName(TapInterfaceName)
+
+ s.AssertNil(vpp.createTap(tapInterface), "failed to create tap interface")
+}
+
+func (s *NoTopoSuite) VppAddr() string {
+ return s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *NoTopoSuite) VppIfName() string {
+ return s.GetInterfaceByName(TapInterfaceName).Peer.Name()
+}
+
+func (s *NoTopoSuite) HostAddr() string {
+ return s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+}
+
+var _ = Describe("NoTopoSuite", Ordered, ContinueOnFailure, func() {
+ var s NoTopoSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range noTopoTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("NoTopoSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s NoTopoSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range noTopoSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_ns.go b/extras/hs-test/infra/suite_ns.go
new file mode 100644
index 00000000000..601ec22a8a9
--- /dev/null
+++ b/extras/hs-test/infra/suite_ns.go
@@ -0,0 +1,127 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ ClientInterface = "hclnvpp"
+ ServerInterface = "hsrvvpp"
+)
+
+var nsTests = map[string][]func(s *NsSuite){}
+var nsSoloTests = map[string][]func(s *NsSuite){}
+
+type NsSuite struct {
+ HstSuite
+}
+
+func RegisterNsTests(tests ...func(s *NsSuite)) {
+ nsTests[getTestFilename()] = tests
+}
+func RegisterNsSoloTests(tests ...func(s *NsSuite)) {
+ nsSoloTests[getTestFilename()] = tests
+}
+
+func (s *NsSuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.ConfigureNetworkTopology("ns")
+ s.LoadContainerTopology("ns")
+}
+
+func (s *NsSuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api").
+ Append("evt_qs_memfd_seg").
+ Append("event-queue-length 100000")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ } else {
+ sessionConfig.Close()
+ }
+
+ container := s.GetContainerByName("vpp")
+ vpp, _ := container.newVppInstance(container.AllocatedCpus, sessionConfig)
+ s.AssertNil(vpp.Start())
+
+ idx, err := vpp.createAfPacket(s.GetInterfaceByName(ServerInterface))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ idx, err = vpp.createAfPacket(s.GetInterfaceByName(ClientInterface))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ container.Exec("chmod 777 -R %s", container.GetContainerWorkDir())
+}
+
+var _ = Describe("NsSuite", Ordered, ContinueOnFailure, func() {
+ var s NsSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nsTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("NsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s NsSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nsSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_veth.go b/extras/hs-test/infra/suite_veth.go
new file mode 100644
index 00000000000..f7b1c3da7d8
--- /dev/null
+++ b/extras/hs-test/infra/suite_veth.go
@@ -0,0 +1,153 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ ServerInterfaceName = "srv"
+ ClientInterfaceName = "cln"
+)
+
+var vethTests = map[string][]func(s *VethsSuite){}
+var vethSoloTests = map[string][]func(s *VethsSuite){}
+
+type VethsSuite struct {
+ HstSuite
+}
+
+func RegisterVethTests(tests ...func(s *VethsSuite)) {
+ vethTests[getTestFilename()] = tests
+}
+func RegisterSoloVethTests(tests ...func(s *VethsSuite)) {
+ vethSoloTests[getTestFilename()] = tests
+}
+
+func (s *VethsSuite) SetupSuite() {
+ time.Sleep(1 * time.Second)
+ s.HstSuite.SetupSuite()
+ s.ConfigureNetworkTopology("2peerVeth")
+ s.LoadContainerTopology("2peerVeth")
+}
+
+func (s *VethsSuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ s.Log("**********************INTERRUPT MODE**********************")
+ } else {
+ sessionConfig.Close()
+ }
+
+ // ... For server
+ serverContainer := s.GetContainerByName("server-vpp")
+
+ serverVpp, err := serverContainer.newVppInstance(serverContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(serverVpp, fmt.Sprint(err))
+
+ s.SetupServerVpp()
+
+ // ... For client
+ clientContainer := s.GetContainerByName("client-vpp")
+
+ clientVpp, err := clientContainer.newVppInstance(clientContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(clientVpp, fmt.Sprint(err))
+
+ s.setupClientVpp()
+}
+
+func (s *VethsSuite) SetupServerVpp() {
+ serverVpp := s.GetContainerByName("server-vpp").VppInstance
+ s.AssertNil(serverVpp.Start())
+
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ idx, err := serverVpp.createAfPacket(serverVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+}
+
+func (s *VethsSuite) setupClientVpp() {
+ clientVpp := s.GetContainerByName("client-vpp").VppInstance
+ s.AssertNil(clientVpp.Start())
+
+ clientVeth := s.GetInterfaceByName(ClientInterfaceName)
+ idx, err := clientVpp.createAfPacket(clientVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+}
+
+var _ = Describe("VethsSuite", Ordered, ContinueOnFailure, func() {
+ var s VethsSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range vethTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("VethsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s VethsSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range vethSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go
new file mode 100644
index 00000000000..868684bcede
--- /dev/null
+++ b/extras/hs-test/infra/suite_vpp_proxy.go
@@ -0,0 +1,212 @@
+// Suite for VPP proxy testing
+//
+// The topology consists of 3 containers: curl (client), VPP (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ VppProxyContainerName = "vpp-proxy"
+ ClientTapInterfaceName = "hstcln"
+ ServerTapInterfaceName = "hstsrv"
+ CurlContainerTestFile = "/tmp/testFile"
+)
+
+type VppProxySuite struct {
+ HstSuite
+ nginxPort uint16
+ maxTimeout int
+}
+
+var vppProxyTests = map[string][]func(s *VppProxySuite){}
+var vppProxySoloTests = map[string][]func(s *VppProxySuite){}
+
+func RegisterVppProxyTests(tests ...func(s *VppProxySuite)) {
+ vppProxyTests[getTestFilename()] = tests
+}
+
+func RegisterVppProxySoloTests(tests ...func(s *VppProxySuite)) {
+ vppProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *VppProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("vppProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *VppProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP HTTP connect-proxy
+ vppContainer := s.GetContainerByName(VppProxyContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+
+ // nginx HTTP server
+ nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxContainer.Create())
+ s.nginxPort = 80
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Port uint16
+ Timeout int
+ }{
+ LogPrefix: nginxContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Port: s.nginxPort,
+ Timeout: s.maxTimeout,
+ }
+ nginxContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxContainer.Start())
+}
+
+func (s *VppProxySuite) TearDownTest() {
+ vpp := s.GetContainerByName(VppProxyContainerName).VppInstance
+ if CurrentSpecReport().Failed() {
+ s.Log(vpp.Vppctl("show session verbose 2"))
+ s.Log(vpp.Vppctl("show error"))
+ s.CollectNginxLogs(NginxServerContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *VppProxySuite) NginxPort() uint16 {
+ return s.nginxPort
+}
+
+func (s *VppProxySuite) NginxAddr() string {
+ return s.GetInterfaceByName(ServerTapInterfaceName).Ip4AddressString()
+}
+
+func (s *VppProxySuite) VppProxyAddr() string {
+ return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *VppProxySuite) CurlRequest(targetUri string) (string, string) {
+ args := fmt.Sprintf("--insecure --noproxy '*' %s", targetUri)
+ body, log := s.RunCurlContainer(args)
+ return body, log
+}
+
+func (s *VppProxySuite) CurlRequestViaTunnel(targetUri string, proxyUri string) (string, string) {
+ args := fmt.Sprintf("--max-time %d --insecure -p -x %s %s", s.maxTimeout, proxyUri, targetUri)
+ body, log := s.RunCurlContainer(args)
+ return body, log
+}
+
+func (s *VppProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlUploadResource(uri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload --max-time %d --insecure --noproxy '*' -T %s %s", s.maxTimeout, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download_connect --max-time %d --insecure -p -x %s --remote-name --output-dir /tmp %s", s.maxTimeout, proxyUri, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "CONNECT response code: 200")
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload_connect --max-time %d --insecure -p -x %s -T %s %s", s.maxTimeout, proxyUri, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "CONNECT response code: 200")
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() {
+ var s VppProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range vppProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("VppProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+ var s VppProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range vppProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/topo.go b/extras/hs-test/infra/topo.go
index 6cb294511b3..f9c6528ba93 100644
--- a/extras/hs-test/topo.go
+++ b/extras/hs-test/infra/topo.go
@@ -1,4 +1,4 @@
-package main
+package hst
import (
"fmt"
diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go
new file mode 100644
index 00000000000..25d8519cb8a
--- /dev/null
+++ b/extras/hs-test/infra/utils.go
@@ -0,0 +1,316 @@
+package hst
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+)
+
+const networkTopologyDir string = "topo-network/"
+const containerTopologyDir string = "topo-containers/"
+
+type Stanza struct {
+ content string
+ pad int
+}
+
+type ActionResult struct {
+ Err error
+ Desc string
+ ErrOutput string
+ StdOutput string
+}
+
+type JsonResult struct {
+ Code int
+ Desc string
+ ErrOutput string
+ StdOutput string
+}
+
+func AssertFileSize(f1, f2 string) error {
+ fi1, err := os.Stat(f1)
+ if err != nil {
+ return err
+ }
+
+ fi2, err1 := os.Stat(f2)
+ if err1 != nil {
+ return err1
+ }
+
+ if fi1.Size() != fi2.Size() {
+ return fmt.Errorf("file sizes differ (%d vs %d)", fi1.Size(), fi2.Size())
+ }
+ return nil
+}
+
+func (c *Stanza) NewStanza(name string) *Stanza {
+ c.Append("\n" + name + " {")
+ c.pad += 2
+ return c
+}
+
+func (c *Stanza) Append(name string) *Stanza {
+ c.content += strings.Repeat(" ", c.pad)
+ c.content += name + "\n"
+ return c
+}
+
+func (c *Stanza) Close() *Stanza {
+ c.content += "}\n"
+ c.pad -= 2
+ return c
+}
+
+func (s *Stanza) ToString() string {
+ return s.content
+}
+
+func (s *Stanza) SaveToFile(fileName string) error {
+ fo, err := os.Create(fileName)
+ if err != nil {
+ return err
+ }
+ defer fo.Close()
+
+ _, err = io.Copy(fo, strings.NewReader(s.content))
+ return err
+}
+
+// NewHttpClient creates [http.Client] with disabled proxy and redirects, it also sets timeout to 30seconds.
+func NewHttpClient() *http.Client {
+ transport := http.DefaultTransport
+ transport.(*http.Transport).Proxy = nil
+ transport.(*http.Transport).DisableKeepAlives = true
+ client := &http.Client{
+ Transport: transport,
+ Timeout: time.Second * 30,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+ }}
+ return client
+}
+
+func DumpHttpResp(resp *http.Response, body bool) string {
+ dump, err := httputil.DumpResponse(resp, body)
+ if err != nil {
+ return ""
+ }
+ return string(dump)
+}
+
+func TcpSendReceive(address, data string) (string, error) {
+ conn, err := net.DialTimeout("tcp", address, time.Second*30)
+ if err != nil {
+ return "", err
+ }
+ defer conn.Close()
+ err = conn.SetDeadline(time.Now().Add(time.Second * 30))
+ if err != nil {
+ return "", err
+ }
+ _, err = conn.Write([]byte(data))
+ if err != nil {
+ return "", err
+ }
+ reply := make([]byte, 1024)
+ _, err = conn.Read(reply)
+ if err != nil {
+ return "", err
+ }
+ return string(reply), nil
+}
+
+/*
+RunCurlContainer execute curl command with given args.
+Container with name "curl" must be available.
+Curl runs in verbose mode and progress meter switch off by default.
+*/
+func (s *HstSuite) RunCurlContainer(args string) (string, string) {
+ curlCont := s.GetContainerByName("curl")
+ cmd := fmt.Sprintf("curl -v -s %s", args)
+ s.Log(cmd)
+ curlCont.ExtraRunningArgs = cmd
+ curlCont.Run()
+ stdout, stderr := curlCont.GetOutput()
+ s.Log(stderr)
+ s.Log(stdout)
+ return stdout, stderr
+}
+
+/*
+CollectNginxLogs save access and error logs to the test execution directory.
+Nginx logging need to be set following way:
+
+ - error_log <default-work-dir>/{{.LogPrefix}}-error.log;
+ - access_log <default-work-dir>/{{.LogPrefix}}-access.log;
+
+where LogPrefix is set to nginxContainer.Name
+*/
+func (s *HstSuite) CollectNginxLogs(containerName string) {
+ nginxContainer := s.GetContainerByName(containerName)
+ targetDir := nginxContainer.Suite.getLogDirPath()
+ source := nginxContainer.GetHostWorkDir() + "/" + nginxContainer.Name + "-"
+ cmd := exec.Command("cp", "-t", targetDir, source+"error.log", source+"access.log")
+ s.Log(cmd.String())
+ err := cmd.Run()
+ if err != nil {
+ s.Log(fmt.Sprint(err))
+ }
+}
+
+/*
+CollectEnvoyLogs save access logs to the test execution directory.
+Envoy access log path need to be set following way:
+<default-work-dir>/{{.LogPrefix}}-access.log
+where LogPrefix is set to envoyContainer.Name
+*/
+func (s *HstSuite) CollectEnvoyLogs(containerName string) {
+ envoyContainer := s.GetContainerByName(containerName)
+ targetDir := envoyContainer.Suite.getLogDirPath()
+ source := envoyContainer.GetHostWorkDir() + "/" + envoyContainer.Name + "-"
+ cmd := exec.Command("cp", "-t", targetDir, source+"access.log")
+ s.Log(cmd.String())
+ err := cmd.Run()
+ if err != nil {
+ s.Log(fmt.Sprint(err))
+ }
+}
+
+func (s *HstSuite) StartIperfServerApp(running chan error, done chan struct{}, env []string) {
+ cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid())
+ if env != nil {
+ cmd.Env = env
+ }
+ s.Log(cmd)
+ err := cmd.Start()
+ if err != nil {
+ msg := fmt.Errorf("failed to start iperf server: %v", err)
+ running <- msg
+ return
+ }
+ running <- nil
+ <-done
+ cmd.Process.Kill()
+}
+
+func (s *HstSuite) StartIperfClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
+ defer func() {
+ clnCh <- nil
+ }()
+
+ nTries := 0
+
+ for {
+ cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid())
+ if env != nil {
+ cmd.Env = env
+ }
+ s.Log(cmd)
+ o, err := cmd.CombinedOutput()
+ if err != nil {
+ if nTries > 5 {
+ clnRes <- ""
+ clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
+ return
+ }
+ time.Sleep(1 * time.Second)
+ nTries++
+ continue
+ } else {
+ clnRes <- fmt.Sprintf("Client output: %s", o)
+ }
+ break
+ }
+}
+
+func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
+ cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs)
+ err := cmd.Start()
+ s.Log(cmd)
+ if err != nil {
+ s.Log("Failed to start http server: " + fmt.Sprint(err))
+ return
+ }
+ running <- struct{}{}
+ <-done
+ cmd.Process.Kill()
+}
+
+func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs string) {
+ defer func() {
+ finished <- errors.New("wget error")
+ }()
+
+ cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
+ netNs)
+ s.Log(cmd)
+ o, err := cmd.CombinedOutput()
+ if err != nil {
+ finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
+ return
+ } else if !strings.Contains(string(o), "200 OK") {
+ finished <- fmt.Errorf("wget error: response not 200 OK")
+ return
+ }
+ finished <- nil
+}
+
+// Start a server app. 'processName' is used to check whether the app started correctly.
+func (s *HstSuite) StartServerApp(c *Container, processName string, cmd string,
+ running chan error, done chan struct{}) {
+
+ s.Log("starting server")
+ c.ExecServer(cmd)
+ cmd2 := exec.Command("docker", "exec", c.Name, "pidof", processName)
+ err := cmd2.Run()
+ if err != nil {
+ msg := fmt.Errorf("failed to start server app: %v", err)
+ running <- msg
+ <-done
+ return
+ }
+ running <- nil
+ <-done
+}
+
+func (s *HstSuite) StartClientApp(c *Container, cmd string,
+ clnCh chan error, clnRes chan string) {
+ defer func() {
+ close(clnCh)
+ close(clnRes)
+ }()
+
+ s.Log("starting client app, please wait")
+
+ nTries := 0
+ for {
+ // exec.Cmd can only be used once, which is why it's in the loop
+ cmd2 := exec.Command("/bin/sh", "-c", "docker exec "+c.getEnvVarsAsCliOption()+" "+
+ c.Name+" "+cmd)
+ s.Log(cmd2)
+ o, err := cmd2.CombinedOutput()
+ if err != nil {
+ s.Log(err)
+ if nTries > 5 {
+ clnRes <- ""
+ clnCh <- fmt.Errorf("failed to start client app '%s'", err)
+ s.AssertNil(err, fmt.Sprint(err))
+ break
+ }
+ time.Sleep(1 * time.Second)
+ nTries++
+ } else {
+ clnRes <- fmt.Sprintf("Client output: %s", o)
+ break
+ }
+ }
+}
diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go
new file mode 100644
index 00000000000..a1f2ce46ed3
--- /dev/null
+++ b/extras/hs-test/infra/vppinstance.go
@@ -0,0 +1,647 @@
+package hst
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "os/signal"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ "go.fd.io/govpp/binapi/ethernet_types"
+
+ "github.com/edwarnicke/exechelper"
+ . "github.com/onsi/ginkgo/v2"
+ "github.com/sirupsen/logrus"
+
+ "go.fd.io/govpp"
+ "go.fd.io/govpp/api"
+ "go.fd.io/govpp/binapi/af_packet"
+ interfaces "go.fd.io/govpp/binapi/interface"
+ "go.fd.io/govpp/binapi/interface_types"
+ "go.fd.io/govpp/binapi/session"
+ "go.fd.io/govpp/binapi/tapv2"
+ "go.fd.io/govpp/core"
+)
+
+const vppConfigTemplate = `unix {
+ nodaemon
+ log %[1]s%[4]s
+ full-coredump
+ coredump-size unlimited
+ cli-listen %[1]s%[2]s
+ runtime-dir %[1]s/var/run
+}
+
+api-trace {
+ on
+}
+
+socksvr {
+ socket-name %[1]s%[3]s
+}
+
+statseg {
+ socket-name %[1]s/var/run/vpp/stats.sock
+}
+
+plugins {
+ plugin default { disable }
+
+ plugin unittest_plugin.so { enable }
+ plugin quic_plugin.so { enable }
+ plugin af_packet_plugin.so { enable }
+ plugin hs_apps_plugin.so { enable }
+ plugin http_plugin.so { enable }
+ plugin http_static_plugin.so { enable }
+ plugin prom_plugin.so { enable }
+ plugin tlsopenssl_plugin.so { enable }
+ plugin ping_plugin.so { enable }
+ plugin nsim_plugin.so { enable }
+ plugin mactime_plugin.so { enable }
+}
+
+logging {
+ default-log-level debug
+ default-syslog-log-level debug
+}
+
+`
+
+const (
+ defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
+ defaultApiSocketFilePath = "/var/run/vpp/api.sock"
+ defaultLogFilePath = "/var/log/vpp/vpp.log"
+)
+
+type VppInstance struct {
+ Container *Container
+ AdditionalConfig []Stanza
+ Connection *core.Connection
+ ApiStream api.Stream
+ Cpus []int
+ CpuConfig VppCpuConfig
+}
+
+type VppCpuConfig struct {
+ PinMainCpu bool
+ PinWorkersCorelist bool
+ SkipCores int
+}
+
+type VppMemTrace struct {
+ Count int `json:"count"`
+ Size int `json:"bytes"`
+ Sample string `json:"sample"`
+ Traceback []string `json:"traceback"`
+}
+
+func (vpp *VppInstance) getSuite() *HstSuite {
+ return vpp.Container.Suite
+}
+
+func (vpp *VppInstance) getCliSocket() string {
+ return fmt.Sprintf("%s%s", vpp.Container.GetContainerWorkDir(), defaultCliSocketFilePath)
+}
+
+func (vpp *VppInstance) getRunDir() string {
+ return vpp.Container.GetContainerWorkDir() + "/var/run/vpp"
+}
+
+func (vpp *VppInstance) getLogDir() string {
+ return vpp.Container.GetContainerWorkDir() + "/var/log/vpp"
+}
+
+func (vpp *VppInstance) getEtcDir() string {
+ return vpp.Container.GetContainerWorkDir() + "/etc/vpp"
+}
+
+func (vpp *VppInstance) Start() error {
+ maxReconnectAttempts := 3
+ // Replace default logger in govpp with our own
+ govppLogger := logrus.New()
+ govppLogger.SetOutput(io.MultiWriter(vpp.getSuite().Logger.Writer(), GinkgoWriter))
+ core.SetLogger(govppLogger)
+ // Create folders
+ containerWorkDir := vpp.Container.GetContainerWorkDir()
+
+ vpp.Container.Exec("mkdir --mode=0700 -p " + vpp.getRunDir())
+ vpp.Container.Exec("mkdir --mode=0700 -p " + vpp.getLogDir())
+ vpp.Container.Exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
+
+ // Create startup.conf inside the container
+ configContent := fmt.Sprintf(
+ vppConfigTemplate,
+ containerWorkDir,
+ defaultCliSocketFilePath,
+ defaultApiSocketFilePath,
+ defaultLogFilePath,
+ )
+ configContent += vpp.generateVPPCpuConfig()
+ for _, c := range vpp.AdditionalConfig {
+ configContent += c.ToString()
+ }
+ startupFileName := vpp.getEtcDir() + "/startup.conf"
+ vpp.Container.CreateFile(startupFileName, configContent)
+
+ // create wrapper script for vppctl with proper CLI socket path
+ cliContent := "#!/usr/bin/bash\nvppctl -s " + vpp.getRunDir() + "/cli.sock"
+ vppcliFileName := "/usr/bin/vppcli"
+ vpp.Container.CreateFile(vppcliFileName, cliContent)
+ vpp.Container.Exec("chmod 0755 " + vppcliFileName)
+
+ vpp.getSuite().Log("starting vpp")
+ if *IsVppDebug {
+ // default = 3; VPP will timeout while debugging if there are not enough attempts
+ maxReconnectAttempts = 5000
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGQUIT)
+ cont := make(chan bool, 1)
+ go func() {
+ <-sig
+ cont <- true
+ }()
+
+ vpp.Container.ExecServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
+ fmt.Println("run following command in different terminal:")
+ fmt.Println("docker exec -it " + vpp.Container.Name + " gdb -ex \"attach $(docker exec " + vpp.Container.Name + " pidof vpp)\"")
+ fmt.Println("Afterwards press CTRL+\\ to continue")
+ <-cont
+ fmt.Println("continuing...")
+ } else {
+ // Start VPP
+ vpp.Container.ExecServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
+ }
+
+ vpp.getSuite().Log("connecting to vpp")
+ // Connect to VPP and store the connection
+ sockAddress := vpp.Container.GetHostWorkDir() + defaultApiSocketFilePath
+ conn, connEv, err := govpp.AsyncConnect(
+ sockAddress,
+ maxReconnectAttempts,
+ core.DefaultReconnectInterval)
+ if err != nil {
+ vpp.getSuite().Log("async connect error: " + fmt.Sprint(err))
+ return err
+ }
+ vpp.Connection = conn
+
+ // ... wait for Connected event
+ e := <-connEv
+ if e.State != core.Connected {
+ vpp.getSuite().Log("connecting to VPP failed: " + fmt.Sprint(e.Error))
+ return e.Error
+ }
+
+ ch, err := conn.NewStream(
+ context.Background(),
+ core.WithRequestSize(50),
+ core.WithReplySize(50),
+ core.WithReplyTimeout(time.Second*5))
+ if err != nil {
+ vpp.getSuite().Log("creating stream failed: " + fmt.Sprint(err))
+ return err
+ }
+ vpp.ApiStream = ch
+
+ return nil
+}
+
+func (vpp *VppInstance) Vppctl(command string, arguments ...any) string {
+ vppCliCommand := fmt.Sprintf(command, arguments...)
+ containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
+ vpp.Container.Name, vpp.getCliSocket(), vppCliCommand)
+ vpp.getSuite().Log(containerExecCommand)
+ output, err := exechelper.CombinedOutput(containerExecCommand)
+ vpp.getSuite().AssertNil(err)
+
+ return string(output)
+}
+
+func (vpp *VppInstance) GetSessionStat(stat string) int {
+ o := vpp.Vppctl("show session stats")
+ vpp.getSuite().Log(o)
+ for _, line := range strings.Split(o, "\n") {
+ if strings.Contains(line, stat) {
+ tokens := strings.Split(strings.TrimSpace(line), " ")
+ val, err := strconv.Atoi(tokens[0])
+ if err != nil {
+ Fail("failed to parse stat value %s" + fmt.Sprint(err))
+ return 0
+ }
+ return val
+ }
+ }
+ return 0
+}
+
+func (vpp *VppInstance) WaitForApp(appName string, timeout int) {
+ vpp.getSuite().Log("waiting for app " + appName)
+ for i := 0; i < timeout; i++ {
+ o := vpp.Vppctl("show app")
+ if strings.Contains(o, appName) {
+ return
+ }
+ time.Sleep(1 * time.Second)
+ }
+ vpp.getSuite().AssertNil(1, "Timeout while waiting for app '%s'", appName)
+}
+
+func (vpp *VppInstance) createAfPacket(
+ veth *NetInterface,
+) (interface_types.InterfaceIndex, error) {
+ createReq := &af_packet.AfPacketCreateV3{
+ Mode: 1,
+ UseRandomHwAddr: true,
+ HostIfName: veth.Name(),
+ Flags: af_packet.AfPacketFlags(11),
+ }
+ if veth.HwAddress != (MacAddress{}) {
+ createReq.UseRandomHwAddr = false
+ createReq.HwAddr = veth.HwAddress
+ }
+
+ vpp.getSuite().Log("create af-packet interface " + veth.Name())
+ if err := vpp.ApiStream.SendMsg(createReq); err != nil {
+ vpp.getSuite().HstFail()
+ return 0, err
+ }
+ replymsg, err := vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return 0, err
+ }
+ reply := replymsg.(*af_packet.AfPacketCreateV3Reply)
+ err = api.RetvalToVPPApiError(reply.Retval)
+ if err != nil {
+ return 0, err
+ }
+
+ veth.Index = reply.SwIfIndex
+
+ // Set to up
+ upReq := &interfaces.SwInterfaceSetFlags{
+ SwIfIndex: veth.Index,
+ Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
+ }
+
+ vpp.getSuite().Log("set af-packet interface " + veth.Name() + " up")
+ if err := vpp.ApiStream.SendMsg(upReq); err != nil {
+ return 0, err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return 0, err
+ }
+ reply2 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
+ if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
+ return 0, err
+ }
+
+ // Add address
+ if veth.AddressWithPrefix() == (AddressWithPrefix{}) {
+ var err error
+ var ip4Address string
+ if ip4Address, err = veth.Ip4AddrAllocator.NewIp4InterfaceAddress(veth.Peer.NetworkNumber); err == nil {
+ veth.Ip4Address = ip4Address
+ } else {
+ return 0, err
+ }
+ }
+ addressReq := &interfaces.SwInterfaceAddDelAddress{
+ IsAdd: true,
+ SwIfIndex: veth.Index,
+ Prefix: veth.AddressWithPrefix(),
+ }
+
+ vpp.getSuite().Log("af-packet interface " + veth.Name() + " add address " + veth.Ip4Address)
+ if err := vpp.ApiStream.SendMsg(addressReq); err != nil {
+ return 0, err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return 0, err
+ }
+ reply3 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
+ err = api.RetvalToVPPApiError(reply3.Retval)
+ if err != nil {
+ return 0, err
+ }
+
+ return veth.Index, nil
+}
+
+func (vpp *VppInstance) addAppNamespace(
+ secret uint64,
+ ifx interface_types.InterfaceIndex,
+ namespaceId string,
+) error {
+ req := &session.AppNamespaceAddDelV4{
+ IsAdd: true,
+ Secret: secret,
+ SwIfIndex: ifx,
+ NamespaceID: namespaceId,
+ SockName: defaultApiSocketFilePath,
+ }
+
+ vpp.getSuite().Log("add app namespace " + namespaceId)
+ if err := vpp.ApiStream.SendMsg(req); err != nil {
+ return err
+ }
+ replymsg, err := vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ reply := replymsg.(*session.AppNamespaceAddDelV4Reply)
+ if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
+ return err
+ }
+
+ sessionReq := &session.SessionEnableDisable{
+ IsEnable: true,
+ }
+
+ vpp.getSuite().Log("enable app namespace " + namespaceId)
+ if err := vpp.ApiStream.SendMsg(sessionReq); err != nil {
+ return err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ reply2 := replymsg.(*session.SessionEnableDisableReply)
+ if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (vpp *VppInstance) createTap(
+ tap *NetInterface,
+ tapId ...uint32,
+) error {
+ var id uint32 = 1
+ if len(tapId) > 0 {
+ id = tapId[0]
+ }
+ createTapReq := &tapv2.TapCreateV3{
+ ID: id,
+ HostIfNameSet: true,
+ HostIfName: tap.Name(),
+ HostIP4PrefixSet: true,
+ HostIP4Prefix: tap.Ip4AddressWithPrefix(),
+ }
+
+ vpp.getSuite().Log("create tap interface " + tap.Name())
+ // Create tap interface
+ if err := vpp.ApiStream.SendMsg(createTapReq); err != nil {
+ return err
+ }
+ replymsg, err := vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ reply := replymsg.(*tapv2.TapCreateV3Reply)
+ if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
+ return err
+ }
+ tap.Peer.Index = reply.SwIfIndex
+
+ // Get name and mac
+ if err := vpp.ApiStream.SendMsg(&interfaces.SwInterfaceDump{
+ SwIfIndex: reply.SwIfIndex,
+ }); err != nil {
+ return err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ ifDetails := replymsg.(*interfaces.SwInterfaceDetails)
+ tap.Peer.name = ifDetails.InterfaceName
+ tap.Peer.HwAddress = ifDetails.L2Address
+
+ // Add address
+ addAddressReq := &interfaces.SwInterfaceAddDelAddress{
+ IsAdd: true,
+ SwIfIndex: reply.SwIfIndex,
+ Prefix: tap.Peer.AddressWithPrefix(),
+ }
+
+ vpp.getSuite().Log("tap interface " + tap.Name() + " add address " + tap.Peer.Ip4Address)
+ if err := vpp.ApiStream.SendMsg(addAddressReq); err != nil {
+ return err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ reply2 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
+ if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
+ return err
+ }
+
+ // Set interface to up
+ upReq := &interfaces.SwInterfaceSetFlags{
+ SwIfIndex: reply.SwIfIndex,
+ Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
+ }
+
+ vpp.getSuite().Log("set tap interface " + tap.Name() + " up")
+ if err := vpp.ApiStream.SendMsg(upReq); err != nil {
+ return err
+ }
+ replymsg, err = vpp.ApiStream.RecvMsg()
+ if err != nil {
+ return err
+ }
+ reply3 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
+ if err = api.RetvalToVPPApiError(reply3.Retval); err != nil {
+ return err
+ }
+
+ // Get host mac
+ netIntf, err := net.InterfaceByName(tap.Name())
+ if err == nil {
+ tap.HwAddress, _ = ethernet_types.ParseMacAddress(netIntf.HardwareAddr.String())
+ }
+
+ return nil
+}
+
+func (vpp *VppInstance) saveLogs() {
+ logTarget := vpp.getSuite().getLogDirPath() + "vppinstance-" + vpp.Container.Name + ".log"
+ logSource := vpp.Container.GetHostWorkDir() + defaultLogFilePath
+ cmd := exec.Command("cp", logSource, logTarget)
+ vpp.getSuite().Log(cmd.String())
+ cmd.Run()
+}
+
+func (vpp *VppInstance) Disconnect() {
+ vpp.Connection.Disconnect()
+ vpp.ApiStream.Close()
+}
+
+func (vpp *VppInstance) setDefaultCpuConfig() {
+ vpp.CpuConfig.PinMainCpu = true
+ vpp.CpuConfig.PinWorkersCorelist = true
+ vpp.CpuConfig.SkipCores = 0
+}
+
+func (vpp *VppInstance) generateVPPCpuConfig() string {
+ var c Stanza
+ var s string
+ startCpu := 0
+ if len(vpp.Cpus) < 1 {
+ return ""
+ }
+
+ c.NewStanza("cpu")
+
+ // If skip-cores is valid, use as start value to assign main/workers CPUs
+ if vpp.CpuConfig.SkipCores != 0 {
+ c.Append(fmt.Sprintf("skip-cores %d", vpp.CpuConfig.SkipCores))
+ vpp.getSuite().Log(fmt.Sprintf("skip-cores %d", vpp.CpuConfig.SkipCores))
+ }
+
+ if len(vpp.Cpus) > vpp.CpuConfig.SkipCores {
+ startCpu = vpp.CpuConfig.SkipCores
+ }
+
+ if vpp.CpuConfig.PinMainCpu {
+ c.Append(fmt.Sprintf("main-core %d", vpp.Cpus[startCpu]))
+ vpp.getSuite().Log(fmt.Sprintf("main-core %d", vpp.Cpus[startCpu]))
+ }
+
+ workers := vpp.Cpus[startCpu+1:]
+
+ if len(workers) > 0 {
+ if vpp.CpuConfig.PinWorkersCorelist {
+ for i := 0; i < len(workers); i++ {
+ if i != 0 {
+ s = s + ", "
+ }
+ s = s + fmt.Sprintf("%d", workers[i])
+ }
+ c.Append(fmt.Sprintf("corelist-workers %s", s))
+ vpp.getSuite().Log("corelist-workers " + s)
+ } else {
+ s = fmt.Sprintf("%d", len(workers))
+ c.Append(fmt.Sprintf("workers %s", s))
+ vpp.getSuite().Log("workers " + s)
+ }
+ }
+
+ return c.Close().ToString()
+}
+
+// EnableMemoryTrace enables memory traces of VPP main-heap
+func (vpp *VppInstance) EnableMemoryTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("memory-trace on main-heap"))
+}
+
+// GetMemoryTrace dumps memory traces for analysis
+func (vpp *VppInstance) GetMemoryTrace() ([]VppMemTrace, error) {
+ var trace []VppMemTrace
+ vpp.getSuite().Log(vpp.Vppctl("save memory-trace trace.json"))
+ err := vpp.Container.GetFile("/tmp/trace.json", "/tmp/trace.json")
+ if err != nil {
+ return nil, err
+ }
+ fileBytes, err := os.ReadFile("/tmp/trace.json")
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(fileBytes, &trace)
+ if err != nil {
+ return nil, err
+ }
+ return trace, nil
+}
+
+// memTracesSuppressCli filter out CLI related samples
+func memTracesSuppressCli(traces []VppMemTrace) []VppMemTrace {
+ var filtered []VppMemTrace
+ for i := 0; i < len(traces); i++ {
+ isCli := false
+ for j := 0; j < len(traces[i].Traceback); j++ {
+ if strings.Contains(traces[i].Traceback[j], "unix_cli") {
+ isCli = true
+ break
+ }
+ }
+ if !isCli {
+ filtered = append(filtered, traces[i])
+ }
+ }
+ return filtered
+}
+
+// MemLeakCheck compares memory traces at different point in time, analyzes if memory leaks happen and produces report
+func (vpp *VppInstance) MemLeakCheck(first, second []VppMemTrace) {
+ totalBytes := 0
+ totalCounts := 0
+ trace1 := memTracesSuppressCli(first)
+ trace2 := memTracesSuppressCli(second)
+ report := ""
+ for i := 0; i < len(trace2); i++ {
+ match := false
+ for j := 0; j < len(trace1); j++ {
+ if trace1[j].Sample == trace2[i].Sample {
+ if trace2[i].Size > trace1[j].Size {
+ deltaBytes := trace2[i].Size - trace1[j].Size
+ deltaCounts := trace2[i].Count - trace1[j].Count
+ report += fmt.Sprintf("grow %d byte(s) in %d allocation(s) from:\n", deltaBytes, deltaCounts)
+ for j := 0; j < len(trace2[i].Traceback); j++ {
+ report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j])
+ }
+ totalBytes += deltaBytes
+ totalCounts += deltaCounts
+ }
+ match = true
+ break
+ }
+ }
+ if !match {
+ report += fmt.Sprintf("\nleak of %d byte(s) in %d allocation(s) from:\n", trace2[i].Size, trace2[i].Count)
+ for j := 0; j < len(trace2[i].Traceback); j++ {
+ report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j])
+ }
+ totalBytes += trace2[i].Size
+ totalCounts += trace2[i].Count
+ }
+ }
+ summary := fmt.Sprintf("\nSUMMARY: %d byte(s) leaked in %d allocation(s)\n", totalBytes, totalCounts)
+ AddReportEntry(summary, report)
+}
+
+// CollectEventLogs saves event logs to the test execution directory
+func (vpp *VppInstance) CollectEventLogs() {
+ vpp.getSuite().Log(vpp.Vppctl("event-logger save event_log"))
+ targetDir := vpp.Container.Suite.getLogDirPath()
+ err := vpp.Container.GetFile("/tmp/event_log", targetDir+"/"+vpp.Container.Name+"-event_log")
+ if err != nil {
+ vpp.getSuite().Log(fmt.Sprint(err))
+ }
+}
+
+// EnablePcapTrace enables packet capture on all interfaces and maximum 10000 packets
+func (vpp *VppInstance) EnablePcapTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("pcap trace rx tx max 10000 intfc any file vppTest.pcap"))
+}
+
+// CollectPcapTrace saves pcap trace to the test execution directory
+func (vpp *VppInstance) CollectPcapTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("pcap trace off"))
+ targetDir := vpp.Container.Suite.getLogDirPath()
+ err := vpp.Container.GetFile("/tmp/vppTest.pcap", targetDir+"/"+vpp.Container.Name+".pcap")
+ if err != nil {
+ vpp.getSuite().Log(fmt.Sprint(err))
+ }
+}
diff --git a/extras/hs-test/iperf_linux_test.go b/extras/hs-test/iperf_linux_test.go
new file mode 100644
index 00000000000..14422fe5efa
--- /dev/null
+++ b/extras/hs-test/iperf_linux_test.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "fmt"
+
+ . "fd.io/hs-test/infra"
+ . "github.com/onsi/ginkgo/v2"
+)
+
+func init() {
+ RegisterIperfTests(IperfLinuxTest)
+}
+
+func IperfLinuxTest(s *IperfSuite) {
+ serverContainer := s.GetContainerByName(ServerIperfContainerName)
+ serverIpAddress := s.GetInterfaceByName(ServerIperfInterfaceName).Ip4AddressString()
+ clientContainer := s.GetContainerByName(ClientIperfContainerName)
+ clientIpAddress := s.GetInterfaceByName(ClientIperfInterfaceName).Ip4AddressString()
+
+ clnCh := make(chan error)
+ stopServerCh := make(chan struct{})
+ srvCh := make(chan error, 1)
+ clnRes := make(chan string, 1)
+
+ defer func() {
+ stopServerCh <- struct{}{}
+ }()
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "iperf3 -4 -s -B " + serverIpAddress + " -p " + s.GetPortFromPpid()
+ s.StartServerApp(serverContainer, "iperf3", cmd, srvCh, stopServerCh)
+ }()
+ err := <-srvCh
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log("server running")
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "iperf3 -c " + serverIpAddress + " -B " + clientIpAddress +
+ " -u -l 1460 -b 10g -p " + s.GetPortFromPpid()
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
+ }()
+
+ s.Log(<-clnRes)
+ err = <-clnCh
+ s.AssertNil(err, "err: '%s'", err)
+}
diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go
index 24d2de39485..03636b11191 100644
--- a/extras/hs-test/ldp_test.go
+++ b/extras/hs-test/ldp_test.go
@@ -2,83 +2,89 @@ package main
import (
"fmt"
- "os"
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
- registerVethTests(LDPreloadIperfVppTest)
+ RegisterLdpTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest, RedisBenchmarkTest)
}
-func LDPreloadIperfVppTest(s *VethsSuite) {
- var clnVclConf, srvVclConf Stanza
-
- serverContainer := s.getContainerByName("server-vpp")
- serverVclFileName := serverContainer.getHostWorkDir() + "/vcl_srv.conf"
-
- clientContainer := s.getContainerByName("client-vpp")
- clientVclFileName := clientContainer.getHostWorkDir() + "/vcl_cln.conf"
+func LDPreloadIperfVppInterruptModeTest(s *LdpSuite) {
+ LDPreloadIperfVppTest(s)
+}
- ldpreload := "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
+func LDPreloadIperfVppTest(s *LdpSuite) {
+ clientContainer := s.GetContainerByName("client-vpp")
+ serverContainer := s.GetContainerByName("server-vpp")
stopServerCh := make(chan struct{}, 1)
srvCh := make(chan error, 1)
clnCh := make(chan error)
+ clnRes := make(chan string, 1)
- s.log("starting VPPs")
-
- clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
- clientContainer.getHostWorkDir())
- err := clnVclConf.
- newStanza("vcl").
- append("rx-fifo-size 4000000").
- append("tx-fifo-size 4000000").
- append("app-scope-local").
- append("app-scope-global").
- append("use-mq-eventfd").
- append(clientAppSocketApi).close().
- saveToFile(clientVclFileName)
- s.assertNil(err, fmt.Sprint(err))
-
- serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
- serverContainer.getHostWorkDir())
- err = srvVclConf.
- newStanza("vcl").
- append("rx-fifo-size 4000000").
- append("tx-fifo-size 4000000").
- append("app-scope-local").
- append("app-scope-global").
- append("use-mq-eventfd").
- append(serverAppSocketApi).close().
- saveToFile(serverVclFileName)
- s.assertNil(err, fmt.Sprint(err))
-
- s.log("attaching server to vpp")
-
- srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+serverVclFileName)
go func() {
defer GinkgoRecover()
- s.startServerApp(srvCh, stopServerCh, srvEnv)
+ cmd := "iperf3 -4 -s -p " + s.GetPortFromPpid()
+ s.StartServerApp(serverContainer, "iperf3", cmd, srvCh, stopServerCh)
}()
- err = <-srvCh
- s.assertNil(err, fmt.Sprint(err))
+ err := <-srvCh
+ s.AssertNil(err, fmt.Sprint(err))
- s.log("attaching client to vpp")
- var clnRes = make(chan string, 1)
- clnEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+clientVclFileName)
- serverVethAddress := s.getInterfaceByName(serverInterfaceName).ip4AddressString()
+ serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
go func() {
defer GinkgoRecover()
- s.startClientApp(serverVethAddress, clnEnv, clnCh, clnRes)
+ cmd := "iperf3 -c " + serverVethAddress + " -u -l 1460 -b 10g -p " + s.GetPortFromPpid()
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
}()
- s.log(<-clnRes)
+ s.Log(<-clnRes)
// wait for client's result
err = <-clnCh
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
// stop server
stopServerCh <- struct{}{}
}
+
+func RedisBenchmarkTest(s *LdpSuite) {
+ s.SkipIfMultiWorker()
+
+ serverContainer := s.GetContainerByName("server-vpp")
+ clientContainer := s.GetContainerByName("client-vpp")
+
+ serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
+ runningSrv := make(chan error)
+ doneSrv := make(chan struct{})
+ clnCh := make(chan error)
+ clnRes := make(chan string, 1)
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "redis-server --daemonize yes --protected-mode no --bind " + serverVethAddress
+ s.StartServerApp(serverContainer, "redis-server", cmd, runningSrv, doneSrv)
+ }()
+
+ err := <-runningSrv
+ s.AssertNil(err)
+
+ go func() {
+ defer GinkgoRecover()
+ var cmd string
+ if *NConfiguredCpus == 1 {
+ cmd = "redis-benchmark --threads 1 -h " + serverVethAddress
+ } else {
+ cmd = "redis-benchmark --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress
+ }
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
+ }()
+
+ s.Log(<-clnRes)
+ // wait for client's result
+ err = <-clnCh
+ s.AssertNil(err, fmt.Sprint(err))
+ // stop server
+ doneSrv <- struct{}{}
+}
diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go
deleted file mode 100644
index e323f7fb721..00000000000
--- a/extras/hs-test/linux_iperf_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package main
-
-import (
- "fmt"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-func init() {
- registerTapTests(LinuxIperfTest)
-}
-
-func LinuxIperfTest(s *TapSuite) {
- clnCh := make(chan error)
- stopServerCh := make(chan struct{})
- srvCh := make(chan error, 1)
- clnRes := make(chan string, 1)
- defer func() {
- stopServerCh <- struct{}{}
- }()
-
- go func() {
- defer GinkgoRecover()
- s.startServerApp(srvCh, stopServerCh, nil)
- }()
- err := <-srvCh
- s.assertNil(err, fmt.Sprint(err))
- s.log("server running")
-
- ipAddress := s.getInterfaceByName(tapInterfaceName).ip4AddressString()
- go func() {
- defer GinkgoRecover()
- s.startClientApp(ipAddress, nil, clnCh, clnRes)
- }()
- s.log("client running")
- s.log(<-clnRes)
- err = <-clnCh
- s.assertNil(err, "err: '%s', ip: '%s'", err, ipAddress)
- s.log("Test completed")
-}
diff --git a/extras/hs-test/mem_leak_test.go b/extras/hs-test/mem_leak_test.go
new file mode 100644
index 00000000000..0d8831d2bbc
--- /dev/null
+++ b/extras/hs-test/mem_leak_test.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "fmt"
+
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterNoTopoSoloTests(MemLeakTest)
+}
+
+func MemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ /* no goVPP less noise */
+ vpp.Disconnect()
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.Vppctl("test mem-leak")
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
diff --git a/extras/hs-test/mirroring_test.go b/extras/hs-test/mirroring_test.go
deleted file mode 100644
index 1fd15dde2f4..00000000000
--- a/extras/hs-test/mirroring_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package main
-
-import (
- "github.com/edwarnicke/exechelper"
-)
-
-func init() {
- registerNginxTests(MirroringTest)
-}
-
-// broken when CPUS > 1
-func MirroringTest(s *NginxSuite) {
- s.SkipIfMultiWorker()
- proxyAddress := s.getInterfaceByName(mirroringClientInterfaceName).peer.ip4AddressString()
-
- path := "/64B.json"
-
- testCommand := "wrk -c 20 -t 10 -d 10 http://" + proxyAddress + ":80" + path
- s.log(testCommand)
- o, _ := exechelper.Output(testCommand)
- s.log(string(o))
- s.assertNotEmpty(o)
-
- vppProxyContainer := s.getContainerByName(vppProxyContainerName)
- s.assertEqual(0, vppProxyContainer.vppInstance.GetSessionStat("no lcl port"))
-}
diff --git a/extras/hs-test/nginx_test.go b/extras/hs-test/nginx_test.go
new file mode 100644
index 00000000000..8bf681d18c6
--- /dev/null
+++ b/extras/hs-test/nginx_test.go
@@ -0,0 +1,137 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ . "fd.io/hs-test/infra"
+ . "github.com/onsi/ginkgo/v2"
+)
+
+func init() {
+ RegisterNoTopoTests(NginxHttp3Test, NginxAsServerTest, NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest,
+ NginxPerfCpsInterruptModeTest, NginxPerfRpsInterruptModeTest, NginxPerfWrkInterruptModeTest)
+}
+
+func NginxHttp3Test(s *NoTopoSuite) {
+ s.SkipUnlessExtendedTestsBuilt()
+
+ query := "index.html"
+ nginxCont := s.GetContainerByName("nginx-http3")
+ nginxCont.Run()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.WaitForApp("nginx-", 5)
+ serverAddress := s.VppAddr()
+
+ defer func() { os.Remove(query) }()
+ curlCont := s.GetContainerByName("curl")
+ args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query)
+ curlCont.ExtraRunningArgs = args
+ curlCont.Run()
+ o, err := curlCont.GetOutput()
+ s.Log(o)
+ s.AssertEmpty(err)
+ s.AssertContains(o, "<http>", "<http> not found in the result!")
+}
+func NginxAsServerTest(s *NoTopoSuite) {
+ query := "return_ok"
+ finished := make(chan error, 1)
+
+ nginxCont := s.GetContainerByName("nginx")
+ nginxCont.Run()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.WaitForApp("nginx-", 5)
+
+ serverAddress := s.VppAddr()
+
+ defer func() { os.Remove(query) }()
+ go func() {
+ defer GinkgoRecover()
+ s.StartWget(finished, serverAddress, "80", query, "")
+ }()
+ s.AssertNil(<-finished)
+}
+
+func parseString(s, pattern string) string {
+ temp := strings.Split(s, "\n")
+ for _, item := range temp {
+ if strings.Contains(item, pattern) {
+ return item
+ }
+ }
+ return ""
+}
+
+func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
+ nRequests := 1000000
+ nClients := 1000
+
+ serverAddress := s.VppAddr()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+
+ nginxCont := s.GetContainerByName(SingleTopoContainerNginx)
+ nginxCont.Run()
+ vpp.WaitForApp("nginx-", 5)
+
+ if ab_or_wrk == "ab" {
+ abCont := s.GetContainerByName("ab")
+ args := fmt.Sprintf("-n %d -c %d", nRequests, nClients)
+ if mode == "rps" {
+ args += " -k"
+ } else if mode != "cps" {
+ return fmt.Errorf("invalid mode %s; expected cps/rps", mode)
+ }
+ // don't exit on socket receive errors
+ args += " -r"
+ args += " http://" + serverAddress + ":80/64B.json"
+ abCont.ExtraRunningArgs = args
+ abCont.Run()
+ o, err := abCont.GetOutput()
+ rps := parseString(o, "Requests per second:")
+ s.Log(rps)
+ s.AssertContains(err, "Finished "+fmt.Sprint(nRequests))
+ } else {
+ wrkCont := s.GetContainerByName("wrk")
+ args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s:80/64B.json", nClients,
+ serverAddress)
+ wrkCont.ExtraRunningArgs = args
+ wrkCont.Run()
+ s.Log("Please wait for 30s, test is running.")
+ o, err := wrkCont.GetOutput()
+ rps := parseString(o, "requests")
+ s.Log(rps)
+ s.Log(err)
+ s.AssertEmpty(err, "err: '%s', output: '%s'", err, o)
+ }
+ return nil
+}
+
+func NginxPerfCpsInterruptModeTest(s *NoTopoSuite) {
+ NginxPerfCpsTest(s)
+}
+
+// unstable with multiple workers
+func NginxPerfCpsTest(s *NoTopoSuite) {
+ s.SkipIfMultiWorker()
+ s.AssertNil(runNginxPerf(s, "cps", "ab"))
+}
+
+func NginxPerfRpsInterruptModeTest(s *NoTopoSuite) {
+ NginxPerfRpsTest(s)
+}
+
+func NginxPerfRpsTest(s *NoTopoSuite) {
+ s.AssertNil(runNginxPerf(s, "rps", "ab"))
+}
+
+func NginxPerfWrkInterruptModeTest(s *NoTopoSuite) {
+ NginxPerfWrkTest(s)
+}
+
+func NginxPerfWrkTest(s *NoTopoSuite) {
+ s.AssertNil(runNginxPerf(s, "", "wrk"))
+}
diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go
index ac5f94c8535..758064424a8 100644
--- a/extras/hs-test/proxy_test.go
+++ b/extras/hs-test/proxy_test.go
@@ -2,114 +2,70 @@ package main
import (
"fmt"
- "os"
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
+ . "fd.io/hs-test/infra"
)
func init() {
- registerNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
+ RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest)
+ RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest)
+ RegisterNginxProxyTests(NginxMirroringTest)
}
-func testProxyHttpTcp(s *NsSuite, proto string) error {
- var outputFile string = "test" + s.pid + ".data"
- var srcFilePid string = "httpTestFile" + s.pid
- const srcFileNoPid = "httpTestFile"
- const fileSize string = "10M"
- stopServer := make(chan struct{}, 1)
- serverRunning := make(chan struct{}, 1)
- serverNetns := s.getNetNamespaceByName("srv")
- clientNetns := s.getNetNamespaceByName("cln")
-
- // create test file
- err := exechelper.Run(fmt.Sprintf("ip netns exec %s truncate -s %s %s", serverNetns, fileSize, srcFilePid))
- s.assertNil(err, "failed to run truncate command: "+fmt.Sprint(err))
- defer func() { os.Remove(srcFilePid) }()
-
- s.log("test file created...")
-
- go func() {
- defer GinkgoRecover()
- s.startHttpServer(serverRunning, stopServer, ":666", serverNetns)
- }()
- // TODO better error handling and recovery
- <-serverRunning
-
- defer func(chan struct{}) {
- stopServer <- struct{}{}
- }(stopServer)
-
- s.log("http server started...")
-
- clientVeth := s.getInterfaceByName(clientInterface)
- c := fmt.Sprintf("ip netns exec %s wget --no-proxy --retry-connrefused"+
- " --retry-on-http-error=503 --tries=10 -O %s ", clientNetns, outputFile)
- if proto == "tls" {
- c += " --secure-protocol=TLSv1_3 --no-check-certificate https://"
- }
- c += fmt.Sprintf("%s:555/%s", clientVeth.ip4AddressString(), srcFileNoPid)
- s.log(c)
- _, err = exechelper.CombinedOutput(c)
-
- defer func() { os.Remove(outputFile) }()
-
- s.assertNil(err, "failed to run wget: '%s', cmd: %s", err, c)
- stopServer <- struct{}{}
-
- s.assertNil(assertFileSize(outputFile, srcFilePid))
- return nil
+func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) {
+ vppProxy := s.GetContainerByName(VppProxyContainerName).VppInstance
+ output := vppProxy.Vppctl(
+ "test proxy server server-uri %s://%s/%d client-uri tcp://%s/%d",
+ proto,
+ s.VppProxyAddr(),
+ proxyPort,
+ s.NginxAddr(),
+ s.NginxPort(),
+ )
+ s.Log("proxy configured: " + output)
}
-func configureVppProxy(s *NsSuite, proto string) {
- serverVeth := s.getInterfaceByName(serverInterface)
- clientVeth := s.getInterfaceByName(clientInterface)
+func VppProxyHttpGetTcpTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tcp", proxyPort)
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+ s.CurlDownloadResource(uri)
+}
- testVppProxy := s.getContainerByName("vpp").vppInstance
- output := testVppProxy.vppctl(
- "test proxy server server-uri %s://%s/555 client-uri tcp://%s/666",
- proto,
- clientVeth.ip4AddressString(),
- serverVeth.peer.ip4AddressString(),
- )
- s.log("proxy configured: " + output)
+func VppProxyHttpGetTlsTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tls", proxyPort)
+ uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+ s.CurlDownloadResource(uri)
}
-func VppProxyHttpTcpTest(s *NsSuite) {
- proto := "tcp"
- configureVppProxy(s, proto)
- err := testProxyHttpTcp(s, proto)
- s.assertNil(err, fmt.Sprint(err))
+func VppProxyHttpPutTcpTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tcp", proxyPort)
+ uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort)
+ s.CurlUploadResource(uri, CurlContainerTestFile)
}
-func VppProxyHttpTlsTest(s *NsSuite) {
- proto := "tls"
- configureVppProxy(s, proto)
- err := testProxyHttpTcp(s, proto)
- s.assertNil(err, fmt.Sprint(err))
+func VppProxyHttpPutTlsTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tls", proxyPort)
+ uri := fmt.Sprintf("https://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort)
+ s.CurlUploadResource(uri, CurlContainerTestFile)
}
-func configureEnvoyProxy(s *NsSuite) {
- envoyContainer := s.getContainerByName("envoy")
- err := envoyContainer.create()
- s.assertNil(err, "Error creating envoy container: %s", err)
+func EnvoyProxyHttpGetTcpTest(s *EnvoyProxySuite) {
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlDownloadResource(uri)
+}
- serverVeth := s.getInterfaceByName(serverInterface)
- address := struct {
- Server string
- }{
- Server: serverVeth.peer.ip4AddressString(),
- }
- envoyContainer.createConfig(
- "/etc/envoy/envoy.yaml",
- "resources/envoy/proxy.yaml",
- address,
- )
- s.assertNil(envoyContainer.start())
+func EnvoyProxyHttpPutTcpTest(s *EnvoyProxySuite) {
+ uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlUploadResource(uri, CurlContainerTestFile)
}
-func EnvoyProxyHttpTcpTest(s *NsSuite) {
- configureEnvoyProxy(s)
- err := testProxyHttpTcp(s, "tcp")
- s.assertNil(err, fmt.Sprint(err))
+// broken when CPUS > 1
+func NginxMirroringTest(s *NginxProxySuite) {
+ s.SkipIfMultiWorker()
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlDownloadResource(uri)
}
diff --git a/extras/hs-test/raw_session_test.go b/extras/hs-test/raw_session_test.go
index 5c66df0b1ce..438b7ba03a5 100644
--- a/extras/hs-test/raw_session_test.go
+++ b/extras/hs-test/raw_session_test.go
@@ -1,40 +1,42 @@
package main
+import . "fd.io/hs-test/infra"
+
func init() {
- registerVethTests(VppEchoQuicTest, VppEchoTcpTest)
+ RegisterVethTests(VppEchoQuicTest, VppEchoTcpTest)
}
func VppEchoQuicTest(s *VethsSuite) {
- s.testVppEcho("quic")
+ testVppEcho(s, "quic")
}
// TODO: udp echo currently broken in vpp
func VppEchoUdpTest(s *VethsSuite) {
- s.testVppEcho("udp")
+ testVppEcho(s, "udp")
}
func VppEchoTcpTest(s *VethsSuite) {
- s.testVppEcho("tcp")
+ testVppEcho(s, "tcp")
}
-func (s *VethsSuite) testVppEcho(proto string) {
- serverVethAddress := s.getInterfaceByName(serverInterfaceName).ip4AddressString()
+func testVppEcho(s *VethsSuite, proto string) {
+ serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
uri := proto + "://" + serverVethAddress + "/12344"
- echoSrvContainer := s.getContainerByName("server-app")
+ echoSrvContainer := s.GetContainerByName("server-app")
serverCommand := "vpp_echo server TX=RX" +
- " socket-name " + echoSrvContainer.getContainerWorkDir() + "/var/run/app_ns_sockets/default" +
+ " socket-name " + echoSrvContainer.GetContainerWorkDir() + "/var/run/app_ns_sockets/default" +
" use-app-socket-api" +
" uri " + uri
- s.log(serverCommand)
- echoSrvContainer.execServer(serverCommand)
+ s.Log(serverCommand)
+ echoSrvContainer.ExecServer(serverCommand)
- echoClnContainer := s.getContainerByName("client-app")
+ echoClnContainer := s.GetContainerByName("client-app")
clientCommand := "vpp_echo client" +
- " socket-name " + echoClnContainer.getContainerWorkDir() + "/var/run/app_ns_sockets/default" +
+ " socket-name " + echoClnContainer.GetContainerWorkDir() + "/var/run/app_ns_sockets/default" +
" use-app-socket-api uri " + uri
- s.log(clientCommand)
- o := echoClnContainer.exec(clientCommand)
- s.log(o)
+ s.Log(clientCommand)
+ o := echoClnContainer.Exec(clientCommand)
+ s.Log(o)
}
diff --git a/extras/hs-test/resources/curl/write_out_download b/extras/hs-test/resources/curl/write_out_download
new file mode 100644
index 00000000000..9fc7699c229
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_download
@@ -0,0 +1 @@
+Download size: %{size_download} bytes\nDownload speed: %{speed_download} bytes per second\nGET response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_download_connect b/extras/hs-test/resources/curl/write_out_download_connect
new file mode 100644
index 00000000000..bd7c9152a0c
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_download_connect
@@ -0,0 +1 @@
+Download size: %{size_download} bytes\nDownload speed: %{speed_download} bytes per second\nCONNECT response code: %{http_connect}\nGET response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_upload b/extras/hs-test/resources/curl/write_out_upload
new file mode 100644
index 00000000000..c2857aa6b0c
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_upload
@@ -0,0 +1 @@
+Upload size: %{size_upload} bytes\nUpload speed: %{speed_upload} bytes per second\nPUT response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_upload_connect b/extras/hs-test/resources/curl/write_out_upload_connect
new file mode 100644
index 00000000000..35021655ec0
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_upload_connect
@@ -0,0 +1 @@
+Upload size: %{size_upload} bytes\nUpload speed: %{speed_upload} bytes per second\nCONNECT response code: %{http_connect}\nPUT response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/envoy/proxy.yaml b/extras/hs-test/resources/envoy/proxy.yaml
index 77da80d934d..67eb6909ed2 100644
--- a/extras/hs-test/resources/envoy/proxy.yaml
+++ b/extras/hs-test/resources/envoy/proxy.yaml
@@ -1,17 +1,15 @@
admin:
- access_log_path: /tmp/envoy.log
address:
socket_address:
address: 0.0.0.0
port_value: 8081
static_resources:
listeners:
- # define a reverse proxy on :10001 that always uses :80 as an origin.
- address:
socket_address:
protocol: TCP
address: 0.0.0.0
- port_value: 555
+ port_value: {{.ProxyPort}}
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
@@ -30,6 +28,13 @@ static_resources:
cluster: proxy_service
http_filters:
- name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ access_log:
+ - name: envoy.access_loggers.file
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+ path: /tmp/vpp-envoy/{{.LogPrefix}}-access.log
clusters:
- name: proxy_service
connect_timeout: 0.25s
@@ -43,9 +48,8 @@ static_resources:
- endpoint:
address:
socket_address:
- # following address will be generated by Addresser during test run
- address: {{.Server}}
- port_value: 666
+ address: {{.ServerAddress}}
+ port_value: {{.ServerPort}}
bootstrap_extensions:
- name: envoy.extensions.vcl.vcl_socket_interface
typed_config:
diff --git a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
index 56debf5c290..7f6b09c6050 100644
--- a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
+++ b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
@@ -3,7 +3,7 @@ worker_processes 4;
worker_rlimit_nofile 102400;
daemon off;
-error_log /tmp/nginx/error.log;
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
events {
use epoll;
@@ -44,7 +44,8 @@ http {
}
server {
- listen 80;
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
+ listen {{.Port}};
server_name {{.Proxy}};
server_tokens off;
diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf
new file mode 100644
index 00000000000..26d58348039
--- /dev/null
+++ b/extras/hs-test/resources/nginx/nginx_server.conf
@@ -0,0 +1,43 @@
+master_process on;
+worker_rlimit_nofile 10240;
+worker_processes 2;
+daemon off;
+
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
+
+events {
+ use epoll;
+ worker_connections 10240;
+ accept_mutex off;
+ multi_accept off;
+}
+
+http {
+ keepalive_timeout 300s;
+ keepalive_requests 1000000;
+ client_body_timeout {{.Timeout}}s;
+ client_header_timeout {{.Timeout}}s;
+ send_timeout {{.Timeout}}s;
+ sendfile on;
+ server {
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
+ listen {{.Port}};
+ server_name {{.Address}};
+ root /usr/share/nginx;
+ index index.html index.htm;
+ location ~ "/upload/([0-9a-zA-Z-.]*)$" {
+ alias /usr/share/nginx/upload/$1;
+ client_body_temp_path /tmp;
+ client_max_body_size 200M;
+ dav_methods PUT;
+ create_full_put_path off;
+ dav_access all:rw;
+ }
+ location /64B {
+ return 200 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+ }
+ location / {
+ sendfile on;
+ }
+ }
+}
diff --git a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
index 4056801ea13..921eb2eefd5 100644
--- a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
+++ b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
@@ -3,6 +3,8 @@ worker_rlimit_nofile 10240;
worker_processes 2;
daemon off;
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
+
events {
use epoll;
worker_connections 10240;
@@ -15,18 +17,17 @@ http {
keepalive_requests 1000000;
sendfile on;
server {
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
listen 8091;
listen 8092;
listen 8093;
root /usr/share/nginx;
index index.html index.htm;
- location /return_ok
- {
- return 200 '';
- }
- location /64B.json
- {
+ location /64B {
return 200 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
}
+ location / {
+ sendfile on;
+ }
}
}
diff --git a/extras/hs-test/script/build_boringssl.sh b/extras/hs-test/script/build_boringssl.sh
index 441878a77ca..cca4e4a31d1 100755
--- a/extras/hs-test/script/build_boringssl.sh
+++ b/extras/hs-test/script/build_boringssl.sh
@@ -1,4 +1,4 @@
#!/bin/bash
-cd boringssl
+cd boringssl || exit 1
cmake -GNinja -B build
ninja -C build
diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh
index cc2d00b6cbd..9b11f5f0272 100755
--- a/extras/hs-test/script/build_hst.sh
+++ b/extras/hs-test/script/build_hst.sh
@@ -1,37 +1,51 @@
#!/usr/bin/env bash
-if [ $(lsb_release -is) != Ubuntu ]; then
+if [ "$(lsb_release -is)" != Ubuntu ]; then
echo "Host stack test framework is supported only on Ubuntu"
exit 1
fi
-if [ -z $(which ab) ]; then
+if [ -z "$(which ab)" ]; then
echo "Host stack test framework requires apache2-utils to be installed"
echo "It is recommended to run 'sudo make install-dep'"
exit 1
fi
-if [ -z $(which wrk) ]; then
+if [ -z "$(which wrk)" ]; then
echo "Host stack test framework requires wrk to be installed"
echo "It is recommended to run 'sudo make install-dep'"
exit 1
fi
export VPP_WS=../..
+OS_ARCH="$(uname -m)"
+DOCKER_BUILD_DIR="/scratch/docker-build"
+DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache"
+
+if [ -d "${DOCKER_BUILD_DIR}" ] ; then
+ mkdir -p "${DOCKER_CACHE_DIR}"
+ DOCKER_HST_BUILDER="hst_builder"
+ set -x
+ if ! docker buildx ls --format "{{.Name}}" | grep -q "${DOCKER_HST_BUILDER}"; then
+ docker buildx create --name=${DOCKER_HST_BUILDER} --driver=docker-container --use --bootstrap || true
+ fi
+ set -x
+ DOCKER_CACHE_ARGS="--builder=${DOCKER_HST_BUILDER} --load --cache-to type=local,dest=${DOCKER_CACHE_DIR},mode=max --cache-from type=local,src=${DOCKER_CACHE_DIR}"
+fi
if [ "$1" == "debug" ]; then
VPP_BUILD_ROOT=${VPP_WS}/build-root/build-vpp_debug-native/vpp
+elif [ "$1" == "gcov" ]; then
+ VPP_BUILD_ROOT=${VPP_WS}/build-root/build-vpp_gcov-native/vpp
else
VPP_BUILD_ROOT=${VPP_WS}/build-root/build-vpp-native/vpp
fi
echo "Taking build objects from ${VPP_BUILD_ROOT}"
-if [ -z "$UBUNTU_VERSION" ] ; then
- export UBUNTU_VERSION=$(lsb_release -rs)
-fi
+export UBUNTU_VERSION=${UBUNTU_VERSION:-"$(lsb_release -rs)"}
echo "Ubuntu version is set to ${UBUNTU_VERSION}"
-export HST_LDPRELOAD=${VPP_BUILD_ROOT}/lib/x86_64-linux-gnu/libvcl_ldpreload.so
+export HST_LDPRELOAD=${VPP_BUILD_ROOT}/lib/${OS_ARCH}-linux-gnu/libvcl_ldpreload.so
echo "HST_LDPRELOAD is set to ${HST_LDPRELOAD}"
export PATH=${VPP_BUILD_ROOT}/bin:$PATH
@@ -45,7 +59,7 @@ rm -rf vpp-data/lib/* || true
cp ${VPP_BUILD_ROOT}/bin/* ${bin}
res+=$?
-cp -r ${VPP_BUILD_ROOT}/lib/x86_64-linux-gnu/* ${lib}
+cp -r ${VPP_BUILD_ROOT}/lib/"${OS_ARCH}"-linux-gnu/* ${lib}
res+=$?
if [ $res -ne 0 ]; then
echo "Failed to copy VPP files. Is VPP built? Try running 'make build' in VPP directory."
@@ -55,25 +69,29 @@ fi
docker_build () {
tag=$1
dockername=$2
- docker build --build-arg UBUNTU_VERSION \
- --build-arg http_proxy=$HTTP_PROXY \
- --build-arg https_proxy=$HTTP_PROXY \
- --build-arg HTTP_PROXY=$HTTP_PROXY \
- --build-arg HTTPS_PROXY=$HTTP_PROXY \
- -t $tag -f docker/Dockerfile.$dockername .
+ set -ex
+ # shellcheck disable=2086
+ docker buildx build ${DOCKER_CACHE_ARGS} \
+ --build-arg UBUNTU_VERSION \
+ --build-arg http_proxy="$HTTP_PROXY" \
+ --build-arg https_proxy="$HTTP_PROXY" \
+ --build-arg HTTP_PROXY="$HTTP_PROXY" \
+ --build-arg HTTPS_PROXY="$HTTP_PROXY" \
+ -t "$tag" -f docker/Dockerfile."$dockername" .
+ set +ex
}
docker_build hs-test/vpp vpp
docker_build hs-test/nginx-ldp nginx
docker_build hs-test/nginx-server nginx-server
-docker_build hs-test/build build
+docker_build hs-test/curl curl
if [ "$HST_EXTENDED_TESTS" = true ] ; then
docker_build hs-test/nginx-http3 nginx-http3
- docker_build hs-test/curl curl
fi
# cleanup detached images
images=$(docker images --filter "dangling=true" -q --no-trunc)
if [ "$images" != "" ]; then
+ # shellcheck disable=SC2086
docker rmi $images
fi
diff --git a/extras/hs-test/script/build_nginx.sh b/extras/hs-test/script/build_nginx.sh
index 69d366aab0e..f21201c4297 100755
--- a/extras/hs-test/script/build_nginx.sh
+++ b/extras/hs-test/script/build_nginx.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-cd nginx
+cd nginx || exit 1
./auto/configure --with-debug --with-http_v3_module --with-cc-opt="-I../boringssl/include" --with-ld-opt="-L../boringssl/build/ssl -L../boringssl/build/crypto" --without-http_rewrite_module --without-http_gzip_module
make
make install
diff --git a/extras/hs-test/script/compress.sh b/extras/hs-test/script/compress.sh
index 1f0205c1efb..09db9b6720d 100644..100755
--- a/extras/hs-test/script/compress.sh
+++ b/extras/hs-test/script/compress.sh
@@ -6,12 +6,12 @@ then
if [ -n "${WORKSPACE}" ]
then
echo -n "Copying docker logs..."
- dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText' ${HS_ROOT}/summary/report.json)
+ dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText | split("/")[1]' ${HS_ROOT}/summary/report.json)
for dirName in $dirs; do
logDir=/tmp/hs-test/$dirName
if [ -d "$logDir" ]; then
mkdir -p ${WORKSPACE}/archives/summary
- cp -r $logDir ${WORKSPACE}/archives/summary/
+ rsync -a --exclude 'volumes' $logDir ${WORKSPACE}/archives/summary/
fi
done
echo "Done."
@@ -29,5 +29,9 @@ then
else
echo "Not compressing files in temporary directories from test runs."
fi
+ echo "*************************** SUMMARY ***************************"
+ cat "${HS_ROOT}/summary/failed-summary.log"
exit 1
+else
+ exit $1
fi
diff --git a/extras/hs-test/script/nginx_ldp.sh b/extras/hs-test/script/nginx_ldp.sh
index 4a22e14aaf7..416aa5499af 100755
--- a/extras/hs-test/script/nginx_ldp.sh
+++ b/extras/hs-test/script/nginx_ldp.sh
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
-LD_PRELOAD=$LDP $@ 2>&1 > /proc/1/fd/1
+# shellcheck disable=SC2068
+$1 -v && LD_PRELOAD=$LDP $@ > /proc/1/fd/1 2>&1
diff --git a/extras/hs-test/script/nginx_server_entrypoint.sh b/extras/hs-test/script/nginx_server_entrypoint.sh
new file mode 100755
index 00000000000..6581c8b74d3
--- /dev/null
+++ b/extras/hs-test/script/nginx_server_entrypoint.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nginx -c /nginx.conf
diff --git a/extras/hs-test/suite_nginx_test.go b/extras/hs-test/suite_nginx_test.go
deleted file mode 100644
index c559496e71b..00000000000
--- a/extras/hs-test/suite_nginx_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package main
-
-import (
- "reflect"
- "runtime"
- "strings"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-// These correspond to names used in yaml config
-const (
- vppProxyContainerName = "vpp-proxy"
- nginxProxyContainerName = "nginx-proxy"
- nginxServerContainerName = "nginx-server"
- mirroringClientInterfaceName = "hstcln"
- mirroringServerInterfaceName = "hstsrv"
-)
-
-var nginxTests = []func(s *NginxSuite){}
-var nginxSoloTests = []func(s *NginxSuite){}
-
-type NginxSuite struct {
- HstSuite
-}
-
-func registerNginxTests(tests ...func(s *NginxSuite)) {
- nginxTests = append(nginxTests, tests...)
-}
-func registerNginxSoloTests(tests ...func(s *NginxSuite)) {
- nginxSoloTests = append(nginxSoloTests, tests...)
-}
-
-func (s *NginxSuite) SetupSuite() {
- s.HstSuite.SetupSuite()
- s.loadNetworkTopology("2taps")
- s.loadContainerTopology("nginxProxyAndServer")
-}
-
-func (s *NginxSuite) SetupTest() {
- s.HstSuite.SetupTest()
-
- // Setup test conditions
- var sessionConfig Stanza
- sessionConfig.
- newStanza("session").
- append("enable").
- append("use-app-socket-api").close()
-
- cpus := s.AllocateCpus()
- // ... for proxy
- vppProxyContainer := s.getContainerByName(vppProxyContainerName)
- proxyVpp, _ := vppProxyContainer.newVppInstance(cpus, sessionConfig)
- s.assertNil(proxyVpp.start())
-
- clientInterface := s.getInterfaceByName(mirroringClientInterfaceName)
- s.assertNil(proxyVpp.createTap(clientInterface, 1))
-
- serverInterface := s.getInterfaceByName(mirroringServerInterfaceName)
- s.assertNil(proxyVpp.createTap(serverInterface, 2))
-
- nginxContainer := s.getTransientContainerByName(nginxProxyContainerName)
- nginxContainer.create()
-
- values := struct {
- Proxy string
- Server string
- }{
- Proxy: clientInterface.peer.ip4AddressString(),
- Server: serverInterface.ip4AddressString(),
- }
- nginxContainer.createConfig(
- "/nginx.conf",
- "./resources/nginx/nginx_proxy_mirroring.conf",
- values,
- )
- s.assertNil(nginxContainer.start())
-
- proxyVpp.waitForApp("nginx-", 5)
-}
-
-var _ = Describe("NginxSuite", Ordered, ContinueOnFailure, func() {
- var s NginxSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
- for _, test := range nginxTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
-
-var _ = Describe("NginxSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s NginxSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range nginxSoloTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, Label("SOLO"), func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
diff --git a/extras/hs-test/suite_no_topo_test.go b/extras/hs-test/suite_no_topo_test.go
deleted file mode 100644
index 625dca9f3cf..00000000000
--- a/extras/hs-test/suite_no_topo_test.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package main
-
-import (
- "reflect"
- "runtime"
- "strings"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-const (
- singleTopoContainerVpp = "vpp"
- singleTopoContainerNginx = "nginx"
- tapInterfaceName = "htaphost"
-)
-
-var noTopoTests = []func(s *NoTopoSuite){}
-var noTopoSoloTests = []func(s *NoTopoSuite){}
-
-type NoTopoSuite struct {
- HstSuite
-}
-
-func registerNoTopoTests(tests ...func(s *NoTopoSuite)) {
- noTopoTests = append(noTopoTests, tests...)
-}
-func registerNoTopoSoloTests(tests ...func(s *NoTopoSuite)) {
- noTopoSoloTests = append(noTopoSoloTests, tests...)
-}
-
-func (s *NoTopoSuite) SetupSuite() {
- s.HstSuite.SetupSuite()
- s.loadNetworkTopology("tap")
- s.loadContainerTopology("single")
-}
-
-func (s *NoTopoSuite) SetupTest() {
- s.HstSuite.SetupTest()
-
- // Setup test conditions
- var sessionConfig Stanza
- sessionConfig.
- newStanza("session").
- append("enable").
- append("use-app-socket-api").close()
-
- cpus := s.AllocateCpus()
- container := s.getContainerByName(singleTopoContainerVpp)
- vpp, _ := container.newVppInstance(cpus, sessionConfig)
- s.assertNil(vpp.start())
-
- tapInterface := s.getInterfaceByName(tapInterfaceName)
-
- s.assertNil(vpp.createTap(tapInterface), "failed to create tap interface")
-}
-
-var _ = Describe("NoTopoSuite", Ordered, ContinueOnFailure, func() {
- var s NoTopoSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range noTopoTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
-
-var _ = Describe("NoTopoSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s NoTopoSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range noTopoSoloTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, Label("SOLO"), func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
diff --git a/extras/hs-test/suite_ns_test.go b/extras/hs-test/suite_ns_test.go
deleted file mode 100644
index 85b90911c2f..00000000000
--- a/extras/hs-test/suite_ns_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package main
-
-import (
- "fmt"
- "reflect"
- "runtime"
- "strings"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-// These correspond to names used in yaml config
-const (
- clientInterface = "hclnvpp"
- serverInterface = "hsrvvpp"
-)
-
-var nsTests = []func(s *NsSuite){}
-var nsSoloTests = []func(s *NsSuite){}
-
-type NsSuite struct {
- HstSuite
-}
-
-func registerNsTests(tests ...func(s *NsSuite)) {
- nsTests = append(nsTests, tests...)
-}
-func registerNsSoloTests(tests ...func(s *NsSuite)) {
- nsSoloTests = append(nsSoloTests, tests...)
-}
-
-func (s *NsSuite) SetupSuite() {
- s.HstSuite.SetupSuite()
- s.configureNetworkTopology("ns")
- s.loadContainerTopology("ns")
-}
-
-func (s *NsSuite) SetupTest() {
- s.HstSuite.SetupTest()
-
- // Setup test conditions
- var sessionConfig Stanza
- sessionConfig.
- newStanza("session").
- append("enable").
- append("use-app-socket-api").
- append("evt_qs_memfd_seg").
- append("event-queue-length 100000").close()
-
- cpus := s.AllocateCpus()
- container := s.getContainerByName("vpp")
- vpp, _ := container.newVppInstance(cpus, sessionConfig)
- s.assertNil(vpp.start())
-
- idx, err := vpp.createAfPacket(s.getInterfaceByName(serverInterface))
- s.assertNil(err, fmt.Sprint(err))
- s.assertNotEqual(0, idx)
-
- idx, err = vpp.createAfPacket(s.getInterfaceByName(clientInterface))
- s.assertNil(err, fmt.Sprint(err))
- s.assertNotEqual(0, idx)
-
- container.exec("chmod 777 -R %s", container.getContainerWorkDir())
-}
-
-var _ = Describe("NsSuite", Ordered, ContinueOnFailure, func() {
- var s NsSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range nsTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
-
-var _ = Describe("NsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s NsSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range nsSoloTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, Label("SOLO"), func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
diff --git a/extras/hs-test/suite_tap_test.go b/extras/hs-test/suite_tap_test.go
deleted file mode 100644
index ebf0f9b3cbc..00000000000
--- a/extras/hs-test/suite_tap_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package main
-
-import (
- "reflect"
- "runtime"
- "strings"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-type TapSuite struct {
- HstSuite
-}
-
-var tapTests = []func(s *TapSuite){}
-var tapSoloTests = []func(s *TapSuite){}
-
-func registerTapTests(tests ...func(s *TapSuite)) {
- tapTests = append(tapTests, tests...)
-}
-func registerTapSoloTests(tests ...func(s *TapSuite)) {
- tapSoloTests = append(tapSoloTests, tests...)
-}
-
-func (s *TapSuite) SetupSuite() {
- time.Sleep(1 * time.Second)
- s.HstSuite.SetupSuite()
- s.configureNetworkTopology("tap")
-}
-
-var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() {
- var s TapSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range tapTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
-
-var _ = Describe("TapSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s TapSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- for _, test := range tapSoloTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, Label("SOLO"), func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
diff --git a/extras/hs-test/suite_veth_test.go b/extras/hs-test/suite_veth_test.go
deleted file mode 100644
index d47bf8c52a9..00000000000
--- a/extras/hs-test/suite_veth_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package main
-
-import (
- "fmt"
- "reflect"
- "runtime"
- "strings"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
-)
-
-// These correspond to names used in yaml config
-const (
- serverInterfaceName = "srv"
- clientInterfaceName = "cln"
-)
-
-var vethTests = []func(s *VethsSuite){}
-var vethSoloTests = []func(s *VethsSuite){}
-
-type VethsSuite struct {
- HstSuite
-}
-
-func registerVethTests(tests ...func(s *VethsSuite)) {
- vethTests = append(vethTests, tests...)
-}
-func registerSoloVethTests(tests ...func(s *VethsSuite)) {
- vethSoloTests = append(vethSoloTests, tests...)
-}
-
-func (s *VethsSuite) SetupSuite() {
- time.Sleep(1 * time.Second)
- s.HstSuite.SetupSuite()
- s.configureNetworkTopology("2peerVeth")
- s.loadContainerTopology("2peerVeth")
-}
-
-func (s *VethsSuite) SetupTest() {
- s.HstSuite.SetupTest()
-
- // Setup test conditions
- var sessionConfig Stanza
- sessionConfig.
- newStanza("session").
- append("enable").
- append("use-app-socket-api").close()
-
- // ... For server
- serverContainer := s.getContainerByName("server-vpp")
-
- cpus := s.AllocateCpus()
- serverVpp, err := serverContainer.newVppInstance(cpus, sessionConfig)
- s.assertNotNil(serverVpp, fmt.Sprint(err))
-
- s.setupServerVpp()
-
- // ... For client
- clientContainer := s.getContainerByName("client-vpp")
-
- cpus = s.AllocateCpus()
- clientVpp, err := clientContainer.newVppInstance(cpus, sessionConfig)
- s.assertNotNil(clientVpp, fmt.Sprint(err))
-
- s.setupClientVpp()
-}
-
-func (s *VethsSuite) setupServerVpp() {
- serverVpp := s.getContainerByName("server-vpp").vppInstance
- s.assertNil(serverVpp.start())
-
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- idx, err := serverVpp.createAfPacket(serverVeth)
- s.assertNil(err, fmt.Sprint(err))
- s.assertNotEqual(0, idx)
-}
-
-func (s *VethsSuite) setupClientVpp() {
- clientVpp := s.getContainerByName("client-vpp").vppInstance
- s.assertNil(clientVpp.start())
-
- clientVeth := s.getInterfaceByName(clientInterfaceName)
- idx, err := clientVpp.createAfPacket(clientVeth)
- s.assertNil(err, fmt.Sprint(err))
- s.assertNotEqual(0, idx)
-}
-
-var _ = Describe("VethsSuite", Ordered, ContinueOnFailure, func() {
- var s VethsSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
-
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- // https://onsi.github.io/ginkgo/#dynamically-generating-specs
- for _, test := range vethTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
-
-var _ = Describe("VethsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s VethsSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- BeforeEach(func() {
- s.SetupTest()
- })
- AfterAll(func() {
- s.TearDownSuite()
- })
- AfterEach(func() {
- s.TearDownTest()
- })
-
- // https://onsi.github.io/ginkgo/#dynamically-generating-specs
- for _, test := range vethSoloTests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := strings.Split(funcValue.Name(), ".")[2]
- It(testName, Label("SOLO"), func(ctx SpecContext) {
- s.log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*5))
- }
-})
diff --git a/extras/hs-test/test b/extras/hs-test/test
deleted file mode 100644
index 398e2b39edb..00000000000
--- a/extras/hs-test/test
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env bash
-
-source vars
-
-args=
-single_test=0
-persist_set=0
-unconfigure_set=0
-debug_set=0
-vppsrc=
-ginkgo_args=
-parallel=
-
-for i in "$@"
-do
-case "${i}" in
- --persist=*)
- persist="${i#*=}"
- if [ $persist = "true" ]; then
- args="$args -persist"
- persist_set=1
- fi
- ;;
- --debug=*)
- debug="${i#*=}"
- if [ $debug = "true" ]; then
- args="$args -debug"
- debug_set=1
- fi
- ;;
- --verbose=*)
- verbose="${i#*=}"
- if [ $verbose = "true" ]; then
- args="$args -verbose"
- fi
- ;;
- --unconfigure=*)
- unconfigure="${i#*=}"
- if [ $unconfigure = "true" ]; then
- args="$args -unconfigure"
- unconfigure_set=1
- fi
- ;;
- --cpus=*)
- args="$args -cpus ${i#*=}"
- ;;
- --vppsrc=*)
- args="$args -vppsrc ${i#*=}"
- ;;
- --test=*)
- tc_name="${i#*=}"
- if [ $tc_name != "all" ]; then
- single_test=1
- ginkgo_args="$ginkgo_args --focus $tc_name -vv"
- args="$args -verbose"
- else
- ginkgo_args="$ginkgo_args -v"
- fi
- ;;
- --parallel=*)
- ginkgo_args="$ginkgo_args -procs=${i#*=}"
- ;;
- --repeat=*)
- ginkgo_args="$ginkgo_args --repeat=${i#*=}"
- ;;
-esac
-done
-
-if [ $single_test -eq 0 ] && [ $persist_set -eq 1 ]; then
- echo "persist flag is not supported while running all tests!"
- exit 1
-fi
-
-if [ $unconfigure_set -eq 1 ] && [ $single_test -eq 0 ]; then
- echo "a single test has to be specified when unconfigure is set"
- exit 1
-fi
-
-if [ $persist_set -eq 1 ] && [ $unconfigure_set -eq 1 ]; then
- echo "setting persist flag and unconfigure flag is not allowed"
- exit 1
-fi
-
-if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then
- echo "VPP debug flag is not supperted while running all tests!"
- exit 1
-fi
-
-mkdir -p summary
-
-sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --no-color --trace --json-report=summary/report.json $ginkgo_args -- $args
-
-jq -r '.[0] | .SpecReports[] | select((.State == "failed") or (.State == "timedout") or (.State == "panicked")) | select(.Failure != null) | "TestName: \(.LeafNodeText)\nSuite:\n\(.Failure.Location.FileName)\nMessage:\n\(.Failure.Message)\n Full Stack Trace:\n\(.Failure.Location.FullStackTrace)\n"' summary/report.json > summary/failed-summary.log \
- && echo "Summary generated -> summary/failed-summary.log" \ No newline at end of file
diff --git a/extras/hs-test/tools/http_server/http_server.go b/extras/hs-test/tools/http_server/http_server.go
index d2ab3851643..a65aa7556e7 100644
--- a/extras/hs-test/tools/http_server/http_server.go
+++ b/extras/hs-test/tools/http_server/http_server.go
@@ -8,13 +8,13 @@ import (
)
func main() {
- if len(os.Args) < 3 {
+ if len(os.Args) < 4 {
fmt.Println("arg expected")
os.Exit(1)
}
http.HandleFunc("/httpTestFile", func(w http.ResponseWriter, r *http.Request) {
- file, _ := os.Open("httpTestFile" + os.Args[2])
+ file, _ := os.Open(os.Args[3] + "httpTestFile" + os.Args[2])
defer file.Close()
io.Copy(w, file)
})
diff --git a/extras/hs-test/topo-containers/2containers.yaml b/extras/hs-test/topo-containers/2containers.yaml
new file mode 100644
index 00000000000..1217c27d3ed
--- /dev/null
+++ b/extras/hs-test/topo-containers/2containers.yaml
@@ -0,0 +1,4 @@
+---
+containers:
+ - name: "server"
+ - name: "client" \ No newline at end of file
diff --git a/extras/hs-test/topo-containers/2peerVethLdp.yaml b/extras/hs-test/topo-containers/2peerVethLdp.yaml
new file mode 100644
index 00000000000..bd6e63a945d
--- /dev/null
+++ b/extras/hs-test/topo-containers/2peerVethLdp.yaml
@@ -0,0 +1,18 @@
+---
+volumes:
+ - volume: &server-vol
+ host-dir: "$HST_VOLUME_DIR/server-share"
+ container-dir: "/tmp/server-share"
+ is-default-work-dir: true
+ - volume: &client-vol
+ host-dir: "$HST_VOLUME_DIR/client-share"
+ container-dir: "/tmp/client-share"
+ is-default-work-dir: true
+
+containers:
+ - name: "server-vpp"
+ volumes:
+ - <<: *server-vol
+ - name: "client-vpp"
+ volumes:
+ - <<: *client-vol
diff --git a/extras/hs-test/topo-containers/envoyProxy.yaml b/extras/hs-test/topo-containers/envoyProxy.yaml
new file mode 100644
index 00000000000..92dd9b93c47
--- /dev/null
+++ b/extras/hs-test/topo-containers/envoyProxy.yaml
@@ -0,0 +1,40 @@
+---
+volumes:
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
+ - name: "envoy-vcl"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp-envoy"
+ is-default-work-dir: true
+ - host-dir: "$HST_DIR/resources/envoy"
+ container-dir: "/tmp"
+ vars:
+ - name: "ENVOY_UID"
+ value: "0"
+ - name: "VCL_CONFIG"
+ value: "/tmp/vcl.conf"
+ image: "envoyproxy/envoy-contrib:v1.30-latest"
+ extra-args: "--log-format [%t][%l][%g:%#]%_ --concurrency 2 -c /etc/envoy/envoy.yaml"
+ is-optional: true
+ - name: "nginx-server"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/topo-containers/nginxProxy.yaml b/extras/hs-test/topo-containers/nginxProxy.yaml
new file mode 100644
index 00000000000..d9ddc14590f
--- /dev/null
+++ b/extras/hs-test/topo-containers/nginxProxy.yaml
@@ -0,0 +1,32 @@
+---
+volumes:
+ - volume: &shared-vol-nginx-proxy
+ host-dir: "$HST_VOLUME_DIR/shared-vol-nginx-proxy"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
+ - name: "nginx-proxy"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-ldp"
+ is-optional: true
+ - name: "nginx-server"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/topo-containers/ns.yaml b/extras/hs-test/topo-containers/ns.yaml
index 2298ad232c2..3de5af59569 100644
--- a/extras/hs-test/topo-containers/ns.yaml
+++ b/extras/hs-test/topo-containers/ns.yaml
@@ -22,6 +22,6 @@ containers:
value: "0"
- name: "VCL_CONFIG"
value: "/tmp/vcl.conf"
- image: "envoyproxy/envoy-contrib:v1.21-latest"
+ image: "envoyproxy/envoy-contrib:v1.30-latest"
extra-args: "--concurrency 2 -c /etc/envoy/envoy.yaml"
is-optional: true
diff --git a/extras/hs-test/topo-containers/singleCpuPinning.yaml b/extras/hs-test/topo-containers/singleCpuPinning.yaml
new file mode 100644
index 00000000000..6e673aa85bf
--- /dev/null
+++ b/extras/hs-test/topo-containers/singleCpuPinning.yaml
@@ -0,0 +1,11 @@
+---
+volumes:
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
diff --git a/extras/hs-test/topo-containers/nginxProxyAndServer.yaml b/extras/hs-test/topo-containers/vppProxy.yaml
index cc6b780bafc..a1f24bbc187 100644
--- a/extras/hs-test/topo-containers/nginxProxyAndServer.yaml
+++ b/extras/hs-test/topo-containers/vppProxy.yaml
@@ -1,20 +1,25 @@
---
volumes:
- - volume: &shared-vol-proxy
- host-dir: "$HST_VOLUME_DIR/shared-vol-proxy"
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
containers:
- name: "vpp-proxy"
volumes:
- - <<: *shared-vol-proxy
+ - <<: *shared-vol
container-dir: "/tmp/vpp"
is-default-work-dir: true
- - name: "nginx-proxy"
+ - name: "nginx-server"
volumes:
- - <<: *shared-vol-proxy
+ - <<: *shared-vol
container-dir: "/tmp/nginx"
is-default-work-dir: true
- image: "hs-test/nginx-ldp"
- is-optional: true
- - name: "nginx-server"
image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/utils.go b/extras/hs-test/utils.go
deleted file mode 100644
index 304dd4c241b..00000000000
--- a/extras/hs-test/utils.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "strings"
-)
-
-const networkTopologyDir string = "topo-network/"
-const containerTopologyDir string = "topo-containers/"
-
-type Stanza struct {
- content string
- pad int
-}
-
-type ActionResult struct {
- Err error
- Desc string
- ErrOutput string
- StdOutput string
-}
-
-type JsonResult struct {
- Code int
- Desc string
- ErrOutput string
- StdOutput string
-}
-
-func assertFileSize(f1, f2 string) error {
- fi1, err := os.Stat(f1)
- if err != nil {
- return err
- }
-
- fi2, err1 := os.Stat(f2)
- if err1 != nil {
- return err1
- }
-
- if fi1.Size() != fi2.Size() {
- return fmt.Errorf("file sizes differ (%d vs %d)", fi1.Size(), fi2.Size())
- }
- return nil
-}
-
-func (c *Stanza) newStanza(name string) *Stanza {
- c.append("\n" + name + " {")
- c.pad += 2
- return c
-}
-
-func (c *Stanza) append(name string) *Stanza {
- c.content += strings.Repeat(" ", c.pad)
- c.content += name + "\n"
- return c
-}
-
-func (c *Stanza) close() *Stanza {
- c.content += "}\n"
- c.pad -= 2
- return c
-}
-
-func (s *Stanza) toString() string {
- return s.content
-}
-
-func (s *Stanza) saveToFile(fileName string) error {
- fo, err := os.Create(fileName)
- if err != nil {
- return err
- }
- defer fo.Close()
-
- _, err = io.Copy(fo, strings.NewReader(s.content))
- return err
-}
diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go
index fdcd60ad503..81da0c533b9 100644
--- a/extras/hs-test/vcl_test.go
+++ b/extras/hs-test/vcl_test.go
@@ -3,11 +3,13 @@ package main
import (
"fmt"
"time"
+
+ . "fd.io/hs-test/infra"
)
func init() {
- registerVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
- XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest)
+ RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
+ XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclRetryAttachTest)
}
func getVclConfig(c *Container, ns_id_optional ...string) string {
@@ -16,141 +18,145 @@ func getVclConfig(c *Container, ns_id_optional ...string) string {
if len(ns_id_optional) > 0 {
ns_id = ns_id_optional[0]
}
- s.newStanza("vcl").
- append(fmt.Sprintf("app-socket-api %[1]s/var/run/app_ns_sockets/%[2]s", c.getContainerWorkDir(), ns_id)).
- append("app-scope-global").
- append("app-scope-local").
- append("use-mq-eventfd")
+ s.NewStanza("vcl").
+ Append(fmt.Sprintf("app-socket-api %[1]s/var/run/app_ns_sockets/%[2]s", c.GetContainerWorkDir(), ns_id)).
+ Append("app-scope-global").
+ Append("app-scope-local").
+ Append("use-mq-eventfd")
if len(ns_id_optional) > 0 {
- s.append(fmt.Sprintf("namespace-id %[1]s", ns_id)).
- append(fmt.Sprintf("namespace-secret %[1]s", ns_id))
+ s.Append(fmt.Sprintf("namespace-id %[1]s", ns_id)).
+ Append(fmt.Sprintf("namespace-secret %[1]s", ns_id))
}
- return s.close().toString()
+ return s.Close().ToString()
}
func XEchoVclClientUdpTest(s *VethsSuite) {
- s.testXEchoVclClient("udp")
+ testXEchoVclClient(s, "udp")
}
func XEchoVclClientTcpTest(s *VethsSuite) {
- s.testXEchoVclClient("tcp")
+ testXEchoVclClient(s, "tcp")
}
-func (s *VethsSuite) testXEchoVclClient(proto string) {
+func testXEchoVclClient(s *VethsSuite, proto string) {
port := "12345"
- serverVpp := s.getContainerByName("server-vpp").vppInstance
+ serverVpp := s.GetContainerByName("server-vpp").VppInstance
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- serverVpp.vppctl("test echo server uri %s://%s/%s fifo-size 64k", proto, serverVeth.ip4AddressString(), port)
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ serverVpp.Vppctl("test echo server uri %s://%s/%s fifo-size 64k", proto, serverVeth.Ip4AddressString(), port)
- echoClnContainer := s.getTransientContainerByName("client-app")
- echoClnContainer.createFile("/vcl.conf", getVclConfig(echoClnContainer))
+ echoClnContainer := s.GetTransientContainerByName("client-app")
+ echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer))
- testClientCommand := "vcl_test_client -N 100 -p " + proto + " " + serverVeth.ip4AddressString() + " " + port
- s.log(testClientCommand)
- echoClnContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
- o := echoClnContainer.exec(testClientCommand)
- s.log(o)
- s.assertContains(o, "CLIENT RESULTS")
+ testClientCommand := "vcl_test_client -N 100 -p " + proto + " " + serverVeth.Ip4AddressString() + " " + port
+ s.Log(testClientCommand)
+ echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf")
+ o := echoClnContainer.Exec(testClientCommand)
+ s.Log(o)
+ s.AssertContains(o, "CLIENT RESULTS")
}
func XEchoVclServerUdpTest(s *VethsSuite) {
- s.testXEchoVclServer("udp")
+ testXEchoVclServer(s, "udp")
}
func XEchoVclServerTcpTest(s *VethsSuite) {
- s.testXEchoVclServer("tcp")
+ testXEchoVclServer(s, "tcp")
}
-func (s *VethsSuite) testXEchoVclServer(proto string) {
+func testXEchoVclServer(s *VethsSuite, proto string) {
port := "12345"
- srvVppCont := s.getContainerByName("server-vpp")
- srvAppCont := s.getContainerByName("server-app")
+ srvVppCont := s.GetContainerByName("server-vpp")
+ srvAppCont := s.GetContainerByName("server-app")
- srvAppCont.createFile("/vcl.conf", getVclConfig(srvVppCont))
- srvAppCont.addEnvVar("VCL_CONFIG", "/vcl.conf")
+ srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont))
+ srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf")
vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s %s", proto, port)
- srvAppCont.execServer(vclSrvCmd)
+ srvAppCont.ExecServer(vclSrvCmd)
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- serverVethAddress := serverVeth.ip4AddressString()
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ serverVethAddress := serverVeth.Ip4AddressString()
- clientVpp := s.getContainerByName("client-vpp").vppInstance
- o := clientVpp.vppctl("test echo client uri %s://%s/%s fifo-size 64k verbose mbytes 2", proto, serverVethAddress, port)
- s.log(o)
- s.assertContains(o, "Test finished at")
+ clientVpp := s.GetContainerByName("client-vpp").VppInstance
+ o := clientVpp.Vppctl("test echo client uri %s://%s/%s fifo-size 64k verbose mbytes 2", proto, serverVethAddress, port)
+ s.Log(o)
+ s.AssertContains(o, "Test finished at")
}
-func (s *VethsSuite) testVclEcho(proto string) {
+func testVclEcho(s *VethsSuite, proto string) {
port := "12345"
- srvVppCont := s.getContainerByName("server-vpp")
- srvAppCont := s.getContainerByName("server-app")
+ srvVppCont := s.GetContainerByName("server-vpp")
+ srvAppCont := s.GetContainerByName("server-app")
- srvAppCont.createFile("/vcl.conf", getVclConfig(srvVppCont))
- srvAppCont.addEnvVar("VCL_CONFIG", "/vcl.conf")
- srvAppCont.execServer("vcl_test_server " + port)
+ srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont))
+ srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf")
+ srvAppCont.ExecServer("vcl_test_server -p " + proto + " " + port)
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- serverVethAddress := serverVeth.ip4AddressString()
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ serverVethAddress := serverVeth.Ip4AddressString()
- echoClnContainer := s.getTransientContainerByName("client-app")
- echoClnContainer.createFile("/vcl.conf", getVclConfig(echoClnContainer))
+ echoClnContainer := s.GetTransientContainerByName("client-app")
+ echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer))
testClientCommand := "vcl_test_client -p " + proto + " " + serverVethAddress + " " + port
- echoClnContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
- o := echoClnContainer.exec(testClientCommand)
- s.log(o)
+ echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf")
+ o := echoClnContainer.Exec(testClientCommand)
+ s.Log(o)
}
func VclEchoTcpTest(s *VethsSuite) {
- s.testVclEcho("tcp")
+ testVclEcho(s, "tcp")
}
func VclEchoUdpTest(s *VethsSuite) {
- s.testVclEcho("udp")
+ testVclEcho(s, "udp")
+}
+
+func VclHttpPostTest(s *VethsSuite) {
+ testVclEcho(s, "http")
}
func VclRetryAttachTest(s *VethsSuite) {
- s.testRetryAttach("tcp")
+ testRetryAttach(s, "tcp")
}
-func (s *VethsSuite) testRetryAttach(proto string) {
- srvVppContainer := s.getTransientContainerByName("server-vpp")
+func testRetryAttach(s *VethsSuite, proto string) {
+ srvVppContainer := s.GetTransientContainerByName("server-vpp")
- echoSrvContainer := s.getContainerByName("server-app")
+ echoSrvContainer := s.GetContainerByName("server-app")
- echoSrvContainer.createFile("/vcl.conf", getVclConfig(echoSrvContainer))
+ echoSrvContainer.CreateFile("/vcl.conf", getVclConfig(echoSrvContainer))
- echoSrvContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
- echoSrvContainer.execServer("vcl_test_server -p " + proto + " 12346")
+ echoSrvContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf")
+ echoSrvContainer.ExecServer("vcl_test_server -p " + proto + " 12346")
- s.log("This whole test case can take around 3 minutes to run. Please be patient.")
- s.log("... Running first echo client test, before disconnect.")
+ s.Log("This whole test case can take around 3 minutes to run. Please be patient.")
+ s.Log("... Running first echo client test, before disconnect.")
- serverVeth := s.getInterfaceByName(serverInterfaceName)
- serverVethAddress := serverVeth.ip4AddressString()
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ serverVethAddress := serverVeth.Ip4AddressString()
- echoClnContainer := s.getTransientContainerByName("client-app")
- echoClnContainer.createFile("/vcl.conf", getVclConfig(echoClnContainer))
+ echoClnContainer := s.GetTransientContainerByName("client-app")
+ echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer))
testClientCommand := "vcl_test_client -U -p " + proto + " " + serverVethAddress + " 12346"
- echoClnContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
- o := echoClnContainer.exec(testClientCommand)
- s.log(o)
- s.log("... First test ended. Stopping VPP server now.")
+ echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf")
+ o := echoClnContainer.Exec(testClientCommand)
+ s.Log(o)
+ s.Log("... First test ended. Stopping VPP server now.")
// Stop server-vpp-instance, start it again and then run vcl-test-client once more
- srvVppContainer.vppInstance.disconnect()
+ srvVppContainer.VppInstance.Disconnect()
stopVppCommand := "/bin/bash -c 'ps -C vpp_main -o pid= | xargs kill -9'"
- srvVppContainer.exec(stopVppCommand)
+ srvVppContainer.Exec(stopVppCommand)
- s.setupServerVpp()
+ s.SetupServerVpp()
- s.log("... VPP server is starting again, so waiting for a bit.")
+ s.Log("... VPP server is starting again, so waiting for a bit.")
time.Sleep(30 * time.Second) // Wait a moment for the re-attachment to happen
- s.log("... Running second echo client test, after disconnect and re-attachment.")
- o = echoClnContainer.exec(testClientCommand)
- s.log(o)
- s.log("Done.")
+ s.Log("... Running second echo client test, after disconnect and re-attachment.")
+ o = echoClnContainer.Exec(testClientCommand)
+ s.Log(o)
+ s.Log("Done.")
}
diff --git a/extras/hs-test/vppinstance.go b/extras/hs-test/vppinstance.go
deleted file mode 100644
index 3276c2dd9ae..00000000000
--- a/extras/hs-test/vppinstance.go
+++ /dev/null
@@ -1,473 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "io"
- "os"
- "os/exec"
- "os/signal"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
- "github.com/sirupsen/logrus"
-
- "go.fd.io/govpp"
- "go.fd.io/govpp/api"
- "go.fd.io/govpp/binapi/af_packet"
- interfaces "go.fd.io/govpp/binapi/interface"
- "go.fd.io/govpp/binapi/interface_types"
- "go.fd.io/govpp/binapi/session"
- "go.fd.io/govpp/binapi/tapv2"
- "go.fd.io/govpp/core"
-)
-
-const vppConfigTemplate = `unix {
- nodaemon
- log %[1]s%[4]s
- full-coredump
- cli-listen %[1]s%[2]s
- runtime-dir %[1]s/var/run
- gid vpp
-}
-
-api-trace {
- on
-}
-
-api-segment {
- gid vpp
-}
-
-socksvr {
- socket-name %[1]s%[3]s
-}
-
-statseg {
- socket-name %[1]s/var/run/vpp/stats.sock
-}
-
-plugins {
- plugin default { disable }
-
- plugin unittest_plugin.so { enable }
- plugin quic_plugin.so { enable }
- plugin af_packet_plugin.so { enable }
- plugin hs_apps_plugin.so { enable }
- plugin http_plugin.so { enable }
- plugin http_static_plugin.so { enable }
- plugin prom_plugin.so { enable }
- plugin tlsopenssl_plugin.so { enable }
- plugin ping_plugin.so { enable }
- plugin nsim_plugin.so { enable }
-}
-
-logging {
- default-log-level debug
- default-syslog-log-level debug
-}
-
-`
-
-const (
- defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
- defaultApiSocketFilePath = "/var/run/vpp/api.sock"
- defaultLogFilePath = "/var/log/vpp/vpp.log"
-)
-
-type VppInstance struct {
- container *Container
- additionalConfig []Stanza
- connection *core.Connection
- apiStream api.Stream
- cpus []int
-}
-
-func (vpp *VppInstance) getSuite() *HstSuite {
- return vpp.container.suite
-}
-
-func (vpp *VppInstance) getCliSocket() string {
- return fmt.Sprintf("%s%s", vpp.container.getContainerWorkDir(), defaultCliSocketFilePath)
-}
-
-func (vpp *VppInstance) getRunDir() string {
- return vpp.container.getContainerWorkDir() + "/var/run/vpp"
-}
-
-func (vpp *VppInstance) getLogDir() string {
- return vpp.container.getContainerWorkDir() + "/var/log/vpp"
-}
-
-func (vpp *VppInstance) getEtcDir() string {
- return vpp.container.getContainerWorkDir() + "/etc/vpp"
-}
-
-func (vpp *VppInstance) start() error {
- // Replace default logger in govpp with our own
- govppLogger := logrus.New()
- govppLogger.SetOutput(io.MultiWriter(vpp.getSuite().logger.Writer(), GinkgoWriter))
- core.SetLogger(govppLogger)
- // Create folders
- containerWorkDir := vpp.container.getContainerWorkDir()
-
- vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
- vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
- vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
-
- // Create startup.conf inside the container
- configContent := fmt.Sprintf(
- vppConfigTemplate,
- containerWorkDir,
- defaultCliSocketFilePath,
- defaultApiSocketFilePath,
- defaultLogFilePath,
- )
- configContent += vpp.generateCpuConfig()
- for _, c := range vpp.additionalConfig {
- configContent += c.toString()
- }
- startupFileName := vpp.getEtcDir() + "/startup.conf"
- vpp.container.createFile(startupFileName, configContent)
-
- // create wrapper script for vppctl with proper CLI socket path
- cliContent := "#!/usr/bin/bash\nvppctl -s " + vpp.getRunDir() + "/cli.sock"
- vppcliFileName := "/usr/bin/vppcli"
- vpp.container.createFile(vppcliFileName, cliContent)
- vpp.container.exec("chmod 0755 " + vppcliFileName)
-
- vpp.getSuite().log("starting vpp")
- if *isVppDebug {
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, syscall.SIGQUIT)
- cont := make(chan bool, 1)
- go func() {
- <-sig
- cont <- true
- }()
-
- vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
- fmt.Println("run following command in different terminal:")
- fmt.Println("docker exec -it " + vpp.container.name + " gdb -ex \"attach $(docker exec " + vpp.container.name + " pidof vpp)\"")
- fmt.Println("Afterwards press CTRL+\\ to continue")
- <-cont
- fmt.Println("continuing...")
- } else {
- // Start VPP
- vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"")
- }
-
- vpp.getSuite().log("connecting to vpp")
- // Connect to VPP and store the connection
- sockAddress := vpp.container.getHostWorkDir() + defaultApiSocketFilePath
- conn, connEv, err := govpp.AsyncConnect(
- sockAddress,
- core.DefaultMaxReconnectAttempts,
- core.DefaultReconnectInterval)
- if err != nil {
- vpp.getSuite().log("async connect error: " + fmt.Sprint(err))
- return err
- }
- vpp.connection = conn
-
- // ... wait for Connected event
- e := <-connEv
- if e.State != core.Connected {
- vpp.getSuite().log("connecting to VPP failed: " + fmt.Sprint(e.Error))
- }
-
- ch, err := conn.NewStream(
- context.Background(),
- core.WithRequestSize(50),
- core.WithReplySize(50),
- core.WithReplyTimeout(time.Second*10))
- if err != nil {
- vpp.getSuite().log("creating stream failed: " + fmt.Sprint(err))
- return err
- }
- vpp.apiStream = ch
-
- return nil
-}
-
-func (vpp *VppInstance) vppctl(command string, arguments ...any) string {
- vppCliCommand := fmt.Sprintf(command, arguments...)
- containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
- vpp.container.name, vpp.getCliSocket(), vppCliCommand)
- vpp.getSuite().log(containerExecCommand)
- output, err := exechelper.CombinedOutput(containerExecCommand)
- vpp.getSuite().assertNil(err)
-
- return string(output)
-}
-
-func (vpp *VppInstance) GetSessionStat(stat string) int {
- o := vpp.vppctl("show session stats")
- vpp.getSuite().log(o)
- for _, line := range strings.Split(o, "\n") {
- if strings.Contains(line, stat) {
- tokens := strings.Split(strings.TrimSpace(line), " ")
- val, err := strconv.Atoi(tokens[0])
- if err != nil {
- Fail("failed to parse stat value %s" + fmt.Sprint(err))
- return 0
- }
- return val
- }
- }
- return 0
-}
-
-func (vpp *VppInstance) waitForApp(appName string, timeout int) {
- vpp.getSuite().log("waiting for app " + appName)
- for i := 0; i < timeout; i++ {
- o := vpp.vppctl("show app")
- if strings.Contains(o, appName) {
- return
- }
- time.Sleep(1 * time.Second)
- }
- vpp.getSuite().assertNil(1, "Timeout while waiting for app '%s'", appName)
-}
-
-func (vpp *VppInstance) createAfPacket(
- veth *NetInterface,
-) (interface_types.InterfaceIndex, error) {
- createReq := &af_packet.AfPacketCreateV3{
- Mode: 1,
- UseRandomHwAddr: true,
- HostIfName: veth.Name(),
- Flags: af_packet.AfPacketFlags(11),
- }
- if veth.hwAddress != (MacAddress{}) {
- createReq.UseRandomHwAddr = false
- createReq.HwAddr = veth.hwAddress
- }
-
- vpp.getSuite().log("create af-packet interface " + veth.Name())
- if err := vpp.apiStream.SendMsg(createReq); err != nil {
- vpp.getSuite().hstFail()
- return 0, err
- }
- replymsg, err := vpp.apiStream.RecvMsg()
- if err != nil {
- return 0, err
- }
- reply := replymsg.(*af_packet.AfPacketCreateV3Reply)
- err = api.RetvalToVPPApiError(reply.Retval)
- if err != nil {
- return 0, err
- }
-
- veth.index = reply.SwIfIndex
-
- // Set to up
- upReq := &interfaces.SwInterfaceSetFlags{
- SwIfIndex: veth.index,
- Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
- }
-
- vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
- if err := vpp.apiStream.SendMsg(upReq); err != nil {
- return 0, err
- }
- replymsg, err = vpp.apiStream.RecvMsg()
- if err != nil {
- return 0, err
- }
- reply2 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
- if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
- return 0, err
- }
-
- // Add address
- if veth.addressWithPrefix() == (AddressWithPrefix{}) {
- var err error
- var ip4Address string
- if ip4Address, err = veth.ip4AddrAllocator.NewIp4InterfaceAddress(veth.peer.networkNumber); err == nil {
- veth.ip4Address = ip4Address
- } else {
- return 0, err
- }
- }
- addressReq := &interfaces.SwInterfaceAddDelAddress{
- IsAdd: true,
- SwIfIndex: veth.index,
- Prefix: veth.addressWithPrefix(),
- }
-
- vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
- if err := vpp.apiStream.SendMsg(addressReq); err != nil {
- return 0, err
- }
- replymsg, err = vpp.apiStream.RecvMsg()
- if err != nil {
- return 0, err
- }
- reply3 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
- err = api.RetvalToVPPApiError(reply3.Retval)
- if err != nil {
- return 0, err
- }
-
- return veth.index, nil
-}
-
-func (vpp *VppInstance) addAppNamespace(
- secret uint64,
- ifx interface_types.InterfaceIndex,
- namespaceId string,
-) error {
- req := &session.AppNamespaceAddDelV4{
- IsAdd: true,
- Secret: secret,
- SwIfIndex: ifx,
- NamespaceID: namespaceId,
- SockName: defaultApiSocketFilePath,
- }
-
- vpp.getSuite().log("add app namespace " + namespaceId)
- if err := vpp.apiStream.SendMsg(req); err != nil {
- return err
- }
- replymsg, err := vpp.apiStream.RecvMsg()
- if err != nil {
- return err
- }
- reply := replymsg.(*session.AppNamespaceAddDelV4Reply)
- if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
- return err
- }
-
- sessionReq := &session.SessionEnableDisable{
- IsEnable: true,
- }
-
- vpp.getSuite().log("enable app namespace " + namespaceId)
- if err := vpp.apiStream.SendMsg(sessionReq); err != nil {
- return err
- }
- replymsg, err = vpp.apiStream.RecvMsg()
- if err != nil {
- return err
- }
- reply2 := replymsg.(*session.SessionEnableDisableReply)
- if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
- return err
- }
-
- return nil
-}
-
-func (vpp *VppInstance) createTap(
- tap *NetInterface,
- tapId ...uint32,
-) error {
- var id uint32 = 1
- if len(tapId) > 0 {
- id = tapId[0]
- }
- createTapReq := &tapv2.TapCreateV3{
- ID: id,
- HostIfNameSet: true,
- HostIfName: tap.Name(),
- HostIP4PrefixSet: true,
- HostIP4Prefix: tap.ip4AddressWithPrefix(),
- }
-
- vpp.getSuite().log("create tap interface " + tap.Name())
- // Create tap interface
- if err := vpp.apiStream.SendMsg(createTapReq); err != nil {
- return err
- }
- replymsg, err := vpp.apiStream.RecvMsg()
- if err != nil {
- return err
- }
- reply := replymsg.(*tapv2.TapCreateV3Reply)
- if err = api.RetvalToVPPApiError(reply.Retval); err != nil {
- return err
- }
-
- // Add address
- addAddressReq := &interfaces.SwInterfaceAddDelAddress{
- IsAdd: true,
- SwIfIndex: reply.SwIfIndex,
- Prefix: tap.peer.addressWithPrefix(),
- }
-
- vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
- if err := vpp.apiStream.SendMsg(addAddressReq); err != nil {
- return err
- }
- replymsg, err = vpp.apiStream.RecvMsg()
- if err != nil {
- return err
- }
- reply2 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply)
- if err = api.RetvalToVPPApiError(reply2.Retval); err != nil {
- return err
- }
-
- // Set interface to up
- upReq := &interfaces.SwInterfaceSetFlags{
- SwIfIndex: reply.SwIfIndex,
- Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
- }
-
- vpp.getSuite().log("set tap interface " + tap.Name() + " up")
- if err := vpp.apiStream.SendMsg(upReq); err != nil {
- return err
- }
- replymsg, err = vpp.apiStream.RecvMsg()
- if err != nil {
- return err
- }
- reply3 := replymsg.(*interfaces.SwInterfaceSetFlagsReply)
- if err = api.RetvalToVPPApiError(reply3.Retval); err != nil {
- return err
- }
-
- return nil
-}
-
-func (vpp *VppInstance) saveLogs() {
- logTarget := vpp.container.getLogDirPath() + "vppinstance-" + vpp.container.name + ".log"
- logSource := vpp.container.getHostWorkDir() + defaultLogFilePath
- cmd := exec.Command("cp", logSource, logTarget)
- vpp.getSuite().log(cmd.String())
- cmd.Run()
-}
-
-func (vpp *VppInstance) disconnect() {
- vpp.connection.Disconnect()
- vpp.apiStream.Close()
-}
-
-func (vpp *VppInstance) generateCpuConfig() string {
- var c Stanza
- var s string
- if len(vpp.cpus) < 1 {
- return ""
- }
- c.newStanza("cpu").
- append(fmt.Sprintf("main-core %d", vpp.cpus[0]))
- vpp.getSuite().log(fmt.Sprintf("main-core %d", vpp.cpus[0]))
- workers := vpp.cpus[1:]
-
- if len(workers) > 0 {
- for i := 0; i < len(workers); i++ {
- if i != 0 {
- s = s + ", "
- }
- s = s + fmt.Sprintf("%d", workers[i])
- }
- c.append(fmt.Sprintf("corelist-workers %s", s))
- vpp.getSuite().log("corelist-workers " + s)
- }
- return c.close().toString()
-}
diff --git a/extras/scripts/checkstyle.sh b/extras/scripts/checkstyle.sh
index 2b884f5f08b..304beff51d0 100755
--- a/extras/scripts/checkstyle.sh
+++ b/extras/scripts/checkstyle.sh
@@ -19,7 +19,7 @@ CLANG_FORMAT_VER_REGEX='([0-9]+)\.[0-9]+\.[0-9]+'
CLANG_FORMAT_DIFF="/usr/share/clang/clang-format-diff.py"
# TODO: Remove clang-format-${CLANG_FORMAT_VER} from 'make install-deps' when
-# CLANG_FORMAT_VER default value is upgraded
+# CLANG_FORMAT_VER default value is upgraded to 15 after Ubuntu-20.04 is deprecated
CLANG_FORMAT_VER=${CLANG_FORMAT_VER:-11}
GIT_DIFF_ARGS="-U0 --no-color --relative HEAD~1"
GIT_DIFF_EXCLUDE_LIST=(
@@ -30,16 +30,20 @@ CLANG_FORMAT_DIFF_ARGS="-style file -p1"
SUFFIX="-${CLANG_FORMAT_VER}"
# Attempt to find clang-format to confirm Clang version.
-if command -v clang-format${SUFFIX} &> /dev/null;
+if command -v "clang-format${SUFFIX}" &> /dev/null;
then
- CLANG_FORMAT=clang-format${SUFFIX}
-elif command -v clang-format &> /dev/null;
-then
- CLANG_FORMAT=clang-format
+ CLANG_FORMAT="clang-format${SUFFIX}"
+else
+ echo "*******************************************************************"
+ echo "* CHECKSTYLE FAILED"
+ echo "* Could not locate the clang-format${SUFFIX} script"
+ echo "* Run 'make install-deps' to install it"
+ echo "*******************************************************************"
+ exit 1
fi
CLANG_FORMAT_VERSION=$(${CLANG_FORMAT} --version)
-echo $CLANG_FORMAT_VERSION
+echo "$CLANG_FORMAT_VERSION"
# Confirm that Clang is the expected version.
if [[ ! $CLANG_FORMAT_VERSION =~ $CLANG_FORMAT_VER_REGEX ]];
@@ -75,6 +79,7 @@ then
echo "*******************************************************************"
echo "* CHECKSTYLE FAILED"
echo "* Could not locate the clang-format-diff script"
+ echo "* Run 'make install-deps' to install it"
echo "*******************************************************************"
exit 1
fi
diff --git a/extras/strongswan/vpp_sswan/Makefile b/extras/strongswan/vpp_sswan/Makefile
index 254b90b3b09..85fb506f346 100644
--- a/extras/strongswan/vpp_sswan/Makefile
+++ b/extras/strongswan/vpp_sswan/Makefile
@@ -76,8 +76,8 @@ install-swan:
echo "SSWAN not downloaded, please run "make" or "make pull-swan" first." ; \
exit 1 ; \
fi
- cd ${SWANDIR} && make -j$(nproc)
- cd ${SWANDIR} && sudo make install
+ cd ${SWANDIR} && $(MAKE) -j$(nproc)
+ cd ${SWANDIR} && sudo $(MAKE) install
# check if VPP is installed
ifneq ($(shell test "$(shell ldconfig -p | grep vppinfra.so | awk 'NR==1{print $$1;}')" && echo "yes"), yes)
diff --git a/extras/vpp_if_stats/apimock.go b/extras/vpp_if_stats/apimock.go
index 77363344090..25e76ed1d31 100755
--- a/extras/vpp_if_stats/apimock.go
+++ b/extras/vpp_if_stats/apimock.go
@@ -5,10 +5,11 @@
package main
import (
- api "git.fd.io/govpp.git/api"
- gomock "github.com/golang/mock/gomock"
reflect "reflect"
time "time"
+
+ api "git.fd.io/govpp.git/api"
+ gomock "github.com/golang/mock/gomock"
)
// MockChannel is a mock of Channel interface
diff --git a/extras/vpp_if_stats/json_structs.go b/extras/vpp_if_stats/json_structs.go
index a42b6d815b2..8e73337418e 100755
--- a/extras/vpp_if_stats/json_structs.go
+++ b/extras/vpp_if_stats/json_structs.go
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+
"git.fd.io/govpp.git/examples/bin_api/vpe"
)
diff --git a/extras/vpp_if_stats/statsmock.go b/extras/vpp_if_stats/statsmock.go
index 72528f53d9a..172176fa744 100755
--- a/extras/vpp_if_stats/statsmock.go
+++ b/extras/vpp_if_stats/statsmock.go
@@ -5,9 +5,10 @@
package main
import (
+ reflect "reflect"
+
adapter "git.fd.io/govpp.git/adapter"
gomock "github.com/golang/mock/gomock"
- reflect "reflect"
)
// MockStatsAPI is a mock of StatsAPI interface
diff --git a/extras/vpp_if_stats/vpp_if_stats.go b/extras/vpp_if_stats/vpp_if_stats.go
index 90c1700430c..751bbe5d5a9 100644
--- a/extras/vpp_if_stats/vpp_if_stats.go
+++ b/extras/vpp_if_stats/vpp_if_stats.go
@@ -3,6 +3,8 @@ package main
import (
"flag"
"fmt"
+ "log"
+
"git.fd.io/govpp.git"
"git.fd.io/govpp.git/adapter"
"git.fd.io/govpp.git/adapter/vppapiclient"
@@ -10,7 +12,6 @@ import (
"git.fd.io/govpp.git/core"
"git.fd.io/govpp.git/examples/bin_api/interfaces"
"git.fd.io/govpp.git/examples/bin_api/vpe"
- "log"
)
//////////////////////////////////////
diff --git a/extras/vpp_if_stats/vpp_if_stats_test.go b/extras/vpp_if_stats/vpp_if_stats_test.go
index 3c22ce85d75..53c990c4158 100644
--- a/extras/vpp_if_stats/vpp_if_stats_test.go
+++ b/extras/vpp_if_stats/vpp_if_stats_test.go
@@ -1,15 +1,16 @@
package main
import (
+ "math/rand"
+ "testing"
+ "time"
+
"git.fd.io/govpp.git/adapter"
"git.fd.io/govpp.git/api"
"git.fd.io/govpp.git/examples/bin_api/interfaces"
"git.fd.io/govpp.git/examples/bin_api/vpe"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
- "math/rand"
- "testing"
- "time"
)
var (
diff --git a/extras/vpp_stats_fs/stats_fs.go b/extras/vpp_stats_fs/stats_fs.go
index 80c15096234..7b0f1ec193f 100644
--- a/extras/vpp_stats_fs/stats_fs.go
+++ b/extras/vpp_stats_fs/stats_fs.go
@@ -132,7 +132,7 @@ func getCounterContent(index uint32, client *statsclient.StatsClient) (content s
return content, fs.OK
}
-//The dirNode structure represents directories
+// The dirNode structure represents directories
type dirNode struct {
fs.Inode
client *statsclient.StatsClient
@@ -175,7 +175,7 @@ func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
return status
}
-//The statNode structure represents counters
+// The statNode structure represents counters
type statNode struct {
fs.Inode
client *statsclient.StatsClient
@@ -192,7 +192,7 @@ func (fh *statNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.Attr
return 0
}
-//When a file is opened, the correpsonding counter value is dumped and a file handle is created
+// When a file is opened, the correpsonding counter value is dumped and a file handle is created
func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
content, status := getCounterContent(sn.index, sn.client)
if status == syscall.ENOENT {
@@ -220,7 +220,7 @@ func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadRe
return fuse.ReadResultData(fh.data[off:end]), fs.OK
}
-//NewStatsFileSystem creates the fs for the stat segment.
+// NewStatsFileSystem creates the fs for the stat segment.
func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
return &dirNode{client: sc}, nil
}