diff options
author | Kevin Krakauer <krakauer@google.com> | 2018-10-17 10:50:24 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-10-17 10:51:39 -0700 |
commit | 9b3550f70bf1612e2c474b3826b0347b21503401 (patch) | |
tree | e9c2e0b2b354f57f2a84b85733d35d99abc5a4da /runsc/container | |
parent | 9d17eba121dab054c21307b9696ba7471dff4a74 (diff) |
runsc: Add --pid flag to runsc kill.
--pid allows specific processes to be signalled rather than the container root
process or all processes in the container. containerd needs to SIGKILL exec'd
processes that timeout and check whether processes are still alive.
PiperOrigin-RevId: 217547636
Change-Id: I2058ebb548b51c8eb748f5884fb88bad0b532e45
Diffstat (limited to 'runsc/container')
-rw-r--r-- | runsc/container/container.go | 32 | ||||
-rw-r--r-- | runsc/container/container_test.go | 58 | ||||
-rw-r--r-- | runsc/container/multi_container_test.go | 12 | ||||
-rw-r--r-- | runsc/container/test_app.go | 10 |
4 files changed, 94 insertions, 18 deletions
diff --git a/runsc/container/container.go b/runsc/container/container.go index 774cb6e07..0ec4d03c1 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -174,7 +174,7 @@ func Load(rootDir, id string) (*Container, error) { } else if c.Status == Running { // Container state should reflect the actual state of the application, so // we don't consider gofer process here. - if err := c.Signal(syscall.Signal(0), false); err != nil { + if err := c.SignalContainer(syscall.Signal(0), false); err != nil { c.changeStatus(Stopped) } } @@ -445,7 +445,7 @@ func (c *Container) SandboxPid() int { func (c *Container) Wait() (syscall.WaitStatus, error) { log.Debugf("Wait on container %q", c.ID) if !c.isSandboxRunning() { - return 0, fmt.Errorf("container is not running") + return 0, fmt.Errorf("sandbox is not running") } return c.Sandbox.Wait(c.ID) } @@ -455,7 +455,7 @@ func (c *Container) Wait() (syscall.WaitStatus, error) { func (c *Container) WaitRootPID(pid int32, clearStatus bool) (syscall.WaitStatus, error) { log.Debugf("Wait on PID %d in sandbox %q", pid, c.Sandbox.ID) if !c.isSandboxRunning() { - return 0, fmt.Errorf("container is not running") + return 0, fmt.Errorf("sandbox is not running") } return c.Sandbox.WaitPID(c.Sandbox.ID, pid, clearStatus) } @@ -465,16 +465,16 @@ func (c *Container) WaitRootPID(pid int32, clearStatus bool) (syscall.WaitStatus func (c *Container) WaitPID(pid int32, clearStatus bool) (syscall.WaitStatus, error) { log.Debugf("Wait on PID %d in container %q", pid, c.ID) if !c.isSandboxRunning() { - return 0, fmt.Errorf("container is not running") + return 0, fmt.Errorf("sandbox is not running") } return c.Sandbox.WaitPID(c.ID, pid, clearStatus) } -// Signal sends the signal to the container. If all is true and signal is -// SIGKILL, then waits for all processes to exit before returning. -// Signal returns an error if the container is already stopped. +// SignalContainer sends the signal to the container. If all is true and signal +// is SIGKILL, then waits for all processes to exit before returning. +// SignalContainer returns an error if the container is already stopped. // TODO: Distinguish different error types. -func (c *Container) Signal(sig syscall.Signal, all bool) error { +func (c *Container) SignalContainer(sig syscall.Signal, all bool) error { log.Debugf("Signal container %q: %v", c.ID, sig) // Signaling container in Stopped state is allowed. When all=false, // an error will be returned anyway; when all=true, this allows @@ -485,11 +485,23 @@ func (c *Container) Signal(sig syscall.Signal, all bool) error { return err } if !c.isSandboxRunning() { - return fmt.Errorf("container is not running") + return fmt.Errorf("sandbox is not running") } return c.Sandbox.SignalContainer(c.ID, sig, all) } +// SignalProcess sends sig to a specific process in the container. +func (c *Container) SignalProcess(sig syscall.Signal, pid int32) error { + log.Debugf("Signal process %d in container %q: %v", pid, c.ID, sig) + if err := c.requireStatus("signal a process inside", Running); err != nil { + return err + } + if !c.isSandboxRunning() { + return fmt.Errorf("sandbox is not running") + } + return c.Sandbox.SignalProcess(c.ID, int32(pid), sig, false) +} + // ForwardSignals forwards all signals received by the current process to the // container process inside the sandbox. It returns a function that will stop // forwarding signals. @@ -663,7 +675,7 @@ func (c *Container) waitForStopped() error { b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx) op := func() error { if c.isSandboxRunning() { - if err := c.Signal(syscall.Signal(0), false); err == nil { + if err := c.SignalContainer(syscall.Signal(0), false); err == nil { return fmt.Errorf("container is still running") } } diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index 94572667e..d9cd38c0a 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -354,7 +354,7 @@ func TestLifecycle(t *testing.T) { <-ch time.Sleep(100 * time.Millisecond) // Send the container a SIGTERM which will cause it to stop. - if err := c.Signal(syscall.SIGTERM, false); err != nil { + if err := c.SignalContainer(syscall.SIGTERM, false); err != nil { t.Fatalf("error sending signal %v to container: %v", syscall.SIGTERM, err) } // Wait for it to die. @@ -559,6 +559,62 @@ func TestExec(t *testing.T) { } } +// TestKillPid verifies that we can signal individual exec'd processes. +func TestKillPid(t *testing.T) { + for _, conf := range configs(overlay) { + t.Logf("Running test with conf: %+v", conf) + + app, err := testutil.FindFile("runsc/container/test_app") + if err != nil { + t.Fatal("error finding test_app:", err) + } + + const nProcs = 4 + spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true") + rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) + if err != nil { + t.Fatalf("error setting up container: %v", err) + } + defer os.RemoveAll(rootDir) + defer os.RemoveAll(bundleDir) + + // Create and start the container. + cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "") + if err != nil { + t.Fatalf("error creating container: %v", err) + } + defer cont.Destroy() + if err := cont.Start(conf); err != nil { + t.Fatalf("error starting container: %v", err) + } + + // Verify that all processes are running. + if err := waitForProcessCount(cont, nProcs); err != nil { + t.Fatalf("timed out waiting for processes to start: %v", err) + } + + // Kill the child process with the largest PID. + procs, err := cont.Processes() + if err != nil { + t.Fatalf("failed to get process list: %v", err) + } + var pid int32 + for _, p := range procs { + if pid < int32(p.PID) { + pid = int32(p.PID) + } + } + if err := cont.SignalProcess(syscall.SIGKILL, pid); err != nil { + t.Fatalf("failed to signal process %d: %v", pid, err) + } + + // Verify that one process is gone. + if err := waitForProcessCount(cont, nProcs-1); err != nil { + t.Fatal(err) + } + } +} + // TestCheckpointRestore creates a container that continuously writes successive integers // to a file. To test checkpoint and restore functionality, the container is // checkpointed and the last number printed to the file is recorded. Then, it is restored in two diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go index 77f8da8b0..1781a4602 100644 --- a/runsc/container/multi_container_test.go +++ b/runsc/container/multi_container_test.go @@ -335,7 +335,7 @@ func TestMultiContainerSignal(t *testing.T) { } // Kill process 2. - if err := containers[1].Signal(syscall.SIGKILL, false); err != nil { + if err := containers[1].SignalContainer(syscall.SIGKILL, false); err != nil { t.Errorf("failed to kill process 2: %v", err) } @@ -368,12 +368,12 @@ func TestMultiContainerSignal(t *testing.T) { // Now that process 2 is gone, ensure we get an error trying to // signal it again. - if err := containers[1].Signal(syscall.SIGKILL, false); err == nil { + if err := containers[1].SignalContainer(syscall.SIGKILL, false); err == nil { t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID) } // Kill process 1. - if err := containers[0].Signal(syscall.SIGKILL, false); err != nil { + if err := containers[0].SignalContainer(syscall.SIGKILL, false); err != nil { t.Errorf("failed to kill process 1: %v", err) } @@ -395,7 +395,7 @@ func TestMultiContainerSignal(t *testing.T) { } // The sentry should be gone, so signaling should yield an error. - if err := containers[0].Signal(syscall.SIGKILL, false); err == nil { + if err := containers[0].SignalContainer(syscall.SIGKILL, false); err == nil { t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID) } } @@ -577,7 +577,7 @@ func TestMultiContainerKillAll(t *testing.T) { if tc.killContainer { // First kill the init process to make the container be stopped with // processes still running inside. - containers[1].Signal(syscall.SIGKILL, false) + containers[1].SignalContainer(syscall.SIGKILL, false) op := func() error { c, err := Load(conf.RootDir, ids[1]) if err != nil { @@ -598,7 +598,7 @@ func TestMultiContainerKillAll(t *testing.T) { t.Fatalf("failed to load child container %q: %v", c.ID, err) } // Kill'Em All - if err := c.Signal(syscall.SIGKILL, true); err != nil { + if err := c.SignalContainer(syscall.SIGKILL, true); err != nil { t.Fatalf("failed to send SIGKILL to container %q: %v", c.ID, err) } diff --git a/runsc/container/test_app.go b/runsc/container/test_app.go index 9e4b5326d..cc3b087e1 100644 --- a/runsc/container/test_app.go +++ b/runsc/container/test_app.go @@ -125,6 +125,7 @@ func server(listener net.Listener, out *os.File) { type taskTree struct { depth int width int + pause bool } // Name implements subcommands.Command. @@ -146,6 +147,7 @@ func (*taskTree) Usage() string { func (c *taskTree) SetFlags(f *flag.FlagSet) { f.IntVar(&c.depth, "depth", 1, "number of levels to create") f.IntVar(&c.width, "width", 1, "number of tasks at each level") + f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually") } // Execute implements subcommands.Command. @@ -164,7 +166,8 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa cmd := exec.Command( "/proc/self/exe", c.Name(), "--depth", strconv.Itoa(c.depth-1), - "--width", strconv.Itoa(c.width)) + "--width", strconv.Itoa(c.width), + "--pause", strconv.FormatBool(c.pause)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -177,6 +180,11 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa for _, c := range cmds { c.Wait() } + + if c.pause { + select {} + } + return subcommands.ExitSuccess } |