diff --git a/cmd/containerd/config_linux.go b/cmd/containerd/config_linux.go index efb82309b..46956c524 100644 --- a/cmd/containerd/config_linux.go +++ b/cmd/containerd/config_linux.go @@ -15,7 +15,5 @@ func defaultConfig() *server.Config { Level: "info", Address: "/run/containerd/debug.sock", }, - Snapshotter: "io.containerd.snapshotter.v1.overlayfs", - Differ: "io.containerd.differ.v1.base-diff", } } diff --git a/cmd/containerd/config_unix.go b/cmd/containerd/config_unix.go index 287fd9ef2..da3b40f25 100644 --- a/cmd/containerd/config_unix.go +++ b/cmd/containerd/config_unix.go @@ -14,7 +14,5 @@ func defaultConfig() *server.Config { Level: "info", Address: "/run/containerd/debug.sock", }, - Snapshotter: "io.containerd.snapshotter.v1.naive", - Differ: "io.containerd.differ.v1.base-diff", } } diff --git a/cmd/containerd/config_windows.go b/cmd/containerd/config_windows.go index 4bf3b802e..628dd90f2 100644 --- a/cmd/containerd/config_windows.go +++ b/cmd/containerd/config_windows.go @@ -17,7 +17,5 @@ func defaultConfig() *server.Config { Level: "info", Address: `\\.\pipe\containerd-debug`, }, - Snapshotter: "io.containerd.snapshotter.v1.windows", - Differ: "io.containerd.differ.v1.base-diff", } } diff --git a/differ/differ.go b/differ/differ.go index 047c5a60c..1738110e3 100644 --- a/differ/differ.go +++ b/differ/differ.go @@ -10,7 +10,6 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/plugin" - "github.com/containerd/containerd/snapshot" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -23,35 +22,28 @@ func init() { ID: "base-diff", Requires: []plugin.PluginType{ plugin.ContentPlugin, - plugin.SnapshotPlugin, }, Init: func(ic *plugin.InitContext) (interface{}, error) { c, err := ic.Get(plugin.ContentPlugin) if err != nil { return nil, err } - s, err := ic.Get(plugin.SnapshotPlugin) - if err != nil { - return nil, err - } - return newBaseDiff(c.(content.Store), s.(snapshot.Snapshotter)) + return newBaseDiff(c.(content.Store)) }, }) } type BaseDiff struct { - store content.Store - snapshotter snapshot.Snapshotter + store content.Store } var _ plugin.Differ = &BaseDiff{} var emptyDesc = ocispec.Descriptor{} -func newBaseDiff(store content.Store, snapshotter snapshot.Snapshotter) (*BaseDiff, error) { +func newBaseDiff(store content.Store) (*BaseDiff, error) { return &BaseDiff{ - store: store, - snapshotter: snapshotter, + store: store, }, nil } diff --git a/metadata/bolt.go b/metadata/bolt.go new file mode 100644 index 000000000..221dba5a2 --- /dev/null +++ b/metadata/bolt.go @@ -0,0 +1,40 @@ +package metadata + +import ( + "context" + + "github.com/boltdb/bolt" + "github.com/pkg/errors" +) + +type transactionKey struct{} + +// WithTransactionContext returns a new context holding the provided +// bolt transaction. Functions which require a bolt transaction will +// first check to see if a transaction is already created on the +// context before creating their own. +func WithTransactionContext(ctx context.Context, tx *bolt.Tx) context.Context { + return context.WithValue(ctx, transactionKey{}, tx) +} + +// view gets a bolt db transaction either from the context +// or starts a new one with the provided bolt database. +func view(ctx context.Context, db *bolt.DB, fn func(*bolt.Tx) error) error { + tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx) + if !ok { + return db.View(fn) + } + return fn(tx) +} + +// update gets a writable bolt db transaction either from the context +// or starts a new one with the provided bolt database. +func update(ctx context.Context, db *bolt.DB, fn func(*bolt.Tx) error) error { + tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx) + if !ok { + return db.Update(fn) + } else if !tx.Writable() { + return errors.Wrap(bolt.ErrTxNotWritable, "unable to use transaction from context") + } + return fn(tx) +} diff --git a/metadata/buckets.go b/metadata/buckets.go index 536d04011..202e0f1d5 100644 --- a/metadata/buckets.go +++ b/metadata/buckets.go @@ -32,6 +32,7 @@ var ( bucketKeyObjectIndexes = []byte("indexes") // reserved bucketKeyObjectImages = []byte("images") // stores image objects bucketKeyObjectContainers = []byte("containers") // stores container objects + bucketKeyObjectSnapshots = []byte("snapshots") // stores snapshot references bucketKeyDigest = []byte("digest") bucketKeyMediaType = []byte("mediatype") @@ -125,3 +126,15 @@ func getContainersBucket(tx *bolt.Tx, namespace string) *bolt.Bucket { func getContainerBucket(tx *bolt.Tx, namespace, id string) *bolt.Bucket { return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContainers, []byte(id)) } + +func createSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) (*bolt.Bucket, error) { + bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectSnapshots, []byte(snapshotter)) + if err != nil { + return nil, err + } + return bkt, nil +} + +func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Bucket { + return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectSnapshots, []byte(snapshotter)) +} diff --git a/metadata/snapshot.go b/metadata/snapshot.go new file mode 100644 index 000000000..0ac3133ef --- /dev/null +++ b/metadata/snapshot.go @@ -0,0 +1,272 @@ +package metadata + +import ( + "context" + "fmt" + "strings" + + "github.com/boltdb/bolt" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/snapshot" + "github.com/pkg/errors" +) + +type snapshotter struct { + snapshot.Snapshotter + name string + db *bolt.DB +} + +// NewSnapshotter returns a new Snapshotter which namespaces the given snapshot +// using the provided name and metadata store. +func NewSnapshotter(db *bolt.DB, name string, sn snapshot.Snapshotter) snapshot.Snapshotter { + return &snapshotter{ + Snapshotter: sn, + name: name, + db: db, + } +} + +func snapshotKey(id uint64, namespace, key string) string { + return fmt.Sprintf("%s/%d/%s", namespace, id, key) +} + +func trimName(key string) string { + parts := strings.SplitN(key, "/", 3) + if len(parts) < 3 { + return "" + } + return parts[2] +} + +func getKey(tx *bolt.Tx, ns, name, key string) string { + bkt := getSnapshotterBucket(tx, ns, name) + if bkt == nil { + return "" + } + v := bkt.Get([]byte(key)) + if len(v) == 0 { + return "" + } + return string(v) +} + +func (s *snapshotter) resolveKey(ctx context.Context, key string) (string, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return "", err + } + + var id string + if err := view(ctx, s.db, func(tx *bolt.Tx) error { + id = getKey(tx, ns, s.name, key) + if id == "" { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) + } + return nil + }); err != nil { + return "", err + } + + return id, nil +} + +func (s *snapshotter) Stat(ctx context.Context, key string) (snapshot.Info, error) { + bkey, err := s.resolveKey(ctx, key) + if err != nil { + return snapshot.Info{}, err + } + info, err := s.Snapshotter.Stat(ctx, bkey) + if err != nil { + return snapshot.Info{}, err + } + info.Name = trimName(info.Name) + if info.Parent != "" { + info.Parent = trimName(info.Parent) + } + + return info, nil +} + +func (s *snapshotter) Usage(ctx context.Context, key string) (snapshot.Usage, error) { + bkey, err := s.resolveKey(ctx, key) + if err != nil { + return snapshot.Usage{}, err + } + return s.Snapshotter.Usage(ctx, bkey) +} + +func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { + bkey, err := s.resolveKey(ctx, key) + if err != nil { + return nil, err + } + return s.Snapshotter.Mounts(ctx, bkey) +} + +func (s *snapshotter) Prepare(ctx context.Context, key, parent string) ([]mount.Mount, error) { + return s.createSnapshot(ctx, key, parent, false) +} + +func (s *snapshotter) View(ctx context.Context, key, parent string) ([]mount.Mount, error) { + return s.createSnapshot(ctx, key, parent, true) +} + +func (s *snapshotter) createSnapshot(ctx context.Context, key, parent string, readonly bool) ([]mount.Mount, error) { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return nil, err + } + + var m []mount.Mount + if err := update(ctx, s.db, func(tx *bolt.Tx) error { + bkt, err := createSnapshotterBucket(tx, ns, s.name) + if err != nil { + return err + } + + bkey := string(bkt.Get([]byte(key))) + if bkey != "" { + return errors.Wrapf(errdefs.ErrAlreadyExists, "snapshot %v already exists", key) + } + var bparent string + if parent != "" { + bparent = string(bkt.Get([]byte(parent))) + if bparent == "" { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", parent) + } + } + + sid, err := bkt.NextSequence() + if err != nil { + return err + } + bkey = snapshotKey(sid, ns, key) + if err := bkt.Put([]byte(key), []byte(bkey)); err != nil { + return err + } + + // TODO: Consider doing this outside of transaction to lessen + // metadata lock time + if readonly { + m, err = s.Snapshotter.View(ctx, bkey, bparent) + } else { + m, err = s.Snapshotter.Prepare(ctx, bkey, bparent) + } + return err + }); err != nil { + return nil, err + } + return m, nil +} + +func (s *snapshotter) Commit(ctx context.Context, name, key string) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + return update(ctx, s.db, func(tx *bolt.Tx) error { + bkt := getSnapshotterBucket(tx, ns, s.name) + if bkt == nil { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) + } + + nameKey := string(bkt.Get([]byte(name))) + if nameKey != "" { + return errors.Wrapf(errdefs.ErrAlreadyExists, "snapshot %v already exists", name) + } + + bkey := string(bkt.Get([]byte(key))) + if bkey == "" { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) + } + + sid, err := bkt.NextSequence() + if err != nil { + return err + } + nameKey = snapshotKey(sid, ns, name) + if err := bkt.Put([]byte(name), []byte(nameKey)); err != nil { + return err + } + if err := bkt.Delete([]byte(key)); err != nil { + return err + } + + // TODO: Consider doing this outside of transaction to lessen + // metadata lock time + return s.Snapshotter.Commit(ctx, nameKey, bkey) + }) + +} + +func (s *snapshotter) Remove(ctx context.Context, key string) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + return update(ctx, s.db, func(tx *bolt.Tx) error { + bkt := getSnapshotterBucket(tx, ns, s.name) + if bkt == nil { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) + } + + bkey := string(bkt.Get([]byte(key))) + if bkey == "" { + return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) + } + if err := bkt.Delete([]byte(key)); err != nil { + return err + } + + return s.Snapshotter.Remove(ctx, bkey) + }) +} + +func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error { + ns, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + + var keys []string + + if err := view(ctx, s.db, func(tx *bolt.Tx) error { + bkt := getSnapshotterBucket(tx, ns, s.name) + if bkt == nil { + return nil + } + + bkt.ForEach(func(k, v []byte) error { + if len(v) > 0 { + keys = append(keys, string(v)) + } + return nil + }) + + return nil + }); err != nil { + return err + } + + for _, k := range keys { + info, err := s.Snapshotter.Stat(ctx, k) + if err != nil { + return err + } + + info.Name = trimName(info.Name) + if info.Parent != "" { + info.Parent = trimName(info.Parent) + } + if err := fn(ctx, info); err != nil { + return err + } + } + + return nil +} diff --git a/plugin/context.go b/plugin/context.go index 6e6d0fc9c..7b84745bd 100644 --- a/plugin/context.go +++ b/plugin/context.go @@ -9,7 +9,7 @@ import ( "github.com/containerd/containerd/log" ) -func NewContext(ctx context.Context, plugins map[PluginType][]interface{}, root, id string) *InitContext { +func NewContext(ctx context.Context, plugins map[PluginType]map[string]interface{}, root, id string) *InitContext { return &InitContext{ plugins: plugins, Root: filepath.Join(root, id), @@ -23,18 +23,17 @@ type InitContext struct { Config interface{} Emitter *events.Emitter - plugins map[PluginType][]interface{} + plugins map[PluginType]map[string]interface{} } func (i *InitContext) Get(t PluginType) (interface{}, error) { - p := i.plugins[t] - if len(p) == 0 { - return nil, fmt.Errorf("no plugins registered for %s", t) + for _, v := range i.plugins[t] { + return v, nil } - return p[0], nil + return nil, fmt.Errorf("no plugins registered for %s", t) } -func (i *InitContext) GetAll(t PluginType) ([]interface{}, error) { +func (i *InitContext) GetAll(t PluginType) (map[string]interface{}, error) { p, ok := i.plugins[t] if !ok { return nil, fmt.Errorf("no plugins registered for %s", t) diff --git a/plugin/plugin.go b/plugin/plugin.go index 14ee9cf35..7f8461790 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -1,18 +1,31 @@ package plugin import ( - "errors" "fmt" "sync" + "github.com/pkg/errors" "google.golang.org/grpc" ) var ( ErrNoPluginType = errors.New("plugin: no type") ErrNoPluginID = errors.New("plugin: no id") + + // SkipPlugin is used when a plugin is not initialized and should not be loaded, + // this allows the plugin loader differentiate between a plugin which is configured + // not to load and one that fails to load. + SkipPlugin = errors.New("skip plugin") ) +// IsSkipPlugin returns true if the error is skipping the plugin +func IsSkipPlugin(err error) bool { + if errors.Cause(err) == SkipPlugin { + return true + } + return false +} + type PluginType string const ( diff --git a/rootfs/apply.go b/rootfs/apply.go index ac2139c27..91ec7d81c 100644 --- a/rootfs/apply.go +++ b/rootfs/apply.go @@ -63,7 +63,7 @@ func applyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snap if err != nil { log.G(ctx).WithError(err).WithField("key", key).Infof("Apply failure, attempting cleanup") if rerr := sn.Remove(ctx, key); rerr != nil { - log.G(ctx).WithError(rerr).Warnf("Extraction snapshot %q removal failed: %v", key) + log.G(ctx).WithError(rerr).Warnf("Extraction snapshot %q removal failed", key) } } }() diff --git a/server/config.go b/server/config.go index 3fa53dea1..02659b669 100644 --- a/server/config.go +++ b/server/config.go @@ -17,11 +17,6 @@ type Config struct { Debug Debug `toml:"debug"` // Metrics and monitoring settings Metrics MetricsConfig `toml:"metrics"` - // Snapshotter specifies which snapshot driver to use - Snapshotter string `toml:"snapshotter"` - // Differ specifies which differ to use. Differ is tightly coupled with the snapshotter - // so not all combinations may work. - Differ string `toml:"differ"` // Plugins provides plugin specific configuration for the initialization of a plugin Plugins map[string]toml.Primitive `toml:"plugins"` // Enable containerd as a subreaper diff --git a/server/server.go b/server/server.go index d91780e6e..4225f9439 100644 --- a/server/server.go +++ b/server/server.go @@ -55,14 +55,10 @@ func New(ctx context.Context, config *Config) (*Server, error) { rpc: rpc, emitter: events.NewEmitter(), } - initialized = make(map[plugin.PluginType][]interface{}) + initialized = make(map[plugin.PluginType]map[string]interface{}) ) for _, p := range plugins { id := p.URI() - if !shouldLoadPlugin(p, config) { - log.G(ctx).WithField("type", p.Type).Infof("skip loading plugin %q...", id) - continue - } log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id) initContext := plugin.NewContext( @@ -83,10 +79,21 @@ func New(ctx context.Context, config *Config) (*Server, error) { } instance, err := p.Init(initContext) if err != nil { - log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id) + if plugin.IsSkipPlugin(err) { + log.G(ctx).WithField("type", p.Type).Infof("skip loading plugin %q...", id) + } else { + log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id) + } continue } - initialized[p.Type] = append(initialized[p.Type], instance) + + if types, ok := initialized[p.Type]; ok { + types[p.ID] = instance + } else { + initialized[p.Type] = map[string]interface{}{ + p.ID: instance, + } + } // check for grpc services that should be registered with the server if service, ok := instance.(plugin.Service); ok { services = append(services, service) @@ -170,17 +177,6 @@ func loadPlugins(config *Config) ([]*plugin.Registration, error) { return plugin.Graph(), nil } -func shouldLoadPlugin(p *plugin.Registration, config *Config) bool { - switch p.Type { - case plugin.SnapshotPlugin: - return p.URI() == config.Snapshotter - case plugin.DiffPlugin: - return p.URI() == config.Differ - default: - return true - } -} - func interceptor( ctx context.Context, req interface{}, diff --git a/services/snapshot/default_linux.go b/services/snapshot/default_linux.go new file mode 100644 index 000000000..13f75af41 --- /dev/null +++ b/services/snapshot/default_linux.go @@ -0,0 +1,5 @@ +package snapshot + +const ( + defaultSnapshotter = "overlayfs" +) diff --git a/services/snapshot/default_unix.go b/services/snapshot/default_unix.go new file mode 100644 index 000000000..1adf5ca7d --- /dev/null +++ b/services/snapshot/default_unix.go @@ -0,0 +1,7 @@ +// +build darwin freebsd + +package snapshot + +const ( + defaultSnapshotter = "naive" +) diff --git a/services/snapshot/default_windows.go b/services/snapshot/default_windows.go new file mode 100644 index 000000000..680631e32 --- /dev/null +++ b/services/snapshot/default_windows.go @@ -0,0 +1,5 @@ +package snapshot + +const ( + defaultSnapshotter = "windows" +) diff --git a/services/snapshot/service.go b/services/snapshot/service.go index 8a0b8f836..a42fa7211 100644 --- a/services/snapshot/service.go +++ b/services/snapshot/service.go @@ -3,35 +3,40 @@ package snapshot import ( gocontext "context" + "github.com/boltdb/bolt" eventsapi "github.com/containerd/containerd/api/services/events/v1" snapshotapi "github.com/containerd/containerd/api/services/snapshot/v1" "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/events" "github.com/containerd/containerd/log" + "github.com/containerd/containerd/metadata" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/plugin" "github.com/containerd/containerd/snapshot" protoempty "github.com/golang/protobuf/ptypes/empty" + "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" ) +type config struct { + // Default is the default snapshotter to use for the service + Default string `toml:"default,omitempty"` +} + func init() { plugin.Register(&plugin.Registration{ Type: plugin.GRPCPlugin, ID: "snapshots", Requires: []plugin.PluginType{ plugin.SnapshotPlugin, + plugin.MetadataPlugin, }, - Init: func(ic *plugin.InitContext) (interface{}, error) { - e := events.GetPoster(ic.Context) - s, err := ic.Get(plugin.SnapshotPlugin) - if err != nil { - return nil, err - } - return newService(s.(snapshot.Snapshotter), e) + Config: &config{ + Default: defaultSnapshotter, }, + Init: newService, }) } @@ -42,9 +47,26 @@ type service struct { emitter events.Poster } -func newService(snapshotter snapshot.Snapshotter, evts events.Poster) (*service, error) { +func newService(ic *plugin.InitContext) (interface{}, error) { + evts := events.GetPoster(ic.Context) + snapshotters, err := ic.GetAll(plugin.SnapshotPlugin) + if err != nil { + return nil, err + } + cfg := ic.Config.(*config) + + sn, ok := snapshotters[cfg.Default] + if !ok { + return nil, errors.Errorf("default snapshotter not loaded: %s", cfg.Default) + } + + md, err := ic.Get(plugin.MetadataPlugin) + if err != nil { + return nil, err + } + return &service{ - snapshotter: snapshotter, + snapshotter: metadata.NewSnapshotter(md.(*bolt.DB), cfg.Default, sn.(snapshot.Snapshotter)), emitter: evts, }, nil } diff --git a/snapshot/btrfs/btrfs_test.go b/snapshot/btrfs/btrfs_test.go index 116e79184..afc251570 100644 --- a/snapshot/btrfs/btrfs_test.go +++ b/snapshot/btrfs/btrfs_test.go @@ -12,7 +12,6 @@ import ( "testing" "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/snapshot/testsuite" "github.com/containerd/containerd/testutil" @@ -51,7 +50,7 @@ func TestBtrfs(t *testing.T) { func TestBtrfsMounts(t *testing.T) { testutil.RequiresRoot(t) - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-btrfs-test") + ctx := context.Background() // create temporary directory for mount point mountPoint, err := ioutil.TempDir("", "containerd-btrfs-test") diff --git a/snapshot/overlay/overlay_test.go b/snapshot/overlay/overlay_test.go index 0a9252fa6..f80d11559 100644 --- a/snapshot/overlay/overlay_test.go +++ b/snapshot/overlay/overlay_test.go @@ -12,7 +12,6 @@ import ( "testing" "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/snapshot/storage" "github.com/containerd/containerd/snapshot/testsuite" @@ -34,7 +33,7 @@ func TestOverlay(t *testing.T) { } func TestOverlayMounts(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-overlay-test") + ctx := context.TODO() root, err := ioutil.TempDir("", "overlay") if err != nil { t.Fatal(err) @@ -70,7 +69,7 @@ func TestOverlayMounts(t *testing.T) { } func TestOverlayCommit(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-overlay-test") + ctx := context.TODO() root, err := ioutil.TempDir("", "overlay") if err != nil { t.Fatal(err) @@ -99,7 +98,7 @@ func TestOverlayCommit(t *testing.T) { } func TestOverlayOverlayMount(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-overlay-test") + ctx := context.TODO() root, err := ioutil.TempDir("", "overlay") if err != nil { t.Fatal(err) @@ -187,7 +186,7 @@ func getParents(ctx context.Context, sn snapshot.Snapshotter, root, key string) func TestOverlayOverlayRead(t *testing.T) { testutil.RequiresRoot(t) - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-overlay-test") + ctx := context.TODO() root, err := ioutil.TempDir("", "overlay") if err != nil { t.Fatal(err) @@ -239,7 +238,7 @@ func TestOverlayOverlayRead(t *testing.T) { } func TestOverlayView(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-overlay-test") + ctx := context.TODO() root, err := ioutil.TempDir("", "overlay") if err != nil { t.Fatal(err) diff --git a/snapshot/storage/bolt.go b/snapshot/storage/bolt.go index 37952fd47..f6dc80161 100644 --- a/snapshot/storage/bolt.go +++ b/snapshot/storage/bolt.go @@ -7,7 +7,6 @@ import ( "github.com/boltdb/bolt" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/snapshot" db "github.com/containerd/containerd/snapshot/storage/proto" "github.com/gogo/protobuf/proto" @@ -138,7 +137,7 @@ func CreateActive(ctx context.Context, key, parent string, readonly bool) (a Act return errors.Wrapf(errdefs.ErrAlreadyExists, "snapshot %v", key) } - id, err := nextSequence(ctx) + id, err := bkt.NextSequence() if err != nil { return errors.Wrap(err, "unable to get identifier") } @@ -299,6 +298,17 @@ func CommitActive(ctx context.Context, key, name string, usage snapshot.Usage) ( if err := bkt.Delete([]byte(key)); err != nil { return errors.Wrap(err, "failed to delete active") } + if ss.Parent != "" { + var ps db.Snapshot + if err := getSnapshot(bkt, ss.Parent, &ps); err != nil { + return errors.Wrap(err, "failed to get parent snapshot") + } + + // Updates parent back link to use new key + if err := pbkt.Put(parentKey(ps.ID, ss.ID), []byte(name)); err != nil { + return errors.Wrap(err, "failed to update parent link") + } + } id = fmt.Sprintf("%d", ss.ID) @@ -311,68 +321,28 @@ func CommitActive(ctx context.Context, key, name string, usage snapshot.Usage) ( return } -// nextSequence maintains the snapshot ids in the same space across namespaces -// to avoid collisions on the filesystem, which is typically not namespace -// aware. This will also be useful to ensure that snapshots can be used across -// namespaces in the future, by projecting parent relationships into an -// alternate namespace without fixing up identifiers. -func nextSequence(ctx context.Context) (uint64, error) { - t, ok := ctx.Value(transactionKey{}).(*boltFileTransactor) - if !ok { - return 0, ErrNoTransaction - } - - bkt := t.tx.Bucket(bucketKeyStorageVersion) - if bkt == nil { - return 0, errors.New("version bucket required for sequence") - } - - return bkt.NextSequence() -} - func withBucket(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error { - namespace, err := namespaces.NamespaceRequired(ctx) - if err != nil { - return err - } t, ok := ctx.Value(transactionKey{}).(*boltFileTransactor) if !ok { return ErrNoTransaction } - nbkt := t.tx.Bucket(bucketKeyStorageVersion) - if nbkt == nil { - return errors.Wrapf(errdefs.ErrNotFound, "bucket does not exist") - } - - bkt := nbkt.Bucket([]byte(namespace)) + bkt := t.tx.Bucket(bucketKeyStorageVersion) if bkt == nil { - return errors.Wrapf(errdefs.ErrNotFound, "namespace not available in snapshotter") + return errors.Wrap(errdefs.ErrNotFound, "bucket does not exist") } - return fn(ctx, bkt.Bucket(bucketKeySnapshot), bkt.Bucket(bucketKeyParents)) } func createBucketIfNotExists(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error { - namespace, err := namespaces.NamespaceRequired(ctx) - if err != nil { - return err - } - t, ok := ctx.Value(transactionKey{}).(*boltFileTransactor) if !ok { return ErrNoTransaction } - nbkt, err := t.tx.CreateBucketIfNotExists(bucketKeyStorageVersion) + bkt, err := t.tx.CreateBucketIfNotExists(bucketKeyStorageVersion) if err != nil { return errors.Wrap(err, "failed to create version bucket") } - - bkt, err := nbkt.CreateBucketIfNotExists([]byte(namespace)) - if err != nil { - return err - } - sbkt, err := bkt.CreateBucketIfNotExists(bucketKeySnapshot) if err != nil { return errors.Wrap(err, "failed to create snapshots bucket") diff --git a/snapshot/storage/metastore_test.go b/snapshot/storage/metastore_test.go index 6b74bb206..cf22e8811 100644 --- a/snapshot/storage/metastore_test.go +++ b/snapshot/storage/metastore_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/snapshot" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -46,7 +45,7 @@ func MetaStoreSuite(t *testing.T, name string, meta func(root string) (*MetaStor // makeTest creates a testsuite with a writable transaction func makeTest(t *testing.T, name string, metaFn metaFactory, fn testFunc) func(t *testing.T) { return func(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "testing-snapshot-metadata") + ctx := context.Background() tmpDir, err := ioutil.TempDir("", "metastore-test-"+name+"-") if err != nil { t.Fatal(err) diff --git a/snapshot/testsuite/testsuite.go b/snapshot/testsuite/testsuite.go index fee6bcdff..f447802d0 100644 --- a/snapshot/testsuite/testsuite.go +++ b/snapshot/testsuite/testsuite.go @@ -10,7 +10,6 @@ import ( "github.com/containerd/containerd/fs/fstest" "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/testutil" "github.com/stretchr/testify/assert" @@ -27,7 +26,7 @@ func SnapshotterSuite(t *testing.T, name string, snapshotterFn func(ctx context. func makeTest(t *testing.T, name string, snapshotterFn func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error), fn func(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string)) func(t *testing.T) { return func(t *testing.T) { - ctx := namespaces.WithNamespace(context.Background(), "snapshotter-test") + ctx := context.Background() restoreMask := clearMask() defer restoreMask() // Make two directories: a snapshotter root and a play area for the tests: