summaryrefslogtreecommitdiffhomepage
path: root/runsc/container
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/container')
-rw-r--r--runsc/container/container.go32
-rw-r--r--runsc/container/container_test.go58
-rw-r--r--runsc/container/multi_container_test.go12
-rw-r--r--runsc/container/test_app.go10
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
}