Merge pull request #3271 from crosbymichael/info

Faster lookups for listing container metadata
This commit is contained in:
Phil Estes 2019-05-10 20:44:43 +02:00 committed by GitHub
commit cfbbda910d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 237 additions and 155 deletions

View 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
},
}

View File

@ -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"
@ -125,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
}
@ -262,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
}
@ -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)
},
}

View 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)
},
}

View File

@ -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) {

View File

@ -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
}