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
This commit is contained in:
Kevin Krakauer
2018-06-28 14:56:36 -07:00
committed by Shentubot
parent 5a8e014c3d
commit 16d37973eb
8 changed files with 237 additions and 27 deletions
+20 -1
View File
@@ -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.
+40 -13
View File
@@ -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 {
l.mu.Lock()
defer l.mu.Unlock()
tgid, ok := l.containerRootTGIDs[*cid]
if !ok {
return fmt.Errorf("can't find process for container %q in %v", *cid, l.containerRootTGIDs)
}
// TODO: Per-container namespaces must be supported
// for -pid.
// TODO: Containers don't map 1:1 with their root
// processes. Container exits should be managed explicitly
// rather than via 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()
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)
}
// 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)
}
tg := l.k.TaskSet().Root.ThreadGroupWithID(tgid)
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)
}
// 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.
+1
View File
@@ -25,6 +25,7 @@ go_library(
"run.go",
"start.go",
"state.go",
"wait.go",
],
importpath = "gvisor.googlesource.com/gvisor/runsc/cmd",
visibility = [
+103
View File
@@ -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
}
+14
View File
@@ -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)
+35 -12
View File
@@ -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()
}
+1
View File
@@ -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.
+23 -1
View File
@@ -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) {