gvisor/runsc/test/root/crictl_test.go

240 lines
7.0 KiB
Go

// Copyright 2018 Google LLC
//
// 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.googlesource.com/gvisor/runsc/specutils"
"gvisor.googlesource.com/gvisor/runsc/test/root/testdata"
"gvisor.googlesource.com/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
}