summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJustine Olshan <justineolshan@google.com>2018-07-11 09:36:20 -0700
committerShentubot <shentubot@google.com>2018-07-11 09:37:28 -0700
commit81ae5f3df533d5e5990baaa105392f59e28d5730 (patch)
tree8a90d2fbca329391f2586b028cef7be7e14ae09f
parent9cd69c2f3db7fd4b30d14d86be5fb5a3401054e5 (diff)
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
-rw-r--r--runsc/test/image/image_test.go222
-rw-r--r--runsc/test/image/python_test.go14
-rwxr-xr-xrunsc/test/install.sh (renamed from runsc/test/image/install.sh)0
-rw-r--r--runsc/test/integration/BUILD26
-rw-r--r--runsc/test/integration/integration.go16
-rw-r--r--runsc/test/integration/integration_test.go148
-rw-r--r--runsc/test/testutil/BUILD5
-rw-r--r--runsc/test/testutil/docker.go185
8 files changed, 422 insertions, 194 deletions
diff --git a/runsc/test/image/image_test.go b/runsc/test/image/image_test.go
index ed2111107..04c334d92 100644
--- a/runsc/test/image/image_test.go
+++ b/runsc/test/image/image_test.go
@@ -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())
}
diff --git a/runsc/test/image/python_test.go b/runsc/test/image/python_test.go
index e931bb444..f0dab3989 100644
--- a/runsc/test/image/python_test.go
+++ b/runsc/test/image/python_test.go
@@ -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)
}
diff --git a/runsc/test/image/install.sh b/runsc/test/install.sh
index c110d96f9..c110d96f9 100755
--- a/runsc/test/image/install.sh
+++ b/runsc/test/install.sh
diff --git a/runsc/test/integration/BUILD b/runsc/test/integration/BUILD
new file mode 100644
index 000000000..b366fe936
--- /dev/null
+++ b/runsc/test/integration/BUILD
@@ -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",
+)
diff --git a/runsc/test/integration/integration.go b/runsc/test/integration/integration.go
new file mode 100644
index 000000000..49c3c893a
--- /dev/null
+++ b/runsc/test/integration/integration.go
@@ -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
diff --git a/runsc/test/integration/integration_test.go b/runsc/test/integration/integration_test.go
new file mode 100644
index 000000000..09d845bfc
--- /dev/null
+++ b/runsc/test/integration/integration_test.go
@@ -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())
+}
diff --git a/runsc/test/testutil/BUILD b/runsc/test/testutil/BUILD
index 2c2555d98..6aec54abe 100644
--- a/runsc/test/testutil/BUILD
+++ b/runsc/test/testutil/BUILD
@@ -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__",
diff --git a/runsc/test/testutil/docker.go b/runsc/test/testutil/docker.go
new file mode 100644
index 000000000..4eb049591
--- /dev/null
+++ b/runsc/test/testutil/docker.go
@@ -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)
+}