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/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 | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 	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)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										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/plugin" | ||||
| 	"github.com/containerd/containerd/snapshot" | ||||
| 	"github.com/containerd/containerd/snapshot/namespaced" | ||||
| 	protoempty "github.com/golang/protobuf/ptypes/empty" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"google.golang.org/grpc" | ||||
| @@ -44,7 +45,7 @@ type service struct { | ||||
|  | ||||
| func newService(snapshotter snapshot.Snapshotter, evts events.Poster) (*service, error) { | ||||
| 	return &service{ | ||||
| 		snapshotter: snapshotter, | ||||
| 		snapshotter: namespaced.NewSnapshotter(snapshotter), | ||||
| 		emitter:     evts, | ||||
| 	}, nil | ||||
| } | ||||
|   | ||||
| @@ -298,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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Derek McGowan
					Derek McGowan