diff options
Diffstat (limited to 'extras/hs-test')
26 files changed, 1173 insertions, 322 deletions
diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index f0ec755afb2..e247bf44160 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -1,3 +1,4 @@ +export HS_ROOT=$(CURDIR) ifeq ($(VERBOSE),) VERBOSE=false @@ -23,6 +24,14 @@ ifeq ($(CPUS),) CPUS=1 endif +ifeq ($(PARALLEL),) +PARALLEL=1 +endif + +ifeq ($(REPEAT),) +REPEAT=0 +endif + ifeq ($(VPPSRC),) VPPSRC=$(shell pwd)/../.. endif @@ -35,13 +44,14 @@ ifeq ($(ARCH),) ARCH=$(shell dpkg --print-architecture) endif -list_tests = @(grep -r ') Test' *_test.go | cut -d '*' -f2 | cut -d '(' -f1 | \ - tr -d ' ' | tr ')' '/' | sed 's/Suite//') +list_tests = @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'Test' | \ + sed 's/^/* /; s/\(Suite\) /\1\//g' .PHONY: help help: @echo "Make targets:" @echo " test - run tests" + @echo " test-debug - run tests (vpp debug image)" @echo " build - build test infra" @echo " build-debug - build test infra (vpp debug image)" @echo " build-go - just build golang files" @@ -60,6 +70,8 @@ help: @echo " TEST=[test-name] - specific test to run" @echo " CPUS=[n-cpus] - number of cpus to run with vpp" @echo " VPPSRC=[path-to-vpp-src] - path to vpp source files (for gdb)" + @echo " PARALLEL=[n-cpus] - number of test processes to spawn to run in parallel" + @echo " REPEAT=[n] - repeat tests up to N times or until a failure occurs" @echo @echo "List of all tests:" $(call list_tests) @@ -68,30 +80,53 @@ help: list-tests: $(call list_tests) +.PHONY: build-vpp-release build-vpp-release: @make -C ../.. build-release +.PHONY: build-vpp-debug build-vpp-debug: @make -C ../.. build +.build.ok: build + @touch .build.ok + +.build_debug.ok: build-debug + @touch .build.ok + .PHONY: test -test: .deps.ok .build.vpp - @bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \ +test: .deps.ok .build.ok + # '-' ignores the exit status, it is set in compress.sh + # necessary so gmake won't skip executing the bash script + -bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \ + --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ + --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) + @bash ./script/compress.sh + +.PHONY: test-debug +test-debug: .deps.ok .build_debug.ok + # '-' ignores the exit status, it is set in compress.sh + # necessary so gmake won't skip executing the bash script + -bash ./test --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ - --vppsrc=$(VPPSRC) + --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) + @bash ./script/compress.sh +.PHONY: build-go build-go: go build ./tools/http_server +.PHONY: build build: .deps.ok build-vpp-release build-go - @rm -f .build.vpp + @rm -f .build.ok bash ./script/build_hst.sh release - @touch .build.vpp + @touch .build.ok +.PHONY: build-debug build-debug: .deps.ok build-vpp-debug build-go - @rm -f .build.vpp + @rm -f .build.ok bash ./script/build_hst.sh debug - @touch .build.vpp + @touch .build.ok .deps.ok: @sudo make install-deps @@ -101,14 +136,13 @@ install-deps: @rm -f .deps.ok @apt-get update \ && apt-get install -y apt-transport-https ca-certificates curl software-properties-common \ - && apt-get install -y golang apache2-utils wrk bridge-utils + apache2-utils wrk bridge-utils @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 @touch .deps.ok .PHONY: fixstyle diff --git a/extras/hs-test/README.rst b/extras/hs-test/README.rst index 6db832b7fbe..1dc1039b33f 100644 --- a/extras/hs-test/README.rst +++ b/extras/hs-test/README.rst @@ -9,7 +9,10 @@ End-to-end tests often want multiple VPP instances, network namespaces, differen and to execute external tools or commands. With such requirements the existing VPP test framework is not sufficient. For this, ``Go`` was chosen as a high level language, allowing rapid development, with ``Docker`` and ``ip`` being the tools for creating required topology. -Go's package `testing`_ together with `go test`_ command form the base framework upon which the *hs-test* is built and run. +`Ginkgo`_ forms the base framework upon which the *hs-test* is built and run. +All tests are technically in a single suite because we are only using ``package main``. We simulate suite behavior by grouping tests by the topology they require. +This allows us to run those mentioned groups in parallel, but not individual tests in parallel. + Anatomy of a test case ---------------------- @@ -24,15 +27,16 @@ Anatomy of a test case **Action flow when running a test case**: #. It starts with running ``make test``. Optional arguments are VERBOSE, PERSIST (topology configuration isn't cleaned up after test run), - and TEST=<test-name> to run specific test. -#. ``make list-tests`` (or ``make help``) shows all test names. -#. ``go test`` compiles package ``main`` along with any files with names matching the file pattern ``*_test.go`` - and then runs the resulting test binaries -#. The go test framework runs each function matching :ref:`naming convention<test-convention>`. Each of these corresponds to a `test suite`_ -#. Testify toolkit's ``suite.Run(t *testing.T, suite TestingSuite)`` function runs the suite and does the following: - + TEST=<test-name> to run a specific test and PARALLEL=[n-cpus]. +#. ``make list-tests`` (or ``make help``) shows all tests. The current `list of tests`_ is at the bottom of this document. +#. ``Ginkgo`` looks for a spec suite in the current directory and then compiles it to a .test binary +#. The Ginkgo test framework runs each function that was registered manually using ``registerMySuiteTest(s *MySuite)``. Each of these functions correspond to a suite +#. Ginkgo's ``RunSpecs(t, "Suite description")`` function is the entry point and does the following: + + #. Ginkgo compiles the spec, builds a spec tree + #. ``Describe`` container nodes in suite\_\*_test.go files are run (in series by default, or in parallel with the argument PARALLEL=[n-cpus]) #. Suite is initialized. The topology is loaded and configured in this step - #. Test suite runs all the tests attached to it + #. Registered tests are run in generated ``It`` subject nodes #. Execute tear-down functions, which currently consists of stopping running containers and clean-up of test topology @@ -43,23 +47,25 @@ This describes adding a new test case to an existing suite. For adding a new suite, please see `Modifying the framework`_ below. #. To write a new test case, create a file whose name ends with ``_test.go`` or pick one that already exists -#. Declare method whose name starts with ``Test`` and specifies its receiver as a pointer to the suite's struct (defined in ``framework_test.go``) +#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``suite_*_test.go``) #. Implement test behaviour inside the test method. This typically includes the following: - #. Retrieve a running container in which to run some action. Method ``getContainerByName`` - from ``HstSuite`` struct serves this purpose - #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI - #. Run arbitrary commands inside the containers with ``exec`` method - #. Run other external tool with one of the preexisting functions in the ``utils.go`` file. - For example, use ``wget`` with ``startWget`` function - #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else - #. Verify results of your tests using ``assert`` methods provided by the test suite, - implemented by HstSuite struct + #. Retrieve a running container in which to run some action. Method ``getContainerByName`` + from ``HstSuite`` struct serves this purpose + #. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``vppctl`` method to access debug CLI + #. Run arbitrary commands inside the containers with ``exec`` method + #. Run other external tool with one of the preexisting functions in the ``utils.go`` file. + For example, use ``wget`` with ``startWget`` function + #. Use ``exechelper`` or just plain ``exec`` packages to run whatever else + #. Verify results of your tests using ``assert`` methods provided by the test suite, implemented by HstSuite struct or use ``Gomega`` assert functions. + +#. Create an ``init()`` function and register the test using ``register*SuiteTests(testCaseFunction)`` + **Example test case** Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other. -This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./test -run TestMySuite/TestMyCase``. +This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest`` or ``ginkgo -v --trace --focus MyTest``. :: @@ -69,7 +75,11 @@ This can be put in file ``extras/hs-test/my_test.go`` and run with command ``./t "fmt" ) - func (s *MySuite) TestMyCase() { + func init(){ + registerMySuiteTest(MyTest) + } + + func MyTest(s *MySuite) { clientVpp := s.getContainerByName("client-vpp").vppInstance serverVethAddress := s.netInterfaces["server-iface"].AddressString() @@ -86,8 +96,7 @@ Modifying the framework .. _test-convention: -#. Adding a new suite takes place in ``framework_test.go`` and by creating a new file for the suite. - Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced +#. To add a new suite, create a new file. Naming convention for the suite files is ``suite_name_test.go`` where *name* will be replaced by the actual name #. Make a ``struct``, in the suite file, with at least ``HstSuite`` struct as its member. @@ -99,7 +108,17 @@ Modifying the framework HstSuite } -#. In suite file, implement ``SetupSuite`` method which testify runs once before starting any of the tests. +#. Create a new slice that will contain test functions with a pointer to the suite's struct: ``var myTests = []func(s *MySuite){}`` + +#. Then create a new function that will append test functions to that slice: + + :: + + func registerMySuiteTests(tests ...func(s *MySuite)) { + nginxTests = append(myTests, tests...) + } + +#. In suite file, implement ``SetupSuite`` method which Ginkgo runs once before starting any of the tests. It's important here to call ``configureNetworkTopology`` method, pass the topology name to the function in a form of file name of one of the *yaml* files in ``topo-network`` folder. Without the extension. In this example, *myTopology* corresponds to file ``extras/hs-test/topo-network/myTopology.yaml`` @@ -111,6 +130,8 @@ Modifying the framework :: func (s *MySuite) SetupSuite() { + s.HstSuite.SetupSuite() + // Add custom setup code here s.configureNetworkTopology("myTopology") @@ -123,19 +144,62 @@ Modifying the framework :: func (s *MySuite) SetupTest() { + s.HstSuite.setupTest() s.SetupVolumes() s.SetupContainers() } -#. In order for ``go test`` to run this suite, we need to create a normal test function and pass our suite to ``suite.Run``. - These functions are placed at the end of ``framework_test.go`` +#. In order for ``Ginkgo`` to run this suite, we need to create a ``Describe`` container node with setup nodes and an ``It`` subject node. + Place them at the end of the suite file + + * Declare a suite struct variable before anything else + * To use ``BeforeAll()`` and ``AfterAll()``, the container has to be marked as ``Ordered`` + * Because the container is now marked as Ordered, if a test fails, all the subsequent tests are skipped. + To override this behavior, decorate the container node with ``ContinueOnFailure`` :: - func TestMySuite(t *testing.T) { - var m MySuite - suite.Run(t, &m) - } + var _ = Describe("MySuite", Ordered, ContinueOnFailure, func() { + var s MySuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + for _, test := range mySuiteTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + It(strings.Split(funcValue.Name(), ".")[2], func(ctx SpecContext) { + test(&s) + }, SpecTimeout(time.Minute*5)) + } + }) + +#. Notice the loop - it will generate multiple ``It`` nodes, each running a different test. + ``test := test`` is necessary, otherwise only the last test in a suite will run. + For a more detailed description, check Ginkgo's documentation: https://onsi.github.io/ginkgo/#dynamically-generating-specs\. + +#. ``funcValue.Name()`` returns the full name of a function (e.g. ``fd.io/hs-test.MyTest``), however, we only need the test name (``MyTest``). + +#. To run certain tests solo, create a new slice that will only contain tests that have to run solo and a new register function. + Add a ``Serial`` decorator to the container node and ``Label("SOLO")`` to the ``It`` subject node: + + :: + + var _ = Describe("MySuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + ... + It(strings.Split(funcValue.Name(), ".")[2], Label("SOLO"), func(ctx SpecContext) { + test(&s) + }, SpecTimeout(time.Minute*5)) + }) #. Next step is to add test cases to the suite. For that, see section `Adding a test case`_ above @@ -186,14 +250,9 @@ Alternatively copy the executable from host system to the Docker image, similarl **Skipping tests** ``HstSuite`` provides several methods that can be called in tests for skipping it conditionally or unconditionally such as: -``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. +``skip()``, ``SkipIfMultiWorker()``, ``SkipUnlessExtendedTestsBuilt()``. You can also use Ginkgo's ``Skip()``. 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 prefix test name with ``Skip``: - -:: - - func (s *MySuite) SkipTest(){ - +test run time it is not advisable to use aforementioned skip methods and instead, just don't register the test. **Debugging a test** @@ -201,11 +260,11 @@ It is possible to debug VPP by attaching ``gdb`` before test execution by adding :: - $ make test TEST=TestVeths/TestLDPreloadIperfVpp DEBUG=true + $ make test TEST=LDPreloadIperfVppTest DEBUG=true ... run following command in different terminal: - docker exec -it server-vpp gdb -ex "attach $(docker exec server-vpp pidof vpp)" - Afterwards press CTRL+C to continue + docker exec -it server-vpp2456109 gdb -ex "attach $(docker exec server-vpp2456109 pidof vpp)" + Afterwards press CTRL+\ to continue If a test consists of more VPP instances then this is done for each of them. @@ -223,8 +282,38 @@ Generally, these will be updated on a per-need basis, for example when a bug is or a new version incompatibility issue occurs. -.. _testing: https://pkg.go.dev/testing -.. _go test: https://pkg.go.dev/cmd/go#hdr-Test_packages -.. _test suite: https://github.com/stretchr/testify#suite-package +.. _ginkgo: https://onsi.github.io/ginkgo/ .. _volumes: https://docs.docker.com/storage/volumes/ +**List of tests** + +.. _list of tests: + +Please update this list whenever you add a new test by pasting the output below. + +* NsSuite/HttpTpsTest +* NsSuite/VppProxyHttpTcpTest +* NsSuite/VppProxyHttpTlsTest +* NsSuite/EnvoyProxyHttpTcpTest +* NginxSuite/MirroringTest +* VethsSuiteSolo TcpWithLossTest [SOLO] +* NoTopoSuiteSolo HttpStaticPromTest [SOLO] +* TapSuite/LinuxIperfTest +* NoTopoSuite/NginxHttp3Test +* NoTopoSuite/NginxAsServerTest +* NoTopoSuite/NginxPerfCpsTest +* NoTopoSuite/NginxPerfRpsTest +* NoTopoSuite/NginxPerfWrkTest +* VethsSuite/EchoBuiltinTest +* VethsSuite/HttpCliTest +* VethsSuite/LDPreloadIperfVppTest +* VethsSuite/VppEchoQuicTest +* VethsSuite/VppEchoTcpTest +* VethsSuite/VppEchoUdpTest +* VethsSuite/XEchoVclClientUdpTest +* VethsSuite/XEchoVclClientTcpTest +* VethsSuite/XEchoVclServerUdpTest +* VethsSuite/XEchoVclServerTcpTest +* VethsSuite/VclEchoTcpTest +* VethsSuite/VclEchoUdpTest +* VethsSuite/VclRetryAttachTest diff --git a/extras/hs-test/address_allocator.go b/extras/hs-test/address_allocator.go index 72bc298fd3f..e05ea76b9bb 100644 --- a/extras/hs-test/address_allocator.go +++ b/extras/hs-test/address_allocator.go @@ -12,7 +12,7 @@ import ( type AddressCounter = int type Ip4AddressAllocator struct { - networks map[int]AddressCounter + networks map[int]AddressCounter chosenOctet int assignedIps []string } @@ -47,7 +47,7 @@ func (a *Ip4AddressAllocator) NewIp4InterfaceAddress(inputNetworkNumber ...int) // Creates a file every time an IP is assigned: used to keep track of addresses in use. // If an address is not in use, 'counter' is then copied to 'chosenOctet' and it is used for the remaining tests. // Also checks host IP addresses. -func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddresses int) (string, error){ +func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddresses int) (string, error) { hostIps, _ := exechelper.CombinedOutput("ip a") counter := 10 var address string @@ -56,7 +56,7 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress if a.chosenOctet != 0 { address = fmt.Sprintf("10.%v.%v.%v", a.chosenOctet, networkNumber, numberOfAddresses) file, err := os.Create(address) - if err != nil{ + if err != nil { return "", errors.New("unable to create file: " + fmt.Sprint(err)) } file.Close() @@ -68,14 +68,14 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress counter++ } else if os.IsNotExist(err) { file, err := os.Create(address) - if err != nil{ + if err != nil { return "", errors.New("unable to create file: " + fmt.Sprint(err)) - } + } file.Close() a.chosenOctet = counter break } else { - return "", errors.New("an error occured while checking if a file exists: " + fmt.Sprint(err)) + return "", errors.New("an error occurred while checking if a file exists: " + fmt.Sprint(err)) } } } @@ -84,8 +84,8 @@ func (a *Ip4AddressAllocator) createIpAddress(networkNumber int, numberOfAddress return address, nil } -func (a *Ip4AddressAllocator) deleteIpAddresses(){ - for ip := range a.assignedIps{ +func (a *Ip4AddressAllocator) deleteIpAddresses() { + for ip := range a.assignedIps { os.Remove(a.assignedIps[ip]) } } diff --git a/extras/hs-test/container.go b/extras/hs-test/container.go index 87e8aa38e98..0bdc3a24996 100644 --- a/extras/hs-test/container.go +++ b/extras/hs-test/container.go @@ -9,6 +9,7 @@ import ( "time" "github.com/edwarnicke/exechelper" + . "github.com/onsi/ginkgo/v2" ) const ( @@ -38,7 +39,7 @@ type Container struct { vppInstance *VppInstance } -func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Container, error) { +func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) { containerName := yamlInput["name"].(string) if len(containerName) == 0 { err := fmt.Errorf("container name must not be blank") @@ -48,7 +49,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Cont var container = new(Container) container.volumes = make(map[string]Volume) container.envVars = make(map[string]string) - container.name = containerName + pid + container.name = containerName container.suite = suite if image, ok := yamlInput["image"]; ok { @@ -76,7 +77,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig, pid string) (*Cont } if _, ok := yamlInput["volumes"]; ok { - workingVolumeDir := logDir + container.suite.T().Name() + pid + volumeDir + workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + volumeDir workDirReplacer := strings.NewReplacer("$HST_DIR", workDir) volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) for _, volu := range yamlInput["volumes"].([]interface{}) { @@ -238,6 +239,7 @@ func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*Vp vpp := new(VppInstance) vpp.container = c vpp.cpus = cpus + c.suite.vppContainerCount += 1 vpp.additionalConfig = append(vpp.additionalConfig, additionalConfigs...) c.vppInstance = vpp return vpp, nil @@ -249,7 +251,7 @@ func (c *Container) copy(sourceFileName string, targetFileName string) error { } func (c *Container) createFile(destFileName string, content string) error { - f, err := os.CreateTemp("/tmp", "hst-config" + c.suite.pid) + f, err := os.CreateTemp("/tmp", "hst-config"+c.suite.pid) if err != nil { return err } @@ -273,7 +275,7 @@ func (c *Container) execServer(command string, arguments ...any) { serverCommand := fmt.Sprintf(command, arguments...) containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() + " " + c.name + " " + serverCommand - c.suite.T().Helper() + GinkgoHelper() c.suite.log(containerExecCommand) c.suite.assertNil(exechelper.Run(containerExecCommand)) } @@ -282,7 +284,7 @@ func (c *Container) exec(command string, arguments ...any) string { cliCommand := fmt.Sprintf(command, arguments...) containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() + " " + c.name + " " + cliCommand - c.suite.T().Helper() + GinkgoHelper() c.suite.log(containerExecCommand) byteOutput, err := exechelper.CombinedOutput(containerExecCommand) c.suite.assertNil(err, err) @@ -291,12 +293,12 @@ func (c *Container) exec(command string, arguments ...any) string { func (c *Container) getLogDirPath() string { testId := c.suite.getTestId() - testName := c.suite.T().Name() + testName := CurrentSpecReport().LeafNodeText logDirPath := logDir + testName + "/" + testId + "/" cmd := exec.Command("mkdir", "-p", logDirPath) if err := cmd.Run(); err != nil { - c.suite.T().Fatalf("mkdir error: %v", err) + Fail("mkdir error: " + fmt.Sprint(err)) } return logDirPath @@ -313,12 +315,12 @@ func (c *Container) saveLogs() { cmd = exec.Command("docker", "logs", "--details", "-t", c.name) output, err := cmd.CombinedOutput() if err != nil { - c.suite.T().Fatalf("fetching logs error: %v", err) + Fail("fetching logs error: " + fmt.Sprint(err)) } f, err := os.Create(testLogFilePath) if err != nil { - c.suite.T().Fatalf("file create error: %v", err) + Fail("file create error: " + fmt.Sprint(err)) } fmt.Fprint(f, string(output)) f.Close() @@ -339,7 +341,7 @@ func (c *Container) log(maxLines int) (string, error) { } func (c *Container) stop() error { - if c.vppInstance != nil && c.vppInstance.apiChannel != nil { + if c.vppInstance != nil && c.vppInstance.apiStream != nil { c.vppInstance.saveLogs() c.vppInstance.disconnect() } diff --git a/extras/hs-test/cpu.go b/extras/hs-test/cpu.go index e17bc11fbe0..69b4cabd4e3 100644 --- a/extras/hs-test/cpu.go +++ b/extras/hs-test/cpu.go @@ -2,43 +2,70 @@ package main import ( "bufio" + "errors" "fmt" + . "github.com/onsi/ginkgo/v2" "os" + "os/exec" + "strings" ) -var CPU_PATH = "/sys/fs/cgroup/cpuset.cpus.effective" +var CgroupPath = "/sys/fs/cgroup/" type CpuContext struct { cpuAllocator *CpuAllocatorT cpus []int } -func (c *CpuContext) Release() { - c.cpuAllocator.cpus = append(c.cpuAllocator.cpus, c.cpus...) - c.cpus = c.cpus[:0] // empty the list -} - type CpuAllocatorT struct { cpus []int } var cpuAllocator *CpuAllocatorT = nil -func (c *CpuAllocatorT) Allocate(nCpus int) (*CpuContext, error) { +func (c *CpuAllocatorT) Allocate(vppContainerCount int, nCpus int) (*CpuContext, error) { var cpuCtx CpuContext - - if len(c.cpus) < nCpus { - return nil, fmt.Errorf("could not allocate %d CPUs; available: %d", nCpus, len(c.cpus)) + maxCpu := GinkgoParallelProcess() * 2 * nCpus + minCpu := (GinkgoParallelProcess() - 1) * 2 * nCpus + if len(c.cpus) < maxCpu { + vppContainerCount += 1 + err := fmt.Errorf("could not allocate %d CPUs; available: %d; attempted to allocate cores %d-%d", + nCpus*vppContainerCount, len(c.cpus), minCpu, minCpu+nCpus*vppContainerCount) + return nil, err } - cpuCtx.cpus = c.cpus[0:nCpus] + if vppContainerCount == 0 { + cpuCtx.cpus = c.cpus[minCpu : maxCpu-nCpus] + } else if vppContainerCount == 1 { + cpuCtx.cpus = c.cpus[minCpu+nCpus : maxCpu] + } else { + return nil, fmt.Errorf("too many VPP containers; CPU allocation for >2 VPP containers is not implemented yet") + } + cpuCtx.cpuAllocator = c - c.cpus = c.cpus[nCpus:] return &cpuCtx, nil } -func (c *CpuAllocatorT) readCpus(fname string) error { +func (c *CpuAllocatorT) readCpus() error { var first, last int - file, err := os.Open(CPU_PATH) + + // Path depends on cgroup version. We need to check which version is in use. + // For that following command can be used: 'stat -fc %T /sys/fs/cgroup/' + // In case the output states 'cgroup2fs' then cgroups v2 is used, 'tmpfs' in case cgroups v1. + cmd := exec.Command("stat", "-fc", "%T", "/sys/fs/cgroup/") + byteOutput, err := cmd.CombinedOutput() + if err != nil { + return err + } + CpuPath := CgroupPath + if strings.Contains(string(byteOutput), "tmpfs") { + CpuPath += "cpuset/cpuset.effective_cpus" + } else if strings.Contains(string(byteOutput), "cgroup2fs") { + CpuPath += "cpuset.cpus.effective" + } else { + return errors.New("cgroup unknown fs: " + string(byteOutput)) + } + + file, err := os.Open(CpuPath) if err != nil { return err } @@ -60,7 +87,7 @@ func (c *CpuAllocatorT) readCpus(fname string) error { func CpuAllocator() (*CpuAllocatorT, error) { if cpuAllocator == nil { cpuAllocator = new(CpuAllocatorT) - err := cpuAllocator.readCpus(CPU_PATH) + err := cpuAllocator.readCpus() if err != nil { return nil, err } diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp index 6b057581d4b..ace83c593c6 100644 --- a/extras/hs-test/docker/Dockerfile.vpp +++ b/extras/hs-test/docker/Dockerfile.vpp @@ -16,6 +16,8 @@ COPY \ $DIR/unittest_plugin.so \ $DIR/quic_plugin.so \ $DIR/http_static_plugin.so \ + $DIR/ping_plugin.so \ + $DIR/nsim_plugin.so \ $DIR/prom_plugin.so \ $DIR/tlsopenssl_plugin.so \ /usr/lib/x86_64-linux-gnu/vpp_plugins/ diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 690f6d1378e..ce852bea3e0 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -1,6 +1,11 @@ package main -func (s *VethsSuite) TestEchoBuiltin() { +func init() { + registerVethTests(EchoBuiltinTest) + registerSoloVethTests(TcpWithLossTest) +} + +func EchoBuiltinTest(s *VethsSuite) { serverVpp := s.getContainerByName("server-vpp").vppInstance serverVeth := s.getInterfaceByName(serverInterfaceName) @@ -16,7 +21,9 @@ func (s *VethsSuite) TestEchoBuiltin() { s.assertNotContains(o, "failed:") } -func (s *VethsSuite) TestTcpWithLoss() { +// unstable with multiple workers +func TcpWithLossTest(s *VethsSuite) { + s.SkipIfMultiWorker() serverVpp := s.getContainerByName("server-vpp").vppInstance serverVeth := s.getInterfaceByName(serverInterfaceName) @@ -26,19 +33,19 @@ func (s *VethsSuite) TestTcpWithLoss() { clientVpp := s.getContainerByName("client-vpp").vppInstance // Ensure that VPP doesn't abort itself with NSIM enabled - // Warning: Removing this ping will make the test fail! + // Warning: Removing this ping will make VPP crash! clientVpp.vppctl("ping %s", serverVeth.ip4AddressString()) // Add loss of packets with Network Delay Simulator clientVpp.vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + " packet-size 1400 packets-per-drop 1000") - clientVpp.vppctl("nsim output-feature enable-disable " + s.getInterfaceByName(clientInterfaceName).name) + clientVpp.vppctl("nsim output-feature enable-disable host-" + s.getInterfaceByName(clientInterfaceName).name) // Do echo test from client-vpp container output := clientVpp.vppctl("test echo client uri tcp://%s/20022 verbose echo-bytes mbytes 50", serverVeth.ip4AddressString()) s.log(output) s.assertNotEqual(len(output), 0) - s.assertNotContains(output, "failed: timeout", output) + s.assertNotContains(output, "failed", output) } diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go index 84aa570e681..8773fa2417d 100644 --- a/extras/hs-test/framework_test.go +++ b/extras/hs-test/framework_test.go @@ -3,30 +3,11 @@ package main import ( "testing" - "github.com/stretchr/testify/suite" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -func TestTapSuite(t *testing.T) { - var m TapSuite - suite.Run(t, &m) -} - -func TestNs(t *testing.T) { - var m NsSuite - suite.Run(t, &m) -} - -func TestVeths(t *testing.T) { - var m VethsSuite - suite.Run(t, &m) -} - -func TestNoTopo(t *testing.T) { - var m NoTopoSuite - suite.Run(t, &m) -} - -func TestNginx(t *testing.T) { - var m NginxSuite - suite.Run(t, &m) +func TestHst(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "HST") } diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod index 00e1213079d..3be9ba20a86 100644 --- a/extras/hs-test/go.mod +++ b/extras/hs-test/go.mod @@ -4,20 +4,29 @@ go 1.21 require ( github.com/edwarnicke/exechelper v1.0.3 - github.com/stretchr/testify v1.8.4 - go.fd.io/govpp v0.9.0 + go.fd.io/govpp v0.10.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + 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/fsnotify/fsnotify v1.7.0 // 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/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.3 github.com/vishvananda/netns v0.0.4 // indirect golang.org/x/sys v0.16.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum index df596738622..479b0289814 100644 --- a/extras/hs-test/go.sum +++ b/extras/hs-test/go.sum @@ -1,3 +1,6 @@ +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/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= @@ -6,10 +9,17 @@ github.com/edwarnicke/exechelper v1.0.3 h1:OY2ocGAITTqnEDvZk0dRQSeMIQvyH0SyL/4nc github.com/edwarnicke/exechelper v1.0.3/go.mod h1:R65OUPKns4bgeHkCmfSHbmqLBU8aHZxTgLmEyUBUk4U= 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/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/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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -18,8 +28,10 @@ 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/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +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/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= @@ -28,22 +40,27 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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= -go.fd.io/govpp v0.9.0 h1:EHUXhQ+dph2K2An4YMqmd/WBE3Fcqsg97KVmdLJoSoU= -go.fd.io/govpp v0.9.0/go.mod h1:9QoqjEbvfuuXNfjHS0A7YS+7QQVVaQ9cMioOWpSM4rY= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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= 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-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= 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= diff --git a/extras/hs-test/hst_suite.go b/extras/hs-test/hst_suite.go index c5c8edbb829..9cb79c563fa 100644 --- a/extras/hs-test/hst_suite.go +++ b/extras/hs-test/hst_suite.go @@ -5,14 +5,16 @@ import ( "errors" "flag" "fmt" + "io" + "log" "os" "os/exec" "strings" "time" "github.com/edwarnicke/exechelper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "gopkg.in/yaml.v3" ) @@ -28,31 +30,39 @@ var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp") var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory") type HstSuite struct { - suite.Suite - containers map[string]*Container - volumes []string - netConfigs []NetConfig - netInterfaces map[string]*NetInterface - ip4AddrAllocator *Ip4AddressAllocator - testIds map[string]string - cpuAllocator *CpuAllocatorT - cpuContexts []*CpuContext - cpuPerVpp int - pid string + containers map[string]*Container + vppContainerCount int + volumes []string + netConfigs []NetConfig + netInterfaces map[string]*NetInterface + ip4AddrAllocator *Ip4AddressAllocator + testIds map[string]string + cpuAllocator *CpuAllocatorT + cpuContexts []*CpuContext + cpuPerVpp int + pid string + logger *log.Logger + logFile *os.File } func (s *HstSuite) SetupSuite() { + s.createLogger() + s.log("Suite Setup") + RegisterFailHandler(func(message string, callerSkip ...int) { + s.hstFail() + Fail(message, callerSkip...) + }) var err error s.pid = fmt.Sprint(os.Getpid()) s.cpuAllocator, err = CpuAllocator() if err != nil { - s.FailNow("failed to init cpu allocator: %v", err) + Fail("failed to init cpu allocator: " + fmt.Sprint(err)) } s.cpuPerVpp = *nConfiguredCpus } func (s *HstSuite) AllocateCpus() []int { - cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp) + cpuCtx, err := s.cpuAllocator.Allocate(s.vppContainerCount, s.cpuPerVpp) s.assertNil(err) s.AddCpuContext(cpuCtx) return cpuCtx.cpus @@ -63,16 +73,16 @@ func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) { } func (s *HstSuite) TearDownSuite() { + defer s.logFile.Close() + s.log("Suite Teardown") s.unconfigureNetworkTopology() } func (s *HstSuite) TearDownTest() { + s.log("Test Teardown") if *isPersistent { return } - for _, c := range s.cpuContexts { - c.Release() - } s.resetContainers() s.removeVolumes() s.ip4AddrAllocator.deleteIpAddresses() @@ -85,6 +95,8 @@ func (s *HstSuite) skipIfUnconfiguring() { } func (s *HstSuite) SetupTest() { + s.log("Test Setup") + s.vppContainerCount = 0 s.skipIfUnconfiguring() s.setupVolumes() s.setupContainers() @@ -106,15 +118,15 @@ func (s *HstSuite) setupContainers() { } } -func logVppInstance(container *Container, maxLines int){ - if container.vppInstance == nil{ +func (s *HstSuite) logVppInstance(container *Container, maxLines int) { + if container.vppInstance == nil { return } logSource := container.getHostWorkDir() + defaultLogFilePath file, err := os.Open(logSource) - if err != nil{ + if err != nil { return } defer file.Close() @@ -123,7 +135,7 @@ func logVppInstance(container *Container, maxLines int){ var lines []string var counter int - for scanner.Scan(){ + for scanner.Scan() { lines = append(lines, scanner.Text()) counter++ if counter > maxLines { @@ -132,82 +144,81 @@ func logVppInstance(container *Container, maxLines int){ } } - fmt.Println("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:") - for _, line := range lines{ - fmt.Println(line) + s.log("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:") + for _, line := range lines { + s.log(line) } - fmt.Printf("^^^^^^^^^^^^^^^\n\n") + s.log("^^^^^^^^^^^^^^^\n\n") } func (s *HstSuite) hstFail() { - fmt.Println("Containers: " + fmt.Sprint(s.containers)) - for _, container := range s.containers{ + s.log("Containers: " + fmt.Sprint(s.containers)) + for _, container := range s.containers { out, err := container.log(20) - if err != nil{ - fmt.Printf("An error occured while obtaining '%s' container logs: %s\n", container.name, fmt.Sprint(err)) - break + if err != nil { + s.log("An error occured while obtaining '" + container.name + "' container logs: " + fmt.Sprint(err)) + continue } - fmt.Printf("\nvvvvvvvvvvvvvvv " + - container.name + ":\n" + - out + - "^^^^^^^^^^^^^^^\n\n") - logVppInstance(container, 20) + s.log("\nvvvvvvvvvvvvvvv " + + container.name + ":\n" + + out + + "^^^^^^^^^^^^^^^\n\n") + s.logVppInstance(container, 20) } - s.T().FailNow() } func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) { - if !assert.Nil(s.T(), object, msgAndArgs...) { - s.hstFail() - } + Expect(object).To(BeNil(), msgAndArgs...) } func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) { - if !assert.NotNil(s.T(), object, msgAndArgs...) { - s.hstFail() - } + Expect(object).ToNot(BeNil(), msgAndArgs...) } func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) { - if !assert.Equal(s.T(), expected, actual, msgAndArgs...) { - s.hstFail() - } + Expect(actual).To(Equal(expected), msgAndArgs...) } func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) { - if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) { - s.hstFail() - } + Expect(actual).ToNot(Equal(expected), msgAndArgs...) } func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) { - if !assert.Contains(s.T(), testString, contains, msgAndArgs...) { - s.hstFail() - } + Expect(testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...) } func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) { - if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) { - s.hstFail() - } + Expect(testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...) } func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) { - if !assert.NotEmpty(s.T(), object, msgAndArgs...) { - s.hstFail() + Expect(object).ToNot(BeEmpty(), msgAndArgs...) +} + +func (s *HstSuite) createLogger() { + suiteName := CurrentSpecReport().ContainerHierarchyTexts[0] + var err error + s.logFile, err = os.Create("summary/" + suiteName + ".log") + if err != nil { + Fail("Unable to create log file.") } + s.logger = log.New(io.Writer(s.logFile), "", log.LstdFlags) } -func (s *HstSuite) log(args ...any) { +// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter +// to keep console tidy +func (s *HstSuite) log(arg any) { + logs := strings.Split(fmt.Sprint(arg), "\n") + for _, line := range logs { + s.logger.Println(line) + } if *isVerbose { - s.T().Helper() - s.T().Log(args...) + GinkgoWriter.Println(arg) } } -func (s *HstSuite) skip(args ...any) { - s.log(args...) - s.T().SkipNow() +func (s *HstSuite) skip(args string) { + Skip(args) } func (s *HstSuite) SkipIfMultiWorker(args ...any) { @@ -249,11 +260,11 @@ func (s *HstSuite) getNetNamespaceByName(name string) string { } func (s *HstSuite) getInterfaceByName(name string) *NetInterface { - return s.netInterfaces[name + s.pid] + return s.netInterfaces[name+s.pid] } func (s *HstSuite) getContainerByName(name string) *Container { - return s.containers[name + s.pid] + return s.containers[name+s.pid] } /* @@ -261,25 +272,25 @@ func (s *HstSuite) getContainerByName(name string) *Container { * are not able to modify the original container and affect other tests by doing that */ func (s *HstSuite) getTransientContainerByName(name string) *Container { - containerCopy := *s.containers[name + s.pid] + containerCopy := *s.containers[name+s.pid] return &containerCopy } func (s *HstSuite) loadContainerTopology(topologyName string) { data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml") if err != nil { - s.T().Fatalf("read error: %v", err) + Fail("read error: " + fmt.Sprint(err)) } var yamlTopo YamlTopology err = yaml.Unmarshal(data, &yamlTopo) if err != nil { - s.T().Fatalf("unmarshal error: %v", err) + Fail("unmarshal error: " + fmt.Sprint(err)) } for _, elem := range yamlTopo.Volumes { volumeMap := elem["volume"].(VolumeConfig) hostDir := volumeMap["host-dir"].(string) - workingVolumeDir := logDir + s.T().Name() + s.pid + volumeDir + workingVolumeDir := logDir + CurrentSpecReport().LeafNodeText + volumeDir volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) hostDir = volDirReplacer.Replace(hostDir) s.volumes = append(s.volumes, hostDir) @@ -287,10 +298,11 @@ func (s *HstSuite) loadContainerTopology(topologyName string) { s.containers = make(map[string]*Container) for _, elem := range yamlTopo.Containers { - newContainer, err := newContainer(s, elem, s.pid) + newContainer, err := newContainer(s, elem) newContainer.suite = s + newContainer.name += newContainer.suite.pid if err != nil { - s.T().Fatalf("container config error: %v", err) + Fail("container config error: " + fmt.Sprint(err)) } s.containers[newContainer.name] = newContainer } @@ -299,12 +311,12 @@ func (s *HstSuite) loadContainerTopology(topologyName string) { func (s *HstSuite) loadNetworkTopology(topologyName string) { data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml") if err != nil { - s.T().Fatalf("read error: %v", err) + Fail("read error: " + fmt.Sprint(err)) } var yamlTopo YamlTopology err = yaml.Unmarshal(data, &yamlTopo) if err != nil { - s.T().Fatalf("unmarshal error: %v", err) + Fail("unmarshal error: " + fmt.Sprint(err)) } s.ip4AddrAllocator = NewIp4AddressAllocator() @@ -316,10 +328,10 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { } if peer, ok := elem["peer"].(NetDevConfig); ok { - if peer["name"].(string) != ""{ + if peer["name"].(string) != "" { peer["name"] = peer["name"].(string) + s.pid } - if _, ok := peer["netns"]; ok{ + if _, ok := peer["netns"]; ok { peer["netns"] = peer["netns"].(string) + s.pid } } @@ -341,7 +353,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { if namespace, err := newNetNamespace(elem); err == nil { s.netConfigs = append(s.netConfigs, &namespace) } else { - s.T().Fatalf("network config error: %v", err) + Fail("network config error: " + fmt.Sprint(err)) } } case Veth, Tap: @@ -350,7 +362,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { s.netConfigs = append(s.netConfigs, netIf) s.netInterfaces[netIf.Name()] = netIf } else { - s.T().Fatalf("network config error: %v", err) + Fail("network config error: " + fmt.Sprint(err)) } } case Bridge: @@ -358,7 +370,7 @@ func (s *HstSuite) loadNetworkTopology(topologyName string) { if bridge, err := newBridge(elem); err == nil { s.netConfigs = append(s.netConfigs, &bridge) } else { - s.T().Fatalf("network config error: %v", err) + Fail("network config error: " + fmt.Sprint(err)) } } } @@ -374,7 +386,7 @@ func (s *HstSuite) configureNetworkTopology(topologyName string) { for _, nc := range s.netConfigs { if err := nc.configure(); err != nil { - s.T().Fatalf("network config error: %v", err) + Fail("Network config error: " + fmt.Sprint(err)) } } } @@ -389,7 +401,7 @@ func (s *HstSuite) unconfigureNetworkTopology() { } func (s *HstSuite) getTestId() string { - testName := s.T().Name() + testName := CurrentSpecReport().LeafNodeText if s.testIds == nil { s.testIds = map[string]string{} @@ -462,7 +474,7 @@ func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, ad err := cmd.Start() s.log(cmd) if err != nil { - fmt.Println("Failed to start http server: " + fmt.Sprint(err)) + s.log("Failed to start http server: " + fmt.Sprint(err)) return } running <- struct{}{} diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 943c8a591d4..94cb0cad064 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -2,11 +2,25 @@ package main import ( "fmt" + "net/http" "os" "strings" + "time" + + . "github.com/onsi/ginkgo/v2" ) -func (s *NsSuite) TestHttpTps() { +func init() { + registerNsTests(HttpTpsTest) + registerVethTests(HttpCliTest, HttpCliConnectErrorTest) + registerNoTopoTests(NginxHttp3Test, NginxAsServerTest, + NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest, HeaderServerTest, + HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, + HttpCliBadRequestTest) + registerNoTopoSoloTests(HttpStaticPromTest) +} + +func HttpTpsTest(s *NsSuite) { iface := s.getInterfaceByName(clientInterface) client_ip := iface.ip4AddressString() port := "8080" @@ -18,13 +32,16 @@ func (s *NsSuite) TestHttpTps() { // configure vpp in the container container.vppInstance.vppctl("http tps uri tcp://0.0.0.0/8080") - go s.startWget(finished, client_ip, port, "test_file_10M", clientNetns) + go func() { + defer GinkgoRecover() + s.startWget(finished, client_ip, port, "test_file_10M", clientNetns) + }() // wait for client err := <-finished - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) } -func (s *VethsSuite) TestHttpCli() { +func HttpCliTest(s *VethsSuite) { serverContainer := s.getContainerByName("server-vpp") clientContainer := s.getContainerByName("client-vpp") @@ -35,13 +52,27 @@ func (s *VethsSuite) TestHttpCli() { uri := "http://" + serverVeth.ip4AddressString() + "/80" o := clientContainer.vppInstance.vppctl("http cli client" + - " uri " + uri + " query /show/version") + " uri " + uri + " query /show/vlib/graph") s.log(o) s.assertContains(o, "<html>", "<html> not found in the result!") } -func (s *NoTopoSuite) TestNginxHttp3() { +func HttpCliConnectErrorTest(s *VethsSuite) { + clientContainer := s.getContainerByName("client-vpp") + + serverVeth := s.getInterfaceByName(serverInterfaceName) + + uri := "http://" + serverVeth.ip4AddressString() + "/80" + + o := clientContainer.vppInstance.vppctl("http cli client" + + " uri " + uri + " query /show/vlib/graph") + + s.log(o) + s.assertContains(o, "failed to connect") +} + +func NginxHttp3Test(s *NoTopoSuite) { s.SkipUnlessExtendedTestsBuilt() query := "index.html" @@ -57,23 +88,117 @@ func (s *NoTopoSuite) TestNginxHttp3() { args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query) curlCont.extraRunningArgs = args o, err := curlCont.combinedOutput() - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.assertContains(o, "<http>", "<http> not found in the result!") } -func (s *NoTopoSuite) TestHttpStaticProm() { +func HttpStaticPromTest(s *NoTopoSuite) { finished := make(chan error, 1) query := "stats.prom" vpp := s.getContainerByName("vpp").vppInstance serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() s.log(vpp.vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) s.log(vpp.vppctl("prom enable")) - go s.startWget(finished, serverAddress, "80", query, "") + time.Sleep(time.Second * 5) + go func() { + defer GinkgoRecover() + s.startWget(finished, serverAddress, "80", query, "") + }() err := <-finished - s.assertNil(err, err) + s.assertNil(err) +} + +func HttpStaticMovedTest(s *NoTopoSuite) { + vpp := s.getContainerByName("vpp").vppInstance + vpp.container.exec("mkdir -p /tmp/tmp.aaa") + vpp.container.createFile("/tmp/tmp.aaa/index.html", "<http><body><p>Hello</p></body></http>") + serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() + s.log(vpp.vppctl("http static server www-root /tmp uri tcp://" + serverAddress + "/80 debug")) + + transport := http.DefaultTransport + transport.(*http.Transport).Proxy = nil + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: transport, + } + req, err := http.NewRequest("GET", "http://"+serverAddress+":80/tmp.aaa", nil) + s.assertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.assertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.assertEqual(301, resp.StatusCode) + s.assertNotEqual("", resp.Header.Get("Location")) +} + +func HttpStaticNotFoundTest(s *NoTopoSuite) { + vpp := s.getContainerByName("vpp").vppInstance + serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() + s.log(vpp.vppctl("http static server www-root /tmp uri tcp://" + serverAddress + "/80 debug")) + + transport := http.DefaultTransport + transport.(*http.Transport).Proxy = nil + client := &http.Client{Transport: transport} + req, err := http.NewRequest("GET", "http://"+serverAddress+":80/notfound.html", nil) + s.assertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.assertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.assertEqual(404, resp.StatusCode) +} + +func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { + vpp := s.getContainerByName("vpp").vppInstance + serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() + vpp.vppctl("http cli server") + + transport := http.DefaultTransport + transport.(*http.Transport).Proxy = nil + client := &http.Client{Transport: transport} + req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test", nil) + s.assertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.assertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.assertEqual(405, resp.StatusCode) + // TODO: need to be fixed in http code + //s.assertNotEqual("", resp.Header.Get("Allow")) +} + +func HttpCliBadRequestTest(s *NoTopoSuite) { + vpp := s.getContainerByName("vpp").vppInstance + serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() + vpp.vppctl("http cli server") + + transport := http.DefaultTransport + transport.(*http.Transport).Proxy = nil + client := &http.Client{Transport: transport} + req, err := http.NewRequest("GET", "http://"+serverAddress+":80", nil) + s.assertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.assertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.assertEqual(400, resp.StatusCode) +} + +func HeaderServerTest(s *NoTopoSuite) { + vpp := s.getContainerByName("vpp").vppInstance + serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() + vpp.vppctl("http cli server") + + transport := http.DefaultTransport + transport.(*http.Transport).Proxy = nil + client := &http.Client{Transport: transport} + req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil) + s.assertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.assertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.assertEqual("http_cli_server", resp.Header.Get("Server")) } -func (s *NoTopoSuite) TestNginxAsServer() { +func NginxAsServerTest(s *NoTopoSuite) { query := "return_ok" finished := make(chan error, 1) @@ -86,7 +211,10 @@ func (s *NoTopoSuite) TestNginxAsServer() { serverAddress := s.getInterfaceByName(tapInterfaceName).peer.ip4AddressString() defer func() { os.Remove(query) }() - go s.startWget(finished, serverAddress, "80", query, "") + go func() { + defer GinkgoRecover() + s.startWget(finished, serverAddress, "80", query, "") + }() s.assertNil(<-finished) } @@ -124,9 +252,11 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error { args += " -r" args += " http://" + serverAddress + ":80/64B.json" abCont.extraRunningArgs = args + time.Sleep(time.Second * 10) o, err := abCont.combinedOutput() rps := parseString(o, "Requests per second:") - s.log(rps, err) + s.log(rps) + s.log(err) s.assertNil(err, "err: '%s', output: '%s'", err, o) } else { wrkCont := s.getContainerByName("wrk") @@ -135,20 +265,23 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string) error { wrkCont.extraRunningArgs = args o, err := wrkCont.combinedOutput() rps := parseString(o, "requests") - s.log(rps, err) + s.log(rps) + s.log(err) s.assertNil(err, "err: '%s', output: '%s'", err, o) } return nil } -func (s *NoTopoSuite) TestNginxPerfCps() { +// unstable with multiple workers +func NginxPerfCpsTest(s *NoTopoSuite) { + s.SkipIfMultiWorker() s.assertNil(runNginxPerf(s, "cps", "ab")) } -func (s *NoTopoSuite) TestNginxPerfRps() { +func NginxPerfRpsTest(s *NoTopoSuite) { s.assertNil(runNginxPerf(s, "rps", "ab")) } -func (s *NoTopoSuite) TestNginxPerfWrk() { +func NginxPerfWrkTest(s *NoTopoSuite) { s.assertNil(runNginxPerf(s, "", "wrk")) } diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 8d9168d3d5d..24d2de39485 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -3,9 +3,15 @@ package main import ( "fmt" "os" + + . "github.com/onsi/ginkgo/v2" ) -func (s *VethsSuite) TestLDPreloadIperfVpp() { +func init() { + registerVethTests(LDPreloadIperfVppTest) +} + +func LDPreloadIperfVppTest(s *VethsSuite) { var clnVclConf, srvVclConf Stanza serverContainer := s.getContainerByName("server-vpp") @@ -14,10 +20,7 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() { clientContainer := s.getContainerByName("client-vpp") clientVclFileName := clientContainer.getHostWorkDir() + "/vcl_cln.conf" - ldpreload := os.Getenv("HST_LDPRELOAD") - s.assertNotEqual("", ldpreload) - - ldpreload = "LD_PRELOAD=" + ldpreload + ldpreload := "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so" stopServerCh := make(chan struct{}, 1) srvCh := make(chan error, 1) @@ -36,7 +39,7 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() { append("use-mq-eventfd"). append(clientAppSocketApi).close(). saveToFile(clientVclFileName) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default", serverContainer.getHostWorkDir()) @@ -49,26 +52,32 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() { append("use-mq-eventfd"). append(serverAppSocketApi).close(). saveToFile(serverVclFileName) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.log("attaching server to vpp") srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+serverVclFileName) - go s.startServerApp(srvCh, stopServerCh, srvEnv) + go func() { + defer GinkgoRecover() + s.startServerApp(srvCh, stopServerCh, srvEnv) + }() err = <-srvCh - s.assertNil(err, err) + 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 s.startClientApp(serverVethAddress, clnEnv, clnCh, clnRes) + go func() { + defer GinkgoRecover() + s.startClientApp(serverVethAddress, clnEnv, clnCh, clnRes) + }() s.log(<-clnRes) // wait for client's result err = <-clnCh - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) // stop server stopServerCh <- struct{}{} diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go index 06247e45240..e323f7fb721 100644 --- a/extras/hs-test/linux_iperf_test.go +++ b/extras/hs-test/linux_iperf_test.go @@ -1,6 +1,16 @@ package main -func (s *TapSuite) TestLinuxIperf() { +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" +) + +func init() { + registerTapTests(LinuxIperfTest) +} + +func LinuxIperfTest(s *TapSuite) { clnCh := make(chan error) stopServerCh := make(chan struct{}) srvCh := make(chan error, 1) @@ -9,13 +19,19 @@ func (s *TapSuite) TestLinuxIperf() { stopServerCh <- struct{}{} }() - go s.startServerApp(srvCh, stopServerCh, nil) + go func() { + defer GinkgoRecover() + s.startServerApp(srvCh, stopServerCh, nil) + }() err := <-srvCh - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.log("server running") ipAddress := s.getInterfaceByName(tapInterfaceName).ip4AddressString() - go s.startClientApp(ipAddress, nil, clnCh, clnRes) + go func() { + defer GinkgoRecover() + s.startClientApp(ipAddress, nil, clnCh, clnRes) + }() s.log("client running") s.log(<-clnRes) err = <-clnCh diff --git a/extras/hs-test/mirroring_test.go b/extras/hs-test/mirroring_test.go index 91f43f45682..1fd15dde2f4 100644 --- a/extras/hs-test/mirroring_test.go +++ b/extras/hs-test/mirroring_test.go @@ -4,7 +4,13 @@ import ( "github.com/edwarnicke/exechelper" ) -func (s *NginxSuite) TestMirroring() { +func init() { + registerNginxTests(MirroringTest) +} + +// broken when CPUS > 1 +func MirroringTest(s *NginxSuite) { + s.SkipIfMultiWorker() proxyAddress := s.getInterfaceByName(mirroringClientInterfaceName).peer.ip4AddressString() path := "/64B.json" diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index c2f9b6f2825..ac5f94c8535 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -5,8 +5,13 @@ import ( "os" "github.com/edwarnicke/exechelper" + . "github.com/onsi/ginkgo/v2" ) +func init() { + registerNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest) +} + func testProxyHttpTcp(s *NsSuite, proto string) error { var outputFile string = "test" + s.pid + ".data" var srcFilePid string = "httpTestFile" + s.pid @@ -19,12 +24,15 @@ func testProxyHttpTcp(s *NsSuite, proto string) error { // create test file err := exechelper.Run(fmt.Sprintf("ip netns exec %s truncate -s %s %s", serverNetns, fileSize, srcFilePid)) - s.assertNil(err, "failed to run truncate command: " + fmt.Sprint(err)) + s.assertNil(err, "failed to run truncate command: "+fmt.Sprint(err)) defer func() { os.Remove(srcFilePid) }() s.log("test file created...") - go s.startHttpServer(serverRunning, stopServer, ":666", serverNetns) + go func() { + defer GinkgoRecover() + s.startHttpServer(serverRunning, stopServer, ":666", serverNetns) + }() // TODO better error handling and recovery <-serverRunning @@ -64,21 +72,21 @@ func configureVppProxy(s *NsSuite, proto string) { clientVeth.ip4AddressString(), serverVeth.peer.ip4AddressString(), ) - s.log("proxy configured...", output) + s.log("proxy configured: " + output) } -func (s *NsSuite) TestVppProxyHttpTcp() { +func VppProxyHttpTcpTest(s *NsSuite) { proto := "tcp" configureVppProxy(s, proto) err := testProxyHttpTcp(s, proto) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) } -func (s *NsSuite) TestVppProxyHttpTls() { +func VppProxyHttpTlsTest(s *NsSuite) { proto := "tls" configureVppProxy(s, proto) err := testProxyHttpTcp(s, proto) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) } func configureEnvoyProxy(s *NsSuite) { @@ -100,8 +108,8 @@ func configureEnvoyProxy(s *NsSuite) { s.assertNil(envoyContainer.start()) } -func (s *NsSuite) TestEnvoyProxyHttpTcp() { +func EnvoyProxyHttpTcpTest(s *NsSuite) { configureEnvoyProxy(s) err := testProxyHttpTcp(s, "tcp") - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) } diff --git a/extras/hs-test/raw_session_test.go b/extras/hs-test/raw_session_test.go index 670ed582522..5c66df0b1ce 100644 --- a/extras/hs-test/raw_session_test.go +++ b/extras/hs-test/raw_session_test.go @@ -1,15 +1,19 @@ package main -func (s *VethsSuite) TestVppEchoQuic() { +func init() { + registerVethTests(VppEchoQuicTest, VppEchoTcpTest) +} + +func VppEchoQuicTest(s *VethsSuite) { s.testVppEcho("quic") } -// udp echo currently broken in vpp, skipping -func (s *VethsSuite) SkipTestVppEchoUdp() { +// TODO: udp echo currently broken in vpp +func VppEchoUdpTest(s *VethsSuite) { s.testVppEcho("udp") } -func (s *VethsSuite) TestVppEchoTcp() { +func VppEchoTcpTest(s *VethsSuite) { s.testVppEcho("tcp") } diff --git a/extras/hs-test/script/compress.sh b/extras/hs-test/script/compress.sh new file mode 100644 index 00000000000..1f0205c1efb --- /dev/null +++ b/extras/hs-test/script/compress.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# if failed-summary.log is not empty, exit status = 1 +if [ -s "${HS_ROOT}/summary/failed-summary.log" ] +then + if [ -n "${WORKSPACE}" ] + then + echo -n "Copying docker logs..." + dirs=$(jq -r '.[0] | .SpecReports[] | select(.State == "failed") | .LeafNodeText' ${HS_ROOT}/summary/report.json) + for dirName in $dirs; do + logDir=/tmp/hs-test/$dirName + if [ -d "$logDir" ]; then + mkdir -p ${WORKSPACE}/archives/summary + cp -r $logDir ${WORKSPACE}/archives/summary/ + fi + done + echo "Done." + + echo -n "Copying failed test logs into build log archive directory (${WORKSPACE}/archives)... " + mkdir -p ${WORKSPACE}/archives/summary + cp -a ${HS_ROOT}/summary/* ${WORKSPACE}/archives/summary + echo "Done." + + echo -n "Compressing files in ${WORKSPACE}/archives from test runs... " + cd ${WORKSPACE}/archives + find . -type f \( -name "*.json" -o -name "*.log" \) -exec gzip {} \; + echo "Done." + + else + echo "Not compressing files in temporary directories from test runs." + fi + exit 1 +fi diff --git a/extras/hs-test/suite_nginx_test.go b/extras/hs-test/suite_nginx_test.go index 8f40590d1f2..c559496e71b 100644 --- a/extras/hs-test/suite_nginx_test.go +++ b/extras/hs-test/suite_nginx_test.go @@ -1,18 +1,37 @@ package main +import ( + "reflect" + "runtime" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" +) + // These correspond to names used in yaml config const ( - vppProxyContainerName = "vpp-proxy" - nginxProxyContainerName = "nginx-proxy" - nginxServerContainerName = "nginx-server" + vppProxyContainerName = "vpp-proxy" + nginxProxyContainerName = "nginx-proxy" + nginxServerContainerName = "nginx-server" mirroringClientInterfaceName = "hstcln" mirroringServerInterfaceName = "hstsrv" ) +var nginxTests = []func(s *NginxSuite){} +var nginxSoloTests = []func(s *NginxSuite){} + type NginxSuite struct { HstSuite } +func registerNginxTests(tests ...func(s *NginxSuite)) { + nginxTests = append(nginxTests, tests...) +} +func registerNginxSoloTests(tests ...func(s *NginxSuite)) { + nginxSoloTests = append(nginxSoloTests, tests...) +} + func (s *NginxSuite) SetupSuite() { s.HstSuite.SetupSuite() s.loadNetworkTopology("2taps") @@ -60,3 +79,56 @@ func (s *NginxSuite) SetupTest() { proxyVpp.waitForApp("nginx-", 5) } + +var _ = Describe("NginxSuite", Ordered, ContinueOnFailure, func() { + var s NginxSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + for _, test := range nginxTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) + +var _ = Describe("NginxSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s NginxSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range nginxSoloTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) diff --git a/extras/hs-test/suite_no_topo_test.go b/extras/hs-test/suite_no_topo_test.go index bbf0cfda685..625dca9f3cf 100644 --- a/extras/hs-test/suite_no_topo_test.go +++ b/extras/hs-test/suite_no_topo_test.go @@ -1,15 +1,34 @@ package main +import ( + "reflect" + "runtime" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" +) + const ( - singleTopoContainerVpp = "vpp" - singleTopoContainerNginx = "nginx" - tapInterfaceName = "htaphost" + singleTopoContainerVpp = "vpp" + singleTopoContainerNginx = "nginx" + tapInterfaceName = "htaphost" ) +var noTopoTests = []func(s *NoTopoSuite){} +var noTopoSoloTests = []func(s *NoTopoSuite){} + type NoTopoSuite struct { HstSuite } +func registerNoTopoTests(tests ...func(s *NoTopoSuite)) { + noTopoTests = append(noTopoTests, tests...) +} +func registerNoTopoSoloTests(tests ...func(s *NoTopoSuite)) { + noTopoSoloTests = append(noTopoSoloTests, tests...) +} + func (s *NoTopoSuite) SetupSuite() { s.HstSuite.SetupSuite() s.loadNetworkTopology("tap") @@ -35,3 +54,57 @@ func (s *NoTopoSuite) SetupTest() { s.assertNil(vpp.createTap(tapInterface), "failed to create tap interface") } + +var _ = Describe("NoTopoSuite", Ordered, ContinueOnFailure, func() { + var s NoTopoSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range noTopoTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) + +var _ = Describe("NoTopoSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s NoTopoSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range noTopoSoloTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) diff --git a/extras/hs-test/suite_ns_test.go b/extras/hs-test/suite_ns_test.go index 46d5bef92ad..85b90911c2f 100644 --- a/extras/hs-test/suite_ns_test.go +++ b/extras/hs-test/suite_ns_test.go @@ -1,15 +1,35 @@ package main +import ( + "fmt" + "reflect" + "runtime" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" +) + // These correspond to names used in yaml config const ( clientInterface = "hclnvpp" serverInterface = "hsrvvpp" ) +var nsTests = []func(s *NsSuite){} +var nsSoloTests = []func(s *NsSuite){} + type NsSuite struct { HstSuite } +func registerNsTests(tests ...func(s *NsSuite)) { + nsTests = append(nsTests, tests...) +} +func registerNsSoloTests(tests ...func(s *NsSuite)) { + nsSoloTests = append(nsSoloTests, tests...) +} + func (s *NsSuite) SetupSuite() { s.HstSuite.SetupSuite() s.configureNetworkTopology("ns") @@ -34,12 +54,66 @@ func (s *NsSuite) SetupTest() { s.assertNil(vpp.start()) idx, err := vpp.createAfPacket(s.getInterfaceByName(serverInterface)) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.assertNotEqual(0, idx) idx, err = vpp.createAfPacket(s.getInterfaceByName(clientInterface)) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.assertNotEqual(0, idx) container.exec("chmod 777 -R %s", container.getContainerWorkDir()) } + +var _ = Describe("NsSuite", Ordered, ContinueOnFailure, func() { + var s NsSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range nsTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) + +var _ = Describe("NsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s NsSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range nsSoloTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) diff --git a/extras/hs-test/suite_tap_test.go b/extras/hs-test/suite_tap_test.go index 8b0950a797e..ebf0f9b3cbc 100644 --- a/extras/hs-test/suite_tap_test.go +++ b/extras/hs-test/suite_tap_test.go @@ -1,15 +1,84 @@ package main import ( + "reflect" + "runtime" + "strings" "time" + + . "github.com/onsi/ginkgo/v2" ) type TapSuite struct { HstSuite } +var tapTests = []func(s *TapSuite){} +var tapSoloTests = []func(s *TapSuite){} + +func registerTapTests(tests ...func(s *TapSuite)) { + tapTests = append(tapTests, tests...) +} +func registerTapSoloTests(tests ...func(s *TapSuite)) { + tapSoloTests = append(tapSoloTests, tests...) +} + func (s *TapSuite) SetupSuite() { time.Sleep(1 * time.Second) s.HstSuite.SetupSuite() s.configureNetworkTopology("tap") } + +var _ = Describe("TapSuite", Ordered, ContinueOnFailure, func() { + var s TapSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range tapTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) + +var _ = Describe("TapSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s TapSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + for _, test := range tapSoloTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) diff --git a/extras/hs-test/suite_veth_test.go b/extras/hs-test/suite_veth_test.go index 061eee07d1f..d47bf8c52a9 100644 --- a/extras/hs-test/suite_veth_test.go +++ b/extras/hs-test/suite_veth_test.go @@ -1,7 +1,13 @@ package main import ( + "fmt" + "reflect" + "runtime" + "strings" "time" + + . "github.com/onsi/ginkgo/v2" ) // These correspond to names used in yaml config @@ -10,10 +16,20 @@ const ( clientInterfaceName = "cln" ) +var vethTests = []func(s *VethsSuite){} +var vethSoloTests = []func(s *VethsSuite){} + type VethsSuite struct { HstSuite } +func registerVethTests(tests ...func(s *VethsSuite)) { + vethTests = append(vethTests, tests...) +} +func registerSoloVethTests(tests ...func(s *VethsSuite)) { + vethSoloTests = append(vethSoloTests, tests...) +} + func (s *VethsSuite) SetupSuite() { time.Sleep(1 * time.Second) s.HstSuite.SetupSuite() @@ -36,7 +52,7 @@ func (s *VethsSuite) SetupTest() { cpus := s.AllocateCpus() serverVpp, err := serverContainer.newVppInstance(cpus, sessionConfig) - s.assertNotNil(serverVpp, err) + s.assertNotNil(serverVpp, fmt.Sprint(err)) s.setupServerVpp() @@ -45,7 +61,7 @@ func (s *VethsSuite) SetupTest() { cpus = s.AllocateCpus() clientVpp, err := clientContainer.newVppInstance(cpus, sessionConfig) - s.assertNotNil(clientVpp, err) + s.assertNotNil(clientVpp, fmt.Sprint(err)) s.setupClientVpp() } @@ -56,7 +72,7 @@ func (s *VethsSuite) setupServerVpp() { serverVeth := s.getInterfaceByName(serverInterfaceName) idx, err := serverVpp.createAfPacket(serverVeth) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.assertNotEqual(0, idx) } @@ -66,6 +82,63 @@ func (s *VethsSuite) setupClientVpp() { clientVeth := s.getInterfaceByName(clientInterfaceName) idx, err := clientVpp.createAfPacket(clientVeth) - s.assertNil(err, err) + s.assertNil(err, fmt.Sprint(err)) s.assertNotEqual(0, idx) } + +var _ = Describe("VethsSuite", Ordered, ContinueOnFailure, func() { + var s VethsSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + + }) + AfterEach(func() { + s.TearDownTest() + }) + + // https://onsi.github.io/ginkgo/#dynamically-generating-specs + for _, test := range vethTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) + +var _ = Describe("VethsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s VethsSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TearDownSuite() + }) + AfterEach(func() { + s.TearDownTest() + }) + + // https://onsi.github.io/ginkgo/#dynamically-generating-specs + for _, test := range vethSoloTests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(time.Minute*5)) + } +}) diff --git a/extras/hs-test/test b/extras/hs-test/test index c3b9eaef145..398e2b39edb 100755..100644 --- a/extras/hs-test/test +++ b/extras/hs-test/test @@ -8,6 +8,8 @@ persist_set=0 unconfigure_set=0 debug_set=0 vppsrc= +ginkgo_args= +parallel= for i in "$@" do @@ -49,8 +51,18 @@ case "${i}" in tc_name="${i#*=}" if [ $tc_name != "all" ]; then single_test=1 - args="$args -run $tc_name -verbose" + ginkgo_args="$ginkgo_args --focus $tc_name -vv" + args="$args -verbose" + else + ginkgo_args="$ginkgo_args -v" fi + ;; + --parallel=*) + ginkgo_args="$ginkgo_args -procs=${i#*=}" + ;; + --repeat=*) + ginkgo_args="$ginkgo_args --repeat=${i#*=}" + ;; esac done @@ -74,4 +86,9 @@ if [ $single_test -eq 0 ] && [ $debug_set -eq 1 ]; then exit 1 fi -sudo -E go test -timeout=20m -buildvcs=false -v $args +mkdir -p summary + +sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --no-color --trace --json-report=summary/report.json $ginkgo_args -- $args + +jq -r '.[0] | .SpecReports[] | select((.State == "failed") or (.State == "timedout") or (.State == "panicked")) | select(.Failure != null) | "TestName: \(.LeafNodeText)\nSuite:\n\(.Failure.Location.FileName)\nMessage:\n\(.Failure.Message)\n Full Stack Trace:\n\(.Failure.Location.FullStackTrace)\n"' summary/report.json > summary/failed-summary.log \ + && echo "Summary generated -> summary/failed-summary.log"
\ No newline at end of file diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go index cb6aaa4adc0..fdcd60ad503 100644 --- a/extras/hs-test/vcl_test.go +++ b/extras/hs-test/vcl_test.go @@ -5,6 +5,11 @@ import ( "time" ) +func init() { + registerVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest, + XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest) +} + func getVclConfig(c *Container, ns_id_optional ...string) string { var s Stanza ns_id := "default" @@ -23,11 +28,11 @@ func getVclConfig(c *Container, ns_id_optional ...string) string { return s.close().toString() } -func (s *VethsSuite) TestXEchoVclClientUdp() { +func XEchoVclClientUdpTest(s *VethsSuite) { s.testXEchoVclClient("udp") } -func (s *VethsSuite) TestXEchoVclClientTcp() { +func XEchoVclClientTcpTest(s *VethsSuite) { s.testXEchoVclClient("tcp") } @@ -49,11 +54,11 @@ func (s *VethsSuite) testXEchoVclClient(proto string) { s.assertContains(o, "CLIENT RESULTS") } -func (s *VethsSuite) TestXEchoVclServerUdp() { +func XEchoVclServerUdpTest(s *VethsSuite) { s.testXEchoVclServer("udp") } -func (s *VethsSuite) TestXEchoVclServerTcp() { +func XEchoVclServerTcpTest(s *VethsSuite) { s.testXEchoVclServer("tcp") } @@ -97,16 +102,15 @@ func (s *VethsSuite) testVclEcho(proto string) { s.log(o) } -func (s *VethsSuite) TestVclEchoTcp() { +func VclEchoTcpTest(s *VethsSuite) { s.testVclEcho("tcp") } -func (s *VethsSuite) TestVclEchoUdp() { +func VclEchoUdpTest(s *VethsSuite) { s.testVclEcho("udp") } -// this test takes too long, for now it's being skipped -func (s *VethsSuite) SkipTestVclRetryAttach() { +func VclRetryAttachTest(s *VethsSuite) { s.testRetryAttach("tcp") } diff --git a/extras/hs-test/vppinstance.go b/extras/hs-test/vppinstance.go index 1a058c8e0e3..3276c2dd9ae 100644 --- a/extras/hs-test/vppinstance.go +++ b/extras/hs-test/vppinstance.go @@ -1,7 +1,9 @@ package main import ( + "context" "fmt" + "io" "os" "os/exec" "os/signal" @@ -11,6 +13,8 @@ import ( "time" "github.com/edwarnicke/exechelper" + . "github.com/onsi/ginkgo/v2" + "github.com/sirupsen/logrus" "go.fd.io/govpp" "go.fd.io/govpp/api" @@ -19,7 +23,6 @@ import ( "go.fd.io/govpp/binapi/interface_types" "go.fd.io/govpp/binapi/session" "go.fd.io/govpp/binapi/tapv2" - "go.fd.io/govpp/binapi/vpe" "go.fd.io/govpp/core" ) @@ -59,6 +62,8 @@ plugins { plugin http_static_plugin.so { enable } plugin prom_plugin.so { enable } plugin tlsopenssl_plugin.so { enable } + plugin ping_plugin.so { enable } + plugin nsim_plugin.so { enable } } logging { @@ -78,7 +83,7 @@ type VppInstance struct { container *Container additionalConfig []Stanza connection *core.Connection - apiChannel api.Channel + apiStream api.Stream cpus []int } @@ -103,6 +108,10 @@ func (vpp *VppInstance) getEtcDir() string { } func (vpp *VppInstance) start() error { + // Replace default logger in govpp with our own + govppLogger := logrus.New() + govppLogger.SetOutput(io.MultiWriter(vpp.getSuite().logger.Writer(), GinkgoWriter)) + core.SetLogger(govppLogger) // Create folders containerWorkDir := vpp.container.getContainerWorkDir() @@ -131,9 +140,10 @@ func (vpp *VppInstance) start() error { vpp.container.createFile(vppcliFileName, cliContent) vpp.container.exec("chmod 0755 " + vppcliFileName) + vpp.getSuite().log("starting vpp") if *isVppDebug { sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGINT) + signal.Notify(sig, syscall.SIGQUIT) cont := make(chan bool, 1) go func() { <-sig @@ -143,7 +153,7 @@ func (vpp *VppInstance) start() error { vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"") fmt.Println("run following command in different terminal:") fmt.Println("docker exec -it " + vpp.container.name + " gdb -ex \"attach $(docker exec " + vpp.container.name + " pidof vpp)\"") - fmt.Println("Afterwards press CTRL+C to continue") + fmt.Println("Afterwards press CTRL+\\ to continue") <-cont fmt.Println("continuing...") } else { @@ -151,6 +161,7 @@ func (vpp *VppInstance) start() error { vpp.container.execServer("su -c \"vpp -c " + startupFileName + " &> /proc/1/fd/1\"") } + vpp.getSuite().log("connecting to vpp") // Connect to VPP and store the connection sockAddress := vpp.container.getHostWorkDir() + defaultApiSocketFilePath conn, connEv, err := govpp.AsyncConnect( @@ -158,7 +169,7 @@ func (vpp *VppInstance) start() error { core.DefaultMaxReconnectAttempts, core.DefaultReconnectInterval) if err != nil { - fmt.Println("async connect error: ", err) + vpp.getSuite().log("async connect error: " + fmt.Sprint(err)) return err } vpp.connection = conn @@ -166,24 +177,19 @@ func (vpp *VppInstance) start() error { // ... wait for Connected event e := <-connEv if e.State != core.Connected { - fmt.Println("connecting to VPP failed: ", e.Error) + vpp.getSuite().log("connecting to VPP failed: " + fmt.Sprint(e.Error)) } - // ... check compatibility of used messages - ch, err := conn.NewAPIChannel() + ch, err := conn.NewStream( + context.Background(), + core.WithRequestSize(50), + core.WithReplySize(50), + core.WithReplyTimeout(time.Second*10)) if err != nil { - fmt.Println("creating channel failed: ", err) + vpp.getSuite().log("creating stream failed: " + fmt.Sprint(err)) return err } - if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil { - fmt.Println("compatibility error: ", err) - return err - } - if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil { - fmt.Println("compatibility error: ", err) - return err - } - vpp.apiChannel = ch + vpp.apiStream = ch return nil } @@ -207,7 +213,7 @@ func (vpp *VppInstance) GetSessionStat(stat string) int { tokens := strings.Split(strings.TrimSpace(line), " ") val, err := strconv.Atoi(tokens[0]) if err != nil { - vpp.getSuite().FailNow("failed to parse stat value %s", err) + Fail("failed to parse stat value %s" + fmt.Sprint(err)) return 0 } return val @@ -217,6 +223,7 @@ func (vpp *VppInstance) GetSessionStat(stat string) int { } func (vpp *VppInstance) waitForApp(appName string, timeout int) { + vpp.getSuite().log("waiting for app " + appName) for i := 0; i < timeout; i++ { o := vpp.vppctl("show app") if strings.Contains(o, appName) { @@ -230,29 +237,50 @@ func (vpp *VppInstance) waitForApp(appName string, timeout int) { func (vpp *VppInstance) createAfPacket( veth *NetInterface, ) (interface_types.InterfaceIndex, error) { - createReq := &af_packet.AfPacketCreateV2{ + createReq := &af_packet.AfPacketCreateV3{ + Mode: 1, UseRandomHwAddr: true, HostIfName: veth.Name(), + Flags: af_packet.AfPacketFlags(11), } if veth.hwAddress != (MacAddress{}) { createReq.UseRandomHwAddr = false createReq.HwAddr = veth.hwAddress } - createReply := &af_packet.AfPacketCreateV2Reply{} - if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil { + vpp.getSuite().log("create af-packet interface " + veth.Name()) + if err := vpp.apiStream.SendMsg(createReq); err != nil { + vpp.getSuite().hstFail() + return 0, err + } + replymsg, err := vpp.apiStream.RecvMsg() + if err != nil { + return 0, err + } + reply := replymsg.(*af_packet.AfPacketCreateV3Reply) + err = api.RetvalToVPPApiError(reply.Retval) + if err != nil { return 0, err } - veth.index = createReply.SwIfIndex + + veth.index = reply.SwIfIndex // Set to up upReq := &interfaces.SwInterfaceSetFlags{ SwIfIndex: veth.index, Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP, } - upReply := &interfaces.SwInterfaceSetFlagsReply{} - if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil { + vpp.getSuite().log("set af-packet interface " + veth.Name() + " up") + if err := vpp.apiStream.SendMsg(upReq); err != nil { + return 0, err + } + replymsg, err = vpp.apiStream.RecvMsg() + if err != nil { + return 0, err + } + reply2 := replymsg.(*interfaces.SwInterfaceSetFlagsReply) + if err = api.RetvalToVPPApiError(reply2.Retval); err != nil { return 0, err } @@ -271,9 +299,18 @@ func (vpp *VppInstance) createAfPacket( SwIfIndex: veth.index, Prefix: veth.addressWithPrefix(), } - addressReply := &interfaces.SwInterfaceAddDelAddressReply{} - if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil { + vpp.getSuite().log("af-packet interface " + veth.Name() + " add address " + veth.ip4Address) + if err := vpp.apiStream.SendMsg(addressReq); err != nil { + return 0, err + } + replymsg, err = vpp.apiStream.RecvMsg() + if err != nil { + return 0, err + } + reply3 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply) + err = api.RetvalToVPPApiError(reply3.Retval) + if err != nil { return 0, err } @@ -285,23 +322,41 @@ func (vpp *VppInstance) addAppNamespace( ifx interface_types.InterfaceIndex, namespaceId string, ) error { - req := &session.AppNamespaceAddDelV2{ + req := &session.AppNamespaceAddDelV4{ + IsAdd: true, Secret: secret, SwIfIndex: ifx, NamespaceID: namespaceId, + SockName: defaultApiSocketFilePath, } - reply := &session.AppNamespaceAddDelV2Reply{} - if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil { + vpp.getSuite().log("add app namespace " + namespaceId) + if err := vpp.apiStream.SendMsg(req); err != nil { + return err + } + replymsg, err := vpp.apiStream.RecvMsg() + if err != nil { + return err + } + reply := replymsg.(*session.AppNamespaceAddDelV4Reply) + if err = api.RetvalToVPPApiError(reply.Retval); err != nil { return err } sessionReq := &session.SessionEnableDisable{ IsEnable: true, } - sessionReply := &session.SessionEnableDisableReply{} - if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil { + vpp.getSuite().log("enable app namespace " + namespaceId) + if err := vpp.apiStream.SendMsg(sessionReq); err != nil { + return err + } + replymsg, err = vpp.apiStream.RecvMsg() + if err != nil { + return err + } + reply2 := replymsg.(*session.SessionEnableDisableReply) + if err = api.RetvalToVPPApiError(reply2.Retval); err != nil { return err } @@ -316,40 +371,64 @@ func (vpp *VppInstance) createTap( if len(tapId) > 0 { id = tapId[0] } - createTapReq := &tapv2.TapCreateV2{ + createTapReq := &tapv2.TapCreateV3{ ID: id, HostIfNameSet: true, HostIfName: tap.Name(), HostIP4PrefixSet: true, HostIP4Prefix: tap.ip4AddressWithPrefix(), } - createTapReply := &tapv2.TapCreateV2Reply{} + vpp.getSuite().log("create tap interface " + tap.Name()) // Create tap interface - if err := vpp.apiChannel.SendRequest(createTapReq).ReceiveReply(createTapReply); err != nil { + if err := vpp.apiStream.SendMsg(createTapReq); err != nil { + return err + } + replymsg, err := vpp.apiStream.RecvMsg() + if err != nil { + return err + } + reply := replymsg.(*tapv2.TapCreateV3Reply) + if err = api.RetvalToVPPApiError(reply.Retval); err != nil { return err } // Add address addAddressReq := &interfaces.SwInterfaceAddDelAddress{ IsAdd: true, - SwIfIndex: createTapReply.SwIfIndex, + SwIfIndex: reply.SwIfIndex, Prefix: tap.peer.addressWithPrefix(), } - addAddressReply := &interfaces.SwInterfaceAddDelAddressReply{} - if err := vpp.apiChannel.SendRequest(addAddressReq).ReceiveReply(addAddressReply); err != nil { + vpp.getSuite().log("tap interface " + tap.Name() + " add address " + tap.peer.ip4Address) + if err := vpp.apiStream.SendMsg(addAddressReq); err != nil { + return err + } + replymsg, err = vpp.apiStream.RecvMsg() + if err != nil { + return err + } + reply2 := replymsg.(*interfaces.SwInterfaceAddDelAddressReply) + if err = api.RetvalToVPPApiError(reply2.Retval); err != nil { return err } // Set interface to up upReq := &interfaces.SwInterfaceSetFlags{ - SwIfIndex: createTapReply.SwIfIndex, + SwIfIndex: reply.SwIfIndex, Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP, } - upReply := &interfaces.SwInterfaceSetFlagsReply{} - if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil { + vpp.getSuite().log("set tap interface " + tap.Name() + " up") + if err := vpp.apiStream.SendMsg(upReq); err != nil { + return err + } + replymsg, err = vpp.apiStream.RecvMsg() + if err != nil { + return err + } + reply3 := replymsg.(*interfaces.SwInterfaceSetFlagsReply) + if err = api.RetvalToVPPApiError(reply3.Retval); err != nil { return err } @@ -360,14 +439,13 @@ func (vpp *VppInstance) saveLogs() { logTarget := vpp.container.getLogDirPath() + "vppinstance-" + vpp.container.name + ".log" logSource := vpp.container.getHostWorkDir() + defaultLogFilePath cmd := exec.Command("cp", logSource, logTarget) - vpp.getSuite().T().Helper() vpp.getSuite().log(cmd.String()) cmd.Run() } func (vpp *VppInstance) disconnect() { vpp.connection.Disconnect() - vpp.apiChannel.Close() + vpp.apiStream.Close() } func (vpp *VppInstance) generateCpuConfig() string { @@ -378,6 +456,7 @@ func (vpp *VppInstance) generateCpuConfig() string { } c.newStanza("cpu"). append(fmt.Sprintf("main-core %d", vpp.cpus[0])) + vpp.getSuite().log(fmt.Sprintf("main-core %d", vpp.cpus[0])) workers := vpp.cpus[1:] if len(workers) > 0 { @@ -388,6 +467,7 @@ func (vpp *VppInstance) generateCpuConfig() string { s = s + fmt.Sprintf("%d", workers[i]) } c.append(fmt.Sprintf("corelist-workers %s", s)) + vpp.getSuite().log("corelist-workers " + s) } return c.close().toString() } |