diff options
author | Brielle Broder <bbroder@google.com> | 2018-06-12 13:24:22 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-06-12 13:25:23 -0700 |
commit | 711a9869e54743b05fc3478be5adce31d45cefe5 (patch) | |
tree | 7e0b61d5b8a075f96dc868a7c548252b231101ed | |
parent | 7a10df454b1c12b207f479cdda7338fff2875d5f (diff) |
Runsc checkpoint works.
This is the first iteration of checkpoint that actually saves to a file.
Tests for checkpoint are included.
Ran into an issue when private unix sockets are enabled. An error message
was added for this case and the mutex state was set.
PiperOrigin-RevId: 200269470
Change-Id: I28d29a9f92c44bf73dc4a4b12ae0509ee4070e93
-rw-r--r-- | pkg/sentry/fs/gofer/session.go | 5 | ||||
-rw-r--r-- | pkg/sentry/fs/gofer/session_state.go | 9 | ||||
-rw-r--r-- | runsc/boot/loader.go | 4 | ||||
-rw-r--r-- | runsc/cmd/checkpoint.go | 17 | ||||
-rw-r--r-- | runsc/container/container.go | 5 | ||||
-rw-r--r-- | runsc/container/container_test.go | 53 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 11 |
7 files changed, 95 insertions, 9 deletions
diff --git a/pkg/sentry/fs/gofer/session.go b/pkg/sentry/fs/gofer/session.go index 1076e3e55..baf00d8e7 100644 --- a/pkg/sentry/fs/gofer/session.go +++ b/pkg/sentry/fs/gofer/session.go @@ -28,8 +28,9 @@ import ( ) type endpointMap struct { - mu sync.RWMutex - m map[device.MultiDeviceKey]unix.BoundEndpoint + mu sync.RWMutex `state:"nosave"` + // TODO: Make map with private unix sockets savable. + m map[device.MultiDeviceKey]unix.BoundEndpoint } // add adds the endpoint to the map. diff --git a/pkg/sentry/fs/gofer/session_state.go b/pkg/sentry/fs/gofer/session_state.go index 4d993a219..0154810c8 100644 --- a/pkg/sentry/fs/gofer/session_state.go +++ b/pkg/sentry/fs/gofer/session_state.go @@ -22,6 +22,15 @@ import ( "gvisor.googlesource.com/gvisor/pkg/unet" ) +// beforeSave is invoked by stateify. +// +// TODO: Make map with private unix sockets savable. +func (e *endpointMap) beforeSave() { + if len(e.m) != 0 { + panic("EndpointMap with existing private unix sockets cannot be saved") + } +} + // afterLoad is invoked by stateify. func (s *session) afterLoad() { // The restore environment contains the 9p connection of this mount. diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 41d1ee50d..4a6528307 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -100,7 +100,9 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console } // Create VDSO. - vdso, err := loader.PrepareVDSO(p) + // + // Pass k as the platform since it is savable, unlike the actual platform. + vdso, err := loader.PrepareVDSO(k) if err != nil { return nil, fmt.Errorf("error creating vdso: %v", err) } diff --git a/runsc/cmd/checkpoint.go b/runsc/cmd/checkpoint.go index 9b045da1c..927027c2b 100644 --- a/runsc/cmd/checkpoint.go +++ b/runsc/cmd/checkpoint.go @@ -15,6 +15,8 @@ package cmd import ( + "os" + "context" "flag" "github.com/google/subcommands" @@ -24,6 +26,7 @@ import ( // Checkpoint implements subcommands.Command for the "checkpoint" command. type Checkpoint struct { + imagePath string } // Name implements subcommands.Command.Name. @@ -44,6 +47,7 @@ func (*Checkpoint) Usage() string { // SetFlags implements subcommands.Command.SetFlags. func (c *Checkpoint) SetFlags(f *flag.FlagSet) { + f.StringVar(&c.imagePath, "image-path", "", "path to saved container image") } // Execute implements subcommands.Command.Execute. @@ -62,7 +66,18 @@ func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...interfa Fatalf("error loading container: %v", err) } - if err := cont.Checkpoint(); err != nil { + if c.imagePath == "" { + Fatalf("image-path flag must be provided") + } + + // Create the image file and open for writing. + file, err := os.OpenFile(c.imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) + if err != nil { + Fatalf("os.OpenFile(%q) failed: %v", c.imagePath, err) + } + defer file.Close() + + if err := cont.Checkpoint(file); err != nil { Fatalf("checkpoint failed: %v", err) } diff --git a/runsc/container/container.go b/runsc/container/container.go index 66a2f27a1..d323388fb 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -339,13 +339,14 @@ func (c *Container) Signal(sig syscall.Signal) error { } // Checkpoint sends the checkpoint call to the container. -func (c *Container) Checkpoint() error { +// The statefile will be written to f, the file at the specified image-path. +func (c *Container) Checkpoint(f *os.File) error { log.Debugf("Checkpoint container %q", c.ID) if c.Status == Stopped { log.Warningf("container %q not running, not checkpointing", c.ID) return nil } - return c.Sandbox.Checkpoint(c.ID) + return c.Sandbox.Checkpoint(c.ID, f) } // State returns the metadata of the container. diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index 43cd177ce..b6d19bf33 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -408,6 +408,57 @@ func TestExec(t *testing.T) { } } +// TestCheckpoint verifies that calling checkpoint with an image-path flag succeeds. +// Since there is no current default image path, confirming that calling +// checkpoint without an image path fails. +// Checks that there is a file with the name and location given by image path. +func TestCheckpoint(t *testing.T) { + // Container will succeed. + spec := testutil.NewSpecWithArgs("sleep", "100") + + 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) + } + + // Set the image path, which is where the checkpoint image will be saved. + imagePath := filepath.Join(os.TempDir(), "test-image-file") + + // Create the image file and open for writing. + file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) + if err != nil { + t.Fatalf("error opening new file at imagePath: %v", err) + } + defer file.Close() + + // Checkpoint running container; save state into new file. + if err := cont.Checkpoint(file); err != nil { + t.Fatalf("error checkpointing container to empty file: %v", err) + } + defer os.RemoveAll(imagePath) + + // Check to see if file exists and contains data. + fileInfo, err := os.Stat(imagePath) + if err != nil { + t.Fatalf("error checkpointing container: %v", err) + } + if size := fileInfo.Size(); size == 0 { + t.Fatalf("failed checkpoint, file still appears empty: %v", err) + } +} + // TestCapabilities verifies that: // - Running exec as non-root UID and GID will result in an error (because the // executable file can't be read). @@ -602,7 +653,7 @@ func TestSpecUnsupported(t *testing.T) { } // TestRunNonRoot checks that sandbox can be configured when running as -// non-priviledged user. +// non-privileged user. func TestRunNonRoot(t *testing.T) { spec := testutil.NewSpecWithArgs("/bin/true") spec.Process.User.UID = 343 diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 48388aa7f..c1efab7f5 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -441,7 +441,8 @@ func (s *Sandbox) Signal(cid string, sig syscall.Signal) error { } // Checkpoint sends the checkpoint call for a container in the sandbox. -func (s *Sandbox) Checkpoint(cid string) error { +// The statefile will be written to f. +func (s *Sandbox) Checkpoint(cid string, f *os.File) error { log.Debugf("Checkpoint sandbox %q", s.ID) conn, err := s.connect() if err != nil { @@ -449,7 +450,13 @@ func (s *Sandbox) Checkpoint(cid string) error { } defer conn.Close() - if err := conn.Call(boot.ContainerCheckpoint, nil, nil); err != nil { + opt := control.SaveOpts{ + FilePayload: urpc.FilePayload{ + Files: []*os.File{f}, + }, + } + + if err := conn.Call(boot.ContainerCheckpoint, &opt, nil); err != nil { return fmt.Errorf("err checkpointing container %q: %v", cid, err) } return nil |