Merge pull request #3271 from crosbymichael/info
Faster lookups for listing container metadata
This commit is contained in:
commit
cfbbda910d
102
cmd/ctr/commands/containers/checkpoint.go
Normal file
102
cmd/ctr/commands/containers/checkpoint.go
Normal file
@ -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
|
||||||
|
},
|
||||||
|
}
|
@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -125,7 +124,7 @@ var listCommand = cli.Command{
|
|||||||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
||||||
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
|
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
info, err := c.Info(ctx)
|
info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -262,7 +261,7 @@ var infoCommand = cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
info, err := container.Info(ctx)
|
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -285,148 +284,3 @@ var infoCommand = cli.Command{
|
|||||||
return nil
|
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
96
cmd/ctr/commands/containers/restore.go
Normal file
96
cmd/ctr/commands/containers/restore.go
Normal file
@ -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)
|
||||||
|
},
|
||||||
|
}
|
30
container.go
30
container.go
@ -49,7 +49,7 @@ type Container interface {
|
|||||||
// ID identifies the container
|
// ID identifies the container
|
||||||
ID() string
|
ID() string
|
||||||
// Info returns the underlying container record type
|
// 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 removes the container
|
||||||
Delete(context.Context, ...DeleteOpts) error
|
Delete(context.Context, ...DeleteOpts) error
|
||||||
// NewTask creates a new task based on the container metadata
|
// 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 {
|
func containerFromRecord(client *Client, c containers.Container) *container {
|
||||||
return &container{
|
return &container{
|
||||||
client: client,
|
client: client,
|
||||||
id: c.ID,
|
id: c.ID,
|
||||||
|
metadata: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = (Container)(&container{})
|
var _ = (Container)(&container{})
|
||||||
|
|
||||||
type container struct {
|
type container struct {
|
||||||
client *Client
|
client *Client
|
||||||
id string
|
id string
|
||||||
|
metadata containers.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the container's unique id
|
// ID returns the container's unique id
|
||||||
@ -97,8 +99,22 @@ func (c *container) ID() string {
|
|||||||
return c.id
|
return c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) Info(ctx context.Context) (containers.Container, error) {
|
func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) {
|
||||||
return c.get(ctx)
|
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) {
|
func (c *container) Extensions(ctx context.Context) (map[string]prototypes.Any, error) {
|
||||||
|
@ -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
|
// UpdateContainerOpts allows the caller to set additional options when updating a container
|
||||||
type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error
|
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
|
// WithRuntime allows a user to specify the runtime name and additional options that should
|
||||||
// be used to create tasks for the container
|
// be used to create tasks for the container
|
||||||
func WithRuntime(name string, options interface{}) NewContainerOpts {
|
func WithRuntime(name string, options interface{}) NewContainerOpts {
|
||||||
@ -235,3 +244,8 @@ func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithoutRefreshedMetadata will use the current metadata attached to the container object
|
||||||
|
func WithoutRefreshedMetadata(i *InfoConfig) {
|
||||||
|
i.Refresh = false
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user