summaryrefslogtreecommitdiffhomepage
path: root/runsc/container
diff options
context:
space:
mode:
authorNicolas Lacasse <nlacasse@google.com>2019-07-23 14:35:50 -0700
committergVisor bot <gvisor-bot@google.com>2019-07-23 14:37:07 -0700
commit04cbb13ce9b151cf906f42e3f18ce3a875f01f63 (patch)
tree3c68885355ff140b59f5aee4b149911bcb72c439 /runsc/container
parent57745994384ee1ff94fc7bed4f814ba75e39d48e (diff)
Give each container a distinct MountNamespace.
This keeps all container filesystem completely separate from eachother (including from the root container filesystem), and allows us to get rid of the "__runsc_containers__" directory. It also simplifies container startup/teardown as we don't have to muck around in the root container's filesystem. PiperOrigin-RevId: 259613346
Diffstat (limited to 'runsc/container')
-rw-r--r--runsc/container/multi_container_test.go120
-rw-r--r--runsc/container/test_app/BUILD6
-rw-r--r--runsc/container/test_app/fds.go185
-rw-r--r--runsc/container/test_app/test_app.go8
4 files changed, 295 insertions, 24 deletions
diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go
index c0f9b372c..e299a0e88 100644
--- a/runsc/container/multi_container_test.go
+++ b/runsc/container/multi_container_test.go
@@ -456,19 +456,6 @@ func TestMultiContainerDestroy(t *testing.T) {
}
defer cleanup()
- // Exec in the root container to check for the existence of the
- // second container's root filesystem directory.
- contDir := path.Join(boot.ChildContainersDir, containers[1].ID)
- dirArgs := &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "-d", contDir},
- }
- if ws, err := containers[0].executeSync(dirArgs); err != nil {
- t.Fatalf("error executing %+v: %v", dirArgs, err)
- } else if ws.ExitStatus() != 0 {
- t.Errorf("exec 'test -f %q' got exit status %d, wanted 0", contDir, ws.ExitStatus())
- }
-
// Exec more processes to ensure signal all works for exec'd processes too.
args := &control.ExecArgs{
Filename: app,
@@ -496,13 +483,6 @@ func TestMultiContainerDestroy(t *testing.T) {
t.Errorf("container got process list: %s, want: %s", procListToString(pss), procListToString(expectedPL))
}
- // Now the container dir should be gone.
- if ws, err := containers[0].executeSync(dirArgs); err != nil {
- t.Fatalf("error executing %+v: %v", dirArgs, err)
- } else if ws.ExitStatus() == 0 {
- t.Errorf("exec 'test -f %q' got exit status 0, wanted non-zero", contDir)
- }
-
// Check that cont.Destroy is safe to call multiple times.
if err := containers[1].Destroy(); err != nil {
t.Errorf("error destroying container: %v", err)
@@ -786,6 +766,47 @@ func TestMultiContainerDestroyStarting(t *testing.T) {
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)
+ }
+ }
+}
+
// TestMultiContainerGoferStop tests that IO operations continue to work after
// containers have been stopped and gofers killed.
func TestMultiContainerGoferStop(t *testing.T) {
@@ -1167,3 +1188,62 @@ func TestMultiContainerSharedMountRestart(t *testing.T) {
}
}
}
+
+// 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)
+ }
+ }
+}
diff --git a/runsc/container/test_app/BUILD b/runsc/container/test_app/BUILD
index 054705ed7..82dbd54d2 100644
--- a/runsc/container/test_app/BUILD
+++ b/runsc/container/test_app/BUILD
@@ -5,10 +5,14 @@ package(licenses = ["notice"])
go_binary(
name = "test_app",
testonly = 1,
- srcs = ["test_app.go"],
+ 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
new file mode 100644
index 000000000..c12809cab
--- /dev/null
+++ b/runsc/container/test_app/fds.go
@@ -0,0 +1,185 @@
+// 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
index b7fc6498f..6578c7b41 100644
--- a/runsc/container/test_app/test_app.go
+++ b/runsc/container/test_app/test_app.go
@@ -35,11 +35,13 @@ import (
func main() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
- subcommands.Register(new(uds), "")
- subcommands.Register(new(taskTree), "")
+ 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()
@@ -76,7 +78,7 @@ func (c *uds) SetFlags(f *flag.FlagSet) {
// 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.Fatal("Flags cannot be empty, given: fileName: %q, socketPath: %q", 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)