summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrielle Broder <bbroder@google.com>2018-06-12 13:24:22 -0700
committerShentubot <shentubot@google.com>2018-06-12 13:25:23 -0700
commit711a9869e54743b05fc3478be5adce31d45cefe5 (patch)
tree7e0b61d5b8a075f96dc868a7c548252b231101ed
parent7a10df454b1c12b207f479cdda7338fff2875d5f (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.go5
-rw-r--r--pkg/sentry/fs/gofer/session_state.go9
-rw-r--r--runsc/boot/loader.go4
-rw-r--r--runsc/cmd/checkpoint.go17
-rw-r--r--runsc/container/container.go5
-rw-r--r--runsc/container/container_test.go53
-rw-r--r--runsc/sandbox/sandbox.go11
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