misc: add test framework for host stack 29/36829/7
authorFilip Tehlar <ftehlar@cisco.com>
Tue, 9 Aug 2022 14:44:47 +0000 (14:44 +0000)
committerFlorin Coras <florin.coras@gmail.com>
Mon, 19 Sep 2022 21:00:18 +0000 (21:00 +0000)
Type: feature

Signed-off-by: Filip Tehlar <ftehlar@cisco.com>
Change-Id: I5a64a2c095cae3a4d5f8fdc73e624b010339ec8e

27 files changed:
extras/hs-test/Dockerfile.vpp [new file with mode: 0755]
extras/hs-test/Makefile [new file with mode: 0755]
extras/hs-test/README [new file with mode: 0755]
extras/hs-test/actions.go [new file with mode: 0755]
extras/hs-test/echo_test.go [new file with mode: 0755]
extras/hs-test/envoy/envoy.log [new file with mode: 0755]
extras/hs-test/envoy/proxy.yaml [new file with mode: 0755]
extras/hs-test/envoy/vcl.conf [new file with mode: 0755]
extras/hs-test/framework_test.go [new file with mode: 0755]
extras/hs-test/go.mod [new file with mode: 0755]
extras/hs-test/go.sum [new file with mode: 0755]
extras/hs-test/ldp_test.go [new file with mode: 0755]
extras/hs-test/linux_iperf_test.go [new file with mode: 0755]
extras/hs-test/main.go [new file with mode: 0755]
extras/hs-test/netconfig.go [new file with mode: 0755]
extras/hs-test/proxy_test.go [new file with mode: 0755]
extras/hs-test/script/build.sh [new file with mode: 0755]
extras/hs-test/test [new file with mode: 0755]
extras/hs-test/tools/http_server/http_server.go [new file with mode: 0755]
extras/hs-test/topo.go [new file with mode: 0755]
extras/hs-test/topo/2peerVeth.yaml [new file with mode: 0755]
extras/hs-test/topo/ns.yaml [new file with mode: 0755]
extras/hs-test/topo/tap.yaml [new file with mode: 0755]
extras/hs-test/tps_test.go [new file with mode: 0755]
extras/hs-test/utils.go [new file with mode: 0755]
extras/hs-test/vars [new file with mode: 0755]
extras/hs-test/vcl_test.go [new file with mode: 0755]

diff --git a/extras/hs-test/Dockerfile.vpp b/extras/hs-test/Dockerfile.vpp
new file mode 100755 (executable)
index 0000000..9257787
--- /dev/null
@@ -0,0 +1,19 @@
+FROM ubuntu:22.04
+
+RUN apt-get update \
+ && apt-get install -y openssl libapr1 libnuma1 libsubunit0 \
+    iproute2 libnl-3-dev libnl-route-3-dev python3 iputils-ping  \
+    vim gdb \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY vpp-data/lib/vat2_plugins/ /usr/lib/vat2_plugins/
+COPY vpp-data/lib/vpp_api_test_plugins/ /usr/lib/vpp_api_test_plugins/
+COPY vpp-data/lib/vpp_plugins/ /usr/lib/x86_64-linux-gnu/vpp_plugins/
+COPY vpp-data/bin/* /usr/bin/
+COPY vpp-data/lib/* /usr/lib/
+
+COPY hs-test /hs-test
+
+RUN addgroup vpp
+
+ENTRYPOINT ["tail", "-f", "/dev/null"]
diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile
new file mode 100755 (executable)
index 0000000..9026555
--- /dev/null
@@ -0,0 +1,10 @@
+all: build docker
+
+build:
+       go build ./tools/http_server
+       go build .
+
+docker:
+       bash ./script/build.sh
+
+.PHONY: docker
diff --git a/extras/hs-test/README b/extras/hs-test/README
new file mode 100755 (executable)
index 0000000..06b2ca6
--- /dev/null
@@ -0,0 +1,9 @@
+Host stack test framework
+-------------------------
+
+For building docker image run `make` first and `./test` to run all the tests.
+`./test` script is basically a wrapper for `go test` and accepts its parameters,
+for example following runs a specific test: `./test -run Veth/EchoBuilt`.
+
+Root privileges is required to run tests as it uses linux `ip` command for
+configuring topology.
diff --git a/extras/hs-test/actions.go b/extras/hs-test/actions.go
new file mode 100755 (executable)
index 0000000..aa82f49
--- /dev/null
@@ -0,0 +1,268 @@
+package main
+
+import (
+       "bytes"
+       "context"
+       "fmt"
+       "os"
+       "path/filepath"
+
+       "git.fd.io/govpp.git/api"
+       "github.com/edwarnicke/exechelper"
+       "github.com/edwarnicke/govpp/binapi/af_packet"
+       interfaces "github.com/edwarnicke/govpp/binapi/interface"
+       "github.com/edwarnicke/govpp/binapi/interface_types"
+       ip_types "github.com/edwarnicke/govpp/binapi/ip_types"
+       "github.com/edwarnicke/govpp/binapi/session"
+       "github.com/edwarnicke/govpp/binapi/vlib"
+       "github.com/edwarnicke/vpphelper"
+)
+
+func RegisterActions() {
+       cfgTable = make(map[string]func([]string) *ActionResult)
+       reg("echo-srv-internal", Configure2Veths)
+       reg("echo-cln-internal", Configure2Veths)
+       reg("echo-client", RunEchoClient)
+       reg("echo-server", RunEchoServer)
+       reg("vpp-proxy", ConfigureVppProxy)
+       reg("vpp-envoy", ConfigureEnvoyProxy)
+       reg("http-tps", ConfigureHttpTps)
+       reg("2veths", Configure2Veths)
+}
+
+func configureProxyTcp(ifName0, ipAddr0, ifName1, ipAddr1 string) ConfFn {
+       return func(ctx context.Context,
+               vppConn api.Connection) error {
+
+               _, err := configureAfPacket(ctx, vppConn, ifName0, ipAddr0)
+               if err != nil {
+                       fmt.Printf("failed to create af packet: %v", err)
+                       return err
+               }
+               _, err = configureAfPacket(ctx, vppConn, ifName1, ipAddr1)
+               if err != nil {
+                       fmt.Printf("failed to create af packet: %v", err)
+                       return err
+               }
+               return nil
+       }
+}
+
+func ConfigureVppProxy(args []string) *ActionResult {
+       ctx, cancel := newVppContext()
+       defer cancel()
+
+       con, vppErrCh := vpphelper.StartAndDialContext(ctx, vpphelper.WithVppConfig(configTemplate))
+       exitOnErrCh(ctx, cancel, vppErrCh)
+
+       confFn := configureProxyTcp("vpp0", "10.0.0.2/24", "vpp1", "10.0.1.2/24")
+       err := confFn(ctx, con)
+       if err != nil {
+               return NewActionResult(err, ActionResultWithDesc("configuration failed"))
+       }
+       writeSyncFile(OkResult())
+       <-ctx.Done()
+       return nil
+}
+
+func ConfigureEnvoyProxy(args []string) *ActionResult {
+       var startup Stanza
+       startup.
+               NewStanza("session").
+               Append("enable").
+               Append("use-app-socket-api").
+               Append("evt_qs_memfd_seg").
+               Append("event-queue-length 100000").Close()
+       ctx, cancel := newVppContext()
+       defer cancel()
+
+       con, vppErrCh := vpphelper.StartAndDialContext(ctx,
+               vpphelper.WithVppConfig(configTemplate+startup.ToString()),
+               vpphelper.WithRootDir("/tmp/vpp-envoy"))
+       exitOnErrCh(ctx, cancel, vppErrCh)
+
+       confFn := configureProxyTcp("vpp0", "10.0.0.2/24", "vpp1", "10.0.1.2/24")
+       err := confFn(ctx, con)
+       if err != nil {
+               return NewActionResult(err, ActionResultWithDesc("configuration failed"))
+       }
+       err0 := exechelper.Run("chmod 777 -R /tmp/vpp-envoy")
+       if err0 != nil {
+               return NewActionResult(err, ActionResultWithDesc("setting permissions failed"))
+       }
+       writeSyncFile(OkResult())
+       <-ctx.Done()
+       return nil
+}
+
+func getArgs() string {
+       s := ""
+       for i := 2; i < len(os.Args); i++ {
+               s = s + " " + os.Args[i]
+       }
+       return s
+}
+
+func ApiCliInband(root, cmd string) *ActionResult {
+       ctx, _ := newVppContext()
+       con := vpphelper.DialContext(ctx, filepath.Join(root, "/var/run/vpp/api.sock"))
+       cliInband := vlib.CliInband{Cmd: cmd}
+       cliInbandReply, err := vlib.NewServiceClient(con).CliInband(ctx, &cliInband)
+       return NewActionResult(err, ActionResultWithStdout(cliInbandReply.Reply))
+}
+
+func RunEchoClient(args []string) *ActionResult {
+       outBuff := bytes.NewBuffer([]byte{})
+       errBuff := bytes.NewBuffer([]byte{})
+
+       cmd := fmt.Sprintf("vpp_echo client socket-name /tmp/echo-cln/var/run/app_ns_sockets/2 use-app-socket-api uri %s://10.10.10.1/12344", 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 RunEchoServer(args []string) *ActionResult {
+       cmd := fmt.Sprintf("vpp_echo server TX=RX socket-name /tmp/echo-srv/var/run/app_ns_sockets/1 use-app-socket-api uri %s://10.10.10.1/12344", args[2])
+       errCh := exechelper.Start(cmd)
+       select {
+       case err := <-errCh:
+               writeSyncFile(NewActionResult(err, ActionResultWithDesc("echo_server: ")))
+       default:
+       }
+       writeSyncFile(OkResult())
+       return nil
+}
+
+func RunEchoSrvInternal() *ActionResult {
+       cmd := fmt.Sprintf("test echo server %s uri tcp://10.10.10.1/1234", getArgs())
+       return ApiCliInband("/tmp/2veths", cmd)
+}
+
+func RunEchoClnInternal() *ActionResult {
+       cmd := fmt.Sprintf("test echo client %s uri tcp://10.10.10.1/1234", getArgs())
+       return ApiCliInband("/tmp/2veths", cmd)
+}
+func configure2vethsTopo(ifName, interfaceAddress, namespaceId string, secret uint64) ConfFn {
+       return func(ctx context.Context,
+               vppConn api.Connection) error {
+
+               swIfIndex, err := configureAfPacket(ctx, vppConn, ifName, interfaceAddress)
+               if err != nil {
+                       fmt.Printf("failed to create af packet: %v", err)
+               }
+               _, er := session.NewServiceClient(vppConn).AppNamespaceAddDelV2(ctx, &session.AppNamespaceAddDelV2{
+                       Secret:      secret,
+                       SwIfIndex:   swIfIndex,
+                       NamespaceID: namespaceId,
+               })
+               if er != nil {
+                       fmt.Printf("add app namespace: %v", err)
+                       return err
+               }
+
+               _, er1 := session.NewServiceClient(vppConn).SessionEnableDisable(ctx, &session.SessionEnableDisable{
+                       IsEnable: true,
+               })
+               if er1 != nil {
+                       fmt.Printf("session enable %v", err)
+                       return err
+               }
+               return nil
+       }
+}
+
+func Configure2Veths(args []string) *ActionResult {
+       var startup Stanza
+       startup.
+               NewStanza("session").
+               Append("enable").
+               Append("use-app-socket-api").Close()
+
+       ctx, cancel := newVppContext()
+       defer cancel()
+       con, vppErrCh := vpphelper.StartAndDialContext(ctx,
+               vpphelper.WithVppConfig(configTemplate+startup.ToString()),
+               vpphelper.WithRootDir(fmt.Sprintf("/tmp/%s", args[1])))
+       exitOnErrCh(ctx, cancel, vppErrCh)
+
+       var fn func(context.Context, api.Connection) error
+       if args[2] == "srv" {
+               fn = configure2vethsTopo("vppsrv", "10.10.10.1/24", "1", 1)
+       } else {
+               fn = configure2vethsTopo("vppcln", "10.10.10.2/24", "2", 2)
+       }
+       err := fn(ctx, con)
+       if err != nil {
+               return NewActionResult(err, ActionResultWithDesc("configuration failed"))
+       }
+       writeSyncFile(OkResult())
+       <-ctx.Done()
+       return nil
+}
+
+func configureAfPacket(ctx context.Context, vppCon api.Connection,
+       name, interfaceAddress string) (interface_types.InterfaceIndex, error) {
+       ifaceClient := interfaces.NewServiceClient(vppCon)
+       afPacketCreate := &af_packet.AfPacketCreateV2{
+               UseRandomHwAddr: true,
+               HostIfName:      name,
+               NumRxQueues:     1,
+       }
+       afPacketCreateRsp, err := af_packet.NewServiceClient(vppCon).AfPacketCreateV2(ctx, afPacketCreate)
+       if err != nil {
+               fmt.Printf("failed to create af packet: %v", err)
+               return 0, err
+       }
+       _, err = ifaceClient.SwInterfaceSetFlags(ctx, &interfaces.SwInterfaceSetFlags{
+               SwIfIndex: afPacketCreateRsp.SwIfIndex,
+               Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
+       })
+       if err != nil {
+               fmt.Printf("set interface state up failed: %v\n", err)
+               return 0, err
+       }
+       ipPrefix, err := ip_types.ParseAddressWithPrefix(interfaceAddress)
+       if err != nil {
+               fmt.Printf("parse ip address %v\n", err)
+               return 0, err
+       }
+       ipAddress := &interfaces.SwInterfaceAddDelAddress{
+               IsAdd:     true,
+               SwIfIndex: afPacketCreateRsp.SwIfIndex,
+               Prefix:    ipPrefix,
+       }
+       _, errx := ifaceClient.SwInterfaceAddDelAddress(ctx, ipAddress)
+       if errx != nil {
+               fmt.Printf("add ip address %v\n", err)
+               return 0, err
+       }
+       return afPacketCreateRsp.SwIfIndex, nil
+}
+
+func ConfigureHttpTps(args []string) *ActionResult {
+       ctx, cancel := newVppContext()
+       defer cancel()
+       con, vppErrCh := vpphelper.StartAndDialContext(ctx,
+               vpphelper.WithVppConfig(configTemplate))
+       exitOnErrCh(ctx, cancel, vppErrCh)
+
+       confFn := configureProxyTcp("vpp0", "10.0.0.2/24", "vpp1", "10.0.1.2/24")
+       err := confFn(ctx, con)
+       if err != nil {
+               return NewActionResult(err, ActionResultWithDesc("configuration failed"))
+       }
+
+       _, err = session.NewServiceClient(con).SessionEnableDisable(ctx, &session.SessionEnableDisable{
+               IsEnable: true,
+       })
+       if err != nil {
+               return NewActionResult(err, ActionResultWithDesc("configuration failed"))
+       }
+       Vppcli("", "http tps uri tcp://0.0.0.0/8080")
+       writeSyncFile(OkResult())
+       <-ctx.Done()
+       return nil
+}
diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go
new file mode 100755 (executable)
index 0000000..55f7500
--- /dev/null
@@ -0,0 +1,51 @@
+package main
+
+import (
+       "fmt"
+
+       "github.com/edwarnicke/exechelper"
+)
+
+func (s *Veths2Suite) TestEchoBuiltin() {
+       t := s.T()
+       srvInstance := "echo-srv-internal"
+       clnInstance := "echo-cln-internal"
+       err := dockerRun(srvInstance, "")
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + srvInstance) }()
+
+       err = dockerRun(clnInstance, "")
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + clnInstance) }()
+
+       _, err = hstExec("2veths srv", srvInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       _, err = hstExec("2veths cln", clnInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       _, err = hstExec("echo-srv-internal private-segment-size 1g fifo-size 4 no-echo", srvInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       o, err := hstExec("echo-cln-internal nclients 10000 bytes 1 syn-timeout 100 test-timeout 100 no-return private-segment-size 1g fifo-size 4", clnInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       fmt.Println(o)
+}
diff --git a/extras/hs-test/envoy/envoy.log b/extras/hs-test/envoy/envoy.log
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/extras/hs-test/envoy/proxy.yaml b/extras/hs-test/envoy/proxy.yaml
new file mode 100755 (executable)
index 0000000..e4a5b81
--- /dev/null
@@ -0,0 +1,52 @@
+admin:
+  access_log_path: /tmp/envoy.log
+  address:
+    socket_address:
+      address: 0.0.0.0
+      port_value: 8081
+static_resources:
+  listeners:
+  # define a reverse proxy on :10001 that always uses :80 as an origin.
+  - address:
+      socket_address:
+        protocol: TCP
+        address: 0.0.0.0
+        port_value: 555
+    filter_chains:
+    - filters:
+      - name: envoy.filters.network.http_connection_manager
+        typed_config:
+          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+          stat_prefix: ingress_http
+          route_config:
+            name: local_route
+            virtual_hosts:
+            - name: service
+              domains: ["*"]
+              routes:
+              - match:
+                  prefix: "/"
+                route:
+                  cluster: proxy_service
+          http_filters:
+          - name: envoy.filters.http.router
+  clusters:
+  - name: proxy_service
+    connect_timeout: 0.25s
+    type: LOGICAL_DNS
+    dns_lookup_family: V4_ONLY
+    lb_policy: ROUND_ROBIN
+    load_assignment:
+      cluster_name: proxy_service
+      endpoints:
+      - lb_endpoints:
+        - endpoint:
+            address:
+              socket_address:
+                address: 10.0.1.1
+                port_value: 666
+bootstrap_extensions:
+  - name: envoy.extensions.vcl.vcl_socket_interface
+    typed_config:
+      "@type": type.googleapis.com/envoy.extensions.vcl.v3alpha.VclSocketInterface
+default_socket_interface: "envoy.extensions.vcl.vcl_socket_interface"
diff --git a/extras/hs-test/envoy/vcl.conf b/extras/hs-test/envoy/vcl.conf
new file mode 100755 (executable)
index 0000000..164435a
--- /dev/null
@@ -0,0 +1,7 @@
+vcl {
+  rx-fifo-size 400000
+  tx-fifo-size 400000
+  app-scope-global
+  use-mq-eventfd
+  app-socket-api /tmp/vpp-envoy/var/run/app_ns_sockets/default
+}
diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go
new file mode 100755 (executable)
index 0000000..78a2892
--- /dev/null
@@ -0,0 +1,82 @@
+package main
+
+import (
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/suite"
+)
+
+type TapSuite struct {
+       suite.Suite
+       teardownSuite func()
+}
+
+func (s *TapSuite) SetupSuite() {
+       time.Sleep(1 * time.Second)
+       s.teardownSuite = setupSuite(&s.Suite, "tap")
+}
+
+func (s *TapSuite) TearDownSuite() {
+       s.teardownSuite()
+}
+
+type Veths2Suite struct {
+       suite.Suite
+       teardownSuite func()
+}
+
+func (s *Veths2Suite) SetupSuite() {
+       time.Sleep(1 * time.Second)
+       s.teardownSuite = setupSuite(&s.Suite, "2peerVeth")
+}
+
+func (s *Veths2Suite) TearDownSuite() {
+       s.teardownSuite()
+}
+
+type NsSuite struct {
+       suite.Suite
+       teardownSuite func()
+}
+
+func (s *NsSuite) SetupSuite() {
+       s.teardownSuite = setupSuite(&s.Suite, "ns")
+}
+
+func (s *NsSuite) TearDownSuite() {
+       s.teardownSuite()
+}
+
+func setupSuite(s *suite.Suite, topologyName string) func() {
+       t := s.T()
+       topology, err := LoadTopology(TopologyDir, topologyName)
+       if err != nil {
+               t.Fatalf("error on loading topology '%s': %v", topologyName, err)
+       }
+       err = topology.Configure()
+       if err != nil {
+               t.Fatalf("failed to configure %s: %v", topologyName, err)
+       }
+
+       t.Logf("topo %s loaded", topologyName)
+       return func() {
+               topology.Unconfigure()
+       }
+}
+
+func TestTapSuite(t *testing.T) {
+       var m TapSuite
+       suite.Run(t, &m)
+}
+
+func TestNs(t *testing.T) {
+       var m NsSuite
+       suite.Run(t, &m)
+}
+
+func TestVeths2(t *testing.T) {
+       var m Veths2Suite
+       suite.Run(t, &m)
+
+}
diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod
new file mode 100755 (executable)
index 0000000..0674b58
--- /dev/null
@@ -0,0 +1,30 @@
+module fd.io/hs-test
+
+go 1.18
+
+require (
+       git.fd.io/govpp.git v0.4.0
+       github.com/edwarnicke/exechelper v1.0.2
+       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
+       gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
+)
+
+require (
+       github.com/davecgh/go-spew v1.1.1 // indirect
+       github.com/edwarnicke/log v1.0.0 // indirect
+       github.com/fsnotify/fsnotify v1.4.9 // indirect
+       github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
+       github.com/kr/text v0.2.0 // indirect
+       github.com/lunixbochs/struc v0.0.0-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
+       gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+       gopkg.in/fsnotify.v1 v1.4.7 // indirect
+)
diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum
new file mode 100755 (executable)
index 0000000..13cd56c
--- /dev/null
@@ -0,0 +1,143 @@
+git.fd.io/govpp.git v0.3.6-0.20210202134006-4c1cccf48cd1/go.mod h1:OCVd4W8SH+666KRQoMj6PM+oipLDZAHhqMz9B1TGbgI=
+git.fd.io/govpp.git v0.3.6-0.20210927044411-385ccc0d8ba9/go.mod h1:OCVd4W8SH+666KRQoMj6PM+oipLDZAHhqMz9B1TGbgI=
+git.fd.io/govpp.git v0.4.0 h1:u/hxo5rwTpwmR8ambm5Xtf1WXEeDyoYOrD2m8TKcD34=
+git.fd.io/govpp.git v0.4.0/go.mod h1:OCVd4W8SH+666KRQoMj6PM+oipLDZAHhqMz9B1TGbgI=
+github.com/bennyscetbun/jsongo v1.1.0/go.mod h1:suxbVmjBV8+A2BBAM5EYVh6Uj8j3rqJhzWf3hv7Ff8U=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/edwarnicke/exechelper v1.0.2 h1:dD49Ui2U0FBFxxhalnKw6vLS0P0TkgnXBRvKL/xmC5w=
+github.com/edwarnicke/exechelper v1.0.2/go.mod h1:/T271jtNX/ND4De6pa2aRy2+8sNtyCDB1A2pp4M+fUs=
+github.com/edwarnicke/govpp v0.0.0-20220311182453-f32f292e0e91 h1:iDVzIaYZjTg+hQGcDeOLJZc+5VfKvW7ye54EFX3CRQA=
+github.com/edwarnicke/govpp v0.0.0-20220311182453-f32f292e0e91/go.mod h1:kHDnxA+SSNFeMEHz7xvhub1zvx4mOTRlWWRCay2n5NM=
+github.com/edwarnicke/log v1.0.0 h1:T6uRNCmR99GTt/CpRr2Gz8eGW8fm0HMThDNGdNxPaGk=
+github.com/edwarnicke/log v1.0.0/go.mod h1:eWsQQlQ0IU5wHlJvyXFH3dS8s2g9GzN7JnXodo6yaIY=
+github.com/edwarnicke/vpphelper v0.0.0-20210617172001-3e6797de32c3 h1:BGWyFzJ9QfXOd0jzTe08d98KOXus/3KS4aSgrZKTGx8=
+github.com/edwarnicke/vpphelper v0.0.0-20210617172001-3e6797de32c3/go.mod h1:2KXgJqOUUCh9S4Zs2jAycQLqAcRGge3q+GXV2rN55ZQ=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+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=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lunixbochs/struc v0.0.0-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/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=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+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=
+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/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=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+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=
diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go
new file mode 100755 (executable)
index 0000000..0783b18
--- /dev/null
@@ -0,0 +1,114 @@
+package main
+
+import (
+       "fmt"
+       "os"
+       "time"
+
+       "github.com/edwarnicke/exechelper"
+)
+
+func (s *Veths2Suite) TestLDPreloadIperfVpp() {
+       t := s.T()
+       var clnVclConf, srvVclConf Stanza
+
+       srvInstance := "vpp-ldp-srv"
+       clnInstance := "vpp-ldp-cln"
+       srvPath := "/tmp/" + srvInstance
+       clnPath := "/tmp/" + clnInstance
+       srvVcl := srvPath + "/vcl_srv.conf"
+       clnVcl := clnPath + "/vcl_cln.conf"
+
+       exechelper.Run("mkdir " + srvPath)
+       exechelper.Run("mkdir " + clnPath)
+
+       ldpreload := os.Getenv("HST_LDPRELOAD")
+       s.Assert().NotEqual("", ldpreload)
+
+       ldpreload = "LD_PRELOAD=" + ldpreload
+
+       stopServerCh := make(chan struct{}, 1)
+       srvCh := make(chan error, 1)
+       clnCh := make(chan error)
+
+       fmt.Println("starting VPPs")
+
+       err := dockerRun(srvInstance, fmt.Sprintf("-v /tmp/%s:/tmp", srvInstance))
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + srvInstance) }()
+
+       err = dockerRun(clnInstance, fmt.Sprintf("-v /tmp/%s:/tmp", clnInstance))
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + clnInstance) }()
+
+       _, err = hstExec("2veths srv", srvInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       _, err = hstExec("2veths cln", clnInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       err = clnVclConf.
+               NewStanza("vcl").
+               Append("rx-fifo-size 4000000").
+               Append("tx-fifo-size 4000000").
+               Append("app-scope-local").
+               Append("app-scope-global").
+               Append("use-mq-eventfd").
+               Append(fmt.Sprintf("app-socket-api /tmp/%s/2veths/var/run/app_ns_sockets/2", clnInstance)).Close().
+               SaveToFile(clnVcl)
+       if err != nil {
+               t.Errorf("%v", err)
+               t.FailNow()
+       }
+
+       err = srvVclConf.
+               NewStanza("vcl").
+               Append("rx-fifo-size 4000000").
+               Append("tx-fifo-size 4000000").
+               Append("app-scope-local").
+               Append("app-scope-global").
+               Append("use-mq-eventfd").
+               Append(fmt.Sprintf("app-socket-api /tmp/%s/2veths/var/run/app_ns_sockets/1", srvInstance)).Close().
+               SaveToFile(srvVcl)
+       if err != nil {
+               t.Errorf("%v", err)
+               t.FailNow()
+       }
+       fmt.Printf("attaching server to vpp")
+
+       // FIXME
+       time.Sleep(5 * time.Second)
+
+       srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+srvVcl)
+       go StartServerApp(srvCh, stopServerCh, srvEnv)
+
+       err = <-srvCh
+       if err != nil {
+               s.FailNow("vcl server", "%v", err)
+       }
+
+       fmt.Println("attaching client to vpp")
+       clnEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+clnVcl)
+       go StartClientApp(clnEnv, clnCh)
+
+       // wait for client's result
+       err = <-clnCh
+       if err != nil {
+               s.Failf("client", "%v", err)
+       }
+
+       // stop server
+       stopServerCh <- struct{}{}
+}
diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go
new file mode 100755 (executable)
index 0000000..92a85cf
--- /dev/null
@@ -0,0 +1,26 @@
+package main
+
+func (s *TapSuite) TestLinuxIperf() {
+       t := s.T()
+       clnCh := make(chan error)
+       stopServerCh := make(chan struct{})
+       srvCh := make(chan error, 1)
+       defer func() {
+               stopServerCh <- struct{}{}
+       }()
+
+       go StartServerApp(srvCh, stopServerCh, nil)
+       err := <-srvCh
+       if err != nil {
+               t.Errorf("%v", err)
+               t.FailNow()
+       }
+       t.Log("server running")
+       go StartClientApp(nil, clnCh)
+       t.Log("client running")
+       err = <-clnCh
+       if err != nil {
+               s.Failf("client", "%v", err)
+       }
+       t.Log("Test completed")
+}
diff --git a/extras/hs-test/main.go b/extras/hs-test/main.go
new file mode 100755 (executable)
index 0000000..1014917
--- /dev/null
@@ -0,0 +1,155 @@
+package main
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "os"
+       "os/exec"
+       "os/signal"
+
+       "git.fd.io/govpp.git/api"
+)
+
+type CfgTable map[string]func([]string) *ActionResult
+
+var cfgTable CfgTable
+
+type ConfFn func(context.Context, api.Connection) error
+
+func newVppContext() (context.Context, context.CancelFunc) {
+       ctx, cancel := signal.NotifyContext(
+               context.Background(),
+               os.Interrupt,
+       )
+       return ctx, cancel
+}
+
+func Vppcli(runDir, command string) (string, error) {
+       cmd := exec.Command("vppctl", "-s", fmt.Sprintf("%s/var/run/vpp/cli.sock", runDir), command)
+       o, err := cmd.CombinedOutput()
+       if err != nil {
+               fmt.Printf("failed to execute command: '%v'.\n", err)
+       }
+       fmt.Printf("Command output %s", string(o))
+       return string(o), err
+}
+
+func exitOnErrCh(ctx context.Context, cancel context.CancelFunc, errCh <-chan error) {
+       // If we already have an error, log it and exit
+       select {
+       case err := <-errCh:
+               fmt.Printf("%v", err)
+       default:
+       }
+       go func(ctx context.Context, errCh <-chan error) {
+               <-errCh
+               cancel()
+       }(ctx, errCh)
+}
+
+func writeSyncFile(res *ActionResult) error {
+       syncFile := "/tmp/sync/rc"
+
+       var jsonRes JsonResult
+
+       jsonRes.ErrOutput = res.ErrOutput
+       jsonRes.StdOutput = res.StdOutput
+       if res.Err != nil {
+               jsonRes.Code = 1
+               jsonRes.Desc = fmt.Sprintf("%s :%v", res.Desc, res.Err)
+       } else {
+               jsonRes.Code = 0
+       }
+
+       str, err := json.Marshal(jsonRes)
+       if err != nil {
+               return fmt.Errorf("error marshaling json result data! %v", err)
+       }
+
+       _, err = os.Open(syncFile)
+       if err != nil {
+               // expecting the file does not exist
+               f, e := os.Create(syncFile)
+               if e != nil {
+                       return fmt.Errorf("failed to open sync file")
+               }
+               defer f.Close()
+               f.Write([]byte(str))
+       } else {
+               return fmt.Errorf("sync file exists, delete the file frst")
+       }
+       return nil
+}
+
+func NewActionResult(err error, opts ...ActionResultOptionFn) *ActionResult {
+       res := &ActionResult{
+               Err: err,
+       }
+       for _, o := range opts {
+               o(res)
+       }
+       return res
+}
+
+type ActionResultOptionFn func(res *ActionResult)
+
+func ActionResultWithDesc(s string) ActionResultOptionFn {
+       return func(res *ActionResult) {
+               res.Desc = s
+       }
+}
+
+func ActionResultWithStderr(s string) ActionResultOptionFn {
+       return func(res *ActionResult) {
+               res.ErrOutput = s
+       }
+}
+
+func ActionResultWithStdout(s string) ActionResultOptionFn {
+       return func(res *ActionResult) {
+               res.ErrOutput = s
+       }
+}
+
+func OkResult() *ActionResult {
+       return NewActionResult(nil)
+}
+
+func reg(key string, fn func([]string) *ActionResult) {
+       cfgTable[key] = fn
+}
+
+func processArgs() *ActionResult {
+       fn := cfgTable[os.Args[1]]
+       if fn == nil {
+               return NewActionResult(fmt.Errorf("internal: no config found for %s", os.Args[1]))
+       }
+       return fn(os.Args)
+}
+
+func main() {
+       if len(os.Args) == 0 {
+               fmt.Println("args required")
+               return
+       }
+
+       if os.Args[1] == "rm" {
+               topology, err := LoadTopology(TopologyDir, os.Args[2])
+               if err != nil {
+                       fmt.Printf("falied to load topologies: %v\n", err)
+                       os.Exit(1)
+               }
+               topology.Unconfigure()
+               os.Exit(0)
+       }
+
+       RegisterActions()
+
+       var err error
+       res := processArgs()
+       err = writeSyncFile(res)
+       if err != nil {
+               fmt.Printf("failed to write to sync file: %v\n", err)
+       }
+}
diff --git a/extras/hs-test/netconfig.go b/extras/hs-test/netconfig.go
new file mode 100755 (executable)
index 0000000..f3f3c1b
--- /dev/null
@@ -0,0 +1,283 @@
+package main
+
+import (
+       "errors"
+       "fmt"
+       "os/exec"
+)
+
+type NetType string
+
+const (
+       NetNs NetType = "netns"
+       Veth          = "veth"
+       Tap           = "tap"
+)
+
+type NetConfig struct {
+       Configure   func() error
+       Unconfigure func()
+}
+
+type NetTopology []NetConfig
+
+func (t *NetTopology) Configure() error {
+       for _, c := range *t {
+               err := c.Configure()
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (t *NetTopology) Unconfigure() {
+       for _, c := range *t {
+               c.Unconfigure()
+       }
+}
+
+func newConfigFn(cfg NetDevConfig) func() error {
+       t := cfg["type"]
+       if t == "netns" {
+               return func() error { return AddNetns(cfg["name"].(string)) }
+       } else if t == "veth" {
+               return func() error {
+                       var peerNs string
+                       peer := cfg["peer"].(NetDevConfig)
+                       peerName := peer["name"].(string)
+                       err := AddVethPair(cfg["name"].(string), peerName)
+                       if err != nil {
+                               return err
+                       }
+
+                       if peer["netns"] != nil {
+                               peerNs = peer["netns"].(string)
+                               if peerNs != "" {
+                                       err := LinkSetNetns(peerName, peerNs)
+                                       if err != nil {
+                                               return err
+                                       }
+                               }
+                       }
+                       if peer["ip4"] != nil {
+                               err = AddAddress(peerName, peer["ip4"].(string), peerNs)
+                               if err != nil {
+                                       return fmt.Errorf("failed to add configure address for %s: %v", peerName, err)
+                               }
+                       }
+                       return nil
+               }
+       } else if t == "bridge" {
+               return func() error { return configureBridge(cfg) }
+       } else if t == "tap" {
+               return func() error { return configureTap(cfg) }
+       }
+       return nil
+}
+
+func newUnconfigFn(cfg NetDevConfig) func() {
+       t := cfg["type"]
+       name := cfg["name"].(string)
+
+       if t == "tap" {
+               return func() { DelLink(name) }
+       } else if t == "netns" {
+               return func() { DelNetns(name) }
+       } else if t == "veth" {
+               return func() { DelLink(name) }
+       } else if t == "bridge" {
+               return func() { DelBridge(name, cfg["netns"].(string)) }
+       }
+       return nil
+}
+
+func NewNetConfig(cfg NetDevConfig) NetConfig {
+       var nc NetConfig
+
+       nc.Configure = newConfigFn(cfg)
+       nc.Unconfigure = newUnconfigFn(cfg)
+
+       return nc
+}
+
+func DelBridge(brName, ns string) error {
+       err := SetDevDown(brName, ns)
+       if err != err {
+               return err
+       }
+
+       err = addDelBridge(brName, ns, false)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func configureBridge(dev NetDevConfig) error {
+       var ifs []string
+       for _, v := range dev["interfaces"].([]interface{}) {
+               ifs = append(ifs, v.(string))
+       }
+       return AddBridge(dev["name"].(string), ifs, dev["netns"].(string))
+}
+
+func configureTap(dev NetDevConfig) error {
+       return AddTap(dev["name"].(string), dev["ip4"].(string))
+}
+
+func SetDevUp(dev, ns string) error {
+       return setDevUpDown(dev, ns, true)
+}
+
+func SetDevDown(dev, ns string) error {
+       return setDevUpDown(dev, ns, false)
+}
+
+func AddTap(ifName, ifAddress string) error {
+       cmd := exec.Command("ip", "tuntap", "add", ifName, "mode", "tap")
+       o, err := cmd.CombinedOutput()
+       if err != nil {
+               s := fmt.Sprintf("error creating tap %s: %v: %s", ifName, err, string(o))
+               return errors.New(s)
+       }
+
+       cmd = exec.Command("ip", "addr", "add", ifAddress, "dev", ifName)
+       err = cmd.Run()
+       if err != nil {
+               DelLink(ifName)
+               s := fmt.Sprintf("error setting addr for tap %s: %v", ifName, err)
+               return errors.New(s)
+       }
+
+       err = SetDevUp(ifName, "")
+       if err != nil {
+               DelLink(ifName)
+               return err
+       }
+       return nil
+}
+
+func DelLink(ifName string) {
+       cmd := exec.Command("ip", "link", "del", ifName)
+       cmd.Run()
+}
+
+func setDevUpDown(dev, ns string, isUp bool) error {
+       var op string
+       if isUp {
+               op = "up"
+       } else {
+               op = "down"
+       }
+       c := []string{"ip", "link", "set", "dev", dev, op}
+       cmd := appendNetns(c, ns)
+       err := cmd.Run()
+       if err != nil {
+               s := fmt.Sprintf("error bringing %s device %s!", dev, op)
+               return errors.New(s)
+       }
+       return nil
+}
+
+func AddVethPair(ifName, peerName string) error {
+       cmd := exec.Command("ip", "link", "add", ifName, "type", "veth", "peer", "name", peerName)
+       err := cmd.Run()
+       if err != nil {
+               return fmt.Errorf("creating veth pair failed: %v", err)
+       }
+       err = SetDevUp(ifName, "")
+       if err != nil {
+               return fmt.Errorf("set link up failed: %v", err)
+       }
+       return nil
+}
+
+func addDelNetns(name string, isAdd bool) error {
+       var op string
+       if isAdd {
+               op = "add"
+       } else {
+               op = "del"
+       }
+       cmd := exec.Command("ip", "netns", op, name)
+       _, err := cmd.CombinedOutput()
+       if err != nil {
+               return errors.New("add/del netns failed")
+       }
+       return nil
+}
+
+func AddNetns(nsName string) error {
+       return addDelNetns(nsName, true)
+}
+
+func DelNetns(nsName string) error {
+       return addDelNetns(nsName, false)
+}
+
+func LinkSetNetns(ifName, ns string) error {
+       cmd := exec.Command("ip", "link", "set", "dev", ifName, "up", "netns", ns)
+       err := cmd.Run()
+       if err != nil {
+               return fmt.Errorf("error setting device '%s' to netns '%s: %v", ifName, ns, err)
+       }
+       return nil
+}
+
+func NewCommand(s []string, ns string) *exec.Cmd {
+       return appendNetns(s, ns)
+}
+
+func appendNetns(s []string, ns string) *exec.Cmd {
+       var cmd *exec.Cmd
+       if ns == "" {
+               // use default namespace
+               cmd = exec.Command(s[0], s[1:]...)
+       } else {
+               var args = []string{"netns", "exec", ns}
+               args = append(args, s[:]...)
+               cmd = exec.Command("ip", args...)
+       }
+       return cmd
+}
+
+func addDelBridge(brName, ns string, isAdd bool) error {
+       var op string
+       if isAdd {
+               op = "addbr"
+       } else {
+               op = "delbr"
+       }
+       var c = []string{"brctl", op, brName}
+       cmd := appendNetns(c, ns)
+       err := cmd.Run()
+       if err != nil {
+               s := fmt.Sprintf("%s %s failed!", op, brName)
+               return errors.New(s)
+       }
+       return nil
+}
+
+func AddBridge(brName string, ifs []string, ns string) error {
+       err := addDelBridge(brName, ns, true)
+       if err != nil {
+               return err
+       }
+
+       for _, v := range ifs {
+               c := []string{"brctl", "addif", brName, v}
+               cmd := appendNetns(c, ns)
+               err = cmd.Run()
+               if err != nil {
+                       s := fmt.Sprintf("error adding %s to bridge %s: %v", v, brName, err)
+                       return errors.New(s)
+               }
+       }
+       err = SetDevUp(brName, ns)
+       if err != nil {
+               return err
+       }
+       return nil
+}
diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go
new file mode 100755 (executable)
index 0000000..797de52
--- /dev/null
@@ -0,0 +1,98 @@
+package main
+
+import (
+       "context"
+       "fmt"
+       "os"
+       "testing"
+
+       "github.com/edwarnicke/exechelper"
+)
+
+func testProxyHttpTcp(t *testing.T, dockerInstance string, proxySetup func() error) error {
+       const outputFile = "test.data"
+       const srcFile = "10M"
+       stopServer := make(chan struct{}, 1)
+       serverRunning := make(chan struct{}, 1)
+
+       volumeArgs := fmt.Sprintf("-v shared-vol:/tmp/%s", dockerInstance)
+       err := dockerRun(dockerInstance, volumeArgs)
+       if err != nil {
+               return fmt.Errorf("failed to start container: %v", err)
+       }
+       defer func() { exechelper.Run("docker stop " + dockerInstance) }()
+
+       // start & configure vpp in the container
+       _, err = hstExec(dockerInstance, dockerInstance)
+       if err != nil {
+               return fmt.Errorf("error starting vpp in container: %v", err)
+       }
+
+       fmt.Println("VPP running and configured...")
+
+       if err := proxySetup(); err != nil {
+               return fmt.Errorf("failed to setup proxy: %v", err)
+       }
+       fmt.Println("Proxy configured...")
+
+       // create test file
+       err = exechelper.Run(fmt.Sprintf("ip netns exec server truncate -s %s %s", srcFile, srcFile))
+       if err != nil {
+               return fmt.Errorf("failed to run truncate command")
+       }
+       defer func() { os.Remove(srcFile) }()
+
+       fmt.Println("Test file created...")
+
+       go startHttpServer(serverRunning, stopServer, ":666", "server")
+       // TODO better error handling and recovery
+       <-serverRunning
+
+       defer func(chan struct{}) {
+               stopServer <- struct{}{}
+       }(stopServer)
+
+       fmt.Println("http server started...")
+
+       c := fmt.Sprintf("ip netns exec client wget --retry-connrefused --retry-on-http-error=503 --tries=10 -O %s 10.0.0.2:555/%s", outputFile, srcFile)
+       _, err = exechelper.CombinedOutput(c)
+       if err != nil {
+               return fmt.Errorf("failed to run wget: %v", err)
+       }
+       stopServer <- struct{}{}
+
+       defer func() { os.Remove(outputFile) }()
+
+       if err = assertFileSize(outputFile, srcFile); err != nil {
+               return err
+       }
+       return nil
+}
+
+func (s *NsSuite) TestVppProxyHttpTcp() {
+       t := s.T()
+       dockerInstance := "vpp-proxy"
+       err := testProxyHttpTcp(t, dockerInstance, configureVppProxy)
+       if err != nil {
+               t.Errorf("%v", err)
+       }
+}
+
+func (s *NsSuite) TestEnvoyProxyHttpTcp() {
+       t := s.T()
+       exechelper.Run("docker volume create --name=shared-vol")
+       defer func() {
+               exechelper.Run("docker stop envoy")
+       }()
+
+       ctx, cancel := context.WithCancel(context.Background())
+
+       dockerInstance := "vpp-envoy"
+       err := testProxyHttpTcp(t, dockerInstance, func() error {
+               return setupEnvoy(t, ctx, dockerInstance)
+       })
+       if err != nil {
+               t.Errorf("%v", err)
+       }
+       cancel()
+}
diff --git a/extras/hs-test/script/build.sh b/extras/hs-test/script/build.sh
new file mode 100755 (executable)
index 0000000..d80823b
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+source vars
+
+bin=vpp-data/bin
+lib=vpp-data/lib
+
+mkdir -p ${bin} ${lib} || true
+
+cp ${VPP_WS}/build-root/build-vpp_debug-native/vpp/bin/* ${bin}
+cp -r ${VPP_WS}/build-root/build-vpp_debug-native/vpp/lib/x86_64-linux-gnu/* ${lib}
+
+docker build -t hs-test/vpp -f Dockerfile.vpp .
diff --git a/extras/hs-test/test b/extras/hs-test/test
new file mode 100755 (executable)
index 0000000..0bccc87
--- /dev/null
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+source vars
+sudo -E go test -buildvcs=false  -v $@
diff --git a/extras/hs-test/tools/http_server/http_server.go b/extras/hs-test/tools/http_server/http_server.go
new file mode 100755 (executable)
index 0000000..2b6512b
--- /dev/null
@@ -0,0 +1,26 @@
+package main
+
+import (
+       "fmt"
+       "io"
+       "net/http"
+       "os"
+)
+
+func main() {
+       if len(os.Args) < 2 {
+               fmt.Println("arg expected")
+               os.Exit(1)
+       }
+
+       http.HandleFunc("/10M", func(w http.ResponseWriter, r *http.Request) {
+               file, _ := os.Open("10M")
+               defer file.Close()
+               io.Copy(w, file)
+       })
+       err := http.ListenAndServe(os.Args[1], nil)
+       if err != nil {
+               fmt.Printf("%v\n", err)
+               os.Exit(1)
+       }
+}
diff --git a/extras/hs-test/topo.go b/extras/hs-test/topo.go
new file mode 100755 (executable)
index 0000000..f117614
--- /dev/null
@@ -0,0 +1,75 @@
+package main
+
+import (
+       "fmt"
+       "io/ioutil"
+       "os"
+       "strings"
+
+       "gopkg.in/yaml.v3"
+)
+
+type NetDevConfig map[string]interface{}
+
+type YamlTopology struct {
+       Devices []NetDevConfig `yaml:"devices"`
+}
+
+func AddAddress(device, address, ns string) error {
+       c := []string{"ip", "addr", "add", address, "dev", device}
+       cmd := appendNetns(c, ns)
+       err := cmd.Run()
+       if err != nil {
+               return fmt.Errorf("failed to set ip address for %s: %v", device, err)
+       }
+       return nil
+}
+
+func convertToNetConfig(t *YamlTopology) (*NetTopology, error) {
+       var topology NetTopology
+       for _, dev := range t.Devices {
+               topology = append(topology, NewNetConfig(dev))
+       }
+       return &topology, nil
+}
+
+func loadTopoFile(topoName string) (*NetTopology, error) {
+       var yamlTopo YamlTopology
+
+       data, err := ioutil.ReadFile(topoName)
+       if err != nil {
+               return nil, fmt.Errorf("read error: %v", err)
+       }
+
+       err = yaml.Unmarshal(data, &yamlTopo)
+       if err != nil {
+               return nil, fmt.Errorf("error parsing topology data: %v", err)
+       }
+
+       return convertToNetConfig(&yamlTopo)
+}
+
+func LoadTopology(path, topoName string) (*NetTopology, error) {
+       dir, err := os.Open(path)
+       if err != nil {
+               return nil, err
+       }
+       defer dir.Close()
+
+       files, err := dir.Readdir(0)
+       if err != nil {
+               return nil, err
+       }
+
+       for i := range files {
+               file := files[i]
+               fileName := file.Name()
+
+               // cut off file extension
+               f := strings.Split(fileName, ".")[0]
+               if f == topoName {
+                       return loadTopoFile(path + fileName)
+               }
+       }
+       return nil, fmt.Errorf("topology '%s' not found", topoName)
+}
diff --git a/extras/hs-test/topo/2peerVeth.yaml b/extras/hs-test/topo/2peerVeth.yaml
new file mode 100755 (executable)
index 0000000..d40f380
--- /dev/null
@@ -0,0 +1,23 @@
+---
+devices:
+  - name: "hsns"
+    type: "netns"
+
+  - name: "vppsrv"
+    type: "veth"
+    peer:
+      name: "vppsrv_veth"
+      netns: "hsns"
+
+  - name: "vppcln"
+    type: "veth"
+    peer:
+      name: "vppcln_veth"
+      netns: "hsns"
+
+  - name: "br"
+    type: "bridge"
+    netns: "hsns"
+    interfaces:
+      - vppsrv_veth
+      - vppcln_veth
diff --git a/extras/hs-test/topo/ns.yaml b/extras/hs-test/topo/ns.yaml
new file mode 100755 (executable)
index 0000000..c1c8c54
--- /dev/null
@@ -0,0 +1,21 @@
+---
+devices:
+  - name: "client"
+    type: "netns"
+
+  - name: "server"
+    type: "netns"
+
+  - name: "vpp0"
+    type: "veth"
+    peer:
+      name: "client"
+      netns: "client"
+      ip4: "10.0.0.1/24"
+
+  - name: "vpp1"
+    type: "veth"
+    peer:
+      name: "server"
+      netns: "server"
+      ip4: "10.0.1.1/24"
\ No newline at end of file
diff --git a/extras/hs-test/topo/tap.yaml b/extras/hs-test/topo/tap.yaml
new file mode 100755 (executable)
index 0000000..4cd95d6
--- /dev/null
@@ -0,0 +1,5 @@
+---
+devices:
+  - name: "tap0"
+    type: "tap"
+    ip4: "10.10.10.1/24"
diff --git a/extras/hs-test/tps_test.go b/extras/hs-test/tps_test.go
new file mode 100755 (executable)
index 0000000..dd87da1
--- /dev/null
@@ -0,0 +1,36 @@
+package main
+
+import (
+       "github.com/edwarnicke/exechelper"
+)
+
+func (s *NsSuite) TestHttpTps() {
+       t := s.T()
+       finished := make(chan error, 1)
+       server_ip := "10.0.0.2"
+       port := "8080"
+       dockerInstance := "http-tps"
+
+       t.Log("starting vpp..")
+
+       err := dockerRun(dockerInstance, "")
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + dockerInstance) }()
+
+       // start & configure vpp in the container
+       _, err = hstExec(dockerInstance, dockerInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       go startWget(finished, server_ip, port, "client")
+       // wait for client
+       err = <-finished
+       if err != nil {
+               t.Errorf("%v", err)
+       }
+}
diff --git a/extras/hs-test/utils.go b/extras/hs-test/utils.go
new file mode 100755 (executable)
index 0000000..2530359
--- /dev/null
@@ -0,0 +1,334 @@
+package main
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/edwarnicke/exechelper"
+)
+
+const configTemplate = `unix {
+  nodaemon
+  log %[1]s/var/log/vpp/vpp.log
+  full-coredump
+  cli-listen %[1]s/var/run/vpp/cli.sock
+  runtime-dir %[1]s/var/run
+  gid vpp
+}
+
+api-trace {
+  on
+}
+
+api-segment {
+  gid vpp
+}
+
+socksvr {
+  socket-name %[1]s/var/run/vpp/api.sock
+}
+
+statseg {
+  socket-name %[1]s/var/run/vpp/stats.sock
+}
+
+plugins {
+       plugin unittest_plugin.so { enable }
+    plugin dpdk_plugin.so { disable }
+    plugin crypto_aesni_plugin.so { enable }
+    plugin quic_plugin.so { enable }
+}
+
+`
+
+const TopologyDir string = "topo/"
+
+type Stanza struct {
+       content string
+       pad     int
+}
+
+type ActionResult struct {
+       Err       error
+       Desc      string
+       ErrOutput string
+       StdOutput string
+}
+
+type JsonResult struct {
+       Code      int
+       Desc      string
+       ErrOutput string
+       StdOutput string
+}
+
+func StartServerApp(running chan error, done chan struct{}, env []string) {
+       cmd := exec.Command("iperf3", "-4", "-s")
+       if env != nil {
+               cmd.Env = env
+       }
+       err := cmd.Start()
+       if err != nil {
+               msg := fmt.Errorf("failed to start iperf server: %v", err)
+               running <- msg
+               return
+       }
+       running <- nil
+       <-done
+       cmd.Process.Kill()
+}
+
+func StartClientApp(env []string, clnCh chan error) {
+       defer func() {
+               clnCh <- nil
+       }()
+
+       nTries := 0
+
+       for {
+               cmd := exec.Command("iperf3", "-c", "10.10.10.1", "-u", "-l", "1460", "-b", "10g")
+               if env != nil {
+                       cmd.Env = env
+               }
+               o, err := cmd.CombinedOutput()
+               if err != nil {
+                       if nTries > 5 {
+                               clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
+                               return
+                       }
+                       time.Sleep(1 * time.Second)
+                       nTries++
+                       continue
+               } else {
+                       fmt.Printf("Client output: %s", o)
+               }
+               break
+       }
+}
+
+// run vpphelper in docker
+func hstExec(args string, instance string) (string, error) {
+       syncFile := fmt.Sprintf("/tmp/%s/sync/rc", instance)
+       os.Remove(syncFile)
+
+       c := "docker exec -d " + instance + " /hs-test " + args
+       err := exechelper.Run(c)
+       if err != nil {
+               return "", err
+       }
+
+       res, err := waitForSyncFile(syncFile)
+
+       if err != nil {
+               return "", fmt.Errorf("failed to read sync file while executing './hs-test %s': %v", args, err)
+       }
+
+       o := res.StdOutput + res.ErrOutput
+       if res.Code != 0 {
+               return o, fmt.Errorf("cmd resulted in non-zero value %d: %s", res.Code, res.Desc)
+       }
+       return o, err
+}
+
+func waitForSyncFile(fname string) (*JsonResult, error) {
+       var res JsonResult
+
+       for i := 0; i < 60; i++ {
+               f, err := os.Open(fname)
+               if err == nil {
+                       defer f.Close()
+
+                       data, err := ioutil.ReadFile(fname)
+                       if err != nil {
+                               return nil, fmt.Errorf("read error: %v", err)
+                       }
+                       err = json.Unmarshal(data, &res)
+                       if err != nil {
+                               return nil, fmt.Errorf("json unmarshal error: %v", err)
+                       }
+                       return &res, nil
+               }
+               time.Sleep(1 * time.Second)
+       }
+       return nil, fmt.Errorf("no sync file found")
+}
+
+func dockerRun(instance, args string) error {
+       exechelper.Run(fmt.Sprintf("mkdir -p /tmp/%s/sync", instance))
+       syncPath := fmt.Sprintf("-v /tmp/%s/sync:/tmp/sync", instance)
+       cmd := "docker run --cap-add=all -d --privileged --network host --rm "
+       cmd += syncPath
+       cmd += " " + args
+       cmd += " --name " + instance + " hs-test/vpp"
+       fmt.Println(cmd)
+       return exechelper.Run(cmd)
+}
+
+func assertFileSize(f1, f2 string) error {
+       fi1, err := os.Stat(f1)
+       if err != nil {
+               return err
+       }
+
+       fi2, err1 := os.Stat(f2)
+       if err1 != nil {
+               return err1
+       }
+
+       if fi1.Size() != fi2.Size() {
+               return fmt.Errorf("file sizes differ (%d vs %d)", fi1.Size(), fi2.Size())
+       }
+       return nil
+}
+
+func dockerExec(cmd string, instance string) ([]byte, error) {
+       c := "docker exec -d " + instance + " " + cmd
+       return exechelper.CombinedOutput(c)
+}
+
+func startEnvoy(ctx context.Context, dockerInstance string) <-chan error {
+       errCh := make(chan error)
+       wd, err := os.Getwd()
+       if err != nil {
+               errCh <- err
+               return errCh
+       }
+
+       c := []string{"docker", "run", "--rm", "--name", "envoy",
+               "-v", fmt.Sprintf("%s/envoy/proxy.yaml:/etc/envoy/envoy.yaml", wd),
+               "-v", fmt.Sprintf("shared-vol:/tmp/%s", dockerInstance),
+               "-v", fmt.Sprintf("%s/envoy:/tmp", wd),
+               "-e", "VCL_CONFIG=/tmp/vcl.conf",
+               "envoyproxy/envoy-contrib:v1.21-latest"}
+       fmt.Println(c)
+
+       go func(errCh chan error) {
+               count := 0
+               var cmd *exec.Cmd
+               for ; ; count++ {
+                       cmd = NewCommand(c, "")
+                       err = cmd.Start()
+                       if err == nil {
+                               break
+                       }
+                       if count > 5 {
+                               errCh <- fmt.Errorf("Failed to start envoy docker after %d attempts", count)
+                               return
+                       }
+               }
+
+               err = cmd.Wait()
+               if err != nil {
+                       errCh <- fmt.Errorf("failed to start docker: %v", err)
+                       return
+               }
+               <-ctx.Done()
+       }(errCh)
+       return errCh
+}
+
+func setupEnvoy(t *testing.T, ctx context.Context, dockerInstance string) error {
+       errCh := startEnvoy(ctx, dockerInstance)
+       select {
+       case err := <-errCh:
+               return err
+       default:
+       }
+
+       go func(ctx context.Context, errCh <-chan error) {
+               for {
+                       select {
+                       // handle cancel() call from outside to gracefully stop the routine
+                       case <-ctx.Done():
+                               return
+                       default:
+                               select {
+                               case err := <-errCh:
+                                       fmt.Printf("error while running envoy: %v", err)
+                               default:
+                               }
+                       }
+               }
+       }(ctx, errCh)
+       return nil
+}
+
+func configureVppProxy() error {
+       _, err := dockerExec("vppctl test proxy server server-uri tcp://10.0.0.2/555 client-uri tcp://10.0.1.1/666",
+               "vpp-proxy")
+       if err != nil {
+               return fmt.Errorf("error while configuring vpp proxy test: %v", err)
+       }
+       return nil
+}
+
+func startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
+       cmd := NewCommand([]string{"./http_server", addressPort}, netNs)
+       err := cmd.Start()
+       if err != nil {
+               fmt.Println("Failed to start http server")
+               return
+       }
+       running <- struct{}{}
+       <-done
+       cmd.Process.Kill()
+}
+
+func startWget(finished chan error, server_ip, port string, netNs string) {
+       fname := "test_file_10M"
+       defer func() {
+               finished <- errors.New("wget error")
+       }()
+
+       cmd := NewCommand([]string{"wget", "--tries=5", "-q", "-O", "/dev/null", server_ip + ":" + port + "/" + fname},
+               netNs)
+       o, err := cmd.CombinedOutput()
+       if err != nil {
+               fmt.Printf("wget error: '%s'.\n%s", err, o)
+               return
+       }
+       fmt.Printf("Client output: %s", o)
+       finished <- nil
+}
+
+func (c *Stanza) NewStanza(name string) *Stanza {
+       c.Append("\n" + name + " {")
+       c.pad += 2
+       return c
+}
+
+func (c *Stanza) Append(name string) *Stanza {
+       c.content += strings.Repeat(" ", c.pad)
+       c.content += name + "\n"
+       return c
+}
+
+func (c *Stanza) Close() *Stanza {
+       c.content += "}\n"
+       c.pad -= 2
+       return c
+}
+
+func (s *Stanza) ToString() string {
+       return s.content
+}
+
+func (s *Stanza) SaveToFile(fileName string) error {
+       fo, err := os.Create(fileName)
+       if err != nil {
+               return err
+       }
+       defer fo.Close()
+
+       _, err = io.Copy(fo, strings.NewReader(s.content))
+       return err
+}
diff --git a/extras/hs-test/vars b/extras/hs-test/vars
new file mode 100755 (executable)
index 0000000..1717b11
--- /dev/null
@@ -0,0 +1,4 @@
+export VPP_WS=../../
+
+export HST_LDPRELOAD=${VPP_WS}/build-root/build-vpp_debug-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so
+export PATH=${VPP_WS}/build-root/build-vpp_debug-native/vpp/bin:$PATH
diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go
new file mode 100755 (executable)
index 0000000..8c4afe8
--- /dev/null
@@ -0,0 +1,86 @@
+package main
+
+import (
+       "fmt"
+
+       "github.com/edwarnicke/exechelper"
+)
+
+func (s *Veths2Suite) TestVclEchoQuic() {
+       s.T().Skip("quic test skipping..")
+       s.testVclEcho("quic")
+}
+
+func (s *Veths2Suite) TestVclEchoUdp() {
+       s.T().Skip("udp echo currently broken in vpp, skipping..")
+       s.testVclEcho("udp")
+}
+
+func (s *Veths2Suite) TestVclEchoTcp() {
+       s.testVclEcho("tcp")
+}
+
+func (s *Veths2Suite) testVclEcho(proto string) {
+       t := s.T()
+
+       exechelper.Run("docker volume create --name=echo-srv-vol")
+       exechelper.Run("docker volume create --name=echo-cln-vol")
+
+       srvInstance := "vpp-echo-srv"
+       clnInstance := "vpp-echo-cln"
+       echoSrv := "echo-srv"
+       echoCln := "echo-cln"
+
+       err := dockerRun(srvInstance, "-v echo-srv-vol:/tmp/2veths")
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + srvInstance) }()
+
+       err = dockerRun(clnInstance, "-v echo-cln-vol:/tmp/2veths")
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + clnInstance) }()
+
+       err = dockerRun(echoSrv, fmt.Sprintf("-v echo-srv-vol:/tmp/%s", echoSrv))
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + echoSrv) }()
+
+       err = dockerRun(echoCln, fmt.Sprintf("-v echo-cln-vol:/tmp/%s", echoCln))
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer func() { exechelper.Run("docker stop " + echoCln) }()
+
+       _, err = hstExec("2veths srv", srvInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       _, err = hstExec("2veths cln", clnInstance)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+
+       // run server app
+       _, err = hstExec("echo-server "+proto, echoSrv)
+       if err != nil {
+               t.Errorf("echo server: %v", err)
+               return
+       }
+
+       o, err := hstExec("echo-client "+proto, echoCln)
+       if err != nil {
+               t.Errorf("echo client: %v", err)
+       }
+       fmt.Println(o)
+}