summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKevin Krakauer <krakauer@google.com>2018-06-28 14:55:46 -0700
committerShentubot <shentubot@google.com>2018-06-28 14:56:36 -0700
commit16d37973ebc8f36ef613c0885879648cceaf1c45 (patch)
treec797f2c9352518b5372e961b74a6ae14f56fc307
parent5a8e014c3d424abfe931b8493d06a129c3fdd388 (diff)
runsc: Add the "wait" subcommand.
Users can now call "runsc wait <container id>" to wait on a particular process inside the container. -pid can also be used to wait on a specific PID. Manually tested the wait subcommand for a single waiter and multiple waiters (simultaneously 2 processes waiting on the container and 2 processes waiting on a PID within the container). PiperOrigin-RevId: 202548978 Change-Id: Idd507c2cdea613c3a14879b51cfb0f7ea3fb3d4c
-rw-r--r--runsc/boot/controller.go21
-rw-r--r--runsc/boot/loader.go49
-rw-r--r--runsc/cmd/BUILD1
-rw-r--r--runsc/cmd/wait.go103
-rw-r--r--runsc/container/container.go14
-rw-r--r--runsc/container/container_test.go49
-rw-r--r--runsc/main.go1
-rw-r--r--runsc/sandbox/sandbox.go24
8 files changed, 236 insertions, 26 deletions
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index 56829c605..ff75a382e 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -61,6 +61,10 @@ const (
// and return its ExitStatus.
ContainerWait = "containerManager.Wait"
+ // ContainerWaitPID is used to wait on a process with a certain PID in
+ // the sandbox and return its ExitStatus.
+ ContainerWaitPID = "containerManager.WaitPID"
+
// NetworkCreateLinksAndRoutes is the URPC endpoint for creating links
// and routes in a network stack.
NetworkCreateLinksAndRoutes = "Network.CreateLinksAndRoutes"
@@ -234,7 +238,22 @@ func (cm *containerManager) Resume(_, _ *struct{}) error {
// Wait waits for the init process in the given container.
func (cm *containerManager) Wait(cid *string, waitStatus *uint32) error {
log.Debugf("containerManager.Wait")
- return cm.l.wait(cid, waitStatus)
+ return cm.l.waitContainer(*cid, waitStatus)
+}
+
+// WaitPIDArgs are arguments to the WaitPID method.
+type WaitPIDArgs struct {
+ // PID is the PID in the container's PID namespace.
+ PID int32
+
+ // CID is the container ID.
+ CID string
+}
+
+// WaitPID waits for the process with PID 'pid' in the sandbox.
+func (cm *containerManager) WaitPID(args *WaitPIDArgs, waitStatus *uint32) error {
+ log.Debugf("containerManager.Wait")
+ return cm.l.waitPID(kernel.ThreadID(args.PID), args.CID, waitStatus)
}
// SignalArgs are arguments to the Signal method.
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index f359a0eb0..014908179 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -83,6 +83,9 @@ type Loader struct {
// rootProcArgs refers to the root sandbox init task.
rootProcArgs kernel.CreateProcessArgs
+ // sandboxID is the ID for the whole sandbox.
+ sandboxID string
+
// mu guards containerRootTGIDs.
mu sync.Mutex
@@ -452,23 +455,46 @@ func (l *Loader) startContainer(args *StartArgs, k *kernel.Kernel) (kernel.Threa
return tgid, nil
}
-// wait waits for the init process in the given container.
-func (l *Loader) wait(cid *string, waitStatus *uint32) error {
+// TODO: Per-container namespaces must be supported
+// for -pid.
+
+// waitContainer waits for the root process of a container to exit.
+func (l *Loader) waitContainer(cid string, waitStatus *uint32) error {
+ // Don't defer unlock, as doing so would make it impossible for
+ // multiple clients to wait on the same container.
l.mu.Lock()
- defer l.mu.Unlock()
- tgid, ok := l.containerRootTGIDs[*cid]
+ tgid, ok := l.containerRootTGIDs[cid]
+ l.mu.Unlock()
if !ok {
- return fmt.Errorf("can't find process for container %q in %v", *cid, l.containerRootTGIDs)
+ return fmt.Errorf("can't find process for container %q in %v", cid, l.containerRootTGIDs)
}
-
- // TODO: Containers don't map 1:1 with their root
- // processes. Container exits should be managed explicitly
- // rather than via PID.
// If the thread either has already exited or exits during waiting,
// consider the container exited.
- defer delete(l.containerRootTGIDs, *cid)
+ defer func() {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ // TODO: Containers don't map 1:1 with their root
+ // processes. Container exits should be managed explicitly
+ // rather than via PID.
+ delete(l.containerRootTGIDs, cid)
+ }()
+ return l.wait(tgid, cid, waitStatus)
+}
+
+func (l *Loader) waitPID(tgid kernel.ThreadID, cid string, waitStatus *uint32) error {
+ // TODO: Containers all currently share a PID namespace.
+ // When per-container PID namespaces are supported, wait should use cid
+ // to find the appropriate PID namespace.
+ if cid != l.sandboxID {
+ return errors.New("non-sandbox PID namespaces are not yet implemented")
+ }
+ return l.wait(tgid, cid, waitStatus)
+}
- tg := l.k.TaskSet().Root.ThreadGroupWithID(tgid)
+// wait waits for the process with TGID 'tgid' in a container's PID namespace
+// to exit.
+func (l *Loader) wait(tgid kernel.ThreadID, cid string, waitStatus *uint32) error {
+ tg := l.k.TaskSet().Root.ThreadGroupWithID(kernel.ThreadID(tgid))
if tg == nil {
return fmt.Errorf("no thread group with ID %d", tgid)
}
@@ -482,6 +508,7 @@ func (l *Loader) setRootContainerID(cid string) {
defer l.mu.Unlock()
// The root container has PID 1.
l.containerRootTGIDs = map[string]kernel.ThreadID{cid: 1}
+ l.sandboxID = cid
}
// WaitForStartSignal waits for a start signal from the control server.
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index 747793efc..18e95284b 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -25,6 +25,7 @@ go_library(
"run.go",
"start.go",
"state.go",
+ "wait.go",
],
importpath = "gvisor.googlesource.com/gvisor/runsc/cmd",
visibility = [
diff --git a/runsc/cmd/wait.go b/runsc/cmd/wait.go
new file mode 100644
index 000000000..8437457c4
--- /dev/null
+++ b/runsc/cmd/wait.go
@@ -0,0 +1,103 @@
+// Copyright 2018 Google Inc.
+//
+// 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 (
+ "syscall"
+
+ "context"
+ "flag"
+ "github.com/google/subcommands"
+ "gvisor.googlesource.com/gvisor/runsc/boot"
+ "gvisor.googlesource.com/gvisor/runsc/container"
+)
+
+const (
+ unsetPID = -1
+)
+
+// Wait implements subcommands.Command for the "wait" command.
+type Wait struct {
+ rootPID int
+ pid int
+}
+
+// Name implements subcommands.Command.Name.
+func (*Wait) Name() string {
+ return "wait"
+}
+
+// Synopsis implements subcommands.Command.Synopsis.
+func (*Wait) Synopsis() string {
+ return "wait on a process inside a container"
+}
+
+// Usage implements subcommands.Command.Usage.
+func (*Wait) Usage() string {
+ return `wait [flags] <container id>`
+}
+
+// SetFlags implements subcommands.Command.SetFlags.
+func (wt *Wait) SetFlags(f *flag.FlagSet) {
+ f.IntVar(&wt.rootPID, "rootpid", unsetPID, "select a PID in the sandbox root PID namespace to wait on instead of the container's root process")
+ f.IntVar(&wt.pid, "pid", unsetPID, "select a PID in the container's PID namespace to wait on instead of the container's root process")
+}
+
+// Execute implements subcommands.Command.Execute. It waits for a process in a
+// container to exit before returning.
+func (wt *Wait) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+ if f.NArg() != 1 {
+ f.Usage()
+ return subcommands.ExitUsageError
+ }
+ // You can't specify both -pid and -rootpid.
+ if wt.rootPID != unsetPID && wt.pid != unsetPID {
+ Fatalf("only up to one of -pid and -rootPid can be set")
+ }
+
+ id := f.Arg(0)
+ conf := args[0].(*boot.Config)
+
+ c, err := container.Load(conf.RootDir, id)
+ if err != nil {
+ Fatalf("error loading container: %v", err)
+ }
+
+ waitStatus := args[1].(*syscall.WaitStatus)
+ switch {
+ // Wait on the whole container.
+ case wt.rootPID == unsetPID && wt.pid == unsetPID:
+ ws, err := c.Wait()
+ if err != nil {
+ Fatalf("error waiting on container %q: %v", c.ID, err)
+ }
+ *waitStatus = ws
+ // Wait on a PID in the root PID namespace.
+ case wt.rootPID != unsetPID:
+ ws, err := c.WaitRootPID(int32(wt.rootPID))
+ if err != nil {
+ Fatalf("error waiting on PID in root PID namespace %d in container %q: %v", wt.rootPID, c.ID, err)
+ }
+ *waitStatus = ws
+ // Wait on a PID in the container's PID namespace.
+ case wt.pid != unsetPID:
+ ws, err := c.WaitPID(int32(wt.pid))
+ if err != nil {
+ Fatalf("error waiting on PID %d in container %q: %v", wt.pid, c.ID, err)
+ }
+ *waitStatus = ws
+ }
+ return subcommands.ExitSuccess
+}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index b2ea78084..042c76577 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -353,6 +353,20 @@ func (c *Container) Wait() (syscall.WaitStatus, error) {
return c.Sandbox.Wait(c.ID)
}
+// WaitRootPID waits for process 'pid' in the sandbox's PID namespace and
+// returns its WaitStatus.
+func (c *Container) WaitRootPID(pid int32) (syscall.WaitStatus, error) {
+ log.Debugf("Wait on pid %d in sandbox %q", pid, c.Sandbox.ID)
+ return c.Sandbox.WaitPID(pid, c.Sandbox.ID)
+}
+
+// WaitPID waits for process 'pid' in the container's PID namespace and returns
+// its WaitStatus.
+func (c *Container) WaitPID(pid int32) (syscall.WaitStatus, error) {
+ log.Debugf("Wait on pid %d in container %q", pid, c.ID)
+ return c.Sandbox.WaitPID(pid, c.ID)
+}
+
// Signal sends the signal to the container.
func (c *Container) Signal(sig syscall.Signal) error {
log.Debugf("Signal container %q", c.ID)
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 11285a123..ae500e7d0 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -75,9 +75,9 @@ func procListsEqual(got, want []*control.Process) bool {
pd1 := got[i]
pd2 := want[i]
// Zero out unimplemented and timing dependant fields.
- pd1.Time, pd2.Time = "", ""
- pd1.STime, pd2.STime = "", ""
- pd1.C, pd2.C = 0, 0
+ pd1.Time = ""
+ pd1.STime = ""
+ pd1.C = 0
if *pd1 != *pd2 {
return false
}
@@ -1074,16 +1074,39 @@ func TestMultiContainerWait(t *testing.T) {
t.Errorf("failed to wait for sleep to start: %v", err)
}
- // Wait on the short lived container.
- if ws, err := containers[1].Wait(); err != nil {
- t.Fatalf("failed to wait for process %q: %v", strings.Join(containers[1].Spec.Process.Args, " "), err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Fatalf("process %q exited with non-zero status %d", strings.Join(containers[1].Spec.Process.Args, " "), es)
- }
+ // Wait on the short lived container from multiple goroutines.
+ wg := sync.WaitGroup{}
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if ws, err := containers[1].Wait(); err != nil {
+ t.Errorf("failed to wait for process %q: %v", strings.Join(containers[1].Spec.Process.Args, " "), err)
+ } else if es := ws.ExitStatus(); es != 0 {
+ t.Errorf("process %q exited with non-zero status %d", strings.Join(containers[1].Spec.Process.Args, " "), es)
+ }
- // After Wait returns, ensure that the root container is running and
- // the child has finished.
- if err := waitForProcessList(containers[0], expectedPL[:1]); err != nil {
- t.Errorf("failed to wait for %q to start: %v", strings.Join(containers[0].Spec.Process.Args, " "), err)
+ // After Wait returns, ensure that the root container is running and
+ // the child has finished.
+ if err := waitForProcessList(containers[0], expectedPL[:1]); err != nil {
+ t.Errorf("failed to wait for %q to start: %v", strings.Join(containers[0].Spec.Process.Args, " "), err)
+ }
+ }()
+ }
+
+ // Also wait via PID.
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ const pid = 2
+ if ws, err := containers[0].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)
+ }
+ }()
}
+
+ wg.Wait()
}
diff --git a/runsc/main.go b/runsc/main.go
index 563ef8c67..dfb338b0f 100644
--- a/runsc/main.go
+++ b/runsc/main.go
@@ -84,6 +84,7 @@ func main() {
subcommands.Register(new(cmd.Run), "")
subcommands.Register(new(cmd.Start), "")
subcommands.Register(new(cmd.State), "")
+ subcommands.Register(new(cmd.Wait), "")
// Register internal commands with the internal group name. This causes
// them to be sorted below the user-facing commands with empty group.
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index e1e7b39d1..9200fbee9 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -432,7 +432,29 @@ func (s *Sandbox) Wait(cid string) (syscall.WaitStatus, error) {
defer conn.Close()
if err := conn.Call(boot.ContainerWait, &cid, &ws); err != nil {
- return ws, fmt.Errorf("err waiting on container %q: %v", cid, err)
+ return ws, fmt.Errorf("error waiting on container %q: %v", cid, err)
+ }
+ return ws, nil
+}
+
+// WaitPID waits for process 'pid' in the container's sandbox and returns its
+// WaitStatus.
+func (s *Sandbox) WaitPID(pid int32, cid string) (syscall.WaitStatus, error) {
+ log.Debugf("Waiting for PID %d in sandbox %q", pid, s.ID)
+ var ws syscall.WaitStatus
+ conn, err := s.connect()
+ if err != nil {
+ return ws, err
+ }
+ defer conn.Close()
+
+ args := &boot.WaitPIDArgs{
+ PID: pid,
+ CID: cid,
+ }
+
+ if err := conn.Call(boot.ContainerWaitPID, args, &ws); err != nil {
+ return ws, fmt.Errorf("error waiting on PID %d in sandbox %q: %v", pid, s.ID, err)
}
if s.IsRootContainer(cid) {