From bd27bef4ad87d703cb5d3e2368fda8db0e47c826 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 May 2019 14:20:19 +0000 Subject: [PATCH 1/2] Move checkpoint and restore commands to new files Signed-off-by: Michael Crosby --- cmd/ctr/commands/containers/checkpoint.go | 102 +++++++++++++++ cmd/ctr/commands/containers/containers.go | 146 ---------------------- cmd/ctr/commands/containers/restore.go | 96 ++++++++++++++ 3 files changed, 198 insertions(+), 146 deletions(-) create mode 100644 cmd/ctr/commands/containers/checkpoint.go create mode 100644 cmd/ctr/commands/containers/restore.go diff --git a/cmd/ctr/commands/containers/checkpoint.go b/cmd/ctr/commands/containers/checkpoint.go new file mode 100644 index 000000000..53bf70b3c --- /dev/null +++ b/cmd/ctr/commands/containers/checkpoint.go @@ -0,0 +1,102 @@ +/* + Copyright The containerd Authors. + + 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 containers + +import ( + "fmt" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/containerd/containerd/errdefs" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var checkpointCommand = cli.Command{ + Name: "checkpoint", + Usage: "checkpoint a container", + ArgsUsage: "CONTAINER REF", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "rw", + Usage: "include the rw layer in the checkpoint", + }, + cli.BoolFlag{ + Name: "image", + Usage: "include the image in the checkpoint", + }, + cli.BoolFlag{ + Name: "task", + Usage: "checkpoint container task", + }, + }, + Action: func(context *cli.Context) error { + id := context.Args().First() + if id == "" { + return errors.New("container id must be provided") + } + ref := context.Args().Get(1) + if ref == "" { + return errors.New("ref must be provided") + } + client, ctx, cancel, err := commands.NewClient(context) + if err != nil { + return err + } + defer cancel() + opts := []containerd.CheckpointOpts{ + containerd.WithCheckpointRuntime, + } + + if context.Bool("image") { + opts = append(opts, containerd.WithCheckpointImage) + } + if context.Bool("rw") { + opts = append(opts, containerd.WithCheckpointRW) + } + if context.Bool("task") { + opts = append(opts, containerd.WithCheckpointTask) + } + container, err := client.LoadContainer(ctx, id) + if err != nil { + return err + } + task, err := container.Task(ctx, nil) + if err != nil { + if !errdefs.IsNotFound(err) { + return err + } + } + // pause if running + if task != nil { + if err := task.Pause(ctx); err != nil { + return err + } + 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 { + return err + } + + return nil + }, +} diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index 91f164c1a..9e40b6aa7 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -28,7 +28,6 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/run" "github.com/containerd/containerd/containers" - "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/typeurl" "github.com/pkg/errors" @@ -285,148 +284,3 @@ var infoCommand = cli.Command{ return nil }, } - -var checkpointCommand = cli.Command{ - Name: "checkpoint", - Usage: "checkpoint a container", - ArgsUsage: "CONTAINER REF", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "rw", - Usage: "include the rw layer in the checkpoint", - }, - cli.BoolFlag{ - Name: "image", - Usage: "include the image in the checkpoint", - }, - cli.BoolFlag{ - Name: "task", - Usage: "checkpoint container task", - }, - }, - Action: func(context *cli.Context) error { - id := context.Args().First() - if id == "" { - return errors.New("container id must be provided") - } - ref := context.Args().Get(1) - if ref == "" { - return errors.New("ref must be provided") - } - client, ctx, cancel, err := commands.NewClient(context) - if err != nil { - return err - } - defer cancel() - opts := []containerd.CheckpointOpts{ - containerd.WithCheckpointRuntime, - } - - if context.Bool("image") { - opts = append(opts, containerd.WithCheckpointImage) - } - if context.Bool("rw") { - opts = append(opts, containerd.WithCheckpointRW) - } - if context.Bool("task") { - opts = append(opts, containerd.WithCheckpointTask) - } - container, err := client.LoadContainer(ctx, id) - if err != nil { - return err - } - task, err := container.Task(ctx, nil) - if err != nil { - if !errdefs.IsNotFound(err) { - return err - } - } - // pause if running - if task != nil { - if err := task.Pause(ctx); err != nil { - return err - } - 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 { - return err - } - - return nil - }, -} - -var restoreCommand = cli.Command{ - Name: "restore", - Usage: "restore a container from checkpoint", - ArgsUsage: "CONTAINER REF", - Flags: []cli.Flag{ - 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().Get(1) - if ref == "" { - return errors.New("ref must be provided") - } - client, ctx, cancel, err := commands.NewClient(context) - if err != nil { - return err - } - defer cancel() - - checkpoint, err := client.GetImage(ctx, ref) - if err != nil { - 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 - } - checkpoint = containerd.NewImage(client, ck) - } - - opts := []containerd.RestoreOpts{ - containerd.WithRestoreImage, - containerd.WithRestoreSpec, - containerd.WithRestoreRuntime, - } - if context.Bool("rw") { - opts = append(opts, containerd.WithRestoreRW) - } - - ctr, err := client.Restore(ctx, id, checkpoint, opts...) - if err != nil { - return err - } - - topts := []containerd.NewTaskOpts{} - if context.Bool("live") { - topts = append(topts, containerd.WithTaskCheckpoint(checkpoint)) - } - - task, err := ctr.NewTask(ctx, cio.NewCreator(cio.WithStdio), topts...) - if err != nil { - return err - } - - return task.Start(ctx) - }, -} diff --git a/cmd/ctr/commands/containers/restore.go b/cmd/ctr/commands/containers/restore.go new file mode 100644 index 000000000..85337b34d --- /dev/null +++ b/cmd/ctr/commands/containers/restore.go @@ -0,0 +1,96 @@ +/* + Copyright The containerd Authors. + + 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 containers + +import ( + "github.com/containerd/containerd" + "github.com/containerd/containerd/cio" + "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/containerd/containerd/errdefs" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var restoreCommand = cli.Command{ + Name: "restore", + Usage: "restore a container from checkpoint", + ArgsUsage: "CONTAINER REF", + Flags: []cli.Flag{ + 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().Get(1) + if ref == "" { + return errors.New("ref must be provided") + } + client, ctx, cancel, err := commands.NewClient(context) + if err != nil { + return err + } + defer cancel() + + checkpoint, err := client.GetImage(ctx, ref) + if err != nil { + 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 + } + checkpoint = containerd.NewImage(client, ck) + } + + opts := []containerd.RestoreOpts{ + containerd.WithRestoreImage, + containerd.WithRestoreSpec, + containerd.WithRestoreRuntime, + } + if context.Bool("rw") { + opts = append(opts, containerd.WithRestoreRW) + } + + ctr, err := client.Restore(ctx, id, checkpoint, opts...) + if err != nil { + return err + } + + topts := []containerd.NewTaskOpts{} + if context.Bool("live") { + topts = append(topts, containerd.WithTaskCheckpoint(checkpoint)) + } + + task, err := ctr.NewTask(ctx, cio.NewCreator(cio.WithStdio), topts...) + if err != nil { + return err + } + + return task.Start(ctx) + }, +} From 67b45aef49425707dce0179d58ccf17dc63b935b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 May 2019 14:32:15 +0000 Subject: [PATCH 2/2] Add WithoutRefreshed metadata Closes #2566 This provides faster lookups and lists for ctr commands. Signed-off-by: Michael Crosby --- cmd/ctr/commands/containers/containers.go | 4 +-- container.go | 30 +++++++++++++++++------ container_opts.go | 14 +++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index 9e40b6aa7..fa0201064 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -124,7 +124,7 @@ var listCommand = cli.Command{ w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t") for _, c := range containers { - info, err := c.Info(ctx) + info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata) if err != nil { return err } @@ -261,7 +261,7 @@ var infoCommand = cli.Command{ if err != nil { return err } - info, err := container.Info(ctx) + info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata) if err != nil { return err } diff --git a/container.go b/container.go index 2073d40b4..9bc43604e 100644 --- a/container.go +++ b/container.go @@ -49,7 +49,7 @@ type Container interface { // ID identifies the container ID() string // Info returns the underlying container record type - Info(context.Context) (containers.Container, error) + Info(context.Context, ...InfoOpts) (containers.Container, error) // Delete removes the container Delete(context.Context, ...DeleteOpts) error // NewTask creates a new task based on the container metadata @@ -80,16 +80,18 @@ type Container interface { func containerFromRecord(client *Client, c containers.Container) *container { return &container{ - client: client, - id: c.ID, + client: client, + id: c.ID, + metadata: c, } } var _ = (Container)(&container{}) type container struct { - client *Client - id string + client *Client + id string + metadata containers.Container } // ID returns the container's unique id @@ -97,8 +99,22 @@ func (c *container) ID() string { return c.id } -func (c *container) Info(ctx context.Context) (containers.Container, error) { - return c.get(ctx) +func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) { + i := &InfoConfig{ + // default to refreshing the container's local metadata + Refresh: true, + } + for _, o := range opts { + o(i) + } + if i.Refresh { + metadata, err := c.get(ctx) + if err != nil { + return c.metadata, err + } + c.metadata = metadata + } + return c.metadata, nil } func (c *container) Extensions(ctx context.Context) (map[string]prototypes.Any, error) { diff --git a/container_opts.go b/container_opts.go index 1ce989432..25b8f9730 100644 --- a/container_opts.go +++ b/container_opts.go @@ -41,6 +41,15 @@ type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Co // UpdateContainerOpts allows the caller to set additional options when updating a container type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error +// InfoOpts controls how container metadata is fetched and returned +type InfoOpts func(*InfoConfig) + +// InfoConfig specifies how container metadata is fetched +type InfoConfig struct { + // Refresh will to a fetch of the latest container metadata + Refresh bool +} + // WithRuntime allows a user to specify the runtime name and additional options that should // be used to create tasks for the container func WithRuntime(name string, options interface{}) NewContainerOpts { @@ -235,3 +244,8 @@ func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts { return err } } + +// WithoutRefreshedMetadata will use the current metadata attached to the container object +func WithoutRefreshedMetadata(i *InfoConfig) { + i.Refresh = false +}