diff options
Diffstat (limited to 'runsc')
66 files changed, 0 insertions, 11529 deletions
diff --git a/runsc/BUILD b/runsc/BUILD deleted file mode 100644 index 6b8c92706..000000000 --- a/runsc/BUILD +++ /dev/null @@ -1,110 +0,0 @@ -package(licenses = ["notice"]) # Apache 2.0 - -load("@io_bazel_rules_go//go:def.bzl", "go_binary") -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_deb", "pkg_tar") - -go_binary( - name = "runsc", - srcs = [ - "main.go", - "version.go", - ], - pure = "on", - visibility = [ - "//visibility:public", - ], - x_defs = {"main.version": "{VERSION}"}, - deps = [ - "//pkg/log", - "//pkg/sentry/platform", - "//runsc/boot", - "//runsc/cmd", - "//runsc/specutils", - "@com_github_google_subcommands//:go_default_library", - ], -) - -# The runsc-race target is a race-compatible BUILD target. This must be built -# via "bazel build --features=race //runsc:runsc-race", since the race feature -# must apply to all dependencies due a bug in gazelle file selection. The pure -# attribute must be off because the race detector requires linking with non-Go -# components, although we still require a static binary. -# -# Note that in the future this might be convertible to a compatible target by -# using the pure and static attributes within a select function, but select is -# not currently compatible with string attributes [1]. -# -# [1] https://github.com/bazelbuild/bazel/issues/1698 -go_binary( - name = "runsc-race", - srcs = [ - "main.go", - "version.go", - ], - static = "on", - visibility = [ - "//visibility:public", - ], - x_defs = {"main.version": "{VERSION}"}, - deps = [ - "//pkg/log", - "//pkg/sentry/platform", - "//runsc/boot", - "//runsc/cmd", - "//runsc/specutils", - "@com_github_google_subcommands//:go_default_library", - ], -) - -pkg_tar( - name = "runsc-bin", - srcs = [":runsc"], - mode = "0755", - package_dir = "/usr/bin", - strip_prefix = "/runsc/linux_amd64_pure_stripped", -) - -pkg_tar( - name = "runsc-tools", - srcs = ["//runsc/tools/dockercfg"], - mode = "0755", - package_dir = "/usr/libexec/runsc", - strip_prefix = "/runsc/tools/dockercfg/linux_amd64_stripped", -) - -pkg_tar( - name = "debian-data", - extension = "tar.gz", - deps = [ - ":runsc-bin", - ":runsc-tools", - ], -) - -genrule( - name = "deb-version", - outs = ["version.txt"], - cmd = "$(location :runsc) -version | grep 'runsc version' | sed 's/^[^0-9]*//' > $@", - stamp = 1, - tools = [":runsc"], -) - -pkg_deb( - name = "runsc-debian", - architecture = "amd64", - data = ":debian-data", - description_file = "debian/description", - homepage = "https://gvisor.dev/", - maintainer = "The gVisor Authors <gvisor-dev@googlegroups.com>", - package = "runsc", - postinst = "debian/postinst.sh", - tags = [ - # TODO(b/135475885): pkg_deb requires python2: - # https://github.com/bazelbuild/bazel/issues/8443 - "manual", - ], - version_file = ":version.txt", - visibility = [ - "//visibility:public", - ], -) diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD deleted file mode 100644 index 588bb8851..000000000 --- a/runsc/boot/BUILD +++ /dev/null @@ -1,116 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "boot", - srcs = [ - "compat.go", - "compat_amd64.go", - "config.go", - "controller.go", - "debug.go", - "events.go", - "fds.go", - "fs.go", - "limits.go", - "loader.go", - "network.go", - "pprof.go", - "strace.go", - "user.go", - ], - importpath = "gvisor.dev/gvisor/runsc/boot", - visibility = [ - "//runsc:__subpackages__", - "//test:__subpackages__", - ], - deps = [ - "//pkg/abi", - "//pkg/abi/linux", - "//pkg/control/server", - "//pkg/cpuid", - "//pkg/eventchannel", - "//pkg/log", - "//pkg/memutil", - "//pkg/rand", - "//pkg/refs", - "//pkg/sentry/arch", - "//pkg/sentry/arch:registers_go_proto", - "//pkg/sentry/context", - "//pkg/sentry/control", - "//pkg/sentry/fs", - "//pkg/sentry/fs/dev", - "//pkg/sentry/fs/gofer", - "//pkg/sentry/fs/host", - "//pkg/sentry/fs/proc", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fs/sys", - "//pkg/sentry/fs/tmpfs", - "//pkg/sentry/fs/tty", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel:uncaught_signal_go_proto", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/limits", - "//pkg/sentry/loader", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/sighandling", - "//pkg/sentry/socket/epsocket", - "//pkg/sentry/socket/hostinet", - "//pkg/sentry/socket/netlink", - "//pkg/sentry/socket/netlink/route", - "//pkg/sentry/socket/unix", - "//pkg/sentry/state", - "//pkg/sentry/strace", - "//pkg/sentry/syscalls/linux", - "//pkg/sentry/time", - "//pkg/sentry/unimpl:unimplemented_syscall_go_proto", - "//pkg/sentry/usage", - "//pkg/sentry/usermem", - "//pkg/sentry/watchdog", - "//pkg/syserror", - "//pkg/tcpip", - "//pkg/tcpip/link/fdbased", - "//pkg/tcpip/link/loopback", - "//pkg/tcpip/link/sniffer", - "//pkg/tcpip/network/arp", - "//pkg/tcpip/network/ipv4", - "//pkg/tcpip/network/ipv6", - "//pkg/tcpip/stack", - "//pkg/tcpip/transport/icmp", - "//pkg/tcpip/transport/tcp", - "//pkg/tcpip/transport/udp", - "//pkg/urpc", - "//runsc/boot/filter", - "//runsc/boot/platforms", - "//runsc/specutils", - "@com_github_golang_protobuf//proto:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "boot_test", - size = "small", - srcs = [ - "compat_test.go", - "fs_test.go", - "loader_test.go", - "user_test.go", - ], - embed = [":boot"], - deps = [ - "//pkg/control/server", - "//pkg/log", - "//pkg/p9", - "//pkg/sentry/arch:registers_go_proto", - "//pkg/sentry/context/contexttest", - "//pkg/sentry/fs", - "//pkg/unet", - "//runsc/fsgofer", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) diff --git a/runsc/boot/compat_test.go b/runsc/boot/compat_test.go deleted file mode 100644 index 388298d8d..000000000 --- a/runsc/boot/compat_test.go +++ /dev/null @@ -1,85 +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 boot - -import ( - "testing" - - rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto" -) - -func TestOnceTracker(t *testing.T) { - o := onceTracker{} - if !o.shouldReport(nil) { - t.Error("first call to checkAndMark, got: false, want: true") - } - o.onReported(nil) - for i := 0; i < 2; i++ { - if o.shouldReport(nil) { - t.Error("after first call to checkAndMark, got: true, want: false") - } - } -} - -func TestArgsTracker(t *testing.T) { - for _, tc := range []struct { - name string - idx []int - rdi1 uint64 - rdi2 uint64 - rsi1 uint64 - rsi2 uint64 - want bool - }{ - {name: "same rdi", idx: []int{0}, rdi1: 123, rdi2: 123, want: false}, - {name: "same rsi", idx: []int{1}, rsi1: 123, rsi2: 123, want: false}, - {name: "diff rdi", idx: []int{0}, rdi1: 123, rdi2: 321, want: true}, - {name: "diff rsi", idx: []int{1}, rsi1: 123, rsi2: 321, want: true}, - {name: "cmd is uint32", idx: []int{0}, rsi1: 0xdead00000123, rsi2: 0xbeef00000123, want: false}, - {name: "same 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 123, rdi2: 321, want: false}, - {name: "diff 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 789, rdi2: 987, want: true}, - } { - t.Run(tc.name, func(t *testing.T) { - c := newArgsTracker(tc.idx...) - regs := &rpb.AMD64Registers{Rdi: tc.rdi1, Rsi: tc.rsi1} - if !c.shouldReport(regs) { - t.Error("first call to shouldReport, got: false, want: true") - } - c.onReported(regs) - - regs.Rdi, regs.Rsi = tc.rdi2, tc.rsi2 - if got := c.shouldReport(regs); tc.want != got { - t.Errorf("second call to shouldReport, got: %t, want: %t", got, tc.want) - } - }) - } -} - -func TestArgsTrackerLimit(t *testing.T) { - c := newArgsTracker(0, 1) - for i := 0; i < reportLimit; i++ { - regs := &rpb.AMD64Registers{Rdi: 123, Rsi: uint64(i)} - if !c.shouldReport(regs) { - t.Error("shouldReport before limit was reached, got: false, want: true") - } - c.onReported(regs) - } - - // Should hit the count limit now. - regs := &rpb.AMD64Registers{Rdi: 123, Rsi: 123456} - if c.shouldReport(regs) { - t.Error("shouldReport after limit was reached, got: true, want: false") - } -} diff --git a/runsc/boot/filter/BUILD b/runsc/boot/filter/BUILD deleted file mode 100644 index f5509b6b7..000000000 --- a/runsc/boot/filter/BUILD +++ /dev/null @@ -1,26 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filter", - srcs = [ - "config.go", - "extra_filters.go", - "extra_filters_msan.go", - "extra_filters_race.go", - "filter.go", - ], - importpath = "gvisor.dev/gvisor/runsc/boot/filter", - visibility = [ - "//runsc/boot:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/seccomp", - "//pkg/sentry/platform", - "//pkg/tcpip/link/fdbased", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/boot/fs_test.go b/runsc/boot/fs_test.go deleted file mode 100644 index 49ab34b33..000000000 --- a/runsc/boot/fs_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2019 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 boot - -import ( - "path" - "reflect" - "strings" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -func TestPodMountHintsHappy(t *testing.T) { - spec := &specs.Spec{ - Annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - - path.Join(MountPrefix, "mount2", "source"): "bar", - path.Join(MountPrefix, "mount2", "type"): "bind", - path.Join(MountPrefix, "mount2", "share"): "container", - path.Join(MountPrefix, "mount2", "options"): "rw,private", - }, - } - podHints, err := newPodMountHints(spec) - if err != nil { - t.Errorf("newPodMountHints failed: %v", err) - } - - // Check that fields were set correctly. - mount1 := podHints.mounts["mount1"] - if want := "mount1"; want != mount1.name { - t.Errorf("mount1 name, want: %q, got: %q", want, mount1.name) - } - if want := "foo"; want != mount1.mount.Source { - t.Errorf("mount1 source, want: %q, got: %q", want, mount1.mount.Source) - } - if want := "tmpfs"; want != mount1.mount.Type { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Type) - } - if want := pod; want != mount1.share { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.share) - } - if want := []string(nil); !reflect.DeepEqual(want, mount1.mount.Options) { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Options) - } - - mount2 := podHints.mounts["mount2"] - if want := "mount2"; want != mount2.name { - t.Errorf("mount2 name, want: %q, got: %q", want, mount2.name) - } - if want := "bar"; want != mount2.mount.Source { - t.Errorf("mount2 source, want: %q, got: %q", want, mount2.mount.Source) - } - if want := "bind"; want != mount2.mount.Type { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Type) - } - if want := container; want != mount2.share { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.share) - } - if want := []string{"private", "rw"}; !reflect.DeepEqual(want, mount2.mount.Options) { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Options) - } -} - -func TestPodMountHintsErrors(t *testing.T) { - for _, tst := range []struct { - name string - annotations map[string]string - error string - }{ - { - name: "too short", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1"): "foo", - }, - error: "invalid mount annotation", - }, - { - name: "no name", - annotations: map[string]string{ - MountPrefix + "//source": "foo", - }, - error: "invalid mount name", - }, - { - name: "missing source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "source field", - }, - { - name: "missing type", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "type field", - }, - { - name: "missing share", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - }, - error: "share field", - }, - { - name: "invalid field name", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "invalid"): "foo", - }, - error: "invalid mount annotation", - }, - { - name: "invalid source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "source cannot be empty", - }, - { - name: "invalid type", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "invalid-type", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "invalid type", - }, - { - name: "invalid share", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "invalid-share", - }, - error: "invalid share", - }, - { - name: "invalid options", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - path.Join(MountPrefix, "mount1", "options"): "invalid-option", - }, - error: "unknown mount option", - }, - { - name: "duplicate source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - - path.Join(MountPrefix, "mount2", "source"): "foo", - path.Join(MountPrefix, "mount2", "type"): "bind", - path.Join(MountPrefix, "mount2", "share"): "container", - }, - error: "have the same mount source", - }, - } { - t.Run(tst.name, func(t *testing.T) { - spec := &specs.Spec{Annotations: tst.annotations} - podHints, err := newPodMountHints(spec) - if err == nil || !strings.Contains(err.Error(), tst.error) { - t.Errorf("newPodMountHints invalid error, want: .*%s.*, got: %v", tst.error, err) - } - if podHints != nil { - t.Errorf("newPodMountHints must return nil on failure: %+v", podHints) - } - }) - } -} diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go deleted file mode 100644 index e0e32b9d5..000000000 --- a/runsc/boot/loader_test.go +++ /dev/null @@ -1,632 +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 boot - -import ( - "fmt" - "math/rand" - "os" - "reflect" - "sync" - "syscall" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/control/server" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/context/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/runsc/fsgofer" -) - -func init() { - log.SetLevel(log.Debug) - rand.Seed(time.Now().UnixNano()) - if err := fsgofer.OpenProcSelfFD(); err != nil { - panic(err) - } -} - -func testConfig() *Config { - return &Config{ - RootDir: "unused_root_dir", - Network: NetworkNone, - DisableSeccomp: true, - Platform: "ptrace", - } -} - -// testSpec returns a simple spec that can be used in tests. -func testSpec() *specs.Spec { - return &specs.Spec{ - // The host filesystem root is the sandbox root. - Root: &specs.Root{ - Path: "/", - Readonly: true, - }, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - } -} - -// startGofer starts a new gofer routine serving 'root' path. It returns the -// sandbox side of the connection, and a function that when called will stop the -// gofer. -func startGofer(root string) (int, func(), error) { - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) - if err != nil { - return 0, nil, err - } - sandboxEnd, goferEnd := fds[0], fds[1] - - socket, err := unet.NewSocket(goferEnd) - if err != nil { - syscall.Close(sandboxEnd) - syscall.Close(goferEnd) - return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err) - } - at, err := fsgofer.NewAttachPoint(root, fsgofer.Config{ROMount: true}) - if err != nil { - return 0, nil, err - } - go func() { - s := p9.NewServer(at) - if err := s.Handle(socket); err != nil { - log.Infof("Gofer is stopping. FD: %d, err: %v\n", goferEnd, err) - } - }() - // Closing the gofer socket will stop the gofer and exit goroutine above. - cleanup := func() { - if err := socket.Close(); err != nil { - log.Warningf("Error closing gofer socket: %v", err) - } - } - return sandboxEnd, cleanup, nil -} - -func createLoader() (*Loader, func(), error) { - fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10])) - if err != nil { - return nil, nil, err - } - conf := testConfig() - spec := testSpec() - - sandEnd, cleanup, err := startGofer(spec.Root.Path) - if err != nil { - return nil, nil, err - } - - stdio := []int{int(os.Stdin.Fd()), int(os.Stdout.Fd()), int(os.Stderr.Fd())} - args := Args{ - ID: "foo", - Spec: spec, - Conf: conf, - ControllerFD: fd, - GoferFDs: []int{sandEnd}, - StdioFDs: stdio, - } - l, err := New(args) - if err != nil { - cleanup() - return nil, nil, err - } - return l, cleanup, nil -} - -// TestRun runs a simple application in a sandbox and checks that it succeeds. -func TestRun(t *testing.T) { - l, cleanup, err := createLoader() - if err != nil { - t.Fatalf("error creating loader: %v", err) - } - defer l.Destroy() - defer cleanup() - - // Start a goroutine to read the start chan result, otherwise Run will - // block forever. - var resultChanErr error - var wg sync.WaitGroup - wg.Add(1) - go func() { - resultChanErr = <-l.ctrl.manager.startResultChan - wg.Done() - }() - - // Run the container. - if err := l.Run(); err != nil { - t.Errorf("error running container: %v", err) - } - - // We should have not gotten an error on the startResultChan. - wg.Wait() - if resultChanErr != nil { - t.Errorf("error on startResultChan: %v", resultChanErr) - } - - // Wait for the application to exit. It should succeed. - if status := l.WaitExit(); status.Code != 0 || status.Signo != 0 { - t.Errorf("application exited with status %+v, want 0", status) - } -} - -// TestStartSignal tests that the controller Start message will cause -// WaitForStartSignal to return. -func TestStartSignal(t *testing.T) { - l, cleanup, err := createLoader() - if err != nil { - t.Fatalf("error creating loader: %v", err) - } - defer l.Destroy() - defer cleanup() - - // We aren't going to wait on this application, so the control server - // needs to be shut down manually. - defer l.ctrl.srv.Stop() - - // Start a goroutine that calls WaitForStartSignal and writes to a - // channel when it returns. - waitFinished := make(chan struct{}) - go func() { - l.WaitForStartSignal() - // Pretend that Run() executed and returned no error. - l.ctrl.manager.startResultChan <- nil - waitFinished <- struct{}{} - }() - - // Nothing has been written to the channel, so waitFinished should not - // return. Give it a little bit of time to make sure the goroutine has - // started. - select { - case <-waitFinished: - t.Errorf("WaitForStartSignal completed but it should not have") - case <-time.After(50 * time.Millisecond): - // OK. - } - - // Trigger the control server StartRoot method. - cid := "foo" - if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil { - t.Errorf("error calling StartRoot: %v", err) - } - - // Now WaitForStartSignal should return (within a short amount of - // time). - select { - case <-waitFinished: - // OK. - case <-time.After(50 * time.Millisecond): - t.Errorf("WaitForStartSignal did not complete but it should have") - } - -} - -// Test that MountNamespace can be created with various specs. -func TestCreateMountNamespace(t *testing.T) { - testCases := []struct { - name string - // Spec that will be used to create the mount manager. Note - // that we can't mount procfs without a kernel, so each spec - // MUST contain something other than procfs mounted at /proc. - spec specs.Spec - // Paths that are expected to exist in the resulting fs. - expectedPaths []string - }{ - { - // Only proc. - name: "only proc mount", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - // /proc, /dev, and /sys should always be mounted. - expectedPaths: []string{"/proc", "/dev", "/sys"}, - }, - { - // Mount at a deep path, with many components that do - // not exist in the root. - name: "deep mount path", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/some/very/very/deep/path", - Type: "tmpfs", - }, - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - // /some/deep/path should be mounted, along with /proc, - // /dev, and /sys. - expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"}, - }, - { - // Mounts are nested inside each other. - name: "nested mounts", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - { - Destination: "/foo", - Type: "tmpfs", - }, - { - Destination: "/foo/qux", - Type: "tmpfs", - }, - { - // File mounts with the same prefix. - Destination: "/foo/qux-quz", - Type: "tmpfs", - }, - { - Destination: "/foo/bar", - Type: "tmpfs", - }, - { - Destination: "/foo/bar/baz", - Type: "tmpfs", - }, - { - // A deep path that is in foo but not the other mounts. - Destination: "/foo/some/very/very/deep/path", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux", - "/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"}, - }, - { - name: "mount inside /dev", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - { - Destination: "/dev", - Type: "tmpfs", - }, - { - // Mounted by runsc by default. - Destination: "/dev/fd", - Type: "tmpfs", - }, - { - // Mount with the same prefix. - Destination: "/dev/fd-foo", - Type: "tmpfs", - }, - { - // Unsupported fs type. - Destination: "/dev/mqueue", - Type: "mqueue", - }, - { - Destination: "/dev/foo", - Type: "tmpfs", - }, - { - Destination: "/dev/bar", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"}, - }, - { - name: "mounts inside mandatory mounts", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - // We don't include /sys, and /tmp in - // the spec, since they will be added - // automatically. - // - // Instead, add submounts inside these - // directories and make sure they are - // visible under the mandatory mounts. - { - Destination: "/sys/bar", - Type: "tmpfs", - }, - { - Destination: "/tmp/baz", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - conf := testConfig() - ctx := contexttest.Context(t) - - sandEnd, cleanup, err := startGofer(tc.spec.Root.Path) - if err != nil { - t.Fatalf("failed to create gofer: %v", err) - } - defer cleanup() - - // setupRootContainer needs to find root from the context after the - // namespace is created. - var mns *fs.MountNamespace - setMountNS := func(m *fs.MountNamespace) { - mns = m - ctx.(*contexttest.TestContext).RegisterValue(fs.CtxRoot, mns.Root()) - } - mntr := newContainerMounter(&tc.spec, []int{sandEnd}, nil, &podMountHints{}) - if err := mntr.setupRootContainer(ctx, ctx, conf, setMountNS); err != nil { - t.Fatalf("createMountNamespace test case %q failed: %v", tc.name, err) - } - root := mns.Root() - defer root.DecRef() - for _, p := range tc.expectedPaths { - maxTraversals := uint(0) - if d, err := mns.FindInode(ctx, root, root, p, &maxTraversals); err != nil { - t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) - } else { - d.DecRef() - } - } - }) - } -} - -// TestRestoreEnvironment tests that the correct mounts are collected from the spec and config -// in order to build the environment for restoring. -func TestRestoreEnvironment(t *testing.T) { - testCases := []struct { - name string - spec *specs.Spec - ioFDs []int - errorExpected bool - expectedRenv fs.RestoreEnvironment - }{ - { - name: "basic spec test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/some/very/very/deep/path", - Type: "tmpfs", - }, - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - ioFDs: []int{0}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - }, - { - Dev: "none", - }, - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - { - name: "bind type test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/dev/fd-foo", - Type: "bind", - }, - }, - }, - ioFDs: []int{0, 1}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - { - Dev: "9pfs-/dev/fd-foo", - DataString: "trans=fd,rfdno=1,wfdno=1,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "proc": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - { - name: "options test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/dev/fd-foo", - Type: "tmpfs", - Options: []string{"uid=1022", "noatime"}, - }, - }, - }, - ioFDs: []int{0}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - Flags: fs.MountSourceFlags{NoAtime: true}, - DataString: "uid=1022", - }, - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "proc": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - conf := testConfig() - mntr := newContainerMounter(tc.spec, tc.ioFDs, nil, &podMountHints{}) - actualRenv, err := mntr.createRestoreEnvironment(conf) - if !tc.errorExpected && err != nil { - t.Fatalf("could not create restore environment for test:%s", tc.name) - } else if tc.errorExpected { - if err == nil { - t.Errorf("expected an error, but no error occurred.") - } - } else { - if !reflect.DeepEqual(*actualRenv, tc.expectedRenv) { - t.Errorf("restore environments did not match for test:%s\ngot:%+v\nwant:%+v\n", tc.name, *actualRenv, tc.expectedRenv) - } - } - }) - } -} diff --git a/runsc/boot/platforms/BUILD b/runsc/boot/platforms/BUILD deleted file mode 100644 index 03391cdca..000000000 --- a/runsc/boot/platforms/BUILD +++ /dev/null @@ -1,16 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "platforms", - srcs = ["platforms.go"], - importpath = "gvisor.dev/gvisor/runsc/boot/platforms", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/sentry/platform/kvm", - "//pkg/sentry/platform/ptrace", - ], -) diff --git a/runsc/boot/user_test.go b/runsc/boot/user_test.go deleted file mode 100644 index 01f666507..000000000 --- a/runsc/boot/user_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2019 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 boot - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - "syscall" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/sentry/context/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -func setupTempDir() (string, error) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "exec-user-test") - if err != nil { - return "", err - } - return tmpDir, nil -} - -func setupPasswd(contents string, perms os.FileMode) func() (string, error) { - return func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - f, err := os.Create(filepath.Join(tmpDir, "etc", "passwd")) - if err != nil { - return "", err - } - defer f.Close() - - _, err = f.WriteString(contents) - if err != nil { - return "", err - } - - err = f.Chmod(perms) - if err != nil { - return "", err - } - return tmpDir, nil - } -} - -// TestGetExecUserHome tests the getExecUserHome function. -func TestGetExecUserHome(t *testing.T) { - tests := map[string]struct { - uid uint32 - createRoot func() (string, error) - expected string - }{ - "success": { - uid: 1000, - createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0666), - expected: "/home/adin", - }, - "no_passwd": { - uid: 1000, - createRoot: setupTempDir, - expected: "/", - }, - "no_perms": { - uid: 1000, - createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0000), - expected: "/", - }, - "directory": { - uid: 1000, - createRoot: func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - if err := syscall.Mkdir(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil { - return "", err - } - - return tmpDir, nil - }, - expected: "/", - }, - // Currently we don't allow named pipes. - "named_pipe": { - uid: 1000, - createRoot: func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - if err := syscall.Mkfifo(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil { - return "", err - } - - return tmpDir, nil - }, - expected: "/", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - tmpDir, err := tc.createRoot() - if err != nil { - t.Fatalf("failed to create root dir: %v", err) - } - - sandEnd, cleanup, err := startGofer(tmpDir) - if err != nil { - t.Fatalf("failed to create gofer: %v", err) - } - defer cleanup() - - ctx := contexttest.Context(t) - conf := &Config{ - RootDir: "unused_root_dir", - Network: NetworkNone, - DisableSeccomp: true, - } - - spec := &specs.Spec{ - Root: &specs.Root{ - Path: tmpDir, - Readonly: true, - }, - // Add /proc mount as tmpfs to avoid needing a kernel. - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - } - - var mns *fs.MountNamespace - setMountNS := func(m *fs.MountNamespace) { - mns = m - ctx.(*contexttest.TestContext).RegisterValue(fs.CtxRoot, mns.Root()) - } - mntr := newContainerMounter(spec, []int{sandEnd}, nil, &podMountHints{}) - if err := mntr.setupRootContainer(ctx, ctx, conf, setMountNS); err != nil { - t.Fatalf("failed to create mount namespace: %v", err) - } - - got, err := getExecUserHome(ctx, mns, tc.uid) - if err != nil { - t.Fatalf("failed to get user home: %v", err) - } - - if got != tc.expected { - t.Fatalf("expected %v, got: %v", tc.expected, got) - } - }) - } -} - -// TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing. -func TestFindHomeInPasswd(t *testing.T) { - tests := map[string]struct { - uid uint32 - passwd string - expected string - def string - }{ - "empty": { - uid: 1000, - passwd: "", - expected: "/", - def: "/", - }, - "whitespace": { - uid: 1000, - passwd: " ", - expected: "/", - def: "/", - }, - "full": { - uid: 1000, - passwd: "adin::1000:1111::/home/adin:/bin/sh", - expected: "/home/adin", - def: "/", - }, - // For better or worse, this is how runc works. - "partial": { - uid: 1000, - passwd: "adin::1000:1111:", - expected: "", - def: "/", - }, - "multiple": { - uid: 1001, - passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh", - expected: "/home/ian", - def: "/", - }, - "duplicate": { - uid: 1000, - passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh", - expected: "/home/adin", - def: "/", - }, - "empty_lines": { - uid: 1001, - passwd: "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh", - expected: "/home/ian", - def: "/", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def) - if err != nil { - t.Fatalf("error parsing passwd: %v", err) - } - if tc.expected != got { - t.Fatalf("expected %v, got: %v", tc.expected, got) - } - }) - } -} diff --git a/runsc/cgroup/BUILD b/runsc/cgroup/BUILD deleted file mode 100644 index ab2387614..000000000 --- a/runsc/cgroup/BUILD +++ /dev/null @@ -1,26 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "cgroup", - srcs = ["cgroup.go"], - importpath = "gvisor.dev/gvisor/runsc/cgroup", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/log", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) - -go_test( - name = "cgroup_test", - size = "small", - srcs = ["cgroup_test.go"], - embed = [":cgroup"], - tags = ["local"], -) diff --git a/runsc/cgroup/cgroup_test.go b/runsc/cgroup/cgroup_test.go deleted file mode 100644 index 548c80e9a..000000000 --- a/runsc/cgroup/cgroup_test.go +++ /dev/null @@ -1,67 +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 cgroup - -import ( - "testing" -) - -func TestUninstallEnoent(t *testing.T) { - c := Cgroup{ - // set a non-existent name - Name: "runsc-test-uninstall-656e6f656e740a", - Own: true, - } - if err := c.Uninstall(); err != nil { - t.Errorf("Uninstall() failed: %v", err) - } -} - -func TestCountCpuset(t *testing.T) { - for _, tc := range []struct { - str string - want int - error bool - }{ - {str: "0", want: 1}, - {str: "0,1,2,8,9,10", want: 6}, - {str: "0-1", want: 2}, - {str: "0-7", want: 8}, - {str: "0-7,16,32-39,64,65", want: 19}, - {str: "a", error: true}, - {str: "5-a", error: true}, - {str: "a-5", error: true}, - {str: "-10", error: true}, - {str: "15-", error: true}, - {str: "-", error: true}, - {str: "--", error: true}, - } { - t.Run(tc.str, func(t *testing.T) { - got, err := countCpuset(tc.str) - if tc.error { - if err == nil { - t.Errorf("countCpuset(%q) should have failed", tc.str) - } - } else { - if err != nil { - t.Errorf("countCpuset(%q) failed: %v", tc.str, err) - } - if tc.want != got { - t.Errorf("countCpuset(%q) want: %d, got: %d", tc.str, tc.want, got) - } - } - }) - } -} diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD deleted file mode 100644 index 5223b9972..000000000 --- a/runsc/cmd/BUILD +++ /dev/null @@ -1,90 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "cmd", - srcs = [ - "boot.go", - "capability.go", - "checkpoint.go", - "chroot.go", - "cmd.go", - "create.go", - "debug.go", - "delete.go", - "do.go", - "error.go", - "events.go", - "exec.go", - "gofer.go", - "help.go", - "kill.go", - "list.go", - "path.go", - "pause.go", - "ps.go", - "restore.go", - "resume.go", - "run.go", - "spec.go", - "start.go", - "state.go", - "syscalls.go", - "wait.go", - ], - importpath = "gvisor.dev/gvisor/runsc/cmd", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/log", - "//pkg/p9", - "//pkg/sentry/control", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/unet", - "//pkg/urpc", - "//runsc/boot", - "//runsc/boot/platforms", - "//runsc/console", - "//runsc/container", - "//runsc/fsgofer", - "//runsc/fsgofer/filter", - "//runsc/specutils", - "@com_github_google_subcommands//: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", - ], -) - -go_test( - name = "cmd_test", - size = "small", - srcs = [ - "capability_test.go", - "delete_test.go", - "exec_test.go", - "gofer_test.go", - ], - data = [ - "//runsc", - ], - embed = [":cmd"], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/kernel/auth", - "//pkg/urpc", - "//runsc/boot", - "//runsc/container", - "//runsc/specutils", - "//runsc/test/testutil", - "@com_github_google_go-cmp//cmp:go_default_library", - "@com_github_google_go-cmp//cmp/cmpopts:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - ], -) diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go deleted file mode 100644 index 3ae25a257..000000000 --- a/runsc/cmd/capability_test.go +++ /dev/null @@ -1,126 +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 cmd - -import ( - "fmt" - "os" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/syndtr/gocapability/capability" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/container" - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func init() { - log.SetLevel(log.Debug) - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } -} - -func checkProcessCaps(pid int, wantCaps *specs.LinuxCapabilities) error { - curCaps, err := capability.NewPid2(pid) - if err != nil { - return fmt.Errorf("capability.NewPid2(%d) failed: %v", pid, err) - } - if err := curCaps.Load(); err != nil { - return fmt.Errorf("unable to load capabilities: %v", err) - } - fmt.Printf("Capabilities (PID: %d): %v\n", pid, curCaps) - - for _, c := range allCapTypes { - if err := checkCaps(c, curCaps, wantCaps); err != nil { - return err - } - } - return nil -} - -func checkCaps(which capability.CapType, curCaps capability.Capabilities, wantCaps *specs.LinuxCapabilities) error { - wantNames := getCaps(which, wantCaps) - for name, c := range capFromName { - want := specutils.ContainsStr(wantNames, name) - got := curCaps.Get(which, c) - if want != got { - if want { - return fmt.Errorf("capability %v:%s should be set", which, name) - } - return fmt.Errorf("capability %v:%s should NOT be set", which, name) - } - } - return nil -} - -func TestCapabilities(t *testing.T) { - stop := testutil.StartReaper() - defer stop() - - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - caps := []string{ - "CAP_CHOWN", - "CAP_SYS_PTRACE", // ptrace is added due to the platform choice. - } - spec.Process.Capabilities = &specs.LinuxCapabilities{ - Permitted: caps, - Bounding: caps, - Effective: caps, - Inheritable: caps, - } - - conf := testutil.TestConfig() - - // Use --network=host to make sandbox use spec's capabilities. - conf.Network = boot.NetworkHost - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := container.Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := container.New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Check that sandbox and gofer have the proper capabilities. - if err := checkProcessCaps(c.Sandbox.Pid, spec.Process.Capabilities); err != nil { - t.Error(err) - } - if err := checkProcessCaps(c.GoferPid, goferCaps); err != nil { - t.Error(err) - } -} - -func TestMain(m *testing.M) { - specutils.MaybeRunAsRoot() - os.Exit(m.Run()) -} diff --git a/runsc/cmd/delete_test.go b/runsc/cmd/delete_test.go deleted file mode 100644 index cb59516a3..000000000 --- a/runsc/cmd/delete_test.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 cmd - -import ( - "io/ioutil" - "testing" - - "gvisor.dev/gvisor/runsc/boot" -) - -func TestNotFound(t *testing.T) { - ids := []string{"123"} - dir, err := ioutil.TempDir("", "metadata") - if err != nil { - t.Fatalf("error creating dir: %v", err) - } - conf := &boot.Config{RootDir: dir} - - d := Delete{} - if err := d.execute(ids, conf); err == nil { - t.Error("Deleting non-existent container should have failed") - } - - d = Delete{force: true} - if err := d.execute(ids, conf); err != nil { - t.Errorf("Deleting non-existent container with --force should NOT have failed: %v", err) - } -} diff --git a/runsc/cmd/exec_test.go b/runsc/cmd/exec_test.go deleted file mode 100644 index eb38a431f..000000000 --- a/runsc/cmd/exec_test.go +++ /dev/null @@ -1,154 +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 cmd - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/urpc" -) - -func TestUser(t *testing.T) { - testCases := []struct { - input string - want user - wantErr bool - }{ - {input: "0", want: user{kuid: 0, kgid: 0}}, - {input: "7", want: user{kuid: 7, kgid: 0}}, - {input: "49:343", want: user{kuid: 49, kgid: 343}}, - {input: "0:2401", want: user{kuid: 0, kgid: 2401}}, - {input: "", wantErr: true}, - {input: "foo", wantErr: true}, - {input: ":123", wantErr: true}, - {input: "1:2:3", wantErr: true}, - } - - for _, tc := range testCases { - var u user - if err := u.Set(tc.input); err != nil && tc.wantErr { - // We got an error and wanted one. - continue - } else if err == nil && tc.wantErr { - t.Errorf("user.Set(%s): got no error, but wanted one", tc.input) - } else if err != nil && !tc.wantErr { - t.Errorf("user.Set(%s): got error %v, but wanted none", tc.input, err) - } else if u != tc.want { - t.Errorf("user.Set(%s): got %+v, but wanted %+v", tc.input, u, tc.want) - } - } -} - -func TestCLIArgs(t *testing.T) { - testCases := []struct { - ex Exec - argv []string - expected control.ExecArgs - }{ - { - ex: Exec{ - cwd: "/foo/bar", - user: user{kuid: 0, kgid: 0}, - extraKGIDs: []string{"1", "2", "3"}, - caps: []string{"CAP_DAC_OVERRIDE"}, - processPath: "", - }, - argv: []string{"ls", "/"}, - expected: control.ExecArgs{ - Argv: []string{"ls", "/"}, - WorkingDirectory: "/foo/bar", - FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}, - KUID: 0, - KGID: 0, - ExtraKGIDs: []auth.KGID{1, 2, 3}, - Capabilities: &auth.TaskCapabilities{ - BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - }, - }, - }, - } - - for _, tc := range testCases { - e, err := tc.ex.argsFromCLI(tc.argv) - if err != nil { - t.Errorf("argsFromCLI(%+v): got error: %+v", tc.ex, err) - } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) { - t.Errorf("argsFromCLI(%+v): got %+v, but expected %+v", tc.ex, *e, tc.expected) - } - } -} - -func TestJSONArgs(t *testing.T) { - testCases := []struct { - // ex is provided to make sure it is overridden by p. - ex Exec - p specs.Process - expected control.ExecArgs - }{ - { - ex: Exec{ - cwd: "/baz/quux", - user: user{kuid: 1, kgid: 1}, - extraKGIDs: []string{"4", "5", "6"}, - caps: []string{"CAP_SETGID"}, - processPath: "/bin/foo", - }, - p: specs.Process{ - User: specs.User{UID: 0, GID: 0, AdditionalGids: []uint32{1, 2, 3}}, - Args: []string{"ls", "/"}, - Cwd: "/foo/bar", - Capabilities: &specs.LinuxCapabilities{ - Bounding: []string{"CAP_DAC_OVERRIDE"}, - Effective: []string{"CAP_DAC_OVERRIDE"}, - Inheritable: []string{"CAP_DAC_OVERRIDE"}, - Permitted: []string{"CAP_DAC_OVERRIDE"}, - }, - }, - expected: control.ExecArgs{ - Argv: []string{"ls", "/"}, - WorkingDirectory: "/foo/bar", - FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}, - KUID: 0, - KGID: 0, - ExtraKGIDs: []auth.KGID{1, 2, 3}, - Capabilities: &auth.TaskCapabilities{ - BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - }, - }, - }, - } - - for _, tc := range testCases { - e, err := argsFromProcess(&tc.p) - if err != nil { - t.Errorf("argsFromProcess(%+v): got error: %+v", tc.p, err) - } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) { - t.Errorf("argsFromProcess(%+v): got %+v, but expected %+v", tc.p, *e, tc.expected) - } - } -} diff --git a/runsc/cmd/gofer_test.go b/runsc/cmd/gofer_test.go deleted file mode 100644 index cbea7f127..000000000 --- a/runsc/cmd/gofer_test.go +++ /dev/null @@ -1,164 +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 cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "testing" -) - -func tmpDir() string { - dir := os.Getenv("TEST_TMPDIR") - if dir == "" { - dir = "/tmp" - } - return dir -} - -type dir struct { - rel string - link string -} - -func construct(root string, dirs []dir) error { - for _, d := range dirs { - p := path.Join(root, d.rel) - if d.link == "" { - if err := os.MkdirAll(p, 0755); err != nil { - return fmt.Errorf("error creating dir: %v", err) - } - } else { - if err := os.MkdirAll(path.Dir(p), 0755); err != nil { - return fmt.Errorf("error creating dir: %v", err) - } - if err := os.Symlink(d.link, p); err != nil { - return fmt.Errorf("error creating symlink: %v", err) - } - } - } - return nil -} - -func TestResolveSymlinks(t *testing.T) { - root, err := ioutil.TempDir(tmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - dirs := []dir{ - {"dir1/dir11/dir111/dir1111", ""}, // Just a boring dir - {"dir1/lnk12", "dir11"}, // Link to sibling - {"dir1/lnk13", "./dir11"}, // Link to sibling through self - {"dir1/lnk14", "../dir1/dir11"}, // Link to sibling through parent - {"dir1/dir15/lnk151", ".."}, // Link to parent - {"dir1/lnk16", "dir11/dir111"}, // Link to child - {"dir1/lnk17", "."}, // Link to self - {"dir1/lnk18", "lnk13"}, // Link to link - {"lnk2", "dir1/lnk13"}, // Link to link to link - {"dir3/dir21/lnk211", "../.."}, // Link to root relative - {"dir3/lnk22", "/"}, // Link to root absolute - {"dir3/lnk23", "/dir1"}, // Link to dir absolute - {"dir3/lnk24", "/dir1/lnk12"}, // Link to link absolute - {"lnk5", "../../.."}, // Link outside root - } - if err := construct(root, dirs); err != nil { - t.Fatal("construct failed:", err) - } - - tests := []struct { - name string - rel string - want string - compareHost bool - }{ - {name: "root", rel: "/", want: "/", compareHost: true}, - {name: "basic dir", rel: "/dir1/dir11/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dot 1", rel: "/dir1/dir11/./dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dot 2", rel: "/dir1/././dir11/./././././dir111/.", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dotdot 1", rel: "/dir1/dir11/../dir15", want: "/dir1/dir15", compareHost: true}, - {name: "dotdot 2", rel: "/dir1/dir11/dir1111/../..", want: "/dir1", compareHost: true}, - - {name: "link sibling", rel: "/dir1/lnk12", want: "/dir1/dir11", compareHost: true}, - {name: "link sibling + dir", rel: "/dir1/lnk12/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link sibling through self", rel: "/dir1/lnk13", want: "/dir1/dir11", compareHost: true}, - {name: "link sibling through parent", rel: "/dir1/lnk14", want: "/dir1/dir11", compareHost: true}, - - {name: "link parent", rel: "/dir1/dir15/lnk151", want: "/dir1", compareHost: true}, - {name: "link parent + dir", rel: "/dir1/dir15/lnk151/dir11", want: "/dir1/dir11", compareHost: true}, - {name: "link child", rel: "/dir1/lnk16", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link child + dir", rel: "/dir1/lnk16/dir1111", want: "/dir1/dir11/dir111/dir1111", compareHost: true}, - {name: "link self", rel: "/dir1/lnk17", want: "/dir1", compareHost: true}, - {name: "link self + dir", rel: "/dir1/lnk17/dir11", want: "/dir1/dir11", compareHost: true}, - - {name: "link^2", rel: "/dir1/lnk18", want: "/dir1/dir11", compareHost: true}, - {name: "link^2 + dir", rel: "/dir1/lnk18/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link^3", rel: "/lnk2", want: "/dir1/dir11", compareHost: true}, - {name: "link^3 + dir", rel: "/lnk2/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - - {name: "link abs", rel: "/dir3/lnk23", want: "/dir1"}, - {name: "link abs + dir", rel: "/dir3/lnk23/dir11", want: "/dir1/dir11"}, - {name: "link^2 abs", rel: "/dir3/lnk24", want: "/dir1/dir11"}, - {name: "link^2 abs + dir", rel: "/dir3/lnk24/dir111", want: "/dir1/dir11/dir111"}, - - {name: "root link rel", rel: "/dir3/dir21/lnk211", want: "/", compareHost: true}, - {name: "root link abs", rel: "/dir3/lnk22", want: "/"}, - {name: "root contain link", rel: "/lnk5/dir1", want: "/dir1"}, - {name: "root contain dotdot", rel: "/dir1/dir11/../../../../../../../..", want: "/"}, - - {name: "crazy", rel: "/dir3/dir21/lnk211/dir3/lnk22/dir1/dir11/../../lnk5/dir3/../dir3/lnk24/dir111/dir1111/..", want: "/dir1/dir11/dir111"}, - } - for _, tst := range tests { - t.Run(tst.name, func(t *testing.T) { - got, err := resolveSymlinks(root, tst.rel) - if err != nil { - t.Errorf("resolveSymlinks(root, %q) failed: %v", tst.rel, err) - } - want := path.Join(root, tst.want) - if got != want { - t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, got, want) - } - if tst.compareHost { - // Check that host got to the same end result. - host, err := filepath.EvalSymlinks(path.Join(root, tst.rel)) - if err != nil { - t.Errorf("path.EvalSymlinks(root, %q) failed: %v", tst.rel, err) - } - if host != got { - t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, host, got) - } - } - }) - } -} - -func TestResolveSymlinksLoop(t *testing.T) { - root, err := ioutil.TempDir(tmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - dirs := []dir{ - {"loop1", "loop2"}, - {"loop2", "loop1"}, - } - if err := construct(root, dirs); err != nil { - t.Fatal("construct failed:", err) - } - if _, err := resolveSymlinks(root, "loop1"); err == nil { - t.Errorf("resolveSymlinks() should have failed") - } -} diff --git a/runsc/console/BUILD b/runsc/console/BUILD deleted file mode 100644 index e623c1a0f..000000000 --- a/runsc/console/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "console", - srcs = [ - "console.go", - ], - importpath = "gvisor.dev/gvisor/runsc/console", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "@com_github_kr_pty//:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/container/BUILD b/runsc/container/BUILD deleted file mode 100644 index de8202bb1..000000000 --- a/runsc/container/BUILD +++ /dev/null @@ -1,65 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "container", - srcs = [ - "container.go", - "hook.go", - "status.go", - ], - importpath = "gvisor.dev/gvisor/runsc/container", - visibility = [ - "//runsc:__subpackages__", - "//test:__subpackages__", - ], - deps = [ - "//pkg/log", - "//pkg/sentry/control", - "//runsc/boot", - "//runsc/cgroup", - "//runsc/sandbox", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_gofrs_flock//:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) - -go_test( - name = "container_test", - size = "medium", - srcs = [ - "console_test.go", - "container_test.go", - "multi_container_test.go", - "shared_volume_test.go", - ], - data = [ - "//runsc", - "//runsc/container/test_app", - ], - embed = [":container"], - shard_count = 5, - tags = [ - "requires-kvm", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/unet", - "//pkg/urpc", - "//runsc/boot", - "//runsc/boot/platforms", - "//runsc/specutils", - "//runsc/test/testutil", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_kr_pty//:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/container/console_test.go b/runsc/container/console_test.go deleted file mode 100644 index e9372989f..000000000 --- a/runsc/container/console_test.go +++ /dev/null @@ -1,483 +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 container - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "sync" - "syscall" - "testing" - "time" - - "github.com/kr/pty" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/pkg/urpc" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// socketPath creates a path inside bundleDir and ensures that the returned -// path is under 108 charactors (the unix socket path length limit), -// relativizing the path if necessary. -func socketPath(bundleDir string) (string, error) { - path := filepath.Join(bundleDir, "socket") - cwd, err := os.Getwd() - if err != nil { - return "", fmt.Errorf("error getting cwd: %v", err) - } - relPath, err := filepath.Rel(cwd, path) - if err != nil { - return "", fmt.Errorf("error getting relative path for %q from cwd %q: %v", path, cwd, err) - } - if len(path) > len(relPath) { - path = relPath - } - const maxPathLen = 108 - if len(path) > maxPathLen { - return "", fmt.Errorf("could not get socket path under length limit %d: %s", maxPathLen, path) - } - return path, nil -} - -// createConsoleSocket creates a socket at the given path that will receive a -// console fd from the sandbox. If no error occurs, it returns the server -// socket and a cleanup function. -func createConsoleSocket(path string) (*unet.ServerSocket, func() error, error) { - srv, err := unet.BindAndListen(path, false) - if err != nil { - return nil, nil, fmt.Errorf("error binding and listening to socket %q: %v", path, err) - } - - cleanup := func() error { - if err := srv.Close(); err != nil { - return fmt.Errorf("error closing socket %q: %v", path, err) - } - if err := os.Remove(path); err != nil { - return fmt.Errorf("error removing socket %q: %v", path, err) - } - return nil - } - - return srv, cleanup, nil -} - -// receiveConsolePTY accepts a connection on the server socket and reads fds. -// It fails if more than one FD is received, or if the FD is not a PTY. It -// returns the PTY master file. -func receiveConsolePTY(srv *unet.ServerSocket) (*os.File, error) { - sock, err := srv.Accept() - if err != nil { - return nil, fmt.Errorf("error accepting socket connection: %v", err) - } - - // Allow 3 fds to be received. We only expect 1. - r := sock.Reader(true /* blocking */) - r.EnableFDs(1) - - // The socket is closed right after sending the FD, so EOF is - // an allowed error. - b := [][]byte{{}} - if _, err := r.ReadVec(b); err != nil && err != io.EOF { - return nil, fmt.Errorf("error reading from socket connection: %v", err) - } - - // We should have gotten a control message. - fds, err := r.ExtractFDs() - if err != nil { - return nil, fmt.Errorf("error extracting fds from socket connection: %v", err) - } - if len(fds) != 1 { - return nil, fmt.Errorf("got %d fds from socket, wanted 1", len(fds)) - } - - // Verify that the fd is a terminal. - if _, err := unix.IoctlGetTermios(fds[0], unix.TCGETS); err != nil { - return nil, fmt.Errorf("fd is not a terminal (ioctl TGGETS got %v)", err) - } - - return os.NewFile(uintptr(fds[0]), "pty_master"), nil -} - -// Test that an pty FD is sent over the console socket if one is provided. -func TestConsoleSocket(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - spec := testutil.NewSpecWithArgs("true") - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup, err := createConsoleSocket(sock) - if err != nil { - t.Fatalf("error creating socket at %q: %v", sock, err) - } - defer cleanup() - - // Create the container and pass the socket name. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - ConsoleSocket: sock, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - - // Make sure we get a console PTY. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - ptyMaster.Close() - } -} - -// Test that job control signals work on a console created with "exec -ti". -func TestJobControlSignalExec(t *testing.T) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - conf := testutil.TestConfig() - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Create a pty master/slave. The slave will be passed to the exec - // process. - ptyMaster, ptySlave, err := pty.Open() - if err != nil { - t.Fatalf("error opening pty: %v", err) - } - defer ptyMaster.Close() - defer ptySlave.Close() - - // Exec bash and attach a terminal. - execArgs := &control.ExecArgs{ - Filename: "/bin/bash", - // Don't let bash execute from profile or rc files, otherwise - // our PID counts get messed up. - Argv: []string{"/bin/bash", "--noprofile", "--norc"}, - // Pass the pty slave as FD 0, 1, and 2. - FilePayload: urpc.FilePayload{ - Files: []*os.File{ptySlave, ptySlave, ptySlave}, - }, - StdioIsPty: true, - } - - pid, err := c.Execute(execArgs) - if err != nil { - t.Fatalf("error executing: %v", err) - } - if pid != 2 { - t.Fatalf("exec got pid %d, wanted %d", pid, 2) - } - - // Make sure all the processes are running. - expectedPL := []*control.Process{ - // Root container process. - {PID: 1, Cmd: "sleep"}, - // Bash from exec process. - {PID: 2, Cmd: "bash"}, - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Execute sleep. - ptyMaster.Write([]byte("sleep 100\n")) - - // Wait for it to start. Sleep's PPID is bash's PID. - expectedPL = append(expectedPL, &control.Process{PID: 3, PPID: 2, Cmd: "sleep"}) - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Send a SIGTERM to the foreground process for the exec PID. Note that - // although we pass in the PID of "bash", it should actually terminate - // "sleep", since that is the foreground process. - if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGTERM, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Sleep process should be gone. - expectedPL = expectedPL[:len(expectedPL)-1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Sleep is dead, but it may take more time for bash to notice and - // change the foreground process back to itself. We know it is done - // when bash writes "Terminated" to the pty. - if err := testutil.WaitUntilRead(ptyMaster, "Terminated", nil, 5*time.Second); err != nil { - t.Fatalf("bash did not take over pty: %v", err) - } - - // Send a SIGKILL to the foreground process again. This time "bash" - // should be killed. We use SIGKILL instead of SIGTERM or SIGINT - // because bash ignores those. - if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGKILL, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - expectedPL = expectedPL[:1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Make sure the process indicates it was killed by a SIGKILL. - ws, err := c.WaitPID(pid) - if err != nil { - t.Errorf("waiting on container failed: %v", err) - } - if !ws.Signaled() { - t.Error("ws.Signaled() got false, want true") - } - if got, want := ws.Signal(), syscall.SIGKILL; got != want { - t.Errorf("ws.Signal() got %v, want %v", got, want) - } -} - -// Test that job control signals work on a console created with "run -ti". -func TestJobControlSignalRootContainer(t *testing.T) { - conf := testutil.TestConfig() - // Don't let bash execute from profile or rc files, otherwise our PID - // counts get messed up. - spec := testutil.NewSpecWithArgs("/bin/bash", "--noprofile", "--norc") - spec.Process.Terminal = true - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup, err := createConsoleSocket(sock) - if err != nil { - t.Fatalf("error creating socket at %q: %v", sock, err) - } - defer cleanup() - - // Create the container and pass the socket name. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - ConsoleSocket: sock, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - - // Get the PTY master. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - defer ptyMaster.Close() - - // Bash output as well as sandbox output will be written to the PTY - // file. Writes after a certain point will block unless we drain the - // PTY, so we must continually copy from it. - // - // We log the output to stdout for debugabilitly, and also to a buffer, - // since we wait on particular output from bash below. We use a custom - // blockingBuffer which is thread-safe and also blocks on Read calls, - // which makes this a suitable Reader for WaitUntilRead. - ptyBuf := newBlockingBuffer() - tee := io.TeeReader(ptyMaster, ptyBuf) - go io.Copy(os.Stdout, tee) - - // Start the container. - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Start waiting for the container to exit in a goroutine. We do this - // very early, otherwise it might exit before we have a chance to call - // Wait. - var ( - ws syscall.WaitStatus - wg sync.WaitGroup - ) - wg.Add(1) - go func() { - var err error - ws, err = c.Wait() - if err != nil { - t.Errorf("error waiting on container: %v", err) - } - wg.Done() - }() - - // Wait for bash to start. - expectedPL := []*control.Process{ - {PID: 1, Cmd: "bash"}, - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Fatal(err) - } - - // Execute sleep via the terminal. - ptyMaster.Write([]byte("sleep 100\n")) - - // Wait for sleep to start. - expectedPL = append(expectedPL, &control.Process{PID: 2, PPID: 1, Cmd: "sleep"}) - if err := waitForProcessList(c, expectedPL); err != nil { - t.Fatal(err) - } - - // Reset the pty buffer, so there is less output for us to scan later. - ptyBuf.Reset() - - // Send a SIGTERM to the foreground process. We pass PID=0, indicating - // that the root process should be killed. However, by setting - // fgProcess=true, the signal should actually be sent to sleep. - if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGTERM, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Sleep process should be gone. - expectedPL = expectedPL[:len(expectedPL)-1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Sleep is dead, but it may take more time for bash to notice and - // change the foreground process back to itself. We know it is done - // when bash writes "Terminated" to the pty. - if err := testutil.WaitUntilRead(ptyBuf, "Terminated", nil, 5*time.Second); err != nil { - t.Fatalf("bash did not take over pty: %v", err) - } - - // Send a SIGKILL to the foreground process again. This time "bash" - // should be killed. We use SIGKILL instead of SIGTERM or SIGINT - // because bash ignores those. - if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGKILL, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Wait for the sandbox to exit. It should exit with a SIGKILL status. - wg.Wait() - if !ws.Signaled() { - t.Error("ws.Signaled() got false, want true") - } - if got, want := ws.Signal(), syscall.SIGKILL; got != want { - t.Errorf("ws.Signal() got %v, want %v", got, want) - } -} - -// blockingBuffer is a thread-safe buffer that blocks when reading if the -// buffer is empty. It implements io.ReadWriter. -type blockingBuffer struct { - // A send to readCh indicates that a previously empty buffer now has - // data for reading. - readCh chan struct{} - - // mu protects buf. - mu sync.Mutex - buf bytes.Buffer -} - -func newBlockingBuffer() *blockingBuffer { - return &blockingBuffer{ - readCh: make(chan struct{}, 1), - } -} - -// Write implements Writer.Write. -func (bb *blockingBuffer) Write(p []byte) (int, error) { - bb.mu.Lock() - defer bb.mu.Unlock() - l := bb.buf.Len() - n, err := bb.buf.Write(p) - if l == 0 && n > 0 { - // New data! - bb.readCh <- struct{}{} - } - return n, err -} - -// Read implements Reader.Read. It will block until data is available. -func (bb *blockingBuffer) Read(p []byte) (int, error) { - for { - bb.mu.Lock() - n, err := bb.buf.Read(p) - if n > 0 || err != io.EOF { - if bb.buf.Len() == 0 { - // Reset the readCh. - select { - case <-bb.readCh: - default: - } - } - bb.mu.Unlock() - return n, err - } - bb.mu.Unlock() - - // Wait for new data. - <-bb.readCh - } -} - -// Reset resets the buffer. -func (bb *blockingBuffer) Reset() { - bb.mu.Lock() - defer bb.mu.Unlock() - bb.buf.Reset() - // Reset the readCh. - select { - case <-bb.readCh: - default: - } -} diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go deleted file mode 100644 index af128bf1c..000000000 --- a/runsc/container/container_test.go +++ /dev/null @@ -1,2062 +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 container - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "path/filepath" - "reflect" - "strconv" - "strings" - "sync" - "syscall" - "testing" - "time" - - "github.com/cenkalti/backoff" - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/boot/platforms" - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// waitForProcessList waits for the given process list to show up in the container. -func waitForProcessList(cont *Container, want []*control.Process) error { - cb := func() error { - got, err := cont.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %v", err) - return &backoff.PermanentError{Err: err} - } - if !procListsEqual(got, want) { - return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want)) - } - return nil - } - // Gives plenty of time as tests can run slow under --race. - return testutil.Poll(cb, 30*time.Second) -} - -func waitForProcessCount(cont *Container, want int) error { - cb := func() error { - pss, err := cont.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %v", err) - return &backoff.PermanentError{Err: err} - } - if got := len(pss); got != want { - return fmt.Errorf("wrong process count, got: %d, want: %d", got, want) - } - return nil - } - // Gives plenty of time as tests can run slow under --race. - return testutil.Poll(cb, 30*time.Second) -} - -func blockUntilWaitable(pid int) error { - _, _, err := specutils.RetryEintr(func() (uintptr, uintptr, error) { - var err error - _, _, err1 := syscall.Syscall6(syscall.SYS_WAITID, 1, uintptr(pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0) - if err1 != 0 { - err = err1 - } - return 0, 0, err - }) - return err -} - -// procListsEqual is used to check whether 2 Process lists are equal for all -// implemented fields. -func procListsEqual(got, want []*control.Process) bool { - if len(got) != len(want) { - return false - } - for i := range got { - pd1 := got[i] - pd2 := want[i] - // Zero out unimplemented and timing dependant fields. - pd1.Time = "" - pd1.STime = "" - pd1.C = 0 - if *pd1 != *pd2 { - return false - } - } - return true -} - -// getAndCheckProcLists is similar to waitForProcessList, but does not wait and retry the -// test for equality. This is because we already confirmed that exec occurred. -func getAndCheckProcLists(cont *Container, want []*control.Process) error { - got, err := cont.Processes() - if err != nil { - return fmt.Errorf("error getting process data from container: %v", err) - } - if procListsEqual(got, want) { - return nil - } - return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want)) -} - -func procListToString(pl []*control.Process) string { - strs := make([]string, 0, len(pl)) - for _, p := range pl { - strs = append(strs, fmt.Sprintf("%+v", p)) - } - return fmt.Sprintf("[%s]", strings.Join(strs, ",")) -} - -// createWriteableOutputFile creates an output file that can be read and -// written to in the sandbox. -func createWriteableOutputFile(path string) (*os.File, error) { - outputFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - return nil, fmt.Errorf("error creating file: %q, %v", path, err) - } - - // Chmod to allow writing after umask. - if err := outputFile.Chmod(0666); err != nil { - return nil, fmt.Errorf("error chmoding file: %q, %v", path, err) - } - return outputFile, nil -} - -func waitForFile(f *os.File) error { - op := func() error { - fi, err := f.Stat() - if err != nil { - return err - } - if fi.Size() == 0 { - return fmt.Errorf("file %q is empty", f.Name()) - } - return nil - } - - timeout := 5 * time.Second - if testutil.RaceEnabled { - // Race makes slow things even slow, so bump the timeout. - timeout = 3 * timeout - } - return testutil.Poll(op, timeout) -} - -// readOutputNum reads a file at given filepath and returns the int at the -// requested position. -func readOutputNum(file string, position int) (int, error) { - f, err := os.Open(file) - if err != nil { - return 0, fmt.Errorf("error opening file: %q, %v", file, err) - } - - // Ensure that there is content in output file. - if err := waitForFile(f); err != nil { - return 0, fmt.Errorf("error waiting for output file: %v", err) - } - - b, err := ioutil.ReadAll(f) - if err != nil { - return 0, fmt.Errorf("error reading file: %v", err) - } - if len(b) == 0 { - return 0, fmt.Errorf("error no content was read") - } - - // Strip leading null bytes caused by file offset not being 0 upon restore. - b = bytes.Trim(b, "\x00") - nums := strings.Split(string(b), "\n") - - if position >= len(nums) { - return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums) - } - if position == -1 { - // Expectation of newline at the end of last position. - position = len(nums) - 2 - } - num, err := strconv.Atoi(nums[position]) - if err != nil { - return 0, fmt.Errorf("error getting number from file: %v", err) - } - return num, nil -} - -// run starts the sandbox and waits for it to exit, checking that the -// application succeeded. -func run(spec *specs.Spec, conf *boot.Config) error { - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - return fmt.Errorf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create, start and wait for the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - return fmt.Errorf("running container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - return fmt.Errorf("container failed, waitStatus: %v", ws) - } - return nil -} - -type configOption int - -const ( - overlay configOption = iota - kvm - nonExclusiveFS -) - -var noOverlay = []configOption{kvm, nonExclusiveFS} -var all = append(noOverlay, overlay) - -// configs generates different configurations to run tests. -func configs(opts ...configOption) []*boot.Config { - // Always load the default config. - cs := []*boot.Config{testutil.TestConfig()} - - for _, o := range opts { - c := testutil.TestConfig() - switch o { - case overlay: - c.Overlay = true - case kvm: - // TODO(b/112165693): KVM tests are flaky. Disable until fixed. - continue - - // TODO(b/68787993): KVM doesn't work with --race. - if testutil.RaceEnabled { - continue - } - c.Platform = platforms.KVM - case nonExclusiveFS: - c.FileAccess = boot.FileAccessShared - default: - panic(fmt.Sprintf("unknown config option %v", o)) - - } - cs = append(cs, c) - } - return cs -} - -// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle. -// It verifies after each step that the container can be loaded from disk, and -// has the correct status. -func TestLifecycle(t *testing.T) { - // Start the child reaper. - childReaper := &testutil.Reaper{} - childReaper.Start() - defer childReaper.Stop() - - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - // The container will just sleep for a long time. We will kill it before - // it finishes sleeping. - spec := testutil.NewSpecWithArgs("sleep", "100") - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - } - // Create the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - - // Load the container from disk and check the status. - c, err = Load(rootDir, args.ID) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Created; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // List should return the container id. - ids, err := List(rootDir) - if err != nil { - t.Fatalf("error listing containers: %v", err) - } - if got, want := ids, []string{args.ID}; !reflect.DeepEqual(got, want) { - t.Errorf("container list got %v, want %v", got, want) - } - - // Start the container. - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Load the container from disk and check the status. - c, err = Load(rootDir, args.ID) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Verify that "sleep 100" is running. - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Wait on the container. - var wg sync.WaitGroup - wg.Add(1) - ch := make(chan struct{}) - go func() { - ch <- struct{}{} - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if got, want := ws.Signal(), syscall.SIGTERM; got != want { - t.Fatalf("got signal %v, want %v", got, want) - } - wg.Done() - }() - - // Wait a bit to ensure that we've started waiting on the - // container before we signal. - <-ch - time.Sleep(100 * time.Millisecond) - // Send the container a SIGTERM which will cause it to stop. - if err := c.SignalContainer(syscall.SIGTERM, false); err != nil { - t.Fatalf("error sending signal %v to container: %v", syscall.SIGTERM, err) - } - // Wait for it to die. - wg.Wait() - - // Load the container from disk and check the status. - c, err = Load(rootDir, args.ID) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Stopped; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Destroy the container. - if err := c.Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - - // List should not return the container id. - ids, err = List(rootDir) - if err != nil { - t.Fatalf("error listing containers: %v", err) - } - if len(ids) != 0 { - t.Errorf("expected container list to be empty, but got %v", ids) - } - - // Loading the container by id should fail. - if _, err = Load(rootDir, args.ID); err == nil { - t.Errorf("expected loading destroyed container to fail, but it did not") - } - } -} - -// Test the we can execute the application with different path formats. -func TestExePath(t *testing.T) { - // Create two directories that will be prepended to PATH. - firstPath, err := ioutil.TempDir(testutil.TmpDir(), "first") - if err != nil { - t.Fatal(err) - } - secondPath, err := ioutil.TempDir(testutil.TmpDir(), "second") - if err != nil { - t.Fatal(err) - } - - // Create two minimal executables in the second path, two of which - // will be masked by files in first path. - for _, p := range []string{"unmasked", "masked1", "masked2"} { - path := filepath.Join(secondPath, p) - f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0777) - if err != nil { - t.Fatal(err) - } - defer f.Close() - if _, err := io.WriteString(f, "#!/bin/true\n"); err != nil { - t.Fatal(err) - } - } - - // Create a non-executable file in the first path which masks a healthy - // executable in the second. - nonExecutable := filepath.Join(firstPath, "masked1") - f2, err := os.OpenFile(nonExecutable, os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - t.Fatal(err) - } - f2.Close() - - // Create a non-regular file in the first path which masks a healthy - // executable in the second. - nonRegular := filepath.Join(firstPath, "masked2") - if err := os.Mkdir(nonRegular, 0777); err != nil { - t.Fatal(err) - } - - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - for _, test := range []struct { - path string - success bool - }{ - {path: "true", success: true}, - {path: "bin/true", success: true}, - {path: "/bin/true", success: true}, - {path: "thisfiledoesntexit", success: false}, - {path: "bin/thisfiledoesntexit", success: false}, - {path: "/bin/thisfiledoesntexit", success: false}, - - {path: "unmasked", success: true}, - {path: filepath.Join(firstPath, "unmasked"), success: false}, - {path: filepath.Join(secondPath, "unmasked"), success: true}, - - {path: "masked1", success: true}, - {path: filepath.Join(firstPath, "masked1"), success: false}, - {path: filepath.Join(secondPath, "masked1"), success: true}, - - {path: "masked2", success: true}, - {path: filepath.Join(firstPath, "masked2"), success: false}, - {path: filepath.Join(secondPath, "masked2"), success: true}, - } { - spec := testutil.NewSpecWithArgs(test.path) - spec.Process.Env = []string{ - fmt.Sprintf("PATH=%s:%s:%s", firstPath, secondPath, os.Getenv("PATH")), - } - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("exec: %s, error setting up container: %v", test.path, err) - } - - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - - os.RemoveAll(rootDir) - os.RemoveAll(bundleDir) - - if test.success { - if err != nil { - t.Errorf("exec: %s, error running container: %v", test.path, err) - } - if ws.ExitStatus() != 0 { - t.Errorf("exec: %s, got exit status %v want %v", test.path, ws.ExitStatus(), 0) - } - } else { - if err == nil { - t.Errorf("exec: %s, got: no error, want: error", test.path) - } - } - } - } -} - -// Test the we can retrieve the application exit status from the container. -func TestAppExitStatus(t *testing.T) { - // First container will succeed. - succSpec := testutil.NewSpecWithArgs("true") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(succSpec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: succSpec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if ws.ExitStatus() != 0 { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0) - } - - // Second container exits with non-zero status. - wantStatus := 123 - errSpec := testutil.NewSpecWithArgs("bash", "-c", fmt.Sprintf("exit %d", wantStatus)) - - rootDir2, bundleDir2, err := testutil.SetupContainer(errSpec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir2) - defer os.RemoveAll(bundleDir2) - - args2 := Args{ - ID: testutil.UniqueContainerID(), - Spec: errSpec, - BundleDir: bundleDir2, - Attached: true, - } - ws, err = Run(conf, args2) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if ws.ExitStatus() != wantStatus { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), wantStatus) - } -} - -// TestExec verifies that a container can exec a new program. -func TestExec(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - const uid = 343 - spec := testutil.NewSpecWithArgs("sleep", "100") - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - { - UID: uid, - PID: 2, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - } - - // Verify that "sleep 100" is running. - if err := waitForProcessList(cont, expectedPL[:1]); err != nil { - t.Error(err) - } - - execArgs := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "5"}, - WorkingDirectory: "/", - KUID: uid, - } - - // Verify that "sleep 100" and "sleep 5" are running after exec. - // First, start running exec (whick blocks). - status := make(chan error, 1) - go func() { - exitStatus, err := cont.executeSync(execArgs) - if err != nil { - log.Debugf("error executing: %v", err) - status <- err - } else if exitStatus != 0 { - log.Debugf("bad status: %d", exitStatus) - status <- fmt.Errorf("failed with exit status: %v", exitStatus) - } else { - status <- nil - } - }() - - if err := waitForProcessList(cont, expectedPL); err != nil { - t.Fatal(err) - } - - // Ensure that exec finished without error. - select { - case <-time.After(10 * time.Second): - t.Fatalf("container timed out waiting for exec to finish.") - case st := <-status: - if st != nil { - t.Errorf("container failed to exec %v: %v", args, err) - } - } - } -} - -// TestKillPid verifies that we can signal individual exec'd processes. -func TestKillPid(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - const nProcs = 4 - spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true") - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Verify that all processes are running. - if err := waitForProcessCount(cont, nProcs); err != nil { - t.Fatalf("timed out waiting for processes to start: %v", err) - } - - // Kill the child process with the largest PID. - procs, err := cont.Processes() - if err != nil { - t.Fatalf("failed to get process list: %v", err) - } - var pid int32 - for _, p := range procs { - if pid < int32(p.PID) { - pid = int32(p.PID) - } - } - if err := cont.SignalProcess(syscall.SIGKILL, pid); err != nil { - t.Fatalf("failed to signal process %d: %v", pid, err) - } - - // Verify that one process is gone. - if err := waitForProcessCount(cont, nProcs-1); err != nil { - t.Fatal(err) - } - - procs, err = cont.Processes() - if err != nil { - t.Fatalf("failed to get process list: %v", err) - } - for _, p := range procs { - if pid == int32(p.PID) { - t.Fatalf("pid %d is still alive, which should be killed", pid) - } - } - } -} - -// TestCheckpointRestore creates a container that continuously writes successive integers -// to a file. To test checkpoint and restore functionality, the container is -// checkpointed and the last number printed to the file is recorded. Then, it is restored in two -// new containers and the first number printed from these containers is checked. Both should -// be the next consecutive number after the last number from the checkpointed container. -func TestCheckpointRestore(t *testing.T) { - // Skip overlay because test requires writing to host file. - for _, conf := range configs(noOverlay...) { - t.Logf("Running test with conf: %+v", conf) - - dir, err := ioutil.TempDir(testutil.TmpDir(), "checkpoint-test") - if err != nil { - t.Fatalf("ioutil.TempDir failed: %v", err) - } - if err := os.Chmod(dir, 0777); err != nil { - t.Fatalf("error chmoding file: %q, %v", dir, err) - } - - outputPath := filepath.Join(dir, "output") - outputFile, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile.Close() - - script := fmt.Sprintf("for ((i=0; ;i++)); do echo $i >> %q; sleep 1; done", outputPath) - spec := testutil.NewSpecWithArgs("bash", "-c", script) - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Set the image path, which is where the checkpoint image will be saved. - imagePath := filepath.Join(dir, "test-image-file") - - // Create the image file and open for writing. - file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) - if err != nil { - t.Fatalf("error opening new file at imagePath: %v", err) - } - defer file.Close() - - // Wait until application has ran. - if err := waitForFile(outputFile); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Checkpoint running container; save state into new file. - if err := cont.Checkpoint(file); err != nil { - t.Fatalf("error checkpointing container to empty file: %v", err) - } - defer os.RemoveAll(imagePath) - - lastNum, err := readOutputNum(outputPath, -1) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile2, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile2.Close() - - // Restore into a new container. - args2 := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont2, err := New(conf, args2) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont2.Destroy() - - if err := cont2.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFile(outputFile2); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - firstNum, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum and that the container picks - // up from where it left off. - if lastNum+1 != firstNum { - t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum) - } - cont2.Destroy() - - // Restore into another container! - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile3, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile3.Close() - - // Restore into a new container. - args3 := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont3, err := New(conf, args3) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont3.Destroy() - - if err := cont3.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFile(outputFile3); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - firstNum2, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum and that the container picks - // up from where it left off. - if lastNum+1 != firstNum2 { - t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum2) - } - cont3.Destroy() - } -} - -// TestUnixDomainSockets checks that Checkpoint/Restore works in cases -// with filesystem Unix Domain Socket use. -func TestUnixDomainSockets(t *testing.T) { - // Skip overlay because test requires writing to host file. - for _, conf := range configs(noOverlay...) { - t.Logf("Running test with conf: %+v", conf) - - // UDS path is limited to 108 chars for compatibility with older systems. - // Use '/tmp' (instead of testutil.TmpDir) to ensure the size limit is - // not exceeded. Assumes '/tmp' exists in the system. - dir, err := ioutil.TempDir("/tmp", "uds-test") - if err != nil { - t.Fatalf("ioutil.TempDir failed: %v", err) - } - defer os.RemoveAll(dir) - - outputPath := filepath.Join(dir, "uds_output") - outputFile, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile.Close() - - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - socketPath := filepath.Join(dir, "uds_socket") - defer os.Remove(socketPath) - - spec := testutil.NewSpecWithArgs(app, "uds", "--file", outputPath, "--socket", socketPath) - spec.Process.User = specs.User{ - UID: uint32(os.Getuid()), - GID: uint32(os.Getgid()), - } - spec.Mounts = []specs.Mount{{ - Type: "bind", - Destination: dir, - Source: dir, - }} - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Set the image path, the location where the checkpoint image will be saved. - imagePath := filepath.Join(dir, "test-image-file") - - // Create the image file and open for writing. - file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) - if err != nil { - t.Fatalf("error opening new file at imagePath: %v", err) - } - defer file.Close() - defer os.RemoveAll(imagePath) - - // Wait until application has ran. - if err := waitForFile(outputFile); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Checkpoint running container; save state into new file. - if err := cont.Checkpoint(file); err != nil { - t.Fatalf("error checkpointing container to empty file: %v", err) - } - - // Read last number outputted before checkpoint. - lastNum, err := readOutputNum(outputPath, -1) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile2, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile2.Close() - - // Restore into a new container. - argsRestore := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - contRestore, err := New(conf, argsRestore) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer contRestore.Destroy() - - if err := contRestore.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFile(outputFile2); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Read first number outputted after restore. - firstNum, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum. - if lastNum+1 != firstNum { - t.Errorf("error numbers not consecutive, previous: %d, next: %d", lastNum, firstNum) - } - contRestore.Destroy() - } -} - -// TestPauseResume tests that we can successfully pause and resume a container. -// It checks starts running sleep and executes another sleep. It pauses and checks -// that both processes are still running: sleep will be paused and still exist. -// It will then unpause and confirm that both processes are running. Then it will -// wait until one sleep completes and check to make sure the other is running. -func TestPauseResume(t *testing.T) { - for _, conf := range configs(noOverlay...) { - t.Logf("Running test with conf: %+v", conf) - const uid = 343 - spec := testutil.NewSpecWithArgs("sleep", "20") - - lock, err := ioutil.TempFile(testutil.TmpDir(), "lock") - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer lock.Close() - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - { - UID: uid, - PID: 2, - PPID: 0, - C: 0, - Cmd: "bash", - }, - } - - script := fmt.Sprintf("while [[ -f %q ]]; do sleep 0.1; done", lock.Name()) - execArgs := &control.ExecArgs{ - Filename: "/bin/bash", - Argv: []string{"bash", "-c", script}, - WorkingDirectory: "/", - KUID: uid, - } - - // First, start running exec. - _, err = cont.Execute(execArgs) - if err != nil { - t.Fatalf("error executing: %v", err) - } - - // Verify that "sleep 5" is running. - if err := waitForProcessList(cont, expectedPL); err != nil { - t.Fatal(err) - } - - // Pause the running container. - if err := cont.Pause(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - if err := os.Remove(lock.Name()); err != nil { - t.Fatalf("os.Remove(lock) failed: %v", err) - } - // Script loops and sleeps for 100ms. Give a bit a time for it to exit in - // case pause didn't work. - time.Sleep(200 * time.Millisecond) - - // Verify that the two processes still exist. - if err := getAndCheckProcLists(cont, expectedPL); err != nil { - t.Fatal(err) - } - - // Resume the running container. - if err := cont.Resume(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - expectedPL2 := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - } - - // Verify that deleting the file triggered the process to exit. - if err := waitForProcessList(cont, expectedPL2); err != nil { - t.Fatal(err) - } - } -} - -// TestPauseResumeStatus makes sure that the statuses are set correctly -// with calls to pause and resume and that pausing and resuming only -// occurs given the correct state. -func TestPauseResumeStatus(t *testing.T) { - spec := testutil.NewSpecWithArgs("sleep", "20") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Pause the running container. - if err := cont.Pause(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Try to Pause again. Should cause error. - if err := cont.Pause(); err == nil { - t.Errorf("error pausing container that was already paused: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Resume the running container. - if err := cont.Resume(); err != nil { - t.Errorf("error resuming container: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Try to resume again. Should cause error. - if err := cont.Resume(); err == nil { - t.Errorf("error resuming container already running: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } -} - -// TestCapabilities verifies that: -// - Running exec as non-root UID and GID will result in an error (because the -// executable file can't be read). -// - Running exec as non-root with CAP_DAC_OVERRIDE succeeds because it skips -// this check. -func TestCapabilities(t *testing.T) { - // Pick uid/gid different than ours. - uid := auth.KUID(os.Getuid() + 1) - gid := auth.KGID(os.Getgid() + 1) - - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - spec := testutil.NewSpecWithArgs("sleep", "100") - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - { - UID: 0, - PID: 1, - PPID: 0, - C: 0, - Cmd: "sleep", - }, - { - UID: uid, - PID: 2, - PPID: 0, - C: 0, - Cmd: "exe", - }, - } - if err := waitForProcessList(cont, expectedPL[:1]); err != nil { - t.Fatalf("Failed to wait for sleep to start, err: %v", err) - } - - // Create an executable that can't be run with the specified UID:GID. - // This shouldn't be callable within the container until we add the - // CAP_DAC_OVERRIDE capability to skip the access check. - exePath := filepath.Join(rootDir, "exe") - if err := ioutil.WriteFile(exePath, []byte("#!/bin/sh\necho hello"), 0770); err != nil { - t.Fatalf("couldn't create executable: %v", err) - } - defer os.Remove(exePath) - - // Need to traverse the intermediate directory. - os.Chmod(rootDir, 0755) - - execArgs := &control.ExecArgs{ - Filename: exePath, - Argv: []string{exePath}, - WorkingDirectory: "/", - KUID: uid, - KGID: gid, - Capabilities: &auth.TaskCapabilities{}, - } - - // "exe" should fail because we don't have the necessary permissions. - if _, err := cont.executeSync(execArgs); err == nil { - t.Fatalf("container executed without error, but an error was expected") - } - - // Now we run with the capability enabled and should succeed. - execArgs.Capabilities = &auth.TaskCapabilities{ - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - } - // "exe" should not fail this time. - if _, err := cont.executeSync(execArgs); err != nil { - t.Fatalf("container failed to exec %v: %v", args, err) - } - } -} - -// TestRunNonRoot checks that sandbox can be configured when running as -// non-privileged user. -func TestRunNonRoot(t *testing.T) { - for _, conf := range configs(noOverlay...) { - t.Logf("Running test with conf: %+v", conf) - - spec := testutil.NewSpecWithArgs("/bin/true") - spec.Process.User.UID = 343 - spec.Process.User.GID = 2401 - - // User that container runs as can't list '$TMP/blocked' and would fail to - // mount it. - dir, err := ioutil.TempDir(testutil.TmpDir(), "blocked") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - if err := os.Chmod(dir, 0700); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", dir, err) - } - dir = path.Join(dir, "test") - if err := os.Mkdir(dir, 0755); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", dir, err) - } - - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } - } -} - -// TestMountNewDir checks that runsc will create destination directory if it -// doesn't exit. -func TestMountNewDir(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - root, err := ioutil.TempDir(testutil.TmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - - srcDir := path.Join(root, "src", "dir", "anotherdir") - if err := os.MkdirAll(srcDir, 0755); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", srcDir, err) - } - - mountDir := path.Join(root, "dir", "anotherdir") - - spec := testutil.NewSpecWithArgs("/bin/ls", mountDir) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: mountDir, - Source: srcDir, - Type: "bind", - }) - - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } - } -} - -func TestReadonlyRoot(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - spec := testutil.NewSpecWithArgs("/bin/touch", "/foo") - spec.Root.Readonly = true - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create, start and wait for the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM { - t.Fatalf("container failed, waitStatus: %v", ws) - } - } -} - -func TestUIDMap(t *testing.T) { - for _, conf := range configs(noOverlay...) { - t.Logf("Running test with conf: %+v", conf) - testDir, err := ioutil.TempDir(testutil.TmpDir(), "test-mount") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(testDir) - testFile := path.Join(testDir, "testfile") - - spec := testutil.NewSpecWithArgs("touch", "/tmp/testfile") - uid := os.Getuid() - gid := os.Getgid() - spec.Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - {Type: specs.UserNamespace}, - {Type: specs.PIDNamespace}, - {Type: specs.MountNamespace}, - }, - UIDMappings: []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: uint32(uid), - Size: 1, - }, - }, - GIDMappings: []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: uint32(gid), - Size: 1, - }, - }, - } - - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp", - Source: testDir, - Type: "bind", - }) - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create, start and wait for the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %v", ws) - } - st := syscall.Stat_t{} - if err := syscall.Stat(testFile, &st); err != nil { - t.Fatalf("error stat /testfile: %v", err) - } - - if st.Uid != uint32(uid) || st.Gid != uint32(gid) { - t.Fatalf("UID: %d (%d) GID: %d (%d)", st.Uid, uid, st.Gid, gid) - } - } -} - -func TestReadonlyMount(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - dir, err := ioutil.TempDir(testutil.TmpDir(), "ro-mount") - spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file")) - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: dir, - Source: dir, - Type: "bind", - Options: []string{"ro"}, - }) - spec.Root.Readonly = false - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create, start and wait for the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM { - t.Fatalf("container failed, waitStatus: %v", ws) - } - } -} - -// TestAbbreviatedIDs checks that runsc supports using abbreviated container -// IDs in place of full IDs. -func TestAbbreviatedIDs(t *testing.T) { - rootDir, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer os.RemoveAll(rootDir) - - conf := testutil.TestConfigWithRoot(rootDir) - - cids := []string{ - "foo-" + testutil.UniqueContainerID(), - "bar-" + testutil.UniqueContainerID(), - "baz-" + testutil.UniqueContainerID(), - } - for _, cid := range cids { - spec := testutil.NewSpecWithArgs("sleep", "100") - bundleDir, err := testutil.SetupBundleDir(spec) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: cid, - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - } - - // These should all be unambigious. - unambiguous := map[string]string{ - "f": cids[0], - cids[0]: cids[0], - "bar": cids[1], - cids[1]: cids[1], - "baz": cids[2], - cids[2]: cids[2], - } - for shortid, longid := range unambiguous { - if _, err := Load(rootDir, shortid); err != nil { - t.Errorf("%q should resolve to %q: %v", shortid, longid, err) - } - } - - // These should be ambiguous. - ambiguous := []string{ - "b", - "ba", - } - for _, shortid := range ambiguous { - if s, err := Load(rootDir, shortid); err == nil { - t.Errorf("%q should be ambiguous, but resolved to %q", shortid, s.ID) - } - } -} - -func TestGoferExits(t *testing.T) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Kill sandbox and expect gofer to exit on its own. - sandboxProc, err := os.FindProcess(c.Sandbox.Pid) - if err != nil { - t.Fatalf("error finding sandbox process: %v", err) - } - if err := sandboxProc.Kill(); err != nil { - t.Fatalf("error killing sandbox process: %v", err) - } - - err = blockUntilWaitable(c.GoferPid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } -} - -func TestRootNotMount(t *testing.T) { - if testutil.RaceEnabled { - // Requires statically linked binary, since it's mapping the root to a - // random dir, libs cannot be located. - t.Skip("race makes test_app not statically linked") - } - - appSym, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - app, err := filepath.EvalSymlinks(appSym) - if err != nil { - t.Fatalf("error resolving %q symlink: %v", appSym, err) - } - log.Infof("App path %q is a symlink to %q", appSym, app) - - root := filepath.Dir(app) - exe := "/" + filepath.Base(app) - log.Infof("Executing %q in %q", exe, root) - - spec := testutil.NewSpecWithArgs(exe, "help") - spec.Root.Path = root - spec.Root.Readonly = true - spec.Mounts = nil - - conf := testutil.TestConfig() - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } -} - -func TestUserLog(t *testing.T) { - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // sched_rr_get_interval = 148 - not implemented in gvisor. - spec := testutil.NewSpecWithArgs(app, "syscall", "--syscall=148") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - dir, err := ioutil.TempDir(testutil.TmpDir(), "user_log_test") - if err != nil { - t.Fatalf("error creating tmp dir: %v", err) - } - userLog := filepath.Join(dir, "user.log") - - // Create, start and wait for the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - UserLog: userLog, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %v", ws) - } - - out, err := ioutil.ReadFile(userLog) - if err != nil { - t.Fatalf("error opening user log file %q: %v", userLog, err) - } - if want := "Unsupported syscall: sched_rr_get_interval"; !strings.Contains(string(out), want) { - t.Errorf("user log file doesn't contain %q, out: %s", want, string(out)) - } -} - -func TestWaitOnExitedSandbox(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Run a shell that sleeps for 1 second and then exits with a - // non-zero code. - const wantExit = 17 - cmd := fmt.Sprintf("sleep 1; exit %d", wantExit) - spec := testutil.NewSpecWithArgs("/bin/sh", "-c", cmd) - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and Start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Wait on the sandbox. This will make an RPC to the sandbox - // and get the actual exit status of the application. - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if got := ws.ExitStatus(); got != wantExit { - t.Errorf("got exit status %d, want %d", got, wantExit) - } - - // Now the sandbox has exited, but the zombie sandbox process - // still exists. Calling Wait() now will return the sandbox - // exit status. - ws, err = c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if got := ws.ExitStatus(); got != wantExit { - t.Errorf("got exit status %d, want %d", got, wantExit) - } - } -} - -func TestDestroyNotStarted(t *testing.T) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "100") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create the container and check that it can be destroyed. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - if err := c.Destroy(); err != nil { - t.Fatalf("deleting non-started container failed: %v", err) - } -} - -// TestDestroyStarting attempts to force a race between start and destroy. -func TestDestroyStarting(t *testing.T) { - for i := 0; i < 10; i++ { - spec := testutil.NewSpecWithArgs("/bin/sleep", "100") - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create the container and check that it can be destroyed. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Container is not thread safe, so load another instance to run in - // concurrently. - startCont, err := Load(rootDir, args.ID) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - // Ignore failures, start can fail if destroy runs first. - startCont.Start(conf) - }() - - wg.Add(1) - go func() { - defer wg.Done() - if err := c.Destroy(); err != nil { - t.Errorf("deleting non-started container failed: %v", err) - } - }() - wg.Wait() - } -} - -func TestCreateWorkingDir(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "cwd-create") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - dir := path.Join(tmpDir, "new/working/dir") - - // touch will fail if the directory doesn't exist. - spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file")) - spec.Process.Cwd = dir - spec.Root.Readonly = true - - if err := run(spec, conf); err != nil { - t.Fatalf("Error running container: %v", err) - } - } -} - -// TestMountPropagation verifies that mount propagates to slave but not to -// private mounts. -func TestMountPropagation(t *testing.T) { - // Setup dir structure: - // - src: is mounted as shared and is used as source for both private and - // slave mounts - // - dir: will be bind mounted inside src and should propagate to slave - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "mount") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - src := filepath.Join(tmpDir, "src") - srcMnt := filepath.Join(src, "mnt") - dir := filepath.Join(tmpDir, "dir") - for _, path := range []string{src, srcMnt, dir} { - if err := os.MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll(%q): %v", path, err) - } - } - dirFile := filepath.Join(dir, "file") - f, err := os.Create(dirFile) - if err != nil { - t.Fatalf("os.Create(%q): %v", dirFile, err) - } - f.Close() - - // Setup src as a shared mount. - if err := syscall.Mount(src, src, "bind", syscall.MS_BIND, ""); err != nil { - t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) - } - if err := syscall.Mount("", src, "", syscall.MS_SHARED, ""); err != nil { - t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err) - } - - spec := testutil.NewSpecWithArgs("sleep", "1000") - - priv := filepath.Join(tmpDir, "priv") - slave := filepath.Join(tmpDir, "slave") - spec.Mounts = []specs.Mount{ - { - Source: src, - Destination: priv, - Type: "bind", - Options: []string{"private"}, - }, - { - Source: src, - Destination: slave, - Type: "bind", - Options: []string{"slave"}, - }, - } - - conf := testutil.TestConfig() - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("creating container: %v", err) - } - defer cont.Destroy() - - if err := cont.Start(conf); err != nil { - t.Fatalf("starting container: %v", err) - } - - // After the container is started, mount dir inside source and check what - // happens to both destinations. - if err := syscall.Mount(dir, srcMnt, "bind", syscall.MS_BIND, ""); err != nil { - t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) - } - - // Check that mount didn't propagate to private mount. - privFile := filepath.Join(priv, "mnt", "file") - execArgs := &control.ExecArgs{ - Filename: "/usr/bin/test", - Argv: []string{"test", "!", "-f", privFile}, - } - if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 { - t.Fatalf("exec: test ! -f %q, ws: %v, err: %v", privFile, ws, err) - } - - // Check that mount propagated to slave mount. - slaveFile := filepath.Join(slave, "mnt", "file") - execArgs = &control.ExecArgs{ - Filename: "/usr/bin/test", - Argv: []string{"test", "-f", slaveFile}, - } - if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 { - t.Fatalf("exec: test -f %q, ws: %v, err: %v", privFile, ws, err) - } -} - -func TestMountSymlink(t *testing.T) { - for _, conf := range configs(overlay) { - t.Logf("Running test with conf: %+v", conf) - - dir, err := ioutil.TempDir(testutil.TmpDir(), "mount-symlink") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - - source := path.Join(dir, "source") - target := path.Join(dir, "target") - for _, path := range []string{source, target} { - if err := os.MkdirAll(path, 0777); err != nil { - t.Fatalf("os.MkdirAll(): %v", err) - } - } - f, err := os.Create(path.Join(source, "file")) - if err != nil { - t.Fatalf("os.Create(): %v", err) - } - f.Close() - - link := path.Join(dir, "link") - if err := os.Symlink(target, link); err != nil { - t.Fatalf("os.Symlink(%q, %q): %v", target, link, err) - } - - spec := testutil.NewSpecWithArgs("/bin/sleep", "1000") - - // Mount to a symlink to ensure the mount code will follow it and mount - // at the symlink target. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Type: "bind", - Destination: link, - Source: source, - }) - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("creating container: %v", err) - } - defer cont.Destroy() - - if err := cont.Start(conf); err != nil { - t.Fatalf("starting container: %v", err) - } - - // Check that symlink was resolved and mount was created where the symlink - // is pointing to. - file := path.Join(target, "file") - execArgs := &control.ExecArgs{ - Filename: "/usr/bin/test", - Argv: []string{"test", "-f", file}, - } - if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 { - t.Fatalf("exec: test -f %q, ws: %v, err: %v", file, ws, err) - } - } -} - -// executeSync synchronously executes a new process. -func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) { - pid, err := cont.Execute(args) - if err != nil { - return 0, fmt.Errorf("error executing: %v", err) - } - ws, err := cont.WaitPID(pid) - if err != nil { - return 0, fmt.Errorf("error waiting: %v", err) - } - return ws, nil -} - -func TestMain(m *testing.M) { - log.SetLevel(log.Debug) - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } - specutils.MaybeRunAsRoot() - - os.Exit(m.Run()) -} diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go deleted file mode 100644 index 2d51fecc6..000000000 --- a/runsc/container/multi_container_test.go +++ /dev/null @@ -1,1487 +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 container - -import ( - "fmt" - "io/ioutil" - "math" - "os" - "path" - "path/filepath" - "strings" - "sync" - "syscall" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) { - var specs []*specs.Spec - var ids []string - rootID := testutil.UniqueContainerID() - - for i, cmd := range cmds { - spec := testutil.NewSpecWithArgs(cmd...) - if i == 0 { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox, - } - ids = append(ids, rootID) - } else { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer, - specutils.ContainerdSandboxIDAnnotation: rootID, - } - ids = append(ids, testutil.UniqueContainerID()) - } - specs = append(specs, spec) - } - return specs, ids -} - -func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*Container, func(), error) { - // Setup root dir if one hasn't been provided. - if len(conf.RootDir) == 0 { - rootDir, err := testutil.SetupRootDir() - if err != nil { - return nil, nil, fmt.Errorf("error creating root dir: %v", err) - } - conf.RootDir = rootDir - } - - var containers []*Container - var bundles []string - cleanup := func() { - for _, c := range containers { - c.Destroy() - } - for _, b := range bundles { - os.RemoveAll(b) - } - os.RemoveAll(conf.RootDir) - } - for i, spec := range specs { - bundleDir, err := testutil.SetupBundleDir(spec) - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("error setting up container: %v", err) - } - bundles = append(bundles, bundleDir) - - args := Args{ - ID: ids[i], - Spec: spec, - BundleDir: bundleDir, - } - cont, err := 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 -} - -type execDesc struct { - c *Container - cmd []string - want int - desc string -} - -func execMany(execs []execDesc) error { - for _, exec := range execs { - args := &control.ExecArgs{Argv: exec.cmd} - if ws, err := exec.c.executeSync(args); err != nil { - return fmt.Errorf("error executing %+v: %v", args, err) - } else if ws.ExitStatus() != exec.want { - return fmt.Errorf("%q: exec %q got exit status: %d, want: %d", exec.desc, exec.cmd, ws.ExitStatus(), exec.want) - } - } - return nil -} - -func createSharedMount(mount specs.Mount, name string, pod ...*specs.Spec) { - for _, spec := range pod { - spec.Annotations[path.Join(boot.MountPrefix, name, "source")] = mount.Source - spec.Annotations[path.Join(boot.MountPrefix, name, "type")] = mount.Type - spec.Annotations[path.Join(boot.MountPrefix, name, "share")] = "pod" - if len(mount.Options) > 0 { - spec.Annotations[path.Join(boot.MountPrefix, name, "options")] = strings.Join(mount.Options, ",") - } - } -} - -// TestMultiContainerSanity checks that it is possible to run 2 dead-simple -// containers in the same sandbox. -func TestMultiContainerSanity(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - expectedPL = []*control.Process{ - {PID: 2, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - } -} - -// TestMultiPIDNS checks that it is possible to run 2 dead-simple -// containers in the same sandbox with different pidns. -func TestMultiPIDNS(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - testSpecs, ids := createSpecs(sleep, sleep) - testSpecs[1].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - }, - }, - } - - containers, cleanup, err := startContainers(conf, testSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - expectedPL = []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - } -} - -// TestMultiPIDNSPath checks the pidns path. -func TestMultiPIDNSPath(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - testSpecs, ids := createSpecs(sleep, sleep, sleep) - testSpecs[0].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/1/ns/pid", - }, - }, - } - testSpecs[1].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/1/ns/pid", - }, - }, - } - testSpecs[2].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/2/ns/pid", - }, - }, - } - - containers, cleanup, err := startContainers(conf, testSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - if err := waitForProcessList(containers[2], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - expectedPL = []*control.Process{ - {PID: 2, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - } -} - -func TestMultiContainerWait(t *testing.T) { - // The first container should run the entire duration of the test. - cmd1 := []string{"sleep", "100"} - // We'll wait on the second container, which is much shorter lived. - cmd2 := []string{"sleep", "1"} - specs, ids := createSpecs(cmd1, cmd2) - - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - {PID: 2, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Wait on the short lived container from multiple goroutines. - wg := sync.WaitGroup{} - for i := 0; i < 3; i++ { - wg.Add(1) - go func(c *Container) { - defer wg.Done() - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - if _, err := c.Wait(); err != nil { - t.Errorf("wait for stopped container %s shouldn't fail: %v", c.Spec.Process.Args, err) - } - }(containers[1]) - } - - // Also wait via PID. - for i := 0; i < 3; i++ { - wg.Add(1) - go func(c *Container) { - defer wg.Done() - const pid = 2 - if ws, err := c.WaitPID(pid); err != nil { - t.Errorf("failed to wait for PID %d: %v", pid, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("PID %d exited with non-zero status %d", pid, es) - } - if _, err := c.WaitPID(pid); err == nil { - t.Errorf("wait for stopped PID %d should fail", pid) - } - }(containers[1]) - } - - wg.Wait() - - // After Wait returns, ensure that the root container is running and - // the child has finished. - expectedPL = []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for %q to start: %v", strings.Join(containers[0].Spec.Process.Args, " "), err) - } -} - -// TestExecWait ensures what we can wait containers and individual processes in the -// sandbox that have already exited. -func TestExecWait(t *testing.T) { - rootDir, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer os.RemoveAll(rootDir) - - // The first container should run the entire duration of the test. - cmd1 := []string{"sleep", "100"} - // We'll wait on the second container, which is much shorter lived. - cmd2 := []string{"sleep", "1"} - specs, ids := createSpecs(cmd1, cmd2) - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that process is running. - expectedPL := []*control.Process{ - {PID: 2, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Fatalf("failed to wait for sleep to start: %v", err) - } - - // Wait for the second container to finish. - if err := waitForProcessCount(containers[1], 0); err != nil { - t.Fatalf("failed to wait for second container to stop: %v", err) - } - - // Get the second container exit status. - if ws, err := containers[1].Wait(); err != nil { - t.Fatalf("failed to wait for process %s: %v", containers[1].Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Fatalf("process %s exited with non-zero status %d", containers[1].Spec.Process.Args, es) - } - if _, err := containers[1].Wait(); err != nil { - t.Fatalf("wait for stopped container %s shouldn't fail: %v", containers[1].Spec.Process.Args, err) - } - - // Execute another process in the first container. - args := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "1"}, - WorkingDirectory: "/", - KUID: 0, - } - pid, err := containers[0].Execute(args) - if err != nil { - t.Fatalf("error executing: %v", err) - } - - // Wait for the exec'd process to exit. - expectedPL = []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Fatalf("failed to wait for second container to stop: %v", err) - } - - // Get the exit status from the exec'd process. - if ws, err := containers[0].WaitPID(pid); err != nil { - t.Fatalf("failed to wait for process %+v with pid %d: %v", args, pid, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Fatalf("process %+v exited with non-zero status %d", args, es) - } - if _, err := containers[0].WaitPID(pid); err == nil { - t.Fatalf("wait for stopped process %+v should fail", args) - } -} - -// TestMultiContainerMount tests that bind mounts can be used with multiple -// containers. -func TestMultiContainerMount(t *testing.T) { - cmd1 := []string{"sleep", "100"} - - // 'src != dst' ensures that 'dst' doesn't exist in the host and must be - // properly mapped inside the container to work. - src, err := ioutil.TempDir(testutil.TmpDir(), "container") - if err != nil { - t.Fatal("ioutil.TempDir failed:", err) - } - dst := src + ".dst" - cmd2 := []string{"touch", filepath.Join(dst, "file")} - - sps, ids := createSpecs(cmd1, cmd2) - sps[1].Mounts = append(sps[1].Mounts, specs.Mount{ - Source: src, - Destination: dst, - Type: "bind", - }) - - // Setup the containers. - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, sps, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - ws, err := containers[1].Wait() - if err != nil { - t.Error("error waiting on container:", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Error("container failed, waitStatus:", ws) - } -} - -// TestMultiContainerSignal checks that it is possible to signal individual -// containers without killing the entire sandbox. -func TestMultiContainerSignal(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that container 1 process is running. - expectedPL := []*control.Process{ - {PID: 2, Cmd: "sleep"}, - } - - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Kill process 2. - if err := containers[1].SignalContainer(syscall.SIGKILL, false); err != nil { - t.Errorf("failed to kill process 2: %v", err) - } - - // Make sure process 1 is still running. - expectedPL = []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // goferPid is reset when container is destroyed. - goferPid := containers[1].GoferPid - - // Destroy container and ensure container's gofer process has exited. - if err := containers[1].Destroy(); err != nil { - t.Errorf("failed to destroy container: %v", err) - } - _, _, err = specutils.RetryEintr(func() (uintptr, uintptr, error) { - cpid, err := syscall.Wait4(goferPid, nil, 0, nil) - return uintptr(cpid), 0, err - }) - if err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } - // Make sure process 1 is still running. - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Now that process 2 is gone, ensure we get an error trying to - // signal it again. - if err := containers[1].SignalContainer(syscall.SIGKILL, false); err == nil { - t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID) - } - - // Kill process 1. - if err := containers[0].SignalContainer(syscall.SIGKILL, false); err != nil { - t.Errorf("failed to kill process 1: %v", err) - } - - // Ensure that container's gofer and sandbox process are no more. - err = blockUntilWaitable(containers[0].GoferPid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } - - err = blockUntilWaitable(containers[0].Sandbox.Pid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for sandbox to exit: %v", err) - } - - // The sentry should be gone, so signaling should yield an error. - if err := containers[0].SignalContainer(syscall.SIGKILL, false); err == nil { - t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID) - } - - if err := containers[0].Destroy(); err != nil { - t.Errorf("failed to destroy container: %v", err) - } - } -} - -// TestMultiContainerDestroy checks that container are properly cleaned-up when -// they are destroyed. -func TestMultiContainerDestroy(t *testing.T) { - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // First container will remain intact while the second container is killed. - specs, ids := createSpecs( - []string{app, "reaper"}, - []string{app, "fork-bomb"}) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Exec more processes to ensure signal all works for exec'd processes too. - args := &control.ExecArgs{ - Filename: app, - Argv: []string{app, "fork-bomb"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - - // Let it brew... - time.Sleep(500 * time.Millisecond) - - if err := containers[1].Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - - // Check that destroy killed all processes belonging to the container and - // waited for them to exit before returning. - pss, err := containers[0].Sandbox.Processes("") - if err != nil { - t.Fatalf("error getting process data from sandbox: %v", err) - } - expectedPL := []*control.Process{{PID: 1, Cmd: "test_app"}} - if !procListsEqual(pss, expectedPL) { - t.Errorf("container got process list: %s, want: %s", procListToString(pss), procListToString(expectedPL)) - } - - // Check that cont.Destroy is safe to call multiple times. - if err := containers[1].Destroy(); err != nil { - t.Errorf("error destroying container: %v", err) - } - } -} - -func TestMultiContainerProcesses(t *testing.T) { - // Note: use curly braces to keep 'sh' process around. Otherwise, shell - // will just execve into 'sleep' and both containers will look the - // same. - specs, ids := createSpecs( - []string{"sleep", "100"}, - []string{"sh", "-c", "{ sleep 100; }"}) - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check root's container process list doesn't include other containers. - expectedPL0 := []*control.Process{ - {PID: 1, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[0], expectedPL0); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - - // Same for the other container. - expectedPL1 := []*control.Process{ - {PID: 2, Cmd: "sh"}, - {PID: 3, PPID: 2, Cmd: "sleep"}, - } - if err := waitForProcessList(containers[1], expectedPL1); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - - // Now exec into the second container and verify it shows up in the container. - args := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "100"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - expectedPL1 = append(expectedPL1, &control.Process{PID: 4, Cmd: "sleep"}) - if err := waitForProcessList(containers[1], expectedPL1); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - // Root container should remain unchanged. - if err := waitForProcessList(containers[0], expectedPL0); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } -} - -// TestMultiContainerKillAll checks that all process that belong to a container -// are killed when SIGKILL is sent to *all* processes in that container. -func TestMultiContainerKillAll(t *testing.T) { - for _, tc := range []struct { - killContainer bool - }{ - {killContainer: true}, - {killContainer: false}, - } { - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // First container will remain intact while the second container is killed. - specs, ids := createSpecs( - []string{app, "task-tree", "--depth=2", "--width=2"}, - []string{app, "task-tree", "--depth=4", "--width=2"}) - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Wait until all processes are created. - rootProcCount := int(math.Pow(2, 3) - 1) - if err := waitForProcessCount(containers[0], rootProcCount); err != nil { - t.Fatal(err) - } - procCount := int(math.Pow(2, 5) - 1) - if err := waitForProcessCount(containers[1], procCount); err != nil { - t.Fatal(err) - } - - // Exec more processes to ensure signal works for exec'd processes too. - args := &control.ExecArgs{ - Filename: app, - Argv: []string{app, "task-tree", "--depth=2", "--width=2"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - // Wait for these new processes to start. - procCount += int(math.Pow(2, 3) - 1) - if err := waitForProcessCount(containers[1], procCount); err != nil { - t.Fatal(err) - } - - if tc.killContainer { - // First kill the init process to make the container be stopped with - // processes still running inside. - containers[1].SignalContainer(syscall.SIGKILL, false) - op := func() error { - c, err := Load(conf.RootDir, ids[1]) - if err != nil { - return err - } - if c.Status != Stopped { - return fmt.Errorf("container is not stopped") - } - return nil - } - if err := testutil.Poll(op, 5*time.Second); err != nil { - t.Fatalf("container did not stop %q: %v", containers[1].ID, err) - } - } - - c, err := Load(conf.RootDir, ids[1]) - if err != nil { - t.Fatalf("failed to load child container %q: %v", c.ID, err) - } - // Kill'Em All - if err := c.SignalContainer(syscall.SIGKILL, true); err != nil { - t.Fatalf("failed to send SIGKILL to container %q: %v", c.ID, err) - } - - // Check that all processes are gone. - if err := waitForProcessCount(containers[1], 0); err != nil { - t.Fatal(err) - } - // Check that root container was not affected. - if err := waitForProcessCount(containers[0], rootProcCount); err != nil { - t.Fatal(err) - } - } -} - -func TestMultiContainerDestroyNotStarted(t *testing.T) { - specs, ids := createSpecs( - []string{"/bin/sleep", "100"}, - []string{"/bin/sleep", "100"}) - rootDir, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer os.RemoveAll(rootDir) - - conf := testutil.TestConfigWithRoot(rootDir) - - // Create and start root container. - rootBundleDir, err := testutil.SetupBundleDir(specs[0]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootBundleDir) - - rootArgs := Args{ - ID: ids[0], - Spec: specs[0], - BundleDir: rootBundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - defer root.Destroy() - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - - // Create and destroy sub-container. - bundleDir, err := testutil.SetupBundleDir(specs[1]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: ids[1], - Spec: specs[1], - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Check that container can be destroyed. - if err := cont.Destroy(); err != nil { - t.Fatalf("deleting non-started container failed: %v", err) - } -} - -// TestMultiContainerDestroyStarting attempts to force a race between start -// and destroy. -func TestMultiContainerDestroyStarting(t *testing.T) { - cmds := make([][]string, 10) - for i := range cmds { - cmds[i] = []string{"/bin/sleep", "100"} - } - specs, ids := createSpecs(cmds...) - - rootDir, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer os.RemoveAll(rootDir) - - conf := testutil.TestConfigWithRoot(rootDir) - - // Create and start root container. - rootBundleDir, err := testutil.SetupBundleDir(specs[0]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootBundleDir) - - rootArgs := Args{ - ID: ids[0], - Spec: specs[0], - BundleDir: rootBundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - defer root.Destroy() - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - - wg := sync.WaitGroup{} - for i := range cmds { - if i == 0 { - continue // skip root container - } - - bundleDir, err := testutil.SetupBundleDir(specs[i]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(bundleDir) - - rootArgs := Args{ - ID: ids[i], - Spec: specs[i], - BundleDir: rootBundleDir, - } - cont, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Container is not thread safe, so load another instance to run in - // concurrently. - startCont, err := Load(rootDir, ids[i]) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - wg.Add(1) - go func() { - defer wg.Done() - startCont.Start(conf) // ignore failures, start can fail if destroy runs first. - }() - - wg.Add(1) - go func() { - defer wg.Done() - if err := cont.Destroy(); err != nil { - t.Errorf("deleting non-started container failed: %v", err) - } - }() - } - wg.Wait() -} - -// TestMultiContainerDifferentFilesystems tests that different containers have -// different root filesystems. -func TestMultiContainerDifferentFilesystems(t *testing.T) { - filename := "/foo" - // Root container will create file and then sleep. - cmdRoot := []string{"sh", "-c", fmt.Sprintf("touch %q && sleep 100", filename)} - - // Child containers will assert that the file does not exist, and will - // then create it. - script := fmt.Sprintf("if [ -f %q ]; then exit 1; else touch %q; fi", filename, filename) - cmd := []string{"sh", "-c", script} - - // Make sure overlay is enabled, and none of the root filesystems are - // read-only, otherwise we won't be able to create the file. - conf := testutil.TestConfig() - conf.Overlay = true - specs, ids := createSpecs(cmdRoot, cmd, cmd) - for _, s := range specs { - s.Root.Readonly = false - } - - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Both child containers should exit successfully. - for i, c := range containers { - if i == 0 { - // Don't wait on the root. - continue - } - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - } -} - -// TestMultiContainerContainerDestroyStress tests that IO operations continue -// to work after containers have been stopped and gofers killed. -func TestMultiContainerContainerDestroyStress(t *testing.T) { - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // Setup containers. Root container just reaps children, while the others - // perform some IOs. Children are executed in 3 batches of 10. Within the - // batch there is overlap between containers starting and being destroyed. In - // between batches all containers stop before starting another batch. - cmds := [][]string{{app, "reaper"}} - const batchSize = 10 - for i := 0; i < 3*batchSize; i++ { - dir, err := ioutil.TempDir(testutil.TmpDir(), "gofer-stop-test") - if err != nil { - t.Fatal("ioutil.TempDir failed:", err) - } - defer os.RemoveAll(dir) - - cmd := "find /bin -type f | head | xargs -I SRC cp SRC " + dir - cmds = append(cmds, []string{"sh", "-c", cmd}) - } - allSpecs, allIDs := createSpecs(cmds...) - - rootDir, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer os.RemoveAll(rootDir) - - // Split up the specs and IDs. - rootSpec := allSpecs[0] - rootID := allIDs[0] - childrenSpecs := allSpecs[1:] - childrenIDs := allIDs[1:] - - bundleDir, err := testutil.SetupBundleDir(rootSpec) - if err != nil { - t.Fatalf("error setting up bundle dir: %v", err) - } - defer os.RemoveAll(bundleDir) - - // Start root container. - conf := testutil.TestConfigWithRoot(rootDir) - rootArgs := Args{ - ID: rootID, - Spec: rootSpec, - BundleDir: bundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - defer root.Destroy() - - // Run batches. Each batch starts containers in parallel, then wait and - // destroy them before starting another batch. - for i := 0; i < len(childrenSpecs); i += batchSize { - t.Logf("Starting batch from %d to %d", i, i+batchSize) - specs := childrenSpecs[i : i+batchSize] - ids := childrenIDs[i : i+batchSize] - - var children []*Container - for j, spec := range specs { - bundleDir, err := testutil.SetupBundleDir(spec) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: ids[j], - Spec: spec, - BundleDir: bundleDir, - } - child, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - children = append(children, child) - - if err := child.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Give a small gap between containers. - time.Sleep(50 * time.Millisecond) - } - for _, child := range children { - ws, err := child.Wait() - if err != nil { - t.Fatalf("waiting for container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %x (%d)", ws, ws.ExitStatus()) - } - if err := child.Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - } - } -} - -// Test that pod shared mounts are properly mounted in 2 containers and that -// changes from one container is reflected in the other. -func TestMultiContainerSharedMount(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: nil, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", mnt0.Destination}, - desc: "directory is mounted in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", mnt1.Destination}, - desc: "directory is mounted in container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/touch", file0}, - desc: "create file in container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - desc: "file appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - desc: "file appears in container1", - }, - { - c: containers[1], - cmd: []string{"/bin/rm", file1}, - desc: "file removed from container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-f", file0}, - desc: "file removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-f", file1}, - desc: "file removed from container1", - }, - { - c: containers[1], - cmd: []string{"/bin/mkdir", file1}, - desc: "create directory in container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", file0}, - desc: "dir appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", file1}, - desc: "dir appears in container1", - }, - { - c: containers[0], - cmd: []string{"/bin/rmdir", file0}, - desc: "create directory in container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-d", file0}, - desc: "dir removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-d", file1}, - desc: "dir removed from container1", - }, - } - if err := execMany(execs); err != nil { - t.Fatal(err.Error()) - } - } -} - -// Test that pod mounts are mounted as readonly when requested. -func TestMultiContainerSharedMountReadonly(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: []string{"ro"}, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", mnt0.Destination}, - desc: "directory is mounted in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", mnt1.Destination}, - desc: "directory is mounted in container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/touch", file0}, - want: 1, - desc: "fails to write to container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/touch", file1}, - want: 1, - desc: "fails to write to container1", - }, - } - if err := execMany(execs); err != nil { - t.Fatal(err.Error()) - } - } -} - -// Test that shared pod mounts continue to work after container is restarted. -func TestMultiContainerSharedMountRestart(t *testing.T) { - for _, conf := range configs(all...) { - t.Logf("Running test with conf: %+v", conf) - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: nil, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/touch", file0}, - desc: "create file in container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - desc: "file appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - desc: "file appears in container1", - }, - } - if err := execMany(execs); err != nil { - t.Fatal(err.Error()) - } - - containers[1].Destroy() - - bundleDir, err := testutil.SetupBundleDir(podSpec[1]) - if err != nil { - t.Fatalf("error restarting container: %v", err) - } - defer os.RemoveAll(bundleDir) - - args := Args{ - ID: ids[1], - Spec: podSpec[1], - BundleDir: bundleDir, - } - containers[1], err = New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - if err := containers[1].Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - execs = []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - desc: "file is still in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - desc: "file is still in container1", - }, - { - c: containers[1], - cmd: []string{"/bin/rm", file1}, - desc: "file removed from container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-f", file0}, - desc: "file removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-f", file1}, - desc: "file removed from container1", - }, - } - if err := execMany(execs); err != nil { - t.Fatal(err.Error()) - } - } -} - -// Test that one container can send an FD to another container, even though -// they have distinct MountNamespaces. -func TestMultiContainerMultiRootCanHandleFDs(t *testing.T) { - app, err := testutil.FindFile("runsc/container/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // We set up two containers with one shared mount that is used for a - // shared socket. The first container will send an FD over the socket - // to the second container. The FD corresponds to a file in the first - // container's mount namespace that is not part of the second - // container's mount namespace. However, the second container still - // should be able to read the FD. - - // Create a shared mount where we will put the socket. - sharedMnt := specs.Mount{ - Destination: "/mydir/test", - Type: "tmpfs", - // Shared mounts need a Source, even for tmpfs. It is only used - // to match up different shared mounts inside the pod. - Source: "/some/dir", - } - socketPath := filepath.Join(sharedMnt.Destination, "socket") - - // Create a writeable tmpfs mount where the FD sender app will create - // files to send. This will only be mounted in the FD sender. - writeableMnt := specs.Mount{ - Destination: "/tmp", - Type: "tmpfs", - } - - // Create the specs. - specs, ids := createSpecs( - []string{"sleep", "1000"}, - []string{app, "fd_sender", "--socket", socketPath}, - []string{app, "fd_receiver", "--socket", socketPath}, - ) - createSharedMount(sharedMnt, "shared-mount", specs...) - specs[1].Mounts = append(specs[2].Mounts, sharedMnt, writeableMnt) - specs[2].Mounts = append(specs[1].Mounts, sharedMnt) - - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Both containers should exit successfully. - for _, c := range containers[1:] { - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - } -} - -// Test that container is destroyed when Gofer is killed. -func TestMultiContainerGoferKilled(t *testing.T) { - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep, sleep) - conf := testutil.TestConfig() - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Ensure container is running - c := containers[2] - expectedPL := []*control.Process{ - {PID: 3, Cmd: "sleep"}, - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Kill container's gofer. - if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil { - t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err) - } - - // Wait until container stops. - if err := waitForProcessList(c, nil); err != nil { - t.Errorf("Container %q was not stopped after gofer death: %v", c.ID, err) - } - - // Check that container isn't running anymore. - args := &control.ExecArgs{Argv: []string{"/bin/true"}} - if _, err := c.executeSync(args); err == nil { - t.Fatalf("Container %q was not stopped after gofer death", c.ID) - } - - // Check that other containers are unaffected. - for i, c := range containers { - if i == 2 { - continue // container[2] has been killed. - } - pl := []*control.Process{ - {PID: kernel.ThreadID(i + 1), Cmd: "sleep"}, - } - if err := waitForProcessList(c, pl); err != nil { - t.Errorf("Container %q was affected by another container: %v", c.ID, err) - } - args := &control.ExecArgs{Argv: []string{"/bin/true"}} - if _, err := c.executeSync(args); err != nil { - t.Fatalf("Container %q was affected by another container: %v", c.ID, err) - } - } - - // Kill root container's gofer to bring entire sandbox down. - c = containers[0] - if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil { - t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err) - } - - // Wait until sandbox stops. waitForProcessList will loop until sandbox exits - // and RPC errors out. - impossiblePL := []*control.Process{ - {PID: 100, Cmd: "non-existent-process"}, - } - if err := waitForProcessList(c, impossiblePL); err == nil { - t.Fatalf("Sandbox was not killed after gofer death") - } - - // Check that entire sandbox isn't running anymore. - for _, c := range containers { - args := &control.ExecArgs{Argv: []string{"/bin/true"}} - if _, err := c.executeSync(args); err == nil { - t.Fatalf("Container %q was not stopped after gofer death", c.ID) - } - } -} - -func TestMultiContainerLoadSandbox(t *testing.T) { - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep, sleep) - conf := testutil.TestConfig() - - // Create containers for the sandbox. - wants, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Then create unrelated containers. - for i := 0; i < 3; i++ { - specs, ids = createSpecs(sleep, sleep, sleep) - _, cleanup, err = startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - } - - // Create an unrelated directory under root. - dir := filepath.Join(conf.RootDir, "not-a-container") - if err := os.MkdirAll(dir, 0755); err != nil { - t.Fatalf("os.MkdirAll(%q)=%v", dir, err) - } - - // Create a valid but empty container directory. - randomCID := testutil.UniqueContainerID() - dir = filepath.Join(conf.RootDir, randomCID) - if err := os.MkdirAll(dir, 0755); err != nil { - t.Fatalf("os.MkdirAll(%q)=%v", dir, err) - } - - // Load the sandbox and check that the correct containers were returned. - id := wants[0].Sandbox.ID - gots, err := loadSandbox(conf.RootDir, id) - if err != nil { - t.Fatalf("loadSandbox()=%v", err) - } - wantIDs := make(map[string]struct{}) - for _, want := range wants { - wantIDs[want.ID] = struct{}{} - } - for _, got := range gots { - if got.Sandbox.ID != id { - t.Errorf("wrong sandbox ID, got: %v, want: %v", got.Sandbox.ID, id) - } - if _, ok := wantIDs[got.ID]; !ok { - t.Errorf("wrong container ID, got: %v, wants: %v", got.ID, wantIDs) - } - delete(wantIDs, got.ID) - } - if len(wantIDs) != 0 { - t.Errorf("containers not found: %v", wantIDs) - } -} diff --git a/runsc/container/shared_volume_test.go b/runsc/container/shared_volume_test.go deleted file mode 100644 index 1f90d2462..000000000 --- a/runsc/container/shared_volume_test.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2019 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 container - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// TestSharedVolume checks that modifications to a volume mount are propagated -// into and out of the sandbox. -func TestSharedVolume(t *testing.T) { - conf := testutil.TestConfig() - conf.FileAccess = boot.FileAccessShared - t.Logf("Running test with conf: %+v", conf) - - // Main process just sleeps. We will use "exec" to probe the state of - // the filesystem. - spec := testutil.NewSpecWithArgs("sleep", "1000") - - dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") - if err != nil { - t.Fatalf("TempDir failed: %v", err) - } - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // File that will be used to check consistency inside/outside sandbox. - filename := filepath.Join(dir, "file") - - // File does not exist yet. Reading from the sandbox should fail. - argsTestFile := &control.ExecArgs{ - Filename: "/usr/bin/test", - Argv: []string{"test", "-f", filename}, - } - if ws, err := c.executeSync(argsTestFile); err != nil { - t.Fatalf("unexpected error testing file %q: %v", filename, err) - } else if ws.ExitStatus() == 0 { - t.Errorf("test %q exited with code %v, wanted not zero", ws.ExitStatus(), err) - } - - // Create the file from outside of the sandbox. - if err := ioutil.WriteFile(filename, []byte("foobar"), 0777); err != nil { - t.Fatalf("error writing to file %q: %v", filename, err) - } - - // Now we should be able to test the file from within the sandbox. - if ws, err := c.executeSync(argsTestFile); err != nil { - t.Fatalf("unexpected error testing file %q: %v", filename, err) - } else if ws.ExitStatus() != 0 { - t.Errorf("test %q exited with code %v, wanted zero", filename, ws.ExitStatus()) - } - - // Rename the file from outside of the sandbox. - newFilename := filepath.Join(dir, "newfile") - if err := os.Rename(filename, newFilename); err != nil { - t.Fatalf("os.Rename(%q, %q) failed: %v", filename, newFilename, err) - } - - // File should no longer exist at the old path within the sandbox. - if ws, err := c.executeSync(argsTestFile); err != nil { - t.Fatalf("unexpected error testing file %q: %v", filename, err) - } else if ws.ExitStatus() == 0 { - t.Errorf("test %q exited with code %v, wanted not zero", filename, ws.ExitStatus()) - } - - // We should be able to test the new filename from within the sandbox. - argsTestNewFile := &control.ExecArgs{ - Filename: "/usr/bin/test", - Argv: []string{"test", "-f", newFilename}, - } - if ws, err := c.executeSync(argsTestNewFile); err != nil { - t.Fatalf("unexpected error testing file %q: %v", newFilename, err) - } else if ws.ExitStatus() != 0 { - t.Errorf("test %q exited with code %v, wanted zero", newFilename, ws.ExitStatus()) - } - - // Delete the renamed file from outside of the sandbox. - if err := os.Remove(newFilename); err != nil { - t.Fatalf("error removing file %q: %v", filename, err) - } - - // Renamed file should no longer exist at the old path within the sandbox. - if ws, err := c.executeSync(argsTestNewFile); err != nil { - t.Fatalf("unexpected error testing file %q: %v", newFilename, err) - } else if ws.ExitStatus() == 0 { - t.Errorf("test %q exited with code %v, wanted not zero", newFilename, ws.ExitStatus()) - } - - // Now create the file from WITHIN the sandbox. - argsTouch := &control.ExecArgs{ - Filename: "/usr/bin/touch", - Argv: []string{"touch", filename}, - KUID: auth.KUID(os.Getuid()), - KGID: auth.KGID(os.Getgid()), - } - if ws, err := c.executeSync(argsTouch); err != nil { - t.Fatalf("unexpected error touching file %q: %v", filename, err) - } else if ws.ExitStatus() != 0 { - t.Errorf("touch %q exited with code %v, wanted zero", filename, ws.ExitStatus()) - } - - // File should exist outside the sandbox. - if _, err := os.Stat(filename); err != nil { - t.Errorf("stat %q got error %v, wanted nil", filename, err) - } - - // File should exist outside the sandbox. - if _, err := os.Stat(filename); err != nil { - t.Errorf("stat %q got error %v, wanted nil", filename, err) - } - - // Delete the file from within the sandbox. - argsRemove := &control.ExecArgs{ - Filename: "/bin/rm", - Argv: []string{"rm", filename}, - } - if ws, err := c.executeSync(argsRemove); err != nil { - t.Fatalf("unexpected error removing file %q: %v", filename, err) - } else if ws.ExitStatus() != 0 { - t.Errorf("remove %q exited with code %v, wanted zero", filename, ws.ExitStatus()) - } - - // File should not exist outside the sandbox. - if _, err := os.Stat(filename); !os.IsNotExist(err) { - t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err) - } -} - -func checkFile(c *Container, filename string, want []byte) error { - cpy := filename + ".copy" - argsCp := &control.ExecArgs{ - Filename: "/bin/cp", - Argv: []string{"cp", "-f", filename, cpy}, - } - if _, err := c.executeSync(argsCp); err != nil { - return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err) - } - got, err := ioutil.ReadFile(cpy) - if err != nil { - return fmt.Errorf("Error reading file %q: %v", filename, err) - } - if !bytes.Equal(got, want) { - return fmt.Errorf("file content inside the sandbox is wrong, got: %q, want: %q", got, want) - } - return nil -} - -// TestSharedVolumeFile tests that changes to file content outside the sandbox -// is reflected inside. -func TestSharedVolumeFile(t *testing.T) { - conf := testutil.TestConfig() - conf.FileAccess = boot.FileAccessShared - t.Logf("Running test with conf: %+v", conf) - - // Main process just sleeps. We will use "exec" to probe the state of - // the filesystem. - spec := testutil.NewSpecWithArgs("sleep", "1000") - - dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") - if err != nil { - t.Fatalf("TempDir failed: %v", err) - } - - rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer os.RemoveAll(rootDir) - defer os.RemoveAll(bundleDir) - - // Create and start the container. - args := Args{ - ID: testutil.UniqueContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // File that will be used to check consistency inside/outside sandbox. - filename := filepath.Join(dir, "file") - - // Write file from outside the container and check that the same content is - // read inside. - want := []byte("host-") - if err := ioutil.WriteFile(filename, []byte(want), 0666); err != nil { - t.Fatalf("Error writing to %q: %v", filename, err) - } - if err := checkFile(c, filename, want); err != nil { - t.Fatal(err.Error()) - } - - // Append to file inside the container and check that content is not lost. - argsAppend := &control.ExecArgs{ - Filename: "/bin/bash", - Argv: []string{"bash", "-c", "echo -n sandbox- >> " + filename}, - } - if _, err := c.executeSync(argsAppend); err != nil { - t.Fatalf("unexpected error appending file %q: %v", filename, err) - } - want = []byte("host-sandbox-") - if err := checkFile(c, filename, want); err != nil { - t.Fatal(err.Error()) - } - - // Write again from outside the container and check that the same content is - // read inside. - f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0) - if err != nil { - t.Fatalf("Error openning file %q: %v", filename, err) - } - defer f.Close() - if _, err := f.Write([]byte("host")); err != nil { - t.Fatalf("Error writing to file %q: %v", filename, err) - } - want = []byte("host-sandbox-host") - if err := checkFile(c, filename, want); err != nil { - t.Fatal(err.Error()) - } - - // Shrink file outside and check that the same content is read inside. - if err := f.Truncate(5); err != nil { - t.Fatalf("Error truncating file %q: %v", filename, err) - } - want = want[:5] - if err := checkFile(c, filename, want); err != nil { - t.Fatal(err.Error()) - } -} diff --git a/runsc/container/test_app/BUILD b/runsc/container/test_app/BUILD deleted file mode 100644 index 82dbd54d2..000000000 --- a/runsc/container/test_app/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "test_app", - testonly = 1, - srcs = [ - "fds.go", - "test_app.go", - ], - pure = "on", - visibility = ["//runsc/container:__pkg__"], - deps = [ - "//pkg/unet", - "//runsc/test/testutil", - "@com_github_google_subcommands//:go_default_library", - ], -) diff --git a/runsc/container/test_app/fds.go b/runsc/container/test_app/fds.go deleted file mode 100644 index c12809cab..000000000 --- a/runsc/container/test_app/fds.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2019 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 main - -import ( - "context" - "io/ioutil" - "log" - "os" - "time" - - "flag" - "github.com/google/subcommands" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -const fileContents = "foobarbaz" - -// fdSender will open a file and send the FD over a unix domain socket. -type fdSender struct { - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*fdSender) Name() string { - return "fd_sender" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*fdSender) Synopsis() string { - return "creates a file and sends the FD over the socket" -} - -// Usage implements subcommands.Command.Usage. -func (*fdSender) Usage() string { - return "fd_sender <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (fds *fdSender) SetFlags(f *flag.FlagSet) { - f.StringVar(&fds.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (fds *fdSender) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if fds.socketPath == "" { - log.Fatalf("socket flag must be set") - } - - dir, err := ioutil.TempDir(testutil.TmpDir(), "") - if err != nil { - log.Fatalf("TempDir failed: %v", err) - } - - fileToSend, err := ioutil.TempFile(dir, "") - if err != nil { - log.Fatalf("TempFile failed: %v", err) - } - defer fileToSend.Close() - - if _, err := fileToSend.WriteString(fileContents); err != nil { - log.Fatalf("Write(%q) failed: %v", fileContents, err) - } - - // Receiver may not be started yet, so try connecting in a poll loop. - var s *unet.Socket - if err := testutil.Poll(func() error { - var err error - s, err = unet.Connect(fds.socketPath, true /* SEQPACKET, so we can send empty message with FD */) - return err - }, 10*time.Second); err != nil { - log.Fatalf("Error connecting to socket %q: %v", fds.socketPath, err) - } - defer s.Close() - - w := s.Writer(true) - w.ControlMessage.PackFDs(int(fileToSend.Fd())) - if _, err := w.WriteVec([][]byte{[]byte{'a'}}); err != nil { - log.Fatalf("Error sending FD %q over socket %q: %v", fileToSend.Fd(), fds.socketPath, err) - } - - log.Print("FD SENDER exiting successfully") - return subcommands.ExitSuccess -} - -// fdReceiver receives an FD from a unix domain socket and does things to it. -type fdReceiver struct { - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*fdReceiver) Name() string { - return "fd_receiver" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*fdReceiver) Synopsis() string { - return "reads an FD from a unix socket, and then does things to it" -} - -// Usage implements subcommands.Command.Usage. -func (*fdReceiver) Usage() string { - return "fd_receiver <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (fdr *fdReceiver) SetFlags(f *flag.FlagSet) { - f.StringVar(&fdr.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if fdr.socketPath == "" { - log.Fatalf("Flags cannot be empty, given: socket: %q", fdr.socketPath) - } - - ss, err := unet.BindAndListen(fdr.socketPath, true /* packet */) - if err != nil { - log.Fatalf("BindAndListen(%q) failed: %v", fdr.socketPath, err) - } - defer ss.Close() - - var s *unet.Socket - c := make(chan error, 1) - go func() { - var err error - s, err = ss.Accept() - c <- err - }() - - select { - case err := <-c: - if err != nil { - log.Fatalf("Accept() failed: %v", err) - } - case <-time.After(10 * time.Second): - log.Fatalf("Timeout waiting for accept") - } - - r := s.Reader(true) - r.EnableFDs(1) - b := [][]byte{{'a'}} - if n, err := r.ReadVec(b); n != 1 || err != nil { - log.Fatalf("ReadVec got n=%d err %v (wanted 0, nil)", n, err) - } - - fds, err := r.ExtractFDs() - if err != nil { - log.Fatalf("ExtractFD() got err %v", err) - } - if len(fds) != 1 { - log.Fatalf("ExtractFD() got %d FDs, wanted 1", len(fds)) - } - fd := fds[0] - - file := os.NewFile(uintptr(fd), "received file") - defer file.Close() - if _, err := file.Seek(0, os.SEEK_SET); err != nil { - log.Fatalf("Seek(0, 0) failed: %v", err) - } - - got, err := ioutil.ReadAll(file) - if err != nil { - log.Fatalf("ReadAll failed: %v", err) - } - if string(got) != fileContents { - log.Fatalf("ReadAll got %q want %q", string(got), fileContents) - } - - log.Print("FD RECEIVER exiting successfully") - return subcommands.ExitSuccess -} diff --git a/runsc/container/test_app/test_app.go b/runsc/container/test_app/test_app.go deleted file mode 100644 index 6578c7b41..000000000 --- a/runsc/container/test_app/test_app.go +++ /dev/null @@ -1,289 +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. - -// Binary test_app is like a swiss knife for tests that need to run anything -// inside the sandbox. New functionality can be added with new commands. -package main - -import ( - "context" - "fmt" - "log" - "net" - "os" - "os/exec" - "strconv" - sys "syscall" - "time" - - "flag" - "github.com/google/subcommands" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func main() { - subcommands.Register(subcommands.HelpCommand(), "") - subcommands.Register(subcommands.FlagsCommand(), "") - subcommands.Register(new(fdReceiver), "") - subcommands.Register(new(fdSender), "") - subcommands.Register(new(forkBomb), "") - subcommands.Register(new(reaper), "") - subcommands.Register(new(syscall), "") - subcommands.Register(new(taskTree), "") - subcommands.Register(new(uds), "") - - flag.Parse() - - exitCode := subcommands.Execute(context.Background()) - os.Exit(int(exitCode)) -} - -type uds struct { - fileName string - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*uds) Name() string { - return "uds" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*uds) Synopsis() string { - return "creates unix domain socket client and server. Client sends a contant flow of sequential numbers. Server prints them to --file" -} - -// Usage implements subcommands.Command.Usage. -func (*uds) Usage() string { - return "uds <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (c *uds) SetFlags(f *flag.FlagSet) { - f.StringVar(&c.fileName, "file", "", "name of output file") - f.StringVar(&c.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (c *uds) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if c.fileName == "" || c.socketPath == "" { - log.Fatalf("Flags cannot be empty, given: fileName: %q, socketPath: %q", c.fileName, c.socketPath) - return subcommands.ExitFailure - } - outputFile, err := os.OpenFile(c.fileName, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - log.Fatal("error opening output file:", err) - } - - defer os.Remove(c.socketPath) - - listener, err := net.Listen("unix", c.socketPath) - if err != nil { - log.Fatal("error listening on socket %q:", c.socketPath, err) - } - - go server(listener, outputFile) - for i := 0; ; i++ { - conn, err := net.Dial("unix", c.socketPath) - if err != nil { - log.Fatal("error dialing:", err) - } - if _, err := conn.Write([]byte(strconv.Itoa(i))); err != nil { - log.Fatal("error writing:", err) - } - conn.Close() - time.Sleep(100 * time.Millisecond) - } -} - -func server(listener net.Listener, out *os.File) { - buf := make([]byte, 16) - - for { - c, err := listener.Accept() - if err != nil { - log.Fatal("error accepting connection:", err) - } - nr, err := c.Read(buf) - if err != nil { - log.Fatal("error reading from buf:", err) - } - data := buf[0:nr] - fmt.Fprint(out, string(data)+"\n") - } -} - -type taskTree struct { - depth int - width int - pause bool -} - -// Name implements subcommands.Command. -func (*taskTree) Name() string { - return "task-tree" -} - -// Synopsis implements subcommands.Command. -func (*taskTree) Synopsis() string { - return "creates a tree of tasks" -} - -// Usage implements subcommands.Command. -func (*taskTree) Usage() string { - return "task-tree <flags>" -} - -// SetFlags implements subcommands.Command. -func (c *taskTree) SetFlags(f *flag.FlagSet) { - f.IntVar(&c.depth, "depth", 1, "number of levels to create") - f.IntVar(&c.width, "width", 1, "number of tasks at each level") - f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually") -} - -// Execute implements subcommands.Command. -func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - stop := testutil.StartReaper() - defer stop() - - if c.depth == 0 { - log.Printf("Child sleeping, PID: %d\n", os.Getpid()) - select {} - } - log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid()) - - var cmds []*exec.Cmd - for i := 0; i < c.width; i++ { - cmd := exec.Command( - "/proc/self/exe", c.Name(), - "--depth", strconv.Itoa(c.depth-1), - "--width", strconv.Itoa(c.width), - "--pause", strconv.FormatBool(c.pause)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - log.Fatal("failed to call self:", err) - } - cmds = append(cmds, cmd) - } - - for _, c := range cmds { - c.Wait() - } - - if c.pause { - select {} - } - - return subcommands.ExitSuccess -} - -type forkBomb struct { - delay time.Duration -} - -// Name implements subcommands.Command. -func (*forkBomb) Name() string { - return "fork-bomb" -} - -// Synopsis implements subcommands.Command. -func (*forkBomb) Synopsis() string { - return "creates child process until the end of times" -} - -// Usage implements subcommands.Command. -func (*forkBomb) Usage() string { - return "fork-bomb <flags>" -} - -// SetFlags implements subcommands.Command. -func (c *forkBomb) SetFlags(f *flag.FlagSet) { - f.DurationVar(&c.delay, "delay", 100*time.Millisecond, "amount of time to delay creation of child") -} - -// Execute implements subcommands.Command. -func (c *forkBomb) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - time.Sleep(c.delay) - - cmd := exec.Command("/proc/self/exe", c.Name()) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - log.Fatal("failed to call self:", err) - } - return subcommands.ExitSuccess -} - -type reaper struct{} - -// Name implements subcommands.Command. -func (*reaper) Name() string { - return "reaper" -} - -// Synopsis implements subcommands.Command. -func (*reaper) Synopsis() string { - return "reaps all children in a loop" -} - -// Usage implements subcommands.Command. -func (*reaper) Usage() string { - return "reaper <flags>" -} - -// SetFlags implements subcommands.Command. -func (*reaper) SetFlags(*flag.FlagSet) {} - -// Execute implements subcommands.Command. -func (c *reaper) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - stop := testutil.StartReaper() - defer stop() - select {} -} - -type syscall struct { - sysno uint64 -} - -// Name implements subcommands.Command. -func (*syscall) Name() string { - return "syscall" -} - -// Synopsis implements subcommands.Command. -func (*syscall) Synopsis() string { - return "syscall makes a syscall" -} - -// Usage implements subcommands.Command. -func (*syscall) Usage() string { - return "syscall <flags>" -} - -// SetFlags implements subcommands.Command. -func (s *syscall) SetFlags(f *flag.FlagSet) { - f.Uint64Var(&s.sysno, "syscall", 0, "syscall to call") -} - -// Execute implements subcommands.Command. -func (s *syscall) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if _, _, errno := sys.Syscall(uintptr(s.sysno), 0, 0, 0); errno != 0 { - fmt.Printf("syscall(%d, 0, 0...) failed: %v\n", s.sysno, errno) - } else { - fmt.Printf("syscall(%d, 0, 0...) success\n", s.sysno) - } - return subcommands.ExitSuccess -} diff --git a/runsc/debian/description b/runsc/debian/description deleted file mode 100644 index 6e3b1b2c0..000000000 --- a/runsc/debian/description +++ /dev/null @@ -1,5 +0,0 @@ -gVisor is a user-space kernel, written in Go, that implements a substantial -portion of the Linux system surface. It includes an Open Container Initiative -(OCI) runtime called runsc that provides an isolation boundary between the -application and the host kernel. The runsc runtime integrates with Docker and -Kubernetes, making it simple to run sandboxed containers. diff --git a/runsc/debian/postinst.sh b/runsc/debian/postinst.sh deleted file mode 100755 index 03a5ff524..000000000 --- a/runsc/debian/postinst.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -e - -# Copyright 2019 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. - -if [ "$1" != configure ]; then - exit 0 -fi - -if [ -f /etc/docker/daemon.json ]; then - /usr/libexec/runsc/dockercfg runtime-add runsc /usr/bin/runsc - systemctl restart docker -fi diff --git a/runsc/fsgofer/BUILD b/runsc/fsgofer/BUILD deleted file mode 100644 index 80a4aa2fe..000000000 --- a/runsc/fsgofer/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "fsgofer", - srcs = [ - "fsgofer.go", - "fsgofer_unsafe.go", - ], - importpath = "gvisor.dev/gvisor/runsc/fsgofer", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/fd", - "//pkg/log", - "//pkg/p9", - "//pkg/syserr", - "//runsc/specutils", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "fsgofer_test", - size = "small", - srcs = ["fsgofer_test.go"], - embed = [":fsgofer"], - deps = [ - "//pkg/log", - "//pkg/p9", - ], -) diff --git a/runsc/fsgofer/filter/BUILD b/runsc/fsgofer/filter/BUILD deleted file mode 100644 index e2318a978..000000000 --- a/runsc/fsgofer/filter/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filter", - srcs = [ - "config.go", - "extra_filters.go", - "extra_filters_msan.go", - "extra_filters_race.go", - "filter.go", - ], - importpath = "gvisor.dev/gvisor/runsc/fsgofer/filter", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/seccomp", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/fsgofer/fsgofer_test.go b/runsc/fsgofer/fsgofer_test.go deleted file mode 100644 index cbbe71019..000000000 --- a/runsc/fsgofer/fsgofer_test.go +++ /dev/null @@ -1,692 +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 fsgofer - -import ( - "fmt" - "io/ioutil" - "net" - "os" - "path" - "path/filepath" - "syscall" - "testing" - - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" -) - -func init() { - log.SetLevel(log.Debug) - - allConfs = append(allConfs, rwConfs...) - allConfs = append(allConfs, roConfs...) - - if err := OpenProcSelfFD(); err != nil { - panic(err) - } -} - -func assertPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("function did not panic") - } - }() - f() -} - -func testReadWrite(f p9.File, flags p9.OpenFlags, content []byte) error { - want := make([]byte, len(content)) - copy(want, content) - - b := []byte("test-1-2-3") - w, err := f.WriteAt(b, uint64(len(content))) - if flags == p9.WriteOnly || flags == p9.ReadWrite { - if err != nil { - return fmt.Errorf("WriteAt(): %v", err) - } - if w != len(b) { - return fmt.Errorf("WriteAt() was partial, got: %d, want: %d", w, len(b)) - } - want = append(want, b...) - } else { - if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF { - return fmt.Errorf("WriteAt() should have failed, got: %d, want: EBADFD", err) - } - } - - rBuf := make([]byte, len(want)) - r, err := f.ReadAt(rBuf, 0) - if flags == p9.ReadOnly || flags == p9.ReadWrite { - if err != nil { - return fmt.Errorf("ReadAt(): %v", err) - } - if r != len(rBuf) { - return fmt.Errorf("ReadAt() was partial, got: %d, want: %d", r, len(rBuf)) - } - if string(rBuf) != string(want) { - return fmt.Errorf("ReadAt() wrong data, got: %s, want: %s", string(rBuf), want) - } - } else { - if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF { - return fmt.Errorf("ReadAt() should have failed, got: %d, want: EBADFD", err) - } - } - return nil -} - -var allOpenFlags = []p9.OpenFlags{p9.ReadOnly, p9.WriteOnly, p9.ReadWrite} - -var ( - allTypes = []fileType{regular, directory, symlink} - - // allConfs is set in init() above. - allConfs []Config - - rwConfs = []Config{{ROMount: false}} - roConfs = []Config{{ROMount: true}} -) - -type state struct { - root *localFile - file *localFile - conf Config - ft fileType -} - -func (s state) String() string { - return fmt.Sprintf("type(%v)", s.ft) -} - -func runAll(t *testing.T, test func(*testing.T, state)) { - runCustom(t, allTypes, allConfs, test) -} - -func runCustom(t *testing.T, types []fileType, confs []Config, test func(*testing.T, state)) { - for _, c := range confs { - t.Logf("Config: %+v", c) - - for _, ft := range types { - t.Logf("File type: %v", ft) - - path, name, err := setup(ft) - if err != nil { - t.Fatalf("%v", err) - } - defer os.RemoveAll(path) - - a, err := NewAttachPoint(path, c) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - t.Fatalf("Attach failed, err: %v", err) - } - - _, file, err := root.Walk([]string{name}) - if err != nil { - root.Close() - t.Fatalf("root.Walk({%q}) failed, err: %v", "symlink", err) - } - - st := state{root: root.(*localFile), file: file.(*localFile), conf: c, ft: ft} - test(t, st) - file.Close() - root.Close() - } - } -} - -func setup(ft fileType) (string, string, error) { - path, err := ioutil.TempDir("", "root-") - if err != nil { - return "", "", fmt.Errorf("ioutil.TempDir() failed, err: %v", err) - } - - // First attach with writable configuration to setup tree. - a, err := NewAttachPoint(path, Config{}) - if err != nil { - return "", "", err - } - root, err := a.Attach() - if err != nil { - return "", "", fmt.Errorf("Attach failed, err: %v", err) - } - defer root.Close() - - var name string - switch ft { - case regular: - name = "file" - _, f, _, _, err := root.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - return "", "", fmt.Errorf("createFile(root, %q) failed, err: %v", "test", err) - } - defer f.Close() - case directory: - name = "dir" - if _, err := root.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - return "", "", fmt.Errorf("root.MkDir(%q) failed, err: %v", name, err) - } - case symlink: - name = "symlink" - if _, err := root.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - return "", "", fmt.Errorf("root.Symlink(%q) failed, err: %v", name, err) - } - default: - panic(fmt.Sprintf("unknown file type %v", ft)) - } - return path, name, nil -} - -func createFile(dir *localFile, name string) (*localFile, error) { - _, f, _, _, err := dir.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - return nil, err - } - return f.(*localFile), nil -} - -func TestReadWrite(t *testing.T) { - runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { - child, err := createFile(s.file, "test") - if err != nil { - t.Fatalf("%v: createFile() failed, err: %v", s, err) - } - defer child.Close() - want := []byte("foobar") - w, err := child.WriteAt(want, 0) - if err != nil { - t.Fatalf("%v: Write() failed, err: %v", s, err) - } - if w != len(want) { - t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) - } - for _, flags := range allOpenFlags { - _, l, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) - } - if _, _, _, err := l.Open(flags); err != nil { - t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) - } - if err := testReadWrite(l, flags, want); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) - } - } - }) -} - -func TestCreate(t *testing.T) { - runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { - for i, flags := range allOpenFlags { - _, l, _, _, err := s.file.Create(fmt.Sprintf("test-%d", i), flags, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("%v, %v: WriteAt() failed, err: %v", s, flags, err) - } - - if err := testReadWrite(l, flags, []byte{}); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) - } - } - }) -} - -// TestReadWriteDup tests that a file opened in any mode can be dup'ed and -// reopened in any other mode. -func TestReadWriteDup(t *testing.T) { - runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { - child, err := createFile(s.file, "test") - if err != nil { - t.Fatalf("%v: createFile() failed, err: %v", s, err) - } - defer child.Close() - want := []byte("foobar") - w, err := child.WriteAt(want, 0) - if err != nil { - t.Fatalf("%v: Write() failed, err: %v", s, err) - } - if w != len(want) { - t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) - } - for _, flags := range allOpenFlags { - _, l, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) - } - defer l.Close() - if _, _, _, err := l.Open(flags); err != nil { - t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) - } - for _, dupFlags := range allOpenFlags { - t.Logf("Original flags: %v, dup flags: %v", flags, dupFlags) - _, dup, err := l.Walk([]string{}) - if err != nil { - t.Fatalf("%v: Walk(<empty>) failed: %v", s, err) - } - defer dup.Close() - if _, _, _, err := dup.Open(dupFlags); err != nil { - t.Fatalf("%v: Open(%v) failed: %v", s, flags, err) - } - if err := testReadWrite(dup, dupFlags, want); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, dupFlags, err) - } - } - } - }) -} - -func TestUnopened(t *testing.T) { - runCustom(t, []fileType{regular}, allConfs, func(t *testing.T, s state) { - b := []byte("foobar") - if _, err := s.file.WriteAt(b, 0); err != syscall.EBADF { - t.Errorf("%v: WriteAt() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if _, err := s.file.ReadAt(b, 0); err != syscall.EBADF { - t.Errorf("%v: ReadAt() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if _, err := s.file.Readdir(0, 100); err != syscall.EBADF { - t.Errorf("%v: Readdir() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if err := s.file.FSync(); err != syscall.EBADF { - t.Errorf("%v: FSync() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - }) -} - -func SetGetAttr(l *localFile, valid p9.SetAttrMask, attr p9.SetAttr) (p9.Attr, error) { - if err := l.SetAttr(valid, attr); err != nil { - return p9.Attr{}, err - } - _, _, a, err := l.GetAttr(p9.AttrMask{}) - if err != nil { - return p9.Attr{}, err - } - return a, nil -} - -func TestSetAttrPerm(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - valid := p9.SetAttrMask{Permissions: true} - attr := p9.SetAttr{Permissions: 0777} - got, err := SetGetAttr(s.file, valid, attr) - if s.ft == symlink { - if err == nil { - t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) - } - } else { - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Permissions, err) - } - if got.Mode.Permissions() != attr.Permissions { - t.Errorf("%v: wrong permission, got: %v, expected: %v", s, got.Mode.Permissions(), attr.Permissions) - } - } - }) -} - -func TestSetAttrSize(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - for _, size := range []uint64{1024, 0, 1024 * 1024} { - valid := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: size} - got, err := SetGetAttr(s.file, valid, attr) - if s.ft == symlink || s.ft == directory { - if err == nil { - t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) - } - // Run for one size only, they will all fail the same way. - return - } - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Size, err) - } - if got.Size != size { - t.Errorf("%v: wrong size, got: %v, expected: %v", s, got.Size, size) - } - } - }) -} - -func TestSetAttrTime(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - valid := p9.SetAttrMask{ATime: true, ATimeNotSystemTime: true} - attr := p9.SetAttr{ATimeSeconds: 123, ATimeNanoSeconds: 456} - got, err := SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.ATimeSeconds, attr.ATimeNanoSeconds, err) - } - if got.ATimeSeconds != 123 { - t.Errorf("%v: wrong ATimeSeconds, got: %v, expected: %v", s, got.ATimeSeconds, 123) - } - if got.ATimeNanoSeconds != 456 { - t.Errorf("%v: wrong ATimeNanoSeconds, got: %v, expected: %v", s, got.ATimeNanoSeconds, 456) - } - - valid = p9.SetAttrMask{MTime: true, MTimeNotSystemTime: true} - attr = p9.SetAttr{MTimeSeconds: 789, MTimeNanoSeconds: 012} - got, err = SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.MTimeSeconds, attr.MTimeNanoSeconds, err) - } - if got.MTimeSeconds != 789 { - t.Errorf("%v: wrong MTimeSeconds, got: %v, expected: %v", s, got.MTimeSeconds, 789) - } - if got.MTimeNanoSeconds != 012 { - t.Errorf("%v: wrong MTimeNanoSeconds, got: %v, expected: %v", s, got.MTimeNanoSeconds, 012) - } - }) -} - -func TestSetAttrOwner(t *testing.T) { - if os.Getuid() != 0 { - t.Skipf("SetAttr(owner) test requires CAP_CHOWN, running as %d", os.Getuid()) - } - - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - newUID := os.Getuid() + 1 - valid := p9.SetAttrMask{UID: true} - attr := p9.SetAttr{UID: p9.UID(newUID)} - got, err := SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.UID, err) - } - if got.UID != p9.UID(newUID) { - t.Errorf("%v: wrong uid, got: %v, expected: %v", s, got.UID, newUID) - } - }) -} - -func TestLink(t *testing.T) { - if os.Getuid() != 0 { - t.Skipf("Link test requires CAP_DAC_READ_SEARCH, running as %d", os.Getuid()) - } - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - const dirName = "linkdir" - const linkFile = "link" - if _, err := s.root.Mkdir(dirName, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: MkDir(%s) failed, err: %v", s, dirName, err) - } - _, dir, err := s.root.Walk([]string{dirName}) - if err != nil { - t.Fatalf("%v: Walk({%s}) failed, err: %v", s, dirName, err) - } - - err = dir.Link(s.file, linkFile) - if s.ft == directory { - if err != syscall.EPERM { - t.Errorf("%v: Link(target, %s) should have failed, got: %v, expected: syscall.EPERM", s, linkFile, err) - } - return - } - if err != nil { - t.Errorf("%v: Link(target, %s) failed, err: %v", s, linkFile, err) - } - }) -} - -func TestROMountChecks(t *testing.T) { - runCustom(t, allTypes, roConfs, func(t *testing.T, s state) { - if _, _, _, _, err := s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { - t.Errorf("%v: Create() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if _, err := s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { - t.Errorf("%v: MkDir() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if err := s.file.RenameAt("some_file", s.file, "other_file"); err != syscall.EBADF { - t.Errorf("%v: Rename() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if _, err := s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF { - t.Errorf("%v: Symlink() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if err := s.file.UnlinkAt("some_file", 0); err != syscall.EBADF { - t.Errorf("%v: UnlinkAt() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - if err := s.file.Link(s.file, "some_link"); err != syscall.EBADF { - t.Errorf("%v: Link() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - - valid := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: 0} - if err := s.file.SetAttr(valid, attr); err != syscall.EBADF { - t.Errorf("%v: SetAttr() should have failed, got: %v, expected: syscall.EBADF", s, err) - } - }) -} - -func TestROMountPanics(t *testing.T) { - conf := Config{ROMount: true, PanicOnWrite: true} - runCustom(t, allTypes, []Config{conf}, func(t *testing.T, s state) { - assertPanic(t, func() { s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) - assertPanic(t, func() { s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) - assertPanic(t, func() { s.file.RenameAt("some_file", s.file, "other_file") }) - assertPanic(t, func() { s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())) }) - assertPanic(t, func() { s.file.UnlinkAt("some_file", 0) }) - assertPanic(t, func() { s.file.Link(s.file, "some_link") }) - - valid := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: 0} - assertPanic(t, func() { s.file.SetAttr(valid, attr) }) - }) -} - -func TestWalkNotFound(t *testing.T) { - runCustom(t, []fileType{directory}, allConfs, func(t *testing.T, s state) { - if _, _, err := s.file.Walk([]string{"nobody-here"}); err != syscall.ENOENT { - t.Errorf("%v: Walk(%q) should have failed, got: %v, expected: syscall.ENOENT", s, "nobody-here", err) - } - }) -} - -func TestWalkDup(t *testing.T) { - runAll(t, func(t *testing.T, s state) { - _, dup, err := s.file.Walk([]string{}) - if err != nil { - t.Fatalf("%v: Walk(nil) failed, err: %v", s, err) - } - // Check that 'dup' is usable. - if _, _, _, err := dup.GetAttr(p9.AttrMask{}); err != nil { - t.Errorf("%v: GetAttr() failed, err: %v", s, err) - } - }) -} - -func TestReaddir(t *testing.T) { - runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) { - name := "dir" - if _, err := s.file.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: MkDir(%s) failed, err: %v", s, name, err) - } - name = "symlink" - if _, err := s.file.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: Symlink(%q) failed, err: %v", s, name, err) - } - name = "file" - _, f, _, _, err := s.file.Create(name, p9.ReadWrite, 0555, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("%v: createFile(root, %q) failed, err: %v", s, name, err) - } - f.Close() - - if _, _, _, err := s.file.Open(p9.ReadOnly); err != nil { - t.Fatalf("%v: Open(ReadOnly) failed, err: %v", s, err) - } - - dirents, err := s.file.Readdir(0, 10) - if err != nil { - t.Fatalf("%v: Readdir(0, 10) failed, err: %v", s, err) - } - if len(dirents) != 3 { - t.Fatalf("%v: Readdir(0, 10) wrong number of items, got: %v, expected: 3", s, len(dirents)) - } - var dir, symlink, file bool - for _, d := range dirents { - switch d.Name { - case "dir": - if d.Type != p9.TypeDir { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeDir) - } - dir = true - case "symlink": - if d.Type != p9.TypeSymlink { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeSymlink) - } - symlink = true - case "file": - if d.Type != p9.TypeRegular { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeRegular) - } - file = true - default: - t.Errorf("%v: dirent.Name got: %v", s, d.Name) - } - - _, f, err := s.file.Walk([]string{d.Name}) - if err != nil { - t.Fatalf("%v: Walk({%s}) failed, err: %v", s, d.Name, err) - } - _, _, a, err := f.GetAttr(p9.AttrMask{}) - if err != nil { - t.Fatalf("%v: GetAttr() failed, err: %v", s, err) - } - if d.Type != a.Mode.QIDType() { - t.Errorf("%v: dirent.Type different than GetAttr().Mode.QIDType(), got: %v, expected: %v", s, d.Type, a.Mode.QIDType()) - } - } - if !dir || !symlink || !file { - t.Errorf("%v: Readdir(0, 10) wrong files returned, dir: %v, symlink: %v, file: %v", s, dir, symlink, file) - } - }) -} - -// Test that attach point can be written to when it points to a file, e.g. -// /etc/hosts. -func TestAttachFile(t *testing.T) { - conf := Config{ROMount: false} - dir, err := ioutil.TempDir("", "root-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(dir) - - path := path.Join(dir, "test") - if _, err := os.Create(path); err != nil { - t.Fatalf("os.Create(%q) failed, err: %v", path, err) - } - - a, err := NewAttachPoint(path, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - t.Fatalf("Attach failed, err: %v", err) - } - - if _, _, _, err := root.Open(p9.ReadWrite); err != nil { - t.Fatalf("Open(ReadWrite) failed, err: %v", err) - } - defer root.Close() - - b := []byte("foobar") - w, err := root.WriteAt(b, 0) - if err != nil { - t.Fatalf("Write() failed, err: %v", err) - } - if w != len(b) { - t.Fatalf("Write() was partial, got: %d, expected: %d", w, len(b)) - } - rBuf := make([]byte, len(b)) - r, err := root.ReadAt(rBuf, 0) - if err != nil { - t.Fatalf("ReadAt() failed, err: %v", err) - } - if r != len(rBuf) { - t.Fatalf("ReadAt() was partial, got: %d, expected: %d", r, len(rBuf)) - } - if string(rBuf) != "foobar" { - t.Fatalf("ReadAt() wrong data, got: %s, expected: %s", string(rBuf), "foobar") - } -} - -func TestAttachInvalidType(t *testing.T) { - dir, err := ioutil.TempDir("", "attach-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(dir) - - fifo := filepath.Join(dir, "fifo") - if err := syscall.Mkfifo(fifo, 0755); err != nil { - t.Fatalf("Mkfifo(%q): %v", fifo, err) - } - - dirFile, err := os.Open(dir) - if err != nil { - t.Fatalf("Open(%s): %v", dir, err) - } - defer dirFile.Close() - - // Bind a socket via /proc to be sure that a length of a socket path - // is less than UNIX_PATH_MAX. - socket := filepath.Join(fmt.Sprintf("/proc/self/fd/%d", dirFile.Fd()), "socket") - l, err := net.Listen("unix", socket) - if err != nil { - t.Fatalf("net.Listen(unix, %q): %v", socket, err) - } - defer l.Close() - - for _, tc := range []struct { - name string - path string - }{ - {name: "fifo", path: fifo}, - {name: "socket", path: socket}, - } { - t.Run(tc.name, func(t *testing.T) { - conf := Config{ROMount: false} - a, err := NewAttachPoint(tc.path, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - f, err := a.Attach() - if f != nil || err == nil { - t.Fatalf("Attach should have failed, got (%v, nil)", f) - } - }) - } -} - -func TestDoubleAttachError(t *testing.T) { - conf := Config{ROMount: false} - root, err := ioutil.TempDir("", "root-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(root) - a, err := NewAttachPoint(root, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - - if _, err := a.Attach(); err != nil { - t.Fatalf("Attach failed: %v", err) - } - if _, err := a.Attach(); err == nil { - t.Fatalf("Attach should have failed, got %v want non-nil", err) - } -} diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD deleted file mode 100644 index 7fdceaab6..000000000 --- a/runsc/sandbox/BUILD +++ /dev/null @@ -1,34 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "sandbox", - srcs = [ - "network.go", - "network_unsafe.go", - "sandbox.go", - ], - importpath = "gvisor.dev/gvisor/runsc/sandbox", - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/control/client", - "//pkg/control/server", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/platform", - "//pkg/urpc", - "//runsc/boot", - "//runsc/boot/platforms", - "//runsc/cgroup", - "//runsc/console", - "//runsc/specutils", - "@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", - "@com_github_vishvananda_netlink//:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD deleted file mode 100644 index fbfb8e2f8..000000000 --- a/runsc/specutils/BUILD +++ /dev/null @@ -1,31 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "specutils", - srcs = [ - "fs.go", - "namespace.go", - "specutils.go", - ], - importpath = "gvisor.dev/gvisor/runsc/specutils", - visibility = ["//:sandbox"], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/sentry/kernel/auth", - "@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", - ], -) - -go_test( - name = "specutils_test", - size = "small", - srcs = ["specutils_test.go"], - embed = [":specutils"], - deps = ["@com_github_opencontainers_runtime-spec//specs-go:go_default_library"], -) diff --git a/runsc/specutils/specutils_test.go b/runsc/specutils/specutils_test.go deleted file mode 100644 index 2c86fffe8..000000000 --- a/runsc/specutils/specutils_test.go +++ /dev/null @@ -1,265 +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 specutils - -import ( - "fmt" - "os/exec" - "strings" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -func TestWaitForReadyHappy(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - var count int - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - if count < 3 { - count++ - return false, nil - } - return true, nil - }) - if err != nil { - t.Errorf("ProcessWaitReady got: %v, expected: nil", err) - } - cmd.Process.Kill() -} - -func TestWaitForReadyFail(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - var count int - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - if count < 3 { - count++ - return false, nil - } - return false, fmt.Errorf("Fake error") - }) - if err == nil { - t.Errorf("ProcessWaitReady got: nil, expected: error") - } - cmd.Process.Kill() -} - -func TestWaitForReadyNotRunning(t *testing.T) { - cmd := exec.Command("/bin/true") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - return false, nil - }) - if err != nil && !strings.Contains(err.Error(), "terminated") { - t.Errorf("ProcessWaitReady got: %v, expected: process terminated", err) - } - if err == nil { - t.Errorf("ProcessWaitReady incorrectly succeeded") - } -} - -func TestWaitForReadyTimeout(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - err := WaitForReady(cmd.Process.Pid, 50*time.Millisecond, func() (bool, error) { - return false, nil - }) - if !strings.Contains(err.Error(), "not running yet") { - t.Errorf("ProcessWaitReady got: %v, expected: not running yet", err) - } - cmd.Process.Kill() -} - -func TestSpecInvalid(t *testing.T) { - for _, test := range []struct { - name string - spec specs.Spec - error string - }{ - { - name: "valid", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "src", - Destination: "/dst", - }, - }, - }, - error: "", - }, - { - name: "valid+warning", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - // This is normally set by docker and will just cause warnings to be logged. - ApparmorProfile: "someprofile", - }, - // This is normally set by docker and will just cause warnings to be logged. - Linux: &specs.Linux{Seccomp: &specs.LinuxSeccomp{}}, - }, - error: "", - }, - { - name: "no root", - spec: specs.Spec{ - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - }, - error: "must be defined", - }, - { - name: "empty root", - spec: specs.Spec{ - Root: &specs.Root{}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - }, - error: "must be defined", - }, - { - name: "no process", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - }, - error: "must be defined", - }, - { - name: "empty args", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{}, - }, - error: "must be defined", - }, - { - name: "selinux", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - SelinuxLabel: "somelabel", - }, - }, - error: "is not supported", - }, - { - name: "solaris", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Solaris: &specs.Solaris{}, - }, - error: "is not supported", - }, - { - name: "windows", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Windows: &specs.Windows{}, - }, - error: "is not supported", - }, - { - name: "relative mount destination", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "src", - Destination: "dst", - }, - }, - }, - error: "must be an absolute path", - }, - { - name: "invalid mount option", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "/src", - Destination: "/dst", - Type: "bind", - Options: []string{"shared"}, - }, - }, - }, - error: "is not supported", - }, - { - name: "invalid rootfs propagation", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Linux: &specs.Linux{ - RootfsPropagation: "foo", - }, - }, - error: "root mount propagation option must specify private or slave", - }, - } { - err := ValidateSpec(&test.spec) - if len(test.error) == 0 { - if err != nil { - t.Errorf("ValidateSpec(%q) failed, err: %v", test.name, err) - } - } else { - if err == nil || !strings.Contains(err.Error(), test.error) { - t.Errorf("ValidateSpec(%q) wrong error, got: %v, want: .*%s.*", test.name, err, test.error) - } - } - } -} diff --git a/runsc/test/BUILD b/runsc/test/BUILD deleted file mode 100644 index e69de29bb..000000000 --- a/runsc/test/BUILD +++ /dev/null diff --git a/runsc/test/README.md b/runsc/test/README.md deleted file mode 100644 index f22a8e017..000000000 --- a/runsc/test/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Tests - -The tests defined under this path are verifying functionality beyond what unit -tests can cover, e.g. integration and end to end tests. Due to their nature, -they may need extra setup in the test machine and extra configuration to run. - -- **integration:** defines integration tests that uses `docker run` to test - functionality. -- **image:** basic end to end test for popular images. -- **root:** tests that require to be run as root. -- **testutil:** utilities library to support the tests. - -The following setup steps are required in order to run these tests: - - `./runsc/test/install.sh [--runtime <name>]` - -The tests expect the runtime name to be provided in the `RUNSC_RUNTIME` -environment variable (default: `runsc-test`). To run the tests execute: - -``` -bazel test --test_env=RUNSC_RUNTIME=runsc-test \ - //runsc/test/image:image_test \ - //runsc/test/integration:integration_test -``` diff --git a/runsc/test/build_defs.bzl b/runsc/test/build_defs.bzl deleted file mode 100644 index ac28cc037..000000000 --- a/runsc/test/build_defs.bzl +++ /dev/null @@ -1,19 +0,0 @@ -"""Defines a rule for runsc test targets.""" - -load("@io_bazel_rules_go//go:def.bzl", _go_test = "go_test") - -# runtime_test is a macro that will create targets to run the given test target -# with different runtime options. -def runtime_test(**kwargs): - """Runs the given test target with different runtime options.""" - name = kwargs["name"] - _go_test(**kwargs) - kwargs["name"] = name + "_hostnet" - kwargs["args"] = ["--runtime-type=hostnet"] - _go_test(**kwargs) - kwargs["name"] = name + "_kvm" - kwargs["args"] = ["--runtime-type=kvm"] - _go_test(**kwargs) - kwargs["name"] = name + "_overlay" - kwargs["args"] = ["--runtime-type=overlay"] - _go_test(**kwargs) diff --git a/runsc/test/image/BUILD b/runsc/test/image/BUILD deleted file mode 100644 index 58758fde5..000000000 --- a/runsc/test/image/BUILD +++ /dev/null @@ -1,31 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//runsc/test:build_defs.bzl", "runtime_test") - -package(licenses = ["notice"]) - -runtime_test( - name = "image_test", - size = "large", - srcs = [ - "image_test.go", - ], - data = [ - "latin10k.txt", - "mysql.sql", - "ruby.rb", - "ruby.sh", - ], - embed = [":image"], - tags = [ - # Requires docker and runsc to be configured before the test runs. - "manual", - "local", - ], - deps = ["//runsc/test/testutil"], -) - -go_library( - name = "image", - srcs = ["image.go"], - importpath = "gvisor.dev/gvisor/runsc/test/image", -) diff --git a/runsc/test/image/image.go b/runsc/test/image/image.go deleted file mode 100644 index 297f1ab92..000000000 --- a/runsc/test/image/image.go +++ /dev/null @@ -1,16 +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 image is empty. See image_test.go for description. -package image diff --git a/runsc/test/image/image_test.go b/runsc/test/image/image_test.go deleted file mode 100644 index ddaa2c13b..000000000 --- a/runsc/test/image/image_test.go +++ /dev/null @@ -1,350 +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 image provides end-to-end image tests for runsc. - -// Each test calls docker commands to start up a container, and tests that it is -// behaving properly, like connecting to a port or looking at the output. The -// container is killed and deleted at the end. -// -// Setup instruction in runsc/test/README.md. -package image - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func TestHelloWorld(t *testing.T) { - d := testutil.MakeDocker("hello-test") - if err := d.Run("hello-world"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - if _, err := d.WaitForOutput("Hello from Docker!", 5*time.Second); err != nil { - t.Fatalf("docker didn't say hello: %v", err) - } -} - -func runHTTPRequest(port int) error { - url := fmt.Sprintf("http://localhost:%d/not-found", port) - resp, err := http.Get(url) - if err != nil { - return fmt.Errorf("error reaching http server: %v", err) - } - if want := http.StatusNotFound; resp.StatusCode != want { - return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - - url = fmt.Sprintf("http://localhost:%d/latin10k.txt", port) - resp, err = http.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) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("Error reading http response: %v", err) - } - defer resp.Body.Close() - - // READALL is the last word in the file. Ensures everything was read. - if want := "READALL"; strings.HasSuffix(string(body), want) { - return fmt.Errorf("response doesn't contain %q, resp: %q", want, body) - } - return nil -} - -func testHTTPServer(t *testing.T, port int) { - const requests = 10 - ch := make(chan error, requests) - for i := 0; i < requests; i++ { - go func() { - start := time.Now() - err := runHTTPRequest(port) - log.Printf("Response time %v: %v", time.Since(start).String(), err) - ch <- err - }() - } - - for i := 0; i < requests; i++ { - err := <-ch - if err != nil { - t.Errorf("testHTTPServer(%d) failed: %v", port, err) - } - } -} - -func TestHttpd(t *testing.T) { - if err := testutil.Pull("httpd"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("http-test") - - dir, err := testutil.PrepareFiles("latin10k.txt") - if err != nil { - t.Fatalf("PrepareFiles() failed: %v", err) - } - - // Start the container. - mountArg := testutil.MountArg(dir, "/usr/local/apache2/htdocs", testutil.ReadOnly) - if err := d.Run("-p", "80", mountArg, "httpd"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - // Find where port 80 is mapped to. - port, err := d.FindPort(80) - if err != nil { - t.Fatalf("docker.FindPort(80) failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Errorf("WaitForHTTP() timeout: %v", err) - } - - testHTTPServer(t, port) -} - -func TestNginx(t *testing.T) { - if err := testutil.Pull("nginx"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("net-test") - - dir, err := testutil.PrepareFiles("latin10k.txt") - if err != nil { - t.Fatalf("PrepareFiles() failed: %v", err) - } - - // Start the container. - mountArg := testutil.MountArg(dir, "/usr/share/nginx/html", testutil.ReadOnly) - if err := d.Run("-p", "80", mountArg, "nginx"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - // Find where port 80 is mapped to. - port, err := d.FindPort(80) - if err != nil { - t.Fatalf("docker.FindPort(80) failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Errorf("WaitForHTTP() timeout: %v", err) - } - - testHTTPServer(t, port) -} - -func TestMysql(t *testing.T) { - if err := testutil.Pull("mysql"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("mysql-test") - - // Start the container. - if err := d.Run("-e", "MYSQL_ROOT_PASSWORD=foobar123", "mysql"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - // Wait until it's up and running. - if _, err := d.WaitForOutput("port: 3306 MySQL Community Server", 3*time.Minute); err != nil { - t.Fatalf("docker.WaitForOutput() timeout: %v", err) - } - - client := testutil.MakeDocker("mysql-client-test") - dir, err := testutil.PrepareFiles("mysql.sql") - if err != nil { - 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{ - testutil.LinkArg(&d, "mysql"), - testutil.MountArg(dir, "/sql", testutil.ReadWrite), - "mysql", - "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql", - } - if err := client.Run(args...); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer client.CleanUp() - - // Ensure file executed to the end and shutdown mysql. - 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 { - t.Fatalf("docker.WaitForOutput() timeout: %v", err) - } -} - -func TestPythonHello(t *testing.T) { - // TODO(b/136503277): Once we have more complete python runtime tests, - // we can drop this one. - const img = "gcr.io/gvisor-presubmit/python-hello" - if err := testutil.Pull(img); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("python-hello-test") - if err := d.Run("-p", "8080", img); err != nil { - t.Fatalf("docker run failed: %v", err) - } - 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 := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Ensure that content is being served. - url := fmt.Sprintf("http://localhost:%d", port) - resp, err := http.Get(url) - if err != nil { - t.Errorf("Error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } -} - -func TestTomcat(t *testing.T) { - if err := testutil.Pull("tomcat:8.0"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("tomcat-test") - if err := d.Run("-p", "8080", "tomcat:8.0"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - 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 := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Ensure that content is being served. - url := fmt.Sprintf("http://localhost:%d", port) - resp, err := http.Get(url) - if err != nil { - t.Errorf("Error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } -} - -func TestRuby(t *testing.T) { - if err := testutil.Pull("ruby"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("ruby-test") - - dir, err := testutil.PrepareFiles("ruby.rb", "ruby.sh") - if err != nil { - t.Fatalf("PrepareFiles() failed: %v", err) - } - if err := os.Chmod(filepath.Join(dir, "ruby.sh"), 0333); err != nil { - t.Fatalf("os.Chmod(%q, 0333) failed: %v", dir, err) - } - - if err := d.Run("-p", "8080", testutil.MountArg(dir, "/src", testutil.ReadOnly), "ruby", "/src/ruby.sh"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - 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, 'gem install' can take some time. - if err := testutil.WaitForHTTP(port, 1*time.Minute); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Ensure that content is being served. - url := fmt.Sprintf("http://localhost:%d", port) - resp, err := http.Get(url) - if err != nil { - t.Errorf("error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - t.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("error reading body: %v", err) - } - if got, want := string(body), "Hello World"; !strings.Contains(got, want) { - t.Errorf("invalid body content, got: %q, want: %q", got, want) - } -} - -func TestStdio(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("stdio-test") - - wantStdout := "hello stdout" - wantStderr := "bonjour stderr" - cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr) - if err := d.Run("alpine", "/bin/sh", "-c", cmd); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - for _, want := range []string{wantStdout, wantStderr} { - if _, err := d.WaitForOutput(want, 5*time.Second); err != nil { - t.Fatalf("docker didn't get output %q : %v", want, err) - } - } -} - -func TestMain(m *testing.M) { - testutil.EnsureSupportedDockerVersion() - os.Exit(m.Run()) -} diff --git a/runsc/test/image/latin10k.txt b/runsc/test/image/latin10k.txt deleted file mode 100644 index 61341e00b..000000000 --- a/runsc/test/image/latin10k.txt +++ /dev/null @@ -1,33 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ut placerat felis. Maecenas urna est, auctor a efficitur sit amet, egestas et augue. Curabitur dignissim scelerisque nunc vel cursus. Ut vehicula est pretium, consectetur nunc non, pharetra ligula. Curabitur ut ultricies metus. Suspendisse pulvinar, orci sed fermentum vestibulum, eros turpis molestie lectus, nec elementum risus dolor mattis felis. Donec ultrices ipsum sem, at pretium lacus convallis at. Mauris nulla enim, tincidunt non bibendum at, vehicula pulvinar mauris. - -Duis in dapibus turpis. Pellentesque maximus magna odio, ac congue libero laoreet quis. Maecenas euismod risus in justo aliquam accumsan. Nunc quis ornare arcu, sit amet sodales elit. Phasellus nec scelerisque nisl, a tincidunt arcu. Proin ornare est nunc, sed suscipit orci interdum et. Suspendisse condimentum venenatis diam in tempor. Aliquam egestas lectus in rutrum tempus. Donec id egestas eros. Donec molestie consequat purus, sed posuere odio venenatis vitae. Nunc placerat augue id vehicula varius. In hac habitasse platea dictumst. Proin at est accumsan, venenatis quam a, fermentum risus. Phasellus posuere pellentesque enim, id suscipit magna consequat ut. Quisque ut tortor ante. - -Cras ut vulputate metus, a laoreet lectus. Vivamus ultrices molestie odio in tristique. Morbi faucibus mi eget sollicitudin fringilla. Fusce vitae lacinia ligula. Sed egestas sed diam eu posuere. Maecenas justo nisl, venenatis vel nibh vel, cursus aliquam velit. Praesent lacinia dui id erat venenatis rhoncus. Morbi gravida felis ante, sit amet vehicula orci rhoncus vitae. - -Sed finibus sagittis dictum. Proin auctor suscipit sem et mattis. Phasellus libero ligula, pellentesque ut felis porttitor, fermentum sollicitudin orci. Nulla eu nulla nibh. Fusce a eros risus. Proin vel magna risus. Donec nec elit eleifend, scelerisque sapien vitae, pharetra quam. Donec porttitor mauris scelerisque, tempus orci hendrerit, dapibus felis. Nullam libero elit, sollicitudin a aliquam at, ultrices in erat. Mauris eget ligula sodales, porta turpis et, scelerisque odio. Mauris mollis leo vitae purus gravida, in tempor nunc efficitur. Nulla facilisis posuere augue, nec pellentesque lectus eleifend ac. Vestibulum convallis est a feugiat tincidunt. Donec vitae enim volutpat, tincidunt eros eu, malesuada nibh. - -Quisque molestie, magna ornare elementum convallis, erat enim sagittis ipsum, eget porttitor sapien arcu id purus. Donec ut cursus diam. Nulla rutrum nulla et mi fermentum, vel tempus tellus posuere. Proin vitae pharetra nulla, nec ornare ex. Nulla consequat, augue a accumsan euismod, turpis leo ornare ligula, a pulvinar enim dolor ut augue. Quisque volutpat, lectus a varius mollis, nisl eros feugiat sem, at egestas lacus justo eu elit. Vestibulum scelerisque mauris est, sagittis interdum nunc accumsan sit amet. Maecenas aliquet ex ut lacus ornare, eu sagittis nibh imperdiet. Duis ultrices nisi velit, sed sodales risus sollicitudin et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam a accumsan augue, vitae pulvinar nulla. Pellentesque euismod sodales magna, nec luctus eros mattis eget. Sed lacinia suscipit lectus, eget consectetur dui pellentesque sed. Nullam nec mattis tellus. - -Aliquam erat volutpat. Praesent lobortis massa porttitor eros tincidunt, nec consequat diam pharetra. Duis efficitur non lorem sed mattis. Suspendisse justo nunc, pulvinar eu porttitor at, facilisis id eros. Suspendisse potenti. Cras molestie aliquet orci ut fermentum. In tempus aliquet eros nec suscipit. Suspendisse in mauris ut lectus ultrices blandit sit amet vitae est. Nam magna massa, porttitor ut semper id, feugiat vel quam. Suspendisse dignissim posuere scelerisque. Donec scelerisque lorem efficitur suscipit suscipit. Nunc luctus ligula et scelerisque lacinia. - -Suspendisse potenti. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed ultrices, sem in venenatis scelerisque, tellus ipsum porttitor urna, et iaculis lectus odio ac nisi. Integer luctus dui urna, at sollicitudin elit dapibus eu. Praesent nibh ante, porttitor a ante in, ullamcorper pretium felis. Aliquam vel tortor imperdiet, imperdiet lorem et, cursus mi. Proin tempus velit est, ut hendrerit metus gravida sed. Sed nibh sapien, faucibus quis ipsum in, scelerisque lacinia elit. In nec magna eu magna laoreet rhoncus. Donec vitae rutrum mauris. Integer urna felis, consequat at rhoncus vitae, auctor quis elit. Duis a pulvinar sem, nec gravida nisl. Nam non dapibus purus. Praesent vestibulum turpis nec erat porttitor, a scelerisque purus tincidunt. - -Nam fringilla leo nisi, nec placerat nisl luctus eget. Aenean malesuada nunc porta sapien sodales convallis. Suspendisse ut massa tempor, ullamcorper mi ut, faucibus turpis. Vivamus at sagittis metus. Donec varius ac mi eget sodales. Nulla feugiat, nulla eu fringilla fringilla, nunc lorem sollicitudin quam, vitae lacinia velit lorem eu orci. Mauris leo urna, pellentesque ac posuere non, pellentesque sit amet quam. - -Vestibulum porta diam urna, a aliquet nibh vestibulum et. Proin interdum bibendum nisl sed rhoncus. Sed vel diam hendrerit, faucibus ante et, hendrerit diam. Nunc dolor augue, mattis non dolor vel, luctus sodales neque. Cras malesuada fermentum dolor eu lobortis. Integer dapibus volutpat consequat. Maecenas posuere feugiat nunc. Donec vel mollis elit, volutpat consequat enim. Nulla id nisi finibus orci imperdiet elementum. Phasellus ultrices, elit vitae consequat rutrum, nisl est congue massa, quis condimentum justo nisi vitae turpis. Maecenas aliquet risus sit amet accumsan elementum. Proin non finibus elit, sit amet lobortis augue. - -Morbi pretium pulvinar sem vel sollicitudin. Proin imperdiet fringilla leo, non pellentesque lacus gravida nec. Vivamus ullamcorper consectetur ligula eu consectetur. Curabitur sit amet tempus purus. Curabitur quam quam, tincidunt eu tempus vel, volutpat at ipsum. Maecenas lobortis elit ac justo interdum, sit amet mattis ligula mollis. Sed posuere ligula et felis convallis tempor. Aliquam nec mollis velit. Donec varius sit amet erat at imperdiet. Nulla ipsum justo, tempor non sollicitudin gravida, dignissim vel orci. In hac habitasse platea dictumst. Cras cursus tellus id arcu aliquet accumsan. Phasellus ac erat dui. - -Duis mollis metus at mi luctus aliquam. Duis varius eget erat ac porttitor. Phasellus lobortis sagittis lacinia. Etiam sagittis eget erat in pulvinar. Phasellus sodales risus nec vulputate accumsan. Cras sit amet pellentesque dui. Praesent consequat felis mi, at vulputate diam convallis a. Donec hendrerit nibh vel justo consequat dictum. In euismod, dui sit amet malesuada suscipit, mauris ex rhoncus eros, sed ornare arcu nunc eu urna. Pellentesque eget erat augue. Integer rutrum mauris sem, nec sodales nulla cursus vel. Vivamus porta, urna vel varius vulputate, nulla arcu malesuada dui, a ultrices magna ante sed nibh. - -Morbi ultricies aliquam lorem id bibendum. Donec sit amet nunc vitae massa gravida eleifend hendrerit vel libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla vestibulum tempus condimentum. Aliquam dolor ipsum, condimentum in sapien et, tempor iaculis nulla. Aenean non pharetra augue. Maecenas mattis dignissim maximus. Fusce elementum tincidunt massa sit amet lobortis. Phasellus nec pharetra dui, et malesuada ante. Nullam commodo pretium tellus. Praesent sollicitudin, enim eget imperdiet scelerisque, odio felis vulputate dolor, eget auctor neque tellus ac lorem. - -In consectetur augue et sapien feugiat varius. Nam tortor mi, consectetur ac felis non, elementum venenatis augue. Suspendisse ut tellus in est sagittis cursus. Quisque faucibus, neque sit amet semper congue, nibh augue finibus odio, vitae interdum dolor arcu eget arcu. Curabitur dictum risus massa, non tincidunt urna molestie non. Maecenas eu quam purus. Donec vulputate, dui eu accumsan blandit, mauris tortor tristique mi, sed blandit leo quam id quam. Ut venenatis sagittis malesuada. Integer non auctor orci. Duis consectetur massa felis. Fusce euismod est sit amet bibendum finibus. Vestibulum dolor ex, tempor at elit in, iaculis cursus dui. Nunc sed neque ac risus rutrum tempus sit amet at ante. In hac habitasse platea dictumst. - -Donec rutrum, velit nec viverra tincidunt, est velit viverra neque, quis auctor leo ex at lectus. Morbi eget purus nisi. Aliquam lacus dui, interdum vitae elit at, venenatis dignissim est. Duis ac mollis lorem. Vivamus a vestibulum quam. Maecenas non metus dolor. Praesent tortor nunc, tristique at nisl molestie, vulputate eleifend diam. Integer ultrices lacus odio, vel imperdiet enim accumsan id. Sed ligula tortor, interdum eu velit eget, pharetra pulvinar magna. Sed non lacus in eros tincidunt sagittis ac vel justo. Donec vitae leo sagittis, accumsan ante sit amet, accumsan odio. Ut volutpat ultricies tortor. Vestibulum tempus purus et est tristique sagittis quis vitae turpis. - -Nam iaculis neque lacus, eget euismod turpis blandit eget. In hac habitasse platea dictumst. Phasellus justo neque, scelerisque sit amet risus ut, pretium commodo nisl. Phasellus auctor sapien sed ex bibendum fermentum. Proin maximus odio a ante ornare, a feugiat lorem egestas. Etiam efficitur tortor a ante tincidunt interdum. Nullam non est ac massa congue efficitur sit amet nec eros. Nullam at ipsum vel mauris tincidunt efficitur. Duis pulvinar nisl elit, id auctor risus laoreet ac. Sed nunc mauris, tristique id leo ut, condimentum congue nunc. Sed ultricies, mauris et convallis faucibus, justo ex faucibus est, at lobortis purus justo non arcu. Integer vel facilisis elit, dapibus imperdiet mauris. - -Pellentesque non mattis turpis, eget bibendum velit. Fusce sollicitudin ante ac tincidunt rhoncus. Praesent porta scelerisque consequat. Donec eleifend faucibus sollicitudin. Quisque vitae purus eget tortor tempor ultrices. Maecenas mauris diam, semper vitae est non, imperdiet tempor magna. Duis elit lacus, auctor vestibulum enim eget, rhoncus porttitor tortor. - -Donec non rhoncus nibh. Cras dapibus justo vitae nunc accumsan, id congue erat egestas. Aenean at ante ante. Duis eleifend imperdiet dREADALL diff --git a/runsc/test/image/mysql.sql b/runsc/test/image/mysql.sql deleted file mode 100644 index 51554b98d..000000000 --- a/runsc/test/image/mysql.sql +++ /dev/null @@ -1,23 +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. - -SHOW databases; -USE mysql; - -CREATE TABLE foo (id int); -INSERT INTO foo VALUES(1); -SELECT * FROM foo; -DROP TABLE foo; - -shutdown; diff --git a/runsc/test/image/ruby.rb b/runsc/test/image/ruby.rb deleted file mode 100644 index aced49c6d..000000000 --- a/runsc/test/image/ruby.rb +++ /dev/null @@ -1,23 +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. - -require 'sinatra' - -set :bind, "0.0.0.0" -set :port, 8080 - -get '/' do - 'Hello World' -end - diff --git a/runsc/test/image/ruby.sh b/runsc/test/image/ruby.sh deleted file mode 100644 index ebe8d5b0e..000000000 --- a/runsc/test/image/ruby.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# 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. - -set -e - -gem install sinatra -ruby /src/ruby.rb diff --git a/runsc/test/install.sh b/runsc/test/install.sh deleted file mode 100755 index 8f05dea20..000000000 --- a/runsc/test/install.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash - -# 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. - -# Fail on any error -set -e - -# Defaults -declare runtime=runsc-test -declare uninstall=0 - -function findExe() { - local exe=${1} - - local path=$(find bazel-bin/runsc -type f -executable -name "${exe}" | head -n1) - if [[ "${path}" == "" ]]; then - echo "Location of ${exe} not found in bazel-bin" >&2 - exit 1 - fi - echo "${path}" -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --runtime) - shift - [ "$#" -le 0 ] && echo "No runtime provided" && exit 1 - runtime=$1 - ;; - -u) - uninstall=1 - ;; - *) - echo "Unknown option: ${1}" - echo "" - echo "Usage: ${0} [--runtime <name>] [-u]" - echo " --runtime sets the runtime name, default: runsc-test" - echo " -u uninstall the runtime" - exit 1 - esac - shift -done - -# Find location of executables. -declare -r dockercfg=$(findExe dockercfg) -[[ "${dockercfg}" == "" ]] && exit 1 - -declare runsc=$(findExe runsc) -[[ "${runsc}" == "" ]] && exit 1 - -if [[ ${uninstall} == 0 ]]; then - rm -rf /tmp/${runtime} - mkdir -p /tmp/${runtime} - cp "${runsc}" /tmp/${runtime}/runsc - runsc=/tmp/${runtime}/runsc - - # Make tmp dir and runsc binary readable and executable to all users, since it - # will run in an empty user namespace. - chmod a+rx "${runsc}" $(dirname "${runsc}") - - # Make log dir executable and writable to all users for the same reason. - declare logdir=/tmp/"${runtime?}/logs" - mkdir -p "${logdir}" - sudo -n chmod a+wx "${logdir}" - - declare -r args="--debug-log '${logdir}/' --debug --strace --log-packets" - # experimental is needed to checkpoint/restore. - sudo -n "${dockercfg}" --experimental=true runtime-add "${runtime}" "${runsc}" ${args} - sudo -n "${dockercfg}" runtime-add "${runtime}"-kvm "${runsc}" --platform=kvm ${args} - sudo -n "${dockercfg}" runtime-add "${runtime}"-hostnet "${runsc}" --network=host ${args} - sudo -n "${dockercfg}" runtime-add "${runtime}"-overlay "${runsc}" --overlay ${args} - -else - sudo -n "${dockercfg}" runtime-rm "${runtime}" - sudo -n "${dockercfg}" runtime-rm "${runtime}"-kvm - sudo -n "${dockercfg}" runtime-rm "${runtime}"-hostnet - sudo -n "${dockercfg}" runtime-rm "${runtime}"-overlay -fi - -echo "Restarting docker service..." -sudo -n /etc/init.d/docker restart diff --git a/runsc/test/integration/BUILD b/runsc/test/integration/BUILD deleted file mode 100644 index 12065617c..000000000 --- a/runsc/test/integration/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//runsc/test:build_defs.bzl", "runtime_test") - -package(licenses = ["notice"]) - -runtime_test( - name = "integration_test", - size = "large", - srcs = [ - "exec_test.go", - "integration_test.go", - "regression_test.go", - ], - embed = [":integration"], - tags = [ - # Requires docker and runsc to be configured before the test runs. - "manual", - "local", - ], - deps = [ - "//pkg/abi/linux", - "//runsc/test/testutil", - ], -) - -go_library( - name = "integration", - srcs = ["integration.go"], - importpath = "gvisor.dev/gvisor/runsc/test/integration", -) diff --git a/runsc/test/integration/exec_test.go b/runsc/test/integration/exec_test.go deleted file mode 100644 index 993136f96..000000000 --- a/runsc/test/integration/exec_test.go +++ /dev/null @@ -1,161 +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 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" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func TestExecCapabilities(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("exec-test") - - // Start the container. - if err := d.Run("alpine", "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - matches, err := d.WaitForOutputSubmatch("CapEff:\t([0-9a-f]+)\n", 5*time.Second) - if err != nil { - t.Fatalf("WaitForOutputSubmatch() timeout: %v", err) - } - if len(matches) != 2 { - t.Fatalf("There should be a match for the whole line and the capability bitmask") - } - capString := matches[1] - t.Log("Root capabilities:", capString) - - // CAP_NET_RAW was in the capability set for the container, but was - // removed. However, `exec` does not remove it. Verify that it's not - // set in the container, then re-add it for comparison. - caps, err := strconv.ParseUint(capString, 16, 64) - if err != nil { - t.Fatalf("failed to convert capabilities %q: %v", capString, err) - } - if caps&(1<<uint64(linux.CAP_NET_RAW)) != 0 { - t.Fatalf("CAP_NET_RAW should be filtered, but is set in the container: %x", caps) - } - caps |= 1 << uint64(linux.CAP_NET_RAW) - want := fmt.Sprintf("CapEff:\t%016x\n", caps) - - // Now check that exec'd process capabilities match the root. - got, err := d.Exec("grep", "CapEff:", "/proc/self/status") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - if got != want { - t.Errorf("wrong capabilities, got: %q, want: %q", got, want) - } -} - -func TestExecJobControl(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("exec-job-control-test") - - // Start the container. - if err := d.Run("alpine", "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - // Exec 'sh' with an attached pty. - cmd, ptmx, err := d.ExecWithTerminal("sh") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - defer ptmx.Close() - - // Call "sleep 100 | cat" in the shell. We pipe to cat so that there - // will be two processes in the foreground process group. - if _, err := ptmx.Write([]byte("sleep 100 | cat\n")); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // Give shell a few seconds to start executing the sleep. - time.Sleep(2 * time.Second) - - // Send a ^C to the pty, which should kill sleep and cat, but not the - // shell. \x03 is ASCII "end of text", which is the same as ^C. - if _, err := ptmx.Write([]byte{'\x03'}); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // The shell should still be alive at this point. Sleep should have - // exited with code 2+128=130. We'll exit with 10 plus that number, so - // that we can be sure that the shell did not get signalled. - if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // Exec process should exit with code 10+130=140. - ps, err := cmd.Process.Wait() - if err != nil { - t.Fatalf("error waiting for exec process: %v", err) - } - ws := ps.Sys().(syscall.WaitStatus) - if !ws.Exited() { - t.Errorf("ws.Exited got false, want true") - } - if got, want := ws.ExitStatus(), 140; got != want { - t.Errorf("ws.ExitedStatus got %d, want %d", got, want) - } -} - -// Test that failure to exec returns proper error message. -func TestExecError(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("exec-error-test") - - // Start the container. - if err := d.Run("alpine", "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - _, err := d.Exec("no_can_find") - if err == nil { - t.Fatalf("docker exec didn't fail") - } - if want := `error finding executable "no_can_find" in PATH`; !strings.Contains(err.Error(), want) { - t.Fatalf("docker exec wrong error, got: %s, want: .*%s.*", err.Error(), want) - } -} diff --git a/runsc/test/integration/integration.go b/runsc/test/integration/integration.go deleted file mode 100644 index 4cd5f6c24..000000000 --- a/runsc/test/integration/integration.go +++ /dev/null @@ -1,16 +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 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 deleted file mode 100644 index 7cef4b9dd..000000000 --- a/runsc/test/integration/integration_test.go +++ /dev/null @@ -1,344 +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 integration provides end-to-end integration tests for runsc. -// -// 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. -// -// Setup instruction in runsc/test/README.md. -package integration - -import ( - "fmt" - "net" - "net/http" - "os" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// httpRequestSucceeds sends a request to a given url and checks that the status is OK. -func httpRequestSucceeds(client http.Client, server string, port int) error { - url := fmt.Sprintf("http://%s:%d", server, port) - // 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) { - if err := testutil.Pull("nginx"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("lifecycle-test") - if err := d.Create("-p", "80", "nginx"); err != nil { - t.Fatal("docker create failed:", err) - } - if err := d.Start(); err != nil { - d.CleanUp() - t.Fatal("docker start failed:", err) - } - - // Test that container is working - port, err := d.FindPort(80) - if err != nil { - t.Fatal("docker.FindPort(80) failed: ", err) - } - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatal("WaitForHTTP() timeout:", err) - } - client := http.Client{Timeout: time.Duration(2 * time.Second)} - if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Error("http request failed:", err) - } - - if err := d.Stop(); err != nil { - d.CleanUp() - t.Fatal("docker stop failed:", err) - } - if err := d.Remove(); err != nil { - t.Fatal("docker rm failed:", err) - } -} - -func TestPauseResume(t *testing.T) { - const img = "gcr.io/gvisor-presubmit/python-hello" - if !testutil.IsPauseResumeSupported() { - t.Log("Pause/resume is not supported, skipping test.") - return - } - - if err := testutil.Pull(img); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("pause-resume-test") - if err := d.Run("-p", "8080", img); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - // Find where port 8080 is mapped to. - port, err := d.FindPort(8080) - if err != nil { - t.Fatal("docker.FindPort(8080) failed:", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatal("WaitForHTTP() timeout:", err) - } - - // Check that container is working. - client := http.Client{Timeout: time.Duration(2 * time.Second)} - if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Error("http request failed:", err) - } - - if err := d.Pause(); err != nil { - t.Fatal("docker pause failed:", err) - } - - // Check if container is paused. - switch _, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); 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) - } - - if err := d.Unpause(); err != nil { - t.Fatal("docker unpause failed:", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatal("WaitForHTTP() timeout:", err) - } - - // Check if container is working again. - if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Error("http request failed:", err) - } -} - -func TestCheckpointRestore(t *testing.T) { - const img = "gcr.io/gvisor-presubmit/python-hello" - if !testutil.IsPauseResumeSupported() { - t.Log("Pause/resume is not supported, skipping test.") - return - } - if err := testutil.Pull(img); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("save-restore-test") - if err := d.Run("-p", "8080", img); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - if err := d.Checkpoint("test"); err != nil { - t.Fatal("docker checkpoint failed:", err) - } - - if _, err := d.Wait(30 * time.Second); err != nil { - t.Fatal(err) - } - - if err := d.Restore("test"); err != nil { - t.Fatal("docker restore failed:", err) - } - - // Find where port 8080 is mapped to. - port, err := d.FindPort(8080) - if err != nil { - t.Fatal("docker.FindPort(8080) failed:", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil { - t.Fatal("WaitForHTTP() timeout:", err) - } - - // Check if container is working again. - client := http.Client{Timeout: time.Duration(2 * time.Second)} - if err := httpRequestSucceeds(client, "localhost", port); err != nil { - t.Error("http request failed:", err) - } -} - -// Create client and server that talk to each other using the local IP. -func TestConnectToSelf(t *testing.T) { - d := testutil.MakeDocker("connect-to-self-test") - - // Creates server that replies "server" and exists. Sleeps at the end because - // 'docker exec' gets killed if the init process exists before it can finish. - if err := d.Run("ubuntu:trusty", "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil { - t.Fatal("docker run failed:", err) - } - defer d.CleanUp() - - // Finds IP address for host. - ip, err := d.Exec("/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'") - if err != nil { - t.Fatal("docker exec failed:", err) - } - ip = strings.TrimRight(ip, "\n") - - // Runs client that sends "client" to the server and exits. - reply, err := d.Exec("/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip)) - if err != nil { - t.Fatal("docker exec failed:", err) - } - - // Ensure both client and server got the message from each other. - if want := "server\n"; reply != want { - t.Errorf("Error on server, want: %q, got: %q", want, reply) - } - if _, err := d.WaitForOutput("^client\n$", 1*time.Second); err != nil { - t.Fatal("docker.WaitForOutput(client) timeout:", err) - } -} - -func TestMemLimit(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("cgroup-test") - cmd := "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'" - out, err := d.RunFg("--memory=500MB", "alpine", "sh", "-c", cmd) - if err != nil { - t.Fatal("docker run failed:", err) - } - defer d.CleanUp() - - // Remove warning message that swap isn't present. - if strings.HasPrefix(out, "WARNING") { - lines := strings.Split(out, "\n") - if len(lines) != 3 { - t.Fatalf("invalid output: %s", out) - } - out = lines[1] - } - - got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) - if err != nil { - t.Fatalf("failed to parse %q: %v", out, err) - } - if want := uint64(500 * 1024); got != want { - t.Errorf("MemTotal got: %d, want: %d", got, want) - } -} - -func TestNumCPU(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("cgroup-test") - cmd := "cat /proc/cpuinfo | grep 'processor.*:' | wc -l" - out, err := d.RunFg("--cpuset-cpus=0", "alpine", "sh", "-c", cmd) - if err != nil { - t.Fatal("docker run failed:", err) - } - defer d.CleanUp() - - got, err := strconv.Atoi(strings.TrimSpace(out)) - if err != nil { - t.Fatalf("failed to parse %q: %v", out, err) - } - if want := 1; got != want { - t.Errorf("MemTotal got: %d, want: %d", got, want) - } -} - -// TestJobControl tests that job control characters are handled properly. -func TestJobControl(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatalf("docker pull failed: %v", err) - } - d := testutil.MakeDocker("job-control-test") - - // Start the container with an attached PTY. - _, ptmx, err := d.RunWithPty("alpine", "sh") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer ptmx.Close() - defer d.CleanUp() - - // Call "sleep 100" in the shell. - if _, err := ptmx.Write([]byte("sleep 100\n")); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // Give shell a few seconds to start executing the sleep. - time.Sleep(2 * time.Second) - - // Send a ^C to the pty, which should kill sleep, but not the shell. - // \x03 is ASCII "end of text", which is the same as ^C. - if _, err := ptmx.Write([]byte{'\x03'}); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // The shell should still be alive at this point. Sleep should have - // exited with code 2+128=130. We'll exit with 10 plus that number, so - // that we can be sure that the shell did not get signalled. - if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil { - t.Fatalf("error writing to pty: %v", err) - } - - // Wait for the container to exit. - got, err := d.Wait(5 * time.Second) - if err != nil { - t.Fatalf("error getting exit code: %v", err) - } - // Container should exit with code 10+130=140. - if want := syscall.WaitStatus(140); got != want { - t.Errorf("container exited with code %d want %d", got, want) - } -} - -// TestTmpFile checks that files inside '/tmp' are not overridden. In addition, -// it checks that working dir is created if it doesn't exit. -func TestTmpFile(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("tmp-file-test") - if err := d.Run("-w=/tmp/foo/bar", "--read-only", "alpine", "touch", "/tmp/foo/bar/file"); err != nil { - t.Fatal("docker run failed:", err) - } - defer d.CleanUp() -} - -func TestMain(m *testing.M) { - testutil.EnsureSupportedDockerVersion() - os.Exit(m.Run()) -} diff --git a/runsc/test/integration/regression_test.go b/runsc/test/integration/regression_test.go deleted file mode 100644 index fb68dda99..000000000 --- a/runsc/test/integration/regression_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 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 integration - -import ( - "strings" - "testing" - - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// Test that UDS can be created using overlay when parent directory is in lower -// layer only (b/134090485). -// -// Prerequisite: the directory where the socket file is created must not have -// been open for write before bind(2) is called. -func TestBindOverlay(t *testing.T) { - if err := testutil.Pull("ubuntu:trusty"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("bind-overlay-test") - - cmd := "nc -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -U /var/run/sock && wait $p" - got, err := d.RunFg("ubuntu:trusty", "bash", "-c", cmd) - if err != nil { - t.Fatal("docker run failed:", err) - } - - if want := "foobar-asdf"; !strings.Contains(got, want) { - t.Fatalf("docker run output is missing %q: %s", want, got) - } - defer d.CleanUp() -} diff --git a/runsc/test/root/BUILD b/runsc/test/root/BUILD deleted file mode 100644 index 500ef7b8e..000000000 --- a/runsc/test/root/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "root", - srcs = ["root.go"], - importpath = "gvisor.dev/gvisor/runsc/test/root", -) - -go_test( - name = "root_test", - size = "small", - srcs = [ - "cgroup_test.go", - "chroot_test.go", - "crictl_test.go", - ], - embed = [":root"], - tags = [ - # Requires docker and runsc to be configured before the test runs. - # Also test only runs as root. - "manual", - "local", - ], - deps = [ - "//runsc/cgroup", - "//runsc/specutils", - "//runsc/test/root/testdata", - "//runsc/test/testutil", - "@com_github_syndtr_gocapability//capability:go_default_library", - ], -) diff --git a/runsc/test/root/cgroup_test.go b/runsc/test/root/cgroup_test.go deleted file mode 100644 index 5392dc6e0..000000000 --- a/runsc/test/root/cgroup_test.go +++ /dev/null @@ -1,237 +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 root - -import ( - "bufio" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - - "gvisor.dev/gvisor/runsc/cgroup" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -func verifyPid(pid int, path string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - var gots []int - scanner := bufio.NewScanner(f) - for scanner.Scan() { - got, err := strconv.Atoi(scanner.Text()) - if err != nil { - return err - } - if got == pid { - return nil - } - gots = append(gots, got) - } - if scanner.Err() != nil { - return scanner.Err() - } - return fmt.Errorf("got: %s, want: %d", gots, pid) -} - -// TestCgroup sets cgroup options and checks that cgroup was properly configured. -func TestCgroup(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("cgroup-test") - - attrs := []struct { - arg string - ctrl string - file string - want string - skipIfNotFound bool - }{ - { - arg: "--cpu-shares=1000", - ctrl: "cpu", - file: "cpu.shares", - want: "1000", - }, - { - arg: "--cpu-period=2000", - ctrl: "cpu", - file: "cpu.cfs_period_us", - want: "2000", - }, - { - arg: "--cpu-quota=3000", - ctrl: "cpu", - file: "cpu.cfs_quota_us", - want: "3000", - }, - { - arg: "--cpuset-cpus=0", - ctrl: "cpuset", - file: "cpuset.cpus", - want: "0", - }, - { - arg: "--cpuset-mems=0", - ctrl: "cpuset", - file: "cpuset.mems", - want: "0", - }, - { - arg: "--kernel-memory=100MB", - ctrl: "memory", - file: "memory.kmem.limit_in_bytes", - want: "104857600", - }, - { - arg: "--memory=1GB", - ctrl: "memory", - file: "memory.limit_in_bytes", - want: "1073741824", - }, - { - arg: "--memory-reservation=500MB", - ctrl: "memory", - file: "memory.soft_limit_in_bytes", - want: "524288000", - }, - { - arg: "--memory-swap=2GB", - 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", - }, - { - arg: "--blkio-weight=750", - ctrl: "blkio", - file: "blkio.weight", - want: "750", - }, - } - - args := make([]string, 0, len(attrs)) - for _, attr := range attrs { - args = append(args, attr.arg) - } - - args = append(args, "alpine", "sleep", "10000") - if err := d.Run(args...); err != nil { - t.Fatal("docker create failed:", err) - } - defer d.CleanUp() - - gid, err := d.ID() - if err != nil { - t.Fatalf("Docker.ID() failed: %v", err) - } - t.Logf("cgroup ID: %s", gid) - - // Check list of attributes defined above. - for _, attr := range attrs { - path := filepath.Join("/sys/fs/cgroup", attr.ctrl, "docker", gid, attr.file) - out, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) && attr.skipIfNotFound { - t.Logf("skipped %s/%s", attr.ctrl, attr.file) - continue - } - 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) - } - } - - // Check that sandbox is inside cgroup. - controllers := []string{ - "blkio", - "cpu", - "cpuset", - "memory", - "net_cls", - "net_prio", - "devices", - "freezer", - "perf_event", - "pids", - "systemd", - } - pid, err := d.SandboxPid() - if err != nil { - t.Fatalf("SandboxPid: %v", err) - } - for _, ctrl := range controllers { - path := filepath.Join("/sys/fs/cgroup", ctrl, "docker", gid, "cgroup.procs") - if err := verifyPid(pid, path); err != nil { - t.Errorf("cgroup control %q processes: %v", ctrl, err) - } - } -} - -func TestCgroupParent(t *testing.T) { - if err := testutil.Pull("alpine"); err != nil { - t.Fatal("docker pull failed:", err) - } - d := testutil.MakeDocker("cgroup-test") - - parent := testutil.RandomName("runsc") - if err := d.Run("--cgroup-parent", parent, "alpine", "sleep", "10000"); err != nil { - t.Fatal("docker create failed:", err) - } - defer d.CleanUp() - gid, err := d.ID() - if err != nil { - t.Fatalf("Docker.ID() failed: %v", err) - } - t.Logf("cgroup ID: %s", gid) - - // Check that sandbox is inside cgroup. - pid, err := d.SandboxPid() - if err != nil { - t.Fatalf("SandboxPid: %v", err) - } - - // Finds cgroup for the sandbox's parent process to check that cgroup is - // created in the right location relative to the parent. - cmd := fmt.Sprintf("grep PPid: /proc/%d/status | sed 's/PPid:\\s//'", pid) - ppid, err := exec.Command("bash", "-c", cmd).CombinedOutput() - if err != nil { - t.Fatalf("Executing %q: %v", cmd, err) - } - cgroups, err := cgroup.LoadPaths(strings.TrimSpace(string(ppid))) - if err != nil { - t.Fatalf("cgroup.LoadPath(%s): %v", ppid, err) - } - path := filepath.Join("/sys/fs/cgroup/memory", cgroups["memory"], parent, gid, "cgroup.procs") - if err := verifyPid(pid, path); err != nil { - t.Errorf("cgroup control %q processes: %v", "memory", err) - } -} diff --git a/runsc/test/root/chroot_test.go b/runsc/test/root/chroot_test.go deleted file mode 100644 index d0f236580..000000000 --- a/runsc/test/root/chroot_test.go +++ /dev/null @@ -1,161 +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 root is used for tests that requires sysadmin privileges run. First, -// follow the setup instruction in runsc/test/README.md. To run these tests: -// -// bazel build //runsc/test/root:root_test -// root_test=$(find -L ./bazel-bin/ -executable -type f -name root_test | grep __main__) -// sudo RUNSC_RUNTIME=runsc-test ${root_test} -package root - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - - "github.com/syndtr/gocapability/capability" - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/runsc/test/testutil" -) - -// 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 := testutil.MakeDocker("chroot-test") - if err := d.Run("alpine", "sleep", "10000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - defer d.CleanUp() - - pid, err := d.SandboxPid() - if err != nil { - t.Fatalf("Docker.SandboxPid(): %v", err) - } - - // Check that sandbox is chroot'ed. - procRoot := filepath.Join("/proc", strconv.Itoa(pid), "root") - chroot, err := filepath.EvalSymlinks(procRoot) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) - } - if chroot != "/" { - t.Errorf("sandbox is not chroot'd, it should be inside: /, got: %q", chroot) - } - - path, err := filepath.EvalSymlinks(filepath.Join("/proc", strconv.Itoa(pid), "cwd")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) - } - if chroot != path { - t.Errorf("sandbox current dir is wrong, want: %q, got: %q", chroot, path) - } - - fi, err := ioutil.ReadDir(procRoot) - if err != nil { - t.Fatalf("error listing %q: %v", chroot, err) - } - if want, got := 1, len(fi); want != got { - t.Fatalf("chroot dir got %d entries, want %d", got, want) - } - - // chroot dir is prepared by runsc and should contains only /proc. - if fi[0].Name() != "proc" { - t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc") - } - - d.CleanUp() -} - -func TestChrootGofer(t *testing.T) { - d := testutil.MakeDocker("chroot-test") - if err := d.Run("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() - if err != nil { - t.Fatalf("Docker.SandboxPid(): %v", err) - } - - // Find sandbox's parent PID. - cmd := fmt.Sprintf("grep PPid /proc/%d/status | awk '{print $2}'", sandPID) - parent, err := exec.Command("sh", "-c", cmd).CombinedOutput() - if err != nil { - t.Fatalf("failed to fetch runsc (%d) parent PID: %v, out:\n%s", sandPID, err, string(parent)) - } - parentPID, err := strconv.Atoi(strings.TrimSpace(string(parent))) - if err != nil { - t.Fatalf("failed to parse PPID %q: %v", string(parent), err) - } - - // Get all children from parent. - childrenOut, err := exec.Command("/usr/bin/pgrep", "-P", strconv.Itoa(parentPID)).CombinedOutput() - if err != nil { - t.Fatalf("failed to fetch containerd-shim children: %v", err) - } - children := strings.Split(strings.TrimSpace(string(childrenOut)), "\n") - - // This where the root directory is mapped on the host and that's where the - // gofer must have chroot'd to. - root := "/root" - - for _, child := range children { - childPID, err := strconv.Atoi(child) - if err != nil { - t.Fatalf("failed to parse child PID %q: %v", child, err) - } - if childPID == sandPID { - // Skip the sandbox, all other immediate children are gofers. - continue - } - - // Check that gofer is chroot'ed. - chroot, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "root")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) - } - if root != chroot { - t.Errorf("gofer chroot is wrong, want: %q, got: %q", root, chroot) - } - - path, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "cwd")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) - } - if root != path { - t.Errorf("gofer current dir is wrong, want: %q, got: %q", root, path) - } - } -} - -func TestMain(m *testing.M) { - testutil.EnsureSupportedDockerVersion() - - if !specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_DAC_OVERRIDE) { - fmt.Println("Test requires sysadmin privileges to run. Try again with sudo.") - os.Exit(1) - } - - os.Exit(m.Run()) -} diff --git a/runsc/test/root/crictl_test.go b/runsc/test/root/crictl_test.go deleted file mode 100644 index 515ae2df1..000000000 --- a/runsc/test/root/crictl_test.go +++ /dev/null @@ -1,242 +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 root - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/runsc/test/root/testdata" - "gvisor.dev/gvisor/runsc/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/. - -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) - } - defer cleanup() - podID, contID, err := crictl.StartPodAndContainer("httpd", testdata.Sandbox, testdata.Httpd) - if 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.Fatal(err) - } -} - -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) - } - - // 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) - } -} - -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) - } - - 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)) - } - - 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)) - } - - // Stop everything. - if err := crictl.StopPodAndContainer(podID, contID); err != nil { - t.Fatal(err) - } -} - -// 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) (*testutil.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() - - // 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) }) - 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") - - // Start containerd. - config, err := testutil.WriteTmpFile("containerd-config", testdata.ContainerdConfig(getRunsc())) - if err != nil { - t.Fatalf("failed to write containerd config") - } - cleanups = append(cleanups, func() { os.RemoveAll(config) }) - containerd := exec.Command(getContainerd(), - "--config", config, - "--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) - } - containerdStdout, err := containerd.StdoutPipe() - if err != nil { - t.Fatalf("failed to get containerd stdout: %v", err) - } - if err := containerd.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())) - }) - - cleanup.Release() - return testutil.NewCrictl(20*time.Second, sockAddr), cleanupFunc, nil -} - -// httpGet GETs the contents of a file served from a pod on port 80. -func httpGet(crictl *testutil.Crictl, podID, filePath string) error { - // Get the IP of the httpd server. - ip, err := crictl.PodIP(podID) - if err != nil { - return fmt.Errorf("failed to get IP from pod %q: %v", podID, err) - } - - // GET the page. We may be waiting for the server to start, so retry - // with a timeout. - var resp *http.Response - cb := func() error { - r, err := http.Get(fmt.Sprintf("http://%s", path.Join(ip, filePath))) - resp = r - return err - } - if err := testutil.Poll(cb, 20*time.Second); err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return fmt.Errorf("bad status returned: %d", resp.StatusCode) - } - return nil -} - -func getContainerd() string { - // Bazel doesn't pass PATH through, assume the location of containerd - // unless specified by environment variable. - c := os.Getenv("CONTAINERD_PATH") - if c == "" { - return "/usr/local/bin/containerd" - } - return c -} - -func getRunsc() string { - // Bazel doesn't pass PATH through, assume the location of runsc unless - // specified by environment variable. - c := os.Getenv("RUNSC_EXEC") - if c == "" { - return "/tmp/runsc-test/runsc" - } - return c -} diff --git a/runsc/test/root/root.go b/runsc/test/root/root.go deleted file mode 100644 index 349c752cc..000000000 --- a/runsc/test/root/root.go +++ /dev/null @@ -1,16 +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 root is empty. See chroot_test.go for description. -package root diff --git a/runsc/test/root/testdata/BUILD b/runsc/test/root/testdata/BUILD deleted file mode 100644 index 80dc5f214..000000000 --- a/runsc/test/root/testdata/BUILD +++ /dev/null @@ -1,18 +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", - ], - importpath = "gvisor.dev/gvisor/runsc/test/root/testdata", - visibility = [ - "//visibility:public", - ], -) diff --git a/runsc/test/root/testdata/busybox.go b/runsc/test/root/testdata/busybox.go deleted file mode 100644 index e4dbd2843..000000000 --- a/runsc/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/runsc/test/root/testdata/containerd_config.go b/runsc/test/root/testdata/containerd_config.go deleted file mode 100644 index e12f1ec88..000000000 --- a/runsc/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/runsc/test/root/testdata/httpd.go b/runsc/test/root/testdata/httpd.go deleted file mode 100644 index 45d5e33d4..000000000 --- a/runsc/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/runsc/test/root/testdata/httpd_mount_paths.go b/runsc/test/root/testdata/httpd_mount_paths.go deleted file mode 100644 index ac3f4446a..000000000 --- a/runsc/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/runsc/test/root/testdata/sandbox.go b/runsc/test/root/testdata/sandbox.go deleted file mode 100644 index 0db210370..000000000 --- a/runsc/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/runsc/test/testutil/BUILD b/runsc/test/testutil/BUILD deleted file mode 100644 index 327e7ca4d..000000000 --- a/runsc/test/testutil/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "testutil", - srcs = [ - "crictl.go", - "docker.go", - "testutil.go", - "testutil_race.go", - ], - importpath = "gvisor.dev/gvisor/runsc/test/testutil", - visibility = ["//:sandbox"], - deps = [ - "//runsc/boot", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_kr_pty//:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) diff --git a/runsc/test/testutil/crictl.go b/runsc/test/testutil/crictl.go deleted file mode 100644 index 4f9ee0c05..000000000 --- a/runsc/test/testutil/crictl.go +++ /dev/null @@ -1,241 +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 testutil - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "strings" - "time" -) - -const endpointPrefix = "unix://" - -// Crictl contains information required to run the crictl utility. -type Crictl struct { - executable string - timeout time.Duration - imageEndpoint string - runtimeEndpoint string -} - -// NewCrictl returns a Crictl configured with a timeout and an endpoint over -// which it will talk to containerd. -func NewCrictl(timeout time.Duration, endpoint string) *Crictl { - // Bazel doesn't pass PATH through, assume the location of crictl - // unless specified by environment variable. - executable := os.Getenv("CRICTL_PATH") - if executable == "" { - executable = "/usr/local/bin/crictl" - } - return &Crictl{ - executable: executable, - timeout: timeout, - imageEndpoint: endpointPrefix + endpoint, - runtimeEndpoint: endpointPrefix + endpoint, - } -} - -// Pull pulls an container image. It corresponds to `crictl pull`. -func (cc *Crictl) Pull(imageName string) error { - _, err := cc.run("pull", imageName) - return err -} - -// RunPod creates a sandbox. It corresponds to `crictl runp`. -func (cc *Crictl) RunPod(sbSpecFile string) (string, error) { - podID, err := cc.run("runp", sbSpecFile) - if err != nil { - return "", fmt.Errorf("runp failed: %v", err) - } - // Strip the trailing newline from crictl output. - return strings.TrimSpace(podID), nil -} - -// Create creates a container within a sandbox. It corresponds to `crictl -// create`. -func (cc *Crictl) Create(podID, contSpecFile, sbSpecFile string) (string, error) { - podID, err := cc.run("create", podID, contSpecFile, sbSpecFile) - if err != nil { - return "", fmt.Errorf("create failed: %v", err) - } - // Strip the trailing newline from crictl output. - return strings.TrimSpace(podID), nil -} - -// Start starts a container. It corresponds to `crictl start`. -func (cc *Crictl) Start(contID string) (string, error) { - output, err := cc.run("start", contID) - if err != nil { - return "", fmt.Errorf("start failed: %v", err) - } - return output, nil -} - -// Stop stops a container. It corresponds to `crictl stop`. -func (cc *Crictl) Stop(contID string) error { - _, err := cc.run("stop", contID) - return err -} - -// Exec execs a program inside a container. It corresponds to `crictl exec`. -func (cc *Crictl) Exec(contID string, args ...string) (string, error) { - a := []string{"exec", contID} - a = append(a, args...) - output, err := cc.run(a...) - if err != nil { - return "", fmt.Errorf("exec failed: %v", err) - } - return output, nil -} - -// Rm removes a container. It corresponds to `crictl rm`. -func (cc *Crictl) Rm(contID string) error { - _, err := cc.run("rm", contID) - return err -} - -// StopPod stops a pod. It corresponds to `crictl stopp`. -func (cc *Crictl) StopPod(podID string) error { - _, err := cc.run("stopp", podID) - return err -} - -// containsConfig is a minimal copy of -// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto -// It only contains fields needed for testing. -type containerConfig struct { - Status containerStatus -} - -type containerStatus struct { - Network containerNetwork -} - -type containerNetwork struct { - IP string -} - -// PodIP returns a pod's IP address. -func (cc *Crictl) PodIP(podID string) (string, error) { - output, err := cc.run("inspectp", podID) - if err != nil { - return "", err - } - conf := &containerConfig{} - if err := json.Unmarshal([]byte(output), conf); err != nil { - return "", fmt.Errorf("failed to unmarshal JSON: %v, %s", err, output) - } - if conf.Status.Network.IP == "" { - return "", fmt.Errorf("no IP found in config: %s", output) - } - return conf.Status.Network.IP, nil -} - -// RmPod removes a container. It corresponds to `crictl rmp`. -func (cc *Crictl) RmPod(podID string) error { - _, err := cc.run("rmp", podID) - return err -} - -// StartPodAndContainer pulls an image, then starts a sandbox and container in -// that sandbox. It returns the pod ID and container ID. -func (cc *Crictl) StartPodAndContainer(image, sbSpec, contSpec string) (string, string, error) { - if err := cc.Pull(image); err != nil { - return "", "", fmt.Errorf("failed to pull %s: %v", image, err) - } - - // Write the specs to files that can be read by crictl. - sbSpecFile, err := WriteTmpFile("sbSpec", sbSpec) - if err != nil { - return "", "", fmt.Errorf("failed to write sandbox spec: %v", err) - } - contSpecFile, err := WriteTmpFile("contSpec", contSpec) - if err != nil { - return "", "", fmt.Errorf("failed to write container spec: %v", err) - } - - podID, err := cc.RunPod(sbSpecFile) - if err != nil { - return "", "", err - } - - contID, err := cc.Create(podID, contSpecFile, sbSpecFile) - if err != nil { - return "", "", fmt.Errorf("failed to create container in pod %q: %v", podID, err) - } - - if _, err := cc.Start(contID); err != nil { - return "", "", fmt.Errorf("failed to start container %q in pod %q: %v", contID, podID, err) - } - - return podID, contID, nil -} - -// StopPodAndContainer stops a container and pod. -func (cc *Crictl) StopPodAndContainer(podID, contID string) error { - if err := cc.Stop(contID); err != nil { - return fmt.Errorf("failed to stop container %q in pod %q: %v", contID, podID, err) - } - - if err := cc.Rm(contID); err != nil { - return fmt.Errorf("failed to remove container %q in pod %q: %v", contID, podID, err) - } - - if err := cc.StopPod(podID); err != nil { - return fmt.Errorf("failed to stop pod %q: %v", podID, err) - } - - if err := cc.RmPod(podID); err != nil { - return fmt.Errorf("failed to remove pod %q: %v", podID, err) - } - - return nil -} - -// run runs crictl with the given args and returns an error if it takes longer -// than cc.Timeout to run. -func (cc *Crictl) run(args ...string) (string, error) { - defaultArgs := []string{ - "--image-endpoint", cc.imageEndpoint, - "--runtime-endpoint", cc.runtimeEndpoint, - } - cmd := exec.Command(cc.executable, append(defaultArgs, args...)...) - - // Run the command with a timeout. - done := make(chan string) - errCh := make(chan error) - go func() { - output, err := cmd.CombinedOutput() - if err != nil { - errCh <- fmt.Errorf("error: \"%v\", output: %s", err, string(output)) - return - } - done <- string(output) - }() - select { - case output := <-done: - return output, nil - case err := <-errCh: - return "", err - case <-time.After(cc.timeout): - if err := KillCommand(cmd); err != nil { - return "", fmt.Errorf("timed out, then couldn't kill process %+v: %v", cmd, err) - } - return "", fmt.Errorf("timed out: %+v", cmd) - } -} diff --git a/runsc/test/testutil/docker.go b/runsc/test/testutil/docker.go deleted file mode 100644 index 3f3e191b0..000000000 --- a/runsc/test/testutil/docker.go +++ /dev/null @@ -1,406 +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 testutil - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "regexp" - "strconv" - "strings" - "syscall" - "time" - - "github.com/kr/pty" -) - -var runtimeType = flag.String("runtime-type", "", "specify which runtime to use: kvm, hostnet, overlay") - -func getRuntime() string { - r, ok := os.LookupEnv("RUNSC_RUNTIME") - if !ok { - r = "runsc-test" - } - if *runtimeType != "" { - r += "-" + *runtimeType - } - return r -} - -// IsPauseResumeSupported returns true if Pause/Resume is supported by runtime. -func IsPauseResumeSupported() bool { - // Native host network stack can't be saved. - return !strings.Contains(getRuntime(), "hostnet") -} - -// 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) - } -} - -// MountMode describes if the mount should be ro or rw. -type MountMode int - -const ( - // ReadOnly is what the name says. - ReadOnly MountMode = iota - // ReadWrite is what the name says. - ReadWrite -) - -// String returns the mount mode argument for this MountMode. -func (m MountMode) String() string { - switch m { - case ReadOnly: - return "ro" - case ReadWrite: - return "rw" - } - panic(fmt.Sprintf("invalid mode: %d", m)) -} - -// MountArg formats the volume argument to mount in the container. -func MountArg(source, target string, mode MountMode) string { - return fmt.Sprintf("-v=%s:%s:%v", source, target, mode) -} - -// LinkArg formats the link argument. -func LinkArg(source *Docker, target string) string { - return fmt.Sprintf("--link=%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) -} - -// do executes docker command. -func do(args ...string) (string, error) { - log.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\nout: %s", args, err, out) - } - return string(out), nil -} - -// doWithPty executes docker command with stdio attached to a pty. -func doWithPty(args ...string) (*exec.Cmd, *os.File, error) { - log.Printf("Running with pty: docker %s\n", args) - cmd := exec.Command("docker", args...) - ptmx, err := pty.Start(cmd) - if err != nil { - return nil, nil, fmt.Errorf("error executing docker %s with a pty: %v", args, err) - } - return cmd, ptmx, nil -} - -// Pull pulls a docker image. This is used in tests to isolate the -// time to pull the image off the network from the time to actually -// start the container, to avoid timeouts over slow networks. -func Pull(image string) error { - _, err := do("pull", image) - return err -} - -// 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 { - return Docker{Name: RandomName(namePrefix), Runtime: getRuntime()} -} - -// logDockerID logs a container id, which is needed to find container runsc logs. -func (d *Docker) logDockerID() { - id, err := d.ID() - if err != nil { - log.Printf("%v\n", err) - } - log.Printf("Name: %s ID: %v\n", d.Name, id) -} - -// Create calls 'docker create' with the arguments provided. -func (d *Docker) Create(args ...string) error { - a := []string{"create", "--runtime", d.Runtime, "--name", d.Name} - a = append(a, args...) - _, err := do(a...) - if err == nil { - d.logDockerID() - } - return err -} - -// Start calls 'docker start'. -func (d *Docker) Start() error { - if _, err := do("start", d.Name); err != nil { - return fmt.Errorf("error starting container %q: %v", d.Name, err) - } - return nil -} - -// Stop calls 'docker stop'. -func (d *Docker) Stop() error { - if _, err := do("stop", d.Name); err != nil { - return fmt.Errorf("error stopping container %q: %v", d.Name, err) - } - return nil -} - -// Run calls 'docker run' with the arguments provided. The container starts -// running in the background and the call returns immediately. -func (d *Docker) Run(args ...string) error { - a := []string{"run", "--runtime", d.Runtime, "--name", d.Name, "-d"} - a = append(a, args...) - _, err := do(a...) - if err == nil { - d.logDockerID() - } - return err -} - -// RunWithPty is like Run but with an attached pty. -func (d *Docker) RunWithPty(args ...string) (*exec.Cmd, *os.File, error) { - a := []string{"run", "--runtime", d.Runtime, "--name", d.Name, "-it"} - a = append(a, args...) - return doWithPty(a...) -} - -// RunFg calls 'docker run' with the arguments provided in the foreground. It -// blocks until the container exits and returns the output. -func (d *Docker) RunFg(args ...string) (string, error) { - a := []string{"run", "--runtime", d.Runtime, "--name", d.Name} - a = append(a, args...) - out, err := do(a...) - if err == nil { - d.logDockerID() - } - return string(out), err -} - -// Logs calls 'docker logs'. -func (d *Docker) Logs() (string, error) { - return do("logs", d.Name) -} - -// Exec calls 'docker exec' with the arguments provided. -func (d *Docker) Exec(args ...string) (string, error) { - a := []string{"exec", d.Name} - a = append(a, args...) - return do(a...) -} - -// ExecWithTerminal calls 'docker exec -it' with the arguments provided and -// attaches a pty to stdio. -func (d *Docker) ExecWithTerminal(args ...string) (*exec.Cmd, *os.File, error) { - a := []string{"exec", "-it", d.Name} - a = append(a, args...) - return doWithPty(a...) -} - -// Pause calls 'docker pause'. -func (d *Docker) Pause() error { - if _, err := do("pause", d.Name); err != nil { - return fmt.Errorf("error pausing container %q: %v", d.Name, err) - } - return nil -} - -// Unpause calls 'docker pause'. -func (d *Docker) Unpause() error { - if _, err := do("unpause", d.Name); err != nil { - return fmt.Errorf("error unpausing container %q: %v", d.Name, err) - } - return nil -} - -// Checkpoint calls 'docker checkpoint'. -func (d *Docker) Checkpoint(name string) error { - if _, err := do("checkpoint", "create", d.Name, name); err != nil { - return fmt.Errorf("error pausing container %q: %v", d.Name, err) - } - return nil -} - -// Restore calls 'docker start --checkname [name]'. -func (d *Docker) Restore(name string) error { - if _, err := do("start", "--checkpoint", name, d.Name); err != nil { - return fmt.Errorf("error starting container %q: %v", d.Name, err) - } - return nil -} - -// Remove calls 'docker rm'. -func (d *Docker) Remove() error { - if _, err := do("rm", d.Name); err != nil { - return fmt.Errorf("error deleting container %q: %v", d.Name, err) - } - return nil -} - -// CleanUp kills and deletes the container (best effort). -func (d *Docker) CleanUp() { - d.logDockerID() - if _, err := do("kill", d.Name); err != nil { - log.Printf("error killing container %q: %v", d.Name, err) - } - if err := d.Remove(); err != nil { - log.Print(err) - } -} - -// 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 := 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 -} - -// SandboxPid returns the PID to the sandbox process. -func (d *Docker) SandboxPid() (int, error) { - out, err := do("inspect", "-f={{.State.Pid}}", d.Name) - if err != nil { - return -1, fmt.Errorf("error retrieving pid: %v", err) - } - pid, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n")) - if err != nil { - return -1, fmt.Errorf("error parsing pid %q: %v", out, err) - } - return pid, nil -} - -// ID returns the container ID. -func (d *Docker) ID() (string, error) { - out, err := do("inspect", "-f={{.Id}}", d.Name) - if err != nil { - return "", fmt.Errorf("error retrieving ID: %v", err) - } - return strings.TrimSpace(string(out)), nil -} - -// Wait waits for container to exit, up to the given timeout. Returns error if -// wait fails or timeout is hit. Returns the application return code otherwise. -// Note that the application may have failed even if err == nil, always check -// the exit code. -func (d *Docker) Wait(timeout time.Duration) (syscall.WaitStatus, error) { - timeoutChan := time.After(timeout) - waitChan := make(chan (syscall.WaitStatus)) - errChan := make(chan (error)) - - go func() { - out, err := do("wait", d.Name) - if err != nil { - errChan <- fmt.Errorf("error waiting for container %q: %v", d.Name, err) - } - exit, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n")) - if err != nil { - errChan <- fmt.Errorf("error parsing exit code %q: %v", out, err) - } - waitChan <- syscall.WaitStatus(uint32(exit)) - }() - - select { - case ws := <-waitChan: - return ws, nil - case err := <-errChan: - return syscall.WaitStatus(1), err - case <-timeoutChan: - return syscall.WaitStatus(1), fmt.Errorf("timeout waiting for container %q", d.Name) - } -} - -// WaitForOutput calls 'docker logs' to retrieve containers output and searches -// for the given pattern. -func (d *Docker) WaitForOutput(pattern string, timeout time.Duration) (string, error) { - matches, err := d.WaitForOutputSubmatch(pattern, timeout) - if err != nil { - return "", err - } - if len(matches) == 0 { - return "", nil - } - return matches[0], nil -} - -// WaitForOutputSubmatch calls 'docker logs' to retrieve containers output and -// searches for the given pattern. It returns any regexp submatches as well. -func (d *Docker) WaitForOutputSubmatch(pattern string, timeout time.Duration) ([]string, error) { - re := regexp.MustCompile(pattern) - var out string - for exp := time.Now().Add(timeout); time.Now().Before(exp); { - var err error - out, err = d.Logs() - if err != nil { - return nil, err - } - if matches := re.FindStringSubmatch(out); matches != nil { - // Success! - return matches, nil - } - time.Sleep(100 * time.Millisecond) - } - return nil, fmt.Errorf("timeout waiting for output %q: %s", re.String(), out) -} diff --git a/runsc/test/testutil/testutil.go b/runsc/test/testutil/testutil.go deleted file mode 100644 index 4a3dfa0e3..000000000 --- a/runsc/test/testutil/testutil.go +++ /dev/null @@ -1,421 +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 testutil contains utility functions for runsc tests. -package testutil - -import ( - "bufio" - "context" - "encoding/base32" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net/http" - "os" - "os/exec" - "os/signal" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "syscall" - "time" - - "github.com/cenkalti/backoff" - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/specutils" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -// RaceEnabled is set to true if it was built with '--race' option. -var RaceEnabled = false - -// TmpDir returns the absolute path to a writable directory that can be used as -// scratch by the test. -func TmpDir() string { - dir := os.Getenv("TEST_TMPDIR") - if dir == "" { - dir = "/tmp" - } - return dir -} - -// ConfigureExePath configures the executable for runsc in the test environment. -func ConfigureExePath() error { - path, err := FindFile("runsc/runsc") - if err != nil { - return err - } - specutils.ExePath = path - return nil -} - -// FindFile searchs for a file inside the test run environment. It returns the -// full path to the file. It fails if none or more than one file is found. -func FindFile(path string) (string, error) { - wd, err := os.Getwd() - if err != nil { - return "", err - } - - // The test root is demarcated by a path element called "__main__". Search for - // it backwards from the working directory. - root := wd - for { - dir, name := filepath.Split(root) - if name == "__main__" { - break - } - if len(dir) == 0 { - return "", fmt.Errorf("directory __main__ not found in %q", wd) - } - // Remove ending slash to loop around. - root = dir[:len(dir)-1] - } - - // Annoyingly, bazel adds the build type to the directory path for go - // binaries, but not for c++ binaries. We use two different patterns to - // to find our file. - patterns := []string{ - // Try the obvious path first. - filepath.Join(root, path), - // If it was a go binary, use a wildcard to match the build - // type. The pattern is: /test-path/__main__/directories/*/file. - filepath.Join(root, filepath.Dir(path), "*", filepath.Base(path)), - } - - for _, p := range patterns { - matches, err := filepath.Glob(p) - if err != nil { - // "The only possible returned error is ErrBadPattern, - // when pattern is malformed." -godoc - return "", fmt.Errorf("error globbing %q: %v", p, err) - } - switch len(matches) { - case 0: - // Try the next pattern. - case 1: - // We found it. - return matches[0], nil - default: - return "", fmt.Errorf("more than one match found for %q: %s", path, matches) - } - } - return "", fmt.Errorf("file %q not found", path) -} - -// TestConfig returns the default configuration to use in tests. Note that -// 'RootDir' must be set by caller if required. -func TestConfig() *boot.Config { - return &boot.Config{ - Debug: true, - LogFormat: "text", - DebugLogFormat: "text", - AlsoLogToStderr: true, - LogPackets: true, - Network: boot.NetworkNone, - Strace: true, - Platform: "ptrace", - FileAccess: boot.FileAccessExclusive, - TestOnlyAllowRunAsCurrentUserWithoutChroot: true, - NumNetworkChannels: 1, - } -} - -// TestConfigWithRoot returns the default configuration to use in tests. -func TestConfigWithRoot(rootDir string) *boot.Config { - conf := TestConfig() - conf.RootDir = rootDir - return conf -} - -// NewSpecWithArgs creates a simple spec with the given args suitable for use -// in tests. -func NewSpecWithArgs(args ...string) *specs.Spec { - return &specs.Spec{ - // The host filesystem root is the container root. - Root: &specs.Root{ - Path: "/", - Readonly: true, - }, - Process: &specs.Process{ - Args: args, - Env: []string{ - "PATH=" + os.Getenv("PATH"), - }, - Capabilities: specutils.AllCapabilities(), - }, - Mounts: []specs.Mount{ - // Root is readonly, but many tests want to write to tmpdir. - // This creates a writable mount inside the root. Also, when tmpdir points - // to "/tmp", it makes the the actual /tmp to be mounted and not a tmpfs - // inside the sentry. - { - Type: "bind", - Destination: TmpDir(), - Source: TmpDir(), - }, - }, - Hostname: "runsc-test-hostname", - } -} - -// SetupRootDir creates a root directory for containers. -func SetupRootDir() (string, error) { - rootDir, err := ioutil.TempDir(TmpDir(), "containers") - if err != nil { - return "", fmt.Errorf("error creating root dir: %v", err) - } - return rootDir, nil -} - -// SetupContainer creates a bundle and root dir for the container, generates a -// test config, and writes the spec to config.json in the bundle dir. -func SetupContainer(spec *specs.Spec, conf *boot.Config) (rootDir, bundleDir string, err error) { - // Setup root dir if one hasn't been provided. - if len(conf.RootDir) == 0 { - rootDir, err = SetupRootDir() - if err != nil { - return "", "", err - } - conf.RootDir = rootDir - } - bundleDir, err = SetupBundleDir(spec) - return rootDir, bundleDir, err -} - -// SetupBundleDir creates a bundle dir and writes the spec to config.json. -func SetupBundleDir(spec *specs.Spec) (bundleDir string, err error) { - bundleDir, err = ioutil.TempDir(TmpDir(), "bundle") - if err != nil { - return "", fmt.Errorf("error creating bundle dir: %v", err) - } - - if err = writeSpec(bundleDir, spec); err != nil { - return "", fmt.Errorf("error writing spec: %v", err) - } - return bundleDir, nil -} - -// writeSpec writes the spec to disk in the given directory. -func writeSpec(dir string, spec *specs.Spec) error { - b, err := json.Marshal(spec) - if err != nil { - return err - } - return ioutil.WriteFile(filepath.Join(dir, "config.json"), b, 0755) -} - -// UniqueContainerID generates a unique container id for each test. -// -// The container id is used to create an abstract unix domain socket, which must -// be unique. While the container forbids creating two containers with the same -// name, sometimes between test runs the socket does not get cleaned up quickly -// enough, causing container creation to fail. -func UniqueContainerID() string { - // Read 20 random bytes. - b := make([]byte, 20) - // "[Read] always returns len(p) and a nil error." --godoc - if _, err := rand.Read(b); err != nil { - panic("rand.Read failed: " + err.Error()) - } - // base32 encode the random bytes, so that the name is a valid - // container id and can be used as a socket name in the filesystem. - return fmt.Sprintf("test-container-%s", base32.StdEncoding.EncodeToString(b)) -} - -// Copy copies file from src to dst. -func Copy(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - - _, err = io.Copy(out, in) - return err -} - -// Poll is a shorthand function to poll for something with given timeout. -func Poll(cb func() error, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx) - return backoff.Retry(cb, b) -} - -// WaitForHTTP tries GET requests on a port until the call succeeds or timeout. -func WaitForHTTP(port int, timeout time.Duration) error { - cb := func() error { - c := &http.Client{ - // Calculate timeout to be able to do minimum 5 attempts. - Timeout: timeout / 5, - } - url := fmt.Sprintf("http://localhost:%d/", port) - resp, err := c.Get(url) - if err != nil { - log.Printf("Waiting %s: %v", url, err) - return err - } - resp.Body.Close() - return nil - } - return Poll(cb, timeout) -} - -// Reaper reaps child processes. -type Reaper struct { - // mu protects ch, which will be nil if the reaper is not running. - mu sync.Mutex - ch chan os.Signal -} - -// Start starts reaping child processes. -func (r *Reaper) Start() { - r.mu.Lock() - defer r.mu.Unlock() - - if r.ch != nil { - panic("reaper.Start called on a running reaper") - } - - r.ch = make(chan os.Signal, 1) - signal.Notify(r.ch, syscall.SIGCHLD) - - go func() { - for { - r.mu.Lock() - ch := r.ch - r.mu.Unlock() - if ch == nil { - return - } - - _, ok := <-ch - if !ok { - // Channel closed. - return - } - for { - cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) - if cpid < 1 { - break - } - } - } - }() -} - -// Stop stops reaping child processes. -func (r *Reaper) Stop() { - r.mu.Lock() - defer r.mu.Unlock() - - if r.ch == nil { - panic("reaper.Stop called on a stopped reaper") - } - - signal.Stop(r.ch) - close(r.ch) - r.ch = nil -} - -// StartReaper is a helper that starts a new Reaper and returns a function to -// stop it. -func StartReaper() func() { - r := &Reaper{} - r.Start() - return r.Stop -} - -// WaitUntilRead reads from the given reader until the wanted string is found -// or until timeout. -func WaitUntilRead(r io.Reader, want string, split bufio.SplitFunc, timeout time.Duration) error { - sc := bufio.NewScanner(r) - if split != nil { - sc.Split(split) - } - // done must be accessed atomically. A value greater than 0 indicates - // that the read loop can exit. - var done uint32 - doneCh := make(chan struct{}) - go func() { - for sc.Scan() { - t := sc.Text() - if strings.Contains(t, want) { - atomic.StoreUint32(&done, 1) - close(doneCh) - break - } - if atomic.LoadUint32(&done) > 0 { - break - } - } - }() - select { - case <-time.After(timeout): - atomic.StoreUint32(&done, 1) - return fmt.Errorf("timeout waiting to read %q", want) - case <-doneCh: - return nil - } -} - -// KillCommand kills the process running cmd unless it hasn't been started. It -// returns an error if it cannot kill the process unless the reason is that the -// process has already exited. -func KillCommand(cmd *exec.Cmd) error { - if cmd.Process == nil { - return nil - } - if err := cmd.Process.Kill(); err != nil { - if !strings.Contains(err.Error(), "process already finished") { - return fmt.Errorf("failed to kill process %v: %v", cmd, err) - } - } - return nil -} - -// WriteTmpFile writes text to a temporary file, closes the file, and returns -// the name of the file. -func WriteTmpFile(pattern, text string) (string, error) { - file, err := ioutil.TempFile(TmpDir(), pattern) - if err != nil { - return "", err - } - defer file.Close() - if _, err := file.Write([]byte(text)); err != nil { - return "", err - } - return file.Name(), nil -} - -// RandomName create a name with a 6 digit random number appended to it. -func RandomName(prefix string) string { - return fmt.Sprintf("%s-%06d", prefix, rand.Int31n(1000000)) -} diff --git a/runsc/test/testutil/testutil_race.go b/runsc/test/testutil/testutil_race.go deleted file mode 100644 index 86db6ffa1..000000000 --- a/runsc/test/testutil/testutil_race.go +++ /dev/null @@ -1,21 +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. - -// +build race - -package testutil - -func init() { - RaceEnabled = true -} diff --git a/runsc/tools/dockercfg/BUILD b/runsc/tools/dockercfg/BUILD deleted file mode 100644 index 5cff917ed..000000000 --- a/runsc/tools/dockercfg/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "dockercfg", - srcs = ["dockercfg.go"], - visibility = ["//visibility:public"], - deps = ["@com_github_google_subcommands//:go_default_library"], -) diff --git a/runsc/tools/dockercfg/dockercfg.go b/runsc/tools/dockercfg/dockercfg.go deleted file mode 100644 index eb9dbd421..000000000 --- a/runsc/tools/dockercfg/dockercfg.go +++ /dev/null @@ -1,193 +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. - -// Helper tool to configure Docker daemon. -package main - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - - "flag" - "github.com/google/subcommands" -) - -var ( - configFile = flag.String("config_file", "/etc/docker/daemon.json", "path to Docker daemon config file") - experimental = flag.Bool("experimental", false, "enable experimental features") -) - -func main() { - subcommands.Register(subcommands.HelpCommand(), "") - subcommands.Register(subcommands.FlagsCommand(), "") - subcommands.Register(&runtimeAdd{}, "") - subcommands.Register(&runtimeRemove{}, "") - - // All subcommands must be registered before flag parsing. - flag.Parse() - - exitCode := subcommands.Execute(context.Background()) - os.Exit(int(exitCode)) -} - -type runtime struct { - Path string `json:"path,omitempty"` - RuntimeArgs []string `json:"runtimeArgs,omitempty"` -} - -// runtimeAdd implements subcommands.Command. -type runtimeAdd struct { -} - -// Name implements subcommands.Command.Name. -func (*runtimeAdd) Name() string { - return "runtime-add" -} - -// Synopsis implements subcommands.Command.Synopsis. -func (*runtimeAdd) Synopsis() string { - return "adds a runtime to docker daemon configuration" -} - -// Usage implements subcommands.Command.Usage. -func (*runtimeAdd) Usage() string { - return `runtime-add [flags] <name> <path> [args...] -- if provided, args are passed as arguments to the runtime -` -} - -// SetFlags implements subcommands.Command.SetFlags. -func (*runtimeAdd) SetFlags(*flag.FlagSet) { -} - -// Execute implements subcommands.Command.Execute. -func (r *runtimeAdd) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if f.NArg() < 2 { - f.Usage() - return subcommands.ExitUsageError - } - name := f.Arg(0) - path := f.Arg(1) - runtimeArgs := f.Args()[2:] - - fmt.Printf("Adding runtime %q to file %q\n", name, *configFile) - c, err := readConfig(*configFile) - if err != nil { - log.Fatalf("Error reading config file %q: %v", *configFile, err) - } - - var rts map[string]interface{} - if i, ok := c["runtimes"]; ok { - rts = i.(map[string]interface{}) - } else { - rts = make(map[string]interface{}) - c["runtimes"] = rts - } - if *experimental { - c["experimental"] = true - } - rts[name] = runtime{Path: path, RuntimeArgs: runtimeArgs} - - if err := writeConfig(c, *configFile); err != nil { - log.Fatalf("Error writing config file %q: %v", *configFile, err) - } - return subcommands.ExitSuccess -} - -// runtimeRemove implements subcommands.Command. -type runtimeRemove struct { -} - -// Name implements subcommands.Command.Name. -func (*runtimeRemove) Name() string { - return "runtime-rm" -} - -// Synopsis implements subcommands.Command.Synopsis. -func (*runtimeRemove) Synopsis() string { - return "removes a runtime from docker daemon configuration" -} - -// Usage implements subcommands.Command.Usage. -func (*runtimeRemove) Usage() string { - return `runtime-rm [flags] <name> -` -} - -// SetFlags implements subcommands.Command.SetFlags. -func (*runtimeRemove) SetFlags(*flag.FlagSet) { -} - -// Execute implements subcommands.Command.Execute. -func (r *runtimeRemove) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if f.NArg() != 1 { - f.Usage() - return subcommands.ExitUsageError - } - name := f.Arg(0) - - fmt.Printf("Removing runtime %q from file %q\n", name, *configFile) - c, err := readConfig(*configFile) - if err != nil { - log.Fatalf("Error reading config file %q: %v", *configFile, err) - } - - var rts map[string]interface{} - if i, ok := c["runtimes"]; ok { - rts = i.(map[string]interface{}) - } else { - log.Fatalf("runtime %q not found", name) - } - if _, ok := rts[name]; !ok { - log.Fatalf("runtime %q not found", name) - } - delete(rts, name) - - if err := writeConfig(c, *configFile); err != nil { - log.Fatalf("Error writing config file %q: %v", *configFile, err) - } - return subcommands.ExitSuccess -} - -func readConfig(path string) (map[string]interface{}, error) { - configBytes, err := ioutil.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - c := make(map[string]interface{}) - if len(configBytes) > 0 { - if err := json.Unmarshal(configBytes, &c); err != nil { - return nil, err - } - } - return c, nil -} - -func writeConfig(c map[string]interface{}, path string) error { - b, err := json.MarshalIndent(c, "", " ") - if err != nil { - return err - } - - if err := os.Rename(path, path+"~"); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("error renaming config file %q: %v", path, err) - } - if err := ioutil.WriteFile(path, b, 0644); err != nil { - return fmt.Errorf("error writing config file %q: %v", path, err) - } - return nil -} |