Add namespaced snapshotter implementation
The namespaced snapshotter wraps an existing snapshotter and enforces namespace. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
		| @@ -10,7 +10,6 @@ import ( | |||||||
| 	"github.com/containerd/containerd/content" | 	"github.com/containerd/containerd/content" | ||||||
| 	"github.com/containerd/containerd/mount" | 	"github.com/containerd/containerd/mount" | ||||||
| 	"github.com/containerd/containerd/plugin" | 	"github.com/containerd/containerd/plugin" | ||||||
| 	"github.com/containerd/containerd/snapshot" |  | ||||||
| 	digest "github.com/opencontainers/go-digest" | 	digest "github.com/opencontainers/go-digest" | ||||||
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| @@ -23,35 +22,28 @@ func init() { | |||||||
| 		ID:   "base-diff", | 		ID:   "base-diff", | ||||||
| 		Requires: []plugin.PluginType{ | 		Requires: []plugin.PluginType{ | ||||||
| 			plugin.ContentPlugin, | 			plugin.ContentPlugin, | ||||||
| 			plugin.SnapshotPlugin, |  | ||||||
| 		}, | 		}, | ||||||
| 		Init: func(ic *plugin.InitContext) (interface{}, error) { | 		Init: func(ic *plugin.InitContext) (interface{}, error) { | ||||||
| 			c, err := ic.Get(plugin.ContentPlugin) | 			c, err := ic.Get(plugin.ContentPlugin) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			s, err := ic.Get(plugin.SnapshotPlugin) | 			return newBaseDiff(c.(content.Store)) | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 			return newBaseDiff(c.(content.Store), s.(snapshot.Snapshotter)) |  | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| type BaseDiff struct { | type BaseDiff struct { | ||||||
| 	store content.Store | 	store content.Store | ||||||
| 	snapshotter snapshot.Snapshotter |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var _ plugin.Differ = &BaseDiff{} | var _ plugin.Differ = &BaseDiff{} | ||||||
|  |  | ||||||
| var emptyDesc = ocispec.Descriptor{} | var emptyDesc = ocispec.Descriptor{} | ||||||
|  |  | ||||||
| func newBaseDiff(store content.Store, snapshotter snapshot.Snapshotter) (*BaseDiff, error) { | func newBaseDiff(store content.Store) (*BaseDiff, error) { | ||||||
| 	return &BaseDiff{ | 	return &BaseDiff{ | ||||||
| 		store: store, | 		store: store, | ||||||
| 		snapshotter: snapshotter, |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								metadata/bolt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								metadata/bolt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
| @@ -32,6 +32,7 @@ var ( | |||||||
| 	bucketKeyObjectIndexes    = []byte("indexes")    // reserved | 	bucketKeyObjectIndexes    = []byte("indexes")    // reserved | ||||||
| 	bucketKeyObjectImages     = []byte("images")     // stores image objects | 	bucketKeyObjectImages     = []byte("images")     // stores image objects | ||||||
| 	bucketKeyObjectContainers = []byte("containers") // stores container objects | 	bucketKeyObjectContainers = []byte("containers") // stores container objects | ||||||
|  | 	bucketKeyObjectSnapshots  = []byte("snapshots")  // stores snapshot references | ||||||
|  |  | ||||||
| 	bucketKeyDigest    = []byte("digest") | 	bucketKeyDigest    = []byte("digest") | ||||||
| 	bucketKeyMediaType = []byte("mediatype") | 	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 { | func getContainerBucket(tx *bolt.Tx, namespace, id string) *bolt.Bucket { | ||||||
| 	return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContainers, []byte(id)) | 	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)) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										272
									
								
								metadata/snapshot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								metadata/snapshot.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"github.com/containerd/containerd/mount" | 	"github.com/containerd/containerd/mount" | ||||||
| 	"github.com/containerd/containerd/plugin" | 	"github.com/containerd/containerd/plugin" | ||||||
| 	"github.com/containerd/containerd/snapshot" | 	"github.com/containerd/containerd/snapshot" | ||||||
|  | 	"github.com/containerd/containerd/snapshot/namespaced" | ||||||
| 	protoempty "github.com/golang/protobuf/ptypes/empty" | 	protoempty "github.com/golang/protobuf/ptypes/empty" | ||||||
| 	"golang.org/x/net/context" | 	"golang.org/x/net/context" | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| @@ -44,7 +45,7 @@ type service struct { | |||||||
|  |  | ||||||
| func newService(snapshotter snapshot.Snapshotter, evts events.Poster) (*service, error) { | func newService(snapshotter snapshot.Snapshotter, evts events.Poster) (*service, error) { | ||||||
| 	return &service{ | 	return &service{ | ||||||
| 		snapshotter: snapshotter, | 		snapshotter: namespaced.NewSnapshotter(snapshotter), | ||||||
| 		emitter:     evts, | 		emitter:     evts, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -298,6 +298,17 @@ func CommitActive(ctx context.Context, key, name string, usage snapshot.Usage) ( | |||||||
| 		if err := bkt.Delete([]byte(key)); err != nil { | 		if err := bkt.Delete([]byte(key)); err != nil { | ||||||
| 			return errors.Wrap(err, "failed to delete active") | 			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) | 		id = fmt.Sprintf("%d", ss.ID) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Derek McGowan
					Derek McGowan