mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
Shentubot
parent
5a8e014c3d
commit
16d37973eb
@@ -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
@@ -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.
|
||||
|
||||
@@ -25,6 +25,7 @@ go_library(
|
||||
"run.go",
|
||||
"start.go",
|
||||
"state.go",
|
||||
"wait.go",
|
||||
],
|
||||
importpath = "gvisor.googlesource.com/gvisor/runsc/cmd",
|
||||
visibility = [
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user