hs-test: configure VPP from test context
Instead of configuring VPP instances running inside of a container, now the configuration is going to be done from within the test context by using binary API and shared volume that exposes api socket. This converts just some of the test cases, rest is to follow. Type: test Signed-off-by: Maros Ondrejicka <maros.ondrejicka@pantheon.tech> Change-Id: I87e4ab15de488f0eebb01ff514596265fc2a787f
This commit is contained in:

committed by
Florin Coras

parent
7a6532bb9f
commit
ffa3f60290
@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -47,17 +46,6 @@ func configureProxyTcp(ifName0, ipAddr0, ifName1, ipAddr1 string) ConfFn {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Actions) RunHttpCliSrv(args []string) *ActionResult {
|
||||
cmd := fmt.Sprintf("http cli server")
|
||||
return ApiCliInband(workDir, cmd)
|
||||
}
|
||||
|
||||
func (a *Actions) RunHttpCliCln(args []string) *ActionResult {
|
||||
cmd := fmt.Sprintf("http cli client uri http://10.10.10.1/80 query %s", getArgs())
|
||||
fmt.Println(cmd)
|
||||
return ApiCliInband(workDir, cmd)
|
||||
}
|
||||
|
||||
func (a *Actions) ConfigureVppProxy(args []string) *ActionResult {
|
||||
ctx, cancel := newVppContext()
|
||||
defer cancel()
|
||||
@ -123,31 +111,6 @@ func ApiCliInband(root, cmd string) *ActionResult {
|
||||
return NewActionResult(err, ActionResultWithStdout(cliInbandReply.Reply))
|
||||
}
|
||||
|
||||
func (a *Actions) RunEchoClient(args []string) *ActionResult {
|
||||
outBuff := bytes.NewBuffer([]byte{})
|
||||
errBuff := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := fmt.Sprintf("vpp_echo client socket-name %s/var/run/app_ns_sockets/2 use-app-socket-api uri %s://10.10.10.1/12344", workDir, args[2])
|
||||
err := exechelper.Run(cmd,
|
||||
exechelper.WithStdout(outBuff), exechelper.WithStderr(errBuff),
|
||||
exechelper.WithStdout(os.Stdout), exechelper.WithStderr(os.Stderr))
|
||||
|
||||
return NewActionResult(err, ActionResultWithStdout(string(outBuff.String())),
|
||||
ActionResultWithStderr(string(errBuff.String())))
|
||||
}
|
||||
|
||||
func (a *Actions) RunEchoServer(args []string) *ActionResult {
|
||||
cmd := fmt.Sprintf("vpp_echo server TX=RX socket-name %s/var/run/app_ns_sockets/1 use-app-socket-api uri %s://10.10.10.1/12344", workDir, args[2])
|
||||
errCh := exechelper.Start(cmd)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
writeSyncFile(NewActionResult(err, ActionResultWithDesc("echo_server: ")))
|
||||
default:
|
||||
}
|
||||
writeSyncFile(OkResult())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Actions) RunEchoSrvInternal(args []string) *ActionResult {
|
||||
cmd := fmt.Sprintf("test echo server %s uri tcp://10.10.10.1/1234", getArgs())
|
||||
return ApiCliInband(workDir, cmd)
|
||||
@ -158,49 +121,6 @@ func (a *Actions) RunEchoClnInternal(args []string) *ActionResult {
|
||||
return ApiCliInband(workDir, cmd)
|
||||
}
|
||||
|
||||
func (a *Actions) RunVclEchoServer(args []string) *ActionResult {
|
||||
f, err := os.Create("vcl_1.conf")
|
||||
if err != nil {
|
||||
return NewActionResult(err, ActionResultWithStderr(("create vcl config: ")))
|
||||
}
|
||||
socketPath := fmt.Sprintf("%s/var/run/app_ns_sockets/1", workDir)
|
||||
fmt.Fprintf(f, vclTemplate, socketPath, "1")
|
||||
f.Close()
|
||||
|
||||
os.Setenv("VCL_CONFIG", "./vcl_1.conf")
|
||||
cmd := fmt.Sprintf("vcl_test_server -p %s 12346", args[2])
|
||||
errCh := exechelper.Start(cmd)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
writeSyncFile(NewActionResult(err, ActionResultWithDesc("vcl_test_server: ")))
|
||||
default:
|
||||
}
|
||||
writeSyncFile(OkResult())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Actions) RunVclEchoClient(args []string) *ActionResult {
|
||||
outBuff := bytes.NewBuffer([]byte{})
|
||||
errBuff := bytes.NewBuffer([]byte{})
|
||||
|
||||
f, err := os.Create("vcl_2.conf")
|
||||
if err != nil {
|
||||
return NewActionResult(err, ActionResultWithStderr(("create vcl config: ")))
|
||||
}
|
||||
socketPath := fmt.Sprintf("%s/var/run/app_ns_sockets/2", workDir)
|
||||
fmt.Fprintf(f, vclTemplate, socketPath, "2")
|
||||
f.Close()
|
||||
|
||||
os.Setenv("VCL_CONFIG", "./vcl_2.conf")
|
||||
cmd := fmt.Sprintf("vcl_test_client -U -p %s 10.10.10.1 12346", args[2])
|
||||
err = exechelper.Run(cmd,
|
||||
exechelper.WithStdout(outBuff), exechelper.WithStderr(errBuff),
|
||||
exechelper.WithStdout(os.Stdout), exechelper.WithStderr(os.Stderr))
|
||||
|
||||
return NewActionResult(err, ActionResultWithStdout(string(outBuff.String())),
|
||||
ActionResultWithStderr(string(errBuff.String())))
|
||||
}
|
||||
|
||||
func configure2vethsTopo(ifName, interfaceAddress, namespaceId string, secret uint64, optionalHardwareAddress ...string) ConfFn {
|
||||
return func(ctx context.Context,
|
||||
vppConn api.Connection) error {
|
||||
|
@ -3,24 +3,27 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/edwarnicke/exechelper"
|
||||
)
|
||||
|
||||
type Volume struct {
|
||||
hostDir string
|
||||
containerDir string
|
||||
hostDir string
|
||||
containerDir string
|
||||
isDefaultWorkDir bool
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
suite *HstSuite
|
||||
isOptional bool
|
||||
name string
|
||||
image string
|
||||
workDir string
|
||||
extraRunningArgs string
|
||||
volumes map[string]Volume
|
||||
envVars map[string]string
|
||||
vppInstance *VppInstance
|
||||
}
|
||||
|
||||
func NewContainer(yamlInput ContainerConfig) (*Container, error) {
|
||||
@ -59,25 +62,53 @@ func NewContainer(yamlInput ContainerConfig) (*Container, error) {
|
||||
volumeMap := volu.(ContainerConfig)
|
||||
hostDir := r.Replace(volumeMap["host-dir"].(string))
|
||||
containerDir := volumeMap["container-dir"].(string)
|
||||
container.addVolume(hostDir, containerDir)
|
||||
isDefaultWorkDir := false
|
||||
|
||||
if isDefaultWorkDir, ok := volumeMap["is-default-work-dir"]; ok &&
|
||||
isDefaultWorkDir.(bool) &&
|
||||
len(container.workDir) == 0 {
|
||||
container.workDir = containerDir
|
||||
if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
|
||||
isDefaultWorkDir = isDefault.(bool)
|
||||
}
|
||||
|
||||
container.addVolume(hostDir, containerDir, isDefaultWorkDir)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := yamlInput["vars"]; ok {
|
||||
for _, envVar := range yamlInput["vars"].([]interface{}) {
|
||||
container.addEnvVar(envVar)
|
||||
envVarMap := envVar.(ContainerConfig)
|
||||
name := envVarMap["name"].(string)
|
||||
value := envVarMap["value"].(string)
|
||||
container.addEnvVar(name, value)
|
||||
}
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
|
||||
for _, v := range c.volumes {
|
||||
if v.isDefaultWorkDir {
|
||||
res = v
|
||||
exists = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) GetHostWorkDir() (res string) {
|
||||
if v, ok := c.getWorkDirVolume(); ok {
|
||||
res = v.hostDir
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) GetContainerWorkDir() (res string) {
|
||||
if v, ok := c.getWorkDirVolume(); ok {
|
||||
res = v.containerDir
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) getRunCommand() string {
|
||||
syncPath := fmt.Sprintf(" -v %s:/tmp/sync", c.getSyncPath())
|
||||
cmd := "docker run --cap-add=all -d --privileged --network host --rm"
|
||||
@ -103,10 +134,11 @@ func (c *Container) run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) addVolume(hostDir string, containerDir string) {
|
||||
func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
|
||||
var volume Volume
|
||||
volume.hostDir = hostDir
|
||||
volume.containerDir = containerDir
|
||||
volume.isDefaultWorkDir = isDefaultWorkDir
|
||||
c.volumes[hostDir] = volume
|
||||
}
|
||||
|
||||
@ -127,16 +159,13 @@ func (c *Container) getVolumesAsCliOption() string {
|
||||
}
|
||||
|
||||
func (c *Container) getWorkDirAsCliOption() string {
|
||||
if len(c.workDir) == 0 {
|
||||
return ""
|
||||
if _, ok := c.getWorkDirVolume(); ok {
|
||||
return fmt.Sprintf(" --workdir=\"%s\"", c.GetContainerWorkDir())
|
||||
}
|
||||
return fmt.Sprintf(" --workdir=\"%s\"", c.workDir)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Container) addEnvVar(envVar interface{}) {
|
||||
envVarMap := envVar.(ContainerConfig)
|
||||
name := envVarMap["name"].(string)
|
||||
value := envVarMap["value"].(string)
|
||||
func (c *Container) addEnvVar(name string, value string) {
|
||||
c.envVars[name] = value
|
||||
}
|
||||
|
||||
@ -156,8 +185,54 @@ func (c *Container) getSyncPath() string {
|
||||
return fmt.Sprintf("/tmp/%s/sync", c.name)
|
||||
}
|
||||
|
||||
func (c *Container) newVppInstance(additionalConfig ...Stanza) (*VppInstance, error) {
|
||||
vppConfig := new(VppConfig)
|
||||
vppConfig.CliSocketFilePath = defaultCliSocketFilePath
|
||||
if len(additionalConfig) > 0 {
|
||||
vppConfig.additionalConfig = additionalConfig[0]
|
||||
}
|
||||
|
||||
vpp := new(VppInstance)
|
||||
vpp.container = c
|
||||
vpp.config = vppConfig
|
||||
|
||||
c.vppInstance = vpp
|
||||
|
||||
return vpp, nil
|
||||
}
|
||||
|
||||
func (c *Container) copy(sourceFileName string, targetFileName string) error {
|
||||
cmd := exec.Command("docker", "cp", sourceFileName, c.name+":"+targetFileName)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (c *Container) createFile(destFileName string, content string) error {
|
||||
f, err := os.CreateTemp("/tmp", "hst-config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if _, err := f.Write([]byte(content)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.copy(f.Name(), destFileName)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes in detached mode so that the started application can continue to run
|
||||
* without blocking execution of test
|
||||
*/
|
||||
func (c *Container) execServer(command string) error {
|
||||
return exechelper.Run("docker exec -d" + c.getEnvVarsAsCliOption() + " " + c.name + " " + command)
|
||||
}
|
||||
|
||||
func (c *Container) exec(command string) (string, error) {
|
||||
cliCommand := "docker exec -d " + c.name + " " + command
|
||||
cliCommand := "docker exec" + c.getEnvVarsAsCliOption() + " " + c.name + " " + command
|
||||
byteOutput, err := exechelper.CombinedOutput(cliCommand)
|
||||
return string(byteOutput), err
|
||||
}
|
||||
@ -187,5 +262,9 @@ func (c *Container) execAction(args string) (string, error) {
|
||||
}
|
||||
|
||||
func (c *Container) stop() error {
|
||||
if c.vppInstance != nil && c.vppInstance.apiChannel != nil {
|
||||
c.vppInstance.disconnect()
|
||||
}
|
||||
c.vppInstance = nil
|
||||
return exechelper.Run("docker stop " + c.name + " -t 0")
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
package main
|
||||
|
||||
func (s *VethsSuite) TestEchoBuiltin() {
|
||||
serverContainer := s.getContainerByName("server-vpp")
|
||||
_, err := serverContainer.execAction("Configure2Veths srv")
|
||||
serverVpp := s.getContainerByName("server-vpp").vppInstance
|
||||
serverVeth := s.veths["vppsrv"]
|
||||
|
||||
_, err := serverVpp.vppctl("test echo server " +
|
||||
" private-segment-size 1g fifo-size 4 no-echo" +
|
||||
" uri tcp://" + serverVeth.Address() + "/1234")
|
||||
s.assertNil(err)
|
||||
|
||||
clientContainer := s.getContainerByName("client-vpp")
|
||||
_, err = clientContainer.execAction("Configure2Veths cln")
|
||||
s.assertNil(err)
|
||||
clientVpp := s.getContainerByName("client-vpp").vppInstance
|
||||
|
||||
_, err = serverContainer.execAction("RunEchoSrvInternal private-segment-size 1g fifo-size 4 no-echo")
|
||||
s.assertNil(err)
|
||||
|
||||
o, err := clientContainer.execAction("RunEchoClnInternal nclients 10000 bytes 1 syn-timeout 100 test-timeout 100 no-return private-segment-size 1g fifo-size 4")
|
||||
o, err := clientVpp.vppctl("test echo client nclients 10000 bytes 1" +
|
||||
" syn-timeout 100 test-timeout 100 no-return private-segment-size 1g" +
|
||||
" fifo-size 4 uri tcp://" + serverVeth.Address() + "/1234")
|
||||
s.assertNil(err)
|
||||
s.log(o)
|
||||
}
|
||||
|
@ -1,152 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/edwarnicke/exechelper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func IsPersistent() bool {
|
||||
return os.Getenv("HST_PERSIST") == "1"
|
||||
}
|
||||
|
||||
func IsVerbose() bool {
|
||||
return os.Getenv("HST_VERBOSE") == "1"
|
||||
}
|
||||
|
||||
type HstSuite struct {
|
||||
suite.Suite
|
||||
teardownSuite func()
|
||||
containers map[string]*Container
|
||||
volumes []string
|
||||
}
|
||||
|
||||
func (s *HstSuite) TearDownSuite() {
|
||||
s.teardownSuite()
|
||||
}
|
||||
|
||||
func (s *HstSuite) TearDownTest() {
|
||||
if IsPersistent() {
|
||||
return
|
||||
}
|
||||
s.ResetContainers()
|
||||
s.RemoveVolumes()
|
||||
}
|
||||
|
||||
func (s *HstSuite) SetupTest() {
|
||||
for _, volume := range s.volumes {
|
||||
cmd := "docker volume create --name=" + volume
|
||||
s.log(cmd)
|
||||
exechelper.Run(cmd)
|
||||
}
|
||||
for _, container := range s.containers {
|
||||
if container.isOptional == false {
|
||||
container.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) hstFail() {
|
||||
s.T().FailNow()
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Nil(s.T(), object, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotNil(s.T(), object, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) log(args ...any) {
|
||||
if IsVerbose() {
|
||||
s.T().Log(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) skip(args ...any) {
|
||||
s.log(args...)
|
||||
s.T().SkipNow()
|
||||
}
|
||||
|
||||
func (s *HstSuite) ResetContainers() {
|
||||
for _, container := range s.containers {
|
||||
container.stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) RemoveVolumes() {
|
||||
for _, volumeName := range s.volumes {
|
||||
cmd := "docker volume rm " + volumeName
|
||||
exechelper.Run(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) getContainerByName(name string) *Container {
|
||||
return s.containers[name]
|
||||
}
|
||||
|
||||
func (s *HstSuite) loadContainerTopology(topologyName string) {
|
||||
data, err := ioutil.ReadFile(ContainerTopologyDir + topologyName + ".yaml")
|
||||
if err != nil {
|
||||
s.T().Fatalf("read error: %v", err)
|
||||
}
|
||||
var yamlTopo YamlTopology
|
||||
err = yaml.Unmarshal(data, &yamlTopo)
|
||||
if err != nil {
|
||||
s.T().Fatalf("unmarshal error: %v", err)
|
||||
}
|
||||
|
||||
for _, elem := range yamlTopo.Volumes {
|
||||
volumeMap := elem["volume"].(VolumeConfig)
|
||||
hostDir := volumeMap["host-dir"].(string)
|
||||
s.volumes = append(s.volumes, hostDir)
|
||||
}
|
||||
|
||||
s.containers = make(map[string]*Container)
|
||||
for _, elem := range yamlTopo.Containers {
|
||||
newContainer, err := NewContainer(elem)
|
||||
if err != nil {
|
||||
s.T().Fatalf("config error: %v", err)
|
||||
}
|
||||
s.log(newContainer.getRunCommand())
|
||||
s.containers[newContainer.name] = newContainer
|
||||
}
|
||||
}
|
||||
|
||||
func setupSuite(s *suite.Suite, topologyName string) func() {
|
||||
t := s.T()
|
||||
topology, err := LoadTopology(NetworkTopologyDir, topologyName)
|
||||
|
@ -8,6 +8,7 @@ require (
|
||||
github.com/edwarnicke/govpp v0.0.0-20220311182453-f32f292e0e91
|
||||
github.com/edwarnicke/vpphelper v0.0.0-20210617172001-3e6797de32c3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.fd.io/govpp v0.7.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@ -18,13 +19,10 @@ require (
|
||||
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-20200521075829-a4cb8d33dbbe // indirect
|
||||
github.com/onsi/gomega v1.16.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
)
|
||||
|
@ -19,21 +19,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@ -47,17 +33,9 @@ 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-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y=
|
||||
github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.1.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
@ -73,59 +51,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
go.fd.io/govpp v0.7.0 h1:8qideC7G0xPeYz2sjwni8GKWWbNk45Ev73oR1igKDYY=
|
||||
go.fd.io/govpp v0.7.0/go.mod h1:VxUPq8HGQH6/9IL9saMURL3UcHsUuN8XmETuao5HA7o=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@ -134,12 +69,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
289
extras/hs-test/hst_suite.go
Normal file
289
extras/hs-test/hst_suite.go
Normal file
@ -0,0 +1,289 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/edwarnicke/exechelper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.fd.io/govpp/binapi/ip_types"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func IsPersistent() bool {
|
||||
return os.Getenv("HST_PERSIST") == "1"
|
||||
}
|
||||
|
||||
func IsVerbose() bool {
|
||||
return os.Getenv("HST_VERBOSE") == "1"
|
||||
}
|
||||
|
||||
type HstSuite struct {
|
||||
suite.Suite
|
||||
teardownSuite func()
|
||||
containers map[string]*Container
|
||||
volumes []string
|
||||
networkNamespaces map[string]*NetworkNamespace
|
||||
veths map[string]*NetworkInterfaceVeth
|
||||
taps map[string]*NetworkInterfaceTap
|
||||
bridges map[string]*NetworkBridge
|
||||
numberOfAddresses int
|
||||
}
|
||||
|
||||
func (s *HstSuite) TearDownSuite() {
|
||||
if s.teardownSuite != nil {
|
||||
s.teardownSuite() // TODO remove this after config moved to SetupTest() for each suite
|
||||
}
|
||||
|
||||
s.unconfigureNetworkTopology()
|
||||
}
|
||||
|
||||
func (s *HstSuite) TearDownTest() {
|
||||
if IsPersistent() {
|
||||
return
|
||||
}
|
||||
s.ResetContainers()
|
||||
s.RemoveVolumes()
|
||||
}
|
||||
|
||||
func (s *HstSuite) SetupTest() {
|
||||
s.SetupVolumes()
|
||||
s.SetupContainers()
|
||||
}
|
||||
|
||||
func (s *HstSuite) SetupVolumes() {
|
||||
for _, volume := range s.volumes {
|
||||
cmd := "docker volume create --name=" + volume
|
||||
s.log(cmd)
|
||||
exechelper.Run(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) SetupContainers() {
|
||||
for _, container := range s.containers {
|
||||
if container.isOptional == false {
|
||||
container.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) hstFail() {
|
||||
s.T().FailNow()
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Nil(s.T(), object, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotNil(s.T(), object, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
|
||||
s.hstFail()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) log(args ...any) {
|
||||
if IsVerbose() {
|
||||
s.T().Log(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) skip(args ...any) {
|
||||
s.log(args...)
|
||||
s.T().SkipNow()
|
||||
}
|
||||
|
||||
func (s *HstSuite) ResetContainers() {
|
||||
for _, container := range s.containers {
|
||||
container.stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) RemoveVolumes() {
|
||||
for _, volumeName := range s.volumes {
|
||||
cmd := "docker volume rm " + volumeName
|
||||
exechelper.Run(cmd)
|
||||
os.RemoveAll(volumeName)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) getContainerByName(name string) *Container {
|
||||
return s.containers[name]
|
||||
}
|
||||
|
||||
func (s *HstSuite) getContainerCopyByName(name string) *Container {
|
||||
// Create a copy and return its address, so that individial tests which call this
|
||||
// are not able to modify the original container and affect other tests by doing that
|
||||
containerCopy := *s.containers[name]
|
||||
return &containerCopy
|
||||
}
|
||||
|
||||
func (s *HstSuite) loadContainerTopology(topologyName string) {
|
||||
data, err := ioutil.ReadFile(ContainerTopologyDir + topologyName + ".yaml")
|
||||
if err != nil {
|
||||
s.T().Fatalf("read error: %v", err)
|
||||
}
|
||||
var yamlTopo YamlTopology
|
||||
err = yaml.Unmarshal(data, &yamlTopo)
|
||||
if err != nil {
|
||||
s.T().Fatalf("unmarshal error: %v", err)
|
||||
}
|
||||
|
||||
for _, elem := range yamlTopo.Volumes {
|
||||
volumeMap := elem["volume"].(VolumeConfig)
|
||||
hostDir := volumeMap["host-dir"].(string)
|
||||
s.volumes = append(s.volumes, hostDir)
|
||||
}
|
||||
|
||||
s.containers = make(map[string]*Container)
|
||||
for _, elem := range yamlTopo.Containers {
|
||||
newContainer, err := NewContainer(elem)
|
||||
newContainer.suite = s
|
||||
if err != nil {
|
||||
s.T().Fatalf("container config error: %v", err)
|
||||
}
|
||||
s.log(newContainer.getRunCommand())
|
||||
s.containers[newContainer.name] = newContainer
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) loadNetworkTopology(topologyName string) {
|
||||
data, err := ioutil.ReadFile(NetworkTopologyDir + topologyName + ".yaml")
|
||||
if err != nil {
|
||||
s.T().Fatalf("read error: %v", err)
|
||||
}
|
||||
var yamlTopo YamlTopology
|
||||
err = yaml.Unmarshal(data, &yamlTopo)
|
||||
if err != nil {
|
||||
s.T().Fatalf("unmarshal error: %v", err)
|
||||
}
|
||||
|
||||
s.networkNamespaces = make(map[string]*NetworkNamespace)
|
||||
s.veths = make(map[string]*NetworkInterfaceVeth)
|
||||
s.taps = make(map[string]*NetworkInterfaceTap)
|
||||
s.bridges = make(map[string]*NetworkBridge)
|
||||
for _, elem := range yamlTopo.Devices {
|
||||
switch elem["type"].(string) {
|
||||
case NetNs:
|
||||
{
|
||||
if namespace, err := NewNetNamespace(elem); err == nil {
|
||||
s.networkNamespaces[namespace.Name()] = &namespace
|
||||
} else {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
case Veth:
|
||||
{
|
||||
if veth, err := NewVeth(elem); err == nil {
|
||||
s.veths[veth.Name()] = &veth
|
||||
} else {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
case Tap:
|
||||
{
|
||||
if tap, err := NewTap(elem); err == nil {
|
||||
s.taps[tap.Name()] = &tap
|
||||
} else {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
case Bridge:
|
||||
{
|
||||
if bridge, err := NewBridge(elem); err == nil {
|
||||
s.bridges[bridge.Name()] = &bridge
|
||||
} else {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) configureNetworkTopology(topologyName string) {
|
||||
s.loadNetworkTopology(topologyName)
|
||||
|
||||
for _, ns := range s.networkNamespaces {
|
||||
if err := ns.Configure(); err != nil {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
for _, veth := range s.veths {
|
||||
if err := veth.Configure(); err != nil {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
for _, tap := range s.taps {
|
||||
if err := tap.Configure(); err != nil {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
for _, bridge := range s.bridges {
|
||||
if err := bridge.Configure(); err != nil {
|
||||
s.T().Fatalf("network config error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) unconfigureNetworkTopology() {
|
||||
if IsPersistent() {
|
||||
return
|
||||
}
|
||||
for _, ns := range s.networkNamespaces {
|
||||
ns.Unconfigure()
|
||||
}
|
||||
for _, veth := range s.veths {
|
||||
veth.Unconfigure()
|
||||
}
|
||||
for _, tap := range s.taps {
|
||||
tap.Unconfigure()
|
||||
}
|
||||
for _, bridge := range s.bridges {
|
||||
bridge.Unconfigure()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HstSuite) NewAddress() (AddressWithPrefix, error) {
|
||||
var ipPrefix AddressWithPrefix
|
||||
var err error
|
||||
|
||||
if s.numberOfAddresses == 255 {
|
||||
s.T().Fatalf("no available IPv4 addresses")
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("10.10.10.%v/24", s.numberOfAddresses+1)
|
||||
ipPrefix, err = ip_types.ParseAddressWithPrefix(address)
|
||||
if err != nil {
|
||||
return AddressWithPrefix{}, err
|
||||
}
|
||||
s.numberOfAddresses++
|
||||
|
||||
return ipPrefix, nil
|
||||
}
|
@ -31,22 +31,18 @@ func (s *VethsSuite) TestHttpCli() {
|
||||
serverContainer := s.getContainerByName("server-vpp")
|
||||
clientContainer := s.getContainerByName("client-vpp")
|
||||
|
||||
_, err := serverContainer.execAction("Configure2Veths srv")
|
||||
serverVeth := s.veths["vppsrv"]
|
||||
|
||||
_, err := serverContainer.vppInstance.vppctl("http cli server")
|
||||
s.assertNil(err)
|
||||
|
||||
_, err = clientContainer.execAction("Configure2Veths cln")
|
||||
s.assertNil(err)
|
||||
|
||||
s.log("configured IPs...")
|
||||
|
||||
_, err = serverContainer.execAction("RunHttpCliSrv")
|
||||
s.assertNil(err)
|
||||
|
||||
s.log("configured http server")
|
||||
|
||||
o, err := clientContainer.execAction("RunHttpCliCln /show/version")
|
||||
uri := "http://" + serverVeth.Address() + "/80"
|
||||
|
||||
o, err := clientContainer.vppInstance.vppctl("http cli client" +
|
||||
" uri " + uri + " query /show/version")
|
||||
s.assertNil(err)
|
||||
|
||||
s.log(o)
|
||||
s.assertContains(o, "<html>", "<html> not found in the result!")
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,10 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
|
||||
var clnVclConf, srvVclConf Stanza
|
||||
|
||||
serverContainer := s.getContainerByName("server-vpp")
|
||||
serverVolume := serverContainer.getVolumeByHostDir("/tmp/server")
|
||||
srvVcl := serverVolume.containerDir + "/vcl_srv.conf"
|
||||
srvVcl := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
|
||||
|
||||
clientContainer := s.getContainerByName("client-vpp")
|
||||
clientVolume := clientContainer.getVolumeByHostDir("/tmp/client")
|
||||
clnVcl := clientVolume.containerDir + "/vcl_cln.conf"
|
||||
clnVcl := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
|
||||
|
||||
ldpreload := os.Getenv("HST_LDPRELOAD")
|
||||
s.assertNotEqual("", ldpreload)
|
||||
@ -28,20 +26,14 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
|
||||
|
||||
s.log("starting VPPs")
|
||||
|
||||
originalWorkDir := serverContainer.workDir
|
||||
serverContainer.workDir = serverVolume.containerDir
|
||||
_, err := serverContainer.execAction("Configure2Veths srv")
|
||||
s.assertNil(err)
|
||||
serverContainer.workDir = originalWorkDir
|
||||
|
||||
originalWorkDir = clientContainer.workDir
|
||||
clientContainer.workDir = clientVolume.containerDir
|
||||
_, err = clientContainer.execAction("Configure2Veths cln")
|
||||
s.assertNil(err)
|
||||
clientContainer.workDir = originalWorkDir
|
||||
|
||||
clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/2",
|
||||
clientVolume.containerDir)
|
||||
clientContainer.GetContainerWorkDir())
|
||||
err = clnVclConf.
|
||||
NewStanza("vcl").
|
||||
Append("rx-fifo-size 4000000").
|
||||
@ -54,7 +46,7 @@ func (s *VethsSuite) TestLDPreloadIperfVpp() {
|
||||
s.assertNil(err)
|
||||
|
||||
serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/1",
|
||||
serverVolume.containerDir)
|
||||
serverContainer.GetContainerWorkDir())
|
||||
err = srvVclConf.
|
||||
NewStanza("vcl").
|
||||
Append("rx-fifo-size 4000000").
|
||||
|
@ -4,22 +4,128 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"go.fd.io/govpp/binapi/ethernet_types"
|
||||
"go.fd.io/govpp/binapi/interface_types"
|
||||
"go.fd.io/govpp/binapi/ip_types"
|
||||
)
|
||||
|
||||
type NetType string
|
||||
type (
|
||||
AddressWithPrefix = ip_types.AddressWithPrefix
|
||||
MacAddress = ethernet_types.MacAddress
|
||||
|
||||
NetConfig struct {
|
||||
Configure func() error
|
||||
Unconfigure func()
|
||||
}
|
||||
|
||||
NetTopology []NetConfig
|
||||
|
||||
NetConfigBase struct {
|
||||
name string
|
||||
category string // what else to call this when `type` is reserved?
|
||||
}
|
||||
|
||||
NetworkInterfaceVeth struct {
|
||||
NetConfigBase
|
||||
index interface_types.InterfaceIndex
|
||||
peerNetworkNamespace string
|
||||
peerName string
|
||||
peerIp4Address string
|
||||
ip4Address ip_types.AddressWithPrefix
|
||||
hwAddress ethernet_types.MacAddress
|
||||
}
|
||||
|
||||
NetworkInterfaceTap struct {
|
||||
NetConfigBase
|
||||
index interface_types.InterfaceIndex
|
||||
ip4Address string
|
||||
}
|
||||
|
||||
NetworkNamespace struct {
|
||||
NetConfigBase
|
||||
}
|
||||
|
||||
NetworkBridge struct {
|
||||
NetConfigBase
|
||||
networkNamespace string
|
||||
interfaces []string
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
NetNs NetType = "netns"
|
||||
Veth string = "veth"
|
||||
Tap string = "tap"
|
||||
NetNs string = "netns"
|
||||
Veth string = "veth"
|
||||
Tap string = "tap"
|
||||
Bridge string = "bridge"
|
||||
)
|
||||
|
||||
type NetConfig struct {
|
||||
Configure func() error
|
||||
Unconfigure func()
|
||||
func (b NetConfigBase) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
type NetTopology []NetConfig
|
||||
func (b NetConfigBase) Type() string {
|
||||
return b.category
|
||||
}
|
||||
|
||||
func (iface NetworkInterfaceVeth) Configure() error {
|
||||
err := AddVethPair(iface.name, iface.peerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if iface.peerNetworkNamespace != "" {
|
||||
err := LinkSetNetns(iface.peerName, iface.peerNetworkNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if iface.peerIp4Address != "" {
|
||||
err = AddAddress(iface.peerName, iface.peerIp4Address, iface.peerNetworkNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add configure address for %s: %v", iface.peerName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iface NetworkInterfaceVeth) Unconfigure() {
|
||||
DelLink(iface.name)
|
||||
}
|
||||
|
||||
func (iface NetworkInterfaceVeth) Address() string {
|
||||
return strings.Split(iface.ip4Address.String(), "/")[0]
|
||||
}
|
||||
|
||||
func (iface NetworkInterfaceTap) Configure() error {
|
||||
err := AddTap(iface.name, iface.ip4Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iface NetworkInterfaceTap) Unconfigure() {
|
||||
DelLink(iface.name)
|
||||
}
|
||||
|
||||
func (ns NetworkNamespace) Configure() error {
|
||||
return addDelNetns(ns.name, true)
|
||||
}
|
||||
|
||||
func (ns NetworkNamespace) Unconfigure() {
|
||||
addDelNetns(ns.name, false)
|
||||
}
|
||||
|
||||
func (b NetworkBridge) Configure() error {
|
||||
return AddBridge(b.name, b.interfaces, b.networkNamespace)
|
||||
}
|
||||
|
||||
func (b NetworkBridge) Unconfigure() {
|
||||
DelBridge(b.name, b.networkNamespace)
|
||||
}
|
||||
|
||||
func (t *NetTopology) Configure() error {
|
||||
for _, c := range *t {
|
||||
@ -101,6 +207,60 @@ func NewNetConfig(cfg NetDevConfig) NetConfig {
|
||||
return nc
|
||||
}
|
||||
|
||||
func NewNetNamespace(cfg NetDevConfig) (NetworkNamespace, error) {
|
||||
var networkNamespace NetworkNamespace
|
||||
networkNamespace.name = cfg["name"].(string)
|
||||
networkNamespace.category = "netns"
|
||||
return networkNamespace, nil
|
||||
}
|
||||
|
||||
func NewBridge(cfg NetDevConfig) (NetworkBridge, error) {
|
||||
var bridge NetworkBridge
|
||||
bridge.name = cfg["name"].(string)
|
||||
bridge.category = "bridge"
|
||||
for _, v := range cfg["interfaces"].([]interface{}) {
|
||||
bridge.interfaces = append(bridge.interfaces, v.(string))
|
||||
}
|
||||
bridge.networkNamespace = cfg["netns"].(string)
|
||||
return bridge, nil
|
||||
}
|
||||
|
||||
func NewVeth(cfg NetDevConfig) (NetworkInterfaceVeth, error) {
|
||||
var veth NetworkInterfaceVeth
|
||||
var err error
|
||||
veth.name = cfg["name"].(string)
|
||||
veth.category = "veth"
|
||||
|
||||
if cfg["preset-hw-address"] != nil {
|
||||
veth.hwAddress, err = ethernet_types.ParseMacAddress(cfg["preset-hw-address"].(string))
|
||||
if err != nil {
|
||||
return NetworkInterfaceVeth{}, err
|
||||
}
|
||||
}
|
||||
|
||||
peer := cfg["peer"].(NetDevConfig)
|
||||
|
||||
veth.peerName = peer["name"].(string)
|
||||
|
||||
if peer["netns"] != nil {
|
||||
veth.peerNetworkNamespace = peer["netns"].(string)
|
||||
}
|
||||
|
||||
if peer["ip4"] != nil {
|
||||
veth.peerIp4Address = peer["ip4"].(string)
|
||||
}
|
||||
|
||||
return veth, nil
|
||||
}
|
||||
|
||||
func NewTap(cfg NetDevConfig) (NetworkInterfaceTap, error) {
|
||||
var tap NetworkInterfaceTap
|
||||
tap.name = cfg["name"].(string)
|
||||
tap.category = "tap"
|
||||
tap.ip4Address = cfg["ip4"].(string)
|
||||
return tap, nil
|
||||
}
|
||||
|
||||
func DelBridge(brName, ns string) error {
|
||||
err := SetDevDown(brName, ns)
|
||||
if err != err {
|
||||
|
@ -4,12 +4,94 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// These correspond to names used in yaml config
|
||||
serverInterfaceName = "vppsrv"
|
||||
clientInterfaceName = "vppcln"
|
||||
)
|
||||
|
||||
type VethsSuite struct {
|
||||
HstSuite
|
||||
}
|
||||
|
||||
var ConvertedTests = map[string]any{
|
||||
"TestVeths/TestEchoBuiltin": "",
|
||||
"TestVeths/TestHttpCli": "",
|
||||
"TestVeths/TestVclEchoTcp": "",
|
||||
"TestVeths/TestVclRetryAttach": "",
|
||||
}
|
||||
|
||||
func (s *VethsSuite) SetupSuite() {
|
||||
time.Sleep(1 * time.Second)
|
||||
s.teardownSuite = setupSuite(&s.Suite, "2peerVeth")
|
||||
|
||||
s.configureNetworkTopology("2peerVeth")
|
||||
|
||||
s.loadContainerTopology("2peerVeth")
|
||||
}
|
||||
|
||||
func (s *VethsSuite) SetupTest() {
|
||||
s.SetupVolumes()
|
||||
s.SetupContainers()
|
||||
|
||||
// TODO remove this after all tests are converted to configuration from test suite
|
||||
if _, ok := ConvertedTests[s.T().Name()]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Setup test conditions
|
||||
|
||||
var startupConfig Stanza
|
||||
startupConfig.
|
||||
NewStanza("session").
|
||||
Append("enable").
|
||||
Append("use-app-socket-api").Close()
|
||||
|
||||
// ... For server
|
||||
serverContainer := s.getContainerByName("server-vpp")
|
||||
|
||||
serverVpp, _ := serverContainer.newVppInstance(startupConfig)
|
||||
s.assertNotNil(serverVpp)
|
||||
|
||||
s.setupServerVpp()
|
||||
|
||||
// ... For client
|
||||
clientContainer := s.getContainerByName("client-vpp")
|
||||
|
||||
clientVpp, _ := clientContainer.newVppInstance(startupConfig)
|
||||
s.assertNotNil(clientVpp)
|
||||
|
||||
s.setupClientVpp()
|
||||
}
|
||||
|
||||
func (s *VethsSuite) setupServerVpp() {
|
||||
serverVpp := s.getContainerByName("server-vpp").vppInstance
|
||||
|
||||
err := serverVpp.start()
|
||||
s.assertNil(err)
|
||||
|
||||
serverVeth := s.veths["vppsrv"]
|
||||
idx, err := serverVpp.createAfPacket(serverVeth)
|
||||
s.assertNil(err)
|
||||
s.assertNotEqual(0, idx)
|
||||
|
||||
namespaceSecret := "1"
|
||||
err = serverVpp.addAppNamespace(1, idx, namespaceSecret)
|
||||
s.assertNil(err)
|
||||
|
||||
}
|
||||
|
||||
func (s *VethsSuite) setupClientVpp() {
|
||||
clientVpp := s.getContainerByName("client-vpp").vppInstance
|
||||
|
||||
err := clientVpp.start()
|
||||
s.assertNil(err)
|
||||
|
||||
clientVeth := s.veths["vppcln"]
|
||||
idx, err := clientVpp.createAfPacket(clientVeth)
|
||||
s.assertNil(err)
|
||||
s.assertNotEqual(0, idx)
|
||||
|
||||
clientNamespaceSecret := "2"
|
||||
err = clientVpp.addAppNamespace(2, idx, clientNamespaceSecret)
|
||||
s.assertNil(err)
|
||||
}
|
||||
|
@ -1,33 +1,25 @@
|
||||
---
|
||||
volumes:
|
||||
- volume: &server-vol
|
||||
host-dir: server-share
|
||||
host-dir: /tmp/server-share
|
||||
container-dir: /tmp/server-share
|
||||
is-default-work-dir: true
|
||||
- volume: &client-vol
|
||||
host-dir: client-share
|
||||
host-dir: /tmp/client-share
|
||||
container-dir: "/tmp/client-share"
|
||||
is-default-work-dir: true
|
||||
|
||||
containers:
|
||||
- name: "server-vpp"
|
||||
volumes:
|
||||
- <<: *server-vol
|
||||
container-dir: "/tmp/server-share"
|
||||
is-default-work-dir: true
|
||||
- host-dir: "/tmp/server"
|
||||
container-dir: "/tmp/server"
|
||||
- name: "client-vpp"
|
||||
volumes:
|
||||
- <<: *client-vol
|
||||
container-dir: "/tmp/client-share"
|
||||
is-default-work-dir: true
|
||||
- host-dir: "/tmp/client"
|
||||
container-dir: "/tmp/client"
|
||||
- name: "server-application"
|
||||
volumes:
|
||||
- <<: *server-vol
|
||||
container-dir: "/tmp/server-share"
|
||||
is-default-work-dir: true
|
||||
- name: "client-application"
|
||||
volumes:
|
||||
- <<: *client-vol
|
||||
container-dir: "/tmp/client-share"
|
||||
is-default-work-dir: true
|
||||
|
||||
|
@ -5,6 +5,7 @@ devices:
|
||||
|
||||
- name: "vppsrv"
|
||||
type: "veth"
|
||||
preset-hw-address: "00:00:5e:00:53:01"
|
||||
peer:
|
||||
name: "vppsrv_veth"
|
||||
netns: "hsns"
|
||||
|
@ -51,7 +51,7 @@ plugins {
|
||||
`
|
||||
|
||||
const vclTemplate = `vcl {
|
||||
app-socket-api %[1]s
|
||||
app-socket-api %[1]s/var/run/app_ns_sockets/%[2]s
|
||||
app-scope-global
|
||||
app-scope-local
|
||||
namespace-id %[2]s
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -19,73 +20,78 @@ func (s *VethsSuite) TestVclEchoTcp() {
|
||||
}
|
||||
|
||||
func (s *VethsSuite) testVclEcho(proto string) {
|
||||
srvVppContainer := s.getContainerByName("server-vpp")
|
||||
|
||||
_, err := srvVppContainer.execAction("Configure2Veths srv")
|
||||
s.assertNil(err)
|
||||
|
||||
clnVppContainer := s.getContainerByName("client-vpp")
|
||||
|
||||
_, err = clnVppContainer.execAction("Configure2Veths cln")
|
||||
s.assertNil(err)
|
||||
serverVethAddress := s.veths["vppsrv"].Address()
|
||||
uri := proto + "://" + serverVethAddress + "/12344"
|
||||
|
||||
echoSrvContainer := s.getContainerByName("server-application")
|
||||
|
||||
// run server app
|
||||
_, err = echoSrvContainer.execAction("RunEchoServer " + proto)
|
||||
serverCommand := "vpp_echo server TX=RX" +
|
||||
" socket-name " + echoSrvContainer.GetContainerWorkDir() + "/var/run/app_ns_sockets/1" +
|
||||
" use-app-socket-api" +
|
||||
" uri " + uri
|
||||
s.log(serverCommand)
|
||||
err := echoSrvContainer.execServer(serverCommand)
|
||||
s.assertNil(err)
|
||||
|
||||
echoClnContainer := s.getContainerByName("client-application")
|
||||
|
||||
o, err := echoClnContainer.execAction("RunEchoClient " + proto)
|
||||
clientCommand := "vpp_echo client" +
|
||||
" socket-name " + echoClnContainer.GetContainerWorkDir() + "/var/run/app_ns_sockets/2" +
|
||||
" use-app-socket-api uri " + uri
|
||||
s.log(clientCommand)
|
||||
o, err := echoClnContainer.exec(clientCommand)
|
||||
s.assertNil(err)
|
||||
|
||||
s.log(o)
|
||||
}
|
||||
|
||||
func (s *VethsSuite) TestVclRetryAttach() {
|
||||
s.skip()
|
||||
s.skip("this test takes too long, for now it's being skipped")
|
||||
s.testRetryAttach("tcp")
|
||||
}
|
||||
|
||||
func (s *VethsSuite) testRetryAttach(proto string) {
|
||||
srvVppContainer := s.getContainerByName("server-vpp")
|
||||
|
||||
_, err := srvVppContainer.execAction("Configure2Veths srv-with-preset-hw-addr")
|
||||
s.assertNil(err)
|
||||
|
||||
clnVppContainer := s.getContainerByName("client-vpp")
|
||||
|
||||
_, err = clnVppContainer.execAction("Configure2Veths cln")
|
||||
s.assertNil(err)
|
||||
srvVppContainer := s.getContainerCopyByName("server-vpp")
|
||||
|
||||
echoSrvContainer := s.getContainerByName("server-application")
|
||||
_, err = echoSrvContainer.execAction("RunVclEchoServer " + proto)
|
||||
|
||||
serverVclConfContent := fmt.Sprintf(vclTemplate, echoSrvContainer.GetContainerWorkDir(), "1")
|
||||
echoSrvContainer.createFile("/vcl.conf", serverVclConfContent)
|
||||
|
||||
echoSrvContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
|
||||
err := echoSrvContainer.execServer("vcl_test_server -p " + proto + " 12346")
|
||||
s.assertNil(err)
|
||||
|
||||
s.log("This whole test case can take around 3 minutes to run. Please be patient.")
|
||||
s.log("... Running first echo client test, before disconnect.")
|
||||
echoClnContainer := s.getContainerByName("client-application")
|
||||
_, err = echoClnContainer.execAction("RunVclEchoClient " + proto)
|
||||
|
||||
serverVeth := s.veths[serverInterfaceName]
|
||||
serverVethAddress := serverVeth.Address()
|
||||
|
||||
echoClnContainer := s.getContainerCopyByName("client-application")
|
||||
clientVclConfContent := fmt.Sprintf(vclTemplate, echoClnContainer.GetContainerWorkDir(), "2")
|
||||
echoClnContainer.createFile("/vcl.conf", clientVclConfContent)
|
||||
|
||||
testClientCommand := "vcl_test_client -U -p " + proto + " " + serverVethAddress + " 12346"
|
||||
echoClnContainer.addEnvVar("VCL_CONFIG", "/vcl.conf")
|
||||
o, err := echoClnContainer.exec(testClientCommand)
|
||||
s.log(o)
|
||||
s.assertNil(err)
|
||||
s.log("... First test ended. Stopping VPP server now.")
|
||||
|
||||
// Stop server-vpp-instance, start it again and then run vcl-test-client once more
|
||||
srvVppContainer.vppInstance.disconnect()
|
||||
stopVppCommand := "/bin/bash -c 'ps -C vpp_main -o pid= | xargs kill -9'"
|
||||
_, err = srvVppContainer.exec(stopVppCommand)
|
||||
s.assertNil(err)
|
||||
time.Sleep(5 * time.Second) // Give parent process time to reap the killed child process
|
||||
stopVppCommand = "/bin/bash -c 'ps -C hs-test -o pid= | xargs kill -9'"
|
||||
_, err = srvVppContainer.exec(stopVppCommand)
|
||||
s.assertNil(err)
|
||||
_, err = srvVppContainer.execAction("Configure2Veths srv-with-preset-hw-addr")
|
||||
s.assertNil(err)
|
||||
|
||||
s.setupServerVpp()
|
||||
|
||||
s.log("... VPP server is starting again, so waiting for a bit.")
|
||||
time.Sleep(30 * time.Second) // Wait a moment for the re-attachment to happen
|
||||
|
||||
s.log("... Running second echo client test, after disconnect and re-attachment.")
|
||||
_, err = echoClnContainer.execAction("RunVclEchoClient " + proto)
|
||||
o, err = echoClnContainer.exec(testClientCommand)
|
||||
s.log(o)
|
||||
s.assertNil(err)
|
||||
s.log("Done.")
|
||||
}
|
||||
@ -99,7 +105,8 @@ func (s *VethsSuite) TestTcpWithLoss() {
|
||||
err := serverVpp.start()
|
||||
s.assertNil(err, "starting VPP failed")
|
||||
|
||||
_, err = serverVpp.vppctl("test echo server uri tcp://10.10.10.1/20022")
|
||||
serverVeth := s.veths[serverInterfaceName]
|
||||
_, err = serverVpp.vppctl("test echo server uri tcp://%s/20022", serverVeth.Address())
|
||||
s.assertNil(err, "starting echo server failed")
|
||||
|
||||
clientContainer := s.getContainerByName("client-vpp")
|
||||
|
@ -4,6 +4,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/edwarnicke/exechelper"
|
||||
|
||||
"go.fd.io/govpp"
|
||||
"go.fd.io/govpp/api"
|
||||
"go.fd.io/govpp/binapi/af_packet"
|
||||
interfaces "go.fd.io/govpp/binapi/interface"
|
||||
"go.fd.io/govpp/binapi/interface_types"
|
||||
"go.fd.io/govpp/binapi/session"
|
||||
"go.fd.io/govpp/binapi/vpe"
|
||||
"go.fd.io/govpp/core"
|
||||
)
|
||||
|
||||
const vppConfigTemplate = `unix {
|
||||
@ -32,27 +41,34 @@ statseg {
|
||||
}
|
||||
|
||||
plugins {
|
||||
plugin default { disable }
|
||||
|
||||
plugin unittest_plugin.so { enable }
|
||||
plugin dpdk_plugin.so { disable }
|
||||
plugin crypto_aesni_plugin.so { enable }
|
||||
plugin quic_plugin.so { enable }
|
||||
plugin af_packet_plugin.so { enable }
|
||||
plugin hs_apps_plugin.so { enable }
|
||||
plugin http_plugin.so { enable }
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
const (
|
||||
defaultCliSocketFilePath = "/var/run/vpp/cli.sock"
|
||||
defaultApiSocketFilePath = "/var/run/vpp/api.sock"
|
||||
)
|
||||
|
||||
type VppInstance struct {
|
||||
container *Container
|
||||
config VppConfig
|
||||
config *VppConfig
|
||||
actionFuncName string
|
||||
connection *core.Connection
|
||||
apiChannel api.Channel
|
||||
}
|
||||
|
||||
type VppConfig struct {
|
||||
Variant string
|
||||
CliSocketFilePath string
|
||||
additionalConfig Stanza
|
||||
}
|
||||
|
||||
func (vc *VppConfig) getTemplate() string {
|
||||
@ -82,10 +98,22 @@ func (vpp *VppInstance) setCliSocket(filePath string) {
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) getCliSocket() string {
|
||||
return fmt.Sprintf("%s%s", vpp.container.workDir, vpp.config.CliSocketFilePath)
|
||||
return fmt.Sprintf("%s%s", vpp.container.GetContainerWorkDir(), vpp.config.CliSocketFilePath)
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) start() error {
|
||||
func (vpp *VppInstance) getRunDir() string {
|
||||
return vpp.container.GetContainerWorkDir() + "/var/run/vpp"
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) getLogDir() string {
|
||||
return vpp.container.GetContainerWorkDir() + "/var/log/vpp"
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) getEtcDir() string {
|
||||
return vpp.container.GetContainerWorkDir() + "/etc/vpp"
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) legacyStart() error {
|
||||
if vpp.actionFuncName == "" {
|
||||
return fmt.Errorf("vpp start failed: action function name must not be blank")
|
||||
}
|
||||
@ -99,14 +127,70 @@ func (vpp *VppInstance) start() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("vpp start failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) start() error {
|
||||
if vpp.actionFuncName != "" {
|
||||
return vpp.legacyStart()
|
||||
}
|
||||
|
||||
// Create folders
|
||||
containerWorkDir := vpp.container.GetContainerWorkDir()
|
||||
|
||||
vpp.container.exec("mkdir --mode=0700 -p " + vpp.getRunDir())
|
||||
vpp.container.exec("mkdir --mode=0700 -p " + vpp.getLogDir())
|
||||
vpp.container.exec("mkdir --mode=0700 -p " + vpp.getEtcDir())
|
||||
|
||||
// Create startup.conf inside the container
|
||||
configContent := fmt.Sprintf(vppConfigTemplate, containerWorkDir, vpp.config.CliSocketFilePath)
|
||||
configContent += vpp.config.additionalConfig.ToString()
|
||||
startupFileName := vpp.getEtcDir() + "/startup.conf"
|
||||
vpp.container.createFile(startupFileName, configContent)
|
||||
|
||||
// Start VPP
|
||||
if err := vpp.container.execServer("vpp -c " + startupFileName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Connect to VPP and store the connection
|
||||
sockAddress := vpp.container.GetHostWorkDir() + defaultApiSocketFilePath
|
||||
conn, connEv, err := govpp.AsyncConnect(
|
||||
sockAddress,
|
||||
core.DefaultMaxReconnectAttempts,
|
||||
core.DefaultReconnectInterval)
|
||||
if err != nil {
|
||||
fmt.Println("async connect error: ", err)
|
||||
}
|
||||
vpp.connection = conn
|
||||
|
||||
// ... wait for Connected event
|
||||
e := <-connEv
|
||||
if e.State != core.Connected {
|
||||
fmt.Println("connecting to VPP failed: ", e.Error)
|
||||
}
|
||||
|
||||
// ... check compatibility of used messages
|
||||
ch, err := conn.NewAPIChannel()
|
||||
if err != nil {
|
||||
fmt.Println("creating channel failed: ", err)
|
||||
}
|
||||
if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
|
||||
fmt.Println("compatibility error: ", err)
|
||||
}
|
||||
if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
|
||||
fmt.Println("compatibility error: ", err)
|
||||
}
|
||||
vpp.apiChannel = ch
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) vppctl(command string) (string, error) {
|
||||
cliExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
|
||||
vpp.container.name, vpp.getCliSocket(), command)
|
||||
output, err := exechelper.CombinedOutput(cliExecCommand)
|
||||
func (vpp *VppInstance) vppctl(command string, arguments ...any) (string, error) {
|
||||
vppCliCommand := fmt.Sprintf(command, arguments...)
|
||||
containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s",
|
||||
vpp.container.name, vpp.getCliSocket(), vppCliCommand)
|
||||
output, err := exechelper.CombinedOutput(containerExecCommand)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("vppctl failed: %s", err)
|
||||
}
|
||||
@ -115,7 +199,7 @@ func (vpp *VppInstance) vppctl(command string) (string, error) {
|
||||
}
|
||||
|
||||
func NewVppInstance(c *Container) *VppInstance {
|
||||
var vppConfig VppConfig
|
||||
vppConfig := new(VppConfig)
|
||||
vppConfig.CliSocketFilePath = defaultCliSocketFilePath
|
||||
vpp := new(VppInstance)
|
||||
vpp.container = c
|
||||
@ -123,7 +207,7 @@ func NewVppInstance(c *Container) *VppInstance {
|
||||
return vpp
|
||||
}
|
||||
|
||||
func serializeVppConfig(vppConfig VppConfig) (string, error) {
|
||||
func serializeVppConfig(vppConfig *VppConfig) (string, error) {
|
||||
serializedConfig, err := json.Marshal(vppConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("vpp start failed: serializing configuration failed: %s", err)
|
||||
@ -142,3 +226,87 @@ func deserializeVppConfig(input string) (VppConfig, error) {
|
||||
}
|
||||
return vppConfig, nil
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) createAfPacket(
|
||||
veth *NetworkInterfaceVeth,
|
||||
) (interface_types.InterfaceIndex, error) {
|
||||
createReq := &af_packet.AfPacketCreateV2{
|
||||
UseRandomHwAddr: true,
|
||||
HostIfName: veth.Name(),
|
||||
}
|
||||
if veth.hwAddress != (MacAddress{}) {
|
||||
createReq.UseRandomHwAddr = false
|
||||
createReq.HwAddr = veth.hwAddress
|
||||
}
|
||||
createReply := &af_packet.AfPacketCreateV2Reply{}
|
||||
|
||||
if err := vpp.apiChannel.SendRequest(createReq).ReceiveReply(createReply); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
veth.index = createReply.SwIfIndex
|
||||
|
||||
// Set to up
|
||||
upReq := &interfaces.SwInterfaceSetFlags{
|
||||
SwIfIndex: veth.index,
|
||||
Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
|
||||
}
|
||||
upReply := &interfaces.SwInterfaceSetFlagsReply{}
|
||||
|
||||
if err := vpp.apiChannel.SendRequest(upReq).ReceiveReply(upReply); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Add address
|
||||
if veth.ip4Address == (AddressWithPrefix{}) {
|
||||
ipPrefix, err := vpp.container.suite.NewAddress()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
veth.ip4Address = ipPrefix
|
||||
}
|
||||
addressReq := &interfaces.SwInterfaceAddDelAddress{
|
||||
IsAdd: true,
|
||||
SwIfIndex: veth.index,
|
||||
Prefix: veth.ip4Address,
|
||||
}
|
||||
addressReply := &interfaces.SwInterfaceAddDelAddressReply{}
|
||||
|
||||
if err := vpp.apiChannel.SendRequest(addressReq).ReceiveReply(addressReply); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return veth.index, nil
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) addAppNamespace(
|
||||
secret uint64,
|
||||
ifx interface_types.InterfaceIndex,
|
||||
namespaceId string,
|
||||
) error {
|
||||
req := &session.AppNamespaceAddDelV2{
|
||||
Secret: secret,
|
||||
SwIfIndex: ifx,
|
||||
NamespaceID: namespaceId,
|
||||
}
|
||||
reply := &session.AppNamespaceAddDelV2Reply{}
|
||||
|
||||
if err := vpp.apiChannel.SendRequest(req).ReceiveReply(reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sessionReq := &session.SessionEnableDisable{
|
||||
IsEnable: true,
|
||||
}
|
||||
sessionReply := &session.SessionEnableDisableReply{}
|
||||
|
||||
if err := vpp.apiChannel.SendRequest(sessionReq).ReceiveReply(sessionReply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vpp *VppInstance) disconnect() {
|
||||
vpp.connection.Disconnect()
|
||||
vpp.apiChannel.Close()
|
||||
}
|
||||
|
Reference in New Issue
Block a user