summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJustine Olshan <justineolshan@google.com>2018-06-19 15:22:23 -0700
committerShentubot <shentubot@google.com>2018-06-19 15:23:36 -0700
commita6dbef045ff684e92f472280eb6f7f688b9bc87a (patch)
tree35aad9e3d975e0c217f12adec007d9a35521fc3d
parentbda2a1ed3503699b8cb814bb3cc7ad0b9694155b (diff)
Added a resume command to unpause a paused container.
Resume checks the status of the container and unpauses the kernel if its status is paused. Otherwise nothing happens. Tests were added to ensure that the process is in the correct state after various commands. PiperOrigin-RevId: 201251234 Change-Id: Ifd11b336c33b654fea6238738f864fcf2bf81e19
-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 {