aboutsummaryrefslogtreecommitdiffstats
path: root/extras
diff options
context:
space:
mode:
Diffstat (limited to 'extras')
-rw-r--r--extras/configs/http/setup.http3
-rw-r--r--extras/hs-test/Makefile58
-rw-r--r--extras/hs-test/README.rst88
-rw-r--r--extras/hs-test/cpu_pinning_test.go30
-rw-r--r--extras/hs-test/docker/Dockerfile.build8
-rw-r--r--extras/hs-test/docker/Dockerfile.curl10
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx-http38
-rw-r--r--extras/hs-test/docker/Dockerfile.nginx-server6
-rw-r--r--extras/hs-test/docker/Dockerfile.vpp2
-rw-r--r--extras/hs-test/framework_test.go12
-rw-r--r--extras/hs-test/go.mod68
-rw-r--r--extras/hs-test/go.sum264
-rw-r--r--extras/hs-test/hs_test.sh52
-rw-r--r--extras/hs-test/http_test.go752
-rw-r--r--extras/hs-test/infra/container.go268
-rw-r--r--extras/hs-test/infra/cpu.go198
-rw-r--r--extras/hs-test/infra/hst_suite.go324
-rw-r--r--extras/hs-test/infra/suite_cpu_pinning.go113
-rw-r--r--extras/hs-test/infra/suite_envoy_proxy.go213
-rw-r--r--extras/hs-test/infra/suite_iperf_linux.go (renamed from extras/hs-test/infra/suite_tap.go)38
-rw-r--r--extras/hs-test/infra/suite_ldp.go203
-rw-r--r--extras/hs-test/infra/suite_nginx.go144
-rw-r--r--extras/hs-test/infra/suite_nginx_proxy.go191
-rw-r--r--extras/hs-test/infra/suite_no_topo.go12
-rw-r--r--extras/hs-test/infra/suite_vpp_proxy.go212
-rw-r--r--extras/hs-test/infra/utils.go197
-rw-r--r--extras/hs-test/infra/vppinstance.go184
-rw-r--r--extras/hs-test/iperf_linux_test.go48
-rw-r--r--extras/hs-test/ldp_test.go104
-rw-r--r--extras/hs-test/linux_iperf_test.go40
-rw-r--r--extras/hs-test/mem_leak_test.go25
-rw-r--r--extras/hs-test/nginx_test.go53
-rw-r--r--extras/hs-test/proxy_test.go142
-rw-r--r--extras/hs-test/resources/curl/write_out_download1
-rw-r--r--extras/hs-test/resources/curl/write_out_download_connect1
-rw-r--r--extras/hs-test/resources/curl/write_out_upload1
-rw-r--r--extras/hs-test/resources/curl/write_out_upload_connect1
-rw-r--r--extras/hs-test/resources/envoy/proxy.yaml16
-rw-r--r--extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf5
-rw-r--r--extras/hs-test/resources/nginx/nginx_server.conf43
-rw-r--r--extras/hs-test/resources/nginx/nginx_server_mirroring.conf13
-rwxr-xr-xextras/hs-test/script/build_hst.sh7
-rwxr-xr-xextras/hs-test/script/compress.sh6
-rwxr-xr-xextras/hs-test/script/nginx_server_entrypoint.sh3
-rw-r--r--extras/hs-test/topo-containers/2containers.yaml4
-rw-r--r--extras/hs-test/topo-containers/2peerVethLdp.yaml18
-rw-r--r--extras/hs-test/topo-containers/envoyProxy.yaml40
-rw-r--r--extras/hs-test/topo-containers/nginxProxy.yaml32
-rw-r--r--extras/hs-test/topo-containers/ns.yaml2
-rw-r--r--extras/hs-test/topo-containers/singleCpuPinning.yaml11
-rw-r--r--extras/hs-test/topo-containers/vppProxy.yaml (renamed from extras/hs-test/topo-containers/nginxProxyAndServer.yaml)21
-rw-r--r--extras/hs-test/vcl_test.go11
-rwxr-xr-xextras/scripts/checkstyle.sh19
-rwxr-xr-xextras/vpp_if_stats/apimock.go5
-rwxr-xr-xextras/vpp_if_stats/json_structs.go1
-rwxr-xr-xextras/vpp_if_stats/statsmock.go3
-rw-r--r--extras/vpp_if_stats/vpp_if_stats.go3
-rw-r--r--extras/vpp_if_stats/vpp_if_stats_test.go7
58 files changed, 3515 insertions, 829 deletions
diff --git a/extras/configs/http/setup.http b/extras/configs/http/setup.http
index 78b7a2f19e8..c4d4d1e6b70 100644
--- a/extras/configs/http/setup.http
+++ b/extras/configs/http/setup.http
@@ -3,5 +3,4 @@ create tap host-if-name lstack host-ip4-addr 192.168.10.2/24
set int ip address tap0 192.168.10.1/24
set int state tap0 up
-http static server www-root <path> uri tcp://0.0.0.0/1234 cache-size 10m fifo-size 2048
-builtinurl enable
+http static server url-handlers www-root <path> uri tcp://0.0.0.0/1234 cache-size 10m fifo-size 2048
diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile
index 596acb1b57d..b72aca1d7dd 100644
--- a/extras/hs-test/Makefile
+++ b/extras/hs-test/Makefile
@@ -41,6 +41,10 @@ ifeq ($(REPEAT),)
REPEAT=0
endif
+ifeq ($(CPU0),)
+CPU0=false
+endif
+
ifeq ($(VPPSRC),)
VPPSRC=$(shell pwd)/../..
endif
@@ -58,6 +62,7 @@ help:
@echo "Make targets:"
@echo " test - run tests"
@echo " test-debug - run tests (vpp debug image)"
+ @echo " test-leak - run memory leak tests (vpp debug image)"
@echo " build - build test infra"
@echo " build-cov - coverage build of VPP and Docker images"
@echo " build-debug - build test infra (vpp debug image)"
@@ -81,6 +86,7 @@ help:
@echo " VPPSRC=[path-to-vpp-src] - path to vpp source files (for gdb)"
@echo " PARALLEL=[n-cpus] - number of test processes to spawn to run in parallel"
@echo " REPEAT=[n] - repeat tests up to N times or until a failure occurs"
+ @echo " CPU0=[true|false] - use cpu0"
@echo
@echo "List of all tests:"
@$(MAKE) list-tests
@@ -113,29 +119,30 @@ build-vpp-gcov:
.PHONY: test
test: .deps.ok .build.ok
- @# '-' ignores the exit status, it is set in compress.sh
- @# necessary so gmake won't skip executing the bash script
- @-bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
- --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT)
- @bash ./script/compress.sh
+ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
+
.PHONY: test-debug
test-debug: .deps.ok .build_debug.ok
- @# '-' ignores the exit status, it is set in compress.sh
- @# necessary so gmake won't skip executing the bash script
- @-bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
--unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \
- --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true
- @bash ./script/compress.sh
+ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true \
+ --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
.PHONY: test-cov
test-cov: .deps.ok .build.cov.ok
- @-bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \
+ @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
+ --vppsrc=$(VPPSRC) --cpu0=$(CPU0); \
+ ./script/compress.sh $$?
+
+.PHONY: test-leak
+test-leak: .deps.ok .build_debug.ok
+ @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC)
.PHONY: build-go
build-go:
@@ -167,19 +174,29 @@ install-deps:
@rm -f .deps.ok
@apt-get update \
&& apt-get install -y apt-transport-https ca-certificates curl software-properties-common \
- apache2-utils wrk bridge-utils
+ apache2-utils wrk bridge-utils gpg
@if [ ! -f /usr/share/keyrings/docker-archive-keyring.gpg ] ; then \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; \
echo "deb [arch=$(ARCH) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(UBUNTU_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ; \
apt-get update; \
fi
+ @apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
@touch .deps.ok
+.goimports.ok:
+ @rm -f .goimports.ok
+ go install golang.org/x/tools/cmd/goimports@v0.25.0
+ @touch .goimports.ok
+
.PHONY: checkstyle-go
-checkstyle-go:
- @output=$$(gofmt -d $${WS_ROOT}); \
- if [ -z "$$output" ]; then \
+checkstyle-go: .goimports.ok
+ $(eval GOPATH := $(shell go env GOPATH))
+ @output=$$($(GOPATH)/bin/goimports -d $${WS_ROOT}); \
+ status=$$?; \
+ if [ $$status -ne 0 ]; then \
+ exit $$status; \
+ elif [ -z "$$output" ]; then \
echo "*******************************************************************"; \
echo "Checkstyle OK."; \
echo "*******************************************************************"; \
@@ -192,9 +209,10 @@ checkstyle-go:
fi
.PHONY: fixstyle-go
-fixstyle-go:
+fixstyle-go: .goimports.ok
+ $(eval GOPATH := $(shell go env GOPATH))
@echo "Modified files:"
- @gofmt -w -l $(WS_ROOT)
+ @$(GOPATH)/bin/goimports -w -l $(WS_ROOT)
@go mod tidy
@echo "*******************************************************************"
@echo "Fixstyle done."
diff --git a/extras/hs-test/README.rst b/extras/hs-test/README.rst
index 7841211e3ab..6a4cea187c4 100644
--- a/extras/hs-test/README.rst
+++ b/extras/hs-test/README.rst
@@ -293,7 +293,23 @@ Alternatively copy the executable from host system to the Docker image, similarl
However the tests currently run under test suites which set up topology and containers before actual test is run. For the reason of saving
test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test.
-**Debugging a test**
+**External dependencies**
+
+* Linux tools ``ip``, ``brctl``
+* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
+ they are reasonably up-to-date automatically
+* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
+* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
+* Non-standard Go libraries - see ``extras/hs-test/go.mod``
+
+Generally, these will be updated on a per-need basis, for example when a bug is discovered
+or a new version incompatibility issue occurs.
+
+Debugging a test
+----------------
+
+GDB
+^^^
It is possible to debug VPP by attaching ``gdb`` before test execution by adding ``DEBUG=true`` like follows:
@@ -307,18 +323,70 @@ It is possible to debug VPP by attaching ``gdb`` before test execution by adding
If a test consists of more VPP instances then this is done for each of them.
+Utility methods
+^^^^^^^^^^^^^^^
-**Eternal dependencies**
+**Packet Capture**
-* Linux tools ``ip``, ``brctl``
-* Standalone programs ``wget``, ``iperf3`` - since these are downloaded when Docker image is made,
- they are reasonably up-to-date automatically
-* Programs in Docker images - ``envoyproxy/envoy-contrib`` and ``nginx``
-* ``http_server`` - homegrown application that listens on specified port and sends a test file in response
-* Non-standard Go libraries - see ``extras/hs-test/go.mod``
+It is possible to use VPP pcap trace to capture received and sent packets.
+You just need to add ``EnablePcapTrace`` to ``SetupTest`` method in test suite and ``CollectPcapTrace`` to ``TearDownTest``.
+This way pcap trace is enabled on all interfaces and to capture maximum 10000 packets.
+Your pcap file will be located in the test execution directory.
-Generally, these will be updated on a per-need basis, for example when a bug is discovered
-or a new version incompatibility issue occurs.
+**Event Logger**
+
+``clib_warning`` is a handy way to add debugging output, but in some cases it's not appropriate for per-packet use in data plane code.
+In this case VPP event logger is better option, for example you can enable it for TCP or session layer in build time.
+To collect traces when test ends you just need to add ``CollectEventLogs`` method to ``TearDownTest`` in the test suite.
+Your event logger file will be located in the test execution directory.
+To view events you can use :ref:`G2 graphical event viewer <eventviewer>` or ``convert_evt`` tool, located in ``src/scripts/host-stack/``,
+which convert event logs to human readable text.
+
+Memory leak testing
+^^^^^^^^^^^^^^^^^^^
+
+It is possible to use VPP memory traces to diagnose if and where memory leaks happen by comparing of two traces at different point in time.
+You can do it by test like following:
+
+::
+
+ func MemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck() // test is excluded from usual test run
+ vpp := s.GetContainerByName("vpp").VppInstance
+ /* do your configuration here */
+ vpp.Disconnect() // no goVPP less noise
+ vpp.EnableMemoryTrace() // enable memory traces
+ traces1, err := vpp.GetMemoryTrace() // get first sample
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.Vppctl("test mem-leak") // execute some action
+ traces2, err := vpp.GetMemoryTrace() // get second sample
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2) // compare samples and generate report
+ }
+
+To get your memory leak report run following command:
+
+::
+
+ $ make test-leak TEST=MemLeakTest
+ ...
+ NoTopoSuiteSolo mem_leak_test.go/MemLeakTest [SOLO]
+ /home/matus/vpp/extras/hs-test/infra/suite_no_topo.go:113
+
+ Report Entries >>
+
+ SUMMARY: 112 byte(s) leaked in 1 allocation(s)
+ - /home/matus/vpp/extras/hs-test/infra/vppinstance.go:624 @ 07/19/24 15:53:33.539
+
+ leak of 112 byte(s) in 1 allocation(s) from:
+ #0 clib_mem_heap_alloc_aligned + 0x31
+ #1 _vec_alloc_internal + 0x113
+ #2 _vec_validate + 0x81
+ #3 leak_memory_fn + 0x4f
+ #4 0x7fc167815ac3
+ #5 0x7fc1678a7850
+ << Report Entries
+ ------------------------------
.. _ginkgo: https://onsi.github.io/ginkgo/
diff --git a/extras/hs-test/cpu_pinning_test.go b/extras/hs-test/cpu_pinning_test.go
new file mode 100644
index 00000000000..b8dec65937e
--- /dev/null
+++ b/extras/hs-test/cpu_pinning_test.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterCpuPinningSoloTests(DefaultCpuConfigurationTest, SkipCoresTest)
+}
+
+// TODO: Add more CPU configuration tests
+
+func DefaultCpuConfigurationTest(s *CpuPinningSuite) {
+ vpp := s.GetContainerByName(SingleTopoContainerVpp).VppInstance
+ s.AssertNil(vpp.Start())
+}
+
+func SkipCoresTest(s *CpuPinningSuite) {
+
+ skipCoresConfiguration := VppCpuConfig{
+ PinMainCpu: true,
+ PinWorkersCorelist: true,
+ SkipCores: 1,
+ }
+
+ vpp := s.GetContainerByName(SingleTopoContainerVpp).VppInstance
+ vpp.CpuConfig = skipCoresConfiguration
+
+ s.AssertNil(vpp.Start())
+}
diff --git a/extras/hs-test/docker/Dockerfile.build b/extras/hs-test/docker/Dockerfile.build
deleted file mode 100644
index 8b2652e93fc..00000000000
--- a/extras/hs-test/docker/Dockerfile.build
+++ /dev/null
@@ -1,8 +0,0 @@
-ARG UBUNTU_VERSION
-
-FROM ubuntu:${UBUNTU_VERSION}
-
-RUN apt-get update \
- && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
- && rm -rf /var/lib/apt/lists/*
-
diff --git a/extras/hs-test/docker/Dockerfile.curl b/extras/hs-test/docker/Dockerfile.curl
index 81d15e86c82..cbb0bbe734f 100644
--- a/extras/hs-test/docker/Dockerfile.curl
+++ b/extras/hs-test/docker/Dockerfile.curl
@@ -1,6 +1,14 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
COPY script/build_curl.sh /build_curl.sh
+COPY resources/curl/* /tmp/
+RUN fallocate -l 10MB /tmp/testFile
RUN apt-get update && apt-get install wget
RUN /build_curl.sh
diff --git a/extras/hs-test/docker/Dockerfile.nginx-http3 b/extras/hs-test/docker/Dockerfile.nginx-http3
index 5d66a2528a6..8b2f8406d38 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-http3
+++ b/extras/hs-test/docker/Dockerfile.nginx-http3
@@ -1,4 +1,10 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
COPY script/build_boringssl.sh /build_boringssl.sh
RUN git clone https://boringssl.googlesource.com/boringssl
diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server
index 1971158131b..ecb8f590f89 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-server
+++ b/extras/hs-test/docker/Dockerfile.nginx-server
@@ -7,6 +7,10 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
COPY resources/nginx/nginx_server_mirroring.conf /nginx.conf
+COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh
+COPY resources/nginx/html/index.html /usr/share/nginx/index.html
+RUN fallocate -l 10MB /usr/share/nginx/httpTestFile
+RUN mkdir /usr/share/nginx/upload && chmod 777 /usr/share/nginx/upload
-ENTRYPOINT ["nginx", "-c", "/nginx.conf"]
+ENTRYPOINT ["nginx_server_entrypoint.sh"]
diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp
index f87ee30c332..82a1a1a73d3 100644
--- a/extras/hs-test/docker/Dockerfile.vpp
+++ b/extras/hs-test/docker/Dockerfile.vpp
@@ -5,7 +5,7 @@ FROM ubuntu:${UBUNTU_VERSION}
RUN apt-get update \
&& apt-get install -y openssl libapr1 libnuma1 libsubunit0 \
iproute2 libnl-3-dev libnl-route-3-dev python3 iputils-ping \
- vim gdb libunwind-dev \
+ vim gdb libunwind-dev redis redis-tools iperf3 \
&& rm -rf /var/lib/apt/lists/*
ENV DIR=vpp-data/lib/vpp_plugins
diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go
index a086f75a5fc..91cb1032bba 100644
--- a/extras/hs-test/framework_test.go
+++ b/extras/hs-test/framework_test.go
@@ -3,8 +3,7 @@ package main
import (
"fmt"
"os"
- "path/filepath"
- "runtime"
+ "strings"
"testing"
"time"
@@ -13,11 +12,6 @@ import (
. "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
@@ -26,6 +20,10 @@ func TestHst(t *testing.T) {
SuiteTimeout = time.Minute * 5
}
+ output, err := os.ReadFile("/sys/devices/system/node/online")
+ if err == nil && strings.Contains(string(output), "-") {
+ NumaAwareCpuAlloc = true
+ }
// creates a file with PPID, used for 'make cleanup-hst'
ppid := fmt.Sprint(os.Getppid())
ppid = ppid[:len(ppid)-1]
diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod
index 3be9ba20a86..01cb9000bdc 100644
--- a/extras/hs-test/go.mod
+++ b/extras/hs-test/go.mod
@@ -1,33 +1,67 @@
module fd.io/hs-test
-go 1.21
+go 1.22.5
require (
+ github.com/cilium/cilium v1.15.7
+ github.com/docker/docker v27.1.1+incompatible
+ github.com/docker/go-units v0.5.0
github.com/edwarnicke/exechelper v1.0.3
+ github.com/onsi/ginkgo/v2 v2.17.2
+ github.com/onsi/gomega v1.33.1
+ github.com/sirupsen/logrus v1.9.3
go.fd.io/govpp v0.10.0
gopkg.in/yaml.v3 v3.0.1
)
require (
- github.com/go-logr/logr v1.4.1 // indirect
- github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
- golang.org/x/net v0.20.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- golang.org/x/tools v0.17.0 // indirect
-)
-
-require (
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/errors v0.22.0 // indirect
+ github.com/go-openapi/strfmt v0.23.0 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/go-openapi/validate v0.24.0 // indirect
+ github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
- github.com/onsi/ginkgo/v2 v2.16.0
- github.com/onsi/gomega v1.32.0
+ github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
+ github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
+ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/sirupsen/logrus v1.9.3
+ github.com/sasha-s/go-deadlock v0.3.1 // indirect
+ github.com/spf13/cobra v1.8.1 // indirect
+ github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect
+ github.com/spf13/viper v1.19.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
- golang.org/x/sys v0.16.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
+ go.opentelemetry.io/otel v1.28.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
+ go.opentelemetry.io/otel/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.28.0 // indirect
+ go.opentelemetry.io/otel/trace v1.28.0 // indirect
+ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
+ golang.org/x/net v0.26.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.21.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ golang.org/x/tools v0.22.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gotest.tools/v3 v3.5.1 // indirect
+ k8s.io/apimachinery v0.30.2 // indirect
+ k8s.io/client-go v0.30.2 // indirect
+ k8s.io/klog/v2 v2.120.1 // indirect
+ k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
)
diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum
index 479b0289814..fb555ad7abf 100644
--- a/extras/hs-test/go.sum
+++ b/extras/hs-test/go.sum
@@ -1,70 +1,268 @@
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
+github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cilium/checkmate v1.0.3 h1:CQC5eOmlAZeEjPrVZY3ZwEBH64lHlx9mXYdUehEwI5w=
+github.com/cilium/checkmate v1.0.3/go.mod h1:KiBTasf39/F2hf2yAmHw21YFl3hcEyP4Yk6filxc12A=
+github.com/cilium/cilium v1.15.7 h1:7LwGfAW/fR/VFcm6zlESjE2Ut5vJWe+kdWq3RNJrNRc=
+github.com/cilium/cilium v1.15.7/go.mod h1:6Ml8eeyWjMJKDeadutWhn5NibMps0H+yLOgfKBoHTUs=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
+github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edwarnicke/exechelper v1.0.3 h1:OY2ocGAITTqnEDvZk0dRQSeMIQvyH0SyL/4ncz+5GeQ=
github.com/edwarnicke/exechelper v1.0.3/go.mod h1:R65OUPKns4bgeHkCmfSHbmqLBU8aHZxTgLmEyUBUk4U=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
+github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
+github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
+github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
+github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
+github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
+github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
-github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
+github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
-github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
-github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
-github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
-github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
-github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
+github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
+github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
+github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
+github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
+github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
+github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
+github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
+github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
+github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
+github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
+github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21 h1:tcHUxOT8j/R+0S+A1j8D2InqguXFNxAiij+8QFOlX7Y=
+github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21/go.mod h1:whJevzBpTrid75eZy99s3DqCmy05NfibNaF2Ol5Ox5A=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.fd.io/govpp v0.10.0 h1:lL93SbqOILjON2pMvazrlHRekGYTRy0Qmj57RuAkxR0=
go.fd.io/govpp v0.10.0/go.mod h1:5m3bZM9ck+2EGC2O3ASmSSJAaoouyOlVWtiwj5BdCv0=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
+golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
-golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
-google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
-google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
+gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
+k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
+k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
+k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
+k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
+k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
+k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
+k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh
index 107fc686176..85c0dd72705 100644
--- a/extras/hs-test/hs_test.sh
+++ b/extras/hs-test/hs_test.sh
@@ -7,8 +7,10 @@ single_test=0
persist_set=0
unconfigure_set=0
debug_set=0
+leak_check_set=0
debug_build=
ginkgo_args=
+tc_name=
for i in "$@"
do
@@ -68,6 +70,19 @@ case "${i}" in
--repeat=*)
ginkgo_args="$ginkgo_args --repeat=${i#*=}"
;;
+ --cpu0=*)
+ cpu0="${i#*=}"
+ if [ "$cpu0" = "true" ]; then
+ args="$args -cpu0"
+ fi
+ ;;
+ --leak_check=*)
+ leak_check="${i#*=}"
+ if [ "$leak_check" = "true" ]; then
+ args="$args -leak_check"
+ leak_check_set=1
+ fi
+ ;;
esac
done
@@ -91,9 +106,42 @@ if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then
exit 1
fi
+if [ $leak_check_set -eq 1 ]; then
+ if [ $single_test -eq 0 ]; then
+ echo "a single test has to be specified when leak_check is set"
+ exit 1
+ fi
+ ginkgo_args="--focus $tc_name"
+ sudo -E go run github.com/onsi/ginkgo/v2/ginkgo $ginkgo_args -- $args
+ exit 0
+fi
+
+if [ -n "${BUILD_NUMBER}" ]; then
+ ginkgo_args="$ginkgo_args --no-color"
+fi
+
mkdir -p summary
# shellcheck disable=SC2086
-sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --no-color --trace --json-report=summary/report.json $ginkgo_args -- $args
+sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --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 \
+if [ $? != 0 ]; then
+ jq -r '.[0] | .SpecReports[] | select((.State == "failed") or (.State == "timedout") or (.State == "panicked")) | select(.Failure != null) |
+"TestName:
+ \(.LeafNodeText)
+Suite:
+ \(.Failure.FailureNodeLocation.FileName)
+Message:\n"
++(if .ReportEntries? then .ReportEntries[] | select(.Name == "VPP Backtrace") |
+"\tVPP crashed
+Full Back Trace:
+\(.Value.Representation | ltrimstr("{{red}}") | rtrimstr("{{/}}"))" else
+ "\(.Failure.Message)"
+ + (if .Failure.Message == "A spec timeout occurred" then "\n" else
+"\nFull Stack Trace:
+\(.Failure.Location.FullStackTrace)\n" end) end)' summary/report.json > summary/failed-summary.log \
&& echo "Summary generated -> summary/failed-summary.log"
+else
+ if [ -e "summary/failed-summary.log" ]; then
+ rm summary/failed-summary.log
+ fi
+fi
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index a5694bfb54b..d1d28fb893c 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -3,26 +3,37 @@ package main
import (
"bytes"
"fmt"
- "github.com/onsi/gomega/gmeasure"
"io"
+ "math/rand"
+ "net"
"net/http"
+ "net/http/httptrace"
+ "os"
+ "strconv"
+ "sync"
"time"
+ "github.com/onsi/gomega/ghttp"
+ "github.com/onsi/gomega/gmeasure"
+
. "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest)
- RegisterNoTopoTests(HeaderServerTest,
+ RegisterSoloVethTests(HttpClientGetMemLeakTest)
+ RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest,
HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest,
HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest,
HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest,
HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
- HttpHeadersTest)
- RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest)
+ HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
+ HttpClientPostFileTest, HttpClientPostFilePtrTest, AuthorityFormTargetTest, HttpRequestLineTest)
+ RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
+ PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest)
}
const wwwRootPath = "/tmp/www_root"
@@ -37,24 +48,159 @@ func httpDownloadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data in
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.AssertHttpStatus(resp, 200)
_, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
duration := time.Since(t)
experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
}
-func HttpTpsInterruptModeTest(s *NoTopoSuite) {
- HttpTpsTest(s)
+func HttpGetTpsInterruptModeTest(s *NoTopoSuite) {
+ HttpGetTpsTest(s)
}
-func HttpTpsTest(s *NoTopoSuite) {
+func HttpGetTpsTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
url := "http://" + serverAddress + ":8080/test_file_10M"
vpp.Vppctl("http tps uri tcp://0.0.0.0/8080")
- s.RunBenchmark("HTTP tps 10M", 10, 0, httpDownloadBenchmark, url)
+ s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url)
+}
+
+func httpUploadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data interface{}) {
+ url, isValid := data.(string)
+ s.AssertEqual(true, isValid)
+ body := make([]byte, 10485760)
+ _, err := rand.Read(body)
+ client := NewHttpClient()
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
+ s.AssertNil(err, fmt.Sprint(err))
+ t := time.Now()
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertHttpStatus(resp, 200)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ duration := time.Since(t)
+ experiment.RecordValue("Upload Speed", (float64(req.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
+}
+
+func HttpPostTpsInterruptModeTest(s *NoTopoSuite) {
+ HttpPostTpsTest(s)
+}
+
+func HttpPostTpsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":8080/test_file_10M"
+
+ vpp.Vppctl("http tps uri tcp://0.0.0.0/8080")
+
+ s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url)
+}
+
+func HttpPersistentConnectionTest(s *NoTopoSuite) {
+ // testing url handler app do not support multi-thread
+ s.SkipIfMultiWorker()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("test-url-handler enable"))
+
+ transport := http.DefaultTransport
+ transport.(*http.Transport).Proxy = nil
+ transport.(*http.Transport).DisableKeepAlives = false
+ client := &http.Client{
+ Transport: transport,
+ Timeout: time.Second * 30,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+ }}
+
+ body := []byte("{\"sandwich\": {\"spam\": 2, \"eggs\": 1}}")
+ req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test3", bytes.NewBuffer(body))
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpContentLength(resp, int64(0))
+ o1 := vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o1)
+ s.AssertContains(o1, "ESTABLISHED")
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test1", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ clientTrace := &httptrace.ClientTrace{
+ GotConn: func(info httptrace.GotConnInfo) {
+ s.AssertEqual(true, info.Reused, "connection not reused")
+ },
+ }
+ req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpBody(resp, "hello")
+ o2 := vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o2)
+ s.AssertContains(o2, "ESTABLISHED")
+ s.AssertEqual(o1, o2)
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test2", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertEqual(false, resp.Close)
+ s.AssertHttpBody(resp, "some data")
+ o2 = vpp.Vppctl("show session verbose proto http state ready")
+ s.Log(o2)
+ s.AssertContains(o2, "ESTABLISHED")
+ s.AssertEqual(o1, o2)
+
+}
+
+func HttpPipeliningTest(s *NoTopoSuite) {
+ // testing url handler app do not support multi-thread
+ s.SkipIfMultiWorker()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+ s.Log(vpp.Vppctl("test-url-handler enable"))
+
+ req1 := "GET /test_delayed HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n"
+ req2 := "GET /test1 HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n"
+
+ conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer conn.Close()
+ err = conn.SetDeadline(time.Now().Add(time.Second * 15))
+ s.AssertNil(err, fmt.Sprint(err))
+ n, err := conn.Write([]byte(req1))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertEqual(n, len([]rune(req1)))
+ // send second request a bit later so first is already in progress
+ time.Sleep(500 * time.Millisecond)
+ n, err = conn.Write([]byte(req2))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertEqual(n, len([]rune(req2)))
+ reply := make([]byte, 1024)
+ n, err = conn.Read(reply)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(string(reply))
+ s.AssertContains(string(reply), "delayed data", "first request response not received")
+ s.AssertNotContains(string(reply), "hello", "second request response received")
+ // make sure response for second request is not received later
+ _, err = conn.Read(reply)
+ s.AssertMatchError(err, os.ErrDeadlineExceeded, "second request response received")
}
func HttpCliTest(s *VethsSuite) {
@@ -72,11 +218,11 @@ func HttpCliTest(s *VethsSuite) {
s.Log(o)
s.AssertContains(o, "<html>", "<html> not found in the result!")
+ s.AssertContains(o, "</html>", "</html> not found in the result!")
}
func HttpCliConnectErrorTest(s *VethsSuite) {
clientContainer := s.GetContainerByName("client-vpp")
-
serverVeth := s.GetInterfaceByName(ServerInterfaceName)
uri := "http://" + serverVeth.Ip4AddressString() + "/80"
@@ -88,28 +234,432 @@ func HttpCliConnectErrorTest(s *VethsSuite) {
s.AssertContains(o, "failed to connect")
}
+func HttpClientTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("GET", "/test"),
+ ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}),
+ ghttp.VerifyHeader(http.Header{"Accept": []string{"text/html"}}),
+ ghttp.RespondWith(http.StatusOK, "<html><body><p>Hello</p></body></html>"),
+ ))
+ server.Start()
+ defer server.Close()
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+ s.Log(o)
+ s.AssertContains(o, "<html>", "<html> not found in the result!")
+ s.AssertContains(o, "</html>", "</html> not found in the result!")
+}
+
+func HttpClientErrRespTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("GET", "/test"),
+ ghttp.RespondWith(http.StatusNotFound, "404: Not Found"),
+ ))
+ server.Start()
+ defer server.Close()
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+ s.Log(o)
+ s.AssertContains(o, "404: Not Found", "error not found in the result!")
+}
+
+func HttpClientPostFormTest(s *NoTopoSuite) {
+ serverAddress := s.HostAddr()
+ body := "field1=value1&field2=value2"
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(true),
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.VerifyContentType("application/x-www-form-urlencoded"),
+ ghttp.VerifyBody([]byte(body)),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ))
+ server.Start()
+ defer server.Close()
+
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ o := vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+ s.Log(o)
+ s.AssertNotContains(o, "error")
+}
+
+func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) {
+ serverAddress := s.HostAddr()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ fileName := "/tmp/test_file.txt"
+ s.Log(vpp.Container.Exec("fallocate -l " + strconv.Itoa(fileSize) + " " + fileName))
+ s.Log(vpp.Container.Exec("ls -la " + fileName))
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ s.LogHttpReq(false),
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.VerifyHeader(http.Header{"Content-Length": []string{strconv.Itoa(fileSize)}}),
+ ghttp.VerifyContentType("application/octet-stream"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ))
+ server.Start()
+ defer server.Close()
+
+ uri := "http://" + serverAddress + "/80"
+ cmd := "http post uri " + uri + " target /test file " + fileName
+ if usePtr {
+ cmd += " use-ptr"
+ }
+ o := vpp.Vppctl(cmd)
+
+ s.Log(o)
+ s.AssertNotContains(o, "error")
+}
+
+func HttpClientPostFileTest(s *NoTopoSuite) {
+ httpClientPostFile(s, false, 32768)
+}
+
+func HttpClientPostFilePtrTest(s *NoTopoSuite) {
+ httpClientPostFile(s, true, 131072)
+}
+
+func cliTestAuthority(s *NoTopoSuite, authority string) {
+ o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
+ s.AssertNotContains(o, "error")
+ s.AssertContains(o, authority)
+}
+
+func cliTestAuthorityError(s *NoTopoSuite, authority string) {
+ o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
+ s.AssertContains(o, "error")
+}
+
+func AuthorityFormTargetTest(s *NoTopoSuite) {
+ cliTestAuthority(s, "10.10.2.45:20")
+ cliTestAuthority(s, "[dead:beef::1234]:443")
+ cliTestAuthorityError(s, "example.com:80")
+ cliTestAuthorityError(s, "10.10.2.45")
+ cliTestAuthorityError(s, "1000.10.2.45:20")
+ cliTestAuthorityError(s, "[xyz0::1234]:443")
+}
+
func HttpStaticPromTest(s *NoTopoSuite) {
- finished := make(chan error, 1)
query := "stats.prom"
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("prom enable"))
+ time.Sleep(time.Second * 5)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, false))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain")
+ s.AssertGreaterThan(resp.ContentLength, 0)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+func promReq(s *NoTopoSuite, url string) {
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", url, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.AssertHttpStatus(resp, 200)
+ _, err = io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) {
+ defer GinkgoRecover()
+ defer wg.Done()
+ promReq(s, url)
+}
+
+func PromConcurrentConnectionsTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":80/stats.prom"
+
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+ s.Log(vpp.Vppctl("prom enable"))
+ time.Sleep(time.Second * 5)
+
+ var wg sync.WaitGroup
+ for i := 0; i < 20; i++ {
+ wg.Add(1)
+ go promReqWg(s, url, &wg)
+ }
+ wg.Wait()
+ s.Log(vpp.Vppctl("show session verbose proto http"))
+}
+
+func PromMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ url := "http://" + serverAddress + ":80/stats.prom"
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
s.Log(vpp.Vppctl("prom enable"))
+ time.Sleep(time.Second * 3)
+
+ /* warmup request (FIB) */
+ promReq(s, url)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* collect stats couple of times */
+ for i := 0; i < 5; i++ {
+ time.Sleep(time.Second * 1)
+ promReq(s, url)
+ }
+
+ /* let's give it some time to clean up sessions */
time.Sleep(time.Second * 5)
- go func() {
- defer GinkgoRecover()
- s.StartWget(finished, serverAddress, "80", query, "")
- }()
- err := <-finished
- s.AssertNil(err)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
+
+func HttpClientGetMemLeakTest(s *VethsSuite) {
+ s.SkipUnlessLeakCheck()
+
+ serverContainer := s.GetContainerByName("server-vpp").VppInstance
+ clientContainer := s.GetContainerByName("client-vpp").VppInstance
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+
+ /* no goVPP less noise */
+ clientContainer.Disconnect()
+
+ serverContainer.Vppctl("http cli server")
+
+ uri := "http://" + serverVeth.Ip4AddressString() + "/80"
+
+ /* warmup request (FIB) */
+ clientContainer.Vppctl("http cli client uri " + uri + " query /show/version")
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ clientContainer.EnableMemoryTrace()
+ traces1, err := clientContainer.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ clientContainer.Vppctl("http cli client uri " + uri + " query /show/vlib/graph")
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := clientContainer.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ clientContainer.MemLeakCheck(traces1, traces2)
+}
+
+func HttpClientPostMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ serverAddress := s.HostAddr()
+ body := "field1=value1&field2=value2"
+
+ uri := "http://" + serverAddress + "/80"
+ vpp := s.GetContainerByName("vpp").VppInstance
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
+ server := ghttp.NewUnstartedServer()
+ l, err := net.Listen("tcp", serverAddress+":80")
+ s.AssertNil(err, fmt.Sprint(err))
+ server.HTTPTestServer.Listener = l
+ server.AppendHandlers(
+ ghttp.CombineHandlers(
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ),
+ ghttp.CombineHandlers(
+ ghttp.VerifyRequest("POST", "/test"),
+ ghttp.RespondWith(http.StatusOK, nil),
+ ),
+ )
+ server.Start()
+ defer server.Close()
+
+ /* warmup request (FIB) */
+ vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
+
+func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+
+ /* no goVPP less noise */
+ vpp.Disconnect()
+
+ vpp.Vppctl("http cli server")
+
+ /* warmup request (FIB) */
+ _, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+ time.Sleep(time.Second * 12)
+
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+
+ _, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+
+ /* let's give it some time to clean up sessions */
+ time.Sleep(time.Second * 12)
+
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+
+}
+
+func HttpStaticFileHandlerDefaultMaxAgeTest(s *NoTopoSuite) {
+ HttpStaticFileHandlerTestFunction(s, "default")
+}
+
+func HttpStaticFileHandlerTest(s *NoTopoSuite) {
+ HttpStaticFileHandlerTestFunction(s, "123")
+}
+
+func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) {
+ var maxAgeFormatted string
+ if max_age == "default" {
+ maxAgeFormatted = ""
+ max_age = "600"
+ } else {
+ maxAgeFormatted = "max-age " + max_age
+ }
+
+ content := "<html><body><p>Hello</p></body></html>"
+ content2 := "<html><body><p>Page</p></body></html>"
+
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ err := vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
+ s.AssertNil(err, fmt.Sprint(err))
+ err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m " + maxAgeFormatted))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5)
+ s.AssertEqual(len(resp.Header.Get("Last-Modified")), 29)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content))))
+ s.AssertHttpBody(resp, content)
+ o := vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertNotContains(o, "page.html")
+
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content))))
+ s.AssertHttpBody(resp, content)
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
+ s.AssertHttpHeaderWithValue(resp, "Cache-Control", "max-age="+max_age)
+ s.AssertHttpContentLength(resp, int64(len([]rune(content2))))
+ s.AssertHttpBody(resp, content2)
+ o = vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertContains(o, "page.html")
}
func HttpStaticPathTraversalTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
vpp.Container.Exec("mkdir -p " + wwwRootPath)
vpp.Container.Exec("mkdir -p " + "/tmp/secret_folder")
- vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ err := vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
client := NewHttpClient()
@@ -118,14 +668,19 @@ func HttpStaticPathTraversalTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(404, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 404)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
}
func 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()
+ err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<html><body><p>Hello</p></body></html>")
+ s.AssertNil(err, fmt.Sprint(err))
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
client := NewHttpClient()
@@ -134,14 +689,18 @@ func HttpStaticMovedTest(s *NoTopoSuite) {
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"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 301)
+ s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/tmp.aaa/index.html")
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpStaticNotFoundTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
vpp.Container.Exec("mkdir -p " + wwwRootPath)
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
client := NewHttpClient()
@@ -150,12 +709,16 @@ func HttpStaticNotFoundTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(404, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 404)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpHeaderNotPresent(resp, "Cache-Control")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
client := NewHttpClient()
@@ -164,14 +727,16 @@ func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
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"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 405)
+ s.AssertHttpHeaderWithValue(resp, "Allow", "GET", "server MUST generate an Allow header")
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpCliBadRequestTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
client := NewHttpClient()
@@ -180,12 +745,15 @@ func HttpCliBadRequestTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(400, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 400)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
client := NewHttpClient()
@@ -194,7 +762,8 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "vpp_details")
@@ -203,11 +772,12 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
s.AssertNotContains(string(data), "build_by")
s.AssertNotContains(string(data), "build_host")
s.AssertNotContains(string(data), "build_dir")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
client := NewHttpClient()
@@ -216,7 +786,8 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "vpp_details")
@@ -225,11 +796,12 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
s.AssertContains(string(data), "build_by")
s.AssertContains(string(data), "build_host")
s.AssertContains(string(data), "build_dir")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
client := NewHttpClient()
@@ -238,16 +810,18 @@ func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "interface_list")
- s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertContains(string(data), s.VppIfName())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
client := NewHttpClient()
@@ -256,26 +830,28 @@ func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "interface_stats")
s.AssertContains(string(data), "local0")
- s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertContains(string(data), s.VppIfName())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
func validatePostInterfaceStats(s *NoTopoSuite, data string) {
s.AssertContains(data, "interface_stats")
- s.AssertContains(data, s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertContains(data, s.VppIfName())
s.AssertNotContains(data, "error")
s.AssertNotContains(data, "local0")
}
func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- body := []byte(s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ body := []byte(s.VppIfName())
client := NewHttpClient()
req, err := http.NewRequest("POST",
@@ -284,17 +860,19 @@ func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
validatePostInterfaceStats(s, string(data))
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
}
func HttpStaticMacTimeTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- s.Log(vpp.Vppctl("mactime enable-disable " + s.GetInterfaceByName(TapInterfaceName).Peer.Name()))
+ s.Log(vpp.Vppctl("mactime enable-disable " + s.VppIfName()))
client := NewHttpClient()
req, err := http.NewRequest("GET", "http://"+serverAddress+":80/mactime.json", nil)
@@ -302,20 +880,38 @@ func HttpStaticMacTimeTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "mactime")
- s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Ip4AddressString())
+ s.AssertContains(string(data), s.HostAddr())
s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json")
+ parsedTime, err := time.Parse(time.RFC1123, resp.Header.Get("Date"))
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertTimeEqualWithinThreshold(parsedTime, time.Now(), time.Minute*5)
+ s.AssertEqual(len(resp.Header.Get("Date")), 29)
}
func HttpInvalidRequestLineTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
- resp, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1")
+ resp, err := TcpSendReceive(serverAddress+":80", " GET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "\rGET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "\nGET / HTTP/1.1")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed")
+
+ resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1")
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed")
@@ -344,9 +940,20 @@ func HttpInvalidRequestLineTest(s *NoTopoSuite) {
s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed")
}
+func HttpRequestLineTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ vpp.Vppctl("http cli server")
+
+ resp, err := TcpSendReceive(serverAddress+":80", "\r\nGET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\n\r\n")
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(resp, "HTTP/1.1 200 OK")
+ s.AssertContains(resp, "<html>", "html content not found")
+}
+
func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
resp, err := TcpSendReceive(serverAddress+":80", "GET /interface|stats.json HTTP/1.1\r\n\r\n")
@@ -394,7 +1001,7 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) {
func HttpInvalidContentLengthTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length:\r\n\r\n")
@@ -413,9 +1020,9 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) {
func HttpContentLengthTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
- ifName := s.GetInterfaceByName(TapInterfaceName).Peer.Name()
+ ifName := s.VppIfName()
resp, err := TcpSendReceive(serverAddress+":80",
"POST /interface_stats.json HTTP/1.1\r\nContent-Length:4\r\n\r\n"+ifName)
@@ -435,7 +1042,7 @@ func HttpContentLengthTest(s *NoTopoSuite) {
func HttpMethodNotImplementedTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
client := NewHttpClient()
@@ -444,12 +1051,15 @@ func HttpMethodNotImplementedTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(501, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 501)
+ s.AssertHttpHeaderNotPresent(resp, "Content-Type")
+ s.AssertHttpContentLength(resp, int64(0))
}
func HttpVersionNotSupportedTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
resp, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/2\r\n\r\n")
@@ -459,7 +1069,7 @@ func HttpVersionNotSupportedTest(s *NoTopoSuite) {
func HttpUriDecodeTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
client := NewHttpClient()
@@ -468,17 +1078,18 @@ func HttpUriDecodeTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual(200, resp.StatusCode)
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
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")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
}
func HttpHeadersTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
resp, err := TcpSendReceive(
@@ -486,13 +1097,13 @@ func HttpHeadersTest(s *NoTopoSuite) {
"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.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()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nUser-Agent: test\r\n")
@@ -530,7 +1141,7 @@ func HttpInvalidHeadersTest(s *NoTopoSuite) {
func HeaderServerTest(s *NoTopoSuite) {
vpp := s.GetContainerByName("vpp").VppInstance
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
client := NewHttpClient()
@@ -539,5 +1150,8 @@ func HeaderServerTest(s *NoTopoSuite) {
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
- s.AssertEqual("http_cli_server", resp.Header.Get("Server"))
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Server", "http_cli_server")
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html")
}
diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go
index 1dd82809f8a..8ec9b8cd02c 100644
--- a/extras/hs-test/infra/container.go
+++ b/extras/hs-test/infra/container.go
@@ -1,13 +1,25 @@
package hst
import (
+ "bytes"
+ "context"
"fmt"
"os"
"os/exec"
+ "regexp"
+ "slices"
+ "strconv"
"strings"
"text/template"
"time"
+ "github.com/docker/go-units"
+
+ "github.com/cilium/cilium/pkg/sysctl"
+ containerTypes "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/image"
+ "github.com/docker/docker/pkg/stdcopy"
"github.com/edwarnicke/exechelper"
. "github.com/onsi/ginkgo/v2"
)
@@ -32,12 +44,14 @@ type Container struct {
IsOptional bool
RunDetached bool
Name string
+ ID string
Image string
ExtraRunningArgs string
Volumes map[string]Volume
EnvVars map[string]string
VppInstance *VppInstance
AllocatedCpus []int
+ ctx context.Context
}
func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
@@ -52,6 +66,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error
container.EnvVars = make(map[string]string)
container.Name = containerName
container.Suite = suite
+ container.ctx = context.Background()
if Image, ok := yamlInput["image"]; ok {
container.Image = Image.(string)
@@ -133,8 +148,6 @@ func (c *Container) GetContainerWorkDir() (res string) {
func (c *Container) getContainerArguments() string {
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 != "" {
@@ -145,22 +158,58 @@ func (c *Container) getContainerArguments() string {
return args
}
-func (c *Container) runWithRetry(cmd string) error {
- nTries := 5
- for i := 0; i < nTries; i++ {
- err := exechelper.Run(cmd)
- if err == nil {
- return nil
- }
- time.Sleep(1 * time.Second)
- }
- return fmt.Errorf("failed to run container command")
+func (c *Container) PullDockerImage(name string, ctx context.Context) {
+ // "func (*Client) ImagePull" doesn't work, returns "No such image"
+ c.Suite.Log("Pulling image: " + name)
+ _, err := exechelper.CombinedOutput("docker pull " + name)
+ c.Suite.AssertNil(err)
}
+// Creates a container
func (c *Container) Create() error {
- cmd := "docker create " + c.getContainerArguments()
- c.Suite.Log(cmd)
- return exechelper.Run(cmd)
+ var sliceOfImageNames []string
+ images, err := c.Suite.Docker.ImageList(c.ctx, image.ListOptions{})
+ c.Suite.AssertNil(err)
+
+ for _, image := range images {
+ sliceOfImageNames = append(sliceOfImageNames, strings.Split(image.RepoTags[0], ":")[0])
+ }
+ if !slices.Contains(sliceOfImageNames, c.Image) {
+ c.PullDockerImage(c.Image, c.ctx)
+ }
+
+ c.allocateCpus()
+ cpuSet := fmt.Sprintf("%d-%d", c.AllocatedCpus[0], c.AllocatedCpus[len(c.AllocatedCpus)-1])
+ resp, err := c.Suite.Docker.ContainerCreate(
+ c.ctx,
+ &containerTypes.Config{
+ Hostname: c.Name,
+ Image: c.Image,
+ Env: c.getEnvVars(),
+ Cmd: strings.Split(c.ExtraRunningArgs, " "),
+ },
+ &containerTypes.HostConfig{
+ Resources: containerTypes.Resources{
+ Ulimits: []*units.Ulimit{
+ {
+ Name: "nofile",
+ Soft: 90000,
+ Hard: 90000,
+ },
+ },
+ CpusetCpus: cpuSet,
+ },
+ CapAdd: []string{"ALL"},
+ Privileged: true,
+ NetworkMode: "host",
+ Binds: c.getVolumesAsSlice(),
+ },
+ nil,
+ nil,
+ c.Name,
+ )
+ c.ID = resp.ID
+ return err
}
func (c *Container) allocateCpus() {
@@ -169,10 +218,66 @@ func (c *Container) allocateCpus() {
c.Suite.Log("Allocated CPUs " + fmt.Sprint(c.AllocatedCpus) + " to container " + c.Name)
}
+// Starts a container
func (c *Container) Start() error {
- cmd := "docker start " + c.Name
- c.Suite.Log(cmd)
- return c.runWithRetry(cmd)
+ var err error
+ var nTries int
+
+ for nTries = 0; nTries < 5; nTries++ {
+ err = c.Suite.Docker.ContainerStart(c.ctx, c.ID, containerTypes.StartOptions{})
+ if err == nil {
+ continue
+ }
+ c.Suite.Log("Error while starting " + c.Name + ". Retrying...")
+ time.Sleep(1 * time.Second)
+ }
+ if nTries >= 5 {
+ return err
+ }
+
+ // wait for container to start
+ time.Sleep(1 * time.Second)
+
+ // check if container exited right after startup
+ containers, err := c.Suite.Docker.ContainerList(c.ctx, containerTypes.ListOptions{
+ All: true,
+ Filters: filters.NewArgs(filters.Arg("name", c.Name)),
+ })
+ if err != nil {
+ return err
+ }
+ if containers[0].State == "exited" {
+ c.Suite.Log("Container details: " + fmt.Sprint(containers[0]))
+ return fmt.Errorf("Container %s exited: '%s'", c.Name, containers[0].Status)
+ }
+
+ return err
+}
+
+func (c *Container) GetOutput() (string, string) {
+ // Wait for the container to finish executing
+ statusCh, errCh := c.Suite.Docker.ContainerWait(c.ctx, c.ID, containerTypes.WaitConditionNotRunning)
+ select {
+ case err := <-errCh:
+ c.Suite.AssertNil(err)
+ case <-statusCh:
+ }
+
+ // Get the logs from the container
+ logOptions := containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true}
+ logReader, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions)
+ c.Suite.AssertNil(err)
+ defer logReader.Close()
+
+ var stdoutBuf, stderrBuf bytes.Buffer
+
+ // Use stdcopy.StdCopy to demultiplex the multiplexed stream
+ _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader)
+ c.Suite.AssertNil(err)
+
+ stdout := stdoutBuf.String()
+ stderr := stderrBuf.String()
+ return stdout, stderr
}
func (c *Container) prepareCommand() (string, error) {
@@ -180,7 +285,7 @@ func (c *Container) prepareCommand() (string, error) {
return "", fmt.Errorf("run container failed: name is blank")
}
- cmd := "docker run "
+ cmd := "docker exec "
if c.RunDetached {
cmd += " -d"
}
@@ -201,22 +306,44 @@ func (c *Container) CombinedOutput() (string, error) {
return string(byteOutput), err
}
-func (c *Container) Run() error {
- cmd, err := c.prepareCommand()
- if err != nil {
- return err
- }
- return c.runWithRetry(cmd)
+// Creates and starts a container
+func (c *Container) Run() {
+ c.Suite.AssertNil(c.Create())
+ c.Suite.AssertNil(c.Start())
}
func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
var volume Volume
- volume.HostDir = hostDir
+ volume.HostDir = strings.Replace(hostDir, "volumes", c.Suite.GetTestId()+"/"+"volumes", 1)
volume.ContainerDir = containerDir
volume.IsDefaultWorkDir = isDefaultWorkDir
c.Volumes[hostDir] = volume
}
+func (c *Container) getVolumesAsSlice() []string {
+ var volumeSlice []string
+
+ if *VppSourceFileDir != "" {
+ volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", *VppSourceFileDir, *VppSourceFileDir))
+ }
+
+ core_pattern, err := sysctl.Read("kernel.core_pattern")
+ if err == nil {
+ index := strings.LastIndex(core_pattern, "/")
+ core_pattern = core_pattern[:index]
+ volumeSlice = append(volumeSlice, c.Suite.getLogDirPath()+":"+core_pattern)
+ } else {
+ c.Suite.Log(err)
+ }
+
+ if len(c.Volumes) > 0 {
+ for _, volume := range c.Volumes {
+ volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", volume.HostDir, volume.ContainerDir))
+ }
+ }
+ return volumeSlice
+}
+
func (c *Container) getVolumesAsCliOption() string {
cliOption := ""
@@ -245,10 +372,23 @@ func (c *Container) getEnvVarsAsCliOption() string {
return cliOption
}
+func (c *Container) getEnvVars() []string {
+ var envVars []string
+ if len(c.EnvVars) == 0 {
+ return envVars
+ }
+
+ for name, value := range c.EnvVars {
+ envVars = append(envVars, fmt.Sprintf("%s=%s", name, value))
+ }
+ return envVars
+}
+
func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) {
vpp := new(VppInstance)
vpp.Container = c
vpp.Cpus = cpus
+ vpp.setDefaultCpuConfig()
vpp.AdditionalConfig = append(vpp.AdditionalConfig, additionalConfigs...)
c.VppInstance = vpp
return vpp, nil
@@ -276,6 +416,11 @@ func (c *Container) CreateFile(destFileName string, content string) error {
return nil
}
+func (c *Container) GetFile(sourceFileName, targetFileName string) error {
+ cmd := exec.Command("docker", "cp", c.Name+":"+sourceFileName, targetFileName)
+ return cmd.Run()
+}
+
/*
* Executes in detached mode so that the started application can continue to run
* without blocking execution of test
@@ -300,49 +445,57 @@ func (c *Container) Exec(command string, arguments ...any) string {
return string(byteOutput)
}
-func (c *Container) getLogDirPath() string {
- testId := c.Suite.GetTestId()
- testName := c.Suite.GetCurrentTestName()
- logDirPath := logDir + testName + "/" + testId + "/"
-
- cmd := exec.Command("mkdir", "-p", logDirPath)
- if err := cmd.Run(); err != nil {
- Fail("mkdir error: " + fmt.Sprint(err))
- }
-
- return logDirPath
-}
-
func (c *Container) saveLogs() {
- testLogFilePath := c.getLogDirPath() + "container-" + c.Name + ".log"
+ testLogFilePath := c.Suite.getLogDirPath() + "container-" + c.Name + ".log"
- cmd := exec.Command("docker", "logs", "--details", "-t", c.Name)
- c.Suite.Log(cmd)
- output, err := cmd.CombinedOutput()
+ logs, err := c.log(0)
if err != nil {
c.Suite.Log(err)
+ return
}
f, err := os.Create(testLogFilePath)
if err != nil {
- Fail("file create error: " + fmt.Sprint(err))
+ c.Suite.Log(err)
+ return
}
- fmt.Fprint(f, string(output))
- f.Close()
+ defer f.Close()
+ fmt.Fprint(f, logs)
}
-// Outputs logs from docker containers. Set 'maxLines' to 0 to output the full log.
+// Returns logs from docker containers. Set 'maxLines' to 0 to output the full log.
func (c *Container) log(maxLines int) (string, error) {
- var cmd string
+ var logOptions containerTypes.LogsOptions
if maxLines == 0 {
- cmd = "docker logs " + c.Name
+ logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true, Timestamps: true}
} else {
- cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.Name)
+ logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true, Tail: strconv.Itoa(maxLines)}
}
- c.Suite.Log(cmd)
- o, err := exechelper.CombinedOutput(cmd)
- return string(o), err
+ out, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions)
+ if err != nil {
+ c.Suite.Log(err)
+ return "", err
+ }
+ defer out.Close()
+
+ var stdoutBuf, stderrBuf bytes.Buffer
+
+ _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out)
+ if err != nil {
+ c.Suite.Log(err)
+ }
+
+ stdout := stdoutBuf.String()
+ stderr := stderrBuf.String()
+
+ re := regexp.MustCompile("(?m)^.*==> /dev/null <==.*$[\r\n]+")
+ stdout = re.ReplaceAllString(stdout, "")
+
+ re = regexp.MustCompile("(?m)^.*tail: cannot open '' for reading: No such file or directory.*$[\r\n]+")
+ stderr = re.ReplaceAllString(stderr, "")
+
+ return stdout + stderr, err
}
func (c *Container) stop() error {
@@ -352,8 +505,13 @@ func (c *Container) stop() error {
}
c.VppInstance = nil
c.saveLogs()
- c.Suite.Log("docker stop " + c.Name + " -t 0")
- return exechelper.Run("docker stop " + c.Name + " -t 0")
+
+ c.Suite.Log("Stopping container " + c.Name)
+ timeout := 0
+ if err := c.Suite.Docker.ContainerStop(c.ctx, c.ID, containerTypes.StopOptions{Timeout: &timeout}); err != nil {
+ return err
+ }
+ return nil
}
func (c *Container) CreateConfig(targetConfigName string, templateName string, values any) {
diff --git a/extras/hs-test/infra/cpu.go b/extras/hs-test/infra/cpu.go
index b5555d85b98..615f8a3f87d 100644
--- a/extras/hs-test/infra/cpu.go
+++ b/extras/hs-test/infra/cpu.go
@@ -4,10 +4,12 @@ import (
"bufio"
"errors"
"fmt"
- . "github.com/onsi/ginkgo/v2"
"os"
"os/exec"
+ "strconv"
"strings"
+
+ . "github.com/onsi/ginkgo/v2"
)
var CgroupPath = "/sys/fs/cgroup/"
@@ -18,80 +20,188 @@ type CpuContext struct {
}
type CpuAllocatorT struct {
- cpus []int
+ cpus []int
+ runningInCi bool
+ buildNumber int
+ maxContainerCount int
+}
+
+func iterateAndAppend(start int, end int, slice []int) []int {
+ for i := start; i <= end; i++ {
+ slice = append(slice, i)
+ }
+ return slice
}
var cpuAllocator *CpuAllocatorT = nil
func (c *CpuAllocatorT) Allocate(containerCount int, nCpus int) (*CpuContext, error) {
var cpuCtx CpuContext
+ // indexes, not actual cores
+ var minCpu, maxCpu int
- // 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 c.runningInCi {
+ minCpu = ((c.buildNumber) * c.maxContainerCount * nCpus)
+ maxCpu = ((c.buildNumber + 1) * c.maxContainerCount * nCpus) - 1
+ } else {
+ minCpu = ((GinkgoParallelProcess() - 1) * c.maxContainerCount * nCpus)
+ maxCpu = (GinkgoParallelProcess() * c.maxContainerCount * nCpus) - 1
+ }
if len(c.cpus)-1 < maxCpu {
- err := fmt.Errorf("could not allocate %d CPUs; available: %d; attempted to allocate cores %d-%d",
- nCpus*containerCount, len(c.cpus)-1, minCpu, maxCpu)
+ err := fmt.Errorf("could not allocate %d CPUs; available count: %d; attempted to allocate cores with index %d-%d; max index: %d;\n"+
+ "available cores: %v", nCpus*containerCount, len(c.cpus), minCpu, maxCpu, len(c.cpus)-1, c.cpus)
return nil, err
}
+
if containerCount == 1 {
cpuCtx.cpus = c.cpus[minCpu : minCpu+nCpus]
- } else if containerCount > 1 && containerCount <= maxContainerCount {
+ } else if containerCount > 1 && containerCount <= c.maxContainerCount {
cpuCtx.cpus = c.cpus[minCpu+(nCpus*(containerCount-1)) : minCpu+(nCpus*containerCount)]
} else {
- return nil, fmt.Errorf("too many containers; CPU allocation for >%d containers is not implemented", maxContainerCount)
+ return nil, fmt.Errorf("too many containers; CPU allocation for >%d containers is not implemented", c.maxContainerCount)
}
-
cpuCtx.cpuAllocator = c
return &cpuCtx, nil
}
func (c *CpuAllocatorT) readCpus() error {
- var first, last int
-
- // Path depends on cgroup version. We need to check which version is in use.
- // For that following command can be used: 'stat -fc %T /sys/fs/cgroup/'
- // In case the output states 'cgroup2fs' then cgroups v2 is used, 'tmpfs' in case cgroups v1.
- cmd := exec.Command("stat", "-fc", "%T", "/sys/fs/cgroup/")
- byteOutput, err := cmd.CombinedOutput()
- if err != nil {
- return err
- }
- CpuPath := CgroupPath
- if strings.Contains(string(byteOutput), "tmpfs") {
- CpuPath += "cpuset/cpuset.effective_cpus"
- } else if strings.Contains(string(byteOutput), "cgroup2fs") {
- CpuPath += "cpuset.cpus.effective"
+ var first, second, third, fourth int
+ var file *os.File
+ var err error
+
+ if c.runningInCi {
+ // non-debug build runs on node0, debug on node1
+ if *IsDebugBuild {
+ file, err = os.Open("/sys/devices/system/node/node1/cpulist")
+ } else {
+ file, err = os.Open("/sys/devices/system/node/node0/cpulist")
+ }
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d,%d-%d", &first, &second, &third, &fourth)
+ if err != nil {
+ return err
+ }
+
+ c.cpus = iterateAndAppend(first, second, c.cpus)
+ c.cpus = iterateAndAppend(third, fourth, c.cpus)
+ } else if NumaAwareCpuAlloc {
+ var fifth, sixth int
+ var tmpCpus []int
+
+ file, err := os.Open("/sys/devices/system/node/online")
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ // get numa node range
+ _, err = fmt.Sscanf(line, "%d-%d", &first, &second)
+ if err != nil {
+ return err
+ }
+
+ for i := first; i <= second; i++ {
+ file, err := os.Open("/sys/devices/system/node/node" + fmt.Sprint(i) + "/cpulist")
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ // get numa node cores
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d,%d-%d", &third, &fourth, &fifth, &sixth)
+ if err != nil {
+ return err
+ }
+
+ // get numa node cores from first range
+ tmpCpus = iterateAndAppend(third, fourth, tmpCpus)
+
+ // discard cpu 0
+ if tmpCpus[0] == 0 && !*UseCpu0 {
+ tmpCpus = tmpCpus[1:]
+ }
+
+ // get numa node cores from second range
+ tmpCpus = iterateAndAppend(fifth, sixth, tmpCpus)
+
+ // make c.cpus divisible by maxContainerCount * nCpus, so we don't have to check which numa will be used
+ // and we can use offsets
+ count_to_remove := len(tmpCpus) % (c.maxContainerCount * *NConfiguredCpus)
+ c.cpus = append(c.cpus, tmpCpus[:len(tmpCpus)-count_to_remove]...)
+ tmpCpus = tmpCpus[:0]
+ }
} else {
- return errors.New("cgroup unknown fs: " + string(byteOutput))
- }
+ // Path depends on cgroup version. We need to check which version is in use.
+ // For that following command can be used: 'stat -fc %T /sys/fs/cgroup/'
+ // In case the output states 'cgroup2fs' then cgroups v2 is used, 'tmpfs' in case cgroups v1.
+ cmd := exec.Command("stat", "-fc", "%T", "/sys/fs/cgroup/")
+ byteOutput, err := cmd.CombinedOutput()
+ if err != nil {
+ return err
+ }
- file, err := os.Open(CpuPath)
- if err != nil {
- return err
- }
- defer file.Close()
-
- sc := bufio.NewScanner(file)
- sc.Scan()
- line := sc.Text()
- _, err = fmt.Sscanf(line, "%d-%d", &first, &last)
- if err != nil {
- return err
+ CpuPath := CgroupPath
+ if strings.Contains(string(byteOutput), "tmpfs") {
+ CpuPath += "cpuset/cpuset.effective_cpus"
+ } else if strings.Contains(string(byteOutput), "cgroup2fs") {
+ CpuPath += "cpuset.cpus.effective"
+ } else {
+ return errors.New("cgroup unknown fs: " + string(byteOutput))
+ }
+
+ file, err := os.Open(CpuPath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ sc := bufio.NewScanner(file)
+ sc.Scan()
+ line := sc.Text()
+ _, err = fmt.Sscanf(line, "%d-%d", &first, &second)
+ if err != nil {
+ return err
+ }
+ c.cpus = iterateAndAppend(first, second, c.cpus)
}
- for i := first; i <= last; i++ {
- c.cpus = append(c.cpus, i)
+
+ // discard cpu 0
+ if c.cpus[0] == 0 && !*UseCpu0 {
+ c.cpus = c.cpus[1:]
}
return nil
}
func CpuAllocator() (*CpuAllocatorT, error) {
if cpuAllocator == nil {
+ var err error
cpuAllocator = new(CpuAllocatorT)
- err := cpuAllocator.readCpus()
+ cpuAllocator.maxContainerCount = 4
+ buildNumberStr := os.Getenv("BUILD_NUMBER")
+
+ if buildNumberStr != "" {
+ cpuAllocator.runningInCi = true
+ // get last digit of build number
+ cpuAllocator.buildNumber, err = strconv.Atoi(buildNumberStr[len(buildNumberStr)-1:])
+ if err != nil {
+ return nil, err
+ }
+ }
+ err = cpuAllocator.readCpus()
if err != nil {
return nil, err
}
diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go
index a6ba14676d0..234a8409ea0 100644
--- a/extras/hs-test/infra/hst_suite.go
+++ b/extras/hs-test/infra/hst_suite.go
@@ -2,22 +2,27 @@ package hst
import (
"bufio"
- "errors"
"flag"
"fmt"
"io"
"log"
+ "net/http"
+ "net/http/httputil"
"os"
"os/exec"
"path/filepath"
"runtime"
+ "strconv"
"strings"
"time"
+ "github.com/edwarnicke/exechelper"
+
+ containerTypes "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/client"
"github.com/onsi/gomega/gmeasure"
"gopkg.in/yaml.v3"
- "github.com/edwarnicke/exechelper"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -33,6 +38,10 @@ var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build")
+var UseCpu0 = flag.Bool("cpu0", false, "use cpu0")
+var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests")
+var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
+var NumaAwareCpuAlloc bool
var SuiteTimeout time.Duration
type HstSuite struct {
@@ -45,11 +54,27 @@ type HstSuite struct {
TestIds map[string]string
CpuAllocator *CpuAllocatorT
CpuContexts []*CpuContext
- CpuPerVpp int
+ CpuCount int
Ppid string
ProcessIndex string
Logger *log.Logger
LogFile *os.File
+ Docker *client.Client
+}
+
+// used for colorful ReportEntry
+type StringerStruct struct {
+ Label string
+}
+
+// ColorableString for ReportEntry to use
+func (s StringerStruct) ColorableString() string {
+ return fmt.Sprintf("{{red}}%s{{/}}", s.Label)
+}
+
+// non-colorable String() is used by go's string formatting support but ignored by ReportEntry
+func (s StringerStruct) String() string {
+ return s.Label
}
func getTestFilename() string {
@@ -57,8 +82,29 @@ func getTestFilename() string {
return filepath.Base(filename)
}
+func (s *HstSuite) getLogDirPath() string {
+ testId := s.GetTestId()
+ testName := s.GetCurrentTestName()
+ logDirPath := logDir + testName + "/" + testId + "/"
+
+ cmd := exec.Command("mkdir", "-p", logDirPath)
+ if err := cmd.Run(); err != nil {
+ Fail("mkdir error: " + fmt.Sprint(err))
+ }
+
+ return logDirPath
+}
+
+func (s *HstSuite) newDockerClient() {
+ var err error
+ s.Docker, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+ s.AssertNil(err)
+ s.Log("docker client created")
+}
+
func (s *HstSuite) SetupSuite() {
s.CreateLogger()
+ s.newDockerClient()
s.Log("Suite Setup")
RegisterFailHandler(func(message string, callerSkip ...int) {
s.HstFail()
@@ -73,12 +119,15 @@ func (s *HstSuite) SetupSuite() {
if err != nil {
Fail("failed to init cpu allocator: " + fmt.Sprint(err))
}
- s.CpuPerVpp = *NConfiguredCpus
+ s.CpuCount = *NConfiguredCpus
}
func (s *HstSuite) AllocateCpus() []int {
- cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuPerVpp)
- s.AssertNil(err)
+ cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount)
+ // using Fail instead of AssertNil to make error message more readable
+ if err != nil {
+ Fail(fmt.Sprint(err))
+ }
s.AddCpuContext(cpuCtx)
return cpuCtx.cpus
}
@@ -89,6 +138,7 @@ func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
func (s *HstSuite) TearDownSuite() {
defer s.LogFile.Close()
+ defer s.Docker.Close()
s.Log("Suite Teardown")
s.UnconfigureNetworkTopology()
}
@@ -98,9 +148,12 @@ func (s *HstSuite) TearDownTest() {
if *IsPersistent {
return
}
+ s.WaitForCoreDump()
s.ResetContainers()
- s.RemoveVolumes()
- s.Ip4AddrAllocator.DeleteIpAddresses()
+
+ if s.Ip4AddrAllocator != nil {
+ s.Ip4AddrAllocator.DeleteIpAddresses()
+ }
}
func (s *HstSuite) SkipIfUnconfiguring() {
@@ -113,18 +166,9 @@ 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 {
@@ -171,7 +215,7 @@ func (s *HstSuite) HstFail() {
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())
+ s.Log("The container might not be running - check logs in " + s.getLogDirPath())
continue
}
s.Log("\nvvvvvvvvvvvvvvv " +
@@ -183,31 +227,67 @@ func (s *HstSuite) HstFail() {
}
func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).To(BeNil(), msgAndArgs...)
+ ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
}
func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).ToNot(BeNil(), msgAndArgs...)
+ ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
}
func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- Expect(actual).To(Equal(expected), msgAndArgs...)
+ ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
}
func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- Expect(actual).ToNot(Equal(expected), msgAndArgs...)
+ ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
}
func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- Expect(testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
+ ExpectWithOffset(2, testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
}
func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- Expect(testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
+ ExpectWithOffset(2, testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
}
func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
- Expect(object).ToNot(BeEmpty(), msgAndArgs...)
+ ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
+}
+
+func (s *HstSuite) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
}
func (s *HstSuite) CreateLogger() {
@@ -242,6 +322,22 @@ func (s *HstSuite) SkipIfMultiWorker(args ...any) {
}
}
+func (s *HstSuite) SkipIfNotEnoughAvailableCpus() bool {
+ var MaxRequestedCpu int
+
+ if s.CpuAllocator.runningInCi {
+ MaxRequestedCpu = ((s.CpuAllocator.buildNumber + 1) * s.CpuAllocator.maxContainerCount * s.CpuCount)
+ } else {
+ MaxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount)
+ }
+
+ if len(s.CpuAllocator.cpus)-1 < MaxRequestedCpu {
+ s.Skip(fmt.Sprintf("test case cannot allocate requested cpus (%d cpus * %d containers)", s.CpuCount, s.CpuAllocator.maxContainerCount))
+ }
+
+ return true
+}
+
func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
imageName := "hs-test/nginx-http3"
@@ -256,18 +352,81 @@ func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
}
}
-func (s *HstSuite) ResetContainers() {
- for _, container := range s.StartedContainers {
- container.stop()
- exechelper.Run("docker rm " + container.Name)
+func (s *HstSuite) SkipUnlessLeakCheck() {
+ if !*IsLeakCheck {
+ s.Skip("leak-check tests excluded")
}
}
-func (s *HstSuite) RemoveVolumes() {
- for _, volumeName := range s.Volumes {
- cmd := "docker volume rm " + volumeName
- exechelper.Run(cmd)
- os.RemoveAll(volumeName)
+func (s *HstSuite) WaitForCoreDump() {
+ var filename string
+ dir, err := os.Open(s.getLogDirPath())
+ if err != nil {
+ s.Log(err)
+ return
+ }
+ defer dir.Close()
+
+ files, err := dir.Readdirnames(0)
+ if err != nil {
+ s.Log(err)
+ return
+ }
+ for _, file := range files {
+ if strings.Contains(file, "core") {
+ filename = file
+ }
+ }
+ timeout := 60
+ waitTime := 5
+
+ if filename != "" {
+ corePath := s.getLogDirPath() + filename
+ s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", corePath))
+ for i := waitTime; i <= timeout; i += waitTime {
+ fileInfo, err := os.Stat(corePath)
+ if err != nil {
+ s.Log("Error while reading file info: " + fmt.Sprint(err))
+ return
+ }
+ currSize := fileInfo.Size()
+ s.Log(fmt.Sprintf("Waiting %ds/%ds...", i, timeout))
+ time.Sleep(time.Duration(waitTime) * time.Second)
+ fileInfo, _ = os.Stat(corePath)
+
+ if currSize == fileInfo.Size() {
+ debug := ""
+ if *IsDebugBuild {
+ debug = "_debug"
+ }
+ vppBinPath := fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug)
+ pluginsLibPath := fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/x86_64-linux-gnu/vpp_plugins", debug)
+ cmd := fmt.Sprintf("sudo gdb %s -c %s -ex 'set solib-search-path %s/%s' -ex 'bt full' -batch", vppBinPath, corePath, *VppSourceFileDir, pluginsLibPath)
+ s.Log(cmd)
+ output, _ := exechelper.Output(cmd)
+ AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)})
+ os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644))
+ if s.CpuAllocator.runningInCi {
+ err = os.Remove(corePath)
+ if err == nil {
+ s.Log("removed " + corePath)
+ } else {
+ s.Log(err)
+ }
+ }
+ return
+ }
+ }
+ }
+}
+
+func (s *HstSuite) ResetContainers() {
+ for _, container := range s.StartedContainers {
+ container.stop()
+ s.Log("Removing container " + container.Name)
+ if err := s.Docker.ContainerRemove(container.ctx, container.ID, containerTypes.RemoveOptions{RemoveVolumes: true}); err != nil {
+ s.Log(err)
+ }
}
}
@@ -448,89 +607,12 @@ func (s *HstSuite) GetPortFromPpid() string {
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),
+RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
passing in suite context, experiment and your data.
You can also instruct runBenchmark to run with multiple concurrent workers.
+Note that if running in parallel Gomega returns from Sample when spins up all samples and does not wait until all finished.
You can record multiple named measurements (float64 or duration) within passed-in callback.
runBenchmark then produces report to show statistical distribution of measurements.
*/
@@ -543,3 +625,19 @@ func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callba
}, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
AddReportEntry(experiment.Name, experiment)
}
+
+/*
+LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
+
+You should put it at the first place, so request is logged always.
+*/
+func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ dump, err := httputil.DumpRequest(req, body)
+ if err == nil {
+ s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
+ string(dump) +
+ "\n------------------------------\n")
+ }
+ }
+}
diff --git a/extras/hs-test/infra/suite_cpu_pinning.go b/extras/hs-test/infra/suite_cpu_pinning.go
new file mode 100644
index 00000000000..355fdf96604
--- /dev/null
+++ b/extras/hs-test/infra/suite_cpu_pinning.go
@@ -0,0 +1,113 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+var cpuPinningTests = map[string][]func(s *CpuPinningSuite){}
+var cpuPinningSoloTests = map[string][]func(s *CpuPinningSuite){}
+
+type CpuPinningSuite struct {
+ HstSuite
+ previousMaxContainerCount int
+}
+
+func RegisterCpuPinningTests(tests ...func(s *CpuPinningSuite)) {
+ cpuPinningTests[getTestFilename()] = tests
+}
+
+func RegisterCpuPinningSoloTests(tests ...func(s *CpuPinningSuite)) {
+ cpuPinningSoloTests[getTestFilename()] = tests
+}
+
+func (s *CpuPinningSuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("tap")
+ s.LoadContainerTopology("singleCpuPinning")
+}
+
+func (s *CpuPinningSuite) SetupTest() {
+ // Skip if we cannot allocate 3 CPUs for test container
+ s.previousMaxContainerCount = s.CpuAllocator.maxContainerCount
+ s.CpuCount = 3
+ s.CpuAllocator.maxContainerCount = 1
+ s.SkipIfNotEnoughAvailableCpus()
+
+ s.HstSuite.SetupTest()
+ container := s.GetContainerByName(SingleTopoContainerVpp)
+ vpp, err := container.newVppInstance(container.AllocatedCpus)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+}
+
+func (s *CpuPinningSuite) TearDownTest() {
+ // reset vars
+ s.CpuCount = *NConfiguredCpus
+ s.CpuAllocator.maxContainerCount = s.previousMaxContainerCount
+ s.HstSuite.TearDownTest()
+
+}
+
+var _ = Describe("CpuPinningSuite", Ordered, ContinueOnFailure, func() {
+ var s CpuPinningSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range cpuPinningTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("CpuPinningSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s CpuPinningSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range cpuPinningSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go
new file mode 100644
index 00000000000..e34a7d74225
--- /dev/null
+++ b/extras/hs-test/infra/suite_envoy_proxy.go
@@ -0,0 +1,213 @@
+// Suite for Envoy proxy testing
+//
+// The topology consists of 4 containers: curl (client), VPP (session layer), Envoy (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+const (
+ VppContainerName = "vpp"
+ EnvoyProxyContainerName = "envoy-vcl"
+)
+
+type EnvoyProxySuite struct {
+ HstSuite
+ nginxPort uint16
+ proxyPort uint16
+ maxTimeout int
+}
+
+var envoyProxyTests = map[string][]func(s *EnvoyProxySuite){}
+var envoyProxySoloTests = map[string][]func(s *EnvoyProxySuite){}
+
+func RegisterEnvoyProxyTests(tests ...func(s *EnvoyProxySuite)) {
+ envoyProxyTests[getTestFilename()] = tests
+}
+
+func RegisterEnvoyProxySoloTests(tests ...func(s *EnvoyProxySuite)) {
+ envoyProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *EnvoyProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("envoyProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *EnvoyProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api").
+ Append("evt_qs_memfd_seg").
+ Append("event-queue-length 100000")
+
+ vppContainer := s.GetContainerByName(VppContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+ vppContainer.Exec("chmod 777 -R %s", vppContainer.GetContainerWorkDir())
+
+ // nginx HTTP server
+ nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxContainer.Create())
+ s.nginxPort = 80
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Port uint16
+ Timeout int
+ }{
+ LogPrefix: nginxContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Port: s.nginxPort,
+ Timeout: s.maxTimeout,
+ }
+ nginxContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxContainer.Start())
+
+ // Envoy
+ envoyContainer := s.GetContainerByName(EnvoyProxyContainerName)
+ s.AssertNil(envoyContainer.Create())
+ s.proxyPort = 8080
+ envoySettings := struct {
+ LogPrefix string
+ ServerAddress string
+ ServerPort uint16
+ ProxyPort uint16
+ }{
+ LogPrefix: envoyContainer.Name,
+ ServerAddress: serverInterface.Ip4AddressString(),
+ ServerPort: s.nginxPort,
+ ProxyPort: s.proxyPort,
+ }
+ envoyContainer.CreateConfig(
+ "/etc/envoy/envoy.yaml",
+ "resources/envoy/proxy.yaml",
+ envoySettings,
+ )
+ s.AssertNil(envoyContainer.Start())
+
+ // Add Ipv4 ARP entry for nginx HTTP server, otherwise first request fail (HTTP error 503)
+ arp := fmt.Sprintf("set ip neighbor %s %s %s",
+ serverInterface.Peer.Name(),
+ serverInterface.Ip4AddressString(),
+ serverInterface.HwAddress)
+ vppContainer.VppInstance.Vppctl(arp)
+}
+
+func (s *EnvoyProxySuite) TearDownTest() {
+ if CurrentSpecReport().Failed() {
+ s.CollectNginxLogs(NginxServerContainerName)
+ s.CollectEnvoyLogs(EnvoyProxyContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *EnvoyProxySuite) ProxyPort() uint16 {
+ return s.proxyPort
+}
+
+func (s *EnvoyProxySuite) ProxyAddr() string {
+ return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *EnvoyProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *EnvoyProxySuite) CurlUploadResource(uri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload --max-time %d --insecure --noproxy '*' -T %s %s", s.maxTimeout, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("EnvoyProxySuite", Ordered, ContinueOnFailure, func() {
+ var s EnvoyProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range envoyProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("EnvoyProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+ var s EnvoyProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range envoyProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_tap.go b/extras/hs-test/infra/suite_iperf_linux.go
index c02ab8e8535..728429b505f 100644
--- a/extras/hs-test/infra/suite_tap.go
+++ b/extras/hs-test/infra/suite_iperf_linux.go
@@ -9,28 +9,36 @@ import (
. "github.com/onsi/ginkgo/v2"
)
-type TapSuite struct {
+type IperfSuite struct {
HstSuite
}
-var tapTests = map[string][]func(s *TapSuite){}
-var tapSoloTests = map[string][]func(s *TapSuite){}
+const (
+ ServerIperfContainerName string = "server"
+ ServerIperfInterfaceName string = "hstsrv"
+ ClientIperfContainerName string = "client"
+ ClientIperfInterfaceName string = "hstcln"
+)
+
+var iperfTests = map[string][]func(s *IperfSuite){}
+var iperfSoloTests = map[string][]func(s *IperfSuite){}
-func RegisterTapTests(tests ...func(s *TapSuite)) {
- tapTests[getTestFilename()] = tests
+func RegisterIperfTests(tests ...func(s *IperfSuite)) {
+ iperfTests[getTestFilename()] = tests
}
-func RegisterTapSoloTests(tests ...func(s *TapSuite)) {
- tapSoloTests[getTestFilename()] = tests
+func RegisterIperfSoloTests(tests ...func(s *IperfSuite)) {
+ iperfSoloTests[getTestFilename()] = tests
}
-func (s *TapSuite) SetupSuite() {
+func (s *IperfSuite) SetupSuite() {
time.Sleep(1 * time.Second)
s.HstSuite.SetupSuite()
- s.ConfigureNetworkTopology("tap")
+ s.ConfigureNetworkTopology("2taps")
+ s.LoadContainerTopology("2containers")
}
-var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() {
- var s TapSuite
+var _ = Describe("IperfSuite", Ordered, ContinueOnFailure, func() {
+ var s IperfSuite
BeforeAll(func() {
s.SetupSuite()
})
@@ -44,7 +52,7 @@ var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() {
s.TearDownTest()
})
- for filename, tests := range tapTests {
+ for filename, tests := range iperfTests {
for _, test := range tests {
test := test
pc := reflect.ValueOf(test).Pointer()
@@ -58,8 +66,8 @@ var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() {
}
})
-var _ = Describe("TapSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
- var s TapSuite
+var _ = Describe("IperfSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s IperfSuite
BeforeAll(func() {
s.SetupSuite()
})
@@ -73,7 +81,7 @@ var _ = Describe("TapSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
s.TearDownTest()
})
- for filename, tests := range tapSoloTests {
+ for filename, tests := range iperfSoloTests {
for _, test := range tests {
test := test
pc := reflect.ValueOf(test).Pointer()
diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go
new file mode 100644
index 00000000000..15b45f710ef
--- /dev/null
+++ b/extras/hs-test/infra/suite_ldp.go
@@ -0,0 +1,203 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ ServerLdpInterfaceName = "srv"
+ ClientLdpInterfaceName = "cln"
+)
+
+var ldpTests = map[string][]func(s *LdpSuite){}
+var ldpSoloTests = map[string][]func(s *LdpSuite){}
+
+type LdpSuite struct {
+ HstSuite
+}
+
+func RegisterLdpTests(tests ...func(s *LdpSuite)) {
+ ldpTests[getTestFilename()] = tests
+}
+func RegisterSoloLdpTests(tests ...func(s *LdpSuite)) {
+ ldpSoloTests[getTestFilename()] = tests
+}
+
+func (s *LdpSuite) SetupSuite() {
+ time.Sleep(1 * time.Second)
+ s.HstSuite.SetupSuite()
+ s.ConfigureNetworkTopology("2peerVeth")
+ s.LoadContainerTopology("2peerVethLdp")
+}
+
+func (s *LdpSuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // Setup test conditions
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+ sessionConfig.Append("use-private-rx-mqs").Close()
+ s.Log("**********************INTERRUPT MODE**********************")
+ } else {
+ sessionConfig.Close()
+ }
+
+ // ... For server
+ serverContainer := s.GetContainerByName("server-vpp")
+
+ serverVpp, err := serverContainer.newVppInstance(serverContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(serverVpp, fmt.Sprint(err))
+
+ s.SetupServerVpp()
+
+ // ... For client
+ clientContainer := s.GetContainerByName("client-vpp")
+
+ clientVpp, err := clientContainer.newVppInstance(clientContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(clientVpp, fmt.Sprint(err))
+
+ s.setupClientVpp()
+
+ serverContainer.AddEnvVar("VCL_CONFIG", serverContainer.GetContainerWorkDir()+"/vcl_srv.conf")
+ clientContainer.AddEnvVar("VCL_CONFIG", clientContainer.GetContainerWorkDir()+"/vcl_cln.conf")
+
+ for _, container := range s.StartedContainers {
+ container.AddEnvVar("LD_PRELOAD", "/usr/lib/libvcl_ldpreload.so")
+ container.AddEnvVar("LDP_DEBUG", "0")
+ container.AddEnvVar("VCL_DEBUG", "0")
+ }
+}
+
+func (s *LdpSuite) TearDownTest() {
+ for _, container := range s.StartedContainers {
+ delete(container.EnvVars, "LD_PRELOAD")
+ delete(container.EnvVars, "VCL_CONFIG")
+ }
+ s.HstSuite.TearDownTest()
+
+}
+
+func (s *LdpSuite) SetupServerVpp() {
+ var srvVclConf Stanza
+ serverContainer := s.GetContainerByName("server-vpp")
+ serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
+ serverVpp := serverContainer.VppInstance
+ s.AssertNil(serverVpp.Start())
+
+ serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+ idx, err := serverVpp.createAfPacket(serverVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+ serverContainer.GetContainerWorkDir())
+ err = srvVclConf.
+ NewStanza("vcl").
+ Append("rx-fifo-size 4000000").
+ Append("tx-fifo-size 4000000").
+ Append("app-scope-local").
+ Append("app-scope-global").
+ Append("use-mq-eventfd").
+ Append(serverAppSocketApi).Close().
+ SaveToFile(serverVclFileName)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+func (s *LdpSuite) setupClientVpp() {
+ var clnVclConf Stanza
+ clientContainer := s.GetContainerByName("client-vpp")
+ clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
+ clientVpp := clientContainer.VppInstance
+ s.AssertNil(clientVpp.Start())
+
+ clientVeth := s.GetInterfaceByName(ClientInterfaceName)
+ idx, err := clientVpp.createAfPacket(clientVeth)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotEqual(0, idx)
+
+ clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+ clientContainer.GetContainerWorkDir())
+ err = clnVclConf.
+ NewStanza("vcl").
+ Append("rx-fifo-size 4000000").
+ Append("tx-fifo-size 4000000").
+ Append("app-scope-local").
+ Append("app-scope-global").
+ Append("use-mq-eventfd").
+ Append(clientAppSocketApi).Close().
+ SaveToFile(clientVclFileName)
+ s.AssertNil(err, fmt.Sprint(err))
+}
+
+var _ = Describe("LdpSuite", Ordered, ContinueOnFailure, func() {
+ var s LdpSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range ldpTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("LdpSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s LdpSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ // https://onsi.github.io/ginkgo/#dynamically-generating-specs
+ for filename, tests := range ldpSoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_nginx.go b/extras/hs-test/infra/suite_nginx.go
deleted file mode 100644
index bb1bdb0f42b..00000000000
--- a/extras/hs-test/infra/suite_nginx.go
+++ /dev/null
@@ -1,144 +0,0 @@
-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_nginx_proxy.go b/extras/hs-test/infra/suite_nginx_proxy.go
new file mode 100644
index 00000000000..75215cfc78d
--- /dev/null
+++ b/extras/hs-test/infra/suite_nginx_proxy.go
@@ -0,0 +1,191 @@
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ NginxProxyContainerName = "nginx-proxy"
+ NginxServerContainerName = "nginx-server"
+ MirroringClientInterfaceName = "hstcln"
+ MirroringServerInterfaceName = "hstsrv"
+)
+
+var nginxProxyTests = map[string][]func(s *NginxProxySuite){}
+var nginxProxySoloTests = map[string][]func(s *NginxProxySuite){}
+
+type NginxProxySuite struct {
+ HstSuite
+ proxyPort uint16
+ maxTimeout int
+}
+
+func RegisterNginxProxyTests(tests ...func(s *NginxProxySuite)) {
+ nginxProxyTests[getTestFilename()] = tests
+}
+func RegisterNginxProxySoloTests(tests ...func(s *NginxProxySuite)) {
+ nginxProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *NginxProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("nginxProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *NginxProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP
+ var sessionConfig Stanza
+ sessionConfig.
+ NewStanza("session").
+ Append("enable").
+ Append("use-app-socket-api")
+
+ vppContainer := s.GetContainerByName(VppContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus, sessionConfig)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(MirroringClientInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(MirroringServerInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+
+ // nginx proxy
+ nginxProxyContainer := s.GetTransientContainerByName(NginxProxyContainerName)
+ s.AssertNil(nginxProxyContainer.Create())
+ s.proxyPort = 80
+ values := struct {
+ LogPrefix string
+ Proxy string
+ Server string
+ Port uint16
+ }{
+ LogPrefix: nginxProxyContainer.Name,
+ Proxy: clientInterface.Peer.Ip4AddressString(),
+ Server: serverInterface.Ip4AddressString(),
+ Port: s.proxyPort,
+ }
+ nginxProxyContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_proxy_mirroring.conf",
+ values,
+ )
+ s.AssertNil(nginxProxyContainer.Start())
+
+ // nginx HTTP server
+ nginxServerContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxServerContainer.Create())
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Timeout int
+ }{
+ LogPrefix: nginxServerContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Timeout: s.maxTimeout,
+ }
+ nginxServerContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server_mirroring.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxServerContainer.Start())
+
+ vpp.WaitForApp("nginx-", 5)
+}
+
+func (s *NginxProxySuite) TearDownTest() {
+ if CurrentSpecReport().Failed() {
+ s.CollectNginxLogs(NginxServerContainerName)
+ s.CollectNginxLogs(NginxProxyContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *NginxProxySuite) ProxyPort() uint16 {
+ return s.proxyPort
+}
+
+func (s *NginxProxySuite) ProxyAddr() string {
+ return s.GetInterfaceByName(MirroringClientInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *NginxProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("NginxProxySuite", Ordered, ContinueOnFailure, func() {
+ var s NginxProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nginxProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("NginxProxySuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+ var s NginxProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range nginxProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go
index 5f53f55f1bb..9b4998a77a1 100644
--- a/extras/hs-test/infra/suite_no_topo.go
+++ b/extras/hs-test/infra/suite_no_topo.go
@@ -60,6 +60,18 @@ func (s *NoTopoSuite) SetupTest() {
s.AssertNil(vpp.createTap(tapInterface), "failed to create tap interface")
}
+func (s *NoTopoSuite) VppAddr() string {
+ return s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *NoTopoSuite) VppIfName() string {
+ return s.GetInterfaceByName(TapInterfaceName).Peer.Name()
+}
+
+func (s *NoTopoSuite) HostAddr() string {
+ return s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+}
+
var _ = Describe("NoTopoSuite", Ordered, ContinueOnFailure, func() {
var s NoTopoSuite
BeforeAll(func() {
diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go
new file mode 100644
index 00000000000..868684bcede
--- /dev/null
+++ b/extras/hs-test/infra/suite_vpp_proxy.go
@@ -0,0 +1,212 @@
+// Suite for VPP proxy testing
+//
+// The topology consists of 3 containers: curl (client), VPP (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+ VppProxyContainerName = "vpp-proxy"
+ ClientTapInterfaceName = "hstcln"
+ ServerTapInterfaceName = "hstsrv"
+ CurlContainerTestFile = "/tmp/testFile"
+)
+
+type VppProxySuite struct {
+ HstSuite
+ nginxPort uint16
+ maxTimeout int
+}
+
+var vppProxyTests = map[string][]func(s *VppProxySuite){}
+var vppProxySoloTests = map[string][]func(s *VppProxySuite){}
+
+func RegisterVppProxyTests(tests ...func(s *VppProxySuite)) {
+ vppProxyTests[getTestFilename()] = tests
+}
+
+func RegisterVppProxySoloTests(tests ...func(s *VppProxySuite)) {
+ vppProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *VppProxySuite) SetupSuite() {
+ s.HstSuite.SetupSuite()
+ s.LoadNetworkTopology("2taps")
+ s.LoadContainerTopology("vppProxy")
+
+ if *IsVppDebug {
+ s.maxTimeout = 600
+ } else {
+ s.maxTimeout = 60
+ }
+}
+
+func (s *VppProxySuite) SetupTest() {
+ s.HstSuite.SetupTest()
+
+ // VPP HTTP connect-proxy
+ vppContainer := s.GetContainerByName(VppProxyContainerName)
+ vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus)
+ s.AssertNotNil(vpp, fmt.Sprint(err))
+ s.AssertNil(vpp.Start())
+ clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+ s.AssertNil(vpp.createTap(clientInterface, 1))
+ serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+ s.AssertNil(vpp.createTap(serverInterface, 2))
+
+ // nginx HTTP server
+ nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+ s.AssertNil(nginxContainer.Create())
+ s.nginxPort = 80
+ nginxSettings := struct {
+ LogPrefix string
+ Address string
+ Port uint16
+ Timeout int
+ }{
+ LogPrefix: nginxContainer.Name,
+ Address: serverInterface.Ip4AddressString(),
+ Port: s.nginxPort,
+ Timeout: s.maxTimeout,
+ }
+ nginxContainer.CreateConfig(
+ "/nginx.conf",
+ "./resources/nginx/nginx_server.conf",
+ nginxSettings,
+ )
+ s.AssertNil(nginxContainer.Start())
+}
+
+func (s *VppProxySuite) TearDownTest() {
+ vpp := s.GetContainerByName(VppProxyContainerName).VppInstance
+ if CurrentSpecReport().Failed() {
+ s.Log(vpp.Vppctl("show session verbose 2"))
+ s.Log(vpp.Vppctl("show error"))
+ s.CollectNginxLogs(NginxServerContainerName)
+ }
+ s.HstSuite.TearDownTest()
+}
+
+func (s *VppProxySuite) NginxPort() uint16 {
+ return s.nginxPort
+}
+
+func (s *VppProxySuite) NginxAddr() string {
+ return s.GetInterfaceByName(ServerTapInterfaceName).Ip4AddressString()
+}
+
+func (s *VppProxySuite) VppProxyAddr() string {
+ return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *VppProxySuite) CurlRequest(targetUri string) (string, string) {
+ args := fmt.Sprintf("--insecure --noproxy '*' %s", targetUri)
+ body, log := s.RunCurlContainer(args)
+ return body, log
+}
+
+func (s *VppProxySuite) CurlRequestViaTunnel(targetUri string, proxyUri string) (string, string) {
+ args := fmt.Sprintf("--max-time %d --insecure -p -x %s %s", s.maxTimeout, proxyUri, targetUri)
+ body, log := s.RunCurlContainer(args)
+ return body, log
+}
+
+func (s *VppProxySuite) CurlDownloadResource(uri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download --max-time %d --insecure --noproxy '*' --remote-name --output-dir /tmp %s", s.maxTimeout, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlUploadResource(uri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload --max-time %d --insecure --noproxy '*' -T %s %s", s.maxTimeout, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_download_connect --max-time %d --insecure -p -x %s --remote-name --output-dir /tmp %s", s.maxTimeout, proxyUri, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "CONNECT response code: 200")
+ s.AssertContains(writeOut, "GET response code: 200")
+ s.AssertNotContains(log, "bytes remaining to read")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) {
+ args := fmt.Sprintf("-w @/tmp/write_out_upload_connect --max-time %d --insecure -p -x %s -T %s %s", s.maxTimeout, proxyUri, file, uri)
+ writeOut, log := s.RunCurlContainer(args)
+ s.AssertContains(writeOut, "CONNECT response code: 200")
+ s.AssertContains(writeOut, "PUT response code: 201")
+ s.AssertNotContains(log, "Operation timed out")
+}
+
+var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() {
+ var s VppProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range vppProxyTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
+
+var _ = Describe("VppProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+ var s VppProxySuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterAll(func() {
+ s.TearDownSuite()
+ })
+ AfterEach(func() {
+ s.TearDownTest()
+ })
+
+ for filename, tests := range vppProxySoloTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, Label("SOLO"), func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(SuiteTimeout))
+ }
+ }
+})
diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go
index 9619efbbf63..25d8519cb8a 100644
--- a/extras/hs-test/infra/utils.go
+++ b/extras/hs-test/infra/utils.go
@@ -1,11 +1,14 @@
package hst
import (
+ "errors"
"fmt"
"io"
"net"
"net/http"
+ "net/http/httputil"
"os"
+ "os/exec"
"strings"
"time"
)
@@ -96,6 +99,14 @@ func NewHttpClient() *http.Client {
return client
}
+func DumpHttpResp(resp *http.Response, body bool) string {
+ dump, err := httputil.DumpResponse(resp, body)
+ if err != nil {
+ return ""
+ }
+ return string(dump)
+}
+
func TcpSendReceive(address, data string) (string, error) {
conn, err := net.DialTimeout("tcp", address, time.Second*30)
if err != nil {
@@ -117,3 +128,189 @@ func TcpSendReceive(address, data string) (string, error) {
}
return string(reply), nil
}
+
+/*
+RunCurlContainer execute curl command with given args.
+Container with name "curl" must be available.
+Curl runs in verbose mode and progress meter switch off by default.
+*/
+func (s *HstSuite) RunCurlContainer(args string) (string, string) {
+ curlCont := s.GetContainerByName("curl")
+ cmd := fmt.Sprintf("curl -v -s %s", args)
+ s.Log(cmd)
+ curlCont.ExtraRunningArgs = cmd
+ curlCont.Run()
+ stdout, stderr := curlCont.GetOutput()
+ s.Log(stderr)
+ s.Log(stdout)
+ return stdout, stderr
+}
+
+/*
+CollectNginxLogs save access and error logs to the test execution directory.
+Nginx logging need to be set following way:
+
+ - error_log <default-work-dir>/{{.LogPrefix}}-error.log;
+ - access_log <default-work-dir>/{{.LogPrefix}}-access.log;
+
+where LogPrefix is set to nginxContainer.Name
+*/
+func (s *HstSuite) CollectNginxLogs(containerName string) {
+ nginxContainer := s.GetContainerByName(containerName)
+ targetDir := nginxContainer.Suite.getLogDirPath()
+ source := nginxContainer.GetHostWorkDir() + "/" + nginxContainer.Name + "-"
+ cmd := exec.Command("cp", "-t", targetDir, source+"error.log", source+"access.log")
+ s.Log(cmd.String())
+ err := cmd.Run()
+ if err != nil {
+ s.Log(fmt.Sprint(err))
+ }
+}
+
+/*
+CollectEnvoyLogs save access logs to the test execution directory.
+Envoy access log path need to be set following way:
+<default-work-dir>/{{.LogPrefix}}-access.log
+where LogPrefix is set to envoyContainer.Name
+*/
+func (s *HstSuite) CollectEnvoyLogs(containerName string) {
+ envoyContainer := s.GetContainerByName(containerName)
+ targetDir := envoyContainer.Suite.getLogDirPath()
+ source := envoyContainer.GetHostWorkDir() + "/" + envoyContainer.Name + "-"
+ cmd := exec.Command("cp", "-t", targetDir, source+"access.log")
+ s.Log(cmd.String())
+ err := cmd.Run()
+ if err != nil {
+ s.Log(fmt.Sprint(err))
+ }
+}
+
+func (s *HstSuite) StartIperfServerApp(running chan error, done chan struct{}, env []string) {
+ cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid())
+ if env != nil {
+ cmd.Env = env
+ }
+ s.Log(cmd)
+ err := cmd.Start()
+ if err != nil {
+ msg := fmt.Errorf("failed to start iperf server: %v", err)
+ running <- msg
+ return
+ }
+ running <- nil
+ <-done
+ cmd.Process.Kill()
+}
+
+func (s *HstSuite) StartIperfClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
+ defer func() {
+ clnCh <- nil
+ }()
+
+ nTries := 0
+
+ for {
+ cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid())
+ if env != nil {
+ cmd.Env = env
+ }
+ s.Log(cmd)
+ o, err := cmd.CombinedOutput()
+ if err != nil {
+ if nTries > 5 {
+ clnRes <- ""
+ clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
+ return
+ }
+ time.Sleep(1 * time.Second)
+ nTries++
+ continue
+ } else {
+ clnRes <- fmt.Sprintf("Client output: %s", o)
+ }
+ break
+ }
+}
+
+func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
+ cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs)
+ err := cmd.Start()
+ s.Log(cmd)
+ if err != nil {
+ s.Log("Failed to start http server: " + fmt.Sprint(err))
+ return
+ }
+ running <- struct{}{}
+ <-done
+ cmd.Process.Kill()
+}
+
+func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs string) {
+ defer func() {
+ finished <- errors.New("wget error")
+ }()
+
+ cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
+ netNs)
+ s.Log(cmd)
+ o, err := cmd.CombinedOutput()
+ if err != nil {
+ finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
+ return
+ } else if !strings.Contains(string(o), "200 OK") {
+ finished <- fmt.Errorf("wget error: response not 200 OK")
+ return
+ }
+ finished <- nil
+}
+
+// Start a server app. 'processName' is used to check whether the app started correctly.
+func (s *HstSuite) StartServerApp(c *Container, processName string, cmd string,
+ running chan error, done chan struct{}) {
+
+ s.Log("starting server")
+ c.ExecServer(cmd)
+ cmd2 := exec.Command("docker", "exec", c.Name, "pidof", processName)
+ err := cmd2.Run()
+ if err != nil {
+ msg := fmt.Errorf("failed to start server app: %v", err)
+ running <- msg
+ <-done
+ return
+ }
+ running <- nil
+ <-done
+}
+
+func (s *HstSuite) StartClientApp(c *Container, cmd string,
+ clnCh chan error, clnRes chan string) {
+ defer func() {
+ close(clnCh)
+ close(clnRes)
+ }()
+
+ s.Log("starting client app, please wait")
+
+ nTries := 0
+ for {
+ // exec.Cmd can only be used once, which is why it's in the loop
+ cmd2 := exec.Command("/bin/sh", "-c", "docker exec "+c.getEnvVarsAsCliOption()+" "+
+ c.Name+" "+cmd)
+ s.Log(cmd2)
+ o, err := cmd2.CombinedOutput()
+ if err != nil {
+ s.Log(err)
+ if nTries > 5 {
+ clnRes <- ""
+ clnCh <- fmt.Errorf("failed to start client app '%s'", err)
+ s.AssertNil(err, fmt.Sprint(err))
+ break
+ }
+ time.Sleep(1 * time.Second)
+ nTries++
+ } else {
+ clnRes <- fmt.Sprintf("Client output: %s", o)
+ break
+ }
+ }
+}
diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go
index 48d2b783917..a1f2ce46ed3 100644
--- a/extras/hs-test/infra/vppinstance.go
+++ b/extras/hs-test/infra/vppinstance.go
@@ -2,8 +2,8 @@ package hst
import (
"context"
+ "encoding/json"
"fmt"
- "go.fd.io/govpp/binapi/ethernet_types"
"io"
"net"
"os"
@@ -14,6 +14,8 @@ import (
"syscall"
"time"
+ "go.fd.io/govpp/binapi/ethernet_types"
+
"github.com/edwarnicke/exechelper"
. "github.com/onsi/ginkgo/v2"
"github.com/sirupsen/logrus"
@@ -32,19 +34,15 @@ const vppConfigTemplate = `unix {
nodaemon
log %[1]s%[4]s
full-coredump
+ coredump-size unlimited
cli-listen %[1]s%[2]s
runtime-dir %[1]s/var/run
- gid vpp
}
api-trace {
on
}
-api-segment {
- gid vpp
-}
-
socksvr {
socket-name %[1]s%[3]s
}
@@ -88,6 +86,20 @@ type VppInstance struct {
Connection *core.Connection
ApiStream api.Stream
Cpus []int
+ CpuConfig VppCpuConfig
+}
+
+type VppCpuConfig struct {
+ PinMainCpu bool
+ PinWorkersCorelist bool
+ SkipCores int
+}
+
+type VppMemTrace struct {
+ Count int `json:"count"`
+ Size int `json:"bytes"`
+ Sample string `json:"sample"`
+ Traceback []string `json:"traceback"`
}
func (vpp *VppInstance) getSuite() *HstSuite {
@@ -131,7 +143,7 @@ func (vpp *VppInstance) Start() error {
defaultApiSocketFilePath,
defaultLogFilePath,
)
- configContent += vpp.generateCpuConfig()
+ configContent += vpp.generateVPPCpuConfig()
for _, c := range vpp.AdditionalConfig {
configContent += c.ToString()
}
@@ -464,7 +476,7 @@ func (vpp *VppInstance) createTap(
}
func (vpp *VppInstance) saveLogs() {
- logTarget := vpp.Container.getLogDirPath() + "vppinstance-" + vpp.Container.Name + ".log"
+ logTarget := vpp.getSuite().getLogDirPath() + "vppinstance-" + vpp.Container.Name + ".log"
logSource := vpp.Container.GetHostWorkDir() + defaultLogFilePath
cmd := exec.Command("cp", logSource, logTarget)
vpp.getSuite().Log(cmd.String())
@@ -476,26 +488,160 @@ func (vpp *VppInstance) Disconnect() {
vpp.ApiStream.Close()
}
-func (vpp *VppInstance) generateCpuConfig() string {
+func (vpp *VppInstance) setDefaultCpuConfig() {
+ vpp.CpuConfig.PinMainCpu = true
+ vpp.CpuConfig.PinWorkersCorelist = true
+ vpp.CpuConfig.SkipCores = 0
+}
+
+func (vpp *VppInstance) generateVPPCpuConfig() string {
var c Stanza
var s string
+ startCpu := 0
if len(vpp.Cpus) < 1 {
return ""
}
- c.NewStanza("cpu").
- Append(fmt.Sprintf("main-core %d", vpp.Cpus[0]))
- vpp.getSuite().Log(fmt.Sprintf("main-core %d", vpp.Cpus[0]))
- workers := vpp.Cpus[1:]
+
+ c.NewStanza("cpu")
+
+ // If skip-cores is valid, use as start value to assign main/workers CPUs
+ if vpp.CpuConfig.SkipCores != 0 {
+ c.Append(fmt.Sprintf("skip-cores %d", vpp.CpuConfig.SkipCores))
+ vpp.getSuite().Log(fmt.Sprintf("skip-cores %d", vpp.CpuConfig.SkipCores))
+ }
+
+ if len(vpp.Cpus) > vpp.CpuConfig.SkipCores {
+ startCpu = vpp.CpuConfig.SkipCores
+ }
+
+ if vpp.CpuConfig.PinMainCpu {
+ c.Append(fmt.Sprintf("main-core %d", vpp.Cpus[startCpu]))
+ vpp.getSuite().Log(fmt.Sprintf("main-core %d", vpp.Cpus[startCpu]))
+ }
+
+ workers := vpp.Cpus[startCpu+1:]
if len(workers) > 0 {
- for i := 0; i < len(workers); i++ {
- if i != 0 {
- s = s + ", "
+ if vpp.CpuConfig.PinWorkersCorelist {
+ for i := 0; i < len(workers); i++ {
+ if i != 0 {
+ s = s + ", "
+ }
+ s = s + fmt.Sprintf("%d", workers[i])
}
- s = s + fmt.Sprintf("%d", workers[i])
+ c.Append(fmt.Sprintf("corelist-workers %s", s))
+ vpp.getSuite().Log("corelist-workers " + s)
+ } else {
+ s = fmt.Sprintf("%d", len(workers))
+ c.Append(fmt.Sprintf("workers %s", s))
+ vpp.getSuite().Log("workers " + s)
}
- c.Append(fmt.Sprintf("corelist-workers %s", s))
- vpp.getSuite().Log("corelist-workers " + s)
}
+
return c.Close().ToString()
}
+
+// EnableMemoryTrace enables memory traces of VPP main-heap
+func (vpp *VppInstance) EnableMemoryTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("memory-trace on main-heap"))
+}
+
+// GetMemoryTrace dumps memory traces for analysis
+func (vpp *VppInstance) GetMemoryTrace() ([]VppMemTrace, error) {
+ var trace []VppMemTrace
+ vpp.getSuite().Log(vpp.Vppctl("save memory-trace trace.json"))
+ err := vpp.Container.GetFile("/tmp/trace.json", "/tmp/trace.json")
+ if err != nil {
+ return nil, err
+ }
+ fileBytes, err := os.ReadFile("/tmp/trace.json")
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(fileBytes, &trace)
+ if err != nil {
+ return nil, err
+ }
+ return trace, nil
+}
+
+// memTracesSuppressCli filter out CLI related samples
+func memTracesSuppressCli(traces []VppMemTrace) []VppMemTrace {
+ var filtered []VppMemTrace
+ for i := 0; i < len(traces); i++ {
+ isCli := false
+ for j := 0; j < len(traces[i].Traceback); j++ {
+ if strings.Contains(traces[i].Traceback[j], "unix_cli") {
+ isCli = true
+ break
+ }
+ }
+ if !isCli {
+ filtered = append(filtered, traces[i])
+ }
+ }
+ return filtered
+}
+
+// MemLeakCheck compares memory traces at different point in time, analyzes if memory leaks happen and produces report
+func (vpp *VppInstance) MemLeakCheck(first, second []VppMemTrace) {
+ totalBytes := 0
+ totalCounts := 0
+ trace1 := memTracesSuppressCli(first)
+ trace2 := memTracesSuppressCli(second)
+ report := ""
+ for i := 0; i < len(trace2); i++ {
+ match := false
+ for j := 0; j < len(trace1); j++ {
+ if trace1[j].Sample == trace2[i].Sample {
+ if trace2[i].Size > trace1[j].Size {
+ deltaBytes := trace2[i].Size - trace1[j].Size
+ deltaCounts := trace2[i].Count - trace1[j].Count
+ report += fmt.Sprintf("grow %d byte(s) in %d allocation(s) from:\n", deltaBytes, deltaCounts)
+ for j := 0; j < len(trace2[i].Traceback); j++ {
+ report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j])
+ }
+ totalBytes += deltaBytes
+ totalCounts += deltaCounts
+ }
+ match = true
+ break
+ }
+ }
+ if !match {
+ report += fmt.Sprintf("\nleak of %d byte(s) in %d allocation(s) from:\n", trace2[i].Size, trace2[i].Count)
+ for j := 0; j < len(trace2[i].Traceback); j++ {
+ report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j])
+ }
+ totalBytes += trace2[i].Size
+ totalCounts += trace2[i].Count
+ }
+ }
+ summary := fmt.Sprintf("\nSUMMARY: %d byte(s) leaked in %d allocation(s)\n", totalBytes, totalCounts)
+ AddReportEntry(summary, report)
+}
+
+// CollectEventLogs saves event logs to the test execution directory
+func (vpp *VppInstance) CollectEventLogs() {
+ vpp.getSuite().Log(vpp.Vppctl("event-logger save event_log"))
+ targetDir := vpp.Container.Suite.getLogDirPath()
+ err := vpp.Container.GetFile("/tmp/event_log", targetDir+"/"+vpp.Container.Name+"-event_log")
+ if err != nil {
+ vpp.getSuite().Log(fmt.Sprint(err))
+ }
+}
+
+// EnablePcapTrace enables packet capture on all interfaces and maximum 10000 packets
+func (vpp *VppInstance) EnablePcapTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("pcap trace rx tx max 10000 intfc any file vppTest.pcap"))
+}
+
+// CollectPcapTrace saves pcap trace to the test execution directory
+func (vpp *VppInstance) CollectPcapTrace() {
+ vpp.getSuite().Log(vpp.Vppctl("pcap trace off"))
+ targetDir := vpp.Container.Suite.getLogDirPath()
+ err := vpp.Container.GetFile("/tmp/vppTest.pcap", targetDir+"/"+vpp.Container.Name+".pcap")
+ if err != nil {
+ vpp.getSuite().Log(fmt.Sprint(err))
+ }
+}
diff --git a/extras/hs-test/iperf_linux_test.go b/extras/hs-test/iperf_linux_test.go
new file mode 100644
index 00000000000..14422fe5efa
--- /dev/null
+++ b/extras/hs-test/iperf_linux_test.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "fmt"
+
+ . "fd.io/hs-test/infra"
+ . "github.com/onsi/ginkgo/v2"
+)
+
+func init() {
+ RegisterIperfTests(IperfLinuxTest)
+}
+
+func IperfLinuxTest(s *IperfSuite) {
+ serverContainer := s.GetContainerByName(ServerIperfContainerName)
+ serverIpAddress := s.GetInterfaceByName(ServerIperfInterfaceName).Ip4AddressString()
+ clientContainer := s.GetContainerByName(ClientIperfContainerName)
+ clientIpAddress := s.GetInterfaceByName(ClientIperfInterfaceName).Ip4AddressString()
+
+ clnCh := make(chan error)
+ stopServerCh := make(chan struct{})
+ srvCh := make(chan error, 1)
+ clnRes := make(chan string, 1)
+
+ defer func() {
+ stopServerCh <- struct{}{}
+ }()
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "iperf3 -4 -s -B " + serverIpAddress + " -p " + s.GetPortFromPpid()
+ s.StartServerApp(serverContainer, "iperf3", cmd, srvCh, stopServerCh)
+ }()
+ err := <-srvCh
+ s.AssertNil(err, fmt.Sprint(err))
+ s.Log("server running")
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "iperf3 -c " + serverIpAddress + " -B " + clientIpAddress +
+ " -u -l 1460 -b 10g -p " + s.GetPortFromPpid()
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
+ }()
+
+ s.Log(<-clnRes)
+ err = <-clnCh
+ s.AssertNil(err, "err: '%s'", err)
+}
diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go
index e9e8bba9274..03636b11191 100644
--- a/extras/hs-test/ldp_test.go
+++ b/extras/hs-test/ldp_test.go
@@ -2,86 +2,42 @@ package main
import (
"fmt"
- "os"
. "fd.io/hs-test/infra"
. "github.com/onsi/ginkgo/v2"
)
func init() {
- RegisterVethTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest)
+ RegisterLdpTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest, RedisBenchmarkTest)
}
-func LDPreloadIperfVppInterruptModeTest(s *VethsSuite) {
+func LDPreloadIperfVppInterruptModeTest(s *LdpSuite) {
LDPreloadIperfVppTest(s)
}
-func LDPreloadIperfVppTest(s *VethsSuite) {
- var clnVclConf, srvVclConf Stanza
- var ldpreload string
-
- serverContainer := s.GetContainerByName("server-vpp")
- serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
-
+func LDPreloadIperfVppTest(s *LdpSuite) {
clientContainer := s.GetContainerByName("client-vpp")
- clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
-
- 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"
- }
+ serverContainer := s.GetContainerByName("server-vpp")
stopServerCh := make(chan struct{}, 1)
srvCh := make(chan error, 1)
clnCh := make(chan error)
+ clnRes := make(chan string, 1)
- s.Log("starting VPPs")
-
- clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
- clientContainer.GetHostWorkDir())
- err := clnVclConf.
- NewStanza("vcl").
- Append("rx-fifo-size 4000000").
- Append("tx-fifo-size 4000000").
- Append("app-scope-local").
- Append("app-scope-global").
- Append("use-mq-eventfd").
- Append(clientAppSocketApi).Close().
- SaveToFile(clientVclFileName)
- s.AssertNil(err, fmt.Sprint(err))
-
- serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
- serverContainer.GetHostWorkDir())
- err = srvVclConf.
- NewStanza("vcl").
- Append("rx-fifo-size 4000000").
- Append("tx-fifo-size 4000000").
- Append("app-scope-local").
- Append("app-scope-global").
- Append("use-mq-eventfd").
- Append(serverAppSocketApi).Close().
- SaveToFile(serverVclFileName)
- s.AssertNil(err, fmt.Sprint(err))
-
- s.Log("attaching server to vpp")
-
- srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+serverVclFileName)
go func() {
defer GinkgoRecover()
- s.StartServerApp(srvCh, stopServerCh, srvEnv)
+ cmd := "iperf3 -4 -s -p " + s.GetPortFromPpid()
+ s.StartServerApp(serverContainer, "iperf3", cmd, srvCh, stopServerCh)
}()
- err = <-srvCh
+ err := <-srvCh
s.AssertNil(err, fmt.Sprint(err))
- s.Log("attaching client to vpp")
- var clnRes = make(chan string, 1)
- clnEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+clientVclFileName)
serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
go func() {
defer GinkgoRecover()
- s.StartClientApp(serverVethAddress, clnEnv, clnCh, clnRes)
+ cmd := "iperf3 -c " + serverVethAddress + " -u -l 1460 -b 10g -p " + s.GetPortFromPpid()
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
}()
s.Log(<-clnRes)
@@ -92,3 +48,43 @@ func LDPreloadIperfVppTest(s *VethsSuite) {
// stop server
stopServerCh <- struct{}{}
}
+
+func RedisBenchmarkTest(s *LdpSuite) {
+ s.SkipIfMultiWorker()
+
+ serverContainer := s.GetContainerByName("server-vpp")
+ clientContainer := s.GetContainerByName("client-vpp")
+
+ serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
+ runningSrv := make(chan error)
+ doneSrv := make(chan struct{})
+ clnCh := make(chan error)
+ clnRes := make(chan string, 1)
+
+ go func() {
+ defer GinkgoRecover()
+ cmd := "redis-server --daemonize yes --protected-mode no --bind " + serverVethAddress
+ s.StartServerApp(serverContainer, "redis-server", cmd, runningSrv, doneSrv)
+ }()
+
+ err := <-runningSrv
+ s.AssertNil(err)
+
+ go func() {
+ defer GinkgoRecover()
+ var cmd string
+ if *NConfiguredCpus == 1 {
+ cmd = "redis-benchmark --threads 1 -h " + serverVethAddress
+ } else {
+ cmd = "redis-benchmark --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress
+ }
+ s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
+ }()
+
+ s.Log(<-clnRes)
+ // wait for client's result
+ err = <-clnCh
+ s.AssertNil(err, fmt.Sprint(err))
+ // stop server
+ doneSrv <- struct{}{}
+}
diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go
deleted file mode 100644
index f49d9bcf227..00000000000
--- a/extras/hs-test/linux_iperf_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package main
-
-import (
- . "fd.io/hs-test/infra"
- "fmt"
- . "github.com/onsi/ginkgo/v2"
-)
-
-func init() {
- RegisterTapTests(LinuxIperfTest)
-}
-
-func LinuxIperfTest(s *TapSuite) {
- clnCh := make(chan error)
- stopServerCh := make(chan struct{})
- srvCh := make(chan error, 1)
- clnRes := make(chan string, 1)
- defer func() {
- stopServerCh <- struct{}{}
- }()
-
- go func() {
- defer GinkgoRecover()
- s.StartServerApp(srvCh, stopServerCh, nil)
- }()
- err := <-srvCh
- s.AssertNil(err, fmt.Sprint(err))
- s.Log("server running")
-
- ipAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
- go func() {
- defer GinkgoRecover()
- s.StartClientApp(ipAddress, nil, clnCh, clnRes)
- }()
- s.Log("client running")
- s.Log(<-clnRes)
- err = <-clnCh
- s.AssertNil(err, "err: '%s', ip: '%s'", err, ipAddress)
- s.Log("Test completed")
-}
diff --git a/extras/hs-test/mem_leak_test.go b/extras/hs-test/mem_leak_test.go
new file mode 100644
index 00000000000..0d8831d2bbc
--- /dev/null
+++ b/extras/hs-test/mem_leak_test.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "fmt"
+
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterNoTopoSoloTests(MemLeakTest)
+}
+
+func MemLeakTest(s *NoTopoSuite) {
+ s.SkipUnlessLeakCheck()
+ vpp := s.GetContainerByName("vpp").VppInstance
+ /* no goVPP less noise */
+ vpp.Disconnect()
+ vpp.EnableMemoryTrace()
+ traces1, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.Vppctl("test mem-leak")
+ traces2, err := vpp.GetMemoryTrace()
+ s.AssertNil(err, fmt.Sprint(err))
+ vpp.MemLeakCheck(traces1, traces2)
+}
diff --git a/extras/hs-test/nginx_test.go b/extras/hs-test/nginx_test.go
index 00869b0c9b4..8bf681d18c6 100644
--- a/extras/hs-test/nginx_test.go
+++ b/extras/hs-test/nginx_test.go
@@ -1,55 +1,38 @@
package main
import (
- . "fd.io/hs-test/infra"
"fmt"
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
"os"
"strings"
+
+ . "fd.io/hs-test/infra"
+ . "github.com/onsi/ginkgo/v2"
)
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())
+ nginxCont.Run()
vpp := s.GetContainerByName("vpp").VppInstance
vpp.WaitForApp("nginx-", 5)
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
defer func() { os.Remove(query) }()
curlCont := s.GetContainerByName("curl")
args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query)
curlCont.ExtraRunningArgs = args
- o, err := curlCont.CombinedOutput()
+ curlCont.Run()
+ o, err := curlCont.GetOutput()
s.Log(o)
- s.AssertNil(err, fmt.Sprint(err))
+ s.AssertEmpty(err)
s.AssertContains(o, "<http>", "<http> not found in the result!")
}
func NginxAsServerTest(s *NoTopoSuite) {
@@ -57,12 +40,12 @@ func NginxAsServerTest(s *NoTopoSuite) {
finished := make(chan error, 1)
nginxCont := s.GetContainerByName("nginx")
- s.AssertNil(nginxCont.Run())
+ nginxCont.Run()
vpp := s.GetContainerByName("vpp").VppInstance
vpp.WaitForApp("nginx-", 5)
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
defer func() { os.Remove(query) }()
go func() {
@@ -86,12 +69,12 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
nRequests := 1000000
nClients := 1000
- serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ serverAddress := s.VppAddr()
vpp := s.GetContainerByName("vpp").VppInstance
nginxCont := s.GetContainerByName(SingleTopoContainerNginx)
- s.AssertNil(nginxCont.Run())
+ nginxCont.Run()
vpp.WaitForApp("nginx-", 5)
if ab_or_wrk == "ab" {
@@ -106,21 +89,23 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error {
args += " -r"
args += " http://" + serverAddress + ":80/64B.json"
abCont.ExtraRunningArgs = args
- o, err := abCont.CombinedOutput()
+ abCont.Run()
+ o, err := abCont.GetOutput()
rps := parseString(o, "Requests per second:")
s.Log(rps)
- s.Log(err)
- s.AssertNil(err, "err: '%s', output: '%s'", err, o)
+ s.AssertContains(err, "Finished "+fmt.Sprint(nRequests))
} else {
wrkCont := s.GetContainerByName("wrk")
args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s:80/64B.json", nClients,
serverAddress)
wrkCont.ExtraRunningArgs = args
- o, err := wrkCont.CombinedOutput()
+ wrkCont.Run()
+ s.Log("Please wait for 30s, test is running.")
+ o, err := wrkCont.GetOutput()
rps := parseString(o, "requests")
s.Log(rps)
s.Log(err)
- s.AssertNil(err, "err: '%s', output: '%s'", err, o)
+ s.AssertEmpty(err, "err: '%s', output: '%s'", err, o)
}
return nil
}
diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go
index 5f7eb45be38..758064424a8 100644
--- a/extras/hs-test/proxy_test.go
+++ b/extras/hs-test/proxy_test.go
@@ -1,115 +1,71 @@
package main
import (
- . "fd.io/hs-test/infra"
"fmt"
- "github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
- "os"
+
+ . "fd.io/hs-test/infra"
)
func init() {
- RegisterNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
+ RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest)
+ RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest)
+ RegisterNginxProxyTests(NginxMirroringTest)
}
-func testProxyHttpTcp(s *NsSuite, proto string) error {
- var outputFile string = 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")
-
- // create test file
- 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...")
-
- go func() {
- defer GinkgoRecover()
- s.StartHttpServer(serverRunning, stopServer, ":666", serverNetns)
- }()
- // TODO better error handling and recovery
- <-serverRunning
-
- defer func(chan struct{}) {
- stopServer <- struct{}{}
- }(stopServer)
-
- s.Log("http server started...")
-
- clientVeth := s.GetInterfaceByName(ClientInterface)
- c := fmt.Sprintf("ip netns exec %s wget --no-proxy --retry-connrefused"+
- " --retry-on-http-error=503 --tries=10 -O %s ", clientNetns, outputFile)
- if proto == "tls" {
- c += " --secure-protocol=TLSv1_3 --no-check-certificate https://"
- }
- c += fmt.Sprintf("%s:555/%s", clientVeth.Ip4AddressString(), 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)
- stopServer <- struct{}{}
-
- s.AssertNil(AssertFileSize(outputFile, srcFilePpid))
- return nil
-}
-
-func configureVppProxy(s *NsSuite, proto string) {
- serverVeth := s.GetInterfaceByName(ServerInterface)
- clientVeth := s.GetInterfaceByName(ClientInterface)
-
- testVppProxy := s.GetContainerByName("vpp").VppInstance
- output := testVppProxy.Vppctl(
- "test proxy server server-uri %s://%s/555 client-uri tcp://%s/666",
+func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) {
+ vppProxy := s.GetContainerByName(VppProxyContainerName).VppInstance
+ output := vppProxy.Vppctl(
+ "test proxy server server-uri %s://%s/%d client-uri tcp://%s/%d",
proto,
- clientVeth.Ip4AddressString(),
- serverVeth.Peer.Ip4AddressString(),
+ s.VppProxyAddr(),
+ proxyPort,
+ s.NginxAddr(),
+ s.NginxPort(),
)
s.Log("proxy configured: " + output)
}
-func VppProxyHttpTcpTest(s *NsSuite) {
- proto := "tcp"
- configureVppProxy(s, proto)
- err := testProxyHttpTcp(s, proto)
- s.AssertNil(err, fmt.Sprint(err))
+func VppProxyHttpGetTcpTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tcp", proxyPort)
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+ s.CurlDownloadResource(uri)
+}
+
+func VppProxyHttpGetTlsTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tls", proxyPort)
+ uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+ s.CurlDownloadResource(uri)
}
-func VppProxyHttpTlsTest(s *NsSuite) {
- proto := "tls"
- configureVppProxy(s, proto)
- err := testProxyHttpTcp(s, proto)
- s.AssertNil(err, fmt.Sprint(err))
+func VppProxyHttpPutTcpTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tcp", proxyPort)
+ uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort)
+ s.CurlUploadResource(uri, CurlContainerTestFile)
}
-func configureEnvoyProxy(s *NsSuite) {
- envoyContainer := s.GetContainerByName("envoy")
- err := envoyContainer.Create()
- s.AssertNil(err, "Error creating envoy container: %s", err)
+func VppProxyHttpPutTlsTest(s *VppProxySuite) {
+ var proxyPort uint16 = 8080
+ configureVppProxy(s, "tls", proxyPort)
+ uri := fmt.Sprintf("https://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort)
+ s.CurlUploadResource(uri, CurlContainerTestFile)
+}
- serverVeth := s.GetInterfaceByName(ServerInterface)
- address := struct {
- Server string
- }{
- Server: serverVeth.Peer.Ip4AddressString(),
- }
- envoyContainer.CreateConfig(
- "/etc/envoy/envoy.yaml",
- "resources/envoy/proxy.yaml",
- address,
- )
- s.AssertNil(envoyContainer.Start())
+func EnvoyProxyHttpGetTcpTest(s *EnvoyProxySuite) {
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlDownloadResource(uri)
+}
+
+func EnvoyProxyHttpPutTcpTest(s *EnvoyProxySuite) {
+ uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlUploadResource(uri, CurlContainerTestFile)
}
-func EnvoyProxyHttpTcpTest(s *NsSuite) {
- configureEnvoyProxy(s)
- err := testProxyHttpTcp(s, "tcp")
- s.AssertNil(err, fmt.Sprint(err))
+// broken when CPUS > 1
+func NginxMirroringTest(s *NginxProxySuite) {
+ s.SkipIfMultiWorker()
+ uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort())
+ s.CurlDownloadResource(uri)
}
diff --git a/extras/hs-test/resources/curl/write_out_download b/extras/hs-test/resources/curl/write_out_download
new file mode 100644
index 00000000000..9fc7699c229
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_download
@@ -0,0 +1 @@
+Download size: %{size_download} bytes\nDownload speed: %{speed_download} bytes per second\nGET response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_download_connect b/extras/hs-test/resources/curl/write_out_download_connect
new file mode 100644
index 00000000000..bd7c9152a0c
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_download_connect
@@ -0,0 +1 @@
+Download size: %{size_download} bytes\nDownload speed: %{speed_download} bytes per second\nCONNECT response code: %{http_connect}\nGET response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_upload b/extras/hs-test/resources/curl/write_out_upload
new file mode 100644
index 00000000000..c2857aa6b0c
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_upload
@@ -0,0 +1 @@
+Upload size: %{size_upload} bytes\nUpload speed: %{speed_upload} bytes per second\nPUT response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/curl/write_out_upload_connect b/extras/hs-test/resources/curl/write_out_upload_connect
new file mode 100644
index 00000000000..35021655ec0
--- /dev/null
+++ b/extras/hs-test/resources/curl/write_out_upload_connect
@@ -0,0 +1 @@
+Upload size: %{size_upload} bytes\nUpload speed: %{speed_upload} bytes per second\nCONNECT response code: %{http_connect}\nPUT response code: %{response_code}\n \ No newline at end of file
diff --git a/extras/hs-test/resources/envoy/proxy.yaml b/extras/hs-test/resources/envoy/proxy.yaml
index 77da80d934d..67eb6909ed2 100644
--- a/extras/hs-test/resources/envoy/proxy.yaml
+++ b/extras/hs-test/resources/envoy/proxy.yaml
@@ -1,17 +1,15 @@
admin:
- access_log_path: /tmp/envoy.log
address:
socket_address:
address: 0.0.0.0
port_value: 8081
static_resources:
listeners:
- # define a reverse proxy on :10001 that always uses :80 as an origin.
- address:
socket_address:
protocol: TCP
address: 0.0.0.0
- port_value: 555
+ port_value: {{.ProxyPort}}
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
@@ -30,6 +28,13 @@ static_resources:
cluster: proxy_service
http_filters:
- name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ access_log:
+ - name: envoy.access_loggers.file
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+ path: /tmp/vpp-envoy/{{.LogPrefix}}-access.log
clusters:
- name: proxy_service
connect_timeout: 0.25s
@@ -43,9 +48,8 @@ static_resources:
- endpoint:
address:
socket_address:
- # following address will be generated by Addresser during test run
- address: {{.Server}}
- port_value: 666
+ address: {{.ServerAddress}}
+ port_value: {{.ServerPort}}
bootstrap_extensions:
- name: envoy.extensions.vcl.vcl_socket_interface
typed_config:
diff --git a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
index 56debf5c290..7f6b09c6050 100644
--- a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
+++ b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf
@@ -3,7 +3,7 @@ worker_processes 4;
worker_rlimit_nofile 102400;
daemon off;
-error_log /tmp/nginx/error.log;
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
events {
use epoll;
@@ -44,7 +44,8 @@ http {
}
server {
- listen 80;
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
+ listen {{.Port}};
server_name {{.Proxy}};
server_tokens off;
diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf
new file mode 100644
index 00000000000..26d58348039
--- /dev/null
+++ b/extras/hs-test/resources/nginx/nginx_server.conf
@@ -0,0 +1,43 @@
+master_process on;
+worker_rlimit_nofile 10240;
+worker_processes 2;
+daemon off;
+
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
+
+events {
+ use epoll;
+ worker_connections 10240;
+ accept_mutex off;
+ multi_accept off;
+}
+
+http {
+ keepalive_timeout 300s;
+ keepalive_requests 1000000;
+ client_body_timeout {{.Timeout}}s;
+ client_header_timeout {{.Timeout}}s;
+ send_timeout {{.Timeout}}s;
+ sendfile on;
+ server {
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
+ listen {{.Port}};
+ server_name {{.Address}};
+ root /usr/share/nginx;
+ index index.html index.htm;
+ location ~ "/upload/([0-9a-zA-Z-.]*)$" {
+ alias /usr/share/nginx/upload/$1;
+ client_body_temp_path /tmp;
+ client_max_body_size 200M;
+ dav_methods PUT;
+ create_full_put_path off;
+ dav_access all:rw;
+ }
+ location /64B {
+ return 200 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+ }
+ location / {
+ sendfile on;
+ }
+ }
+}
diff --git a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
index 4056801ea13..921eb2eefd5 100644
--- a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
+++ b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf
@@ -3,6 +3,8 @@ worker_rlimit_nofile 10240;
worker_processes 2;
daemon off;
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
+
events {
use epoll;
worker_connections 10240;
@@ -15,18 +17,17 @@ http {
keepalive_requests 1000000;
sendfile on;
server {
+ access_log /tmp/nginx/{{.LogPrefix}}-access.log;
listen 8091;
listen 8092;
listen 8093;
root /usr/share/nginx;
index index.html index.htm;
- location /return_ok
- {
- return 200 '';
- }
- location /64B.json
- {
+ location /64B {
return 200 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
}
+ location / {
+ sendfile on;
+ }
}
}
diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh
index 0f5c0a43630..9b11f5f0272 100755
--- a/extras/hs-test/script/build_hst.sh
+++ b/extras/hs-test/script/build_hst.sh
@@ -69,7 +69,7 @@ fi
docker_build () {
tag=$1
dockername=$2
- set -x
+ set -ex
# shellcheck disable=2086
docker buildx build ${DOCKER_CACHE_ARGS} \
--build-arg UBUNTU_VERSION \
@@ -78,16 +78,15 @@ docker_build () {
--build-arg HTTP_PROXY="$HTTP_PROXY" \
--build-arg HTTPS_PROXY="$HTTP_PROXY" \
-t "$tag" -f docker/Dockerfile."$dockername" .
- set +x
+ set +ex
}
docker_build hs-test/vpp vpp
docker_build hs-test/nginx-ldp nginx
docker_build hs-test/nginx-server nginx-server
-docker_build hs-test/build build
+docker_build hs-test/curl curl
if [ "$HST_EXTENDED_TESTS" = true ] ; then
docker_build hs-test/nginx-http3 nginx-http3
- docker_build hs-test/curl curl
fi
# cleanup detached images
diff --git a/extras/hs-test/script/compress.sh b/extras/hs-test/script/compress.sh
index c6b23cf9bdd..09db9b6720d 100755
--- a/extras/hs-test/script/compress.sh
+++ b/extras/hs-test/script/compress.sh
@@ -6,12 +6,12 @@ then
if [ -n "${WORKSPACE}" ]
then
echo -n "Copying docker logs..."
- dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText' ${HS_ROOT}/summary/report.json)
+ dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText | split("/")[1]' ${HS_ROOT}/summary/report.json)
for dirName in $dirs; do
logDir=/tmp/hs-test/$dirName
if [ -d "$logDir" ]; then
mkdir -p ${WORKSPACE}/archives/summary
- cp -r $logDir ${WORKSPACE}/archives/summary/
+ rsync -a --exclude 'volumes' $logDir ${WORKSPACE}/archives/summary/
fi
done
echo "Done."
@@ -32,4 +32,6 @@ then
echo "*************************** SUMMARY ***************************"
cat "${HS_ROOT}/summary/failed-summary.log"
exit 1
+else
+ exit $1
fi
diff --git a/extras/hs-test/script/nginx_server_entrypoint.sh b/extras/hs-test/script/nginx_server_entrypoint.sh
new file mode 100755
index 00000000000..6581c8b74d3
--- /dev/null
+++ b/extras/hs-test/script/nginx_server_entrypoint.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nginx -c /nginx.conf
diff --git a/extras/hs-test/topo-containers/2containers.yaml b/extras/hs-test/topo-containers/2containers.yaml
new file mode 100644
index 00000000000..1217c27d3ed
--- /dev/null
+++ b/extras/hs-test/topo-containers/2containers.yaml
@@ -0,0 +1,4 @@
+---
+containers:
+ - name: "server"
+ - name: "client" \ No newline at end of file
diff --git a/extras/hs-test/topo-containers/2peerVethLdp.yaml b/extras/hs-test/topo-containers/2peerVethLdp.yaml
new file mode 100644
index 00000000000..bd6e63a945d
--- /dev/null
+++ b/extras/hs-test/topo-containers/2peerVethLdp.yaml
@@ -0,0 +1,18 @@
+---
+volumes:
+ - volume: &server-vol
+ host-dir: "$HST_VOLUME_DIR/server-share"
+ container-dir: "/tmp/server-share"
+ is-default-work-dir: true
+ - volume: &client-vol
+ host-dir: "$HST_VOLUME_DIR/client-share"
+ container-dir: "/tmp/client-share"
+ is-default-work-dir: true
+
+containers:
+ - name: "server-vpp"
+ volumes:
+ - <<: *server-vol
+ - name: "client-vpp"
+ volumes:
+ - <<: *client-vol
diff --git a/extras/hs-test/topo-containers/envoyProxy.yaml b/extras/hs-test/topo-containers/envoyProxy.yaml
new file mode 100644
index 00000000000..92dd9b93c47
--- /dev/null
+++ b/extras/hs-test/topo-containers/envoyProxy.yaml
@@ -0,0 +1,40 @@
+---
+volumes:
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
+ - name: "envoy-vcl"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp-envoy"
+ is-default-work-dir: true
+ - host-dir: "$HST_DIR/resources/envoy"
+ container-dir: "/tmp"
+ vars:
+ - name: "ENVOY_UID"
+ value: "0"
+ - name: "VCL_CONFIG"
+ value: "/tmp/vcl.conf"
+ image: "envoyproxy/envoy-contrib:v1.30-latest"
+ extra-args: "--log-format [%t][%l][%g:%#]%_ --concurrency 2 -c /etc/envoy/envoy.yaml"
+ is-optional: true
+ - name: "nginx-server"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/topo-containers/nginxProxy.yaml b/extras/hs-test/topo-containers/nginxProxy.yaml
new file mode 100644
index 00000000000..d9ddc14590f
--- /dev/null
+++ b/extras/hs-test/topo-containers/nginxProxy.yaml
@@ -0,0 +1,32 @@
+---
+volumes:
+ - volume: &shared-vol-nginx-proxy
+ host-dir: "$HST_VOLUME_DIR/shared-vol-nginx-proxy"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
+ - name: "nginx-proxy"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-ldp"
+ is-optional: true
+ - name: "nginx-server"
+ volumes:
+ - <<: *shared-vol-nginx-proxy
+ container-dir: "/tmp/nginx"
+ is-default-work-dir: true
+ image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/topo-containers/ns.yaml b/extras/hs-test/topo-containers/ns.yaml
index 2298ad232c2..3de5af59569 100644
--- a/extras/hs-test/topo-containers/ns.yaml
+++ b/extras/hs-test/topo-containers/ns.yaml
@@ -22,6 +22,6 @@ containers:
value: "0"
- name: "VCL_CONFIG"
value: "/tmp/vcl.conf"
- image: "envoyproxy/envoy-contrib:v1.21-latest"
+ image: "envoyproxy/envoy-contrib:v1.30-latest"
extra-args: "--concurrency 2 -c /etc/envoy/envoy.yaml"
is-optional: true
diff --git a/extras/hs-test/topo-containers/singleCpuPinning.yaml b/extras/hs-test/topo-containers/singleCpuPinning.yaml
new file mode 100644
index 00000000000..6e673aa85bf
--- /dev/null
+++ b/extras/hs-test/topo-containers/singleCpuPinning.yaml
@@ -0,0 +1,11 @@
+---
+volumes:
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
+
+containers:
+ - name: "vpp"
+ volumes:
+ - <<: *shared-vol
+ container-dir: "/tmp/vpp"
+ is-default-work-dir: true
diff --git a/extras/hs-test/topo-containers/nginxProxyAndServer.yaml b/extras/hs-test/topo-containers/vppProxy.yaml
index cc6b780bafc..a1f24bbc187 100644
--- a/extras/hs-test/topo-containers/nginxProxyAndServer.yaml
+++ b/extras/hs-test/topo-containers/vppProxy.yaml
@@ -1,20 +1,25 @@
---
volumes:
- - volume: &shared-vol-proxy
- host-dir: "$HST_VOLUME_DIR/shared-vol-proxy"
+ - volume: &shared-vol
+ host-dir: "$HST_VOLUME_DIR/shared-vol"
containers:
- name: "vpp-proxy"
volumes:
- - <<: *shared-vol-proxy
+ - <<: *shared-vol
container-dir: "/tmp/vpp"
is-default-work-dir: true
- - name: "nginx-proxy"
+ - name: "nginx-server"
volumes:
- - <<: *shared-vol-proxy
+ - <<: *shared-vol
container-dir: "/tmp/nginx"
is-default-work-dir: true
- image: "hs-test/nginx-ldp"
- is-optional: true
- - name: "nginx-server"
image: "hs-test/nginx-server"
+ is-optional: true
+ - name: "curl"
+ vars:
+ - name: LD_LIBRARY_PATH
+ value: "/usr/local/lib"
+ image: "hs-test/curl"
+ is-optional: true
+ run-detached: false
diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go
index 3b413b26bed..81da0c533b9 100644
--- a/extras/hs-test/vcl_test.go
+++ b/extras/hs-test/vcl_test.go
@@ -1,14 +1,15 @@
package main
import (
- . "fd.io/hs-test/infra"
"fmt"
"time"
+
+ . "fd.io/hs-test/infra"
)
func init() {
RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
- XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest)
+ XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclRetryAttachTest)
}
func getVclConfig(c *Container, ns_id_optional ...string) string {
@@ -89,7 +90,7 @@ func testVclEcho(s *VethsSuite, proto string) {
srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont))
srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf")
- srvAppCont.ExecServer("vcl_test_server " + port)
+ srvAppCont.ExecServer("vcl_test_server -p " + proto + " " + port)
serverVeth := s.GetInterfaceByName(ServerInterfaceName)
serverVethAddress := serverVeth.Ip4AddressString()
@@ -111,6 +112,10 @@ func VclEchoUdpTest(s *VethsSuite) {
testVclEcho(s, "udp")
}
+func VclHttpPostTest(s *VethsSuite) {
+ testVclEcho(s, "http")
+}
+
func VclRetryAttachTest(s *VethsSuite) {
testRetryAttach(s, "tcp")
}
diff --git a/extras/scripts/checkstyle.sh b/extras/scripts/checkstyle.sh
index 2b884f5f08b..304beff51d0 100755
--- a/extras/scripts/checkstyle.sh
+++ b/extras/scripts/checkstyle.sh
@@ -19,7 +19,7 @@ CLANG_FORMAT_VER_REGEX='([0-9]+)\.[0-9]+\.[0-9]+'
CLANG_FORMAT_DIFF="/usr/share/clang/clang-format-diff.py"
# TODO: Remove clang-format-${CLANG_FORMAT_VER} from 'make install-deps' when
-# CLANG_FORMAT_VER default value is upgraded
+# CLANG_FORMAT_VER default value is upgraded to 15 after Ubuntu-20.04 is deprecated
CLANG_FORMAT_VER=${CLANG_FORMAT_VER:-11}
GIT_DIFF_ARGS="-U0 --no-color --relative HEAD~1"
GIT_DIFF_EXCLUDE_LIST=(
@@ -30,16 +30,20 @@ CLANG_FORMAT_DIFF_ARGS="-style file -p1"
SUFFIX="-${CLANG_FORMAT_VER}"
# Attempt to find clang-format to confirm Clang version.
-if command -v clang-format${SUFFIX} &> /dev/null;
+if command -v "clang-format${SUFFIX}" &> /dev/null;
then
- CLANG_FORMAT=clang-format${SUFFIX}
-elif command -v clang-format &> /dev/null;
-then
- CLANG_FORMAT=clang-format
+ CLANG_FORMAT="clang-format${SUFFIX}"
+else
+ echo "*******************************************************************"
+ echo "* CHECKSTYLE FAILED"
+ echo "* Could not locate the clang-format${SUFFIX} script"
+ echo "* Run 'make install-deps' to install it"
+ echo "*******************************************************************"
+ exit 1
fi
CLANG_FORMAT_VERSION=$(${CLANG_FORMAT} --version)
-echo $CLANG_FORMAT_VERSION
+echo "$CLANG_FORMAT_VERSION"
# Confirm that Clang is the expected version.
if [[ ! $CLANG_FORMAT_VERSION =~ $CLANG_FORMAT_VER_REGEX ]];
@@ -75,6 +79,7 @@ then
echo "*******************************************************************"
echo "* CHECKSTYLE FAILED"
echo "* Could not locate the clang-format-diff script"
+ echo "* Run 'make install-deps' to install it"
echo "*******************************************************************"
exit 1
fi
diff --git a/extras/vpp_if_stats/apimock.go b/extras/vpp_if_stats/apimock.go
index 77363344090..25e76ed1d31 100755
--- a/extras/vpp_if_stats/apimock.go
+++ b/extras/vpp_if_stats/apimock.go
@@ -5,10 +5,11 @@
package main
import (
- api "git.fd.io/govpp.git/api"
- gomock "github.com/golang/mock/gomock"
reflect "reflect"
time "time"
+
+ api "git.fd.io/govpp.git/api"
+ gomock "github.com/golang/mock/gomock"
)
// MockChannel is a mock of Channel interface
diff --git a/extras/vpp_if_stats/json_structs.go b/extras/vpp_if_stats/json_structs.go
index a42b6d815b2..8e73337418e 100755
--- a/extras/vpp_if_stats/json_structs.go
+++ b/extras/vpp_if_stats/json_structs.go
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+
"git.fd.io/govpp.git/examples/bin_api/vpe"
)
diff --git a/extras/vpp_if_stats/statsmock.go b/extras/vpp_if_stats/statsmock.go
index 72528f53d9a..172176fa744 100755
--- a/extras/vpp_if_stats/statsmock.go
+++ b/extras/vpp_if_stats/statsmock.go
@@ -5,9 +5,10 @@
package main
import (
+ reflect "reflect"
+
adapter "git.fd.io/govpp.git/adapter"
gomock "github.com/golang/mock/gomock"
- reflect "reflect"
)
// MockStatsAPI is a mock of StatsAPI interface
diff --git a/extras/vpp_if_stats/vpp_if_stats.go b/extras/vpp_if_stats/vpp_if_stats.go
index 90c1700430c..751bbe5d5a9 100644
--- a/extras/vpp_if_stats/vpp_if_stats.go
+++ b/extras/vpp_if_stats/vpp_if_stats.go
@@ -3,6 +3,8 @@ package main
import (
"flag"
"fmt"
+ "log"
+
"git.fd.io/govpp.git"
"git.fd.io/govpp.git/adapter"
"git.fd.io/govpp.git/adapter/vppapiclient"
@@ -10,7 +12,6 @@ import (
"git.fd.io/govpp.git/core"
"git.fd.io/govpp.git/examples/bin_api/interfaces"
"git.fd.io/govpp.git/examples/bin_api/vpe"
- "log"
)
//////////////////////////////////////
diff --git a/extras/vpp_if_stats/vpp_if_stats_test.go b/extras/vpp_if_stats/vpp_if_stats_test.go
index 3c22ce85d75..53c990c4158 100644
--- a/extras/vpp_if_stats/vpp_if_stats_test.go
+++ b/extras/vpp_if_stats/vpp_if_stats_test.go
@@ -1,15 +1,16 @@
package main
import (
+ "math/rand"
+ "testing"
+ "time"
+
"git.fd.io/govpp.git/adapter"
"git.fd.io/govpp.git/api"
"git.fd.io/govpp.git/examples/bin_api/interfaces"
"git.fd.io/govpp.git/examples/bin_api/vpe"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
- "math/rand"
- "testing"
- "time"
)
var (