hs-test: proxy testing improvement

- new container topologies and suites for VPP proxy and Envoy proxy
- removed build docker image since it can't be used with CI cache
  builder, container builders are designed to be stateless, they
  only preserve build-cache, but not images

Type: test
Change-Id: I93e4d079780d18d6aa3b5ce807adc4707b6f2d9b
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/extras/hs-test/docker/Dockerfile.build b/extras/hs-test/docker/Dockerfile.build
deleted file mode 100644
index 8b2652e..0000000
--- a/extras/hs-test/docker/Dockerfile.build
+++ /dev/null
@@ -1,8 +0,0 @@
-ARG UBUNTU_VERSION
-
-FROM ubuntu:${UBUNTU_VERSION}
-
-RUN apt-get update \
- && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
- && rm -rf /var/lib/apt/lists/*
-
diff --git a/extras/hs-test/docker/Dockerfile.curl b/extras/hs-test/docker/Dockerfile.curl
index 81d15e8..90f415f 100644
--- a/extras/hs-test/docker/Dockerfile.curl
+++ b/extras/hs-test/docker/Dockerfile.curl
@@ -1,4 +1,10 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
 
 COPY script/build_curl.sh /build_curl.sh
 RUN apt-get update && apt-get install wget
diff --git a/extras/hs-test/docker/Dockerfile.nginx-http3 b/extras/hs-test/docker/Dockerfile.nginx-http3
index 5d66a25..8b2f840 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-http3
+++ b/extras/hs-test/docker/Dockerfile.nginx-http3
@@ -1,4 +1,10 @@
-FROM hs-test/build
+ARG UBUNTU_VERSION
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+RUN apt-get update \
+ && apt-get install -y gcc git make autoconf libtool pkg-config cmake ninja-build golang \
+ && rm -rf /var/lib/apt/lists/*
 
 COPY script/build_boringssl.sh /build_boringssl.sh
 RUN git clone https://boringssl.googlesource.com/boringssl
diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server
index ce397ec..08f428d 100644
--- a/extras/hs-test/docker/Dockerfile.nginx-server
+++ b/extras/hs-test/docker/Dockerfile.nginx-server
@@ -7,6 +7,9 @@
  && rm -rf /var/lib/apt/lists/*
 
 COPY resources/nginx/nginx_server_mirroring.conf /nginx.conf
-COPY script/nginx_mirroring_entrypoint.sh /usr/bin/nginx_mirroring_entrypoint.sh
+COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh
 
-ENTRYPOINT ["nginx_mirroring_entrypoint.sh"]
+COPY resources/nginx/html/index.html /usr/share/nginx/index.html
+RUN fallocate -l 10MB /usr/share/nginx/httpTestFile
+
+ENTRYPOINT ["nginx_server_entrypoint.sh"]
diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go
new file mode 100644
index 0000000..e1bfae2
--- /dev/null
+++ b/extras/hs-test/infra/suite_envoy_proxy.go
@@ -0,0 +1,195 @@
+// Suite for Envoy proxy testing
+//
+// The topology consists of 4 containers: curl (client), VPP (session layer), Envoy (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+	"fmt"
+	. "github.com/onsi/ginkgo/v2"
+	"reflect"
+	"runtime"
+	"strings"
+)
+
+const (
+	VppContainerName        = "vpp"
+	EnvoyProxyContainerName = "envoy-vcl"
+)
+
+type EnvoyProxySuite struct {
+	HstSuite
+	nginxPort uint16
+	proxyPort uint16
+}
+
+var envoyProxyTests = map[string][]func(s *EnvoyProxySuite){}
+var envoyProxySoloTests = map[string][]func(s *EnvoyProxySuite){}
+
+func RegisterEnvoyProxyTests(tests ...func(s *EnvoyProxySuite)) {
+	envoyProxyTests[getTestFilename()] = tests
+}
+
+func RegisterEnvoyProxySoloTests(tests ...func(s *EnvoyProxySuite)) {
+	envoyProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *EnvoyProxySuite) SetupSuite() {
+	s.HstSuite.SetupSuite()
+	s.LoadNetworkTopology("2taps")
+	s.LoadContainerTopology("envoyProxy")
+}
+
+func (s *EnvoyProxySuite) SetupTest() {
+	s.HstSuite.SetupTest()
+
+	// VPP
+	var sessionConfig Stanza
+	sessionConfig.
+		NewStanza("session").
+		Append("enable").
+		Append("use-app-socket-api").
+		Append("evt_qs_memfd_seg").
+		Append("event-queue-length 100000")
+
+	vppContainer := s.GetContainerByName(VppContainerName)
+	vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus, sessionConfig)
+	s.AssertNotNil(vpp, fmt.Sprint(err))
+	s.AssertNil(vpp.Start())
+	clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+	s.AssertNil(vpp.createTap(clientInterface, 1))
+	serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+	s.AssertNil(vpp.createTap(serverInterface, 2))
+	vppContainer.Exec("chmod 777 -R %s", vppContainer.GetContainerWorkDir())
+
+	// nginx HTTP server
+	nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+	s.AssertNil(nginxContainer.Create())
+	s.nginxPort = 80
+	nginxSettings := struct {
+		LogPrefix string
+		Address   string
+		Port      uint16
+	}{
+		LogPrefix: nginxContainer.Name,
+		Address:   serverInterface.Ip4AddressString(),
+		Port:      s.nginxPort,
+	}
+	nginxContainer.CreateConfig(
+		"/nginx.conf",
+		"./resources/nginx/nginx_server.conf",
+		nginxSettings,
+	)
+	s.AssertNil(nginxContainer.Start())
+
+	// Envoy
+	envoyContainer := s.GetContainerByName(EnvoyProxyContainerName)
+	s.AssertNil(envoyContainer.Create())
+	s.proxyPort = 8080
+	envoySettings := struct {
+		LogPrefix     string
+		ServerAddress string
+		ServerPort    uint16
+		ProxyPort     uint16
+	}{
+		LogPrefix:     envoyContainer.Name,
+		ServerAddress: serverInterface.Ip4AddressString(),
+		ServerPort:    s.nginxPort,
+		ProxyPort:     s.proxyPort,
+	}
+	envoyContainer.CreateConfig(
+		"/etc/envoy/envoy.yaml",
+		"resources/envoy/proxy.yaml",
+		envoySettings,
+	)
+	s.AssertNil(envoyContainer.Start())
+
+	// Add Ipv4 ARP entry for nginx HTTP server, otherwise first request fail (HTTP error 503)
+	arp := fmt.Sprintf("set ip neighbor %s %s %s",
+		serverInterface.Peer.Name(),
+		serverInterface.Ip4AddressString(),
+		serverInterface.HwAddress)
+	vppContainer.VppInstance.Vppctl(arp)
+}
+
+func (s *EnvoyProxySuite) TearDownTest() {
+	if CurrentSpecReport().Failed() {
+		s.CollectNginxLogs(NginxServerContainerName)
+		s.CollectEnvoyLogs(EnvoyProxyContainerName)
+	}
+	s.HstSuite.TearDownTest()
+}
+
+func (s *EnvoyProxySuite) ProxyPort() uint16 {
+	return s.proxyPort
+}
+
+func (s *EnvoyProxySuite) ProxyAddr() string {
+	return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *EnvoyProxySuite) CurlDownloadResource(uri string) {
+	args := fmt.Sprintf("--insecure --noproxy '*' --remote-name --output-dir /tmp %s", uri)
+	_, log := s.RunCurlContainer(args)
+	s.AssertNotContains(log, "Recv failure")
+	s.AssertContains(log, "HTTP/1.1 200")
+}
+
+var _ = Describe("EnvoyProxySuite", Ordered, ContinueOnFailure, func() {
+	var s EnvoyProxySuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	for filename, tests := range envoyProxyTests {
+		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("EnvoyProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+	var s EnvoyProxySuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	for filename, tests := range envoyProxySoloTests {
+		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/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go
new file mode 100644
index 0000000..bfe7de7
--- /dev/null
+++ b/extras/hs-test/infra/suite_vpp_proxy.go
@@ -0,0 +1,179 @@
+// Suite for VPP proxy testing
+//
+// The topology consists of 3 containers: curl (client), VPP (proxy), nginx (target HTTP server).
+// VPP has 2 tap interfaces configured, one for client network and second for server/target network.
+
+package hst
+
+import (
+	"fmt"
+	. "github.com/onsi/ginkgo/v2"
+	"reflect"
+	"runtime"
+	"strings"
+)
+
+// These correspond to names used in yaml config
+const (
+	ClientTapInterfaceName = "hstcln"
+	ServerTapInterfaceName = "hstsrv"
+)
+
+type VppProxySuite struct {
+	HstSuite
+	nginxPort uint16
+}
+
+var vppProxyTests = map[string][]func(s *VppProxySuite){}
+var vppProxySoloTests = map[string][]func(s *VppProxySuite){}
+
+func RegisterVppProxyTests(tests ...func(s *VppProxySuite)) {
+	vppProxyTests[getTestFilename()] = tests
+}
+
+func RegisterVppProxySoloTests(tests ...func(s *VppProxySuite)) {
+	vppProxySoloTests[getTestFilename()] = tests
+}
+
+func (s *VppProxySuite) SetupSuite() {
+	s.HstSuite.SetupSuite()
+	s.LoadNetworkTopology("2taps")
+	s.LoadContainerTopology("vppProxy")
+}
+
+func (s *VppProxySuite) SetupTest() {
+	s.HstSuite.SetupTest()
+
+	// VPP HTTP connect-proxy
+	vppContainer := s.GetContainerByName(VppProxyContainerName)
+	vpp, err := vppContainer.newVppInstance(vppContainer.AllocatedCpus)
+	s.AssertNotNil(vpp, fmt.Sprint(err))
+	s.AssertNil(vpp.Start())
+	clientInterface := s.GetInterfaceByName(ClientTapInterfaceName)
+	s.AssertNil(vpp.createTap(clientInterface, 1))
+	serverInterface := s.GetInterfaceByName(ServerTapInterfaceName)
+	s.AssertNil(vpp.createTap(serverInterface, 2))
+
+	// nginx HTTP server
+	nginxContainer := s.GetTransientContainerByName(NginxServerContainerName)
+	s.AssertNil(nginxContainer.Create())
+	s.nginxPort = 80
+	nginxSettings := struct {
+		LogPrefix string
+		Address   string
+		Port      uint16
+	}{
+		LogPrefix: nginxContainer.Name,
+		Address:   serverInterface.Ip4AddressString(),
+		Port:      s.nginxPort,
+	}
+	nginxContainer.CreateConfig(
+		"/nginx.conf",
+		"./resources/nginx/nginx_server.conf",
+		nginxSettings,
+	)
+	s.AssertNil(nginxContainer.Start())
+}
+
+func (s *VppProxySuite) TearDownTest() {
+	if CurrentSpecReport().Failed() {
+		s.CollectNginxLogs(NginxServerContainerName)
+	}
+	s.HstSuite.TearDownTest()
+}
+
+func (s *VppProxySuite) NginxPort() uint16 {
+	return s.nginxPort
+}
+
+func (s *VppProxySuite) NginxAddr() string {
+	return s.GetInterfaceByName(ServerTapInterfaceName).Ip4AddressString()
+}
+
+func (s *VppProxySuite) VppProxyAddr() string {
+	return s.GetInterfaceByName(ClientTapInterfaceName).Peer.Ip4AddressString()
+}
+
+func (s *VppProxySuite) CurlRequest(targetUri string) (string, string) {
+	args := fmt.Sprintf("--insecure --noproxy '*' %s", targetUri)
+	body, log := s.RunCurlContainer(args)
+	return body, log
+}
+
+func (s *VppProxySuite) CurlRequestViaTunnel(targetUri string, proxyUri string) (string, string) {
+	args := fmt.Sprintf("--insecure -p -x %s %s", proxyUri, targetUri)
+	body, log := s.RunCurlContainer(args)
+	return body, log
+}
+
+func (s *VppProxySuite) CurlDownloadResource(uri string) {
+	args := fmt.Sprintf("--insecure --noproxy '*' --remote-name --output-dir /tmp %s", uri)
+	_, log := s.RunCurlContainer(args)
+	s.AssertNotContains(log, "Recv failure")
+	s.AssertContains(log, "HTTP/1.1 200")
+}
+
+func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string) {
+	args := fmt.Sprintf("--insecure -p -x %s --remote-name --output-dir /tmp %s", proxyUri, uri)
+	_, log := s.RunCurlContainer(args)
+	s.AssertNotContains(log, "Recv failure")
+	s.AssertContains(log, "HTTP/1.1 200")
+}
+
+var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() {
+	var s VppProxySuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	for filename, tests := range vppProxyTests {
+		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("VppProxySuiteSolo", Ordered, ContinueOnFailure, func() {
+	var s VppProxySuite
+	BeforeAll(func() {
+		s.SetupSuite()
+	})
+	BeforeEach(func() {
+		s.SetupTest()
+	})
+	AfterAll(func() {
+		s.TearDownSuite()
+	})
+	AfterEach(func() {
+		s.TearDownTest()
+	})
+
+	for filename, tests := range vppProxySoloTests {
+		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 05b7b36..3df83f3 100644
--- a/extras/hs-test/infra/utils.go
+++ b/extras/hs-test/infra/utils.go
@@ -7,6 +7,7 @@
 	"net/http"
 	"net/http/httputil"
 	"os"
+	"os/exec"
 	"strings"
 	"time"
 )
@@ -126,3 +127,59 @@
 	}
 	return string(reply), nil
 }
+
+/*
+RunCurlContainer execute curl command with given args.
+Container with name "curl" must be available.
+Curl runs in verbose mode and progress meter switch off by default.
+*/
+func (s *HstSuite) RunCurlContainer(args string) (string, string) {
+	curlCont := s.GetContainerByName("curl")
+	cmd := fmt.Sprintf("curl -v -s %s", args)
+	s.Log(cmd)
+	curlCont.ExtraRunningArgs = cmd
+	curlCont.Run()
+	stdout, stderr := curlCont.GetOutput()
+	s.Log(stderr)
+	s.Log(stdout)
+	return stdout, stderr
+}
+
+/*
+CollectNginxLogs save access and error logs to the test execution directory.
+Nginx logging need to be set following way:
+
+  - error_log <default-work-dir>/{{.LogPrefix}}-error.log;
+  - access_log <default-work-dir>/{{.LogPrefix}}-access.log;
+
+where LogPrefix is set to nginxContainer.Name
+*/
+func (s *HstSuite) CollectNginxLogs(containerName string) {
+	nginxContainer := s.GetContainerByName(containerName)
+	targetDir := nginxContainer.getLogDirPath()
+	source := nginxContainer.GetHostWorkDir() + "/" + nginxContainer.Name + "-"
+	cmd := exec.Command("cp", "-t", targetDir, source+"error.log", source+"access.log")
+	s.Log(cmd.String())
+	err := cmd.Run()
+	if err != nil {
+		s.Log(fmt.Sprint(err))
+	}
+}
+
+/*
+CollectEnvoyLogs save access logs to the test execution directory.
+Envoy access log path need to be set following way:
+<default-work-dir>/{{.LogPrefix}}-access.log
+where LogPrefix is set to envoyContainer.Name
+*/
+func (s *HstSuite) CollectEnvoyLogs(containerName string) {
+	envoyContainer := s.GetContainerByName(containerName)
+	targetDir := envoyContainer.getLogDirPath()
+	source := envoyContainer.GetHostWorkDir() + "/" + envoyContainer.Name + "-"
+	cmd := exec.Command("cp", "-t", targetDir, source+"access.log")
+	s.Log(cmd.String())
+	err := cmd.Run()
+	if err != nil {
+		s.Log(fmt.Sprint(err))
+	}
+}
diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go
index e646a96..c09fe56 100644
--- a/extras/hs-test/proxy_test.go
+++ b/extras/hs-test/proxy_test.go
@@ -3,112 +3,41 @@
 import (
 	. "fd.io/hs-test/infra"
 	"fmt"
-	"github.com/edwarnicke/exechelper"
-	. "github.com/onsi/ginkgo/v2"
-	"os"
 )
 
 func init() {
-	RegisterNsTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest, EnvoyProxyHttpTcpTest)
+	RegisterVppProxyTests(VppProxyHttpTcpTest, VppProxyHttpTlsTest)
+	RegisterEnvoyProxyTests(EnvoyProxyHttpTcpTest)
 }
 
-func testProxyHttpTcp(s *NsSuite, proto string) error {
-	var outputFile string = s.ProcessIndex + "test" + s.Ppid + ".data"
-	var srcFilePpid string = s.ProcessIndex + "httpTestFile" + s.Ppid
-	const srcFileNoPpid = "httpTestFile"
-	const fileSize string = "10M"
-	stopServer := make(chan struct{}, 1)
-	serverRunning := make(chan struct{}, 1)
-	serverNetns := s.GetNetNamespaceByName("srv")
-	clientNetns := s.GetNetNamespaceByName("cln")
-
-	// create test file
-	err := exechelper.Run(fmt.Sprintf("ip netns exec %s truncate -s %s %s", serverNetns, fileSize, srcFilePpid))
-	s.AssertNil(err, "failed to run truncate command: "+fmt.Sprint(err))
-	defer func() { os.Remove(srcFilePpid) }()
-
-	s.Log("test file created...")
-
-	go func() {
-		defer GinkgoRecover()
-		s.StartHttpServer(serverRunning, stopServer, ":666", serverNetns)
-	}()
-	// TODO better error handling and recovery
-	<-serverRunning
-
-	defer func(chan struct{}) {
-		stopServer <- struct{}{}
-	}(stopServer)
-
-	s.Log("http server started...")
-
-	clientVeth := s.GetInterfaceByName(ClientInterface)
-	c := fmt.Sprintf("ip netns exec %s wget --no-proxy --retry-connrefused"+
-		" --retry-on-http-error=503 --tries=10 -O %s ", clientNetns, outputFile)
-	if proto == "tls" {
-		c += " --secure-protocol=TLSv1_3 --no-check-certificate https://"
-	}
-	c += fmt.Sprintf("%s:555/%s", clientVeth.Ip4AddressString(), srcFileNoPpid)
-	s.Log(c)
-	_, err = exechelper.CombinedOutput(c)
-
-	defer func() { os.Remove(outputFile) }()
-
-	s.AssertNil(err, "failed to run wget: '%s', cmd: %s", err, c)
-	stopServer <- struct{}{}
-
-	s.AssertNil(AssertFileSize(outputFile, srcFilePpid))
-	return nil
-}
-
-func configureVppProxy(s *NsSuite, proto string) {
-	serverVeth := s.GetInterfaceByName(ServerInterface)
-	clientVeth := s.GetInterfaceByName(ClientInterface)
-
-	testVppProxy := s.GetContainerByName("vpp").VppInstance
-	output := testVppProxy.Vppctl(
-		"test proxy server server-uri %s://%s/555 client-uri tcp://%s/666",
+func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) {
+	vppProxy := s.GetContainerByName(VppProxyContainerName).VppInstance
+	output := vppProxy.Vppctl(
+		"test proxy server server-uri %s://%s/%d client-uri tcp://%s/%d",
 		proto,
-		clientVeth.Ip4AddressString(),
-		serverVeth.Peer.Ip4AddressString(),
+		s.VppProxyAddr(),
+		proxyPort,
+		s.NginxAddr(),
+		s.NginxPort(),
 	)
 	s.Log("proxy configured: " + output)
 }
 
-func VppProxyHttpTcpTest(s *NsSuite) {
-	proto := "tcp"
-	configureVppProxy(s, proto)
-	err := testProxyHttpTcp(s, proto)
-	s.AssertNil(err, fmt.Sprint(err))
+func VppProxyHttpTcpTest(s *VppProxySuite) {
+	var proxyPort uint16 = 8080
+	configureVppProxy(s, "tcp", proxyPort)
+	uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+	s.CurlDownloadResource(uri)
 }
 
-func VppProxyHttpTlsTest(s *NsSuite) {
-	proto := "tls"
-	configureVppProxy(s, proto)
-	err := testProxyHttpTcp(s, proto)
-	s.AssertNil(err, fmt.Sprint(err))
+func VppProxyHttpTlsTest(s *VppProxySuite) {
+	var proxyPort uint16 = 8080
+	configureVppProxy(s, "tls", proxyPort)
+	uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort)
+	s.CurlDownloadResource(uri)
 }
 
-func configureEnvoyProxy(s *NsSuite) {
-	envoyContainer := s.GetContainerByName("envoy")
-	s.AssertNil(envoyContainer.Create())
-
-	serverVeth := s.GetInterfaceByName(ServerInterface)
-	address := struct {
-		Server string
-	}{
-		Server: serverVeth.Peer.Ip4AddressString(),
-	}
-	envoyContainer.CreateConfig(
-		"/etc/envoy/envoy.yaml",
-		"resources/envoy/proxy.yaml",
-		address,
-	)
-	s.AssertNil(envoyContainer.Start())
-}
-
-func EnvoyProxyHttpTcpTest(s *NsSuite) {
-	configureEnvoyProxy(s)
-	err := testProxyHttpTcp(s, "tcp")
-	s.AssertNil(err, fmt.Sprint(err))
+func EnvoyProxyHttpTcpTest(s *EnvoyProxySuite) {
+	uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort())
+	s.CurlDownloadResource(uri)
 }
diff --git a/extras/hs-test/resources/envoy/proxy.yaml b/extras/hs-test/resources/envoy/proxy.yaml
index 41fc004..67eb690 100644
--- a/extras/hs-test/resources/envoy/proxy.yaml
+++ b/extras/hs-test/resources/envoy/proxy.yaml
@@ -1,17 +1,15 @@
 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
+        port_value: {{.ProxyPort}}
     filter_chains:
     - filters:
       - name: envoy.filters.network.http_connection_manager
@@ -32,6 +30,11 @@
           - name: envoy.filters.http.router
             typed_config:
               "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+          access_log:
+          - name: envoy.access_loggers.file
+            typed_config:
+              "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+              path: /tmp/vpp-envoy/{{.LogPrefix}}-access.log
   clusters:
   - name: proxy_service
     connect_timeout: 0.25s
@@ -45,9 +48,8 @@
         - endpoint:
             address:
               socket_address:
-                # following address will be generated by Addresser during test run
-                address: {{.Server}}
-                port_value: 666
+                address: {{.ServerAddress}}
+                port_value: {{.ServerPort}}
 bootstrap_extensions:
   - name: envoy.extensions.vcl.vcl_socket_interface
     typed_config:
diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf
new file mode 100644
index 0000000..80ea602
--- /dev/null
+++ b/extras/hs-test/resources/nginx/nginx_server.conf
@@ -0,0 +1,32 @@
+master_process on;
+worker_rlimit_nofile 10240;
+worker_processes 2;
+daemon off;
+
+error_log /tmp/nginx/{{.LogPrefix}}-error.log info;
+
+events {
+  use epoll;
+  worker_connections  10240;
+  accept_mutex       off;
+  multi_accept       off;
+}
+
+http {
+  keepalive_timeout 300s;
+  keepalive_requests 1000000;
+  sendfile on;
+  server {
+    access_log /tmp/nginx/{{.LogPrefix}}-access.log;
+    listen {{.Port}};
+    server_name {{.Address}};
+    root /usr/share/nginx;
+    index index.html index.htm;
+    location /64B {
+      return 200 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+    }
+    location / {
+      sendfile on;
+    }
+  }
+}
diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh
index e7e8a0f..9b11f5f 100755
--- a/extras/hs-test/script/build_hst.sh
+++ b/extras/hs-test/script/build_hst.sh
@@ -84,10 +84,9 @@
 docker_build hs-test/vpp vpp
 docker_build hs-test/nginx-ldp nginx
 docker_build hs-test/nginx-server nginx-server
-docker_build hs-test/build build
+docker_build hs-test/curl curl
 if [ "$HST_EXTENDED_TESTS" = true ] ; then
     docker_build hs-test/nginx-http3 nginx-http3
-    docker_build hs-test/curl curl
 fi
 
 # cleanup detached images
diff --git a/extras/hs-test/script/nginx_mirroring_entrypoint.sh b/extras/hs-test/script/nginx_server_entrypoint.sh
similarity index 100%
rename from extras/hs-test/script/nginx_mirroring_entrypoint.sh
rename to extras/hs-test/script/nginx_server_entrypoint.sh
diff --git a/extras/hs-test/topo-containers/envoyProxy.yaml b/extras/hs-test/topo-containers/envoyProxy.yaml
new file mode 100644
index 0000000..4980807
--- /dev/null
+++ b/extras/hs-test/topo-containers/envoyProxy.yaml
@@ -0,0 +1,40 @@
+---
+volumes:
+  - volume: &shared-vol-envoy-proxy
+      host-dir: "$HST_VOLUME_DIR/shared-vol-envoy-proxy"
+
+containers:
+  - name: "vpp"
+    volumes:
+      - <<: *shared-vol-envoy-proxy
+        container-dir: "/tmp/vpp"
+        is-default-work-dir: true
+  - name: "envoy-vcl"
+    volumes:
+      - <<: *shared-vol-envoy-proxy
+        container-dir: "/tmp/vpp-envoy"
+        is-default-work-dir: true
+      - host-dir: "$HST_DIR/resources/envoy"
+        container-dir: "/tmp"
+    vars:
+      - name: "ENVOY_UID"
+        value: "0"
+      - name: "VCL_CONFIG"
+        value: "/tmp/vcl.conf"
+    image: "envoyproxy/envoy-contrib:v1.30-latest"
+    extra-args: "--log-format [%t][%l][%g:%#]%_ --concurrency 2 -c /etc/envoy/envoy.yaml"
+    is-optional: true
+  - name: "nginx-server"
+    volumes:
+      - <<: *shared-vol-envoy-proxy
+        container-dir: "/tmp/nginx"
+        is-default-work-dir: true
+    image: "hs-test/nginx-server"
+    is-optional: true
+  - name: "curl"
+    vars:
+      - name: LD_LIBRARY_PATH
+        value: "/usr/local/lib"
+    image: "hs-test/curl"
+    is-optional: true
+    run-detached: false
diff --git a/extras/hs-test/topo-containers/vppProxy.yaml b/extras/hs-test/topo-containers/vppProxy.yaml
new file mode 100644
index 0000000..c4bbbe9
--- /dev/null
+++ b/extras/hs-test/topo-containers/vppProxy.yaml
@@ -0,0 +1,26 @@
+---
+volumes:
+  - volume: &shared-vol-vpp-proxy
+      host-dir: "$HST_VOLUME_DIR/shared-vol-vpp-proxy"
+
+containers:
+  - name: "vpp-proxy"
+    volumes:
+      - <<: *shared-vol-vpp-proxy
+        container-dir: "/tmp/vpp"
+        is-default-work-dir: true
+  - name: "nginx-server"
+    volumes:
+      - <<: *shared-vol-vpp-proxy
+        container-dir: "/tmp/nginx"
+        is-default-work-dir: true
+    image: "hs-test/nginx-server"
+    is-optional: true
+  - name: "curl"
+    vars:
+      - name: LD_LIBRARY_PATH
+        value: "/usr/local/lib"
+    image: "hs-test/curl"
+    is-optional: true
+    run-detached: false
+