aboutsummaryrefslogtreecommitdiffstats
path: root/extras/hs-test
diff options
context:
space:
mode:
Diffstat (limited to 'extras/hs-test')
-rw-r--r--extras/hs-test/Makefile134
-rw-r--r--extras/hs-test/README.rst182
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx2
-rw-r--r--extras/hs-test/docker/Dockerfile.vpp3
-rw-r--r--extras/hs-test/echo_test.go55
-rw-r--r--extras/hs-test/framework_test.go25
-rw-r--r--extras/hs-test/go.mod2
-rw-r--r--[-rwxr-xr-x]extras/hs-test/hs_test.sh (renamed from extras/hs-test/test)26
-rw-r--r--extras/hs-test/hst_suite.go503
-rw-r--r--extras/hs-test/http_test.go644
-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.go (renamed from extras/hs-test/container.go)201
-rw-r--r--extras/hs-test/infra/cpu.go (renamed from extras/hs-test/cpu.go)32
-rw-r--r--extras/hs-test/infra/hst_suite.go545
-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_nginx.go144
-rw-r--r--extras/hs-test/infra/suite_no_topo.go119
-rw-r--r--extras/hs-test/infra/suite_ns.go127
-rw-r--r--extras/hs-test/infra/suite_tap.go88
-rw-r--r--extras/hs-test/infra/suite_veth.go153
-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.go119
-rw-r--r--extras/hs-test/infra/vppinstance.go501
-rw-r--r--extras/hs-test/ldp_test.go82
-rw-r--r--extras/hs-test/linux_iperf_test.go22
-rw-r--r--extras/hs-test/mirroring_test.go24
-rw-r--r--extras/hs-test/nginx_test.go152
-rw-r--r--extras/hs-test/proxy_test.go72
-rw-r--r--extras/hs-test/raw_session_test.go32
-rwxr-xr-xextras/hs-test/script/build_boringssl.sh2
-rwxr-xr-xextras/hs-test/script/build_hst.sh49
-rwxr-xr-xextras/hs-test/script/build_nginx.sh2
-rwxr-xr-x[-rw-r--r--]extras/hs-test/script/compress.sh55
-rwxr-xr-xextras/hs-test/script/nginx_ldp.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/tools/http_server/http_server.go4
-rw-r--r--extras/hs-test/utils.go80
-rw-r--r--extras/hs-test/vcl_test.go155
-rw-r--r--extras/hs-test/vppinstance.go412
43 files changed, 3145 insertions, 2285 deletions
diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile
index 6ee89bc2770..596acb1b57d 100644
--- a/extras/hs-test/Makefile
+++ b/extras/hs-test/Makefile
@@ -1,3 +1,9 @@
+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
@@ -15,6 +21,10 @@ ifeq ($(TEST),)
TEST=all
endif
+ifeq ($(TEST-HS),)
+TEST-HS=all
+endif
+
ifeq ($(DEBUG),)
DEBUG=false
endif
@@ -43,69 +53,89 @@ 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 " 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
@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
- -bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @# '-' ignores the exit status, it is set in compress.sh
+ @# necessary so gmake won't skip executing the bash script
+ @-bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
--vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT)
- $(call jq-summary)
@bash ./script/compress.sh
.PHONY: test-debug
test-debug: .deps.ok .build_debug.ok
- @bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @# '-' ignores the exit status, it is set in compress.sh
+ @# necessary so gmake won't skip executing the bash script
+ @-bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
- --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT)
+ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true
+ @bash ./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)
+ @$(MAKE) -C ../.. test-cov-post HS_TEST=1
+ @bash ./script/compress.sh
.PHONY: build-go
build-go:
@@ -117,6 +147,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
@@ -124,7 +160,7 @@ 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:
@@ -140,11 +176,49 @@ install-deps:
fi
@touch .deps.ok
-.PHONY: fixstyle
-fixstyle:
- @gofmt -w .
+.PHONY: checkstyle-go
+checkstyle-go:
+ @output=$$(gofmt -d $${WS_ROOT}); \
+ if [ -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:
+ @echo "Modified files:"
+ @gofmt -w -l $(WS_ROOT)
@go mod tidy
-
-# splitting this into multiple lines breaks the command
-jq-summary = @jq -r '.[0] | .SpecReports[] | select(.State == "failed") | select(.Failure != null) | "TestName: \(.LeafNodeText)\nMessage:\n\(.Failure.Message)\n Full Stack Trace:\n\(.Failure.Location.FullStackTrace)\n"' summary/report.json > summary/failed-summary.log \
- && echo "Summary generated -> failed-summary.log"
+ @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..7841211e3ab 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))
})
@@ -284,36 +323,3 @@ or a new version incompatibility issue occurs.
.. _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/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.vpp b/extras/hs-test/docker/Dockerfile.vpp
index ace83c593c6..f87ee30c332 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 \
&& 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 0515b5e0411..6b4739a5457 100644
--- a/extras/hs-test/echo_test.go
+++ b/extras/hs-test/echo_test.go
@@ -1,49 +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) {
- serverVpp := s.getContainerByName("server-vpp").vppInstance
+ s.SkipIfMultiWorker()
+ 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: timeout", 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..a086f75a5fc 100644
--- a/extras/hs-test/framework_test.go
+++ b/extras/hs-test/framework_test.go
@@ -1,13 +1,38 @@
package main
import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
"testing"
+ "time"
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
+func getTestFilename() string {
+ _, filename, _, _ := runtime.Caller(2)
+ return filepath.Base(filename)
+}
+
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
+ }
+
+ // 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 0f3854d2148..3be9ba20a86 100644
--- a/extras/hs-test/go.mod
+++ b/extras/hs-test/go.mod
@@ -26,7 +26,7 @@ require (
github.com/onsi/ginkgo/v2 v2.16.0
github.com/onsi/gomega v1.32.0
github.com/pkg/errors v0.9.1 // indirect
- github.com/sirupsen/logrus v1.9.3 // indirect
+ github.com/sirupsen/logrus v1.9.3
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
diff --git a/extras/hs-test/test b/extras/hs-test/hs_test.sh
index fd17feb2c50..107fc686176 100755..100644
--- a/extras/hs-test/test
+++ b/extras/hs-test/hs_test.sh
@@ -7,36 +7,41 @@ single_test=0
persist_set=0
unconfigure_set=0
debug_set=0
-vppsrc=
+debug_build=
ginkgo_args=
-parallel=
for i in "$@"
do
case "${i}" in
--persist=*)
persist="${i#*=}"
- if [ $persist = "true" ]; then
+ if [ "$persist" = "true" ]; then
args="$args -persist"
persist_set=1
fi
;;
--debug=*)
debug="${i#*=}"
- if [ $debug = "true" ]; then
+ 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
+ if [ "$verbose" = "true" ]; then
args="$args -verbose"
fi
;;
--unconfigure=*)
unconfigure="${i#*=}"
- if [ $unconfigure = "true" ]; then
+ if [ "$unconfigure" = "true" ]; then
args="$args -unconfigure"
unconfigure_set=1
fi
@@ -49,7 +54,7 @@ case "${i}" in
;;
--test=*)
tc_name="${i#*=}"
- if [ $tc_name != "all" ]; then
+ if [ "$tc_name" != "all" ]; then
single_test=1
ginkgo_args="$ginkgo_args --focus $tc_name -vv"
args="$args -verbose"
@@ -82,10 +87,13 @@ if [ $persist_set -eq 1 ] && [ $unconfigure_set -eq 1 ]; then
fi
if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then
- echo "VPP debug flag is not supperted while running all tests!"
+ echo "VPP debug flag is not supported while running all tests!"
exit 1
fi
mkdir -p summary
-
+# shellcheck disable=SC2086
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.FailureNodeLocation.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"
diff --git a/extras/hs-test/hst_suite.go b/extras/hs-test/hst_suite.go
deleted file mode 100644
index 725fee73f24..00000000000
--- a/extras/hs-test/hst_suite.go
+++ /dev/null
@@ -1,503 +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
- 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.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
- }
- for _, c := range s.cpuContexts {
- c.Release()
- }
- 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.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 {
- fmt.Printf("An error occured while obtaining '%s' container logs: %s\n", container.name, 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 {
- fmt.Println("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 fe12f5a2f65..a5694bfb54b 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -1,193 +1,543 @@
package main
import (
+ "bytes"
"fmt"
- "os"
- "strings"
+ "github.com/onsi/gomega/gmeasure"
+ "io"
+ "net/http"
"time"
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
- registerNsTests(HttpTpsTest)
- registerVethTests(HttpCliTest)
- registerNoTopoTests(NginxHttp3Test, NginxAsServerTest,
- NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest, HeaderServerTest)
- registerNoTopoSoloTests(HttpStaticPromTest)
+ RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest)
+ RegisterNoTopoTests(HeaderServerTest,
+ HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest,
+ HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest,
+ HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest,
+ HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
+ HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
+ HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
+ HttpHeadersTest)
+ RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest)
}
-func HttpTpsTest(s *NsSuite) {
- iface := s.getInterfaceByName(clientInterface)
- client_ip := iface.ip4AddressString()
- port := "8080"
- finished := make(chan error, 1)
- clientNetns := s.getNetNamespaceByName("cln")
+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.AssertEqual(200, resp.StatusCode)
+ _, err = io.ReadAll(resp.Body)
+ duration := time.Since(t)
+ experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
+}
- container := s.getContainerByName("vpp")
+func HttpTpsInterruptModeTest(s *NoTopoSuite) {
+ HttpTpsTest(s)
+}
- // configure vpp in the container
- container.vppInstance.vppctl("http tps uri tcp://0.0.0.0/8080")
+func HttpTpsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ url := "http://" + serverAddress + ":8080/test_file_10M"
- 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))
+ vpp.Vppctl("http tps uri tcp://0.0.0.0/8080")
+
+ s.RunBenchmark("HTTP tps 10M", 10, 0, httpDownloadBenchmark, url)
}
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!")
}
-func NginxHttp3Test(s *NoTopoSuite) {
- s.SkipUnlessExtendedTestsBuilt()
+func HttpCliConnectErrorTest(s *VethsSuite) {
+ clientContainer := s.GetContainerByName("client-vpp")
- query := "index.html"
- nginxCont := s.getContainerByName("nginx-http3")
- s.assertNil(nginxCont.run())
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
- vpp := s.getContainerByName("vpp").vppInstance
- vpp.waitForApp("nginx-", 5)
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
+ uri := "http://" + serverVeth.Ip4AddressString() + "/80"
+
+ o := clientContainer.VppInstance.Vppctl("http cli client" +
+ " uri " + uri + " query /show/vlib/graph")
- 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!")
+ s.Log(o)
+ s.AssertContains(o, "failed to connect")
}
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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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, "")
+ s.StartWget(finished, serverAddress, "80", query, "")
}()
err := <-finished
- s.assertNil(err)
+ s.AssertNil(err)
}
-func HeaderServerTest(s *NoTopoSuite) {
- query := "show/version"
- vpp := s.getContainerByName("vpp").vppInstance
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
- vpp.vppctl("http cli server")
-
- curlCont := s.getContainerByName("curl")
- args := fmt.Sprintf("curl -i -s http://%s:80/%s", serverAddress, query)
- curlCont.extraRunningArgs = args
- o, err := curlCont.combinedOutput()
- s.assertNil(err, fmt.Sprint(err))
- s.log(o)
- s.assertContains(o, "Server: http_cli_server")
-}
-
-func NginxAsServerTest(s *NoTopoSuite) {
- query := "return_ok"
- finished := make(chan error, 1)
+func HttpStaticPathTraversalTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ vpp.Container.Exec("mkdir -p " + "/tmp/secret_folder")
+ vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.AssertEqual(404, resp.StatusCode)
+}
- nginxCont := s.getContainerByName("nginx")
- s.assertNil(nginxCont.run())
+func HttpStaticMovedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath + "/tmp.aaa")
+ vpp.Container.CreateFile(wwwRootPath+"/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 " + 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))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertEqual(301, resp.StatusCode)
+ s.AssertNotEqual("", resp.Header.Get("Location"))
+}
- vpp := s.getContainerByName("vpp").vppInstance
- vpp.waitForApp("nginx-", 5)
+func HttpStaticNotFoundTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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/notfound.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertEqual(404, resp.StatusCode)
+}
- serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString()
+func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ vpp.Vppctl("http cli server")
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ 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"))
+}
- 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.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 NginxPerfCpsTest(s *NoTopoSuite) {
- s.assertNil(runNginxPerf(s, "cps", "ab"))
-}
-
-func NginxPerfRpsTest(s *NoTopoSuite) {
- s.assertNil(runNginxPerf(s, "rps", "ab"))
-}
-
-func NginxPerfWrkTest(s *NoTopoSuite) {
- s.assertNil(runNginxPerf(s, "", "wrk"))
+func HttpCliBadRequestTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ vpp.Vppctl("http cli server")
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertEqual(400, resp.StatusCode)
+}
+
+func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertEqual(200, resp.StatusCode)
+ 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")
+}
+
+func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.AssertEqual(200, resp.StatusCode)
+ 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")
+}
+
+func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+
+ 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.AssertEqual(200, resp.StatusCode)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "interface_list")
+ s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+}
+
+func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+
+ 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.AssertEqual(200, resp.StatusCode)
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Name())
+}
+
+func validatePostInterfaceStats(s *NoTopoSuite, data string) {
+ s.AssertContains(data, "interface_stats")
+ s.AssertContains(data, s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertNotContains(data, "error")
+ s.AssertNotContains(data, "local0")
+}
+
+func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ body := []byte(s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+
+ 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.AssertEqual(200, resp.StatusCode)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ validatePostInterfaceStats(s, string(data))
+}
+
+func HttpStaticMacTimeTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ s.Log(vpp.Vppctl("mactime enable-disable " + s.GetInterfaceByName(TapInterfaceName).Peer.Name()))
+
+ 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.AssertEqual(200, resp.StatusCode)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data), "mactime")
+ s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Ip4AddressString())
+ s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
+}
+
+func HttpInvalidRequestLineTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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 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 HttpInvalidTargetSyntaxTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ ifName := s.GetInterfaceByName(TapInterfaceName).Peer.Name()
+
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.AssertEqual(501, resp.StatusCode)
+}
+
+func HttpVersionNotSupportedTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.AssertEqual(200, resp.StatusCode)
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(string(data))
+ s.AssertNotContains(string(data), "unknown input")
+ s.AssertContains(string(data), "Compiler")
+}
+
+func HttpHeadersTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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 HttpInvalidHeadersTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ 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.AssertEqual("http_cli_server", resp.Header.Get("Server"))
}
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/container.go b/extras/hs-test/infra/container.go
index 45f41d26a87..1dd82809f8a 100644
--- a/extras/hs-test/container.go
+++ b/extras/hs-test/infra/container.go
@@ -1,4 +1,4 @@
-package main
+package hst
import (
"fmt"
@@ -22,21 +22,22 @@ var (
)
type Volume struct {
- hostDir string
- containerDir string
- isDefaultWorkDir bool
+ 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
+ Suite *HstSuite
+ IsOptional bool
+ RunDetached bool
+ Name string
+ Image string
+ ExtraRunningArgs string
+ Volumes map[string]Volume
+ EnvVars map[string]string
+ VppInstance *VppInstance
+ AllocatedCpus []int
}
func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
@@ -47,37 +48,37 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error
}
var container = new(Container)
- container.volumes = make(map[string]Volume)
- container.envVars = make(map[string]string)
- container.name = containerName
- container.suite = suite
+ 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)
+ if Image, ok := yamlInput["image"]; ok {
+ container.Image = Image.(string)
} else {
- container.image = "hs-test/vpp"
+ container.Image = "hs-test/vpp"
}
if args, ok := yamlInput["extra-args"]; ok {
- container.extraRunningArgs = args.(string)
+ container.ExtraRunningArgs = args.(string)
} else {
- container.extraRunningArgs = ""
+ container.ExtraRunningArgs = ""
}
if isOptional, ok := yamlInput["is-optional"]; ok {
- container.isOptional = isOptional.(bool)
+ container.IsOptional = isOptional.(bool)
} else {
- container.isOptional = false
+ container.IsOptional = false
}
if runDetached, ok := yamlInput["run-detached"]; ok {
- container.runDetached = runDetached.(bool)
+ container.RunDetached = runDetached.(bool)
} else {
- container.runDetached = true
+ container.RunDetached = true
}
if _, ok := yamlInput["volumes"]; ok {
- workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + volumeDir
+ workingVolumeDir := logDir + suite.GetCurrentTestName() + volumeDir
workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
for _, volu := range yamlInput["volumes"].([]interface{}) {
@@ -99,15 +100,15 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error
envVarMap := envVar.(ContainerConfig)
name := envVarMap["name"].(string)
value := envVarMap["value"].(string)
- container.addEnvVar(name, value)
+ container.AddEnvVar(name, value)
}
}
return container, nil
}
func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
- for _, v := range c.volumes {
- if v.isDefaultWorkDir {
+ for _, v := range c.Volumes {
+ if v.IsDefaultWorkDir {
res = v
exists = true
return
@@ -116,29 +117,31 @@ func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
return
}
-func (c *Container) getHostWorkDir() (res string) {
+func (c *Container) GetHostWorkDir() (res string) {
if v, ok := c.getWorkDirVolume(); ok {
- res = v.hostDir
+ res = v.HostDir
}
return
}
-func (c *Container) getContainerWorkDir() (res string) {
+func (c *Container) GetContainerWorkDir() (res string) {
if v, ok := c.getWorkDirVolume(); ok {
- res = v.containerDir
+ res = v.ContainerDir
}
return
}
func (c *Container) getContainerArguments() string {
- args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host --rm"
+ args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host"
+ c.allocateCpus()
+ args += fmt.Sprintf(" --cpuset-cpus=\"%d-%d\"", c.AllocatedCpus[0], c.AllocatedCpus[len(c.AllocatedCpus)-1])
args += c.getVolumesAsCliOption()
args += c.getEnvVarsAsCliOption()
- if *vppSourceFileDir != "" {
- args += fmt.Sprintf(" -v %s:%s", *vppSourceFileDir, *vppSourceFileDir)
+ if *VppSourceFileDir != "" {
+ args += fmt.Sprintf(" -v %s:%s", *VppSourceFileDir, *VppSourceFileDir)
}
- args += " --name " + c.name + " " + c.image
- args += " " + c.extraRunningArgs
+ args += " --name " + c.Name + " " + c.Image
+ args += " " + c.ExtraRunningArgs
return args
}
@@ -154,34 +157,41 @@ func (c *Container) runWithRetry(cmd string) error {
return fmt.Errorf("failed to run container command")
}
-func (c *Container) create() error {
+func (c *Container) Create() error {
cmd := "docker create " + c.getContainerArguments()
- c.suite.log(cmd)
+ c.Suite.Log(cmd)
return exechelper.Run(cmd)
}
-func (c *Container) start() error {
- cmd := "docker start " + c.name
- c.suite.log(cmd)
+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)
+}
+
+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 == "" {
+ if c.Name == "" {
return "", fmt.Errorf("run container failed: name is blank")
}
cmd := "docker run "
- if c.runDetached {
+ if c.RunDetached {
cmd += " -d"
}
+
cmd += " " + c.getContainerArguments()
- c.suite.log(cmd)
+ c.Suite.Log(cmd)
return cmd, nil
}
-func (c *Container) combinedOutput() (string, error) {
+func (c *Container) CombinedOutput() (string, error) {
cmd, err := c.prepareCommand()
if err != nil {
return "", err
@@ -191,7 +201,7 @@ func (c *Container) combinedOutput() (string, error) {
return string(byteOutput), err
}
-func (c *Container) run() error {
+func (c *Container) Run() error {
cmd, err := c.prepareCommand()
if err != nil {
return err
@@ -201,35 +211,35 @@ func (c *Container) run() error {
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
+ 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)
+ 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) AddEnvVar(name string, value string) {
+ c.EnvVars[name] = value
}
func (c *Container) getEnvVarsAsCliOption() string {
cliOption := ""
- if len(c.envVars) == 0 {
+ if len(c.EnvVars) == 0 {
return cliOption
}
- for name, value := range c.envVars {
+ for name, value := range c.EnvVars {
cliOption += fmt.Sprintf(" -e %s=%s", name, value)
}
return cliOption
@@ -237,20 +247,20 @@ func (c *Container) getEnvVarsAsCliOption() string {
func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) {
vpp := new(VppInstance)
- vpp.container = c
- vpp.cpus = cpus
- vpp.additionalConfig = append(vpp.additionalConfig, additionalConfigs...)
- c.vppInstance = vpp
+ vpp.Container = c
+ vpp.Cpus = cpus
+ 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)
+ 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)
+func (c *Container) CreateFile(destFileName string, content string) error {
+ f, err := os.CreateTemp("/tmp", "hst-config"+c.Suite.Ppid)
if err != nil {
return err
}
@@ -270,29 +280,29 @@ func (c *Container) createFile(destFileName string, content string) error {
* 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) {
+func (c *Container) ExecServer(command string, arguments ...any) {
serverCommand := fmt.Sprintf(command, arguments...)
containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
- " " + c.name + " " + serverCommand
+ " " + c.Name + " " + serverCommand
GinkgoHelper()
- c.suite.log(containerExecCommand)
- c.suite.assertNil(exechelper.Run(containerExecCommand))
+ c.Suite.Log(containerExecCommand)
+ c.Suite.AssertNil(exechelper.Run(containerExecCommand))
}
-func (c *Container) exec(command string, arguments ...any) string {
+func (c *Container) Exec(command string, arguments ...any) string {
cliCommand := fmt.Sprintf(command, arguments...)
containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
- " " + c.name + " " + cliCommand
+ " " + c.Name + " " + cliCommand
GinkgoHelper()
- c.suite.log(containerExecCommand)
+ c.Suite.Log(containerExecCommand)
byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
- c.suite.assertNil(err, err)
+ c.Suite.AssertNil(err, fmt.Sprint(err))
return string(byteOutput)
}
func (c *Container) getLogDirPath() string {
- testId := c.suite.getTestId()
- testName := CurrentSpecReport().LeafNodeText
+ testId := c.Suite.GetTestId()
+ testName := c.Suite.GetCurrentTestName()
logDirPath := logDir + testName + "/" + testId + "/"
cmd := exec.Command("mkdir", "-p", logDirPath)
@@ -304,17 +314,13 @@ func (c *Container) getLogDirPath() string {
}
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"
+ testLogFilePath := c.getLogDirPath() + "container-" + c.Name + ".log"
- cmd = exec.Command("docker", "logs", "--details", "-t", c.name)
+ cmd := exec.Command("docker", "logs", "--details", "-t", c.Name)
+ c.Suite.Log(cmd)
output, err := cmd.CombinedOutput()
if err != nil {
- Fail("fetching logs error: " + fmt.Sprint(err))
+ c.Suite.Log(err)
}
f, err := os.Create(testLogFilePath)
@@ -329,38 +335,39 @@ func (c *Container) saveLogs() {
func (c *Container) log(maxLines int) (string, error) {
var cmd string
if maxLines == 0 {
- cmd = "docker logs " + c.name
+ cmd = "docker logs " + c.Name
} else {
- cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.name)
+ cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.Name)
}
- c.suite.log(cmd)
+ c.Suite.Log(cmd)
o, err := exechelper.CombinedOutput(cmd)
return string(o), err
}
func (c *Container) stop() error {
- if c.vppInstance != nil && c.vppInstance.apiChannel != nil {
- c.vppInstance.saveLogs()
- c.vppInstance.disconnect()
+ if c.VppInstance != nil && c.VppInstance.ApiStream != nil {
+ c.VppInstance.saveLogs()
+ c.VppInstance.Disconnect()
}
- c.vppInstance = nil
+ c.VppInstance = nil
c.saveLogs()
- return exechelper.Run("docker stop " + c.name + " -t 0")
+ c.Suite.Log("docker stop " + c.Name + " -t 0")
+ return exechelper.Run("docker stop " + c.Name + " -t 0")
}
-func (c *Container) createConfig(targetConfigName string, templateName string, values any) {
+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)
+ c.Suite.AssertNil(err, err)
defer os.Remove(f.Name())
err = template.Execute(f, values)
- c.suite.assertNil(err, err)
+ c.Suite.AssertNil(err, err)
err = f.Close()
- c.suite.assertNil(err, err)
+ c.Suite.AssertNil(err, err)
c.copy(f.Name(), targetConfigName)
}
diff --git a/extras/hs-test/cpu.go b/extras/hs-test/infra/cpu.go
index a976f47d8a5..b5555d85b98 100644
--- a/extras/hs-test/cpu.go
+++ b/extras/hs-test/infra/cpu.go
@@ -1,9 +1,10 @@
-package main
+package hst
import (
"bufio"
"errors"
"fmt"
+ . "github.com/onsi/ginkgo/v2"
"os"
"os/exec"
"strings"
@@ -16,26 +17,35 @@ type CpuContext struct {
cpus []int
}
-func (c *CpuContext) Release() {
- c.cpuAllocator.cpus = append(c.cpuAllocator.cpus, c.cpus...)
- c.cpus = c.cpus[:0] // empty the list
-}
-
type CpuAllocatorT struct {
cpus []int
}
var cpuAllocator *CpuAllocatorT = nil
-func (c *CpuAllocatorT) Allocate(nCpus int) (*CpuContext, error) {
+func (c *CpuAllocatorT) Allocate(containerCount int, nCpus int) (*CpuContext, error) {
var cpuCtx CpuContext
- if len(c.cpus) < nCpus {
- return nil, fmt.Errorf("could not allocate %d CPUs; available: %d", nCpus, len(c.cpus))
+ // splitting cpus into equal parts; this will over-allocate cores but it's good enough for now
+ maxContainerCount := 4
+ // skip CPU 0
+ minCpu := ((GinkgoParallelProcess() - 1) * maxContainerCount * nCpus) + 1
+ maxCpu := (GinkgoParallelProcess() * maxContainerCount * nCpus)
+
+ if len(c.cpus)-1 < maxCpu {
+ err := fmt.Errorf("could not allocate %d CPUs; available: %d; attempted to allocate cores %d-%d",
+ nCpus*containerCount, len(c.cpus)-1, minCpu, maxCpu)
+ return nil, err
}
- cpuCtx.cpus = c.cpus[0:nCpus]
+ if containerCount == 1 {
+ cpuCtx.cpus = c.cpus[minCpu : minCpu+nCpus]
+ } else if containerCount > 1 && containerCount <= 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", maxContainerCount)
+ }
+
cpuCtx.cpuAllocator = c
- c.cpus = c.cpus[nCpus:]
return &cpuCtx, 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..a6ba14676d0
--- /dev/null
+++ b/extras/hs-test/infra/hst_suite.go
@@ -0,0 +1,545 @@
+package hst
+
+import (
+ "bufio"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/onsi/gomega/gmeasure"
+ "gopkg.in/yaml.v3"
+
+ "github.com/edwarnicke/exechelper"
+ . "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 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
+ CpuPerVpp int
+ Ppid string
+ ProcessIndex string
+ Logger *log.Logger
+ LogFile *os.File
+}
+
+func getTestFilename() string {
+ _, filename, _, _ := runtime.Caller(2)
+ return filepath.Base(filename)
+}
+
+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.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.CpuPerVpp = *NConfiguredCpus
+}
+
+func (s *HstSuite) AllocateCpus() []int {
+ cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), 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.StartedContainers = s.StartedContainers[: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() {
+ 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 " + container.getLogDirPath())
+ 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 := 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) 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.StartedContainers {
+ container.stop()
+ exechelper.Run("docker rm " + container.Name)
+ }
+}
+
+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 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
+}
+
+func (s *HstSuite) StartServerApp(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) 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.GetPortFromPpid())
+ 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.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
+}
+
+/*
+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.
+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)
+}
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_nginx.go b/extras/hs-test/infra/suite_nginx.go
new file mode 100644
index 00000000000..bb1bdb0f42b
--- /dev/null
+++ b/extras/hs-test/infra/suite_nginx.go
@@ -0,0 +1,144 @@
+package hst
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "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 = map[string][]func(s *NginxSuite){}
+var nginxSoloTests = map[string][]func(s *NginxSuite){}
+
+type NginxSuite struct {
+ HstSuite
+}
+
+func RegisterNginxTests(tests ...func(s *NginxSuite)) {
+ nginxTests[getTestFilename()] = tests
+}
+func RegisterNginxSoloTests(tests ...func(s *NginxSuite)) {
+ nginxSoloTests[getTestFilename()] = 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")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ s.Log("**********************INTERRUPT MODE**********************")
+ } else {
+ sessionConfig.Close()
+ }
+
+ // ... for proxy
+ vppProxyContainer := s.GetContainerByName(VppProxyContainerName)
+ proxyVpp, _ := vppProxyContainer.newVppInstance(vppProxyContainer.AllocatedCpus, 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 filename, tests := range nginxTests {
+ 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("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 filename, tests := range nginxSoloTests {
+ 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..5f53f55f1bb
--- /dev/null
+++ b/extras/hs-test/infra/suite_no_topo.go
@@ -0,0 +1,119 @@
+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")
+}
+
+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_tap.go b/extras/hs-test/infra/suite_tap.go
new file mode 100644
index 00000000000..c02ab8e8535
--- /dev/null
+++ b/extras/hs-test/infra/suite_tap.go
@@ -0,0 +1,88 @@
+package hst
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+type TapSuite struct {
+ HstSuite
+}
+
+var tapTests = map[string][]func(s *TapSuite){}
+var tapSoloTests = map[string][]func(s *TapSuite){}
+
+func RegisterTapTests(tests ...func(s *TapSuite)) {
+ tapTests[getTestFilename()] = tests
+}
+func RegisterTapSoloTests(tests ...func(s *TapSuite)) {
+ tapSoloTests[getTestFilename()] = 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 filename, tests := range tapTests {
+ 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("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 filename, tests := range tapSoloTests {
+ 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/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..9619efbbf63
--- /dev/null
+++ b/extras/hs-test/infra/utils.go
@@ -0,0 +1,119 @@
+package hst
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "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 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
+}
diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go
new file mode 100644
index 00000000000..48d2b783917
--- /dev/null
+++ b/extras/hs-test/infra/vppinstance.go
@@ -0,0 +1,501 @@
+package hst
+
+import (
+ "context"
+ "fmt"
+ "go.fd.io/govpp/binapi/ethernet_types"
+ "io"
+ "net"
+ "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 }
+ 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
+}
+
+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.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 {
+ // 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.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/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go
index 24d2de39485..e9e8bba9274 100644
--- a/extras/hs-test/ldp_test.go
+++ b/extras/hs-test/ldp_test.go
@@ -4,80 +4,90 @@ import (
"fmt"
"os"
+ . "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
- registerVethTests(LDPreloadIperfVppTest)
+ RegisterVethTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest)
+}
+
+func LDPreloadIperfVppInterruptModeTest(s *VethsSuite) {
+ LDPreloadIperfVppTest(s)
}
func LDPreloadIperfVppTest(s *VethsSuite) {
var clnVclConf, srvVclConf Stanza
+ var ldpreload string
- serverContainer := s.getContainerByName("server-vpp")
- serverVclFileName := serverContainer.getHostWorkDir() + "/vcl_srv.conf"
+ serverContainer := s.GetContainerByName("server-vpp")
+ serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
- clientContainer := s.getContainerByName("client-vpp")
- clientVclFileName := clientContainer.getHostWorkDir() + "/vcl_cln.conf"
+ clientContainer := s.GetContainerByName("client-vpp")
+ clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
- ldpreload := "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
+ if *IsDebugBuild {
+ ldpreload = "LD_PRELOAD=../../build-root/build-vpp_debug-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
+ } else {
+ ldpreload = "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
+ }
stopServerCh := make(chan struct{}, 1)
srvCh := make(chan error, 1)
clnCh := make(chan error)
- s.log("starting VPPs")
+ s.Log("starting VPPs")
clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
- clientContainer.getHostWorkDir())
+ 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))
+ 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())
+ 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")
+ 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)
+ s.StartServerApp(srvCh, stopServerCh, srvEnv)
}()
err = <-srvCh
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
- s.log("attaching client to vpp")
+ 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)
+ s.StartClientApp(serverVethAddress, clnEnv, 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{}{}
diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go
index e323f7fb721..f49d9bcf227 100644
--- a/extras/hs-test/linux_iperf_test.go
+++ b/extras/hs-test/linux_iperf_test.go
@@ -1,13 +1,13 @@
package main
import (
+ . "fd.io/hs-test/infra"
"fmt"
-
. "github.com/onsi/ginkgo/v2"
)
func init() {
- registerTapTests(LinuxIperfTest)
+ RegisterTapTests(LinuxIperfTest)
}
func LinuxIperfTest(s *TapSuite) {
@@ -21,20 +21,20 @@ func LinuxIperfTest(s *TapSuite) {
go func() {
defer GinkgoRecover()
- s.startServerApp(srvCh, stopServerCh, nil)
+ s.StartServerApp(srvCh, stopServerCh, nil)
}()
err := <-srvCh
- s.assertNil(err, fmt.Sprint(err))
- s.log("server running")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log("server running")
- ipAddress := s.getInterfaceByName(tapInterfaceName).ip4AddressString()
+ ipAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
go func() {
defer GinkgoRecover()
- s.startClientApp(ipAddress, nil, clnCh, clnRes)
+ s.StartClientApp(ipAddress, nil, clnCh, clnRes)
}()
- s.log("client running")
- s.log(<-clnRes)
+ s.Log("client running")
+ s.Log(<-clnRes)
err = <-clnCh
- s.assertNil(err, "err: '%s', ip: '%s'", err, ipAddress)
- s.log("Test completed")
+ s.AssertNil(err, "err: '%s', ip: '%s'", err, ipAddress)
+ s.Log("Test completed")
}
diff --git a/extras/hs-test/mirroring_test.go b/extras/hs-test/mirroring_test.go
deleted file mode 100644
index 6c5a860b01c..00000000000
--- a/extras/hs-test/mirroring_test.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package main
-
-import (
- "github.com/edwarnicke/exechelper"
-)
-
-func init() {
- registerNginxTests(MirroringTest)
-}
-
-func MirroringTest(s *NginxSuite) {
- 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..00869b0c9b4
--- /dev/null
+++ b/extras/hs-test/nginx_test.go
@@ -0,0 +1,152 @@
+package main
+
+import (
+ . "fd.io/hs-test/infra"
+ "fmt"
+ "github.com/edwarnicke/exechelper"
+ . "github.com/onsi/ginkgo/v2"
+ "os"
+ "strings"
+)
+
+func init() {
+ RegisterNginxTests(MirroringTest)
+ RegisterNoTopoTests(NginxHttp3Test, NginxAsServerTest, NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest,
+ NginxPerfCpsInterruptModeTest, NginxPerfRpsInterruptModeTest, NginxPerfWrkInterruptModeTest)
+}
+
+// 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"))
+}
+
+func NginxHttp3Test(s *NoTopoSuite) {
+ s.SkipUnlessExtendedTestsBuilt()
+
+ query := "index.html"
+ nginxCont := s.GetContainerByName("nginx-http3")
+ s.AssertNil(nginxCont.Run())
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.WaitForApp("nginx-", 5)
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+
+ 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.Log(o)
+ s.AssertNil(err, fmt.Sprint(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")
+ s.AssertNil(nginxCont.Run())
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.WaitForApp("nginx-", 5)
+
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+
+ 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.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
+ 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 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..5f7eb45be38 100644
--- a/extras/hs-test/proxy_test.go
+++ b/extras/hs-test/proxy_test.go
@@ -1,37 +1,37 @@
package main
import (
+ . "fd.io/hs-test/infra"
"fmt"
- "os"
-
"github.com/edwarnicke/exechelper"
. "github.com/onsi/ginkgo/v2"
+ "os"
)
func init() {
- registerNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
+ RegisterNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
}
func testProxyHttpTcp(s *NsSuite, proto string) error {
- var outputFile string = "test" + s.pid + ".data"
- var srcFilePid string = "httpTestFile" + s.pid
- const srcFileNoPid = "httpTestFile"
+ var outputFile string = s.ProcessIndex + "test" + s.Ppid + ".data"
+ var srcFilePpid string = s.ProcessIndex + "httpTestFile" + s.Ppid
+ const srcFileNoPpid = "httpTestFile"
const fileSize string = "10M"
stopServer := make(chan struct{}, 1)
serverRunning := make(chan struct{}, 1)
- serverNetns := s.getNetNamespaceByName("srv")
- clientNetns := s.getNetNamespaceByName("cln")
+ 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) }()
+ err := exechelper.Run(fmt.Sprintf("ip netns exec %s truncate -s %s %s", serverNetns, fileSize, srcFilePpid))
+ s.AssertNil(err, "failed to run truncate command: "+fmt.Sprint(err))
+ defer func() { os.Remove(srcFilePpid) }()
- s.log("test file created...")
+ s.Log("test file created...")
go func() {
defer GinkgoRecover()
- s.startHttpServer(serverRunning, stopServer, ":666", serverNetns)
+ s.StartHttpServer(serverRunning, stopServer, ":666", serverNetns)
}()
// TODO better error handling and recovery
<-serverRunning
@@ -40,76 +40,76 @@ func testProxyHttpTcp(s *NsSuite, proto string) error {
stopServer <- struct{}{}
}(stopServer)
- s.log("http server started...")
+ s.Log("http server started...")
- clientVeth := s.getInterfaceByName(clientInterface)
+ 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)
+ c += fmt.Sprintf("%s:555/%s", clientVeth.Ip4AddressString(), srcFileNoPpid)
+ s.Log(c)
_, err = exechelper.CombinedOutput(c)
defer func() { os.Remove(outputFile) }()
- s.assertNil(err, "failed to run wget: '%s', cmd: %s", err, c)
+ s.AssertNil(err, "failed to run wget: '%s', cmd: %s", err, c)
stopServer <- struct{}{}
- s.assertNil(assertFileSize(outputFile, srcFilePid))
+ s.AssertNil(AssertFileSize(outputFile, srcFilePpid))
return nil
}
func configureVppProxy(s *NsSuite, proto string) {
- serverVeth := s.getInterfaceByName(serverInterface)
- clientVeth := s.getInterfaceByName(clientInterface)
+ serverVeth := s.GetInterfaceByName(ServerInterface)
+ clientVeth := s.GetInterfaceByName(ClientInterface)
- testVppProxy := s.getContainerByName("vpp").vppInstance
- output := testVppProxy.vppctl(
+ 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(),
+ clientVeth.Ip4AddressString(),
+ serverVeth.Peer.Ip4AddressString(),
)
- s.log("proxy configured: " + output)
+ s.Log("proxy configured: " + output)
}
func VppProxyHttpTcpTest(s *NsSuite) {
proto := "tcp"
configureVppProxy(s, proto)
err := testProxyHttpTcp(s, proto)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
}
func VppProxyHttpTlsTest(s *NsSuite) {
proto := "tls"
configureVppProxy(s, proto)
err := testProxyHttpTcp(s, proto)
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
}
func configureEnvoyProxy(s *NsSuite) {
- envoyContainer := s.getContainerByName("envoy")
- err := envoyContainer.create()
- s.assertNil(err, "Error creating envoy container: %s", err)
+ envoyContainer := s.GetContainerByName("envoy")
+ err := envoyContainer.Create()
+ s.AssertNil(err, "Error creating envoy container: %s", err)
- serverVeth := s.getInterfaceByName(serverInterface)
+ serverVeth := s.GetInterfaceByName(ServerInterface)
address := struct {
Server string
}{
- Server: serverVeth.peer.ip4AddressString(),
+ Server: serverVeth.Peer.Ip4AddressString(),
}
- envoyContainer.createConfig(
+ envoyContainer.CreateConfig(
"/etc/envoy/envoy.yaml",
"resources/envoy/proxy.yaml",
address,
)
- s.assertNil(envoyContainer.start())
+ s.AssertNil(envoyContainer.Start())
}
func EnvoyProxyHttpTcpTest(s *NsSuite) {
configureEnvoyProxy(s)
err := testProxyHttpTcp(s, "tcp")
- s.assertNil(err, fmt.Sprint(err))
+ s.AssertNil(err, fmt.Sprint(err))
}
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/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 33a8393b8b5..0f5c0a43630 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,30 @@ 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 -x
+ # 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 +x
}
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 92a2fbd6789..c6b23cf9bdd 100644..100755
--- a/extras/hs-test/script/compress.sh
+++ b/extras/hs-test/script/compress.sh
@@ -1,34 +1,35 @@
#!/usr/bin/env bash
-if [ "${COMPRESS_FAILED_TEST_LOGS}" == "yes" -a -s "${HS_SUMMARY}/failed-summary.log" ]
+# if failed-summary.log is not empty, exit status = 1
+if [ -s "${HS_ROOT}/summary/failed-summary.log" ]
then
- echo -n "Copying docker logs..."
- dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText' ${HS_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/
- fi
- done
- echo "Done."
-
- if [ -n "$WORKSPACE" ]
+ if [ -n "${WORKSPACE}" ]
then
- echo -n "Copying failed test logs into build log archive directory ($WORKSPACE/archives)... "
- mkdir -p $WORKSPACE/archives/summary
- cp -a ${HS_SUMMARY}/* $WORKSPACE/archives/summary
- echo "Done."
- fi
+ echo -n "Copying docker logs..."
+ dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText' ${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/
+ fi
+ done
+ echo "Done."
- echo -n "Compressing files in $WORKSPACE/archives from test runs... "
- cd $WORKSPACE/archives
- find . -type f \( -name "*.json" -o -name "*.log" \) -exec gzip {} \;
- echo "Done."
+ echo -n "Copying failed test logs into build log archive directory (${WORKSPACE}/archives)... "
+ mkdir -p ${WORKSPACE}/archives/summary
+ cp -a ${HS_ROOT}/summary/* ${WORKSPACE}/archives/summary
+ echo "Done."
-else
- echo "Not compressing files in temporary directories from test runs."
- exit 0
-fi
+ echo -n "Compressing files in ${WORKSPACE}/archives from test runs... "
+ cd ${WORKSPACE}/archives
+ find . -type f \( -name "*.json" -o -name "*.log" \) -exec gzip {} \;
+ echo "Done."
-exit 1 \ No newline at end of file
+ else
+ echo "Not compressing files in temporary directories from test runs."
+ fi
+ echo "*************************** SUMMARY ***************************"
+ cat "${HS_ROOT}/summary/failed-summary.log"
+ 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/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/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/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..3b413b26bed 100644
--- a/extras/hs-test/vcl_test.go
+++ b/extras/hs-test/vcl_test.go
@@ -1,12 +1,13 @@
package main
import (
+ . "fd.io/hs-test/infra"
"fmt"
"time"
)
func init() {
- registerVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
+ RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest)
}
@@ -16,141 +17,141 @@ 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 " + 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 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 9b400cfcb77..00000000000
--- a/extras/hs-test/vppinstance.go
+++ /dev/null
@@ -1,412 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "os/exec"
- "os/signal"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- "github.com/sirupsen/logrus"
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
-
- "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/binapi/vpe"
- "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
- apiChannel api.Channel
- 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 {
- fmt.Println("async connect error: ", err)
- return err
- }
- vpp.connection = conn
-
- // ... wait for Connected event
- e := <-connEv
- if e.State != core.Connected {
- fmt.Println("connecting to VPP failed: ", e.Error)
- }
-
- // ... check compatibility of used messages
- ch, err := conn.NewAPIChannel()
- if err != nil {
- fmt.Println("creating channel failed: ", err)
- return err
- }
- if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
- fmt.Println("compatibility error: ", err)
- return err
- }
- if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
- fmt.Println("compatibility error: ", err)
- return err
- }
- vpp.apiChannel = 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.AfPacketCreateV2{
- UseRandomHwAddr: true,
- HostIfName: veth.Name(),
- }
- if veth.hwAddress != (MacAddress{}) {
- createReq.UseRandomHwAddr = false
- createReq.HwAddr = veth.hwAddress
- }
- createReply := &af_packet.AfPacketCreateV2Reply{}
-
- vpp.getSuite().log("create af-packet interface " + veth.Name())
- if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
- return 0, err
- }
- veth.index = createReply.SwIfIndex
-
- // Set to up
- upReq := &interfaces.SwInterfaceSetFlags{
- SwIfIndex: veth.index,
- Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
- }
- upReply := &interfaces.SwInterfaceSetFlagsReply{}
-
- vpp.getSuite().log("set af-packet interface " + veth.Name() + " up")
- if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); 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(),
- }
- addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
-
- vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address)
- if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
- return 0, err
- }
-
- return veth.index, nil
-}
-
-func (vpp *VppInstance) addAppNamespace(
- secret uint64,
- ifx interface_types.InterfaceIndex,
- namespaceId string,
-) error {
- req := &session.AppNamespaceAddDelV2{
- Secret: secret,
- SwIfIndex: ifx,
- NamespaceID: namespaceId,
- }
- reply := &session.AppNamespaceAddDelV2Reply{}
-
- vpp.getSuite().log("add app namespace " + namespaceId)
- if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
- return err
- }
-
- sessionReq := &session.SessionEnableDisable{
- IsEnable: true,
- }
- sessionReply := &session.SessionEnableDisableReply{}
-
- vpp.getSuite().log("enable app namespace " + namespaceId)
- if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); 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.TapCreateV2{
- ID: id,
- HostIfNameSet: true,
- HostIfName: tap.Name(),
- HostIP4PrefixSet: true,
- HostIP4Prefix: tap.ip4AddressWithPrefix(),
- }
- createTapReply := &tapv2.TapCreateV2Reply{}
-
- vpp.getSuite().log("create tap interface " + tap.Name())
- // Create tap interface
- if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil {
- return err
- }
-
- // Add address
- addAddressReq := &interfaces.SwInterfaceAddDelAddress{
- IsAdd: true,
- SwIfIndex: createTapReply.SwIfIndex,
- Prefix: tap.peer.addressWithPrefix(),
- }
- addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{}
-
- vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address)
- if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil {
- return err
- }
-
- // Set interface to up
- upReq := &interfaces.SwInterfaceSetFlags{
- SwIfIndex: createTapReply.SwIfIndex,
- Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
- }
- upReply := &interfaces.SwInterfaceSetFlagsReply{}
-
- vpp.getSuite().log("set tap interface " + tap.Name() + " up")
- if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); 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.apiChannel.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]))
- 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))
- }
- return c.close().toString()
-}