* Install hs-test dependencies with ``make install-deps``
* `Install Go <https://go.dev/doc/install>`_, it has to be in path of both the running user (follow instructions on Go installation page) and root (run ``sudo visudo`` and edit ``secure_path`` line, run ``sudo go version`` to verify)
* Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology
-* Tests use *hs-test*'s own docker image, they are rebuild automatically when needed, you can run ``make build[-debug]`` to do so or use ``FORCE_BUILD=true`` make parameter
+* Tests use *hs-test*'s own docker image, they are rebuilt automatically when needed, you can run ``make build[-debug]`` to do so or use ``FORCE_BUILD=true`` make parameter
**Action flow when running a test case**:
#. Declare method whose name ends with ``Test`` and specifies its parameter as a pointer to the suite's struct (defined in ``infra/suite_*.go``)
#. Implement test behaviour inside the test method. This typically includes the following:
- #. Import ``. "fd.io/hs-test/infra"``
- #. Retrieve a running container in which to run some action. Method ``GetContainerByName``
- from ``HstSuite`` struct serves this purpose
+ #. Import ``. "fd.io/hs-test/infra"`` and ``. "fd.io/hs-test/infra/infra_common"``
+ #. Retrieve a running container in which to run some action. Each suite has a struct called ``Containers``
#. Interact with VPP through the ``VppInstance`` struct embedded in container. It provides ``Vppctl`` method to access debug CLI
#. Run arbitrary commands inside the containers with ``Exec`` method
- #. Run other external tool with one of the preexisting functions in the ``infra/utils.go`` file.
+ #. Run other external tool with one of the preexisting functions in the ``infra/utils.go`` or ``infra/infra_common/utils_common.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.
Filtering test cases
--------------------
-The framework allows us to filter test cases in a few different ways, using ``make test TEST=``:
+The framework allows us to filter test cases in a few different ways, using ``make test TEST=xyz SKIP=xyz``:
* Suite name
* File name
* All of the above as long as they are ordered properly, e.g. ``make test TEST=VethsSuite.http_test.go.HeaderServerTest``
* Multiple tests/suites: ``make test TEST=HttpClient,LdpSuite``
+All of the above also applies to ``SKIP``
+
**Names are case sensitive!**
Names don't have to be complete, as long as they are last:
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range myTests {
**Packet Capture**
It is possible to use VPP pcap trace to capture received and sent packets.
-You just need to add ``EnablePcapTrace`` to ``SetupTest`` method in test suite and ``CollectPcapTrace`` to ``TearDownTest``.
+You just need to add ``EnablePcapTrace`` to ``SetupTest`` method in test suite and ``CollectPcapTrace`` to ``TeardownTest``.
This way pcap trace is enabled on all interfaces and to capture maximum 10000 packets.
Your pcap file will be located in the test execution directory.
``clib_warning`` is a handy way to add debugging output, but in some cases it's not appropriate for per-packet use in data plane code.
In this case VPP event logger is better option, for example you can enable it for TCP or session layer in build time.
-To collect traces when test ends you just need to add ``CollectEventLogs`` method to ``TearDownTest`` in the test suite.
+To collect traces when test ends you just need to add ``CollectEventLogs`` method to ``TeardownTest`` in the test suite.
Your event logger file will be located in the test execution directory.
To view events you can use :ref:`G2 graphical event viewer <eventviewer>` or ``convert_evt`` tool, located in ``src/scripts/host-stack/``,
which convert event logs to human readable text.
"testing"
"time"
- . "fd.io/hs-test/infra"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
--- /dev/null
+package hst_common
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var IsCoverage = flag.Bool("coverage", false, "use coverage run config")
+var IsPersistent = flag.Bool("persist", false, "persists topology config")
+var IsVerbose = flag.Bool("verbose", false, "verbose test output")
+var SudoUser = flag.String("sudo_user", "root", "what user ran hs-test with sudo")
+var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
+var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
+var DryRun = flag.Bool("dryrun", false, "set up containers but don't run tests")
+var Timeout = flag.Int("timeout", 5, "test timeout override (in minutes)")
+var NumaAwareCpuAlloc bool
+var TestTimeout time.Duration
+var RunningInCi bool
+
+const (
+ LogDir string = "/tmp/hs-test/"
+ VolumeDir string = "/volumes"
+)
+
+type HstCommon struct {
+ Ppid string
+ ProcessIndex string
+ Logger *log.Logger
+ LogFile *os.File
+}
+
+func (s *HstCommon) Skip(args string) {
+ Skip(args)
+}
+
+func (s *HstCommon) SetupTest() {
+ TestCounterFunc()
+ s.Log("[* TEST SETUP]")
+}
+
+func (s *HstCommon) SetupSuite() {
+ s.CreateLogger()
+ s.Log("[* SUITE SETUP]")
+ s.Ppid = fmt.Sprint(os.Getppid())
+ // remove last number so we have space to prepend a process index (interfaces have a char limit)
+ s.Ppid = s.Ppid[:len(s.Ppid)-1]
+ s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
+}
+
+func (s *HstCommon) TeardownTest() {
+ if *IsPersistent || *DryRun {
+ return
+ }
+ s.Log("[* TEST TEARDOWN]")
+}
+
+func (s *HstCommon) TeardownSuite() {
+ if *IsPersistent || *DryRun {
+ return
+ }
+ s.Log("[* SUITE TEARDOWN]")
+}
+
+func (s *HstCommon) GetCurrentSuiteName() string {
+ return CurrentSpecReport().ContainerHierarchyTexts[0]
+}
+
+func (s *HstCommon) CreateLogger() {
+ suiteName := s.GetCurrentSuiteName()
+ var err error
+ s.LogFile, err = os.Create("summary/" + suiteName + ".log")
+ if err != nil {
+ Fail("Unable to create log file.")
+ }
+ s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
+}
+
+// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
+// to keep console tidy
+func (s *HstCommon) Log(log any, arg ...any) {
+ var logStr string
+ if len(arg) == 0 {
+ logStr = fmt.Sprint(log)
+ } else {
+ logStr = fmt.Sprintf(fmt.Sprint(log), arg...)
+ }
+ logs := strings.Split(logStr, "\n")
+
+ for _, line := range logs {
+ s.Logger.Println(line)
+ }
+ if *IsVerbose {
+ GinkgoWriter.Println(logStr)
+ }
+}
+
+func (s *HstCommon) AssertNil(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).To(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).ToNot(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertEqualWithinThreshold(actual, expected, threshold interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeNumerically("~", expected, threshold), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
+ ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
+}
+
+func (s *HstCommon) AssertChannelClosed(timeout time.Duration, channel chan error) {
+ EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed())
+}
+
+// Pass the parsed result struct and the minimum amount of data transferred in MB.
+// Won't do anything when testing a coverage build.
+func (s *HstCommon) AssertIperfMinTransfer(result IPerfResult, minTransferred int) {
+ if *IsCoverage {
+ s.Log("Coverage build; not asserting")
+ return
+ }
+ if result.Start.Details.Protocol == "TCP" {
+ s.AssertGreaterThan(result.End.TcpReceived.MBytes, minTransferred)
+ } else {
+ s.AssertGreaterThan(result.End.Udp.MBytes, minTransferred)
+ }
+}
--- /dev/null
+package hst_common
+
+import (
+ "encoding/json"
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+)
+
+func GetTestFilename() string {
+ _, filename, _, _ := runtime.Caller(2)
+ return filepath.Base(filename)
+}
+
+var testCounter uint16
+var startTime time.Time = time.Now()
+
+func TestCounterFunc() {
+ if ParallelTotal.Value.String() != "1" {
+ return
+ }
+ testCounter++
+ fmt.Printf("Test counter: %d\n"+
+ "Time elapsed: %.2fs\n",
+ testCounter, time.Since(startTime).Seconds())
+}
+
+type IPerfResult struct {
+ Start struct {
+ Timestamp struct {
+ Time string `json:"time"`
+ } `json:"timestamp"`
+ Connected []struct {
+ Socket int `json:"socket"`
+ LocalHost string `json:"local_host"`
+ LocalPort int `json:"local_port"`
+ RemoteHost string `json:"remote_host"`
+ RemotePort int `json:"remote_port"`
+ } `json:"connected"`
+ Version string `json:"version"`
+ Details struct {
+ Protocol string `json:"protocol"`
+ } `json:"test_start"`
+ } `json:"start"`
+ End struct {
+ TcpSent *struct {
+ MbitsPerSecond float64 `json:"bits_per_second"`
+ MBytes float64 `json:"bytes"`
+ } `json:"sum_sent,omitempty"`
+ TcpReceived *struct {
+ MbitsPerSecond float64 `json:"bits_per_second"`
+ MBytes float64 `json:"bytes"`
+ } `json:"sum_received,omitempty"`
+ Udp *struct {
+ MbitsPerSecond float64 `json:"bits_per_second"`
+ JitterMs float64 `json:"jitter_ms,omitempty"`
+ LostPackets int `json:"lost_packets,omitempty"`
+ Packets int `json:"packets,omitempty"`
+ LostPercent float64 `json:"lost_percent,omitempty"`
+ MBytes float64 `json:"bytes"`
+ } `json:"sum,omitempty"`
+ } `json:"end"`
+}
+
+func (s *HstCommon) ParseJsonIperfOutput(jsonResult []byte) IPerfResult {
+ var result IPerfResult
+ // remove iperf warning line if present
+ if strings.Contains(string(jsonResult), "warning") {
+ index := strings.Index(string(jsonResult), "\n")
+ jsonResult = jsonResult[index+1:]
+ }
+
+ err := json.Unmarshal(jsonResult, &result)
+ s.AssertNil(err)
+
+ if result.Start.Details.Protocol == "TCP" {
+ result.End.TcpSent.MbitsPerSecond = result.End.TcpSent.MbitsPerSecond / 1000000
+ result.End.TcpSent.MBytes = result.End.TcpSent.MBytes / 1000000
+ result.End.TcpReceived.MbitsPerSecond = result.End.TcpReceived.MbitsPerSecond / 1000000
+ result.End.TcpReceived.MBytes = result.End.TcpReceived.MBytes / 1000000
+ } else {
+ result.End.Udp.MBytes = result.End.Udp.MBytes / 1000000
+ result.End.Udp.MbitsPerSecond = result.End.Udp.MbitsPerSecond / 1000000
+ }
+
+ return result
+}
+
+func (s *HstCommon) LogJsonIperfOutput(result IPerfResult) {
+ s.Log("\n*******************************************\n"+
+ "%s\n"+
+ "[%s] %s:%d connected to %s:%d\n"+
+ "Started: %s\n",
+ result.Start.Version,
+ result.Start.Details.Protocol,
+ result.Start.Connected[0].LocalHost, result.Start.Connected[0].LocalPort,
+ result.Start.Connected[0].RemoteHost, result.Start.Connected[0].RemotePort,
+ result.Start.Timestamp.Time)
+
+ if result.Start.Details.Protocol == "TCP" {
+ s.Log("Transfer (sent): %.2f MBytes\n"+
+ "Bitrate (sent): %.2f Mbits/sec\n"+
+ "Transfer (received): %.2f MBytes\n"+
+ "Bitrate (received): %.2f Mbits/sec",
+ result.End.TcpSent.MBytes,
+ result.End.TcpSent.MbitsPerSecond,
+ result.End.TcpReceived.MBytes,
+ result.End.TcpReceived.MbitsPerSecond)
+ } else {
+ s.Log("Transfer: %.2f MBytes\n"+
+ "Bitrate: %.2f Mbits/sec\n"+
+ "Jitter: %.3f ms\n"+
+ "Packets: %d\n"+
+ "Packets lost: %d\n"+
+ "Percent lost: %.2f%%",
+ result.End.Udp.MBytes,
+ result.End.Udp.MbitsPerSecond,
+ result.End.Udp.JitterMs,
+ result.End.Udp.Packets,
+ result.End.Udp.LostPackets,
+ result.End.Udp.LostPercent)
+ }
+ s.Log("*******************************************\n")
+}
"text/template"
"time"
- "github.com/docker/go-units"
-
+ . "fd.io/hs-test/infra/common"
"github.com/cilium/cilium/pkg/sysctl"
containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/pkg/stdcopy"
+ "github.com/docker/go-units"
"github.com/edwarnicke/exechelper"
- . "github.com/onsi/ginkgo/v2"
-)
-
-const (
- LogDir string = "/tmp/hs-test/"
- volumeDir string = "/volumes"
+ "github.com/onsi/ginkgo/v2"
)
var (
}
if _, ok := yamlInput["volumes"]; ok {
- workingVolumeDir := LogDir + suite.GetCurrentTestName() + volumeDir
+ workingVolumeDir := LogDir + suite.GetCurrentTestName() + VolumeDir
workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
for _, volu := range yamlInput["volumes"].([]interface{}) {
envVars = ""
}
containerExecCommand := fmt.Sprintf("docker exec -d %s %s %s", envVars, c.Name, serverCommand)
- GinkgoHelper()
+ ginkgo.GinkgoHelper()
c.Suite.Log(containerExecCommand)
c.Suite.AssertNil(exechelper.Run(containerExecCommand))
}
envVars = ""
}
containerExecCommand := fmt.Sprintf("docker exec %s %s %s", envVars, c.Name, serverCommand)
- GinkgoHelper()
+ ginkgo.GinkgoHelper()
c.Suite.Log(containerExecCommand)
byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
return string(byteOutput), err
"os/exec"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
"bufio"
"flag"
"fmt"
- "io"
- "log"
"net/http"
"net/http/httputil"
"os"
"os/exec"
- "path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
- "github.com/edwarnicke/exechelper"
-
+ . "fd.io/hs-test/infra/common"
containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
- "github.com/onsi/gomega/gmeasure"
- "gopkg.in/yaml.v3"
-
+ "github.com/edwarnicke/exechelper"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gmeasure"
+ "gopkg.in/yaml.v3"
)
const (
DEFAULT_NETWORK_NUM int = 1
)
-var IsPersistent = flag.Bool("persist", false, "persists topology config")
-var IsVerbose = flag.Bool("verbose", false, "verbose test output")
-var IsCoverage = flag.Bool("coverage", false, "use coverage run config")
var IsUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
-var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build")
var UseCpu0 = flag.Bool("cpu0", false, "use cpu0")
var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests")
-var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
-var DryRun = flag.Bool("dryrun", false, "set up containers but don't run tests")
-var SudoUser = flag.String("sudo_user", "root", "what user ran hs-test with sudo")
-var Timeout = flag.Int("timeout", 5, "test timeout override (in minutes)")
-var NumaAwareCpuAlloc bool
-var TestTimeout time.Duration
-var RunningInCi bool
type HstSuite struct {
+ HstCommon
AllContainers map[string]*Container
StartedContainers []*Container
Volumes []string
CpuAllocator *CpuAllocatorT
CpuContexts []*CpuContext
CpuCount int
- Ppid string
- ProcessIndex string
- Logger *log.Logger
- LogFile *os.File
Docker *client.Client
CoverageRun bool
}
Label string
}
-var testCounter uint16
-var startTime time.Time = time.Now()
-
-func testCounterFunc() {
- if ParallelTotal.Value.String() != "1" {
- return
- }
- testCounter++
- fmt.Printf("Test counter: %d\n"+
- "Time elapsed: %.2fs\n",
- testCounter, time.Since(startTime).Seconds())
-}
-
// ColorableString for ReportEntry to use
func (s StringerStruct) ColorableString() string {
return fmt.Sprintf("{{red}}%s{{/}}", s.Label)
return s.Label
}
-func GetTestFilename() string {
- _, filename, _, _ := runtime.Caller(2)
- return filepath.Base(filename)
-}
-
func (s *HstSuite) getLogDirPath() string {
testId := s.GetTestId()
testName := s.GetCurrentTestName()
s.Log("docker client created")
}
-func (s *HstSuite) SetupKindSuite() {
- s.CreateLogger()
- s.Log("[* SUITE SETUP]")
- RegisterFailHandler(func(message string, callerSkip ...int) {
- s.HstFail()
- Fail(message, callerSkip...)
- })
- s.Ppid = fmt.Sprint(os.Getppid())
- // remove last number so we have space to prepend a process index (interfaces have a char limit)
- s.Ppid = s.Ppid[:len(s.Ppid)-1]
- s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
-}
-
func (s *HstSuite) SetupSuite() {
- s.CreateLogger()
- s.Log("[* SUITE SETUP]")
- s.newDockerClient()
RegisterFailHandler(func(message string, callerSkip ...int) {
s.HstFail()
Fail(message, callerSkip...)
})
+ s.HstCommon.SetupSuite()
+ s.newDockerClient()
+
var err error
- s.Ppid = fmt.Sprint(os.Getppid())
- // remove last number so we have space to prepend a process index (interfaces have a char limit)
- s.Ppid = s.Ppid[:len(s.Ppid)-1]
- s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
s.CpuAllocator, err = CpuAllocator()
if err != nil {
Fail("failed to init cpu allocator: " + fmt.Sprint(err))
s.CpuContexts = append(s.CpuContexts, cpuCtx)
}
-func (s *HstSuite) TearDownSuite() {
+func (s *HstSuite) TeardownSuite() {
+ s.HstCommon.TeardownSuite()
defer s.LogFile.Close()
defer s.Docker.Close()
- if *IsPersistent || *DryRun {
- return
- }
- s.Log("[* SUITE TEARDOWN]")
s.UnconfigureNetworkTopology()
}
-func (s *HstSuite) TearDownTest() {
- s.Log("[* TEST TEARDOWN]")
- if *IsPersistent || *DryRun {
- return
- }
+func (s *HstSuite) TeardownTest() {
+ s.HstCommon.TeardownTest()
coreDump := s.WaitForCoreDump()
s.ResetContainers()
}
func (s *HstSuite) SetupTest() {
- testCounterFunc()
- s.Log("[* TEST SETUP]")
+ s.HstCommon.SetupTest()
s.StartedContainers = s.StartedContainers[:0]
s.SkipIfUnconfiguring()
s.SetupContainers()
}
}
-func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).To(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).ToNot(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertEqualWithinThreshold(actual, expected, threshold interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).Should(BeNumerically("~", expected, threshold), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
- ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
-}
-
-func (s *HstSuite) AssertChannelClosed(timeout time.Duration, channel chan error) {
- EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed())
-}
-
-// Pass the parsed result struct and the minimum amount of data transferred in MB.
-// Won't do anything when testing a coverage build.
-func (s *HstSuite) AssertIperfMinTransfer(result IPerfResult, minTransferred int) {
- if *IsCoverage {
- s.Log("Coverage build; not asserting")
- return
- }
- if result.Start.Details.Protocol == "TCP" {
- s.AssertGreaterThan(result.End.TcpReceived.MBytes, minTransferred)
- } else {
- s.AssertGreaterThan(result.End.Udp.MBytes, minTransferred)
- }
-}
-
-func (s *HstSuite) CreateLogger() {
- suiteName := s.GetCurrentSuiteName()
- var err error
- s.LogFile, err = os.Create("summary/" + suiteName + ".log")
- if err != nil {
- Fail("Unable to create log file.")
- }
- s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
-}
-
-// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
-// to keep console tidy
-func (s *HstSuite) Log(log any, arg ...any) {
- var logStr string
- if len(arg) == 0 {
- logStr = fmt.Sprint(log)
- } else {
- logStr = fmt.Sprintf(fmt.Sprint(log), arg...)
- }
- logs := strings.Split(logStr, "\n")
-
- for _, line := range logs {
- s.Logger.Println(line)
- }
- if *IsVerbose {
- GinkgoWriter.Println(logStr)
- }
-}
-
-func (s *HstSuite) Skip(args string) {
- Skip(args)
-}
-
func (s *HstSuite) SkipIfMultiWorker(args ...any) {
if *NConfiguredCpus > 1 {
s.Skip("test case not supported with multiple vpp workers")
for _, elem := range yamlTopo.Volumes {
volumeMap := elem["volume"].(VolumeConfig)
hostDir := volumeMap["host-dir"].(string)
- workingVolumeDir := LogDir + s.GetCurrentTestName() + volumeDir
+ workingVolumeDir := LogDir + s.GetCurrentTestName() + VolumeDir
volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
hostDir = volDirReplacer.Replace(hostDir)
s.Volumes = append(s.Volumes, hostDir)
+++ /dev/null
-package hst
-
-import (
- "context"
- "fmt"
- "time"
-
- . "github.com/onsi/ginkgo/v2"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-func (s *KindSuite) CreateNamespace(name string) {
- namespace := &corev1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: s.Namespace,
- },
- }
-
- // Create the namespace in the cluster
- _, err := s.ClientSet.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
- s.AssertNil(err)
- s.Log("Namespace '%s' created", s.Namespace)
-}
-
-func (s *KindSuite) DeployServerClient(imageNameServer string, imageNameClient string, serverPod string, clientPod string) {
- var err error
- var counter uint8
- var serverDetails *corev1.Pod
- s.CurrentlyRunning = append(s.CurrentlyRunning, serverPod, clientPod)
-
- server := &corev1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: s.Namespace,
- Name: serverPod,
- Labels: map[string]string{
- "app": serverPod,
- },
- Annotations: map[string]string{
- "cni.projectcalico.org/vppVcl": "enable",
- },
- },
- Spec: corev1.PodSpec{
- Containers: []corev1.Container{
- {
- Name: "server",
- Image: imageNameServer,
- SecurityContext: &corev1.SecurityContext{
- Privileged: boolPtr(true),
- },
- Command: []string{"tail", "-f", "/dev/null"},
- ImagePullPolicy: corev1.PullIfNotPresent,
- Ports: []corev1.ContainerPort{
- {
- ContainerPort: 5201,
- },
- },
- },
- },
- NodeName: "kind-worker",
- },
- }
-
- // Create the Pod
- _, err = s.ClientSet.CoreV1().Pods(s.Namespace).Create(context.TODO(), server, metav1.CreateOptions{})
- s.AssertNil(err)
- s.Log("Pod '%s' created", serverPod)
-
- // Get IP
- s.Log("Obtaining IP from '%s'", server.Name)
- for s.ServerIp == "" {
- serverDetails, err = s.ClientSet.CoreV1().Pods(s.Namespace).Get(context.TODO(), serverPod, metav1.GetOptions{})
- s.ServerIp = serverDetails.Status.PodIP
- time.Sleep(time.Second * 1)
- counter++
- if counter >= 10 {
- Fail("Unable to get IP. Check if all pods are running. " + fmt.Sprint(err))
- }
- }
-
- s.Log("IP: %s", s.ServerIp)
-
- client := &corev1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: s.Namespace,
- Name: clientPod,
- Annotations: map[string]string{
- "cni.projectcalico.org/vppVcl": "enable",
- },
- },
- Spec: corev1.PodSpec{
- Containers: []corev1.Container{
- {
- Name: "client",
- Image: imageNameClient,
- ImagePullPolicy: corev1.PullIfNotPresent,
- Command: []string{"tail", "-f", "/dev/null"},
- Ports: []corev1.ContainerPort{
- {
- ContainerPort: 5201,
- },
- },
- SecurityContext: &corev1.SecurityContext{
- Privileged: boolPtr(true),
- },
- },
- },
- NodeName: "kind-worker2",
- },
- }
-
- _, err = s.ClientSet.CoreV1().Pods(s.Namespace).Create(context.TODO(), client, metav1.CreateOptions{})
- s.AssertNil(err)
- s.Log("Pod '%s' created", clientPod)
-
- // let pods start properly
- time.Sleep(time.Second * 5)
-}
+++ /dev/null
-package hst
-
-import (
- "context"
- "errors"
- "os"
- "os/exec"
- "reflect"
- "runtime"
- "strings"
- "text/template"
- "time"
-
- . "fd.io/hs-test/infra"
- . "github.com/onsi/ginkgo/v2"
-
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/rest"
- "k8s.io/client-go/tools/clientcmd"
-)
-
-type KindSuite struct {
- HstSuite
- ClientSet *kubernetes.Clientset
- Config *rest.Config
- ServerIp string
- Namespace string
- KubeconfigPath string
- ImageNames
- PodNames
- ContainerNames
-}
-
-type ImageNames struct {
- HstVpp string
- Nginx string
- Ab string
-}
-
-type PodNames struct {
- ClientVpp string
- ServerVpp string
- Nginx string
- Ab string
- CurrentlyRunning []string
-}
-
-type ContainerNames struct {
- Server string
- Client string
-}
-
-var kindTests = map[string][]func(s *KindSuite){}
-
-func RegisterKindTests(tests ...func(s *KindSuite)) {
- kindTests[GetTestFilename()] = tests
-}
-
-func deletePod(clientset *kubernetes.Clientset, namespace, podName string) error {
- return clientset.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{GracePeriodSeconds: int64Ptr(0)})
-}
-
-func deleteNamespace(clientset *kubernetes.Clientset, namespace string) error {
- return clientset.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{})
-}
-
-func (s *KindSuite) loadDockerImages() {
- s.Log("This may take a while. If you encounter problems, " +
- "try loading docker images manually: 'kind load docker-image [image]'")
- value := reflect.ValueOf(s.ImageNames)
- reflType := reflect.TypeOf(s.ImageNames)
- var cmd *exec.Cmd
- var out []byte
- var err error
-
- if reflType.Kind() == reflect.Struct {
- for i := range value.NumField() {
- if value.Field(i).Kind() == reflect.String {
- fieldValue := value.Field(i).Interface().(string)
- s.Log("loading docker image %s...", fieldValue)
- cmd = exec.Command("kind", "load", "docker-image", fieldValue)
- out, err = cmd.CombinedOutput()
- s.Log(string(out))
- s.AssertNil(err, string(out))
- }
- }
- } else {
- s.AssertNil(errors.New("not a struct"))
- }
-}
-
-func (s *KindSuite) SetupSuite() {
- s.SetupKindSuite()
- s.ImageNames.Ab = "hs-test/ab:latest"
- s.ImageNames.Nginx = "hs-test/nginx-ldp:latest"
- s.ImageNames.HstVpp = "hs-test/vpp:latest"
- s.PodNames.ServerVpp = "server" + s.Ppid
- s.PodNames.ClientVpp = "client" + s.Ppid
- s.PodNames.Nginx = "nginx-ldp" + s.Ppid
- s.PodNames.Ab = "ab" + s.Ppid
- s.Namespace = "namespace" + s.Ppid
- s.ContainerNames.Client = "client"
- s.ContainerNames.Server = "server"
-
- s.loadDockerImages()
-
- var err error
- if *SudoUser == "root" {
- s.KubeconfigPath = "/.kube/config"
- } else {
- s.KubeconfigPath = "/home/" + *SudoUser + "/.kube/config"
- }
-
- s.Config, err = clientcmd.BuildConfigFromFlags("", s.KubeconfigPath)
- s.AssertNil(err)
-
- s.ClientSet, err = kubernetes.NewForConfig(s.Config)
- s.AssertNil(err)
-
- s.CreateNamespace(s.Namespace)
-}
-
-func (s *KindSuite) TeardownTest() {
- if *IsPersistent {
- return
- }
- s.Log("[TEST TEARDOWN]")
- s.ServerIp = ""
- if len(s.CurrentlyRunning) != 0 {
- for _, pod := range s.CurrentlyRunning {
- s.Log(" %s", pod)
- deletePod(s.ClientSet, s.Namespace, pod)
- }
- }
-}
-
-func (s *KindSuite) TeardownSuite() {
- if *IsPersistent {
- return
- }
- s.Log("[SUITE TEARDOWN]")
- s.Log(" %s", s.Namespace)
- s.AssertNil(deleteNamespace(s.ClientSet, s.Namespace))
-}
-
-func (s *KindSuite) CreateConfigFromTemplate(targetConfigName string, templateName string, values any) {
- template := template.Must(template.ParseFiles(templateName))
-
- f, err := os.CreateTemp(LogDir, "hst-config")
- s.AssertNil(err, err)
- defer os.Remove(f.Name())
-
- err = template.Execute(f, values)
- s.AssertNil(err, err)
-
- err = f.Close()
- s.AssertNil(err, err)
-
- s.CopyToPod(s.PodNames.Nginx, s.Namespace, f.Name(), targetConfigName)
-}
-
-func (s *KindSuite) CreateNginxConfig() {
- values := struct {
- Workers uint8
- }{
- Workers: 1,
- }
- s.CreateConfigFromTemplate(
- "/nginx.conf",
- "./resources/nginx/nginx.conf",
- values,
- )
-}
-
-var _ = Describe("KindSuite", Ordered, ContinueOnFailure, Label("Perf"), func() {
- var s KindSuite
- BeforeAll(func() {
- s.SetupSuite()
- })
- AfterEach(func() {
- s.TeardownTest()
- })
- AfterAll(func() {
- s.TeardownSuite()
- })
-
- for filename, tests := range kindTests {
- for _, test := range tests {
- test := test
- pc := reflect.ValueOf(test).Pointer()
- funcValue := runtime.FuncForPC(pc)
- testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
- It(testName, func(ctx SpecContext) {
- s.Log(testName + ": BEGIN")
- test(&s)
- }, SpecTimeout(time.Minute*15))
- }
- }
-})
--- /dev/null
+package hst_kind
+
+import (
+ "context"
+ "fmt"
+ "os/exec"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func (s *KindSuite) loadDockerImages() {
+ s.Log("This may take a while. If you encounter problems, " +
+ "try loading docker images manually: 'kind load docker-image [image]'")
+
+ var cmd *exec.Cmd
+ var out []byte
+ var err error
+ for _, image := range s.images {
+ s.Log("loading docker image %s...", image)
+ cmd = exec.Command("kind", "load", "docker-image", image)
+ out, err = cmd.CombinedOutput()
+ s.Log(string(out))
+ s.AssertNil(err, string(out))
+ }
+}
+
+func (s *KindSuite) createNamespace(name string) {
+ namespace := &corev1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ },
+ }
+
+ // Create the namespace in the cluster
+ _, err := s.ClientSet.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
+ s.AssertNil(err)
+ s.Log("Namespace '%s' created", name)
+}
+
+func (s *KindSuite) deletePod(namespace string, podName string) error {
+ return s.ClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{GracePeriodSeconds: int64Ptr(0)})
+}
+
+func (s *KindSuite) deleteNamespace(namespace string) error {
+ return s.ClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{})
+}
+
+func (s *KindSuite) DeployPod(pod *Pod) {
+ s.CurrentlyRunning = append(s.CurrentlyRunning, pod.Name)
+ pod.CreatedPod = &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: s.Namespace,
+ Name: pod.Name,
+ Labels: map[string]string{
+ "app": "HST",
+ },
+ Annotations: map[string]string{
+ "cni.projectcalico.org/vppVcl": "enable",
+ },
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: pod.ContainerName,
+ Image: pod.Image,
+ SecurityContext: &corev1.SecurityContext{
+ Privileged: boolPtr(true),
+ },
+ Command: []string{"tail", "-f", "/dev/null"},
+ ImagePullPolicy: corev1.PullIfNotPresent,
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 5201,
+ },
+ },
+ },
+ },
+ NodeName: pod.Worker,
+ },
+ }
+
+ // Create the Pod
+ _, err := s.ClientSet.CoreV1().Pods(s.Namespace).Create(context.TODO(), pod.CreatedPod, metav1.CreateOptions{})
+ s.AssertNil(err)
+ s.Log("Pod '%s' created", pod.Name)
+
+ // Get IP
+ s.Log("Obtaining IP from '%s'", pod.Name)
+ counter := 1
+ for pod.IpAddress == "" {
+ pod.CreatedPod, err = s.ClientSet.CoreV1().Pods(s.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{})
+ pod.IpAddress = pod.CreatedPod.Status.PodIP
+ time.Sleep(time.Second * 1)
+ counter++
+ if counter >= 10 {
+ Fail("Unable to get IP. Check if all pods are running. " + fmt.Sprint(err))
+ }
+ }
+
+ s.Log("IP: %s", pod.IpAddress)
+}
--- /dev/null
+package hst_kind
+
+import corev1 "k8s.io/api/core/v1"
+
+type Pod struct {
+ Name string
+ Image string
+ ContainerName string
+ Worker string
+ IpAddress string
+ CreatedPod *corev1.Pod
+}
+
+// Sets pod names, image names, namespace name
+func (s *KindSuite) initPods() {
+ wrk1 := "kind-worker"
+ wrk2 := "kind-worker2"
+ vppImg := "hs-test/vpp:latest"
+ nginxLdpImg := "hs-test/nginx-ldp:latest"
+ abImg := "hs-test/ab:latest"
+ clientCont := "client"
+ serverCont := "server"
+
+ s.images = append(s.images, vppImg, nginxLdpImg, abImg)
+ s.Namespace = "namespace" + s.Ppid
+
+ s.Pods.ClientGeneric = new(Pod)
+ s.Pods.ClientGeneric.Name = "client" + s.Ppid
+ s.Pods.ClientGeneric.Image = vppImg
+ s.Pods.ClientGeneric.ContainerName = clientCont
+ s.Pods.ClientGeneric.Worker = wrk1
+
+ s.Pods.ServerGeneric = new(Pod)
+ s.Pods.ServerGeneric.Name = "server" + s.Ppid
+ s.Pods.ServerGeneric.Image = vppImg
+ s.Pods.ServerGeneric.ContainerName = serverCont
+ s.Pods.ServerGeneric.Worker = wrk2
+
+ s.Pods.Ab = new(Pod)
+ s.Pods.Ab.Name = "ab" + s.Ppid
+ s.Pods.Ab.Image = abImg
+ s.Pods.Ab.ContainerName = clientCont
+ s.Pods.Ab.Worker = wrk1
+
+ s.Pods.Nginx = new(Pod)
+ s.Pods.Nginx.Name = "nginx-ldp" + s.Ppid
+ s.Pods.Nginx.Image = nginxLdpImg
+ s.Pods.Nginx.ContainerName = serverCont
+ s.Pods.Nginx.Worker = wrk2
+}
--- /dev/null
+package hst_kind
+
+import (
+ "os"
+ "reflect"
+ "runtime"
+ "strings"
+ "text/template"
+
+ . "fd.io/hs-test/infra/common"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/clientcmd"
+)
+
+type KindSuite struct {
+ HstCommon
+ ClientSet *kubernetes.Clientset
+ Config *rest.Config
+ Namespace string
+ KubeconfigPath string
+ CurrentlyRunning []string
+ images []string
+ Pods struct {
+ ServerGeneric *Pod
+ ClientGeneric *Pod
+ Nginx *Pod
+ Ab *Pod
+ }
+}
+
+var kindTests = map[string][]func(s *KindSuite){}
+
+func RegisterKindTests(tests ...func(s *KindSuite)) {
+ kindTests[GetTestFilename()] = tests
+}
+
+func (s *KindSuite) SetupTest() {
+ s.HstCommon.SetupTest()
+}
+
+func (s *KindSuite) SetupSuite() {
+ s.HstCommon.SetupSuite()
+ RegisterFailHandler(func(message string, callerSkip ...int) {
+ Fail(message, callerSkip...)
+ })
+
+ s.initPods()
+ s.loadDockerImages()
+
+ var err error
+ if *SudoUser == "root" {
+ s.KubeconfigPath = "/.kube/config"
+ } else {
+ s.KubeconfigPath = "/home/" + *SudoUser + "/.kube/config"
+ }
+
+ s.Config, err = clientcmd.BuildConfigFromFlags("", s.KubeconfigPath)
+ s.AssertNil(err)
+
+ s.ClientSet, err = kubernetes.NewForConfig(s.Config)
+ s.AssertNil(err)
+
+ s.createNamespace(s.Namespace)
+}
+
+func (s *KindSuite) TeardownTest() {
+ s.HstCommon.TeardownTest()
+ if len(s.CurrentlyRunning) != 0 {
+ for _, pod := range s.CurrentlyRunning {
+ s.Log(" %s", pod)
+ s.deletePod(s.Namespace, pod)
+ }
+ }
+}
+
+func (s *KindSuite) TeardownSuite() {
+ s.Log(" %s", s.Namespace)
+ s.AssertNil(s.deleteNamespace(s.Namespace))
+}
+
+func (s *KindSuite) CreateConfigFromTemplate(targetConfigName string, templateName string, values any) {
+ template := template.Must(template.ParseFiles(templateName))
+
+ f, err := os.CreateTemp(LogDir, "hst-config")
+ s.AssertNil(err, err)
+ defer os.Remove(f.Name())
+
+ err = template.Execute(f, values)
+ s.AssertNil(err, err)
+
+ err = f.Close()
+ s.AssertNil(err, err)
+
+ s.CopyToPod(s.Pods.Nginx.Name, s.Namespace, f.Name(), targetConfigName)
+}
+
+func (s *KindSuite) CreateNginxConfig() {
+ values := struct {
+ Workers uint8
+ }{
+ Workers: 1,
+ }
+ s.CreateConfigFromTemplate(
+ "/nginx.conf",
+ "./resources/nginx/nginx.conf",
+ values,
+ )
+}
+
+var _ = Describe("KindSuite", Ordered, ContinueOnFailure, Label("Perf"), func() {
+ var s KindSuite
+ BeforeAll(func() {
+ s.SetupSuite()
+ })
+ BeforeEach(func() {
+ s.SetupTest()
+ })
+ AfterEach(func() {
+ s.TeardownTest()
+ })
+ AfterAll(func() {
+ s.TeardownSuite()
+ })
+
+ for filename, tests := range kindTests {
+ for _, test := range tests {
+ test := test
+ pc := reflect.ValueOf(test).Pointer()
+ funcValue := runtime.FuncForPC(pc)
+ testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+ It(testName, func(ctx SpecContext) {
+ s.Log(testName + ": BEGIN")
+ test(&s)
+ }, SpecTimeout(TestTimeout))
+ }
+ }
+})
-package hst
+package hst_kind
import (
"bytes"
s.AssertNil(err, string(out))
}
-func (s *KindSuite) Exec(podName string, containerName string, command []string) (string, error) {
+func (s *KindSuite) Exec(pod *Pod, command []string) (string, error) {
var stdout, stderr bytes.Buffer
// Prepare the request
req := s.ClientSet.CoreV1().RESTClient().Post().
Resource("pods").
- Name(podName).
+ Name(pod.Name).
Namespace(s.Namespace).
SubResource("exec").
- Param("container", containerName).
+ Param("container", pod.ContainerName).
Param("stdout", "true").
Param("stderr", "true").
Param("tty", "true")
for _, cmd := range command {
req = req.Param("command", cmd)
}
- s.Log("%s: %s", podName, command)
+ s.Log("%s: %s", pod.Name, command)
executor, err := remotecommand.NewSPDYExecutor(s.Config, "POST", req.URL())
if err != nil {
return output, nil
}
-// Alternative exec function. Use if regular Exec() doesn't work.
-func (s *KindSuite) ExecAlt(podName string, containerName string, namespace string, command []string) (string, error) {
- baseCmd := []string{
- "kubectl",
- "--kubeconfig=" + s.KubeconfigPath,
- "-n", namespace,
- "exec",
- podName,
- "--",
- }
- fullCmd := append(baseCmd, command...)
- cmd := exec.Command(fullCmd[0], fullCmd[1:]...)
- s.Log(cmd)
- out, err := cmd.CombinedOutput()
-
- return string(out), err
-}
-
func boolPtr(b bool) *bool {
return &b
}
"runtime"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *CpuPinningSuite) TearDownTest() {
+func (s *CpuPinningSuite) TeardownTest() {
// reset vars
s.CpuCount = *NConfiguredCpus
s.CpuAllocator.maxContainerCount = s.previousMaxContainerCount
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range cpuPinningSoloTests {
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
s.AssertNil(s.Containers.EnvoyProxy.Start())
}
-func (s *EnvoyProxySuite) TearDownTest() {
+func (s *EnvoyProxySuite) TeardownTest() {
if CurrentSpecReport().Failed() {
s.CollectNginxLogs(s.Containers.NginxServerTransient)
s.CollectEnvoyLogs(s.Containers.EnvoyProxy)
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *EnvoyProxySuite) ProxyPort() uint16 {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range envoyProxyTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range envoyProxySoloTests {
"strings"
"time"
- "github.com/summerwind/h2spec/spec"
-
"fd.io/hs-test/h2spec_extras"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
"github.com/summerwind/h2spec/config"
"github.com/summerwind/h2spec/generic"
"github.com/summerwind/h2spec/hpack"
"github.com/summerwind/h2spec/http2"
+ "github.com/summerwind/h2spec/spec"
)
var h2Tests = map[string][]func(s *H2Suite){}
}
}
-func (s *H2Suite) TearDownTest() {
- s.HstSuite.TearDownTest()
+func (s *H2Suite) TeardownTest() {
+ s.HstSuite.TeardownTest()
}
func (s *H2Suite) VppAddr() string {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range h2Tests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for _, sp := range specs {
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range iperfTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range iperfSoloTests {
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *LdpSuite) TearDownTest() {
+func (s *LdpSuite) TeardownTest() {
if CurrentSpecReport().Failed() {
s.CollectIperfLogs(s.Containers.ServerVpp)
s.CollectRedisServerLogs(s.Containers.ServerVpp)
delete(container.EnvVars, "LD_PRELOAD")
delete(container.EnvVars, "VCL_CONFIG")
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
"runtime"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
s.AssertNil(s.Containers.NginxServerTransient.Start())
}
-func (s *NginxProxySuite) TearDownTest() {
+func (s *NginxProxySuite) TeardownTest() {
if CurrentSpecReport().Failed() {
s.CollectNginxLogs(s.Containers.NginxProxy)
s.CollectNginxLogs(s.Containers.NginxServerTransient)
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *NginxProxySuite) CreateNginxProxyConfig(container *Container, multiThreadWorkers bool) {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range nginxProxyTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range nginxProxySoloTests {
"runtime"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *NoTopoSuite) TearDownTest() {
+func (s *NoTopoSuite) TeardownTest() {
if CurrentSpecReport().Failed() {
s.CollectNginxLogs(s.Containers.NginxHttp3)
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *NoTopoSuite) CreateNginxConfig(container *Container, multiThreadWorkers bool) {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range noTopoTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range noTopoSoloTests {
"runtime"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *NoTopo6Suite) TearDownTest() {
+func (s *NoTopo6Suite) TeardownTest() {
if CurrentSpecReport().Failed() {
s.CollectNginxLogs(s.Containers.NginxHttp3)
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *NoTopo6Suite) CreateNginxConfig(container *Container, multiThreadWorkers bool) {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range noTopo6Tests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range noTopo6SoloTests {
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
// https://onsi.github.io/ginkgo/#dynamically-generating-specs
"strconv"
"strings"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *VppProxySuite) TearDownTest() {
+func (s *VppProxySuite) TeardownTest() {
vpp := s.Containers.VppProxy.VppInstance
if CurrentSpecReport().Failed() {
s.Log(vpp.Vppctl("show session verbose 2"))
s.CollectNginxLogs(s.Containers.NginxServerTransient)
s.CollectIperfLogs(s.Containers.IperfS)
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *VppProxySuite) SetupNginxServer() {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range vppProxyTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range vppProxySoloTests {
"strings"
"time"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)
}
}
-func (s *VppUdpProxySuite) TearDownTest() {
+func (s *VppUdpProxySuite) TeardownTest() {
vpp := s.Containers.VppProxy.VppInstance
if CurrentSpecReport().Failed() {
s.Log(vpp.Vppctl("show session verbose 2"))
s.Log(vpp.Vppctl("show error"))
}
- s.HstSuite.TearDownTest()
+ s.HstSuite.TeardownTest()
}
func (s *VppUdpProxySuite) VppProxyAddr() string {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range vppUdpProxyTests {
s.SetupTest()
})
AfterAll(func() {
- s.TearDownSuite()
+ s.TeardownSuite()
})
AfterEach(func() {
- s.TearDownTest()
+ s.TeardownTest()
})
for filename, tests := range vppUdpProxySoloTests {
import (
"crypto/tls"
- "encoding/json"
"errors"
"fmt"
"io"
StdOutput string
}
-type IPerfResult struct {
- Start struct {
- Timestamp struct {
- Time string `json:"time"`
- } `json:"timestamp"`
- Connected []struct {
- Socket int `json:"socket"`
- LocalHost string `json:"local_host"`
- LocalPort int `json:"local_port"`
- RemoteHost string `json:"remote_host"`
- RemotePort int `json:"remote_port"`
- } `json:"connected"`
- Version string `json:"version"`
- Details struct {
- Protocol string `json:"protocol"`
- } `json:"test_start"`
- } `json:"start"`
- End struct {
- TcpSent *struct {
- MbitsPerSecond float64 `json:"bits_per_second"`
- MBytes float64 `json:"bytes"`
- } `json:"sum_sent,omitempty"`
- TcpReceived *struct {
- MbitsPerSecond float64 `json:"bits_per_second"`
- MBytes float64 `json:"bytes"`
- } `json:"sum_received,omitempty"`
- Udp *struct {
- MbitsPerSecond float64 `json:"bits_per_second"`
- JitterMs float64 `json:"jitter_ms,omitempty"`
- LostPackets int `json:"lost_packets,omitempty"`
- Packets int `json:"packets,omitempty"`
- LostPercent float64 `json:"lost_percent,omitempty"`
- MBytes float64 `json:"bytes"`
- } `json:"sum,omitempty"`
- } `json:"end"`
-}
-
func AssertFileSize(f1, f2 string) error {
fi1, err := os.Stat(f1)
if err != nil {
clnRes <- o
}
}
-
-func (s *HstSuite) ParseJsonIperfOutput(jsonResult []byte) IPerfResult {
- var result IPerfResult
- // remove iperf warning line if present
- if strings.Contains(string(jsonResult), "warning") {
- index := strings.Index(string(jsonResult), "\n")
- jsonResult = jsonResult[index+1:]
- }
-
- err := json.Unmarshal(jsonResult, &result)
- s.AssertNil(err)
-
- if result.Start.Details.Protocol == "TCP" {
- result.End.TcpSent.MbitsPerSecond = result.End.TcpSent.MbitsPerSecond / 1000000
- result.End.TcpSent.MBytes = result.End.TcpSent.MBytes / 1000000
- result.End.TcpReceived.MbitsPerSecond = result.End.TcpReceived.MbitsPerSecond / 1000000
- result.End.TcpReceived.MBytes = result.End.TcpReceived.MBytes / 1000000
- } else {
- result.End.Udp.MBytes = result.End.Udp.MBytes / 1000000
- result.End.Udp.MbitsPerSecond = result.End.Udp.MbitsPerSecond / 1000000
- }
-
- return result
-}
-
-func (s *HstSuite) LogJsonIperfOutput(result IPerfResult) {
- s.Log("\n*******************************************\n"+
- "%s\n"+
- "[%s] %s:%d connected to %s:%d\n"+
- "Started: %s\n",
- result.Start.Version,
- result.Start.Details.Protocol,
- result.Start.Connected[0].LocalHost, result.Start.Connected[0].LocalPort,
- result.Start.Connected[0].RemoteHost, result.Start.Connected[0].RemotePort,
- result.Start.Timestamp.Time)
-
- if result.Start.Details.Protocol == "TCP" {
- s.Log("Transfer (sent): %.2f MBytes\n"+
- "Bitrate (sent): %.2f Mbits/sec\n"+
- "Transfer (received): %.2f MBytes\n"+
- "Bitrate (received): %.2f Mbits/sec",
- result.End.TcpSent.MBytes,
- result.End.TcpSent.MbitsPerSecond,
- result.End.TcpReceived.MBytes,
- result.End.TcpReceived.MbitsPerSecond)
- } else {
- s.Log("Transfer: %.2f MBytes\n"+
- "Bitrate: %.2f Mbits/sec\n"+
- "Jitter: %.3f ms\n"+
- "Packets: %d\n"+
- "Packets lost: %d\n"+
- "Percent lost: %.2f%%",
- result.End.Udp.MBytes,
- result.End.Udp.MbitsPerSecond,
- result.End.Udp.JitterMs,
- result.End.Udp.Packets,
- result.End.Udp.LostPackets,
- result.End.Udp.LostPercent)
- }
- s.Log("*******************************************\n")
-}
"syscall"
"time"
+ . "fd.io/hs-test/infra/common"
"go.fd.io/govpp/binapi/ethernet_types"
"github.com/edwarnicke/exechelper"
vpp.getSuite().AssertNil(err)
} else {
fn := runtime.FuncForPC(pc)
- if fn != nil && strings.Contains(fn.Name(), "TearDownTest") {
+ if fn != nil && strings.Contains(fn.Name(), "TeardownTest") {
vpp.getSuite().Log("vppctl failed in test teardown (skipping assert): %v", err)
} else {
vpp.getSuite().AssertNil(err)
import (
"time"
- . "fd.io/hs-test/infra/infra_kind"
+ . "fd.io/hs-test/infra/kind"
. "github.com/onsi/ginkgo/v2"
)
}
func KindIperfVclTest(s *KindSuite) {
- s.DeployServerClient(s.ImageNames.HstVpp, s.ImageNames.HstVpp, s.PodNames.ServerVpp, s.PodNames.ClientVpp)
+ s.DeployPod(s.Pods.ClientGeneric)
+ s.DeployPod(s.Pods.ServerGeneric)
vclPath := "/vcl.conf"
ldpPath := "/usr/lib/libvcl_ldpreload.so"
"app-socket-api abstract:vpp/session\n" +
"}\" > /vcl.conf"
- s.Exec(s.PodNames.ClientVpp, s.ContainerNames.Client, []string{"/bin/bash", "-c", symLink})
- s.Exec(s.PodNames.ServerVpp, s.ContainerNames.Server, []string{"/bin/bash", "-c", symLink})
+ s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", symLink})
+ s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", symLink})
- _, err := s.Exec(s.PodNames.ClientVpp, s.ContainerNames.Client, []string{"/bin/bash", "-c", vclConf})
+ _, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", vclConf})
s.AssertNil(err)
- _, err = s.Exec(s.PodNames.ServerVpp, s.ContainerNames.Server, []string{"/bin/bash", "-c", vclConf})
+ _, err = s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", vclConf})
s.AssertNil(err)
- _, err = s.Exec(s.PodNames.ServerVpp, s.ContainerNames.Server, []string{"/bin/bash", "-c",
+ _, err = s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c",
"VCL_CONFIG=" + vclPath + " LD_PRELOAD=" + ldpPath + " iperf3 -s -D -4"})
s.AssertNil(err)
- output, err := s.Exec(s.PodNames.ClientVpp, s.ContainerNames.Client, []string{"/bin/bash", "-c",
- "VCL_CONFIG=" + vclPath + " LD_PRELOAD=" + ldpPath + " iperf3 -c " + s.ServerIp})
+ output, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c",
+ "VCL_CONFIG=" + vclPath + " LD_PRELOAD=" + ldpPath + " iperf3 -c " + s.Pods.ServerGeneric.IpAddress})
s.Log(output)
s.AssertNil(err)
}
func NginxRpsTest(s *KindSuite) {
- s.DeployServerClient(s.ImageNames.Nginx, s.ImageNames.Ab, s.PodNames.Nginx, s.PodNames.Ab)
+ s.DeployPod(s.Pods.Nginx)
+ s.DeployPod(s.Pods.Ab)
s.CreateNginxConfig()
vcl := "VCL_CONFIG=/vcl.conf"
ldp := "LD_PRELOAD=/usr/lib/libvcl_ldpreload.so"
"app-socket-api abstract:vpp/session\n" +
"}\" > /vcl.conf"
- out, err := s.Exec(s.PodNames.Nginx, s.ContainerNames.Server, []string{"/bin/bash", "-c", symLink})
+ out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", symLink})
s.AssertNil(err, out)
- out, err = s.Exec(s.PodNames.Nginx, s.ContainerNames.Server, []string{"/bin/bash", "-c", vclConf})
+ out, err = s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", vclConf})
s.AssertNil(err, out)
go func() {
defer GinkgoRecover()
- out, err := s.Exec(s.PodNames.Nginx, s.ContainerNames.Server, []string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"})
+ out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"})
s.AssertNil(err, out)
}()
// wait for nginx to start up
time.Sleep(time.Second * 2)
- out, err = s.Exec(s.PodNames.Ab, s.ContainerNames.Client, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.ServerIp + ":80/64B.json"})
+ out, err = s.Exec(s.Pods.Ab, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":80/64B.json"})
s.Log(out)
s.AssertNil(err)
}
"time"
. "fd.io/hs-test/infra"
+ . "fd.io/hs-test/infra/common"
. "github.com/onsi/ginkgo/v2"
)