diff options
author | Justine Olshan <justineolshan@google.com> | 2018-06-19 15:22:23 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-06-19 15:23:36 -0700 |
commit | a6dbef045ff684e92f472280eb6f7f688b9bc87a (patch) | |
tree | 35aad9e3d975e0c217f12adec007d9a35521fc3d /runsc/container | |
parent | bda2a1ed3503699b8cb814bb3cc7ad0b9694155b (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
Diffstat (limited to 'runsc/container')
-rw-r--r-- | runsc/container/container.go | 21 | ||||
-rw-r--r-- | runsc/container/container_test.go | 152 |
2 files changed, 164 insertions, 9 deletions
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: |