mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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
This commit is contained in:
committed by
Shentubot
parent
bda2a1ed35
commit
a6dbef045f
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"pause.go",
|
||||
"ps.go",
|
||||
"restore.go",
|
||||
"resume.go",
|
||||
"run.go",
|
||||
"start.go",
|
||||
"state.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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,116 @@ 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -486,11 +604,33 @@ func TestPause(t *testing.T) {
|
||||
if err := cont.Pause(); err != nil {
|
||||
t.Errorf("error pausing container: %v", err)
|
||||
}
|
||||
|
||||
// Confirm the status of the container is paused.
|
||||
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:
|
||||
|
||||
@@ -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), "")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user