aboutsummaryrefslogtreecommitdiffstats
path: root/extras
diff options
context:
space:
mode:
Diffstat (limited to 'extras')
-rwxr-xr-xextras/hs-test/Dockerfile.vpp19
-rwxr-xr-xextras/hs-test/Makefile10
-rwxr-xr-xextras/hs-test/README9
-rwxr-xr-xextras/hs-test/actions.go268
-rwxr-xr-xextras/hs-test/echo_test.go51
-rwxr-xr-xextras/hs-test/envoy/envoy.log0
-rwxr-xr-xextras/hs-test/envoy/proxy.yaml52
-rwxr-xr-xextras/hs-test/envoy/vcl.conf7
-rwxr-xr-xextras/hs-test/framework_test.go82
-rwxr-xr-xextras/hs-test/go.mod30
-rwxr-xr-xextras/hs-test/go.sum143
-rwxr-xr-xextras/hs-test/ldp_test.go114
-rwxr-xr-xextras/hs-test/linux_iperf_test.go26
-rwxr-xr-xextras/hs-test/main.go155
-rwxr-xr-xextras/hs-test/netconfig.go283
-rwxr-xr-xextras/hs-test/proxy_test.go98
-rwxr-xr-xextras/hs-test/script/build.sh13
-rwxr-xr-xextras/hs-test/test4
-rwxr-xr-xextras/hs-test/tools/http_server/http_server.go26
-rwxr-xr-xextras/hs-test/topo.go75
-rwxr-xr-xextras/hs-test/topo/2peerVeth.yaml23
-rwxr-xr-xextras/hs-test/topo/ns.yaml21
-rwxr-xr-xextras/hs-test/topo/tap.yaml5
-rwxr-xr-xextras/hs-test/tps_test.go36
-rwxr-xr-xextras/hs-test/utils.go334
-rwxr-xr-xextras/hs-test/vars4
-rwxr-xr-xextras/hs-test/vcl_test.go86
27 files changed, 1974 insertions, 0 deletions
diff --git a/extras/hs-test/Dockerfile.vpp b/extras/hs-test/Dockerfile.vpp
new file mode 100755
index 00000000000..92577870b42
--- /dev/null
+++ b/extras/hs-test/Dockerfile.vpp
@@ -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
index 00000000000..90265551c87
--- /dev/null
+++ b/extras/hs-test/Makefile
@@ -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
index 00000000000..06b2ca644a8
--- /dev/null
+++ b/extras/hs-test/README
@@ -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
index 00000000000..aa82f49c4c0
--- /dev/null
+++ b/extras/hs-test/actions.go
@@ -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))
+
+
+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
index 00000000000..f3f3c1b2fcb
--- /dev/null
+++ b/extras/hs-test/netconfig.go
@@ -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
index 00000000000..797de5255fa
--- /dev/null
+++ b/extras/hs-test/proxy_test.go
@@ -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
index 00000000000..d80823b5859
--- /dev/null
+++ b/extras/hs-test/script/build.sh
@@ -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
index 00000000000..0bccc871711
--- /dev/null
+++ b/extras/hs-test/test
@@ -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
index 00000000000..2b6512be5fd
--- /dev/null
+++ b/extras/hs-test/tools/http_server/http_server.go
@@ -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
index 00000000000..f11761460d8
--- /dev/null
+++ b/extras/hs-test/topo.go
@@ -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
index 00000000000..d40f3803879
--- /dev/null
+++ b/extras/hs-test/topo/2peerVeth.yaml
@@ -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
index 00000000000..c1c8c540b2b
--- /dev/null
+++ b/extras/hs-test/topo/ns.yaml
@@ -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
index 00000000000..4cd95d6e48a
--- /dev/null
+++ b/extras/hs-test/topo/tap.yaml
@@ -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
index 00000000000..dd87da1718e
--- /dev/null
+++ b/extras/hs-test/tps_test.go
@@ -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
index 00000000000..25303591a43
--- /dev/null
+++ b/extras/hs-test/utils.go
@@ -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
index 00000000000..1717b114de7
--- /dev/null
+++ b/extras/hs-test/vars
@@ -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
index 00000000000..8c4afe8fe30
--- /dev/null
+++ b/extras/hs-test/vcl_test.go
@@ -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)
+}