mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
273 lines
7.6 KiB
Go
273 lines
7.6 KiB
Go
// Copyright 2023 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package portforward_test holds a docker test for port forward. It is separate
|
|
// from other root tests so that both hostinet and netstack can be tested in
|
|
// one Makefile target.
|
|
package portforward_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/moby/sys/capability"
|
|
"golang.org/x/sync/errgroup"
|
|
"gvisor.dev/gvisor/pkg/cleanup"
|
|
"gvisor.dev/gvisor/pkg/test/dockerutil"
|
|
"gvisor.dev/gvisor/runsc/config"
|
|
"gvisor.dev/gvisor/runsc/flag"
|
|
"gvisor.dev/gvisor/runsc/specutils"
|
|
)
|
|
|
|
func TestPortForwardLocalMode(t *testing.T) {
|
|
ctx := context.Background()
|
|
server := dockerutil.MakeContainer(ctx, t)
|
|
defer server.CleanUp(ctx)
|
|
|
|
redisPort := 6379
|
|
if err := server.Spawn(ctx, dockerutil.RunOpts{
|
|
Image: "basic/redis",
|
|
}); err != nil {
|
|
t.Fatalf("failed to create redis server: %v", err)
|
|
}
|
|
|
|
if err := waitUntilServerIsUp(ctx, server, redisPort); err != nil {
|
|
t.Fatalf("failed to wait for redis server to be up: %v", err)
|
|
}
|
|
|
|
localPort, err := getUnusedPort()
|
|
if err != nil {
|
|
t.Fatalf("failed to pick unused port: %v", err)
|
|
}
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
pf, err := newPortForwardProcess(ctx, server, localPort, redisPort)
|
|
if err != nil {
|
|
t.Fatalf("failed to create port forward process: %v", err)
|
|
}
|
|
|
|
var g errgroup.Group
|
|
g.Go(func() error {
|
|
// To end this test, we kill the portforward process, which will result in a "signal: killed"
|
|
// error. Just ignore this error.
|
|
if err := pf.Wait(); err != nil && !strings.Contains(err.Error(), "signal: killed") {
|
|
return fmt.Errorf("portforward command: err: %v process error: %v out: %s", err, pf.Error(), pf.Output())
|
|
}
|
|
return nil
|
|
})
|
|
cu := cleanup.Make(pf.Kill)
|
|
defer cu.Clean()
|
|
|
|
client := dockerutil.MakeNativeContainer(ctx, t)
|
|
defer client.CleanUp(ctx)
|
|
|
|
out, err := client.Run(ctx, dockerutil.RunOpts{
|
|
Image: "basic/redis",
|
|
NetworkMode: "host",
|
|
}, "redis-cli", "--verbose", "-p", fmt.Sprintf("%d", localPort), "-i", "1", "-r", "5", "ping")
|
|
|
|
if err != nil {
|
|
t.Logf("portforward command: err: %v out: %s", pf.Error(), pf.Output())
|
|
t.Fatalf("failed to run client: %v out: %s", err, out)
|
|
}
|
|
|
|
if !strings.Contains(out, "PONG") {
|
|
t.Logf("portforward command: err: %v out: %s", pf.Error(), pf.Output())
|
|
t.Fatalf("could not reach redis server: %s", out)
|
|
}
|
|
|
|
cu.Clean()
|
|
if err := g.Wait(); err != nil {
|
|
t.Fatalf("failed to kill portforward process: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPortForwardStreamMode(t *testing.T) {
|
|
ctx := context.Background()
|
|
sockAddrDir, err := os.MkdirTemp("", "temp-")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(sockAddrDir)
|
|
sockAddr := path.Join(sockAddrDir, "echo.sock")
|
|
|
|
server := dockerutil.MakeContainer(ctx, t)
|
|
defer server.CleanUp(ctx)
|
|
|
|
nginxPort := 80
|
|
if err := server.Spawn(ctx, dockerutil.RunOpts{
|
|
Image: "basic/nginx",
|
|
}); err != nil {
|
|
t.Fatalf("failed to create nginx server: %v", err)
|
|
}
|
|
|
|
if err := waitUntilServerIsUp(ctx, server, nginxPort); err != nil {
|
|
t.Fatalf("failed to wait for nginx server to be up: %v", err)
|
|
}
|
|
|
|
socket, err := net.Listen("unix", sockAddr)
|
|
if err != nil {
|
|
t.Fatalf("failed to listen: %v", err)
|
|
}
|
|
defer socket.Close()
|
|
|
|
pf, err := newPortForwardStreamProcess(ctx, server, sockAddr, nginxPort)
|
|
if err != nil {
|
|
t.Fatalf("failed to create port forward process: %v", err)
|
|
}
|
|
|
|
if err := pf.Wait(); err != nil {
|
|
t.Fatalf("failed to wait: %v out: %s", err, pf.Output())
|
|
}
|
|
|
|
conn, err := socket.Accept()
|
|
if err != nil {
|
|
t.Fatalf("failed to accept: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
const getMsg = "GET / HTTP/1.0\r\n\r\n"
|
|
if n, err := io.Copy(conn, bytes.NewBufferString(getMsg)); err != nil {
|
|
t.Fatalf("failed to copy: %v n: %d", err, n)
|
|
}
|
|
|
|
buf, err := io.ReadAll(conn)
|
|
if err != nil {
|
|
t.Fatalf("failed to read: %v out: %s", err, string(buf))
|
|
}
|
|
|
|
const want = "Thank you for using nginx."
|
|
if !strings.Contains(string(buf), want) {
|
|
t.Fatalf("could not find %q in output: %s", want, string(buf))
|
|
}
|
|
}
|
|
|
|
func getUnusedPort() (int, error) {
|
|
l, err := net.Listen("tcp", ":0")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer l.Close()
|
|
return l.Addr().(*net.TCPAddr).Port, nil
|
|
}
|
|
|
|
func waitUntilServerIsUp(ctx context.Context, server *dockerutil.Container, port int) error {
|
|
// This is a bit crude, but we need to make sure the server is up without exposing a port to the
|
|
// host. When the server container boots, the nginx process should run first. If we run nginx
|
|
// again, it will fail to bind to port 80. Run exec calls until we get that failure.
|
|
serverUpChan := make(chan struct{})
|
|
var upOut string
|
|
var upErr error
|
|
reg := regexp.MustCompile(fmt.Sprintf(`0\.0\.0\.0:%d[\s]*0\.0\.0\.0:\*[\s]*LISTEN`, port))
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 500)
|
|
upOut, upErr = server.Exec(ctx, dockerutil.ExecOpts{}, []string{"netstat", "-l"}...)
|
|
if reg.MatchString(upOut) {
|
|
close(serverUpChan)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// If the server isn't up after 30 seconds, there is probably something wrong.
|
|
select {
|
|
case <-serverUpChan:
|
|
break
|
|
case <-time.After(time.Second * 30):
|
|
return fmt.Errorf("could not verify server is up: err: %v out: %s", upErr, upOut)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type portForwardProcess struct {
|
|
cmd *exec.Cmd
|
|
buf bytes.Buffer
|
|
}
|
|
|
|
func newPortForwardProcess(ctx context.Context, c *dockerutil.Container, localPort, containerPort int) (*portForwardProcess, error) {
|
|
rootDir, err := c.RootDirectory()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args := []string{"-root", rootDir, "port-forward", c.ID(), fmt.Sprintf("%d:%d", localPort, containerPort)}
|
|
return startPortForwardPorcess(ctx, args)
|
|
}
|
|
|
|
func newPortForwardStreamProcess(ctx context.Context, c *dockerutil.Container, uds string, containerPort int) (*portForwardProcess, error) {
|
|
rootDir, err := c.RootDirectory()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args := []string{"-root", rootDir, "-alsologtostderr", "port-forward", "-stream", uds, c.ID(), fmt.Sprintf("%d", containerPort)}
|
|
return startPortForwardPorcess(ctx, args)
|
|
}
|
|
|
|
func startPortForwardPorcess(ctx context.Context, args []string) (*portForwardProcess, error) {
|
|
cmd := exec.CommandContext(ctx, specutils.ExePath, args...)
|
|
ret := &portForwardProcess{cmd: cmd}
|
|
ret.cmd.Stdout = &ret.buf
|
|
ret.cmd.Stderr = &ret.buf
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (p *portForwardProcess) Close() error {
|
|
return p.cmd.Wait()
|
|
}
|
|
|
|
func (p *portForwardProcess) Wait() error { return p.cmd.Wait() }
|
|
|
|
func (p *portForwardProcess) Kill() { p.cmd.Process.Kill() }
|
|
|
|
func (p *portForwardProcess) Output() string { return p.buf.String() }
|
|
|
|
func (p *portForwardProcess) Error() error { return p.cmd.Err }
|
|
|
|
func TestMain(m *testing.M) {
|
|
config.RegisterFlags(flag.CommandLine)
|
|
if !flag.CommandLine.Parsed() {
|
|
flag.Parse()
|
|
}
|
|
|
|
if !specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_DAC_OVERRIDE) {
|
|
fmt.Println("Test requires sysadmin privileges to run. Try again with sudo.")
|
|
os.Exit(1)
|
|
}
|
|
|
|
dockerutil.EnsureSupportedDockerVersion()
|
|
|
|
// Configure exe for tests.
|
|
path, err := dockerutil.RuntimePath()
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
specutils.ExePath = path
|
|
|
|
os.Exit(m.Run())
|
|
}
|