Merge pull request #1366 from stevvooe/containers-metadata-tests

metadata: ensure correct updates on Container
This commit is contained in:
Michael Crosby 2017-08-21 16:08:06 -04:00 committed by GitHub
commit 873a34649a
17 changed files with 750 additions and 87 deletions

View File

@ -192,6 +192,7 @@ func defaultRemoteContext() *RemoteContext {
Resolver: docker.NewResolver(docker.ResolverOptions{ Resolver: docker.NewResolver(docker.ResolverOptions{
Client: http.DefaultClient, Client: http.DefaultClient,
}), }),
Snapshotter: DefaultSnapshotter,
} }
} }

View File

@ -174,6 +174,10 @@ func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...Ne
Stderr: cfg.Stderr, Stderr: cfg.Stderr,
} }
if c.c.RootFS != "" { if c.c.RootFS != "" {
if c.c.Snapshotter == "" {
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "unable to resolve rootfs mounts without snapshotter on container")
}
// get the rootfs from the snapshotter and add it to the request // get the rootfs from the snapshotter and add it to the request
mounts, err := c.client.SnapshotService(c.c.Snapshotter).Mounts(ctx, c.c.RootFS) mounts, err := c.client.SnapshotService(c.c.Snapshotter).Mounts(ctx, c.c.RootFS)
if err != nil { if err != nil {

View File

@ -4,7 +4,9 @@ import (
"context" "context"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/identity"
"github.com/pkg/errors"
) )
// NewContainerOpts allows the caller to set additional options when creating a container // NewContainerOpts allows the caller to set additional options when creating a container
@ -38,6 +40,8 @@ func WithContainerLabels(labels map[string]string) NewContainerOpts {
} }
// WithSnapshotter sets the provided snapshotter for use by the container // WithSnapshotter sets the provided snapshotter for use by the container
//
// This option must appear before other snapshotter options to have an effect.
func WithSnapshotter(name string) NewContainerOpts { func WithSnapshotter(name string) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error { return func(ctx context.Context, client *Client, c *containers.Container) error {
c.Snapshotter = name c.Snapshotter = name
@ -48,6 +52,7 @@ func WithSnapshotter(name string) NewContainerOpts {
// WithSnapshot uses an existing root filesystem for the container // WithSnapshot uses an existing root filesystem for the container
func WithSnapshot(id string) NewContainerOpts { func WithSnapshot(id string) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error { return func(ctx context.Context, client *Client, c *containers.Container) error {
setSnapshotterIfEmpty(c)
// check that the snapshot exists, if not, fail on creation // check that the snapshot exists, if not, fail on creation
if _, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, id); err != nil { if _, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, id); err != nil {
return err return err
@ -65,6 +70,7 @@ func WithNewSnapshot(id string, i Image) NewContainerOpts {
if err != nil { if err != nil {
return err return err
} }
setSnapshotterIfEmpty(c)
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil { if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil {
return err return err
} }
@ -77,6 +83,9 @@ func WithNewSnapshot(id string, i Image) NewContainerOpts {
// WithSnapshotCleanup deletes the rootfs allocated for the container // WithSnapshotCleanup deletes the rootfs allocated for the container
func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error { func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error {
if c.RootFS != "" { if c.RootFS != "" {
if c.Snapshotter == "" {
return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter must be set to cleanup rootfs")
}
return client.SnapshotService(c.Snapshotter).Remove(ctx, c.RootFS) return client.SnapshotService(c.Snapshotter).Remove(ctx, c.RootFS)
} }
return nil return nil
@ -90,6 +99,7 @@ func WithNewSnapshotView(id string, i Image) NewContainerOpts {
if err != nil { if err != nil {
return err return err
} }
setSnapshotterIfEmpty(c)
if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, identity.ChainID(diffIDs).String()); err != nil { if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, identity.ChainID(diffIDs).String()); err != nil {
return err return err
} }
@ -98,3 +108,9 @@ func WithNewSnapshotView(id string, i Image) NewContainerOpts {
return nil return nil
} }
} }
func setSnapshotterIfEmpty(c *containers.Container) {
if c.Snapshotter == "" {
c.Snapshotter = DefaultSnapshotter
}
}

View File

@ -46,6 +46,7 @@ func WithCheckpoint(desc v1.Descriptor, rootfsID string) NewContainerOpts {
if err != nil { if err != nil {
return err return err
} }
setSnapshotterIfEmpty(c)
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, rootfsID, identity.ChainID(diffIDs).String()); err != nil { if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, rootfsID, identity.ChainID(diffIDs).String()); err != nil {
if !errdefs.IsAlreadyExists(err) { if !errdefs.IsAlreadyExists(err) {
return err return err

View File

@ -12,14 +12,50 @@ import (
// //
// The resources specified in this object are used to create tasks from the container. // The resources specified in this object are used to create tasks from the container.
type Container struct { type Container struct {
// ID uniquely identifies the container in a nameapace.
//
// This property is required and cannot be changed after creation.
ID string ID string
// Labels provide metadata extension for a contaienr.
//
// These are optional and fully mutable.
Labels map[string]string Labels map[string]string
// Image specifies the image reference used for a container.
//
// This property is optional but immutable.
Image string Image string
// Runtime specifies which runtime should be used when launching container
// tasks.
//
// This property is required and immutable.
Runtime RuntimeInfo Runtime RuntimeInfo
// Spec should carry the the runtime specification used to implement the
// container.
//
// This field is required but mutable.
Spec *types.Any Spec *types.Any
// RootFS specifies the snapshot key to use for the container's root
// filesystem. When starting a task from this container, a caller should
// look up the mounts from the snapshot service and include those on the
// task create request.
//
// This field is not required but immutable.
RootFS string RootFS string
// Snapshotter specifies the snapshotter name used for rootfs
//
// This field is not required but immutable.
Snapshotter string Snapshotter string
// CreatedAt is the time at which the container was created.
CreatedAt time.Time CreatedAt time.Time
// UpdatedAt is the time at which the container was updated.
UpdatedAt time.Time UpdatedAt time.Time
} }
@ -34,6 +70,7 @@ type Store interface {
// List returns containers that match one or more of the provided filters. // List returns containers that match one or more of the provided filters.
List(ctx context.Context, filters ...string) ([]Container, error) List(ctx context.Context, filters ...string) ([]Container, error)
// Create a container in the store from the provided container.
Create(ctx context.Context, container Container) (Container, error) Create(ctx context.Context, container Container) (Container, error)
// Update the container with the provided container object. ID must be set. // Update the container with the provided container object. ID must be set.
@ -42,5 +79,9 @@ type Store interface {
// the fieldpaths will be mutated. // the fieldpaths will be mutated.
Update(ctx context.Context, container Container, fieldpaths ...string) (Container, error) Update(ctx context.Context, container Container, fieldpaths ...string) (Container, error)
// Delete a container using the id.
//
// nil will be returned on success. If the container is not known to the
// store, ErrNotFound will be returned.
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id string) error
} }

View File

@ -48,6 +48,7 @@ var (
bucketKeyOptions = []byte("options") bucketKeyOptions = []byte("options")
bucketKeySpec = []byte("spec") bucketKeySpec = []byte("spec")
bucketKeyRootFS = []byte("rootfs") bucketKeyRootFS = []byte("rootfs")
bucketKeySnapshotter = []byte("snapshotter")
bucketKeyTarget = []byte("target") bucketKeyTarget = []byte("target")
) )

View File

@ -91,8 +91,8 @@ func (s *containerStore) Create(ctx context.Context, container containers.Contai
return containers.Container{}, err return containers.Container{}, err
} }
if err := identifiers.Validate(container.ID); err != nil { if err := validateContainer(&container); err != nil {
return containers.Container{}, err return containers.Container{}, errors.Wrap(err, "create container failed validation")
} }
bkt, err := createContainersBucket(s.tx, namespace) bkt, err := createContainersBucket(s.tx, namespace)
@ -144,11 +144,33 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
createdat := updated.CreatedAt createdat := updated.CreatedAt
updated.ID = container.ID updated.ID = container.ID
if len(fieldpaths) == 0 {
// only allow updates to these field on full replace.
fieldpaths = []string{"labels", "spec"}
// Fields that are immutable must cause an error when no field paths
// are provided. This allows these fields to become mutable in the
// future.
if updated.Image != container.Image {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.Image field is immutable")
}
if updated.RootFS != container.RootFS {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.RootFS field is immutable")
}
if updated.Snapshotter != container.Snapshotter {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter field is immutable")
}
if updated.Runtime.Name != container.Runtime.Name {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name field is immutable")
}
}
// apply the field mask. If you update this code, you better follow the // apply the field mask. If you update this code, you better follow the
// field mask rules in field_mask.proto. If you don't know what this // field mask rules in field_mask.proto. If you don't know what this
// is, do not update this code. // is, do not update this code.
if len(fieldpaths) > 0 {
// TODO(stevvooe): Move this logic into the store itself.
for _, path := range fieldpaths { for _, path := range fieldpaths {
if strings.HasPrefix(path, "labels.") { if strings.HasPrefix(path, "labels.") {
if updated.Labels == nil { if updated.Labels == nil {
@ -162,19 +184,15 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
switch path { switch path {
case "labels": case "labels":
updated.Labels = container.Labels updated.Labels = container.Labels
case "image":
updated.Image = container.Image
case "spec": case "spec":
updated.Spec = container.Spec updated.Spec = container.Spec
case "rootfs":
updated.RootFS = container.RootFS
default: default:
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on %q", path, container.ID) return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on %q", path, container.ID)
} }
} }
} else {
// no field mask present, just replace everything if err := validateContainer(&updated); err != nil {
updated = container return containers.Container{}, errors.Wrap(err, "update failed validation")
} }
updated.CreatedAt = createdat updated.CreatedAt = createdat
@ -183,7 +201,7 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
return containers.Container{}, errors.Wrap(err, "failed to write container") return containers.Container{}, errors.Wrap(err, "failed to write container")
} }
return container, nil return updated, nil
} }
func (s *containerStore) Delete(ctx context.Context, id string) error { func (s *containerStore) Delete(ctx context.Context, id string) error {
@ -203,6 +221,27 @@ func (s *containerStore) Delete(ctx context.Context, id string) error {
return err return err
} }
func validateContainer(container *containers.Container) error {
if err := identifiers.Validate(container.ID); err != nil {
return errors.Wrapf(err, "container.ID validation error")
}
// labels and image have no validation
if container.Runtime.Name == "" {
return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name must be set")
}
if container.Spec == nil {
return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Spec must be set")
}
if container.RootFS != "" && container.Snapshotter == "" {
return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter must be set if container.RootFS is set")
}
return nil
}
func readContainer(container *containers.Container, bkt *bolt.Bucket) error { func readContainer(container *containers.Container, bkt *bolt.Bucket) error {
labels, err := boltutil.ReadLabels(bkt) labels, err := boltutil.ReadLabels(bkt)
if err != nil { if err != nil {
@ -247,7 +286,8 @@ func readContainer(container *containers.Container, bkt *bolt.Bucket) error {
container.Spec = &any container.Spec = &any
case string(bucketKeyRootFS): case string(bucketKeyRootFS):
container.RootFS = string(v) container.RootFS = string(v)
case string(bucketKeySnapshotter):
container.Snapshotter = string(v)
} }
return nil return nil
@ -272,6 +312,7 @@ func writeContainer(bkt *bolt.Bucket, container *containers.Container) error {
for _, v := range [][2][]byte{ for _, v := range [][2][]byte{
{bucketKeyImage, []byte(container.Image)}, {bucketKeyImage, []byte(container.Image)},
{bucketKeySnapshotter, []byte(container.Snapshotter)},
{bucketKeyRootFS, []byte(container.RootFS)}, {bucketKeyRootFS, []byte(container.RootFS)},
} { } {
if err := bkt.Put(v[0], v[1]); err != nil { if err := bkt.Put(v[0], v[1]); err != nil {

557
metadata/containers_test.go Normal file
View File

@ -0,0 +1,557 @@
package metadata
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/typeurl"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
func init() {
typeurl.Register(&specs.Spec{}, "opencontainers/runtime-spec", "v1", "Spec")
}
func TestContainersList(t *testing.T) {
ctx, db, cancel := testEnv(t)
defer cancel()
spec := &specs.Spec{}
encoded, err := typeurl.MarshalAny(spec)
if err != nil {
t.Fatal(err)
}
testset := map[string]*containers.Container{}
for i := 0; i < 4; i++ {
id := "container-" + fmt.Sprint(i)
testset[id] = &containers.Container{
ID: id,
Labels: map[string]string{
"idlabel": id,
"even": fmt.Sprint(i%2 == 0),
"odd": fmt.Sprint(i%2 != 0),
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
}
if err := db.Update(func(tx *bolt.Tx) error {
store := NewContainerStore(tx)
now := time.Now()
result, err := store.Create(ctx, *testset[id])
if err != nil {
return err
}
checkContainerTimestamps(t, &result, now, true)
testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt
checkContainersEqual(t, &result, testset[id], "ensure that containers were created as expected for list")
return nil
}); err != nil {
t.Fatal(err)
}
}
for _, testcase := range []struct {
name string
filters []string
}{
{
name: "FullSet",
},
{
name: "FullSetFiltered", // full set, but because we have OR filter
filters: []string{"labels.even==true", "labels.odd==true"},
},
{
name: "Even",
filters: []string{"labels.even==true"},
},
{
name: "Odd",
filters: []string{"labels.odd==true"},
},
{
name: "ByID",
filters: []string{"id==container-0"},
},
{
name: "ByIDLabelEven",
filters: []string{"labels.idlabel==container-0,labels.even==true"},
},
{
name: "ByRuntime",
filters: []string{"runtime.name==testruntime"},
},
} {
t.Run(testcase.name, func(t *testing.T) {
testset := testset
if len(testcase.filters) > 0 {
fs, err := filters.ParseAll(testcase.filters...)
if err != nil {
t.Fatal(err)
}
newtestset := make(map[string]*containers.Container, len(testset))
for k, v := range testset {
if fs.Match(adaptContainer(*v)) {
newtestset[k] = v
}
}
testset = newtestset
}
if err := db.View(func(tx *bolt.Tx) error {
store := NewContainerStore(tx)
results, err := store.List(ctx, testcase.filters...)
if err != nil {
t.Fatal(err)
}
if len(results) == 0 { // all tests return a non-empty result set
t.Fatalf("not results returned")
}
if len(results) != len(testset) {
t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset))
}
for _, result := range results {
checkContainersEqual(t, &result, testset[result.ID], "list results did not match")
}
return nil
}); err != nil {
t.Fatal(err)
}
})
}
// delete everything to test it
for id := range testset {
if err := db.Update(func(tx *bolt.Tx) error {
store := NewContainerStore(tx)
return store.Delete(ctx, id)
}); err != nil {
t.Fatal(err)
}
// try it again, get NotFound
if err := db.Update(func(tx *bolt.Tx) error {
store := NewContainerStore(tx)
return store.Delete(ctx, id)
}); errors.Cause(err) != errdefs.ErrNotFound {
t.Fatalf("unexpected error %v", err)
}
}
}
// TestContainersUpdate ensures that updates are taken in an expected manner.
func TestContainersCreateUpdateDelete(t *testing.T) {
ctx, db, cancel := testEnv(t)
defer cancel()
spec := &specs.Spec{}
encoded, err := typeurl.MarshalAny(spec)
if err != nil {
t.Fatal(err)
}
spec.Annotations = map[string]string{"updated": "true"}
encodedUpdated, err := typeurl.MarshalAny(spec)
if err != nil {
t.Fatal(err)
}
for _, testcase := range []struct {
name string
original containers.Container
createerr error
input containers.Container
fieldpaths []string
expected containers.Container
cause error
}{
{
name: "UpdateIDFail",
original: containers.Container{
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
input: containers.Container{
ID: "newid",
Spec: encoded,
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
fieldpaths: []string{"id"},
cause: errdefs.ErrNotFound,
},
{
name: "UpdateRuntimeFail",
original: containers.Container{
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Spec: encoded,
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
input: containers.Container{
Spec: encoded,
Runtime: containers.RuntimeInfo{
Name: "testruntimedifferent",
},
},
fieldpaths: []string{"runtime"},
cause: errdefs.ErrInvalidArgument,
},
{
name: "UpdateRuntimeClearFail",
original: containers.Container{
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
input: containers.Container{
Spec: encoded,
},
fieldpaths: []string{"runtime"},
cause: errdefs.ErrInvalidArgument,
},
{
name: "UpdateFail",
original: containers.Container{
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
input: containers.Container{
Spec: encoded,
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
// try to clear image field
},
cause: errdefs.ErrInvalidArgument,
},
{
name: "UpdateSpec",
original: containers.Container{
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
input: containers.Container{
Spec: encodedUpdated,
},
fieldpaths: []string{"spec"},
expected: containers.Container{
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Spec: encodedUpdated,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Image: "test image",
},
},
{
name: "UpdateLabel",
original: containers.Container{
Labels: map[string]string{
"foo": "one",
"bar": "two",
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
input: containers.Container{
Labels: map[string]string{
"bar": "baz",
},
},
fieldpaths: []string{"labels.bar"},
expected: containers.Container{
Labels: map[string]string{
"foo": "one",
"bar": "baz",
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
},
{
name: "DeleteAllLabels",
original: containers.Container{
Labels: map[string]string{
"foo": "one",
"bar": "two",
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
input: containers.Container{
Labels: nil,
},
fieldpaths: []string{"labels"},
expected: containers.Container{
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
},
{
name: "DeleteLabel",
original: containers.Container{
Labels: map[string]string{
"foo": "one",
"bar": "two",
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
input: containers.Container{
Labels: map[string]string{
"bar": "",
},
},
fieldpaths: []string{"labels.bar"},
expected: containers.Container{
Labels: map[string]string{
"foo": "one",
},
Spec: encoded,
RootFS: "test-rootfs",
Snapshotter: "snapshotter",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
Image: "test image",
},
},
{
name: "UpdateRootFSImmutable",
original: containers.Container{
Spec: encoded,
RootFS: "",
Snapshotter: "",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
input: containers.Container{
RootFS: "something",
Snapshotter: "something",
},
fieldpaths: []string{"rootfs", "snapshotter"},
cause: errdefs.ErrInvalidArgument,
},
{
name: "RootFSWithoutSnapshot",
original: containers.Container{
Spec: encoded,
RootFS: "/nosnapshot",
Snapshotter: "",
Runtime: containers.RuntimeInfo{
Name: "testruntime",
},
},
createerr: errdefs.ErrInvalidArgument,
},
} {
t.Run(testcase.name, func(t *testing.T) {
testcase.original.ID = testcase.name
if testcase.input.ID == "" {
testcase.input.ID = testcase.name
}
testcase.expected.ID = testcase.name
done := errors.New("test complete")
if err := db.Update(func(tx *bolt.Tx) error {
var (
now = time.Now().UTC()
store = NewContainerStore(tx)
)
result, err := store.Create(ctx, testcase.original)
if errors.Cause(err) != testcase.createerr {
if testcase.createerr == nil {
t.Fatalf("unexpected error: %v", err)
} else {
t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr)
}
} else if testcase.createerr != nil {
return done
}
checkContainerTimestamps(t, &result, now, true)
// ensure that createdat is never tampered with
testcase.original.CreatedAt = result.CreatedAt
testcase.expected.CreatedAt = result.CreatedAt
testcase.original.UpdatedAt = result.UpdatedAt
testcase.expected.UpdatedAt = result.UpdatedAt
checkContainersEqual(t, &result, &testcase.original, "unexpected result on container update")
return nil
}); err != nil {
if err == done {
return
}
t.Fatal(err)
}
if err := db.Update(func(tx *bolt.Tx) error {
now := time.Now()
store := NewContainerStore(tx)
result, err := store.Update(ctx, testcase.input, testcase.fieldpaths...)
if errors.Cause(err) != testcase.cause {
if testcase.cause == nil {
t.Fatalf("unexpected error: %v", err)
} else {
t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause)
}
} else if testcase.cause != nil {
return done
}
checkContainerTimestamps(t, &result, now, false)
testcase.expected.UpdatedAt = result.UpdatedAt
checkContainersEqual(t, &result, &testcase.expected, "updated failed to get expected result")
return nil
}); err != nil {
if err == done {
return
}
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
store := NewContainerStore(tx)
result, err := store.Get(ctx, testcase.original.ID)
if err != nil {
t.Fatal(err)
}
checkContainersEqual(t, &result, &testcase.expected, "get after failed to get expected result")
return nil
}); err != nil {
t.Fatal(err)
}
})
}
}
func checkContainerTimestamps(t *testing.T, c *containers.Container, now time.Time, oncreate bool) {
if c.UpdatedAt.IsZero() || c.CreatedAt.IsZero() {
t.Fatalf("timestamps not set")
}
if oncreate {
if !c.CreatedAt.Equal(c.UpdatedAt) {
t.Fatal("timestamps should be equal on create")
}
} else {
// ensure that updatedat is always after createdat
if !c.UpdatedAt.After(c.CreatedAt) {
t.Fatalf("timestamp for updatedat not after createdat: %v <= %v", c.UpdatedAt, c.CreatedAt)
}
}
if c.UpdatedAt.Before(now) {
t.Fatal("createdat time incorrect should be after the start of the operation")
}
}
func checkContainersEqual(t *testing.T, a, b *containers.Container, format string, args ...interface{}) {
if !reflect.DeepEqual(a, b) {
t.Fatalf("containers not equal %v != %v: "+format, append([]interface{}{a, b}, args...)...)
}
}
func testEnv(t *testing.T) (context.Context, *bolt.DB, func()) {
ctx, cancel := context.WithCancel(context.Background())
ctx = namespaces.WithNamespace(ctx, "testing")
dirname, err := ioutil.TempDir("", t.Name()+"-")
if err != nil {
t.Fatal(err)
}
db, err := bolt.Open(filepath.Join(dirname, "meta.db"), 0644, nil)
if err != nil {
t.Fatal(err)
}
return ctx, db, func() {
db.Close()
if err := os.RemoveAll(dirname); err != nil {
t.Log("failed removing temp dir", err)
}
cancel()
}
}

View File

@ -1,5 +0,0 @@
package snapshot
const (
defaultSnapshotter = "overlayfs"
)

View File

@ -1,7 +0,0 @@
// +build darwin freebsd solaris
package snapshot
const (
defaultSnapshotter = "naive"
)

View File

@ -1,5 +0,0 @@
package snapshot
const (
defaultSnapshotter = "windows"
)

View File

@ -20,11 +20,6 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type config struct {
// Default is the default snapshotter to use for the service
Default string `toml:"default,omitempty"`
}
func init() { func init() {
plugin.Register(&plugin.Registration{ plugin.Register(&plugin.Registration{
Type: plugin.GRPCPlugin, Type: plugin.GRPCPlugin,
@ -33,9 +28,6 @@ func init() {
plugin.SnapshotPlugin, plugin.SnapshotPlugin,
plugin.MetadataPlugin, plugin.MetadataPlugin,
}, },
Config: &config{
Default: defaultSnapshotter,
},
Init: newService, Init: newService,
}) })
} }
@ -44,7 +36,6 @@ var empty = &protoempty.Empty{}
type service struct { type service struct {
snapshotters map[string]snapshot.Snapshotter snapshotters map[string]snapshot.Snapshotter
defaultSnapshotterName string
publisher events.Publisher publisher events.Publisher
} }
@ -62,26 +53,24 @@ func newService(ic *plugin.InitContext) (interface{}, error) {
snapshotters[name] = metadata.NewSnapshotter(md.(*bolt.DB), name, sn.(snapshot.Snapshotter)) snapshotters[name] = metadata.NewSnapshotter(md.(*bolt.DB), name, sn.(snapshot.Snapshotter))
} }
cfg := ic.Config.(*config) if len(snapshotters) == 0 {
_, ok := snapshotters[cfg.Default] return nil, errors.Errorf("failed to create snapshotter service: no snapshotters loaded")
if !ok {
return nil, errors.Errorf("default snapshotter not loaded: %s", cfg.Default)
} }
return &service{ return &service{
snapshotters: snapshotters, snapshotters: snapshotters,
defaultSnapshotterName: cfg.Default,
publisher: ic.Events, publisher: ic.Events,
}, nil }, nil
} }
func (s *service) getSnapshotter(name string) (snapshot.Snapshotter, error) { func (s *service) getSnapshotter(name string) (snapshot.Snapshotter, error) {
if name == "" { if name == "" {
name = s.defaultSnapshotterName return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter argument missing")
} }
sn, ok := s.snapshotters[name] sn, ok := s.snapshotters[name]
if !ok { if !ok {
return nil, errors.Errorf("snapshotter not loaded: %s", name) return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, "snapshotter not loaded: %s", name)
} }
return sn, nil return sn, nil
} }

View File

@ -15,7 +15,7 @@ func newSnapshotter(ctx context.Context, root string) (snapshot.Snapshotter, fun
return nil, nil, err return nil, nil, err
} }
sn := client.SnapshotService("") sn := client.SnapshotService(DefaultSnapshotter)
return sn, func() { return sn, func() {
client.Close() client.Close()

View File

@ -0,0 +1,8 @@
package containerd
const (
// DefaultSnapshotter will set the default snapshotter for the platform.
// This will be based on the client compilation target, so take that into
// account when choosing this value.
DefaultSnapshotter = "overlayfs"
)

View File

@ -0,0 +1,10 @@
// +build darwin freebsd solaris
package containerd
const (
// DefaultSnapshotter will set the default snapshotter for the platform.
// This will be based on the client compilation target, so take that into
// account when choosing this value.
DefaultSnapshotter = "naive"
)

View File

@ -0,0 +1,8 @@
package containerd
const (
// DefaultSnapshotter will set the default snapshotter for the platform.
// This will be based on the client compilation target, so take that into
// account when choosing this value.
DefaultSnapshotter = "windows"
)

View File

@ -233,6 +233,9 @@ func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts
if err != nil { if err != nil {
return err return err
} }
setSnapshotterIfEmpty(c)
var ( var (
snapshotter = client.SnapshotService(c.Snapshotter) snapshotter = client.SnapshotService(c.Snapshotter)
parent = identity.ChainID(diffIDs).String() parent = identity.ChainID(diffIDs).String()