diff options
-rw-r--r-- | pkg/sentry/control/proc.go | 2 | ||||
-rw-r--r-- | runsc/boot/controller.go | 11 | ||||
-rw-r--r-- | runsc/cmd/BUILD | 1 | ||||
-rw-r--r-- | runsc/cmd/resume.go | 68 | ||||
-rw-r--r-- | runsc/container/container.go | 21 | ||||
-rw-r--r-- | runsc/container/container_test.go | 152 | ||||
-rw-r--r-- | runsc/main.go | 2 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 15 |
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 { |