hs-test: configure VPP from test context 40/38040/8
authorMaros Ondrejicka <maros.ondrejicka@pantheon.tech>
Thu, 26 Jan 2023 09:07:29 +0000 (10:07 +0100)
committerFlorin Coras <florin.coras@gmail.com>
Thu, 9 Feb 2023 17:02:43 +0000 (17:02 +0000)
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

16 files changed:
extras/hs-test/actions.go
extras/hs-test/container.go
extras/hs-test/echo_test.go
extras/hs-test/framework_test.go
extras/hs-test/go.mod
extras/hs-test/go.sum
extras/hs-test/hst_suite.go [new file with mode: 0644]
extras/hs-test/http_test.go
extras/hs-test/ldp_test.go
extras/hs-test/netconfig.go
extras/hs-test/suite_veth_test.go
extras/hs-test/topo-containers/2peerVeth.yaml
extras/hs-test/topo-network/2peerVeth.yaml
extras/hs-test/utils.go
extras/hs-test/vcl_test.go
extras/hs-test/vppinstance.go

index 279589b..6039870 100644 (file)
@@ -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 {
index 971f3b6..91ca2c2 100644 (file)
@@ -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")
 }
index 813297c..1b24e08 100644 (file)
@@ -1,18 +1,19 @@
 package main
 
 func (s *VethsSuite) TestEchoBuiltin() {
-       serverContainer := s.getContainerByName("server-vpp")
-       _, err := serverContainer.execAction("Configure2Veths srv")
-       s.assertNil(err)
+       serverVpp := s.getContainerByName("server-vpp").vppInstance
+       serverVeth := s.veths["vppsrv"]
 
-       clientContainer := s.getContainerByName("client-vpp")
-       _, err = clientContainer.execAction("Configure2Veths cln")
+       _, err := serverVpp.vppctl("test echo server " +
+               " private-segment-size 1g fifo-size 4 no-echo" +
+               " uri tcp://" + serverVeth.Address() + "/1234")
        s.assertNil(err)
 
-       _, err = serverContainer.execAction("RunEchoSrvInternal private-segment-size 1g fifo-size 4 no-echo")
-       s.assertNil(err)
+       clientVpp := s.getContainerByName("client-vpp").vppInstance
 
-       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)
 }
index 76d8e58..6de8f16 100644 (file)
 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)
index 3b11dd2..0f6bffb 100644 (file)
@@ -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
 )
index e08a0d5..531153e 100644 (file)
@@ -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=
diff --git a/extras/hs-test/hst_suite.go b/extras/hs-test/hst_suite.go
new file mode 100644 (file)
index 0000000..9cf38d9
--- /dev/null
@@ -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
+}
index 28d27bb..52b7c39 100644 (file)
@@ -31,22 +31,18 @@ func (s *VethsSuite) TestHttpCli() {
        serverContainer := s.getContainerByName("server-vpp")
        clientContainer := s.getContainerByName("client-vpp")
 
-       _, err := serverContainer.execAction("Configure2Veths srv")
-       s.assertNil(err)
-
-       _, err = clientContainer.execAction("Configure2Veths cln")
-       s.assertNil(err)
-
-       s.log("configured IPs...")
+       serverVeth := s.veths["vppsrv"]
 
-       _, err = serverContainer.execAction("RunHttpCliSrv")
+       _, err := serverContainer.vppInstance.vppctl("http cli server")
        s.assertNil(err)
 
-       s.log("configured http server")
+       uri := "http://" + serverVeth.Address() + "/80"
 
-       o, err := clientContainer.execAction("RunHttpCliCln /show/version")
+       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!")
 }
 
index 2dfdf8b..cbba227 100644 (file)
@@ -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").
index 46f23c0..b93e460 100644 (file)
@@ -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
+}
+
+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
 }
 
-type NetTopology []NetConfig
+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 {
index 5276072..81a21a2 100644 (file)
@@ -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)
+}
index 246e5ca..72af1c9 100644 (file)
@@ -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
 
index e62e173..9a966dc 100644 (file)
@@ -5,6 +5,7 @@ devices:
 
   - name: "vppsrv"
     type: "veth"
+    preset-hw-address: "00:00:5e:00:53:01"
     peer:
       name: "vppsrv_veth"
       netns: "hsns"
index 1338498..cf30ece 100644 (file)
@@ -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
index f4273c8..6c809f4 100644 (file)
@@ -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")
+       srvVppContainer := s.getContainerCopyByName("server-vpp")
 
-       _, err := srvVppContainer.execAction("Configure2Veths srv-with-preset-hw-addr")
-       s.assertNil(err)
+       echoSrvContainer := s.getContainerByName("server-application")
 
-       clnVppContainer := s.getContainerByName("client-vpp")
+       serverVclConfContent := fmt.Sprintf(vclTemplate, echoSrvContainer.GetContainerWorkDir(), "1")
+       echoSrvContainer.createFile("/vcl.conf", serverVclConfContent)
 
-       _, err = clnVppContainer.execAction("Configure2Veths cln")
-       s.assertNil(err)
-
-       echoSrvContainer := s.getContainerByName("server-application")
-       _, err = echoSrvContainer.execAction("RunVclEchoServer " + proto)
+       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")
index 14d6ab6..3f9ea87 100644 (file)
@@ -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()
+}