summaryrefslogtreecommitdiffhomepage
path: root/test/root/crictl_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/root/crictl_test.go')
-rw-r--r--test/root/crictl_test.go393
1 files changed, 393 insertions, 0 deletions
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
new file mode 100644
index 000000000..732fae821
--- /dev/null
+++ b/test/root/crictl_test.go
@@ -0,0 +1,393 @@
+// 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"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/cleanup"
+ "gvisor.dev/gvisor/pkg/test/criutil"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+)
+
+// Tests for crictl have to be run as root (rather than in a user namespace)
+// because crictl creates named network namespaces in /var/run/netns/.
+
+// Sandbox returns a JSON config for a simple sandbox. Sandbox names must be
+// unique so different names should be used when running tests on the same
+// containerd instance.
+func Sandbox(name string) string {
+ // Sandbox is a default JSON config for a sandbox.
+ s := map[string]interface{}{
+ "metadata": map[string]string{
+ "name": name,
+ "namespace": "default",
+ "uid": testutil.RandomID(""),
+ },
+ "linux": map[string]string{},
+ "log_directory": "/tmp",
+ }
+
+ v, err := json.Marshal(s)
+ if err != nil {
+ // This shouldn't happen.
+ panic(err)
+ }
+ return string(v)
+}
+
+// SimpleSpec returns a JSON config for a simple container that runs the
+// specified command in the specified image.
+func SimpleSpec(name, image string, cmd []string, extra map[string]interface{}) string {
+ s := map[string]interface{}{
+ "metadata": map[string]string{
+ "name": name,
+ },
+ "image": map[string]string{
+ "image": testutil.ImageByName(image),
+ },
+ // Log files are not deleted after root tests are run. Log to random
+ // paths to ensure logs are fresh.
+ "log_path": fmt.Sprintf("%s.log", testutil.RandomID(name)),
+ }
+ if len(cmd) > 0 { // Omit if empty.
+ s["command"] = cmd
+ }
+ for k, v := range extra {
+ s[k] = v // Extra settings.
+ }
+ v, err := json.Marshal(s)
+ if err != nil {
+ // This shouldn't happen.
+ panic(err)
+ }
+ return string(v)
+}
+
+// Httpd is a JSON config for an httpd container.
+var Httpd = SimpleSpec("httpd", "basic/httpd", nil, nil)
+
+// TestCrictlSanity refers to b/112433158.
+func TestCrictlSanity(t *testing.T) {
+ // 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("basic/httpd", Sandbox("default"), Httpd)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ // Look for the httpd page.
+ if err = httpGet(crictl, podID, "index.html"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+}
+
+// HttpdMountPaths is a JSON config for an httpd container with additional
+// mounts.
+var HttpdMountPaths = SimpleSpec("httpd", "basic/httpd", nil, map[string]interface{}{
+ "mounts": []map[string]interface{}{
+ map[string]interface{}{
+ "container_path": "/var/run/secrets/kubernetes.io/serviceaccount",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/volumes/kubernetes.io~secret/default-token-2rpfx",
+ "readonly": true,
+ },
+ map[string]interface{}{
+ "container_path": "/etc/hosts",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/etc-hosts",
+ "readonly": false,
+ },
+ map[string]interface{}{
+ "container_path": "/dev/termination-log",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/containers/httpd/d1709580",
+ "readonly": false,
+ },
+ map[string]interface{}{
+ "container_path": "/usr/local/apache2/htdocs/test",
+ "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064",
+ "readonly": true,
+ },
+ },
+ "linux": map[string]interface{}{},
+})
+
+// TestMountPaths refers to b/117635704.
+func TestMountPaths(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+ podID, contID, err := crictl.StartPodAndContainer("basic/httpd", Sandbox("default"), HttpdMountPaths)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ // Look for the directory available at /test.
+ if err = httpGet(crictl, podID, "test"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+}
+
+// TestMountPaths refers to b/118728671.
+func TestMountOverSymlinks(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+
+ spec := SimpleSpec("busybox", "basic/resolv", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer("basic/resolv", Sandbox("default"), spec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("readlink failed: %v, out: %s", err, out)
+ }
+ if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
+ t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
+ }
+
+ etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, etc)
+ }
+ tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, out)
+ }
+ if tmp != etc {
+ t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+}
+
+// TestHomeDir tests that the HOME environment variable is set for
+// Pod containers.
+func TestHomeDir(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+
+ // Note that container ID returned here is a sub-container. All Pod
+ // containers are sub-containers. The root container of the sandbox is the
+ // pause container.
+ t.Run("sub-container", func(t *testing.T) {
+ contSpec := SimpleSpec("subcontainer", "basic/busybox", []string{"sh", "-c", "echo $HOME"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer("basic/busybox", Sandbox("subcont-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Logs(contID)
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
+
+ // Tests that HOME is set for the exec process.
+ t.Run("exec", func(t *testing.T) {
+ contSpec := SimpleSpec("exec", "basic/busybox", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer("basic/busybox", Sandbox("exec-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME")
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
+}
+
+// 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"
+`
+
+// setup sets up before a test. Specifically it:
+// * Creates directories and a socket for containerd to utilize.
+// * Runs containerd and waits for it to reach a "ready" state for testing.
+// * Returns a cleanup function that should be called at the end of the test.
+func setup(t *testing.T) (*criutil.Crictl, func(), error) {
+ // 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)
+ }
+ cu := cleanup.Make(func() { os.RemoveAll(containerdRoot) })
+ defer cu.Clean()
+
+ containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state")
+ if err != nil {
+ t.Fatalf("failed to create containerd state: %v", err)
+ }
+ cu.Add(func() { os.RemoveAll(containerdState) })
+ sockAddr := filepath.Join(testutil.TmpDir(), "containerd-test.sock")
+
+ // We rewrite a configuration. This is based on the current docker
+ // configuration for the runtime under test.
+ runtime, err := dockerutil.RuntimePath()
+ if err != nil {
+ t.Fatalf("error discovering runtime path: %v", err)
+ }
+ config, configCleanup, err := testutil.WriteTmpFile("containerd-config", fmt.Sprintf(containerdConfigTemplate, runtime, runtime))
+ if err != nil {
+ t.Fatalf("failed to write containerd config")
+ }
+ cu.Add(configCleanup)
+
+ // Start containerd.
+ cmd := exec.Command(getContainerd(),
+ "--config", config,
+ "--log-level", "debug",
+ "--root", containerdRoot,
+ "--state", containerdState,
+ "--address", sockAddr)
+ startupR, startupW := io.Pipe()
+ defer startupR.Close()
+ defer startupW.Close()
+ stderr := &bytes.Buffer{}
+ stdout := &bytes.Buffer{}
+ cmd.Stderr = io.MultiWriter(startupW, stderr)
+ cmd.Stdout = io.MultiWriter(startupW, stdout)
+ cu.Add(func() {
+ // Log output in case of failure.
+ t.Logf("containerd stdout: %s", stdout.String())
+ t.Logf("containerd stderr: %s", stderr.String())
+ })
+
+ // Start the process.
+ if err := cmd.Start(); err != nil {
+ t.Fatalf("failed running containerd: %v", err)
+ }
+
+ // Wait for containerd to boot.
+ if err := testutil.WaitUntilRead(startupR, "Start streaming server", nil, 10*time.Second); err != nil {
+ t.Fatalf("failed to start containerd: %v", err)
+ }
+
+ // Kill must be the last cleanup (as it will be executed first).
+ cc := criutil.NewCrictl(t, sockAddr)
+ cu.Add(func() {
+ cc.CleanUp() // Remove tmp files, etc.
+ if err := testutil.KillCommand(cmd); err != nil {
+ log.Printf("error killing containerd: %v", err)
+ }
+ })
+
+ return cc, cu.Release(), nil
+}
+
+// httpGet GETs the contents of a file served from a pod on port 80.
+func httpGet(crictl *criutil.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 {
+ // Use the local path if it exists, otherwise, use the system one.
+ if _, err := os.Stat("/usr/local/bin/containerd"); err == nil {
+ return "/usr/local/bin/containerd"
+ }
+ return "/usr/bin/containerd"
+}