diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index af5cbd539..9ff476ba1 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -18,7 +18,6 @@ package containers import ( "context" - "errors" "fmt" "os" "strings" @@ -32,6 +31,7 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/typeurl" + "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -309,7 +309,7 @@ var checkpointCommand = cli.Command{ if id == "" { return errors.New("container id must be provided") } - ref := context.Args()[1] + ref := context.Args().Get(1) if ref == "" { return errors.New("ref must be provided") } @@ -346,7 +346,11 @@ var checkpointCommand = cli.Command{ if err := task.Pause(ctx); err != nil { return err } - defer task.Resume(ctx) + defer func() { + if err := task.Resume(ctx); err != nil { + fmt.Println(errors.Wrap(err, "error resuming task")) + } + }() } if _, err := container.Checkpoint(ctx, ref, opts...); err != nil { @@ -362,21 +366,21 @@ var restoreCommand = cli.Command{ Usage: "restore a container from checkpoint", ArgsUsage: "CONTAINER REF", Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "live", - Usage: "restore the runtime and memory data from the checkpoint", - }, cli.BoolFlag{ Name: "rw", Usage: "restore the rw layer from the checkpoint", }, + cli.BoolFlag{ + Name: "live", + Usage: "restore the runtime and memory data from the checkpoint", + }, }, Action: func(context *cli.Context) error { id := context.Args().First() if id == "" { return errors.New("container id must be provided") } - ref := context.Args()[1] + ref := context.Args().Get(1) if ref == "" { return errors.New("ref must be provided") } @@ -391,6 +395,7 @@ var restoreCommand = cli.Command{ if !errdefs.IsNotFound(err) { return err } + // TODO (ehazlett): consider other options (always/never fetch) ck, err := client.Fetch(ctx, ref) if err != nil { return err @@ -401,7 +406,6 @@ var restoreCommand = cli.Command{ opts := []containerd.RestoreOpts{ containerd.WithRestoreImage, containerd.WithRestoreSpec, - containerd.WithRestoreSnapshot, containerd.WithRestoreRuntime, } if context.Bool("rw") { diff --git a/container.go b/container.go index 39993faae..26247592b 100644 --- a/container.go +++ b/container.go @@ -39,8 +39,9 @@ import ( ) const ( - checkpointImageNameLabel = "image.name" - checkpointRuntimeNameLabel = "runtime.name" + checkpointImageNameLabel = "org.opencontainers.image.ref.name" + checkpointRuntimeNameLabel = "io.containerd.checkpoint.runtime" + checkpointSnapshotterNameLabel = "io.containerd.checkpoint.snapshotter" ) // Container is a metadata object for container resources and task creation @@ -318,6 +319,8 @@ func (c *container) Checkpoint(ctx context.Context, ref string, opts ...Checkpoi index.Annotations[checkpointImageNameLabel] = img.Name() // add runtime info to index index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name + // add snapshotter info to index + index.Annotations[checkpointSnapshotterNameLabel] = info.Snapshotter // process remaining opts for _, o := range opts { diff --git a/container_checkpoint_opts.go b/container_checkpoint_opts.go index f336fb219..7d261421e 100644 --- a/container_checkpoint_opts.go +++ b/container_checkpoint_opts.go @@ -26,6 +26,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/diff" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/rootfs" "github.com/containerd/containerd/runtime/v2/runc/options" "github.com/containerd/typeurl" @@ -67,16 +68,29 @@ func WithCheckpointTask(ctx context.Context, client *Client, c *containers.Conta return err } for _, d := range task.Descriptors { + platformSpec := platforms.DefaultSpec() index.Manifests = append(index.Manifests, imagespec.Descriptor{ MediaType: d.MediaType, Size: d.Size_, Digest: d.Digest, - Platform: &imagespec.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - }, + Platform: &platformSpec, }) } + // save copts + data, err := any.Marshal() + if err != nil { + return err + } + r := bytes.NewReader(data) + desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointOptions, c.ID+"-checkpoint-options", r) + if err != nil { + return err + } + desc.Platform = &imagespec.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + } + index.Manifests = append(index.Manifests, desc) return nil } diff --git a/container_checkpoint_test.go b/container_checkpoint_test.go index c4f0d7f37..a120ce4c4 100644 --- a/container_checkpoint_test.go +++ b/container_checkpoint_test.go @@ -128,7 +128,6 @@ func TestCheckpointRestorePTY(t *testing.T) { if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{ WithRestoreImage, WithRestoreSpec, - WithRestoreSnapshot, WithRestoreRuntime, WithRestoreRW, }...); err != nil { @@ -227,7 +226,6 @@ func TestCheckpointRestore(t *testing.T) { if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{ WithRestoreImage, WithRestoreSpec, - WithRestoreSnapshot, WithRestoreRuntime, WithRestoreRW, }...); err != nil { @@ -315,7 +313,6 @@ func TestCheckpointRestoreNewContainer(t *testing.T) { if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{ WithRestoreImage, WithRestoreSpec, - WithRestoreSnapshot, WithRestoreRuntime, WithRestoreRW, }...); err != nil { diff --git a/container_restore_opts.go b/container_restore_opts.go index bfb6effb0..4f251c4a6 100644 --- a/container_restore_opts.go +++ b/container_restore_opts.go @@ -33,8 +33,10 @@ import ( var ( // ErrImageNameNotFoundInIndex is returned when the image name is not found in the index ErrImageNameNotFoundInIndex = errors.New("image name not found in index") - // ErrRuntimeNameNotFoundInIndex is returned when the runtime name is not found in the index - ErrRuntimeNameNotFoundInIndex = errors.New("runtime name not found in index") + // ErrRuntimeNameNotFoundInIndex is returned when the runtime is not found in the index + ErrRuntimeNameNotFoundInIndex = errors.New("runtime not found in index") + // ErrSnapshotterNameNotFoundInIndex is returned when the snapshotter is not found in the index + ErrSnapshotterNameNotFoundInIndex = errors.New("snapshotter not found in index") ) // RestoreOpts are options to manage the restore operation @@ -47,12 +49,26 @@ func WithRestoreImage(ctx context.Context, id string, client *Client, checkpoint if !ok || name == "" { return ErrRuntimeNameNotFoundInIndex } + snapshotter, ok := index.Annotations[checkpointSnapshotterNameLabel] + if !ok || name == "" { + return ErrSnapshotterNameNotFoundInIndex + } i, err := client.GetImage(ctx, name) if err != nil { return err } + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) + if err != nil { + return err + } + parent := identity.ChainID(diffIDs).String() + if _, err := client.SnapshotService(snapshotter).Prepare(ctx, id, parent); err != nil { + return err + } c.Image = i.Name() + c.SnapshotKey = id + c.Snapshotter = snapshotter return nil } } @@ -113,31 +129,6 @@ func WithRestoreSpec(ctx context.Context, id string, client *Client, checkpoint } } -// WithRestoreSnapshot restores the snapshot from the checkpoint for the container -func WithRestoreSnapshot(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - imageName, ok := index.Annotations[checkpointImageNameLabel] - if !ok || imageName == "" { - return ErrRuntimeNameNotFoundInIndex - } - i, err := client.GetImage(ctx, imageName) - if err != nil { - return err - } - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) - if err != nil { - return err - } - parent := identity.ChainID(diffIDs).String() - if _, err := client.SnapshotService(DefaultSnapshotter).Prepare(ctx, id, parent); err != nil { - return err - } - c.SnapshotKey = id - c.Snapshotter = DefaultSnapshotter - return nil - } -} - // WithRestoreRW restores the rw layer from the checkpoint for the container func WithRestoreRW(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { diff --git a/images/mediatypes.go b/images/mediatypes.go index 005a2720b..186a3b673 100644 --- a/images/mediatypes.go +++ b/images/mediatypes.go @@ -34,7 +34,7 @@ const ( MediaTypeContainerd1Resource = "application/vnd.containerd.container.resource.tar" MediaTypeContainerd1RW = "application/vnd.containerd.container.rw.tar" MediaTypeContainerd1CheckpointConfig = "application/vnd.containerd.container.checkpoint.config.v1+proto" - MediaTypeContainerd1CheckpointImageName = "application/vnd.containerd.container.checkpoint.image.name" + MediaTypeContainerd1CheckpointOptions = "application/vnd.containerd.container.checkpoint.options.v1+proto" MediaTypeContainerd1CheckpointRuntimeName = "application/vnd.containerd.container.checkpoint.runtime.name" MediaTypeContainerd1CheckpointRuntimeOptions = "application/vnd.containerd.container.checkpoint.runtime.options+proto" // Legacy Docker schema1 manifest