hs-test: added a redis-benchmark test

- basically a copy of LdpIperf test
- small update of LdpIperf test
- new LDP suite

Type: test

Change-Id: I3f8653288c6fc6dfd6a061315e983c000974d3ff
Signed-off-by: Adrian Villin <avillin@cisco.com>
diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp
index f87ee30..82a1a1a 100644
--- a/extras/hs-test/docker/Dockerfile.vpp
+++ b/extras/hs-test/docker/Dockerfile.vpp
@@ -5,7 +5,7 @@
 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 libunwind-dev \
+    vim gdb libunwind-dev redis redis-tools iperf3 \
  && rm -rf /var/lib/apt/lists/*
 
 ENV DIR=vpp-data/lib/vpp_plugins
diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go
index 5c124d3..ed8da3f 100644
--- a/extras/hs-test/infra/hst_suite.go
+++ b/extras/hs-test/infra/hst_suite.go
@@ -2,7 +2,6 @@
 
 import (
 	"bufio"
-	"errors"
 	"flag"
 	"fmt"
 	"github.com/edwarnicke/exechelper"
@@ -607,84 +606,6 @@
 	return port[len(port)-3:] + s.ProcessIndex
 }
 
-func (s *HstSuite) StartServerApp(running chan error, done chan struct{}, env []string) {
-	cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid())
-	if env != nil {
-		cmd.Env = env
-	}
-	s.Log(cmd)
-	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 (s *HstSuite) StartClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
-	defer func() {
-		clnCh <- nil
-	}()
-
-	nTries := 0
-
-	for {
-		cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid())
-		if env != nil {
-			cmd.Env = env
-		}
-		s.Log(cmd)
-		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 {
-			clnRes <- fmt.Sprintf("Client output: %s", o)
-		}
-		break
-	}
-}
-
-func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
-	cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs)
-	err := cmd.Start()
-	s.Log(cmd)
-	if err != nil {
-		s.Log("Failed to start http server: " + fmt.Sprint(err))
-		return
-	}
-	running <- struct{}{}
-	<-done
-	cmd.Process.Kill()
-}
-
-func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs string) {
-	defer func() {
-		finished <- errors.New("wget error")
-	}()
-
-	cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
-		netNs)
-	s.Log(cmd)
-	o, err := cmd.CombinedOutput()
-	if err != nil {
-		finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
-		return
-	} else if !strings.Contains(string(o), "200 OK") {
-		finished <- fmt.Errorf("wget error: response not 200 OK")
-		return
-	}
-	finished <- nil
-}
-
 /*
 RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
 passing in suite context, experiment and your data.
diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go
new file mode 100644
index 0000000..15b45f7
--- /dev/null
+++ b/extras/hs-test/infra/suite_ldp.go
@@ -0,0 +1,203 @@
+package hst
+
+import (
+	"fmt"
+	"reflect"
+	"runtime"
+	"strings"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+)
+
+// These correspond to names used in yaml config
+const (
+	ServerLdpInterfaceName = "srv"
+	ClientLdpInterfaceName = "cln"
+)
+
+var ldpTests = map[string][]func(s *LdpSuite){}
+var ldpSoloTests = map[string][]func(s *LdpSuite){}
+
+type LdpSuite struct {
+	HstSuite
+}
+
+func RegisterLdpTests(tests ...func(s *LdpSuite)) {
+	ldpTests[getTestFilename()] = tests
+}
+func RegisterSoloLdpTests(tests ...func(s *LdpSuite)) {
+	ldpSoloTests[getTestFilename()] = tests
+}
+
+func (s *LdpSuite) SetupSuite() {
+	time.Sleep(1 * time.Second)
+	s.HstSuite.SetupSuite()
+	s.ConfigureNetworkTopology("2peerVeth")
+	s.LoadContainerTopology("2peerVethLdp")
+}
+
+func (s *LdpSuite) SetupTest() {
+	s.HstSuite.SetupTest()
+
+	// Setup test conditions
+	var sessionConfig Stanza
+	sessionConfig.
+		NewStanza("session").
+		Append("enable").
+		Append("use-app-socket-api")
+
+	if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") {
+		sessionConfig.Append("use-private-rx-mqs").Close()
+		s.Log("**********************INTERRUPT MODE**********************")
+	} else {
+		sessionConfig.Close()
+	}
+
+	// ... For server
+	serverContainer := s.GetContainerByName("server-vpp")
+
+	serverVpp, err := serverContainer.newVppInstance(serverContainer.AllocatedCpus, sessionConfig)
+	s.AssertNotNil(serverVpp, fmt.Sprint(err))
+
+	s.SetupServerVpp()
+
+	// ... For client
+	clientContainer := s.GetContainerByName("client-vpp")
+
+	clientVpp, err := clientContainer.newVppInstance(clientContainer.AllocatedCpus, sessionConfig)
+	s.AssertNotNil(clientVpp, fmt.Sprint(err))
+
+	s.setupClientVpp()
+
+	serverContainer.AddEnvVar("VCL_CONFIG", serverContainer.GetContainerWorkDir()+"/vcl_srv.conf")
+	clientContainer.AddEnvVar("VCL_CONFIG", clientContainer.GetContainerWorkDir()+"/vcl_cln.conf")
+
+	for _, container := range s.StartedContainers {
+		container.AddEnvVar("LD_PRELOAD", "/usr/lib/libvcl_ldpreload.so")
+		container.AddEnvVar("LDP_DEBUG", "0")
+		container.AddEnvVar("VCL_DEBUG", "0")
+	}
+}
+
+func (s *LdpSuite) TearDownTest() {
+	for _, container := range s.StartedContainers {
+		delete(container.EnvVars, "LD_PRELOAD")
+		delete(container.EnvVars, "VCL_CONFIG")
+	}
+	s.HstSuite.TearDownTest()
+
+}
+
+func (s *LdpSuite) SetupServerVpp() {
+	var srvVclConf Stanza
+	serverContainer := s.GetContainerByName("server-vpp")
+	serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
+	serverVpp := serverContainer.VppInstance
+	s.AssertNil(serverVpp.Start())
+
+	serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+	idx, err := serverVpp.createAfPacket(serverVeth)
+	s.AssertNil(err, fmt.Sprint(err))
+	s.AssertNotEqual(0, idx)
+
+	serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+		serverContainer.GetContainerWorkDir())
+	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(serverAppSocketApi).Close().
+		SaveToFile(serverVclFileName)
+	s.AssertNil(err, fmt.Sprint(err))
+}
+
+func (s *LdpSuite) setupClientVpp() {
+	var clnVclConf Stanza
+	clientContainer := s.GetContainerByName("client-vpp")
+	clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
+	clientVpp := clientContainer.VppInstance
+	s.AssertNil(clientVpp.Start())
+
+	clientVeth := s.GetInterfaceByName(ClientInterfaceName)
+	idx, err := clientVpp.createAfPacket(clientVeth)
+	s.AssertNil(err, fmt.Sprint(err))
+	s.AssertNotEqual(0, idx)
+
+	clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
+		clientContainer.GetContainerWorkDir())
+	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(clientAppSocketApi).Close().
+		SaveToFile(clientVclFileName)
+	s.AssertNil(err, fmt.Sprint(err))
+}
+
+var _ = Describe("LdpSuite", Ordered, ContinueOnFailure, func() {
+	var s LdpSuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	// https://onsi.github.io/ginkgo/#dynamically-generating-specs
+	for filename, tests := range ldpTests {
+		for _, test := range tests {
+			test := test
+			pc := reflect.ValueOf(test).Pointer()
+			funcValue := runtime.FuncForPC(pc)
+			testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+			It(testName, func(ctx SpecContext) {
+				s.Log(testName + ": BEGIN")
+				test(&s)
+			}, SpecTimeout(SuiteTimeout))
+		}
+	}
+})
+
+var _ = Describe("LdpSuiteSolo", Ordered, ContinueOnFailure, Serial, func() {
+	var s LdpSuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	// https://onsi.github.io/ginkgo/#dynamically-generating-specs
+	for filename, tests := range ldpSoloTests {
+		for _, test := range tests {
+			test := test
+			pc := reflect.ValueOf(test).Pointer()
+			funcValue := runtime.FuncForPC(pc)
+			testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2]
+			It(testName, Label("SOLO"), func(ctx SpecContext) {
+				s.Log(testName + ": BEGIN")
+				test(&s)
+			}, SpecTimeout(SuiteTimeout))
+		}
+	}
+})
diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go
index 9dd2dbd..25d8519 100644
--- a/extras/hs-test/infra/utils.go
+++ b/extras/hs-test/infra/utils.go
@@ -1,6 +1,7 @@
 package hst
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"net"
@@ -183,3 +184,133 @@
 		s.Log(fmt.Sprint(err))
 	}
 }
+
+func (s *HstSuite) StartIperfServerApp(running chan error, done chan struct{}, env []string) {
+	cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid())
+	if env != nil {
+		cmd.Env = env
+	}
+	s.Log(cmd)
+	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 (s *HstSuite) StartIperfClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
+	defer func() {
+		clnCh <- nil
+	}()
+
+	nTries := 0
+
+	for {
+		cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid())
+		if env != nil {
+			cmd.Env = env
+		}
+		s.Log(cmd)
+		o, err := cmd.CombinedOutput()
+		if err != nil {
+			if nTries > 5 {
+				clnRes <- ""
+				clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
+				return
+			}
+			time.Sleep(1 * time.Second)
+			nTries++
+			continue
+		} else {
+			clnRes <- fmt.Sprintf("Client output: %s", o)
+		}
+		break
+	}
+}
+
+func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
+	cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs)
+	err := cmd.Start()
+	s.Log(cmd)
+	if err != nil {
+		s.Log("Failed to start http server: " + fmt.Sprint(err))
+		return
+	}
+	running <- struct{}{}
+	<-done
+	cmd.Process.Kill()
+}
+
+func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs string) {
+	defer func() {
+		finished <- errors.New("wget error")
+	}()
+
+	cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
+		netNs)
+	s.Log(cmd)
+	o, err := cmd.CombinedOutput()
+	if err != nil {
+		finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
+		return
+	} else if !strings.Contains(string(o), "200 OK") {
+		finished <- fmt.Errorf("wget error: response not 200 OK")
+		return
+	}
+	finished <- nil
+}
+
+// Start a server app. 'processName' is used to check whether the app started correctly.
+func (s *HstSuite) StartServerApp(c *Container, processName string, cmd string,
+	running chan error, done chan struct{}) {
+
+	s.Log("starting server")
+	c.ExecServer(cmd)
+	cmd2 := exec.Command("docker", "exec", c.Name, "pidof", processName)
+	err := cmd2.Run()
+	if err != nil {
+		msg := fmt.Errorf("failed to start server app: %v", err)
+		running <- msg
+		<-done
+		return
+	}
+	running <- nil
+	<-done
+}
+
+func (s *HstSuite) StartClientApp(c *Container, cmd string,
+	clnCh chan error, clnRes chan string) {
+	defer func() {
+		close(clnCh)
+		close(clnRes)
+	}()
+
+	s.Log("starting client app, please wait")
+
+	nTries := 0
+	for {
+		// exec.Cmd can only be used once, which is why it's in the loop
+		cmd2 := exec.Command("/bin/sh", "-c", "docker exec "+c.getEnvVarsAsCliOption()+" "+
+			c.Name+" "+cmd)
+		s.Log(cmd2)
+		o, err := cmd2.CombinedOutput()
+		if err != nil {
+			s.Log(err)
+			if nTries > 5 {
+				clnRes <- ""
+				clnCh <- fmt.Errorf("failed to start client app '%s'", err)
+				s.AssertNil(err, fmt.Sprint(err))
+				break
+			}
+			time.Sleep(1 * time.Second)
+			nTries++
+		} else {
+			clnRes <- fmt.Sprintf("Client output: %s", o)
+			break
+		}
+	}
+}
diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go
index e9e8bba..03636b1 100644
--- a/extras/hs-test/ldp_test.go
+++ b/extras/hs-test/ldp_test.go
@@ -2,86 +2,42 @@
 
 import (
 	"fmt"
-	"os"
 
 	. "fd.io/hs-test/infra"
 	. "github.com/onsi/ginkgo/v2"
 )
 
 func init() {
-	RegisterVethTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest)
+	RegisterLdpTests(LDPreloadIperfVppTest, LDPreloadIperfVppInterruptModeTest, RedisBenchmarkTest)
 }
 
-func LDPreloadIperfVppInterruptModeTest(s *VethsSuite) {
+func LDPreloadIperfVppInterruptModeTest(s *LdpSuite) {
 	LDPreloadIperfVppTest(s)
 }
 
-func LDPreloadIperfVppTest(s *VethsSuite) {
-	var clnVclConf, srvVclConf Stanza
-	var ldpreload string
-
-	serverContainer := s.GetContainerByName("server-vpp")
-	serverVclFileName := serverContainer.GetHostWorkDir() + "/vcl_srv.conf"
-
+func LDPreloadIperfVppTest(s *LdpSuite) {
 	clientContainer := s.GetContainerByName("client-vpp")
-	clientVclFileName := clientContainer.GetHostWorkDir() + "/vcl_cln.conf"
-
-	if *IsDebugBuild {
-		ldpreload = "LD_PRELOAD=../../build-root/build-vpp_debug-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
-	} else {
-		ldpreload = "LD_PRELOAD=../../build-root/build-vpp-native/vpp/lib/x86_64-linux-gnu/libvcl_ldpreload.so"
-	}
+	serverContainer := s.GetContainerByName("server-vpp")
 
 	stopServerCh := make(chan struct{}, 1)
 	srvCh := make(chan error, 1)
 	clnCh := make(chan error)
+	clnRes := make(chan string, 1)
 
-	s.Log("starting VPPs")
-
-	clientAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
-		clientContainer.GetHostWorkDir())
-	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(clientAppSocketApi).Close().
-		SaveToFile(clientVclFileName)
-	s.AssertNil(err, fmt.Sprint(err))
-
-	serverAppSocketApi := fmt.Sprintf("app-socket-api %s/var/run/app_ns_sockets/default",
-		serverContainer.GetHostWorkDir())
-	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(serverAppSocketApi).Close().
-		SaveToFile(serverVclFileName)
-	s.AssertNil(err, fmt.Sprint(err))
-
-	s.Log("attaching server to vpp")
-
-	srvEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+serverVclFileName)
 	go func() {
 		defer GinkgoRecover()
-		s.StartServerApp(srvCh, stopServerCh, srvEnv)
+		cmd := "iperf3 -4 -s -p " + s.GetPortFromPpid()
+		s.StartServerApp(serverContainer, "iperf3", cmd, srvCh, stopServerCh)
 	}()
 
-	err = <-srvCh
+	err := <-srvCh
 	s.AssertNil(err, fmt.Sprint(err))
 
-	s.Log("attaching client to vpp")
-	var clnRes = make(chan string, 1)
-	clnEnv := append(os.Environ(), ldpreload, "VCL_CONFIG="+clientVclFileName)
 	serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
 	go func() {
 		defer GinkgoRecover()
-		s.StartClientApp(serverVethAddress, clnEnv, clnCh, clnRes)
+		cmd := "iperf3 -c " + serverVethAddress + " -u -l 1460 -b 10g -p " + s.GetPortFromPpid()
+		s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
 	}()
 	s.Log(<-clnRes)
 
@@ -92,3 +48,43 @@
 	// stop server
 	stopServerCh <- struct{}{}
 }
+
+func RedisBenchmarkTest(s *LdpSuite) {
+	s.SkipIfMultiWorker()
+
+	serverContainer := s.GetContainerByName("server-vpp")
+	clientContainer := s.GetContainerByName("client-vpp")
+
+	serverVethAddress := s.GetInterfaceByName(ServerInterfaceName).Ip4AddressString()
+	runningSrv := make(chan error)
+	doneSrv := make(chan struct{})
+	clnCh := make(chan error)
+	clnRes := make(chan string, 1)
+
+	go func() {
+		defer GinkgoRecover()
+		cmd := "redis-server --daemonize yes --protected-mode no --bind " + serverVethAddress
+		s.StartServerApp(serverContainer, "redis-server", cmd, runningSrv, doneSrv)
+	}()
+
+	err := <-runningSrv
+	s.AssertNil(err)
+
+	go func() {
+		defer GinkgoRecover()
+		var cmd string
+		if *NConfiguredCpus == 1 {
+			cmd = "redis-benchmark --threads 1 -h " + serverVethAddress
+		} else {
+			cmd = "redis-benchmark --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress
+		}
+		s.StartClientApp(clientContainer, cmd, clnCh, clnRes)
+	}()
+
+	s.Log(<-clnRes)
+	// wait for client's result
+	err = <-clnCh
+	s.AssertNil(err, fmt.Sprint(err))
+	// stop server
+	doneSrv <- struct{}{}
+}
diff --git a/extras/hs-test/linux_iperf_test.go b/extras/hs-test/linux_iperf_test.go
index f49d9bc..9342e86 100644
--- a/extras/hs-test/linux_iperf_test.go
+++ b/extras/hs-test/linux_iperf_test.go
@@ -21,7 +21,7 @@
 
 	go func() {
 		defer GinkgoRecover()
-		s.StartServerApp(srvCh, stopServerCh, nil)
+		s.StartIperfServerApp(srvCh, stopServerCh, nil)
 	}()
 	err := <-srvCh
 	s.AssertNil(err, fmt.Sprint(err))
@@ -30,7 +30,7 @@
 	ipAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
 	go func() {
 		defer GinkgoRecover()
-		s.StartClientApp(ipAddress, nil, clnCh, clnRes)
+		s.StartIperfClientApp(ipAddress, nil, clnCh, clnRes)
 	}()
 	s.Log("client running")
 	s.Log(<-clnRes)
diff --git a/extras/hs-test/topo-containers/2peerVethLdp.yaml b/extras/hs-test/topo-containers/2peerVethLdp.yaml
new file mode 100644
index 0000000..bd6e63a
--- /dev/null
+++ b/extras/hs-test/topo-containers/2peerVethLdp.yaml
@@ -0,0 +1,18 @@
+---
+volumes:
+  - volume: &server-vol
+      host-dir: "$HST_VOLUME_DIR/server-share"
+      container-dir: "/tmp/server-share"
+      is-default-work-dir: true
+  - volume: &client-vol
+      host-dir: "$HST_VOLUME_DIR/client-share"
+      container-dir: "/tmp/client-share"
+      is-default-work-dir: true
+
+containers:
+  - name: "server-vpp"
+    volumes:
+      - <<: *server-vol
+  - name: "client-vpp"
+    volumes:
+      - <<: *client-vol