summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--pkg/sentry/control/proc.go2
-rw-r--r--runsc/boot/controller.go11
-rw-r--r--runsc/cmd/BUILD1
-rw-r--r--runsc/cmd/resume.go68
-rw-r--r--runsc/container/container.go21
-rw-r--r--runsc/container/container_test.go152
-rw-r--r--runsc/main.go2
-rw-r--r--runsc/sandbox/sandbox.go15
8 files changed, 262 insertions, 10 deletions
diff --git a/pkg/sentry/control/proc.go b/pkg/sentry/control/proc.go
index d77b30c90..d94ae560f 100644
--- a/pkg/sentry/control/proc.go
+++ b/pkg/sentry/control/proc.go
@@ -18,6 +18,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "sort"
"syscall"
"text/tabwriter"
"time"
@@ -245,6 +246,7 @@ func Processes(k *kernel.Kernel, out *[]*Process) error {
Cmd: tg.Leader().Name(),
})
}
+ sort.Slice(*out, func(i, j int) bool { return (*out)[i].PID < (*out)[j].PID })
return nil
}
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index 564f2d271..ae727f144 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -44,6 +44,9 @@ const (
// processes running in a container.
ContainerProcesses = "containerManager.Processes"
+ // ContainerResume unpauses the paused container.
+ ContainerResume = "containerManager.Resume"
+
// ContainerSignal is used to send a signal to a container.
ContainerSignal = "containerManager.Signal"
@@ -156,12 +159,18 @@ func (cm *containerManager) Checkpoint(o *control.SaveOpts, _ *struct{}) error {
return state.Save(o, nil)
}
-// Pause suspends the process in a container.
+// Pause suspends a container.
func (cm *containerManager) Pause(_, _ *struct{}) error {
cm.k.Pause()
return nil
}
+// Resume unpauses a container.
+func (cm *containerManager) Resume(_, _ *struct{}) error {
+ cm.k.Unpause()
+ return nil
+}
+
// Wait waits for the init process in the given container.
func (cm *containerManager) Wait(cid *string, waitStatus *uint32) error {
// TODO: Use the cid and wait on the init process in that
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index 8fbce294f..fffb6f359 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -20,6 +20,7 @@ go_library(
"pause.go",
"ps.go",
"restore.go",
+ "resume.go",
"run.go",
"start.go",
"state.go",
diff --git a/runsc/cmd/resume.go b/runsc/cmd/resume.go
new file mode 100644
index 000000000..a12adf1a3
--- /dev/null
+++ b/runsc/cmd/resume.go
@@ -0,0 +1,68 @@
+// 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 (
+ "context"
+ "flag"
+ "github.com/google/subcommands"
+ "gvisor.googlesource.com/gvisor/runsc/boot"
+ "gvisor.googlesource.com/gvisor/runsc/container"
+)
+
+// Resume implements subcommands.Command for the "resume" command.
+type Resume struct{}
+
+// Name implements subcommands.Command.Name.
+func (*Resume) Name() string {
+ return "resume"
+}
+
+// Synopsis implements subcommands.Command.Synopsis.
+func (*Resume) Synopsis() string {
+ return "Resume unpauses a paused container"
+}
+
+// Usage implements subcommands.Command.Usage.
+func (*Resume) Usage() string {
+ return `resume <container id> - resume a paused container.
+`
+}
+
+// SetFlags implements subcommands.Command.SetFlags.
+func (r *Resume) SetFlags(f *flag.FlagSet) {
+}
+
+// Execute implements subcommands.Command.Execute.
+func (r *Resume) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+ if f.NArg() != 1 {
+ f.Usage()
+ return subcommands.ExitUsageError
+ }
+
+ id := f.Arg(0)
+ conf := args[0].(*boot.Config)
+
+ cont, err := container.Load(conf.RootDir, id)
+ if err != nil {
+ Fatalf("error loading container: %v", err)
+ }
+
+ if err := cont.Resume(); err != nil {
+ Fatalf("resume failed: %v", err)
+ }
+
+ return subcommands.ExitSuccess
+}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index dc7fccdee..571784e07 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -361,8 +361,23 @@ func (c *Container) Pause() error {
c.Status = Paused
return c.save()
default:
- log.Warningf("container %q not created or running, not pausing", c.ID)
- return nil
+ return fmt.Errorf("container %q not created or running, not pausing", c.ID)
+ }
+}
+
+// Resume unpauses the container and its kernel.
+// The call only succeeds if the container's status is paused.
+func (c *Container) Resume() error {
+ log.Debugf("Resuming container %q", c.ID)
+ switch c.Status {
+ case Paused:
+ if err := c.Sandbox.Resume(c.ID); err != nil {
+ return fmt.Errorf("error resuming container: %v", err)
+ }
+ c.Status = Running
+ return c.save()
+ default:
+ return fmt.Errorf("container %q not paused, not resuming", c.ID)
}
}
@@ -380,7 +395,7 @@ func (c *Container) State() specs.State {
// Processes retrieves the list of processes and associated metadata inside a
// container.
func (c *Container) Processes() ([]*control.Process, error) {
- if c.Status != Running {
+ if c.Status != Running && c.Status != Paused {
return nil, fmt.Errorf("cannot get processes of container %q because it isn't running. It is in state %v", c.ID, c.Status)
}
return c.Sandbox.Processes(c.ID)
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 5659abab3..7818990a7 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -84,6 +84,19 @@ func procListsEqual(got, want []*control.Process) bool {
return true
}
+// getAndCheckProcLists is similar to waitForProcessList, but does not wait and retry the
+// test for equality. This is because we already confirmed that exec occurred.
+func getAndCheckProcLists(cont *container.Container, want []*control.Process) error {
+ got, err := cont.Processes()
+ if err != nil {
+ return fmt.Errorf("error getting process data from container: %v", err)
+ }
+ if procListsEqual(got, want) {
+ return nil
+ }
+ return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want))
+}
+
func procListToString(pl []*control.Process) string {
strs := make([]string, 0, len(pl))
for _, p := range pl {
@@ -459,11 +472,14 @@ func TestCheckpoint(t *testing.T) {
}
}
-// TestPause tests that calling pause successfully pauses the container.
-// It checks that no errors are returned and that the state of the container
-// is in fact 'Paused.'
-func TestPause(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "100")
+// TestPauseResume tests that we can successfully pause and resume a container.
+// It checks starts running sleep and executes another sleep. It pauses and checks
+// that both processes are still running: sleep will be paused and still exist.
+// It will then unpause and confirm that both processes are running. Then it will
+// wait until one sleep completes and check to make sure the other is running.
+func TestPauseResume(t *testing.T) {
+ const uid = 343
+ spec := testutil.NewSpecWithArgs("sleep", "20")
rootDir, bundleDir, conf, err := testutil.SetupContainer(spec)
if err != nil {
@@ -482,15 +498,139 @@ func TestPause(t *testing.T) {
t.Fatalf("error starting container: %v", err)
}
+ // expectedPL lists the expected process state of the container.
+ expectedPL := []*control.Process{
+ {
+ UID: 0,
+ PID: 1,
+ PPID: 0,
+ C: 0,
+ Cmd: "sleep",
+ },
+ {
+ UID: uid,
+ PID: 2,
+ PPID: 0,
+ C: 0,
+ Cmd: "sleep",
+ },
+ }
+
+ execArgs := control.ExecArgs{
+ Filename: "/bin/sleep",
+ Argv: []string{"sleep", "5"},
+ Envv: []string{"PATH=" + os.Getenv("PATH")},
+ WorkingDirectory: "/",
+ KUID: uid,
+ }
+
+ // First, start running exec (whick blocks).
+ go cont.Execute(&execArgs)
+
+ // Verify that "sleep 5" is running.
+ if err := waitForProcessList(cont, expectedPL); err != nil {
+ t.Fatal(err)
+ }
+
// Pause the running container.
if err := cont.Pause(); err != nil {
t.Errorf("error pausing container: %v", err)
}
+ if got, want := cont.Status, container.Paused; got != want {
+ t.Errorf("container status got %v, want %v", got, want)
+ }
+
+ time.Sleep(10 * time.Second)
+
+ // Verify that the two processes still exist. Sleep 5 is paused so
+ // it should still be in the process list after 10 seconds.
+ if err := getAndCheckProcLists(cont, expectedPL); err != nil {
+ t.Fatal(err)
+ }
- // Confirm the status of the container is paused.
+ // Resume the running container.
+ if err := cont.Resume(); err != nil {
+ t.Errorf("error pausing container: %v", err)
+ }
+ if got, want := cont.Status, container.Running; got != want {
+ t.Errorf("container status got %v, want %v", got, want)
+ }
+
+ if err := getAndCheckProcLists(cont, expectedPL); err != nil {
+ t.Fatal(err)
+ }
+
+ expectedPL2 := []*control.Process{
+ {
+ UID: 0,
+ PID: 1,
+ PPID: 0,
+ C: 0,
+ Cmd: "sleep",
+ },
+ }
+
+ // Verify there is only one process left since we waited 10 at most seconds for
+ // sleep 5 to end.
+ if err := waitForProcessList(cont, expectedPL2); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// TestPauseResumeStatus makes sure that the statuses are set correctly
+// with calls to pause and resume and that pausing and resuming only
+// occurs given the correct state.
+func TestPauseResumeStatus(t *testing.T) {
+ spec := testutil.NewSpecWithArgs("sleep", "20")
+
+ rootDir, bundleDir, conf, err := testutil.SetupContainer(spec)
+ 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 := container.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)
+ }
+
+ // Pause the running container.
+ if err := cont.Pause(); err != nil {
+ t.Errorf("error pausing container: %v", err)
+ }
if got, want := cont.Status, container.Paused; got != want {
t.Errorf("container status got %v, want %v", got, want)
}
+
+ // Try to Pause again. Should cause error.
+ if err := cont.Pause(); err == nil {
+ t.Errorf("error pausing container that was already paused: %v", err)
+ }
+ if got, want := cont.Status, container.Paused; got != want {
+ t.Errorf("container status got %v, want %v", got, want)
+ }
+
+ // Resume the running container.
+ if err := cont.Resume(); err != nil {
+ t.Errorf("error resuming container: %v", err)
+ }
+ if got, want := cont.Status, container.Running; got != want {
+ t.Errorf("container status got %v, want %v", got, want)
+ }
+
+ // Try to resume again. Should cause error.
+ if err := cont.Resume(); err == nil {
+ t.Errorf("error resuming container already running: %v", err)
+ }
+ if got, want := cont.Status, container.Running; got != want {
+ t.Errorf("container status got %v, want %v", got, want)
+ }
}
// TestCapabilities verifies that:
diff --git a/runsc/main.go b/runsc/main.go
index 42c8ee315..4d69f5803 100644
--- a/runsc/main.go
+++ b/runsc/main.go
@@ -76,7 +76,9 @@ func main() {
subcommands.Register(new(cmd.Gofer), "")
subcommands.Register(new(cmd.Kill), "")
subcommands.Register(new(cmd.List), "")
+ subcommands.Register(new(cmd.Pause), "")
subcommands.Register(new(cmd.PS), "")
+ subcommands.Register(new(cmd.Resume), "")
subcommands.Register(new(cmd.Run), "")
subcommands.Register(new(cmd.Start), "")
subcommands.Register(new(cmd.State), "")
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index b008eba1e..0181dc9d4 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -477,6 +477,21 @@ func (s *Sandbox) Pause(cid string) error {
return nil
}
+// Resume sends the resume call for a container in the sandbox.
+func (s *Sandbox) Resume(cid string) error {
+ log.Debugf("Resume sandbox %q", s.ID)
+ conn, err := s.connect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ if err := conn.Call(boot.ContainerResume, nil, nil); err != nil {
+ return fmt.Errorf("err resuming container %q: %v", cid, err)
+ }
+ return nil
+}
+
// IsRunning returns true if the sandbox or gofer process is running.
func (s *Sandbox) IsRunning() bool {
if s.Pid != 0 {