summaryrefslogtreecommitdiffhomepage
path: root/test/root
diff options
context:
space:
mode:
authorIan Lewis <ianmlewis@gmail.com>2020-08-17 21:44:31 -0400
committerIan Lewis <ianmlewis@gmail.com>2020-08-17 21:44:31 -0400
commitac324f646ee3cb7955b0b45a7453aeb9671cbdf1 (patch)
tree0cbc5018e8807421d701d190dc20525726c7ca76 /test/root
parent352ae1022ce19de28fc72e034cc469872ad79d06 (diff)
parent6d0c5803d557d453f15ac6f683697eeb46dab680 (diff)
Merge branch 'master' into ip-forwarding
- Merges aleksej-paschenko's with HEAD - Adds vfs2 support for ip_forward
Diffstat (limited to 'test/root')
-rw-r--r--test/root/BUILD31
-rw-r--r--test/root/cgroup_test.go255
-rw-r--r--test/root/chroot_test.go29
-rw-r--r--test/root/crictl_test.go549
-rw-r--r--test/root/main_test.go2
-rw-r--r--test/root/oom_score_adj_test.go70
-rw-r--r--test/root/runsc_test.go151
-rw-r--r--test/root/testdata/BUILD19
-rw-r--r--test/root/testdata/busybox.go32
-rw-r--r--test/root/testdata/containerd_config.go39
-rw-r--r--test/root/testdata/httpd.go32
-rw-r--r--test/root/testdata/httpd_mount_paths.go53
-rw-r--r--test/root/testdata/sandbox.go30
-rw-r--r--test/root/testdata/simple.go41
14 files changed, 803 insertions, 530 deletions
diff --git a/test/root/BUILD b/test/root/BUILD
index d5dd9bca2..a9130b34f 100644
--- a/test/root/BUILD
+++ b/test/root/BUILD
@@ -1,11 +1,11 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools/vm:defs.bzl", "vm_test")
package(licenses = ["notice"])
go_library(
name = "root",
srcs = ["root.go"],
- importpath = "gvisor.dev/gvisor/test/root",
)
go_test(
@@ -17,28 +17,39 @@ go_test(
"crictl_test.go",
"main_test.go",
"oom_score_adj_test.go",
+ "runsc_test.go",
],
data = [
"//runsc",
],
- embed = [":root"],
+ library = ":root",
tags = [
# Requires docker and runsc to be configured before the test runs.
- # Also test only runs as root.
+ # Also, the test needs to be run as root. Note that below, the
+ # root_vm_test relies on the default runtime 'runsc' being installed by
+ # the default installer.
"manual",
"local",
],
visibility = ["//:sandbox"],
deps = [
- "//runsc/boot",
+ "//pkg/cleanup",
+ "//pkg/test/criutil",
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
"//runsc/cgroup",
"//runsc/container",
- "//runsc/criutil",
- "//runsc/dockerutil",
"//runsc/specutils",
- "//runsc/testutil",
- "//test/root/testdata",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
+ "@com_github_cenkalti_backoff//:go_default_library",
+ "@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@com_github_syndtr_gocapability//capability:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
],
)
+
+vm_test(
+ name = "root_vm_test",
+ size = "large",
+ shard_count = 1,
+ targets = [":root_test"],
+)
diff --git a/test/root/cgroup_test.go b/test/root/cgroup_test.go
index 76f1e4f2a..a26b83081 100644
--- a/test/root/cgroup_test.go
+++ b/test/root/cgroup_test.go
@@ -16,6 +16,7 @@ package root
import (
"bufio"
+ "context"
"fmt"
"io/ioutil"
"os"
@@ -24,10 +25,11 @@ import (
"strconv"
"strings"
"testing"
+ "time"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/cgroup"
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/testutil"
)
func verifyPid(pid int, path string) error {
@@ -52,15 +54,82 @@ func verifyPid(pid int, path string) error {
if scanner.Err() != nil {
return scanner.Err()
}
- return fmt.Errorf("got: %s, want: %d", gots, pid)
+ return fmt.Errorf("got: %v, want: %d", gots, pid)
+}
+
+func TestMemCgroup(t *testing.T) {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ // Start a new container and allocate the specified about of memory.
+ allocMemSize := 128 << 20
+ allocMemLimit := 2 * allocMemSize
+
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
+ Image: "basic/ubuntu",
+ Memory: allocMemLimit, // Must be in bytes.
+ }, "python3", "-c", fmt.Sprintf("import time; s = 'a' * %d; time.sleep(100)", allocMemSize)); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ }
+
+ // Extract the ID to lookup the cgroup.
+ gid := d.ID()
+ t.Logf("cgroup ID: %s", gid)
+
+ // Wait when the container will allocate memory.
+ memUsage := 0
+ start := time.Now()
+ for time.Since(start) < 30*time.Second {
+ // Sleep for a brief period of time after spawning the
+ // container (so that Docker can create the cgroup etc.
+ // or after looping below (so the application can start).
+ time.Sleep(100 * time.Millisecond)
+
+ // Read the cgroup memory limit.
+ path := filepath.Join("/sys/fs/cgroup/memory/docker", gid, "memory.limit_in_bytes")
+ outRaw, err := ioutil.ReadFile(path)
+ if err != nil {
+ // It's possible that the container does not exist yet.
+ continue
+ }
+ out := strings.TrimSpace(string(outRaw))
+ memLimit, err := strconv.Atoi(out)
+ if err != nil {
+ t.Fatalf("Atoi(%v): %v", out, err)
+ }
+ if memLimit != allocMemLimit {
+ // The group may not have had the correct limit set yet.
+ continue
+ }
+
+ // Read the cgroup memory usage.
+ path = filepath.Join("/sys/fs/cgroup/memory/docker", gid, "memory.max_usage_in_bytes")
+ outRaw, err = ioutil.ReadFile(path)
+ if err != nil {
+ t.Fatalf("error reading usage: %v", err)
+ }
+ out = strings.TrimSpace(string(outRaw))
+ memUsage, err = strconv.Atoi(out)
+ if err != nil {
+ t.Fatalf("Atoi(%v): %v", out, err)
+ }
+ t.Logf("read usage: %v, wanted: %v", memUsage, allocMemSize)
+
+ // Are we done?
+ if memUsage >= allocMemSize {
+ return
+ }
+ }
+
+ t.Fatalf("%vMB is less than %vMB", memUsage>>20, allocMemSize>>20)
}
// TestCgroup sets cgroup options and checks that cgroup was properly configured.
func TestCgroup(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// This is not a comprehensive list of attributes.
//
@@ -69,84 +138,133 @@ func TestCgroup(t *testing.T) {
// are often run on a single core virtual machine, and there is only a single
// CPU available in our current set, and every container's set.
attrs := []struct {
- arg string
+ field string
+ value int64
ctrl string
file string
want string
skipIfNotFound bool
}{
{
- arg: "--cpu-shares=1000",
- ctrl: "cpu",
- file: "cpu.shares",
- want: "1000",
+ field: "cpu-shares",
+ value: 1000,
+ ctrl: "cpu",
+ file: "cpu.shares",
+ want: "1000",
},
{
- arg: "--cpu-period=2000",
- ctrl: "cpu",
- file: "cpu.cfs_period_us",
- want: "2000",
+ field: "cpu-period",
+ value: 2000,
+ ctrl: "cpu",
+ file: "cpu.cfs_period_us",
+ want: "2000",
},
{
- arg: "--cpu-quota=3000",
- ctrl: "cpu",
- file: "cpu.cfs_quota_us",
- want: "3000",
+ field: "cpu-quota",
+ value: 3000,
+ ctrl: "cpu",
+ file: "cpu.cfs_quota_us",
+ want: "3000",
},
{
- arg: "--kernel-memory=100MB",
- ctrl: "memory",
- file: "memory.kmem.limit_in_bytes",
- want: "104857600",
+ field: "kernel-memory",
+ value: 100 << 20,
+ ctrl: "memory",
+ file: "memory.kmem.limit_in_bytes",
+ want: "104857600",
},
{
- arg: "--memory=1GB",
- ctrl: "memory",
- file: "memory.limit_in_bytes",
- want: "1073741824",
+ field: "memory",
+ value: 1 << 30,
+ ctrl: "memory",
+ file: "memory.limit_in_bytes",
+ want: "1073741824",
},
{
- arg: "--memory-reservation=500MB",
- ctrl: "memory",
- file: "memory.soft_limit_in_bytes",
- want: "524288000",
+ field: "memory-reservation",
+ value: 500 << 20,
+ ctrl: "memory",
+ file: "memory.soft_limit_in_bytes",
+ want: "524288000",
},
{
- arg: "--memory-swap=2GB",
+ field: "memory-swap",
+ value: 2 << 30,
ctrl: "memory",
file: "memory.memsw.limit_in_bytes",
want: "2147483648",
skipIfNotFound: true, // swap may be disabled on the machine.
},
{
- arg: "--memory-swappiness=5",
- ctrl: "memory",
- file: "memory.swappiness",
- want: "5",
+ field: "memory-swappiness",
+ value: 5,
+ ctrl: "memory",
+ file: "memory.swappiness",
+ want: "5",
+ },
+ {
+ field: "blkio-weight",
+ value: 750,
+ ctrl: "blkio",
+ file: "blkio.weight",
+ want: "750",
+ skipIfNotFound: true, // blkio groups may not be available.
},
{
- arg: "--blkio-weight=750",
- ctrl: "blkio",
- file: "blkio.weight",
- want: "750",
+ field: "pids-limit",
+ value: 1000,
+ ctrl: "pids",
+ file: "pids.max",
+ want: "1000",
},
}
- args := make([]string, 0, len(attrs))
+ // Make configs.
+ conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{
+ Image: "basic/alpine",
+ }, "sleep", "10000")
+
+ // Add Cgroup arguments to configs.
for _, attr := range attrs {
- args = append(args, attr.arg)
+ switch attr.field {
+ case "cpu-shares":
+ hostconf.Resources.CPUShares = attr.value
+ case "cpu-period":
+ hostconf.Resources.CPUPeriod = attr.value
+ case "cpu-quota":
+ hostconf.Resources.CPUQuota = attr.value
+ case "kernel-memory":
+ hostconf.Resources.KernelMemory = attr.value
+ case "memory":
+ hostconf.Resources.Memory = attr.value
+ case "memory-reservation":
+ hostconf.Resources.MemoryReservation = attr.value
+ case "memory-swap":
+ hostconf.Resources.MemorySwap = attr.value
+ case "memory-swappiness":
+ val := attr.value
+ hostconf.Resources.MemorySwappiness = &val
+ case "blkio-weight":
+ hostconf.Resources.BlkioWeight = uint16(attr.value)
+ case "pids-limit":
+ val := attr.value
+ hostconf.Resources.PidsLimit = &val
+
+ }
}
- args = append(args, "alpine", "sleep", "10000")
- if err := d.Run(args...); err != nil {
- t.Fatal("docker create failed:", err)
+ // Create container.
+ if err := d.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("create failed with: %v", err)
}
- defer d.CleanUp()
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
+ // Start container.
+ if err := d.Start(ctx); err != nil {
+ t.Fatalf("start failed with: %v", err)
}
+
+ // Lookup the relevant cgroup ID.
+ gid := d.ID()
t.Logf("cgroup ID: %s", gid)
// Check list of attributes defined above.
@@ -161,7 +279,7 @@ func TestCgroup(t *testing.T) {
t.Fatalf("failed to read %q: %v", path, err)
}
if got := strings.TrimSpace(string(out)); got != attr.want {
- t.Errorf("arg: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.arg, attr.ctrl, attr.file, got, attr.want)
+ t.Errorf("field: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.field, attr.ctrl, attr.file, got, attr.want)
}
}
@@ -179,7 +297,7 @@ func TestCgroup(t *testing.T) {
"pids",
"systemd",
}
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("SandboxPid: %v", err)
}
@@ -191,25 +309,34 @@ func TestCgroup(t *testing.T) {
}
}
+// TestCgroupParent sets the "CgroupParent" option and checks that the child and parent's
+// cgroups are created correctly relative to each other.
func TestCgroupParent(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
- parent := testutil.RandomName("runsc")
- if err := d.Run("--cgroup-parent", parent, "alpine", "sleep", "10000"); err != nil {
- t.Fatal("docker create failed:", err)
+ // Construct a known cgroup name.
+ parent := testutil.RandomID("runsc-")
+ conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{
+ Image: "basic/alpine",
+ }, "sleep", "10000")
+ hostconf.Resources.CgroupParent = parent
+
+ if err := d.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("create failed with: %v", err)
}
- defer d.CleanUp()
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
+
+ if err := d.Start(ctx); err != nil {
+ t.Fatalf("start failed with: %v", err)
}
+
+ // Extract the ID to look up the cgroup.
+ gid := d.ID()
t.Logf("cgroup ID: %s", gid)
// Check that sandbox is inside cgroup.
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("SandboxPid: %v", err)
}
diff --git a/test/root/chroot_test.go b/test/root/chroot_test.go
index be0f63d18..58fcd6f08 100644
--- a/test/root/chroot_test.go
+++ b/test/root/chroot_test.go
@@ -16,6 +16,7 @@
package root
import (
+ "context"
"fmt"
"io/ioutil"
"os/exec"
@@ -24,19 +25,23 @@ import (
"strings"
"testing"
- "gvisor.dev/gvisor/runsc/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
)
// TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned
// up after the sandbox is destroyed.
func TestChroot(t *testing.T) {
- d := dockerutil.MakeDocker("chroot-test")
- if err := d.Run("alpine", "sleep", "10000"); err != nil {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
+ Image: "basic/alpine",
+ }, "sleep", "10000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
- defer d.CleanUp()
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("Docker.SandboxPid(): %v", err)
}
@@ -72,20 +77,24 @@ func TestChroot(t *testing.T) {
t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc")
}
- d.CleanUp()
+ d.CleanUp(ctx)
}
func TestChrootGofer(t *testing.T) {
- d := dockerutil.MakeDocker("chroot-test")
- if err := d.Run("alpine", "sleep", "10000"); err != nil {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
+ Image: "basic/alpine",
+ }, "sleep", "10000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
- defer d.CleanUp()
// It's tricky to find gofers. Get sandbox PID first, then find parent. From
// parent get all immediate children, remove the sandbox, and everything else
// are gofers.
- sandPID, err := d.SandboxPid()
+ sandPID, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("Docker.SandboxPid(): %v", err)
}
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
index 3f90c4c6a..df91fa0fe 100644
--- a/test/root/crictl_test.go
+++ b/test/root/crictl_test.go
@@ -16,196 +16,362 @@ package root
import (
"bytes"
+ "encoding/json"
"fmt"
"io"
"io/ioutil"
- "log"
"net/http"
"os"
"os/exec"
"path"
- "path/filepath"
+ "regexp"
+ "strconv"
"strings"
+ "sync"
"testing"
"time"
- "gvisor.dev/gvisor/runsc/criutil"
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
- "gvisor.dev/gvisor/test/root/testdata"
+ "gvisor.dev/gvisor/pkg/cleanup"
+ "gvisor.dev/gvisor/pkg/test/criutil"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
)
// Tests for crictl have to be run as root (rather than in a user namespace)
// because crictl creates named network namespaces in /var/run/netns/.
-// TestCrictlSanity refers to b/112433158.
-func TestCrictlSanity(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
+// Sandbox returns a JSON config for a simple sandbox. Sandbox names must be
+// unique so different names should be used when running tests on the same
+// containerd instance.
+func Sandbox(name string) string {
+ // Sandbox is a default JSON config for a sandbox.
+ s := map[string]interface{}{
+ "metadata": map[string]string{
+ "name": name,
+ "namespace": "default",
+ "uid": testutil.RandomID(""),
+ },
+ "linux": map[string]string{},
+ "log_directory": "/tmp",
}
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("httpd", testdata.Sandbox, testdata.Httpd)
+
+ v, err := json.Marshal(s)
if err != nil {
- t.Fatal(err)
+ // This shouldn't happen.
+ panic(err)
}
+ return string(v)
+}
- // Look for the httpd page.
- if err = httpGet(crictl, podID, "index.html"); err != nil {
- t.Fatalf("failed to get page: %v", err)
+// SimpleSpec returns a JSON config for a simple container that runs the
+// specified command in the specified image.
+func SimpleSpec(name, image string, cmd []string, extra map[string]interface{}) string {
+ s := map[string]interface{}{
+ "metadata": map[string]string{
+ "name": name,
+ },
+ "image": map[string]string{
+ "image": testutil.ImageByName(image),
+ },
+ // Log files are not deleted after root tests are run. Log to random
+ // paths to ensure logs are fresh.
+ "log_path": fmt.Sprintf("%s.log", testutil.RandomID(name)),
+ "stdin": false,
+ "tty": false,
+ }
+ if len(cmd) > 0 { // Omit if empty.
+ s["command"] = cmd
+ }
+ for k, v := range extra {
+ s[k] = v // Extra settings.
+ }
+ v, err := json.Marshal(s)
+ if err != nil {
+ // This shouldn't happen.
+ panic(err)
}
+ return string(v)
+}
+
+// Httpd is a JSON config for an httpd container.
+var Httpd = SimpleSpec("httpd", "basic/httpd", nil, nil)
+
+// TestCrictlSanity refers to b/112433158.
+func TestCrictlSanity(t *testing.T) {
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), Httpd)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
+ // Look for the httpd page.
+ if err = httpGet(crictl, podID, "index.html"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
+// HttpdMountPaths is a JSON config for an httpd container with additional
+// mounts.
+var HttpdMountPaths = SimpleSpec("httpd", "basic/httpd", nil, map[string]interface{}{
+ "mounts": []map[string]interface{}{
+ map[string]interface{}{
+ "container_path": "/var/run/secrets/kubernetes.io/serviceaccount",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/volumes/kubernetes.io~secret/default-token-2rpfx",
+ "readonly": true,
+ },
+ map[string]interface{}{
+ "container_path": "/etc/hosts",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/etc-hosts",
+ "readonly": false,
+ },
+ map[string]interface{}{
+ "container_path": "/dev/termination-log",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/containers/httpd/d1709580",
+ "readonly": false,
+ },
+ map[string]interface{}{
+ "container_path": "/usr/local/apache2/htdocs/test",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064",
+ "readonly": true,
+ },
+ },
+ "linux": map[string]interface{}{},
+})
+
// TestMountPaths refers to b/117635704.
func TestMountPaths(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("httpd", testdata.Sandbox, testdata.HttpdMountPaths)
- if err != nil {
- t.Fatal(err)
- }
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), HttpdMountPaths)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
- // Look for the directory available at /test.
- if err = httpGet(crictl, podID, "test"); err != nil {
- t.Fatalf("failed to get page: %v", err)
- }
+ // Look for the directory available at /test.
+ if err = httpGet(crictl, podID, "test"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
// TestMountPaths refers to b/118728671.
func TestMountOverSymlinks(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("k8s.gcr.io/busybox", testdata.Sandbox, testdata.MountOverSymlink)
- if err != nil {
- t.Fatal(err)
- }
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
- out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
- t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
- }
+ spec := SimpleSpec("busybox", "basic/resolv", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/resolv", Sandbox("default"), spec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
- etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- if tmp != etc {
- t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
- }
+ out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("readlink failed: %v, out: %s", err, out)
+ }
+ if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
+ t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
+ }
+
+ etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, etc)
+ }
+ tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, out)
+ }
+ if tmp != etc {
+ t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
+ }
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
// TestHomeDir tests that the HOME environment variable is set for
-// multi-containers.
+// Pod containers.
func TestHomeDir(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- contSpec := testdata.SimpleSpec("root", "k8s.gcr.io/busybox", []string{"sleep", "1000"})
- podID, contID, err := crictl.StartPodAndContainer("k8s.gcr.io/busybox", testdata.Sandbox, contSpec)
- if err != nil {
- t.Fatal(err)
- }
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
- t.Run("root container", func(t *testing.T) {
- out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME")
- if err != nil {
- t.Fatal(err)
- }
- if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
- t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
- }
- })
+ // Note that container ID returned here is a sub-container. All Pod
+ // containers are sub-containers. The root container of the sandbox is the
+ // pause container.
+ t.Run("sub-container", func(t *testing.T) {
+ contSpec := SimpleSpec("subcontainer", "basic/busybox", []string{"sh", "-c", "echo $HOME"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("subcont-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
- t.Run("sub-container", func(t *testing.T) {
- // Create a sub container in the same pod.
- subContSpec := testdata.SimpleSpec("subcontainer", "k8s.gcr.io/busybox", []string{"sleep", "1000"})
- subContID, err := crictl.StartContainer(podID, "k8s.gcr.io/busybox", testdata.Sandbox, subContSpec)
- if err != nil {
- t.Fatal(err)
- }
+ out, err := crictl.Logs(contID)
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
- out, err := crictl.Exec(subContID, "sh", "-c", "echo $HOME")
- if err != nil {
- t.Fatal(err)
- }
- if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
- t.Fatalf("Home directory invalid. Got %q, Want: %q", got, want)
- }
+ // Stop everything; note that the pod may have already stopped.
+ crictl.StopPodAndContainer(podID, contID)
+ })
- if err := crictl.StopContainer(subContID); err != nil {
- t.Fatal(err)
- }
- })
+ // Tests that HOME is set for the exec process.
+ t.Run("exec", func(t *testing.T) {
+ contSpec := SimpleSpec("exec", "basic/busybox", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("exec-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
- }
+ out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME")
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
+ })
+ }
}
+const containerdRuntime = "runsc"
+
+const v1Template = `
+disabled_plugins = ["restart"]
+[plugins.cri]
+ disable_tcp_service = true
+[plugins.linux]
+ shim = "%s"
+ shim_debug = true
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `]
+ runtime_type = "io.containerd.runtime.v1.linux"
+ runtime_engine = "%s"
+ runtime_root = "%s/root/runsc"
+`
+
+const v2Template = `
+disabled_plugins = ["restart"]
+[plugins.cri]
+ disable_tcp_service = true
+[plugins.linux]
+ shim_debug = true
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `]
+ runtime_type = "io.containerd.` + containerdRuntime + `.v1"
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `.options]
+ TypeUrl = "io.containerd.` + containerdRuntime + `.v1.options"
+`
+
+const (
+ // v1 is the containerd API v1.
+ v1 string = "v1"
+
+ // v1 is the containerd API v21.
+ v2 string = "v2"
+)
+
+// allVersions is the set of known versions.
+var allVersions = []string{v1, v2}
+
// setup sets up before a test. Specifically it:
// * Creates directories and a socket for containerd to utilize.
// * Runs containerd and waits for it to reach a "ready" state for testing.
// * Returns a cleanup function that should be called at the end of the test.
-func setup(t *testing.T) (*criutil.Crictl, func(), error) {
- var cleanups []func()
- cleanupFunc := func() {
- for i := len(cleanups) - 1; i >= 0; i-- {
- cleanups[i]()
- }
- }
- cleanup := specutils.MakeCleanup(cleanupFunc)
- defer cleanup.Clean()
-
+func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) {
// Create temporary containerd root and state directories, and a socket
// via which crictl and containerd communicate.
containerdRoot, err := ioutil.TempDir(testutil.TmpDir(), "containerd-root")
if err != nil {
t.Fatalf("failed to create containerd root: %v", err)
}
- cleanups = append(cleanups, func() { os.RemoveAll(containerdRoot) })
+ cu := cleanup.Make(func() { os.RemoveAll(containerdRoot) })
+ defer cu.Clean()
+ t.Logf("Using containerd root: %s", containerdRoot)
+
containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state")
if err != nil {
t.Fatalf("failed to create containerd state: %v", err)
}
- cleanups = append(cleanups, func() { os.RemoveAll(containerdState) })
- sockAddr := filepath.Join(testutil.TmpDir(), "containerd-test.sock")
+ cu.Add(func() { os.RemoveAll(containerdState) })
+ t.Logf("Using containerd state: %s", containerdState)
+
+ sockDir, err := ioutil.TempDir(testutil.TmpDir(), "containerd-sock")
+ if err != nil {
+ t.Fatalf("failed to create containerd socket directory: %v", err)
+ }
+ cu.Add(func() { os.RemoveAll(sockDir) })
+ sockAddr := path.Join(sockDir, "test.sock")
+ t.Logf("Using containerd socket: %s", sockAddr)
+
+ // Extract the containerd version.
+ versionCmd := exec.Command(getContainerd(), "-v")
+ out, err := versionCmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("error extracting containerd version: %v (%s)", err, string(out))
+ }
+ r := regexp.MustCompile(" v([0-9]+)\\.([0-9]+)\\.([0-9+])")
+ vs := r.FindStringSubmatch(string(out))
+ if len(vs) != 4 {
+ t.Fatalf("error unexpected version string: %s", string(out))
+ }
+ major, err := strconv.ParseUint(vs[1], 10, 64)
+ if err != nil {
+ t.Fatalf("error parsing containerd major version: %v (%s)", err, string(out))
+ }
+ minor, err := strconv.ParseUint(vs[2], 10, 64)
+ if err != nil {
+ t.Fatalf("error parsing containerd minor version: %v (%s)", err, string(out))
+ }
+ t.Logf("Using containerd version: %d.%d", major, minor)
// We rewrite a configuration. This is based on the current docker
// configuration for the runtime under test.
@@ -213,50 +379,125 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
if err != nil {
t.Fatalf("error discovering runtime path: %v", err)
}
- config, err := testutil.WriteTmpFile("containerd-config", testdata.ContainerdConfig(runtime))
+ t.Logf("Using runtime: %v", runtime)
+
+ // Construct a PATH that includes the runtime directory. This is
+ // because the shims will be installed there, and containerd may infer
+ // the binary name and search the PATH.
+ runtimeDir := path.Dir(runtime)
+ modifiedPath := os.Getenv("PATH")
+ if modifiedPath != "" {
+ modifiedPath = ":" + modifiedPath // We prepend below.
+ }
+ modifiedPath = path.Dir(getContainerd()) + modifiedPath
+ modifiedPath = runtimeDir + ":" + modifiedPath
+ t.Logf("Using PATH: %v", modifiedPath)
+
+ var (
+ config string
+ runpArgs []string
+ )
+ switch version {
+ case v1:
+ // This is only supported less than 1.3.
+ if major > 1 || (major == 1 && minor >= 3) {
+ t.Skipf("skipping unsupported containerd (want less than 1.3, got %d.%d)", major, minor)
+ }
+
+ // We provide the shim, followed by the runtime, and then a
+ // temporary root directory.
+ config = fmt.Sprintf(v1Template, criutil.ResolvePath("gvisor-containerd-shim"), runtime, containerdRoot)
+ case v2:
+ // This is only supported past 1.2.
+ if major < 1 || (major == 1 && minor <= 1) {
+ t.Skipf("skipping incompatible containerd (want at least 1.2, got %d.%d)", major, minor)
+ }
+
+ // The runtime is provided via parameter. Note that the v2 shim
+ // binary name is always containerd-shim-* so we don't actually
+ // care about the docker runtime name.
+ config = v2Template
+ default:
+ t.Fatalf("unknown version: %d", version)
+ }
+ t.Logf("Using config: %s", config)
+
+ // Generate the configuration for the test.
+ configFile, configCleanup, err := testutil.WriteTmpFile("containerd-config", config)
if err != nil {
t.Fatalf("failed to write containerd config")
}
- cleanups = append(cleanups, func() { os.RemoveAll(config) })
+ cu.Add(configCleanup)
// Start containerd.
- containerd := exec.Command(getContainerd(),
- "--config", config,
+ args := []string{
+ getContainerd(),
+ "--config", configFile,
"--log-level", "debug",
"--root", containerdRoot,
"--state", containerdState,
- "--address", sockAddr)
- cleanups = append(cleanups, func() {
- if err := testutil.KillCommand(containerd); err != nil {
- log.Printf("error killing containerd: %v", err)
- }
- })
- containerdStderr, err := containerd.StderrPipe()
- if err != nil {
- t.Fatalf("failed to get containerd stderr: %v", err)
+ "--address", sockAddr,
}
- containerdStdout, err := containerd.StdoutPipe()
+ t.Logf("Using args: %s", strings.Join(args, " "))
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Env = append(os.Environ(), "PATH="+modifiedPath)
+
+ // Include output in logs.
+ stderrPipe, err := cmd.StderrPipe()
if err != nil {
- t.Fatalf("failed to get containerd stdout: %v", err)
+ t.Fatalf("failed to create stderr pipe: %v", err)
}
- if err := containerd.Start(); err != nil {
+ cu.Add(func() { stderrPipe.Close() })
+ stdoutPipe, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatalf("failed to create stdout pipe: %v", err)
+ }
+ cu.Add(func() { stdoutPipe.Close() })
+ var (
+ wg sync.WaitGroup
+ stderr bytes.Buffer
+ stdout bytes.Buffer
+ )
+ startupR, startupW := io.Pipe()
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ io.Copy(io.MultiWriter(startupW, &stderr), stderrPipe)
+ }()
+ go func() {
+ defer wg.Done()
+ io.Copy(io.MultiWriter(startupW, &stdout), stdoutPipe)
+ }()
+ cu.Add(func() {
+ wg.Wait()
+ t.Logf("containerd stdout: %s", stdout.String())
+ t.Logf("containerd stderr: %s", stderr.String())
+ })
+
+ // Start the process.
+ if err := cmd.Start(); err != nil {
t.Fatalf("failed running containerd: %v", err)
}
- // Wait for containerd to boot. Then put all containerd output into a
- // buffer to be logged at the end of the test.
- testutil.WaitUntilRead(containerdStderr, "Start streaming server", nil, 10*time.Second)
- stdoutBuf := &bytes.Buffer{}
- stderrBuf := &bytes.Buffer{}
- go func() { io.Copy(stdoutBuf, containerdStdout) }()
- go func() { io.Copy(stderrBuf, containerdStderr) }()
- cleanups = append(cleanups, func() {
- t.Logf("containerd stdout: %s", string(stdoutBuf.Bytes()))
- t.Logf("containerd stderr: %s", string(stderrBuf.Bytes()))
+ // Wait for containerd to boot.
+ if err := testutil.WaitUntilRead(startupR, "Start streaming server", nil, 10*time.Second); err != nil {
+ t.Fatalf("failed to start containerd: %v", err)
+ }
+
+ // Discard all subsequent data.
+ go io.Copy(ioutil.Discard, startupR)
+
+ // Create the crictl interface.
+ cc := criutil.NewCrictl(t, sockAddr, runpArgs)
+ cu.Add(cc.CleanUp)
+
+ // Kill must be the last cleanup (as it will be executed first).
+ cu.Add(func() {
+ // Best effort: ignore errors.
+ testutil.KillCommand(cmd)
})
- cleanup.Release()
- return criutil.NewCrictl(20*time.Second, sockAddr), cleanupFunc, nil
+ return cc, cu.Release(), nil
}
// httpGet GETs the contents of a file served from a pod on port 80.
diff --git a/test/root/main_test.go b/test/root/main_test.go
index d74dec85f..9fb17e0dd 100644
--- a/test/root/main_test.go
+++ b/test/root/main_test.go
@@ -21,7 +21,7 @@ import (
"testing"
"github.com/syndtr/gocapability/capability"
- "gvisor.dev/gvisor/runsc/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/runsc/specutils"
)
diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go
index 126f0975a..4243eb59e 100644
--- a/test/root/oom_score_adj_test.go
+++ b/test/root/oom_score_adj_test.go
@@ -20,10 +20,10 @@ import (
"testing"
specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/runsc/boot"
+ "gvisor.dev/gvisor/pkg/cleanup"
+ "gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
)
var (
@@ -40,15 +40,6 @@ var (
// TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a
// single container sandbox.
func TestOOMScoreAdjSingle(t *testing.T) {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- conf := testutil.TestConfig()
- conf.RootDir = rootDir
-
ppid, err := specutils.GetParentPid(os.Getpid())
if err != nil {
t.Fatalf("getting parent pid: %v", err)
@@ -89,11 +80,11 @@ func TestOOMScoreAdjSingle(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
- id := testutil.UniqueContainerID()
+ id := testutil.RandomContainerID()
s := testutil.NewSpecWithArgs("sleep", "1000")
s.Process.OOMScoreAdj = testCase.OOMScoreAdj
- containers, cleanup, err := startContainers(conf, []*specs.Spec{s}, []string{id})
+ containers, cleanup, err := startContainers(t, []*specs.Spec{s}, []string{id})
if err != nil {
t.Fatalf("error starting containers: %v", err)
}
@@ -131,15 +122,6 @@ func TestOOMScoreAdjSingle(t *testing.T) {
// TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a
// multi-container sandbox.
func TestOOMScoreAdjMulti(t *testing.T) {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- conf := testutil.TestConfig()
- conf.RootDir = rootDir
-
ppid, err := specutils.GetParentPid(os.Getpid())
if err != nil {
t.Fatalf("getting parent pid: %v", err)
@@ -257,7 +239,7 @@ func TestOOMScoreAdjMulti(t *testing.T) {
}
}
- containers, cleanup, err := startContainers(conf, specs, ids)
+ containers, cleanup, err := startContainers(t, specs, ids)
if err != nil {
t.Fatalf("error starting containers: %v", err)
}
@@ -321,7 +303,7 @@ func TestOOMScoreAdjMulti(t *testing.T) {
func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
var specs []*specs.Spec
var ids []string
- rootID := testutil.UniqueContainerID()
+ rootID := testutil.RandomContainerID()
for i, cmd := range cmds {
spec := testutil.NewSpecWithArgs(cmd...)
@@ -335,35 +317,34 @@ func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer,
specutils.ContainerdSandboxIDAnnotation: rootID,
}
- ids = append(ids, testutil.UniqueContainerID())
+ ids = append(ids, testutil.RandomContainerID())
}
specs = append(specs, spec)
}
return specs, ids
}
-func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) {
- if len(conf.RootDir) == 0 {
- panic("conf.RootDir not set. Call testutil.SetupRootDir() to set.")
- }
-
+func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) {
var containers []*container.Container
- var bundles []string
- cleanup := func() {
- for _, c := range containers {
- c.Destroy()
- }
- for _, b := range bundles {
- os.RemoveAll(b)
- }
+
+ // All containers must share the same root.
+ rootDir, clean, err := testutil.SetupRootDir()
+ if err != nil {
+ t.Fatalf("error creating root dir: %v", err)
}
+ cu := cleanup.Make(clean)
+ defer cu.Clean()
+
+ // Point this to from the configuration.
+ conf := testutil.TestConfig(t)
+ conf.RootDir = rootDir
+
for i, spec := range specs {
- bundleDir, err := testutil.SetupBundleDir(spec)
+ bundleDir, clean, err := testutil.SetupBundleDir(spec)
if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error setting up container: %v", err)
+ return nil, nil, fmt.Errorf("error setting up bundle: %v", err)
}
- bundles = append(bundles, bundleDir)
+ cu.Add(clean)
args := container.Args{
ID: ids[i],
@@ -372,15 +353,14 @@ func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*c
}
cont, err := container.New(conf, args)
if err != nil {
- cleanup()
return nil, nil, fmt.Errorf("error creating container: %v", err)
}
containers = append(containers, cont)
if err := cont.Start(conf); err != nil {
- cleanup()
return nil, nil, fmt.Errorf("error starting container: %v", err)
}
}
- return containers, cleanup, nil
+
+ return containers, cu.Release(), nil
}
diff --git a/test/root/runsc_test.go b/test/root/runsc_test.go
new file mode 100644
index 000000000..25204bebb
--- /dev/null
+++ b/test/root/runsc_test.go
@@ -0,0 +1,151 @@
+// Copyright 2020 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 root
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/cenkalti/backoff"
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+ "gvisor.dev/gvisor/runsc/specutils"
+)
+
+// TestDoKill checks that when "runsc do..." is killed, the sandbox process is
+// also terminated. This ensures that parent death signal is propagate to the
+// sandbox process correctly.
+func TestDoKill(t *testing.T) {
+ // Make the sandbox process be reparented here when it's killed, so we can
+ // wait for it.
+ if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
+ t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err)
+ }
+
+ cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000")
+ buf := &bytes.Buffer{}
+ cmd.Stdout = buf
+ cmd.Stderr = buf
+ cmd.Start()
+
+ var pid int
+ findSandbox := func() error {
+ var err error
+ pid, err = sandboxPid(cmd.Process.Pid)
+ if err != nil {
+ return &backoff.PermanentError{Err: err}
+ }
+ if pid == 0 {
+ return fmt.Errorf("sandbox process not found")
+ }
+ return nil
+ }
+ if err := testutil.Poll(findSandbox, 10*time.Second); err != nil {
+ t.Fatalf("failed to find sandbox: %v", err)
+ }
+ t.Logf("Found sandbox, pid: %d", pid)
+
+ if err := cmd.Process.Kill(); err != nil {
+ t.Fatalf("failed to kill run process: %v", err)
+ }
+ cmd.Wait()
+ t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String())
+
+ ch := make(chan struct{})
+ go func() {
+ defer func() { ch <- struct{}{} }()
+ t.Logf("Waiting for sandbox process (%d) termination", pid)
+ if _, err := unix.Wait4(pid, nil, 0, nil); err != nil {
+ t.Errorf("error waiting for sandbox process (%d): %v", pid, err)
+ }
+ }()
+ select {
+ case <-ch:
+ // Done
+ case <-time.After(5 * time.Second):
+ t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid)
+ }
+}
+
+// sandboxPid looks for the sandbox process inside the process tree starting
+// from "pid". It returns 0 and no error if no sandbox process is found. It
+// returns error if anything failed.
+func sandboxPid(pid int) (int, error) {
+ cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid))
+ buf := &bytes.Buffer{}
+ cmd.Stdout = buf
+ if err := cmd.Start(); err != nil {
+ return 0, err
+ }
+ ps, err := cmd.Process.Wait()
+ if err != nil {
+ return 0, err
+ }
+ if ps.ExitCode() == 1 {
+ // pgrep returns 1 when no process is found.
+ return 0, nil
+ }
+
+ var children []int
+ for _, line := range strings.Split(buf.String(), "\n") {
+ if len(line) == 0 {
+ continue
+ }
+ child, err := strconv.Atoi(line)
+ if err != nil {
+ return 0, err
+ }
+
+ cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline"))
+ if err != nil {
+ if os.IsNotExist(err) {
+ // Raced with process exit.
+ continue
+ }
+ return 0, err
+ }
+ args := strings.SplitN(string(cmdline), "\x00", 2)
+ if len(args) == 0 {
+ return 0, fmt.Errorf("malformed cmdline file: %q", cmdline)
+ }
+ // The sandbox process has the first argument set to "runsc-sandbox".
+ if args[0] == "runsc-sandbox" {
+ return child, nil
+ }
+
+ children = append(children, child)
+ }
+
+ // Sandbox process wasn't found, try another level down.
+ for _, pid := range children {
+ sand, err := sandboxPid(pid)
+ if err != nil {
+ return 0, err
+ }
+ if sand != 0 {
+ return sand, nil
+ }
+ // Not found, continue the search.
+ }
+ return 0, nil
+}
diff --git a/test/root/testdata/BUILD b/test/root/testdata/BUILD
deleted file mode 100644
index 125633680..000000000
--- a/test/root/testdata/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "testdata",
- srcs = [
- "busybox.go",
- "containerd_config.go",
- "httpd.go",
- "httpd_mount_paths.go",
- "sandbox.go",
- "simple.go",
- ],
- importpath = "gvisor.dev/gvisor/test/root/testdata",
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/test/root/testdata/busybox.go b/test/root/testdata/busybox.go
deleted file mode 100644
index e4dbd2843..000000000
--- a/test/root/testdata/busybox.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 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 testdata
-
-// MountOverSymlink is a JSON config for a container that /etc/resolv.conf is a
-// symlink to /tmp/resolv.conf.
-var MountOverSymlink = `
-{
- "metadata": {
- "name": "busybox"
- },
- "image": {
- "image": "k8s.gcr.io/busybox"
- },
- "command": [
- "sleep",
- "1000"
- ]
-}
-`
diff --git a/test/root/testdata/containerd_config.go b/test/root/testdata/containerd_config.go
deleted file mode 100644
index e12f1ec88..000000000
--- a/test/root/testdata/containerd_config.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 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 testdata contains data required for root tests.
-package testdata
-
-import "fmt"
-
-// containerdConfigTemplate is a .toml config for containerd. It contains a
-// formatting verb so the runtime field can be set via fmt.Sprintf.
-const containerdConfigTemplate = `
-disabled_plugins = ["restart"]
-[plugins.linux]
- runtime = "%s"
- runtime_root = "/tmp/test-containerd/runsc"
- shim = "/usr/local/bin/gvisor-containerd-shim"
- shim_debug = true
-
-[plugins.cri.containerd.runtimes.runsc]
- runtime_type = "io.containerd.runtime.v1.linux"
- runtime_engine = "%s"
-`
-
-// ContainerdConfig returns a containerd config file with the specified
-// runtime.
-func ContainerdConfig(runtime string) string {
- return fmt.Sprintf(containerdConfigTemplate, runtime, runtime)
-}
diff --git a/test/root/testdata/httpd.go b/test/root/testdata/httpd.go
deleted file mode 100644
index 45d5e33d4..000000000
--- a/test/root/testdata/httpd.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 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 testdata
-
-// Httpd is a JSON config for an httpd container.
-const Httpd = `
-{
- "metadata": {
- "name": "httpd"
- },
- "image":{
- "image": "httpd"
- },
- "mounts": [
- ],
- "linux": {
- },
- "log_path": "httpd.log"
-}
-`
diff --git a/test/root/testdata/httpd_mount_paths.go b/test/root/testdata/httpd_mount_paths.go
deleted file mode 100644
index ac3f4446a..000000000
--- a/test/root/testdata/httpd_mount_paths.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2018 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 testdata
-
-// HttpdMountPaths is a JSON config for an httpd container with additional
-// mounts.
-const HttpdMountPaths = `
-{
- "metadata": {
- "name": "httpd"
- },
- "image":{
- "image": "httpd"
- },
- "mounts": [
- {
- "container_path": "/var/run/secrets/kubernetes.io/serviceaccount",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/volumes/kubernetes.io~secret/default-token-2rpfx",
- "readonly": true
- },
- {
- "container_path": "/etc/hosts",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/etc-hosts",
- "readonly": false
- },
- {
- "container_path": "/dev/termination-log",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/containers/httpd/d1709580",
- "readonly": false
- },
- {
- "container_path": "/usr/local/apache2/htdocs/test",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064",
- "readonly": true
- }
- ],
- "linux": {
- },
- "log_path": "httpd.log"
-}
-`
diff --git a/test/root/testdata/sandbox.go b/test/root/testdata/sandbox.go
deleted file mode 100644
index 0db210370..000000000
--- a/test/root/testdata/sandbox.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 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 testdata
-
-// Sandbox is a default JSON config for a sandbox.
-const Sandbox = `
-{
- "metadata": {
- "name": "default-sandbox",
- "namespace": "default",
- "attempt": 1,
- "uid": "hdishd83djaidwnduwk28bcsb"
- },
- "linux": {
- },
- "log_directory": "/tmp"
-}
-`
diff --git a/test/root/testdata/simple.go b/test/root/testdata/simple.go
deleted file mode 100644
index 1cca53f0c..000000000
--- a/test/root/testdata/simple.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 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 testdata
-
-import (
- "encoding/json"
- "fmt"
-)
-
-// SimpleSpec returns a JSON config for a simple container that runs the
-// specified command in the specified image.
-func SimpleSpec(name, image string, cmd []string) string {
- cmds, err := json.Marshal(cmd)
- if err != nil {
- // This shouldn't happen.
- panic(err)
- }
- return fmt.Sprintf(`
-{
- "metadata": {
- "name": %q
- },
- "image": {
- "image": %q
- },
- "command": %s
- }
-`, name, image, cmds)
-}