mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Created runsc and docker integration tests.
Moved some of the docker image functions to testutil.go. Test runsc commands create, start, stop, pause, and resume. PiperOrigin-RevId: 204138452 Change-Id: Id00bc58d2ad230db5e9e905eed942187e68e7c7b
This commit is contained in:
committed by
Shentubot
parent
9cd69c2f3d
commit
81ae5f3df5
+35
-187
@@ -15,7 +15,7 @@
|
||||
// Package image provides end-to-end image tests for runsc. These tests require
|
||||
// docker and runsc to be installed on the machine. To set it up, run:
|
||||
//
|
||||
// ./runsc/test/image/install.sh [--runtime <name>]
|
||||
// ./runsc/test/install.sh [--runtime <name>]
|
||||
//
|
||||
// The tests expect the runtime name to be provided in the RUNSC_RUNTIME
|
||||
// environment variable (default: runsc-test).
|
||||
@@ -28,14 +28,8 @@ package image
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -43,144 +37,14 @@ import (
|
||||
"gvisor.googlesource.com/gvisor/runsc/test/testutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func runtime() string {
|
||||
r := os.Getenv("RUNSC_RUNTIME")
|
||||
if r == "" {
|
||||
return "runsc-test"
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func mountArg(source, target string) string {
|
||||
return fmt.Sprintf("%s:%s", source, target)
|
||||
}
|
||||
|
||||
func linkArg(source *docker, target string) string {
|
||||
return fmt.Sprintf("%s:%s", source.name, target)
|
||||
}
|
||||
|
||||
// prepareFiles creates temp directory to copy files there. The sandbox doesn't
|
||||
// have access to files in the test dir.
|
||||
func prepareFiles(names ...string) (string, error) {
|
||||
dir, err := ioutil.TempDir("", "image-test")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ioutil.TempDir failed: %v", err)
|
||||
}
|
||||
if err := os.Chmod(dir, 0777); err != nil {
|
||||
return "", fmt.Errorf("os.Chmod(%q, 0777) failed: %v", dir, err)
|
||||
}
|
||||
for _, name := range names {
|
||||
src := getLocalPath(name)
|
||||
dst := path.Join(dir, name)
|
||||
if err := testutil.Copy(src, dst); err != nil {
|
||||
return "", fmt.Errorf("testutil.Copy(%q, %q) failed: %v", src, dst, err)
|
||||
}
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func getLocalPath(file string) string {
|
||||
return path.Join(".", file)
|
||||
}
|
||||
|
||||
type docker struct {
|
||||
runtime string
|
||||
name string
|
||||
}
|
||||
|
||||
func makeDocker(namePrefix string) docker {
|
||||
suffix := fmt.Sprintf("-%06d", rand.Int())[:7]
|
||||
return docker{name: namePrefix + suffix, runtime: runtime()}
|
||||
}
|
||||
|
||||
// do executes docker command.
|
||||
func (d *docker) do(args ...string) (string, error) {
|
||||
fmt.Printf("Running: docker %s\n", args)
|
||||
cmd := exec.Command("docker", args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing docker %s: %v", args, err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// run calls 'docker run' with the arguments provided.
|
||||
func (d *docker) run(args ...string) (string, error) {
|
||||
a := []string{"run", "--runtime", d.runtime, "--name", d.name, "-d"}
|
||||
a = append(a, args...)
|
||||
return d.do(a...)
|
||||
}
|
||||
|
||||
// cleanUp kills and deletes the container.
|
||||
func (d *docker) cleanUp() error {
|
||||
if _, err := d.do("kill", d.name); err != nil {
|
||||
return fmt.Errorf("error killing container %q: %v", d.name, err)
|
||||
}
|
||||
if _, err := d.do("rm", d.name); err != nil {
|
||||
return fmt.Errorf("error deleting container %q: %v", d.name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findPort returns the host port that is mapped to 'sandboxPort'. This calls
|
||||
// docker to allocate a free port in the host and prevent conflicts.
|
||||
func (d *docker) findPort(sandboxPort int) (int, error) {
|
||||
format := fmt.Sprintf(`{{ (index (index .NetworkSettings.Ports "%d/tcp") 0).HostPort }}`, sandboxPort)
|
||||
out, err := d.do("inspect", "-f", format, d.name)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error retrieving port: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error parsing port %q: %v", out, err)
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// waitForOutput calls 'docker logs' to retrieve containers output and searches
|
||||
// for the given pattern.
|
||||
func (d *docker) waitForOutput(pattern string, timeout time.Duration) error {
|
||||
re := regexp.MustCompile(pattern)
|
||||
var out string
|
||||
for exp := time.Now().Add(timeout); time.Now().Before(exp); {
|
||||
var err error
|
||||
out, err = d.do("logs", d.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if re.MatchString(out) {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for output %q: %s", re.String(), out)
|
||||
}
|
||||
|
||||
func (d *docker) waitForHTTP(port int, timeout time.Duration) error {
|
||||
for exp := time.Now().Add(timeout); time.Now().Before(exp); {
|
||||
url := fmt.Sprintf("http://localhost:%d/", port)
|
||||
if _, err := http.Get(url); err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for HTTP server on port %d", port)
|
||||
}
|
||||
|
||||
func TestHelloWorld(t *testing.T) {
|
||||
d := makeDocker("hello-test")
|
||||
if out, err := d.run("hello-world"); err != nil {
|
||||
d := testutil.MakeDocker("hello-test")
|
||||
if out, err := d.Run("hello-world"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.cleanUp()
|
||||
defer d.CleanUp()
|
||||
|
||||
if err := d.waitForOutput("Hello from Docker!", 5*time.Second); err != nil {
|
||||
if err := d.WaitForOutput("Hello from Docker!", 5*time.Second); err != nil {
|
||||
t.Fatalf("docker didn't say hello: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -218,27 +82,27 @@ func testHTTPServer(port int) error {
|
||||
}
|
||||
|
||||
func TestHttpd(t *testing.T) {
|
||||
d := makeDocker("http-test")
|
||||
d := testutil.MakeDocker("http-test")
|
||||
|
||||
dir, err := prepareFiles("latin10k.txt")
|
||||
dir, err := testutil.PrepareFiles("latin10k.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("prepareFiles() failed: %v", err)
|
||||
t.Fatalf("PrepareFiles() failed: %v", err)
|
||||
}
|
||||
|
||||
// Start the container.
|
||||
if out, err := d.run("-p", "80", "-v", mountArg(dir, "/usr/local/apache2/htdocs:ro"), "httpd"); err != nil {
|
||||
if out, err := d.Run("-p", "80", "-v", testutil.MountArg(dir, "/usr/local/apache2/htdocs:ro"), "httpd"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.cleanUp()
|
||||
defer d.CleanUp()
|
||||
|
||||
// Find where port 80 is mapped to.
|
||||
port, err := d.findPort(80)
|
||||
port, err := d.FindPort(80)
|
||||
if err != nil {
|
||||
t.Fatalf("docker.findPort(80) failed: %v", err)
|
||||
t.Fatalf("docker.FindPort(80) failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.waitForHTTP(port, 5*time.Second); err != nil {
|
||||
if err := d.WaitForHTTP(port, 5*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForHTTP() timeout: %v", err)
|
||||
}
|
||||
|
||||
@@ -248,27 +112,27 @@ func TestHttpd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNginx(t *testing.T) {
|
||||
d := makeDocker("net-test")
|
||||
d := testutil.MakeDocker("net-test")
|
||||
|
||||
dir, err := prepareFiles("latin10k.txt")
|
||||
dir, err := testutil.PrepareFiles("latin10k.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("prepareFiles() failed: %v", err)
|
||||
t.Fatalf("PrepareFiles() failed: %v", err)
|
||||
}
|
||||
|
||||
// Start the container.
|
||||
if out, err := d.run("-p", "80", "-v", mountArg(dir, "/usr/share/nginx/html:ro"), "nginx"); err != nil {
|
||||
if out, err := d.Run("-p", "80", "-v", testutil.MountArg(dir, "/usr/share/nginx/html:ro"), "nginx"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.cleanUp()
|
||||
defer d.CleanUp()
|
||||
|
||||
// Find where port 80 is mapped to.
|
||||
port, err := d.findPort(80)
|
||||
port, err := d.FindPort(80)
|
||||
if err != nil {
|
||||
t.Fatalf("docker.findPort(80) failed: %v", err)
|
||||
t.Fatalf("docker.FindPort(80) failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.waitForHTTP(port, 5*time.Second); err != nil {
|
||||
if err := d.WaitForHTTP(port, 5*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForHTTP() timeout: %v", err)
|
||||
}
|
||||
|
||||
@@ -278,64 +142,48 @@ func TestNginx(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMysql(t *testing.T) {
|
||||
d := makeDocker("mysql-test")
|
||||
d := testutil.MakeDocker("mysql-test")
|
||||
|
||||
// Start the container.
|
||||
if out, err := d.run("-e", "MYSQL_ROOT_PASSWORD=foobar123", "mysql"); err != nil {
|
||||
if out, err := d.Run("-e", "MYSQL_ROOT_PASSWORD=foobar123", "mysql"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.cleanUp()
|
||||
defer d.CleanUp()
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.waitForOutput("port: 3306 MySQL Community Server", 3*time.Minute); err != nil {
|
||||
if err := d.WaitForOutput("port: 3306 MySQL Community Server", 3*time.Minute); err != nil {
|
||||
t.Fatalf("docker.WaitForOutput() timeout: %v", err)
|
||||
}
|
||||
|
||||
client := makeDocker("mysql-client-test")
|
||||
dir, err := prepareFiles("mysql.sql")
|
||||
client := testutil.MakeDocker("mysql-client-test")
|
||||
dir, err := testutil.PrepareFiles("mysql.sql")
|
||||
if err != nil {
|
||||
t.Fatalf("prepareFiles() failed: %v", err)
|
||||
t.Fatalf("PrepareFiles() failed: %v", err)
|
||||
}
|
||||
|
||||
// Tell mysql client to connect to the server and execute the file in verbose
|
||||
// mode to verify the output.
|
||||
args := []string{
|
||||
"--link", linkArg(&d, "mysql"),
|
||||
"-v", mountArg(dir, "/sql"),
|
||||
"--link", testutil.LinkArg(&d, "mysql"),
|
||||
"-v", testutil.MountArg(dir, "/sql"),
|
||||
"mysql",
|
||||
"mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql",
|
||||
}
|
||||
if out, err := client.run(args...); err != nil {
|
||||
if out, err := client.Run(args...); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer client.cleanUp()
|
||||
defer client.CleanUp()
|
||||
|
||||
// Ensure file executed to the end and shutdown mysql.
|
||||
if err := client.waitForOutput("--------------\nshutdown\n--------------", 15*time.Second); err != nil {
|
||||
if err := client.WaitForOutput("--------------\nshutdown\n--------------", 15*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForOutput() timeout: %v", err)
|
||||
}
|
||||
if err := d.waitForOutput("mysqld: Shutdown complete", 30*time.Second); err != nil {
|
||||
if err := d.WaitForOutput("mysqld: Shutdown complete", 30*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForOutput() timeout: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
// Check correct docker is installed.
|
||||
cmd := exec.Command("docker", "version")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Error running %q: %v", "docker version", err)
|
||||
}
|
||||
re := regexp.MustCompile(`Version:\s+(\d+)\.(\d+)\.\d.*`)
|
||||
matches := re.FindStringSubmatch(string(out))
|
||||
if len(matches) != 3 {
|
||||
log.Fatalf("Invalid docker output: %s", out)
|
||||
}
|
||||
major, _ := strconv.Atoi(matches[1])
|
||||
minor, _ := strconv.Atoi(matches[2])
|
||||
if major < 17 || (major == 17 && minor < 9) {
|
||||
log.Fatalf("Docker version 17.09.0 or greater is required, found: %02d.%02d", major, minor)
|
||||
}
|
||||
|
||||
testutil.EnsureSupportedDockerVersion()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -19,23 +19,25 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.googlesource.com/gvisor/runsc/test/testutil"
|
||||
)
|
||||
|
||||
func TestPythonHello(t *testing.T) {
|
||||
d := makeDocker("python-hello-test")
|
||||
if out, err := d.run("-p", "8080", "google/python-hello"); err != nil {
|
||||
d := testutil.MakeDocker("python-hello-test")
|
||||
if out, err := d.Run("-p", "8080", "google/python-hello"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.cleanUp()
|
||||
defer d.CleanUp()
|
||||
|
||||
// Find where port 8080 is mapped to.
|
||||
port, err := d.findPort(8080)
|
||||
port, err := d.FindPort(8080)
|
||||
if err != nil {
|
||||
t.Fatalf("docker.findPort(8080) failed: %v", err)
|
||||
t.Fatalf("docker.FindPort(8080) failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.waitForHTTP(port, 5*time.Second); err != nil {
|
||||
if err := d.WaitForHTTP(port, 5*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForHTTP() timeout: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package(licenses = ["notice"]) # Apache 2.0
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_test(
|
||||
name = "integration_test",
|
||||
size = "large",
|
||||
srcs = [
|
||||
"integration_test.go",
|
||||
],
|
||||
embed = [":integration"],
|
||||
tags = [
|
||||
# Requires docker and runsc to be configured before the test runs.
|
||||
"manual",
|
||||
"local",
|
||||
],
|
||||
deps = [
|
||||
"//runsc/test/testutil",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "integration",
|
||||
srcs = ["integration.go"],
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/test/integration",
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// 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 integration is empty. See integration_test.go for description.
|
||||
package integration
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// 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 image provides end-to-end integration tests for runsc. These tests require
|
||||
// docker and runsc to be installed on the machine. To set it up, run:
|
||||
//
|
||||
// ./runsc/test/install.sh [--runtime <name>]
|
||||
//
|
||||
// The tests expect the runtime name to be provided in the RUNSC_RUNTIME
|
||||
// environment variable (default: runsc-test).
|
||||
//
|
||||
// Each test calls docker commands to start up a container, and tests that it is
|
||||
// behaving properly, with various runsc commands. The container is killed and deleted
|
||||
// at the end.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.googlesource.com/gvisor/runsc/test/testutil"
|
||||
)
|
||||
|
||||
// This container is a docker image for the Flask microframework hello world application.
|
||||
const container = "python-hello-test"
|
||||
|
||||
// httpRequestSucceeds sends a request to a given url and checks that the status is OK.
|
||||
func httpRequestSucceeds(client http.Client, url string) error {
|
||||
// Ensure that content is being served.
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reaching http server: %v", err)
|
||||
}
|
||||
if want := http.StatusOK; resp.StatusCode != want {
|
||||
return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestLifeCycle tests a basic Create/Start/Stop docker container life cycle.
|
||||
func TestLifeCycle(t *testing.T) {
|
||||
d := testutil.MakeDocker(container)
|
||||
|
||||
// Test docker create.
|
||||
if out, err := d.Do("create", "--runtime", d.Runtime, "--name", d.Name, "-p", "8080", "google/python-hello"); err != nil {
|
||||
t.Fatalf("docker create failed: %v\nout: %s", err, out)
|
||||
}
|
||||
|
||||
// Test docker start.
|
||||
if out, err := d.Do("start", d.Name); err != nil {
|
||||
d.CleanUp()
|
||||
t.Fatalf("docker start failed: %v\nout: %s", err, out)
|
||||
}
|
||||
|
||||
// Test docker stop.
|
||||
if out, err := d.Do("stop", d.Name); err != nil {
|
||||
d.CleanUp()
|
||||
t.Fatalf("docker stop failed: %v\nout: %s", err, out)
|
||||
}
|
||||
|
||||
// Test removing the container.
|
||||
if out, err := d.Do("rm", d.Name); err != nil {
|
||||
t.Fatalf("docker rm failed: %v\nout: %s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPauseResume(t *testing.T) {
|
||||
d := testutil.MakeDocker(container)
|
||||
if out, err := d.Run("-p", "8080", "google/python-hello"); err != nil {
|
||||
t.Fatalf("docker run failed: %v\nout: %s", err, out)
|
||||
}
|
||||
defer d.CleanUp()
|
||||
|
||||
// Find where port 8080 is mapped to.
|
||||
port, err := d.FindPort(8080)
|
||||
if err != nil {
|
||||
t.Fatalf("docker.FindPort(8080) failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.WaitForHTTP(port, 5*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForHTTP() timeout: %v", err)
|
||||
}
|
||||
|
||||
timeout := time.Duration(2 * time.Second)
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://localhost:%d", port)
|
||||
// Check that container is working.
|
||||
if err := httpRequestSucceeds(client, url); err != nil {
|
||||
t.Errorf("http request failed: %v", err)
|
||||
}
|
||||
|
||||
// Pause container.
|
||||
if out, err := d.Do("pause", d.Name); err != nil {
|
||||
t.Fatalf("docker pause failed: %v\nout: %s", err, out)
|
||||
}
|
||||
|
||||
// Check if container is paused.
|
||||
switch _, err := client.Get(url); v := err.(type) {
|
||||
case nil:
|
||||
t.Errorf("http req expected to fail but it succeeded")
|
||||
case net.Error:
|
||||
if !v.Timeout() {
|
||||
t.Errorf("http req got error %v, wanted timeout", v)
|
||||
}
|
||||
default:
|
||||
t.Errorf("http req got unexpected error %v", v)
|
||||
}
|
||||
|
||||
// Resume container.
|
||||
if out, err := d.Do("unpause", d.Name); err != nil {
|
||||
t.Fatalf("docker unpause failed: %v\nout: %s", err, out)
|
||||
}
|
||||
|
||||
// Wait until it's up and running.
|
||||
if err := d.WaitForHTTP(port, 5*time.Second); err != nil {
|
||||
t.Fatalf("docker.WaitForHTTP() timeout: %v", err)
|
||||
}
|
||||
|
||||
// Check if container is working again.
|
||||
if err := httpRequestSucceeds(client, url); err != nil {
|
||||
t.Errorf("http request failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func MainTest(m *testing.M) {
|
||||
testutil.EnsureSupportedDockerVersion()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -4,7 +4,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "testutil",
|
||||
srcs = ["testutil.go"],
|
||||
srcs = [
|
||||
"docker.go",
|
||||
"testutil.go",
|
||||
],
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/test/testutil",
|
||||
visibility = [
|
||||
"//runsc:__subpackages__",
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2018 Google Inc.
|
||||
//
|
||||
// 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 testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func runtime() string {
|
||||
r := os.Getenv("RUNSC_RUNTIME")
|
||||
if r == "" {
|
||||
return "runsc-test"
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// EnsureSupportedDockerVersion checks if correct docker is installed.
|
||||
func EnsureSupportedDockerVersion() {
|
||||
cmd := exec.Command("docker", "version")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatalf("Error running %q: %v", "docker version", err)
|
||||
}
|
||||
re := regexp.MustCompile(`Version:\s+(\d+)\.(\d+)\.\d.*`)
|
||||
matches := re.FindStringSubmatch(string(out))
|
||||
if len(matches) != 3 {
|
||||
log.Fatalf("Invalid docker output: %s", out)
|
||||
}
|
||||
major, _ := strconv.Atoi(matches[1])
|
||||
minor, _ := strconv.Atoi(matches[2])
|
||||
if major < 17 || (major == 17 && minor < 9) {
|
||||
log.Fatalf("Docker version 17.09.0 or greater is required, found: %02d.%02d", major, minor)
|
||||
}
|
||||
}
|
||||
|
||||
// MountArg formats the volume argument to mount in the container.
|
||||
func MountArg(source, target string) string {
|
||||
return fmt.Sprintf("%s:%s", source, target)
|
||||
}
|
||||
|
||||
// LinkArg formats the link argument.
|
||||
func LinkArg(source *Docker, target string) string {
|
||||
return fmt.Sprintf("%s:%s", source.Name, target)
|
||||
}
|
||||
|
||||
// PrepareFiles creates temp directory to copy files there. The sandbox doesn't
|
||||
// have access to files in the test dir.
|
||||
func PrepareFiles(names ...string) (string, error) {
|
||||
dir, err := ioutil.TempDir("", "image-test")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ioutil.TempDir failed: %v", err)
|
||||
}
|
||||
if err := os.Chmod(dir, 0777); err != nil {
|
||||
return "", fmt.Errorf("os.Chmod(%q, 0777) failed: %v", dir, err)
|
||||
}
|
||||
for _, name := range names {
|
||||
src := getLocalPath(name)
|
||||
dst := path.Join(dir, name)
|
||||
if err := Copy(src, dst); err != nil {
|
||||
return "", fmt.Errorf("testutil.Copy(%q, %q) failed: %v", src, dst, err)
|
||||
}
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func getLocalPath(file string) string {
|
||||
return path.Join(".", file)
|
||||
}
|
||||
|
||||
// Docker contains the name and the runtime of a docker container.
|
||||
type Docker struct {
|
||||
Runtime string
|
||||
Name string
|
||||
}
|
||||
|
||||
// MakeDocker sets up the struct for a Docker container.
|
||||
// Names of containers will be unique.
|
||||
func MakeDocker(namePrefix string) Docker {
|
||||
suffix := fmt.Sprintf("-%06d", rand.Int())[:7]
|
||||
return Docker{Name: namePrefix + suffix, Runtime: runtime()}
|
||||
}
|
||||
|
||||
// Do executes docker command.
|
||||
func (d *Docker) Do(args ...string) (string, error) {
|
||||
fmt.Printf("Running: docker %s\n", args)
|
||||
cmd := exec.Command("docker", args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing docker %s: %v", args, err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// Run calls 'docker run' with the arguments provided.
|
||||
func (d *Docker) Run(args ...string) (string, error) {
|
||||
a := []string{"run", "--runtime", d.Runtime, "--name", d.Name, "-d"}
|
||||
a = append(a, args...)
|
||||
return d.Do(a...)
|
||||
}
|
||||
|
||||
// CleanUp kills and deletes the container.
|
||||
func (d *Docker) CleanUp() error {
|
||||
if _, err := d.Do("kill", d.Name); err != nil {
|
||||
return fmt.Errorf("error killing container %q: %v", d.Name, err)
|
||||
}
|
||||
if _, err := d.Do("rm", d.Name); err != nil {
|
||||
return fmt.Errorf("error deleting container %q: %v", d.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindPort returns the host port that is mapped to 'sandboxPort'. This calls
|
||||
// docker to allocate a free port in the host and prevent conflicts.
|
||||
func (d *Docker) FindPort(sandboxPort int) (int, error) {
|
||||
format := fmt.Sprintf(`{{ (index (index .NetworkSettings.Ports "%d/tcp") 0).HostPort }}`, sandboxPort)
|
||||
out, err := d.Do("inspect", "-f", format, d.Name)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error retrieving port: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error parsing port %q: %v", out, err)
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// WaitForOutput calls 'docker logs' to retrieve containers output and searches
|
||||
// for the given pattern.
|
||||
func (d *Docker) WaitForOutput(pattern string, timeout time.Duration) error {
|
||||
re := regexp.MustCompile(pattern)
|
||||
var out string
|
||||
for exp := time.Now().Add(timeout); time.Now().Before(exp); {
|
||||
var err error
|
||||
out, err = d.Do("logs", d.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if re.MatchString(out) {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for output %q: %s", re.String(), out)
|
||||
}
|
||||
|
||||
// WaitForHTTP tries GET requests on a port until the call succeeds or a timeout.
|
||||
func (d *Docker) WaitForHTTP(port int, timeout time.Duration) error {
|
||||
for exp := time.Now().Add(timeout); time.Now().Before(exp); {
|
||||
url := fmt.Sprintf("http://localhost:%d/", port)
|
||||
if _, err := http.Get(url); err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for HTTP server on port %d", port)
|
||||
}
|
||||
Reference in New Issue
Block a user