Merge pull request #635 from dmcgowan/snapshot-metastore-context
Snapshot driver metastore
This commit is contained in:
commit
44d2ef4db3
13 changed files with 2223 additions and 538 deletions
|
@ -2,16 +2,17 @@ package btrfs
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/log"
|
||||
"github.com/docker/containerd/plugin"
|
||||
"github.com/docker/containerd/snapshot"
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
"github.com/docker/containerd/snapshot/storage/boltdb"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stevvooe/go-btrfs"
|
||||
)
|
||||
|
@ -25,11 +26,19 @@ func init() {
|
|||
Type: plugin.SnapshotPlugin,
|
||||
Config: &btrfsConfig{},
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
root := filepath.Join(ic.Root, "snapshot", "btrfs")
|
||||
conf := ic.Config.(*btrfsConfig)
|
||||
if conf.Device == "" {
|
||||
// TODO: check device for root
|
||||
return nil, errors.Errorf("btrfs requires \"device\" configuration")
|
||||
}
|
||||
return NewSnapshotter(conf.Device, filepath.Join(ic.Root, "snapshot", "btrfs"))
|
||||
|
||||
ms, err := boltdb.NewMetaStore(ic.Context, filepath.Join(root, "metadata.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSnapshotter(conf.Device, root, ms)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -37,30 +46,29 @@ func init() {
|
|||
type Snapshotter struct {
|
||||
device string // maybe we can resolve it with path?
|
||||
root string // root provides paths for internal storage.
|
||||
ms storage.MetaStore
|
||||
}
|
||||
|
||||
func NewSnapshotter(device, root string) (snapshot.Snapshotter, error) {
|
||||
func NewSnapshotter(device, root string, ms storage.MetaStore) (snapshot.Snapshotter, error) {
|
||||
var (
|
||||
active = filepath.Join(root, "active")
|
||||
snapshots = filepath.Join(root, "snapshots")
|
||||
parents = filepath.Join(root, "parents")
|
||||
index = filepath.Join(root, "index")
|
||||
names = filepath.Join(root, "names")
|
||||
)
|
||||
|
||||
for _, path := range []string{
|
||||
active,
|
||||
snapshots,
|
||||
parents,
|
||||
index,
|
||||
names,
|
||||
} {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Snapshotter{device: device, root: root}, nil
|
||||
return &Snapshotter{
|
||||
device: device,
|
||||
root: root,
|
||||
ms: ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stat returns the info for an active or committed snapshot by name or
|
||||
|
@ -69,123 +77,71 @@ func NewSnapshotter(device, root string) (snapshot.Snapshotter, error) {
|
|||
// Should be used for parent resolution, existence checks and to discern
|
||||
// the kind of snapshot.
|
||||
func (b *Snapshotter) Stat(ctx context.Context, key string) (snapshot.Info, error) {
|
||||
// resolve the snapshot out of the index.
|
||||
target, err := os.Readlink(filepath.Join(b.root, "index", hash(key)))
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
return snapshot.Info{}, errors.Errorf("snapshot %v not found", key)
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
return b.stat(target)
|
||||
defer t.Rollback()
|
||||
return b.ms.Stat(ctx, key)
|
||||
}
|
||||
|
||||
func (b *Snapshotter) stat(target string) (snapshot.Info, error) {
|
||||
var (
|
||||
parents = filepath.Join(b.root, "parents")
|
||||
names = filepath.Join(b.root, "names")
|
||||
namep = filepath.Join(names, filepath.Base(target))
|
||||
parentlink = filepath.Join(parents, filepath.Base(target))
|
||||
)
|
||||
|
||||
// grab information about the subvolume
|
||||
info, err := btrfs.SubvolInfo(target)
|
||||
// Walk the committed snapshots.
|
||||
func (b *Snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error {
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
return err
|
||||
}
|
||||
|
||||
// read the name out of the names!
|
||||
nameraw, err := ioutil.ReadFile(namep)
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
// resolve the parents path.
|
||||
parentp, err := os.Readlink(parentlink)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
// no parent!
|
||||
}
|
||||
|
||||
var parent string
|
||||
if parentp != "" {
|
||||
// okay, grab the basename of the parent and look up its name!
|
||||
parentnamep := filepath.Join(names, filepath.Base(parentp))
|
||||
|
||||
p, err := ioutil.ReadFile(parentnamep)
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
parent = string(p)
|
||||
}
|
||||
|
||||
kind := snapshot.KindCommitted
|
||||
if strings.HasPrefix(target, filepath.Join(b.root, "active")) {
|
||||
kind = snapshot.KindActive
|
||||
}
|
||||
|
||||
return snapshot.Info{
|
||||
Name: string(nameraw),
|
||||
Parent: parent,
|
||||
Readonly: info.Readonly,
|
||||
Kind: kind,
|
||||
}, nil
|
||||
|
||||
defer t.Rollback()
|
||||
return b.ms.Walk(ctx, fn)
|
||||
}
|
||||
|
||||
func (b *Snapshotter) Prepare(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
return b.makeActive(key, parent, false)
|
||||
return b.makeActive(ctx, key, parent, false)
|
||||
}
|
||||
|
||||
func (b *Snapshotter) View(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
return b.makeActive(key, parent, true)
|
||||
return b.makeActive(ctx, key, parent, true)
|
||||
}
|
||||
|
||||
func (b *Snapshotter) makeActive(key, parent string, readonly bool) ([]containerd.Mount, error) {
|
||||
var (
|
||||
active = filepath.Join(b.root, "active")
|
||||
snapshots = filepath.Join(b.root, "snapshots")
|
||||
parents = filepath.Join(b.root, "parents")
|
||||
index = filepath.Join(b.root, "index")
|
||||
names = filepath.Join(b.root, "names")
|
||||
keyh = hash(key)
|
||||
parenth = hash(parent)
|
||||
target = filepath.Join(active, keyh)
|
||||
namep = filepath.Join(names, keyh)
|
||||
indexlink = filepath.Join(index, keyh)
|
||||
parentlink = filepath.Join(parents, keyh)
|
||||
parentp = filepath.Join(snapshots, parenth) // parent must be restricted to snaps
|
||||
)
|
||||
|
||||
if parent == "" {
|
||||
// create new subvolume
|
||||
// btrfs subvolume create /dir
|
||||
if err := btrfs.SubvolCreate(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// btrfs subvolume snapshot /parent /subvol
|
||||
if err := btrfs.SubvolSnapshot(target, parentp, readonly); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.Symlink(parentp, parentlink); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (b *Snapshotter) makeActive(ctx context.Context, key, parent string, readonly bool) ([]containerd.Mount, error) {
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// write in the name
|
||||
if err := ioutil.WriteFile(namep, []byte(key), 0644); err != nil {
|
||||
a, err := b.ms.CreateActive(ctx, key, parent, readonly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.Symlink(target, indexlink); err != nil {
|
||||
target := filepath.Join(b.root, "active", a.ID)
|
||||
|
||||
if len(a.ParentIDs) == 0 {
|
||||
// create new subvolume
|
||||
// btrfs subvolume create /dir
|
||||
if err = btrfs.SubvolCreate(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
parentp := filepath.Join(b.root, "snapshots", a.ParentIDs[0])
|
||||
// btrfs subvolume snapshot /parent /subvol
|
||||
if err = btrfs.SubvolSnapshot(target, parentp, a.Readonly); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = t.Commit()
|
||||
t = nil
|
||||
if err != nil {
|
||||
if derr := btrfs.SubvolDelete(target); derr != nil {
|
||||
log.G(ctx).WithError(derr).WithField("subvolume", target).Error("Failed to delete subvolume")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -218,82 +174,43 @@ func (b *Snapshotter) mounts(dir string) ([]containerd.Mount, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b *Snapshotter) Commit(ctx context.Context, name, key string) error {
|
||||
var (
|
||||
active = filepath.Join(b.root, "active")
|
||||
snapshots = filepath.Join(b.root, "snapshots")
|
||||
index = filepath.Join(b.root, "index")
|
||||
parents = filepath.Join(b.root, "parents")
|
||||
names = filepath.Join(b.root, "names")
|
||||
keyh = hash(key)
|
||||
nameh = hash(name)
|
||||
dir = filepath.Join(active, keyh)
|
||||
target = filepath.Join(snapshots, nameh)
|
||||
keynamep = filepath.Join(names, keyh)
|
||||
namep = filepath.Join(names, nameh)
|
||||
keyparentlink = filepath.Join(parents, keyh)
|
||||
parentlink = filepath.Join(parents, nameh)
|
||||
keyindexlink = filepath.Join(index, keyh)
|
||||
indexlink = filepath.Join(index, nameh)
|
||||
)
|
||||
|
||||
info, err := btrfs.SubvolInfo(dir)
|
||||
func (b *Snapshotter) Commit(ctx context.Context, name, key string) (err error) {
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if info.Readonly {
|
||||
return fmt.Errorf("may not commit view snapshot %q", dir)
|
||||
}
|
||||
|
||||
// look up the parent information to make sure we have the right parent link.
|
||||
parentp, err := os.Readlink(keyparentlink)
|
||||
id, err := b.ms.Commit(ctx, key, name)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to commit")
|
||||
}
|
||||
|
||||
source := filepath.Join(b.root, "active", id)
|
||||
target := filepath.Join(b.root, "snapshots", id)
|
||||
|
||||
if err := btrfs.SubvolSnapshot(target, source, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
t = nil
|
||||
if err != nil {
|
||||
if derr := btrfs.SubvolDelete(target); derr != nil {
|
||||
log.G(ctx).WithError(derr).WithField("subvolume", target).Error("Failed to delete subvolume")
|
||||
}
|
||||
|
||||
// we have no parent!
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(snapshots, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := btrfs.SubvolSnapshot(target, dir, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove the key name path as we no longer need it.
|
||||
if err := os.Remove(keynamep); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(keyindexlink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(namep, []byte(name), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parentp != "" {
|
||||
// move over the parent link into the commit name.
|
||||
if err := os.Rename(keyparentlink, parentlink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(stevvooe): For this to not break horribly, we should really
|
||||
// start taking a full lock. We are going to move all this metadata
|
||||
// into common storage, so let's not fret over it for now.
|
||||
}
|
||||
|
||||
if err := os.Symlink(target, indexlink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := btrfs.SubvolDelete(dir); err != nil {
|
||||
return errors.Wrapf(err, "delete subvol failed on %v", dir)
|
||||
if derr := btrfs.SubvolDelete(source); derr != nil {
|
||||
// Log as warning, only needed for cleanup, will not cause name collision
|
||||
log.G(ctx).WithError(derr).WithField("subvolume", source).Warn("Failed to delete subvolume")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -304,55 +221,91 @@ func (b *Snapshotter) Commit(ctx context.Context, name, key string) error {
|
|||
//
|
||||
// This can be used to recover mounts after calling View or Prepare.
|
||||
func (b *Snapshotter) Mounts(ctx context.Context, key string) ([]containerd.Mount, error) {
|
||||
dir := filepath.Join(b.root, "active", hash(key))
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := b.ms.GetActive(ctx, key)
|
||||
t.Rollback()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get active snapshot")
|
||||
}
|
||||
dir := filepath.Join(b.root, "active", a.ID)
|
||||
return b.mounts(dir)
|
||||
}
|
||||
|
||||
// Remove abandons the transaction identified by key. All resources
|
||||
// associated with the key will be removed.
|
||||
func (b *Snapshotter) Remove(ctx context.Context, key string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (b *Snapshotter) Remove(ctx context.Context, key string) (err error) {
|
||||
var (
|
||||
source, removed string
|
||||
readonly bool
|
||||
)
|
||||
|
||||
// Walk the committed snapshots.
|
||||
func (b *Snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error {
|
||||
// TODO(stevvooe): Copy-pasted almost verbatim from overlay. Really need to
|
||||
// unify the metadata for snapshot implementations.
|
||||
root := filepath.Join(b.root, "index")
|
||||
return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == root {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
// only follow links
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
ctx, t, err := b.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
}
|
||||
|
||||
si, err := b.stat(target)
|
||||
if removed != "" {
|
||||
if derr := btrfs.SubvolDelete(removed); derr != nil {
|
||||
log.G(ctx).WithError(derr).WithField("subvolume", removed).Warn("Failed to delete subvolume")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
id, k, err := b.ms.Remove(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to remove snapshot")
|
||||
}
|
||||
|
||||
if k == snapshot.KindActive {
|
||||
source = filepath.Join(b.root, "active", id)
|
||||
|
||||
info, err := btrfs.SubvolInfo(source)
|
||||
if err != nil {
|
||||
source = ""
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(ctx, si); err != nil {
|
||||
return err
|
||||
readonly = info.Readonly
|
||||
removed = filepath.Join(b.root, "active", "rm-"+id)
|
||||
} else {
|
||||
source = filepath.Join(b.root, "snapshots", id)
|
||||
removed = filepath.Join(b.root, "snapshots", "rm-"+id)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
if err := btrfs.SubvolSnapshot(removed, source, readonly); err != nil {
|
||||
removed = ""
|
||||
return err
|
||||
}
|
||||
|
||||
if err := btrfs.SubvolDelete(source); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove snapshot %v", source)
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
t = nil
|
||||
if err != nil {
|
||||
// Attempt to restore source
|
||||
if err1 := btrfs.SubvolSnapshot(source, removed, readonly); err1 != nil {
|
||||
log.G(ctx).WithFields(logrus.Fields{
|
||||
logrus.ErrorKey: err1,
|
||||
"subvolume": source,
|
||||
"renamed": removed,
|
||||
}).Error("Failed to restore subvolume from renamed")
|
||||
// Keep removed to allow for manual restore
|
||||
removed = ""
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func hash(k string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum224([]byte(k)))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/snapshot"
|
||||
"github.com/docker/containerd/snapshot/storage/boltdb"
|
||||
"github.com/docker/containerd/snapshot/testsuite"
|
||||
"github.com/docker/containerd/testutil"
|
||||
)
|
||||
|
@ -19,11 +20,14 @@ const (
|
|||
mib = 1024 * 1024
|
||||
)
|
||||
|
||||
func TestBtrfs(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
testsuite.SnapshotterSuite(t, "Btrfs", func(root string) (snapshot.Snapshotter, func(), error) {
|
||||
func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshot.Snapshotter, func(), error) {
|
||||
return func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error) {
|
||||
device := setupBtrfsLoopbackDevice(t, root)
|
||||
snapshotter, err := NewSnapshotter(device.deviceName, root)
|
||||
store, err := boltdb.NewMetaStore(ctx, filepath.Join(root, "metadata.db"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
snapshotter, err := NewSnapshotter(device.deviceName, root, store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -31,34 +35,39 @@ func TestBtrfs(t *testing.T) {
|
|||
return snapshotter, func() {
|
||||
device.remove(t)
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBtrfs(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
testsuite.SnapshotterSuite(t, "Btrfs", boltSnapshotter(t))
|
||||
}
|
||||
|
||||
func TestBtrfsMounts(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
ctx := context.TODO()
|
||||
ctx := context.Background()
|
||||
|
||||
// create temporary directory for mount point
|
||||
mountPoint, err := ioutil.TempDir("", "containerd-btrfs-test")
|
||||
if err != nil {
|
||||
t.Fatal("could not create mount point for btrfs test", err)
|
||||
}
|
||||
defer os.RemoveAll(mountPoint)
|
||||
t.Log("temporary mount point created", mountPoint)
|
||||
|
||||
device := setupBtrfsLoopbackDevice(t, mountPoint)
|
||||
defer device.remove(t)
|
||||
|
||||
root, err := ioutil.TempDir(device.mountPoint, "TestBtrfsPrepare-")
|
||||
root, err := ioutil.TempDir(mountPoint, "TestBtrfsPrepare-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
target := filepath.Join(root, "test")
|
||||
b, err := NewSnapshotter(device.deviceName, root)
|
||||
b, c, err := boltSnapshotter(t)(ctx, root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c()
|
||||
|
||||
target := filepath.Join(root, "test")
|
||||
mounts, err := b.Prepare(ctx, target, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
44
snapshot/errors.go
Normal file
44
snapshot/errors.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package snapshot
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var (
|
||||
// ErrSnapshotNotExist is returned when a snapshot cannot be found
|
||||
ErrSnapshotNotExist = errors.New("snapshot does not exist")
|
||||
|
||||
// ErrSnapshotExist is returned when an operation to create a snapshot
|
||||
// encounters a snapshot with the same key
|
||||
ErrSnapshotExist = errors.New("snapshot already exists")
|
||||
|
||||
// ErrSnapshotNotActive is returned when a request which requires an
|
||||
// active snapshot encounters a non-active snapshot.
|
||||
ErrSnapshotNotActive = errors.New("snapshot is not active")
|
||||
|
||||
// ErrSnapshotNotCommitted is returned when a request which requires a
|
||||
// committed snapshot encounters a non-committed snapshot.
|
||||
ErrSnapshotNotCommitted = errors.New("snapshot is not committed")
|
||||
)
|
||||
|
||||
// IsNotExist returns whether the error represents that a snapshot
|
||||
// was not found.
|
||||
func IsNotExist(err error) bool {
|
||||
return errors.Cause(err) == ErrSnapshotNotExist
|
||||
}
|
||||
|
||||
// IsExist returns whether the error represents whether a snapshot
|
||||
// already exists using a provided key.
|
||||
func IsExist(err error) bool {
|
||||
return errors.Cause(err) == ErrSnapshotExist
|
||||
}
|
||||
|
||||
// IsNotActive returns whether the error represents a request
|
||||
// for a non active snapshot when an active snapshot is expected.
|
||||
func IsNotActive(err error) bool {
|
||||
return errors.Cause(err) == ErrSnapshotNotActive
|
||||
}
|
||||
|
||||
// IsNotCommitted returns whether the error represents a request
|
||||
// for a non committed snapshot when a committed snapshot is expected.
|
||||
func IsNotCommitted(err error) bool {
|
||||
return errors.Cause(err) == ErrSnapshotNotCommitted
|
||||
}
|
|
@ -7,12 +7,13 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/log"
|
||||
"github.com/docker/containerd/plugin"
|
||||
"github.com/docker/containerd/snapshot"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
"github.com/docker/containerd/snapshot/storage/boltdb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -20,32 +21,40 @@ func init() {
|
|||
plugin.Register("snapshot-overlay", &plugin.Registration{
|
||||
Type: plugin.SnapshotPlugin,
|
||||
Init: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
return NewSnapshotter(filepath.Join(ic.Root, "snapshot", "overlay"))
|
||||
root := filepath.Join(ic.Root, "snapshot", "overlay")
|
||||
ms, err := boltdb.NewMetaStore(ic.Context, filepath.Join(root, "metadata.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSnapshotter(root, ms)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Snapshotter struct {
|
||||
root string
|
||||
links *cache
|
||||
root string
|
||||
ms storage.MetaStore
|
||||
}
|
||||
|
||||
func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
|
||||
type activeSnapshot struct {
|
||||
id string
|
||||
name string
|
||||
parentID interface{}
|
||||
readonly bool
|
||||
}
|
||||
|
||||
func NewSnapshotter(root string, ms storage.MetaStore) (snapshot.Snapshotter, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range []string{
|
||||
"committed", // committed snapshots
|
||||
"active", // active snapshots
|
||||
"index", // snapshots by hashed name
|
||||
} {
|
||||
if err := os.MkdirAll(filepath.Join(root, p), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(root, "snapshots"), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Snapshotter{
|
||||
root: root,
|
||||
links: newCache(),
|
||||
root: root,
|
||||
ms: ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -55,86 +64,20 @@ func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
|
|||
// Should be used for parent resolution, existence checks and to discern
|
||||
// the kind of snapshot.
|
||||
func (o *Snapshotter) Stat(ctx context.Context, key string) (snapshot.Info, error) {
|
||||
path, err := o.links.get(filepath.Join(o.root, "index", hash(key)))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
return snapshot.Info{}, errors.Errorf("snapshot %v not found", key)
|
||||
}
|
||||
|
||||
// TODO(stevvooe): We don't confirm the name to avoid the lookup cost.
|
||||
return o.stat(path)
|
||||
}
|
||||
|
||||
func (o *Snapshotter) stat(path string) (snapshot.Info, error) {
|
||||
ppath, err := o.links.get(filepath.Join(path, "parent"))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
// no parent
|
||||
}
|
||||
|
||||
kp, err := ioutil.ReadFile(filepath.Join(path, "name"))
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
var parent string
|
||||
if ppath != "" {
|
||||
p, err := ioutil.ReadFile(filepath.Join(ppath, "name"))
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
parent = string(p)
|
||||
}
|
||||
|
||||
ro := true
|
||||
kind := snapshot.KindCommitted
|
||||
if strings.HasPrefix(path, filepath.Join(o.root, "active")) {
|
||||
// TODO(stevvooe): Maybe there is a better way?
|
||||
kind = snapshot.KindActive
|
||||
|
||||
// TODO(stevvooe): We haven't introduced this to overlay yet.
|
||||
// We'll add it when we add tests for it.
|
||||
ro = false
|
||||
}
|
||||
|
||||
return snapshot.Info{
|
||||
Name: string(kp),
|
||||
Parent: parent,
|
||||
Kind: kind,
|
||||
Readonly: ro,
|
||||
}, nil
|
||||
defer t.Rollback()
|
||||
return o.ms.Stat(ctx, key)
|
||||
}
|
||||
|
||||
func (o *Snapshotter) Prepare(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
active, err := o.newActiveDir(key, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parent != "" {
|
||||
if err := active.setParent(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return active.mounts(o.links)
|
||||
return o.createActive(ctx, key, parent, false)
|
||||
}
|
||||
|
||||
func (o *Snapshotter) View(ctx context.Context, key, parent string) ([]containerd.Mount, error) {
|
||||
active, err := o.newActiveDir(key, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parent != "" {
|
||||
if err := active.setParent(parent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return active.mounts(o.links)
|
||||
return o.createActive(ctx, key, parent, true)
|
||||
}
|
||||
|
||||
// Mounts returns the mounts for the transaction identified by key. Can be
|
||||
|
@ -142,263 +85,208 @@ func (o *Snapshotter) View(ctx context.Context, key, parent string) ([]container
|
|||
//
|
||||
// This can be used to recover mounts after calling View or Prepare.
|
||||
func (o *Snapshotter) Mounts(ctx context.Context, key string) ([]containerd.Mount, error) {
|
||||
active := o.getActive(key)
|
||||
return active.mounts(o.links)
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
active, err := o.ms.GetActive(ctx, key)
|
||||
t.Rollback()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get active mount")
|
||||
}
|
||||
return o.mounts(active), nil
|
||||
}
|
||||
|
||||
func (o *Snapshotter) Commit(ctx context.Context, name, key string) error {
|
||||
active := o.getActive(key)
|
||||
return active.commit(name, o.links)
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := o.ms.Commit(ctx, key, name); err != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
return errors.Wrap(err, "failed to commit snapshot")
|
||||
}
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
// Remove abandons the transaction identified by key. All resources
|
||||
// associated with the key will be removed.
|
||||
func (o *Snapshotter) Remove(ctx context.Context, key string) error {
|
||||
panic("not implemented")
|
||||
func (o *Snapshotter) Remove(ctx context.Context, key string) (err error) {
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
id, _, err := o.ms.Remove(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to remove")
|
||||
}
|
||||
|
||||
path := filepath.Join(o.root, "snapshots", id)
|
||||
renamed := filepath.Join(o.root, "snapshots", "rm-"+id)
|
||||
if err := os.Rename(path, renamed); err != nil {
|
||||
return errors.Wrap(err, "failed to rename")
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
t = nil
|
||||
if err != nil {
|
||||
if err1 := os.Rename(renamed, path); err1 != nil {
|
||||
// May cause inconsistent data on disk
|
||||
log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit")
|
||||
}
|
||||
return errors.Wrap(err, "failed to commit")
|
||||
}
|
||||
if err := os.RemoveAll(renamed); err != nil {
|
||||
// Must be cleaned up, any "rm-*" could be removed if no active transactions
|
||||
log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk the committed snapshots.
|
||||
func (o *Snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error {
|
||||
root := filepath.Join(o.root, "index")
|
||||
return filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == root {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
// only follow links
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
target, err := o.links.get(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
si, err := o.stat(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(ctx, si); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.Rollback()
|
||||
return o.ms.Walk(ctx, fn)
|
||||
}
|
||||
|
||||
func (o *Snapshotter) newActiveDir(key string, readonly bool) (*activeDir, error) {
|
||||
func (o *Snapshotter) createActive(ctx context.Context, key, parent string, readonly bool) ([]containerd.Mount, error) {
|
||||
var (
|
||||
path = filepath.Join(o.root, "active", hash(key))
|
||||
name = filepath.Join(path, "name")
|
||||
indexlink = filepath.Join(o.root, "index", hash(key))
|
||||
path string
|
||||
snapshotDir = filepath.Join(o.root, "snapshots")
|
||||
)
|
||||
a := &activeDir{
|
||||
path: path,
|
||||
committedDir: filepath.Join(o.root, "committed"),
|
||||
indexlink: indexlink,
|
||||
|
||||
td, err := ioutil.TempDir(snapshotDir, "new-")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if td != "" {
|
||||
if err1 := os.RemoveAll(td); err1 != nil {
|
||||
err = errors.Wrapf(err, "remove failed: %v", err1)
|
||||
}
|
||||
}
|
||||
if path != "" {
|
||||
if err1 := os.RemoveAll(path); err1 != nil {
|
||||
err = errors.Wrapf(err, "failed to remove path: %v", err1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err = os.MkdirAll(filepath.Join(td, "fs"), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !readonly {
|
||||
for _, p := range []string{
|
||||
"work",
|
||||
"fs",
|
||||
} {
|
||||
if err := os.MkdirAll(filepath.Join(path, p), 0700); err != nil {
|
||||
a.delete()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := os.MkdirAll(filepath.Join(path, "fs"), 0700); err != nil {
|
||||
a.delete()
|
||||
if err = os.MkdirAll(filepath.Join(td, "work"), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(name, []byte(key), 0644); err != nil {
|
||||
a.delete()
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// link from namespace
|
||||
if err := os.Symlink(path, indexlink); err != nil {
|
||||
a.delete()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (o *Snapshotter) getActive(key string) *activeDir {
|
||||
return &activeDir{
|
||||
path: filepath.Join(o.root, "active", hash(key)),
|
||||
committedDir: filepath.Join(o.root, "committed"),
|
||||
indexlink: filepath.Join(o.root, "index", hash(key)),
|
||||
}
|
||||
}
|
||||
|
||||
func hash(k string) string {
|
||||
return digest.FromString(k).Hex()
|
||||
}
|
||||
|
||||
type activeDir struct {
|
||||
committedDir string
|
||||
path string
|
||||
indexlink string
|
||||
}
|
||||
|
||||
func (a *activeDir) delete() error {
|
||||
return os.RemoveAll(a.path)
|
||||
}
|
||||
|
||||
func (a *activeDir) setParent(name string) error {
|
||||
return os.Symlink(filepath.Join(a.committedDir, hash(name)), filepath.Join(a.path, "parent"))
|
||||
}
|
||||
|
||||
func (a *activeDir) commit(name string, c *cache) error {
|
||||
if _, err := os.Stat(filepath.Join(a.path, "fs")); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("cannot commit view")
|
||||
active, err := o.ms.CreateActive(ctx, key, parent, readonly)
|
||||
if err != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
return err
|
||||
return nil, errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
|
||||
// TODO(stevvooe): This doesn't quite meet the current model. The new model
|
||||
// is to copy all of this out and let the transaction continue. We don't
|
||||
// really have tests for it yet, but this will be the spot to fix it.
|
||||
//
|
||||
// Nothing should be removed until remove is called on the active
|
||||
// transaction.
|
||||
if err := os.RemoveAll(filepath.Join(a.path, "work")); err != nil {
|
||||
return err
|
||||
path = filepath.Join(snapshotDir, active.ID)
|
||||
if err := os.Rename(td, path); err != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("Failure rolling back transaction")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to rename")
|
||||
}
|
||||
td = ""
|
||||
|
||||
if err := t.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "commit failed")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(a.path, "name"), []byte(name), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.invalidate(a.path) // clears parent cache, since we end up moving.
|
||||
c.invalidate(filepath.Join(a.path, "parent"))
|
||||
c.invalidate(a.indexlink)
|
||||
|
||||
committed := filepath.Join(a.committedDir, hash(name))
|
||||
if err := os.Rename(a.path, committed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(a.indexlink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
indexlink := filepath.Join(filepath.Dir(a.indexlink), hash(name))
|
||||
return os.Symlink(committed, indexlink)
|
||||
return o.mounts(active), nil
|
||||
}
|
||||
|
||||
func (a *activeDir) mounts(c *cache) ([]containerd.Mount, error) {
|
||||
var (
|
||||
parents []string
|
||||
err error
|
||||
current = a.path
|
||||
)
|
||||
for {
|
||||
if current, err = c.get(filepath.Join(current, "parent")); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
parents = append(parents, filepath.Join(current, "fs"))
|
||||
}
|
||||
if len(parents) == 0 {
|
||||
func (o *Snapshotter) mounts(active storage.Active) []containerd.Mount {
|
||||
if len(active.ParentIDs) == 0 {
|
||||
// if we only have one layer/no parents then just return a bind mount as overlay
|
||||
// will not work
|
||||
roFlag := "rw"
|
||||
if _, err := os.Stat(filepath.Join(a.path, "work")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if active.Readonly {
|
||||
roFlag = "ro"
|
||||
}
|
||||
|
||||
return []containerd.Mount{
|
||||
{
|
||||
Source: filepath.Join(a.path, "fs"),
|
||||
Source: o.upperPath(active.ID),
|
||||
Type: "bind",
|
||||
Options: []string{
|
||||
roFlag,
|
||||
"rbind",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
var options []string
|
||||
|
||||
if _, err := os.Stat(filepath.Join(a.path, "work")); err == nil {
|
||||
if !active.Readonly {
|
||||
options = append(options,
|
||||
fmt.Sprintf("workdir=%s", filepath.Join(a.path, "work")),
|
||||
fmt.Sprintf("upperdir=%s", filepath.Join(a.path, "fs")),
|
||||
fmt.Sprintf("workdir=%s", o.workPath(active.ID)),
|
||||
fmt.Sprintf("upperdir=%s", o.upperPath(active.ID)),
|
||||
)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
} else if len(parents) == 1 {
|
||||
} else if len(active.ParentIDs) == 1 {
|
||||
return []containerd.Mount{
|
||||
{
|
||||
Source: parents[0],
|
||||
Source: o.upperPath(active.ParentIDs[0]),
|
||||
Type: "bind",
|
||||
Options: []string{
|
||||
"ro",
|
||||
"rbind",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parents, ":")))
|
||||
parentPaths := make([]string, len(active.ParentIDs))
|
||||
for i := range active.ParentIDs {
|
||||
parentPaths[i] = o.upperPath(active.ParentIDs[i])
|
||||
}
|
||||
|
||||
options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":")))
|
||||
return []containerd.Mount{
|
||||
{
|
||||
Type: "overlay",
|
||||
Source: "overlay",
|
||||
Options: options,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newCache() *cache {
|
||||
return &cache{
|
||||
links: make(map[string]string),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
mu sync.Mutex
|
||||
links map[string]string
|
||||
func (o *Snapshotter) upperPath(id string) string {
|
||||
return filepath.Join(o.root, "snapshots", id, "fs")
|
||||
}
|
||||
|
||||
func (c *cache) get(path string) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
target, ok := c.links[path]
|
||||
if !ok {
|
||||
link, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.links[path], target = link, link
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (c *cache) invalidate(path string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
delete(c.links, path)
|
||||
func (o *Snapshotter) workPath(id string) string {
|
||||
return filepath.Join(o.root, "snapshots", id, "work")
|
||||
}
|
||||
|
|
|
@ -11,20 +11,27 @@ import (
|
|||
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/snapshot"
|
||||
"github.com/docker/containerd/snapshot/storage/boltdb"
|
||||
"github.com/docker/containerd/snapshot/testsuite"
|
||||
"github.com/docker/containerd/testutil"
|
||||
)
|
||||
|
||||
func boltSnapshotter(ctx context.Context, root string) (snapshot.Snapshotter, func(), error) {
|
||||
store, err := boltdb.NewMetaStore(ctx, filepath.Join(root, "metadata.db"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
snapshotter, err := NewSnapshotter(root, store)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return snapshotter, func() {}, nil
|
||||
}
|
||||
|
||||
func TestOverlay(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
testsuite.SnapshotterSuite(t, "Overlay", func(root string) (snapshot.Snapshotter, func(), error) {
|
||||
snapshotter, err := NewSnapshotter(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return snapshotter, func() {}, nil
|
||||
})
|
||||
testsuite.SnapshotterSuite(t, "Overlay", boltSnapshotter)
|
||||
}
|
||||
|
||||
func TestOverlayMounts(t *testing.T) {
|
||||
|
@ -34,7 +41,7 @@ func TestOverlayMounts(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
o, err := NewSnapshotter(root)
|
||||
o, _, err := boltSnapshotter(ctx, root)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -51,7 +58,7 @@ func TestOverlayMounts(t *testing.T) {
|
|||
if m.Type != "bind" {
|
||||
t.Errorf("mount type should be bind but received %q", m.Type)
|
||||
}
|
||||
expected := filepath.Join(root, "active", hash("/tmp/test"), "fs")
|
||||
expected := filepath.Join(root, "snapshots", "1", "fs")
|
||||
if m.Source != expected {
|
||||
t.Errorf("expected source %q but received %q", expected, m.Source)
|
||||
}
|
||||
|
@ -70,7 +77,7 @@ func TestOverlayCommit(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
o, err := NewSnapshotter(root)
|
||||
o, _, err := boltSnapshotter(ctx, root)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -99,7 +106,7 @@ func TestOverlayOverlayMount(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
o, err := NewSnapshotter(root)
|
||||
o, _, err := boltSnapshotter(ctx, root)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -129,11 +136,10 @@ func TestOverlayOverlayMount(t *testing.T) {
|
|||
t.Errorf("expected source %q but received %q", "overlay", m.Source)
|
||||
}
|
||||
var (
|
||||
ah = hash("/tmp/layer2")
|
||||
sh = hash("base")
|
||||
work = "workdir=" + filepath.Join(root, "active", ah, "work")
|
||||
upper = "upperdir=" + filepath.Join(root, "active", ah, "fs")
|
||||
lower = "lowerdir=" + filepath.Join(root, "committed", sh, "fs")
|
||||
bp = getBasePath(ctx, o, root, "/tmp/layer2")
|
||||
work = "workdir=" + filepath.Join(bp, "work")
|
||||
upper = "upperdir=" + filepath.Join(bp, "fs")
|
||||
lower = "lowerdir=" + getParents(ctx, o, root, "/tmp/layer2")[0]
|
||||
)
|
||||
for i, v := range []string{
|
||||
work,
|
||||
|
@ -146,6 +152,40 @@ func TestOverlayOverlayMount(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func getBasePath(ctx context.Context, sn snapshot.Snapshotter, root, key string) string {
|
||||
o := sn.(*Snapshotter)
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Rollback()
|
||||
|
||||
active, err := o.ms.GetActive(ctx, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return filepath.Join(root, "snapshots", active.ID)
|
||||
}
|
||||
|
||||
func getParents(ctx context.Context, sn snapshot.Snapshotter, root, key string) []string {
|
||||
o := sn.(*Snapshotter)
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Rollback()
|
||||
active, err := o.ms.GetActive(ctx, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
parents := make([]string, len(active.ParentIDs))
|
||||
for i := range active.ParentIDs {
|
||||
parents[i] = filepath.Join(root, "snapshots", active.ParentIDs[i], "fs")
|
||||
}
|
||||
return parents
|
||||
}
|
||||
|
||||
func TestOverlayOverlayRead(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
ctx := context.TODO()
|
||||
|
@ -154,7 +194,7 @@ func TestOverlayOverlayRead(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
o, err := NewSnapshotter(root)
|
||||
o, _, err := boltSnapshotter(ctx, root)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -206,7 +246,7 @@ func TestOverlayView(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
o, err := NewSnapshotter(root)
|
||||
o, _, err := boltSnapshotter(ctx, root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -228,7 +268,7 @@ func TestOverlayView(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(root, "active", hash(key), "fs", "foo"), []byte("hi, again"), 0660); err != nil {
|
||||
if err := ioutil.WriteFile(filepath.Join(getParents(ctx, o, root, "/tmp/top")[0], "foo"), []byte("hi, again"), 0660); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := o.Commit(ctx, "top", key); err != nil {
|
||||
|
@ -246,7 +286,7 @@ func TestOverlayView(t *testing.T) {
|
|||
if m.Type != "bind" {
|
||||
t.Errorf("mount type should be bind but received %q", m.Type)
|
||||
}
|
||||
expected := filepath.Join(root, "committed", hash("base"), "fs")
|
||||
expected := getParents(ctx, o, root, "/tmp/view1")[0]
|
||||
if m.Source != expected {
|
||||
t.Errorf("expected source %q but received %q", expected, m.Source)
|
||||
}
|
||||
|
@ -274,7 +314,8 @@ func TestOverlayView(t *testing.T) {
|
|||
if len(m.Options) != 1 {
|
||||
t.Errorf("expected 1 mount option but got %d", len(m.Options))
|
||||
}
|
||||
expected = fmt.Sprintf("lowerdir=%s:%s", filepath.Join(root, "committed", hash("top"), "fs"), filepath.Join(root, "committed", hash("base"), "fs"))
|
||||
lowers := getParents(ctx, o, root, "/tmp/view2")
|
||||
expected = fmt.Sprintf("lowerdir=%s:%s", lowers[0], lowers[1])
|
||||
if m.Options[0] != expected {
|
||||
t.Errorf("expected option %q but received %q", expected, m.Options[0])
|
||||
}
|
||||
|
|
396
snapshot/storage/boltdb/bolt.go
Normal file
396
snapshot/storage/boltdb/bolt.go
Normal file
|
@ -0,0 +1,396 @@
|
|||
package boltdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/docker/containerd/snapshot"
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
bucketKeyStorageVersion = []byte("v1")
|
||||
bucketKeySnapshot = []byte("snapshots")
|
||||
bucketKeyParents = []byte("parents")
|
||||
)
|
||||
|
||||
type boltFileTransactor struct {
|
||||
db *bolt.DB
|
||||
tx *bolt.Tx
|
||||
}
|
||||
|
||||
type boltMetastore struct {
|
||||
dbfile string
|
||||
}
|
||||
|
||||
// NewMetaStore returns a snapshot MetaStore for storage of metadata related to
|
||||
// a snapshot driver backed by a bolt file database. This implementation is
|
||||
// strongly consistent and does all metadata changes in a transaction to prevent
|
||||
// against process crashes causing inconsistent metadata state.
|
||||
func NewMetaStore(ctx context.Context, dbfile string) (storage.MetaStore, error) {
|
||||
return &boltMetastore{
|
||||
dbfile: dbfile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ms *boltFileTransactor) Rollback() error {
|
||||
defer ms.db.Close()
|
||||
return ms.tx.Rollback()
|
||||
}
|
||||
|
||||
func (ms *boltFileTransactor) Commit() error {
|
||||
defer ms.db.Close()
|
||||
return ms.tx.Commit()
|
||||
}
|
||||
|
||||
type transactionKey struct{}
|
||||
|
||||
func (ms *boltMetastore) TransactionContext(ctx context.Context, writable bool) (context.Context, storage.Transactor, error) {
|
||||
db, err := bolt.Open(ms.dbfile, 0600, nil)
|
||||
if err != nil {
|
||||
return ctx, nil, errors.Wrap(err, "failed to open database file")
|
||||
}
|
||||
|
||||
tx, err := db.Begin(writable)
|
||||
if err != nil {
|
||||
return ctx, nil, errors.Wrap(err, "failed to start transaction")
|
||||
}
|
||||
|
||||
t := &boltFileTransactor{
|
||||
db: db,
|
||||
tx: tx,
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, transactionKey{}, t)
|
||||
|
||||
return ctx, t, nil
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) withBucket(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
|
||||
t, ok := ctx.Value(transactionKey{}).(*boltFileTransactor)
|
||||
if !ok {
|
||||
return errors.Errorf("no transaction in context")
|
||||
}
|
||||
bkt := t.tx.Bucket(bucketKeyStorageVersion)
|
||||
if bkt == nil {
|
||||
return errors.Wrap(snapshot.ErrSnapshotNotExist, "bucket does not exist")
|
||||
}
|
||||
return fn(ctx, bkt.Bucket(bucketKeySnapshot), bkt.Bucket(bucketKeyParents))
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) createBucketIfNotExists(ctx context.Context, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
|
||||
t, ok := ctx.Value(transactionKey{}).(*boltFileTransactor)
|
||||
if !ok {
|
||||
return errors.Errorf("no transaction in context")
|
||||
}
|
||||
|
||||
bkt, err := t.tx.CreateBucketIfNotExists(bucketKeyStorageVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create version bucket")
|
||||
}
|
||||
sbkt, err := bkt.CreateBucketIfNotExists(bucketKeySnapshot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create snapshots bucket")
|
||||
}
|
||||
pbkt, err := bkt.CreateBucketIfNotExists(bucketKeyParents)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create snapshots bucket")
|
||||
}
|
||||
return fn(ctx, sbkt, pbkt)
|
||||
}
|
||||
|
||||
func fromProtoKind(k Kind) snapshot.Kind {
|
||||
if k == KindActive {
|
||||
return snapshot.KindActive
|
||||
}
|
||||
return snapshot.KindCommitted
|
||||
}
|
||||
|
||||
// parentKey returns a composite key of the parent and child identifiers. The
|
||||
// parts of the key are separated by a zero byte.
|
||||
func parentKey(parent, child uint64) []byte {
|
||||
b := make([]byte, binary.Size([]uint64{parent, child})+1)
|
||||
i := binary.PutUvarint(b, parent)
|
||||
j := binary.PutUvarint(b[i+1:], child)
|
||||
return b[0 : i+j+1]
|
||||
}
|
||||
|
||||
// parentPrefixKey returns the parent part of the composite key with the
|
||||
// zero byte separator.
|
||||
func parentPrefixKey(parent uint64) []byte {
|
||||
b := make([]byte, binary.Size(parent)+1)
|
||||
i := binary.PutUvarint(b, parent)
|
||||
return b[0 : i+1]
|
||||
}
|
||||
|
||||
// getParentPrefix returns the first part of the composite key which
|
||||
// represents the parent identifier.
|
||||
func getParentPrefix(b []byte) uint64 {
|
||||
parent, _ := binary.Uvarint(b)
|
||||
return parent
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) Stat(ctx context.Context, key string) (snapshot.Info, error) {
|
||||
var ss Snapshot
|
||||
err := ms.withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
return getSnapshot(bkt, key, &ss)
|
||||
})
|
||||
if err != nil {
|
||||
return snapshot.Info{}, err
|
||||
}
|
||||
|
||||
return snapshot.Info{
|
||||
Name: key,
|
||||
Parent: ss.Parent,
|
||||
Kind: fromProtoKind(ss.Kind),
|
||||
Readonly: ss.Readonly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error {
|
||||
return ms.withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
// skip nested buckets
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
var ss Snapshot
|
||||
if err := proto.Unmarshal(v, &ss); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal snapshot")
|
||||
}
|
||||
|
||||
info := snapshot.Info{
|
||||
Name: string(k),
|
||||
Parent: ss.Parent,
|
||||
Kind: fromProtoKind(ss.Kind),
|
||||
Readonly: ss.Readonly,
|
||||
}
|
||||
return fn(ctx, info)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) CreateActive(ctx context.Context, key, parent string, readonly bool) (a storage.Active, err error) {
|
||||
err = ms.createBucketIfNotExists(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
var (
|
||||
parentS *Snapshot
|
||||
)
|
||||
if parent != "" {
|
||||
parentS = new(Snapshot)
|
||||
if err := getSnapshot(bkt, parent, parentS); err != nil {
|
||||
return errors.Wrap(err, "failed to get parent snapshot")
|
||||
}
|
||||
|
||||
if parentS.Kind != KindCommitted {
|
||||
return errors.Wrap(snapshot.ErrSnapshotNotCommitted, "parent is not committed snapshot")
|
||||
}
|
||||
}
|
||||
b := bkt.Get([]byte(key))
|
||||
if len(b) != 0 {
|
||||
return snapshot.ErrSnapshotExist
|
||||
}
|
||||
|
||||
id, err := bkt.NextSequence()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get identifier")
|
||||
}
|
||||
|
||||
ss := Snapshot{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
Kind: KindActive,
|
||||
Readonly: readonly,
|
||||
}
|
||||
if err := putSnapshot(bkt, key, &ss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parentS != nil {
|
||||
// Store a backlink from the key to the parent. Store the snapshot name
|
||||
// as the value to allow following the backlink to the snapshot value.
|
||||
if err := pbkt.Put(parentKey(parentS.ID, ss.ID), []byte(key)); err != nil {
|
||||
return errors.Wrap(err, "failed to write parent link")
|
||||
}
|
||||
|
||||
a.ParentIDs, err = ms.parents(bkt, parentS)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get parent chain")
|
||||
}
|
||||
}
|
||||
|
||||
a.ID = fmt.Sprintf("%d", id)
|
||||
a.Readonly = readonly
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return storage.Active{}, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) GetActive(ctx context.Context, key string) (a storage.Active, err error) {
|
||||
err = ms.withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
b := bkt.Get([]byte(key))
|
||||
if len(b) == 0 {
|
||||
return snapshot.ErrSnapshotNotExist
|
||||
}
|
||||
|
||||
var ss Snapshot
|
||||
if err := proto.Unmarshal(b, &ss); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal snapshot")
|
||||
}
|
||||
if ss.Kind != KindActive {
|
||||
return snapshot.ErrSnapshotNotActive
|
||||
}
|
||||
|
||||
a.ID = fmt.Sprintf("%d", ss.ID)
|
||||
a.Readonly = ss.Readonly
|
||||
|
||||
if ss.Parent != "" {
|
||||
var parent Snapshot
|
||||
if err := getSnapshot(bkt, ss.Parent, &parent); err != nil {
|
||||
return errors.Wrap(err, "failed to get parent snapshot")
|
||||
}
|
||||
|
||||
a.ParentIDs, err = ms.parents(bkt, &parent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get parent chain")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return storage.Active{}, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) parents(bkt *bolt.Bucket, parent *Snapshot) (parents []string, err error) {
|
||||
for {
|
||||
parents = append(parents, fmt.Sprintf("%d", parent.ID))
|
||||
|
||||
if parent.Parent == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var ps Snapshot
|
||||
if err := getSnapshot(bkt, parent.Parent, &ps); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get parent snapshot")
|
||||
}
|
||||
parent = &ps
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) Remove(ctx context.Context, key string) (id string, k snapshot.Kind, err error) {
|
||||
err = ms.withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
var ss Snapshot
|
||||
b := bkt.Get([]byte(key))
|
||||
if len(b) == 0 {
|
||||
return snapshot.ErrSnapshotNotExist
|
||||
}
|
||||
|
||||
if err := proto.Unmarshal(b, &ss); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal snapshot")
|
||||
}
|
||||
|
||||
if pbkt != nil {
|
||||
k, _ := pbkt.Cursor().Seek(parentPrefixKey(ss.ID))
|
||||
if getParentPrefix(k) == ss.ID {
|
||||
return errors.Errorf("cannot remove snapshot with child")
|
||||
}
|
||||
|
||||
if ss.Parent != "" {
|
||||
var ps Snapshot
|
||||
if err := getSnapshot(bkt, ss.Parent, &ps); err != nil {
|
||||
return errors.Wrap(err, "failed to get parent snapshot")
|
||||
}
|
||||
|
||||
if err := pbkt.Delete(parentKey(ps.ID, ss.ID)); err != nil {
|
||||
return errors.Wrap(err, "failed to delte parent link")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := bkt.Delete([]byte(key)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete snapshot")
|
||||
}
|
||||
|
||||
id = fmt.Sprintf("%d", ss.ID)
|
||||
k = fromProtoKind(ss.Kind)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ms *boltMetastore) Commit(ctx context.Context, key, name string) (id string, err error) {
|
||||
err = ms.withBucket(ctx, func(ctx context.Context, bkt, pbkt *bolt.Bucket) error {
|
||||
b := bkt.Get([]byte(name))
|
||||
if len(b) != 0 {
|
||||
return errors.Wrap(snapshot.ErrSnapshotExist, "committed name already exists")
|
||||
}
|
||||
|
||||
var ss Snapshot
|
||||
if err := getSnapshot(bkt, key, &ss); err != nil {
|
||||
return errors.Wrap(err, "failed to get active snapshot")
|
||||
}
|
||||
if ss.Kind != KindActive {
|
||||
return snapshot.ErrSnapshotNotActive
|
||||
}
|
||||
if ss.Readonly {
|
||||
return errors.Errorf("active snapshot is readonly")
|
||||
}
|
||||
|
||||
ss.Kind = KindCommitted
|
||||
ss.Readonly = true
|
||||
|
||||
if err := putSnapshot(bkt, name, &ss); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bkt.Delete([]byte(key)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete active")
|
||||
}
|
||||
|
||||
id = fmt.Sprintf("%d", ss.ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getSnapshot(bkt *bolt.Bucket, key string, ss *Snapshot) error {
|
||||
b := bkt.Get([]byte(key))
|
||||
if len(b) == 0 {
|
||||
return snapshot.ErrSnapshotNotExist
|
||||
}
|
||||
if err := proto.Unmarshal(b, ss); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal snapshot")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func putSnapshot(bkt *bolt.Bucket, key string, ss *Snapshot) error {
|
||||
b, err := proto.Marshal(ss)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal snapshot")
|
||||
}
|
||||
|
||||
if err := bkt.Put([]byte(key), b); err != nil {
|
||||
return errors.Wrap(err, "failed to save snapshot")
|
||||
}
|
||||
return nil
|
||||
}
|
25
snapshot/storage/boltdb/bolt_test.go
Normal file
25
snapshot/storage/boltdb/bolt_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package boltdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
"github.com/docker/containerd/snapshot/storage/testsuite"
|
||||
|
||||
// Does not require root but flag must be defined for snapshot tests
|
||||
_ "github.com/docker/containerd/testutil"
|
||||
)
|
||||
|
||||
func TestBoltDB(t *testing.T) {
|
||||
testsuite.MetaStoreSuite(t, "BoltDB", func(ctx context.Context, root string) (storage.MetaStore, error) {
|
||||
return NewMetaStore(ctx, filepath.Join(root, "metadata.db"))
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSuite(b *testing.B) {
|
||||
testsuite.Benchmarks(b, "BoltDBBench", func(ctx context.Context, root string) (storage.MetaStore, error) {
|
||||
return NewMetaStore(ctx, filepath.Join(root, "metadata.db"))
|
||||
})
|
||||
}
|
468
snapshot/storage/boltdb/record.pb.go
Normal file
468
snapshot/storage/boltdb/record.pb.go
Normal file
|
@ -0,0 +1,468 @@
|
|||
// Code generated by protoc-gen-gogo.
|
||||
// source: github.com/docker/containerd/snapshot/storage/boltdb/record.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package boltdb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
github.com/docker/containerd/snapshot/storage/boltdb/record.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Snapshot
|
||||
*/
|
||||
package boltdb
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
import strings "strings"
|
||||
import reflect "reflect"
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Kind defines the kind of snapshot.
|
||||
type Kind int32
|
||||
|
||||
const (
|
||||
// KindActive represents an active snapshot
|
||||
KindActive Kind = 0
|
||||
// KindCommitted represents a committed immutable snapshot
|
||||
KindCommitted Kind = 1
|
||||
)
|
||||
|
||||
var Kind_name = map[int32]string{
|
||||
0: "ACTIVE",
|
||||
1: "COMMITTED",
|
||||
}
|
||||
var Kind_value = map[string]int32{
|
||||
"ACTIVE": 0,
|
||||
"COMMITTED": 1,
|
||||
}
|
||||
|
||||
func (x Kind) String() string {
|
||||
return proto.EnumName(Kind_name, int32(x))
|
||||
}
|
||||
func (Kind) EnumDescriptor() ([]byte, []int) { return fileDescriptorRecord, []int{0} }
|
||||
|
||||
type Snapshot struct {
|
||||
ID uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Parent string `protobuf:"bytes,2,opt,name=parent,proto3" json:"parent,omitempty"`
|
||||
Kind Kind `protobuf:"varint,4,opt,name=kind,proto3,enum=containerd.v1.Kind" json:"kind,omitempty"`
|
||||
Readonly bool `protobuf:"varint,5,opt,name=readonly,proto3" json:"readonly,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Snapshot) Reset() { *m = Snapshot{} }
|
||||
func (*Snapshot) ProtoMessage() {}
|
||||
func (*Snapshot) Descriptor() ([]byte, []int) { return fileDescriptorRecord, []int{0} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Snapshot)(nil), "containerd.v1.Snapshot")
|
||||
proto.RegisterEnum("containerd.v1.Kind", Kind_name, Kind_value)
|
||||
}
|
||||
func (m *Snapshot) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Snapshot) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintRecord(dAtA, i, uint64(m.ID))
|
||||
}
|
||||
if len(m.Parent) > 0 {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintRecord(dAtA, i, uint64(len(m.Parent)))
|
||||
i += copy(dAtA[i:], m.Parent)
|
||||
}
|
||||
if m.Kind != 0 {
|
||||
dAtA[i] = 0x20
|
||||
i++
|
||||
i = encodeVarintRecord(dAtA, i, uint64(m.Kind))
|
||||
}
|
||||
if m.Readonly {
|
||||
dAtA[i] = 0x28
|
||||
i++
|
||||
if m.Readonly {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Record(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
dAtA[offset+4] = uint8(v >> 32)
|
||||
dAtA[offset+5] = uint8(v >> 40)
|
||||
dAtA[offset+6] = uint8(v >> 48)
|
||||
dAtA[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Record(dAtA []byte, offset int, v uint32) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintRecord(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *Snapshot) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
n += 1 + sovRecord(uint64(m.ID))
|
||||
}
|
||||
l = len(m.Parent)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovRecord(uint64(l))
|
||||
}
|
||||
if m.Kind != 0 {
|
||||
n += 1 + sovRecord(uint64(m.Kind))
|
||||
}
|
||||
if m.Readonly {
|
||||
n += 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovRecord(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozRecord(x uint64) (n int) {
|
||||
return sovRecord(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (this *Snapshot) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&Snapshot{`,
|
||||
`ID:` + fmt.Sprintf("%v", this.ID) + `,`,
|
||||
`Parent:` + fmt.Sprintf("%v", this.Parent) + `,`,
|
||||
`Kind:` + fmt.Sprintf("%v", this.Kind) + `,`,
|
||||
`Readonly:` + fmt.Sprintf("%v", this.Readonly) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func valueToStringRecord(v interface{}) string {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.IsNil() {
|
||||
return "nil"
|
||||
}
|
||||
pv := reflect.Indirect(rv).Interface()
|
||||
return fmt.Sprintf("*%v", pv)
|
||||
}
|
||||
func (m *Snapshot) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Snapshot: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Snapshot: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
|
||||
}
|
||||
m.ID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ID |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Parent", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthRecord
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Parent = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType)
|
||||
}
|
||||
m.Kind = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Kind |= (Kind(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Readonly", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Readonly = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRecord(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthRecord
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipRecord(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthRecord
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowRecord
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipRecord(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthRecord = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowRecord = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("github.com/docker/containerd/snapshot/storage/boltdb/record.proto", fileDescriptorRecord)
|
||||
}
|
||||
|
||||
var fileDescriptorRecord = []byte{
|
||||
// 314 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x8f, 0xcf, 0x4a, 0xf3, 0x40,
|
||||
0x14, 0xc5, 0x33, 0x21, 0x5f, 0x68, 0x87, 0xaf, 0xa5, 0x46, 0x29, 0x21, 0x8b, 0x71, 0x70, 0x63,
|
||||
0x70, 0x91, 0x41, 0x7d, 0x82, 0xfe, 0x5b, 0x94, 0x52, 0x84, 0x58, 0xdc, 0x27, 0x99, 0x21, 0x1d,
|
||||
0xda, 0xce, 0x2d, 0xd3, 0xb1, 0xe0, 0x4a, 0x97, 0xd2, 0x77, 0xe8, 0x4a, 0x9f, 0xc2, 0x27, 0xe8,
|
||||
0xd2, 0xa5, 0x2b, 0xb1, 0x79, 0x12, 0x69, 0x5a, 0x14, 0x77, 0xf7, 0xdc, 0xf3, 0xbb, 0xe7, 0x72,
|
||||
0x70, 0x2b, 0x97, 0x66, 0x7c, 0x9f, 0x46, 0x19, 0xcc, 0x18, 0x87, 0x6c, 0x22, 0x34, 0xcb, 0x40,
|
||||
0x99, 0x44, 0x2a, 0xa1, 0x39, 0x5b, 0xa8, 0x64, 0xbe, 0x18, 0x83, 0x61, 0x0b, 0x03, 0x3a, 0xc9,
|
||||
0x05, 0x4b, 0x61, 0x6a, 0x78, 0xca, 0xb4, 0xc8, 0x40, 0xf3, 0x68, 0xae, 0xc1, 0x80, 0x57, 0xfb,
|
||||
0x3d, 0x88, 0x96, 0x97, 0xc1, 0x49, 0x0e, 0x39, 0x94, 0x0e, 0xdb, 0x4d, 0x7b, 0xe8, 0xec, 0x11,
|
||||
0x57, 0x6e, 0x0f, 0x61, 0x5e, 0x13, 0xdb, 0x92, 0xfb, 0x88, 0xa2, 0xd0, 0x69, 0xbb, 0xc5, 0xe7,
|
||||
0xa9, 0xdd, 0xef, 0xc6, 0xb6, 0xe4, 0x5e, 0x13, 0xbb, 0xf3, 0x44, 0x0b, 0x65, 0x7c, 0x9b, 0xa2,
|
||||
0xb0, 0x1a, 0x1f, 0x94, 0x77, 0x8e, 0x9d, 0x89, 0x54, 0xdc, 0x77, 0x28, 0x0a, 0xeb, 0x57, 0xc7,
|
||||
0xd1, 0x9f, 0x7f, 0xd1, 0x40, 0x2a, 0x1e, 0x97, 0x80, 0x17, 0xe0, 0x8a, 0x16, 0x09, 0x07, 0x35,
|
||||
0x7d, 0xf0, 0xff, 0x51, 0x14, 0x56, 0xe2, 0x1f, 0x7d, 0x11, 0x63, 0x67, 0xb0, 0x67, 0xdc, 0x56,
|
||||
0x67, 0xd4, 0xbf, 0xeb, 0x35, 0xac, 0xa0, 0xbe, 0x5a, 0x53, 0xbc, 0xdb, 0xb6, 0x32, 0x23, 0x97,
|
||||
0xc2, 0xa3, 0xb8, 0xda, 0xb9, 0x19, 0x0e, 0xfb, 0xa3, 0x51, 0xaf, 0xdb, 0x40, 0xc1, 0xd1, 0x6a,
|
||||
0x4d, 0x6b, 0x3b, 0xbb, 0x03, 0xb3, 0x99, 0x34, 0x46, 0xf0, 0xe0, 0xff, 0xf3, 0x0b, 0xb1, 0xde,
|
||||
0x5e, 0x49, 0x99, 0xd5, 0xf6, 0x37, 0x5b, 0x62, 0x7d, 0x6c, 0x89, 0xf5, 0x54, 0x10, 0xb4, 0x29,
|
||||
0x08, 0x7a, 0x2f, 0x08, 0xfa, 0x2a, 0x08, 0x4a, 0xdd, 0xb2, 0xf5, 0xf5, 0x77, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0x9c, 0xce, 0xfc, 0xc2, 0x5f, 0x01, 0x00, 0x00,
|
||||
}
|
24
snapshot/storage/boltdb/record.proto
Normal file
24
snapshot/storage/boltdb/record.proto
Normal file
|
@ -0,0 +1,24 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package containerd.v1;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
// Kind defines the kind of snapshot.
|
||||
enum Kind {
|
||||
option (gogoproto.goproto_enum_prefix) = false;
|
||||
option (gogoproto.enum_customname) = "Kind";
|
||||
|
||||
// KindActive represents an active snapshot
|
||||
ACTIVE = 0 [(gogoproto.enumvalue_customname) = "KindActive"];
|
||||
|
||||
// KindCommitted represents a committed immutable snapshot
|
||||
COMMITTED = 1 [(gogoproto.enumvalue_customname) = "KindCommitted"];
|
||||
}
|
||||
|
||||
message Snapshot {
|
||||
uint64 id = 1 [(gogoproto.customname) = "ID"];
|
||||
string parent = 2;
|
||||
Kind kind = 4;
|
||||
bool readonly = 5;
|
||||
}
|
69
snapshot/storage/metastore.go
Normal file
69
snapshot/storage/metastore.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/containerd/snapshot"
|
||||
)
|
||||
|
||||
// MetaStore is used to store metadata related to a snapshot driver. The
|
||||
// MetaStore is intended to store metadata related to name, state and
|
||||
// parentage. Using the MetaStore is not required to implement a snapshot
|
||||
// driver but can be used to handle the persistence and transactional
|
||||
// complexities of a driver implementation.
|
||||
type MetaStore interface {
|
||||
// TransactionContext creates a new transaction context.
|
||||
TransactionContext(ctx context.Context, writable bool) (context.Context, Transactor, error)
|
||||
|
||||
// Stat returns the snapshot stat Info directly from
|
||||
// the metadata.
|
||||
Stat(ctx context.Context, key string) (snapshot.Info, error)
|
||||
|
||||
// Walk iterates through all metadata for the stored
|
||||
// snapshots and calls the provided function for each.
|
||||
Walk(ctx context.Context, fn func(context.Context, snapshot.Info) error) error
|
||||
|
||||
// CreateActive creates a new active snapshot transaction referenced by
|
||||
// the provided key. The new active snapshot will have the provided
|
||||
// parent. If the readonly option is given, the active snapshot will be
|
||||
// marked as readonly and can only be removed, and not committed. The
|
||||
// provided context must contain a writable transaction.
|
||||
CreateActive(ctx context.Context, key, parent string, readonly bool) (Active, error)
|
||||
|
||||
// GetActive returns the metadata for the active snapshot transaction
|
||||
// referenced by the given key.
|
||||
GetActive(ctx context.Context, key string) (Active, error)
|
||||
|
||||
// Remove removes a snapshot from the metastore. The provided context
|
||||
// must contain a writable transaction. The string identifier for the
|
||||
// snapshot is returned as well as the kind.
|
||||
Remove(ctx context.Context, key string) (string, snapshot.Kind, error)
|
||||
|
||||
// Commit renames the active snapshot transaction referenced by `key`
|
||||
// as a committed snapshot referenced by `Name`. The resulting snapshot
|
||||
// will be committed and readonly. The `key` reference will no longer
|
||||
// be available for lookup or removal. The returned string identifier
|
||||
// for the committed snapshot is the same identifier of the original
|
||||
// active snapshot.
|
||||
Commit(ctx context.Context, key, name string) (string, error)
|
||||
}
|
||||
|
||||
// Transactor is used to finalize an active transaction.
|
||||
type Transactor interface {
|
||||
// Commit commits any changes made during the transaction.
|
||||
Commit() error
|
||||
|
||||
// Rollback rolls back any changes made during the transaction.
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// Active hold the metadata for an active snapshot transaction. The ParentIDs
|
||||
// hold the snapshot identifiers for the committed snapshots this active is
|
||||
// based on. The ParentIDs are ordered from the lowest base to highest, meaning
|
||||
// they should be applied in order from the first index to the last index. The
|
||||
// last index should always be considered the active snapshots immediate parent.
|
||||
type Active struct {
|
||||
ID string
|
||||
ParentIDs []string
|
||||
Readonly bool
|
||||
}
|
217
snapshot/storage/testsuite/bench.go
Normal file
217
snapshot/storage/testsuite/bench.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
)
|
||||
|
||||
// Benchmarks returns a benchmark suite using the provided metadata store
|
||||
// creation method
|
||||
func Benchmarks(b *testing.B, name string, metaFn func(context.Context, string) (storage.MetaStore, error)) {
|
||||
b.Run("StatActive", makeBench(b, name, metaFn, statActiveBenchmark))
|
||||
b.Run("StatCommitted", makeBench(b, name, metaFn, statCommittedBenchmark))
|
||||
b.Run("CreateActive", makeBench(b, name, metaFn, createActiveBenchmark))
|
||||
b.Run("Remove", makeBench(b, name, metaFn, removeBenchmark))
|
||||
b.Run("Commit", makeBench(b, name, metaFn, commitBenchmark))
|
||||
b.Run("GetActive", makeBench(b, name, metaFn, getActiveBenchmark))
|
||||
b.Run("WriteTransaction", openCloseWritable(b, name, metaFn))
|
||||
b.Run("ReadTransaction", openCloseReadonly(b, name, metaFn))
|
||||
}
|
||||
|
||||
// makeBench creates a benchmark with a writable transaction
|
||||
func makeBench(b *testing.B, name string, metaFn func(context.Context, string) (storage.MetaStore, error), fn func(context.Context, *testing.B, storage.MetaStore)) func(b *testing.B) {
|
||||
return func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
tmpDir, err := ioutil.TempDir("", "metastore-bench-"+name+"-")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
ms, err := metaFn(ctx, tmpDir)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, t, err := ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer t.Commit()
|
||||
|
||||
b.ResetTimer()
|
||||
fn(ctx, b, ms)
|
||||
}
|
||||
}
|
||||
|
||||
func openCloseWritable(b *testing.B, name string, metaFn func(context.Context, string) (storage.MetaStore, error)) func(b *testing.B) {
|
||||
return func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
tmpDir, err := ioutil.TempDir("", "metastore-bench-"+name+"-")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
ms, err := metaFn(ctx, tmpDir)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, t, err := ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := t.Commit(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openCloseReadonly(b *testing.B, name string, metaFn func(context.Context, string) (storage.MetaStore, error)) func(b *testing.B) {
|
||||
return func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
tmpDir, err := ioutil.TempDir("", "metastore-bench-"+name+"-")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
ms, err := metaFn(ctx, tmpDir)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, t, err := ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := t.Rollback(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createActiveFromBase(ctx context.Context, ms storage.MetaStore, active, base string) error {
|
||||
if _, err := ms.CreateActive(ctx, "bottom", "", false); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := ms.Commit(ctx, "bottom", base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := ms.CreateActive(ctx, active, base, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func statActiveBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
if err := createActiveFromBase(ctx, ms, "active", "base"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ms.Stat(ctx, "active")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func statCommittedBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
if err := createActiveFromBase(ctx, ms, "active", "base"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if _, err := ms.Commit(ctx, "active", "committed"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ms.Stat(ctx, "committed")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createActiveBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ms.CreateActive(ctx, "active", "", false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StopTimer()
|
||||
if _, _, err := ms.Remove(ctx, "active"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
func removeBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
if _, err := ms.CreateActive(ctx, "active", "", false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
if _, _, err := ms.Remove(ctx, "active"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commitBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
b.StopTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ms.CreateActive(ctx, "active", "", false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
if _, err := ms.Commit(ctx, "active", "committed"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StopTimer()
|
||||
if _, _, err := ms.Remove(ctx, "committed"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getActiveBenchmark(ctx context.Context, b *testing.B, ms storage.MetaStore) {
|
||||
var base string
|
||||
for i := 1; i <= 10; i++ {
|
||||
if _, err := ms.CreateActive(ctx, "tmp", base, false); err != nil {
|
||||
b.Fatalf("create active failed: %+v", err)
|
||||
}
|
||||
base = fmt.Sprintf("base-%d", i)
|
||||
if _, err := ms.Commit(ctx, "tmp", base); err != nil {
|
||||
b.Fatalf("commit failed: %+v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if _, err := ms.CreateActive(ctx, "active", base, false); err != nil {
|
||||
b.Fatalf("create active failed: %+v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ms.GetActive(ctx, "active"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
552
snapshot/storage/testsuite/testsuite.go
Normal file
552
snapshot/storage/testsuite/testsuite.go
Normal file
|
@ -0,0 +1,552 @@
|
|||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/containerd/snapshot"
|
||||
"github.com/docker/containerd/snapshot/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testFunc func(context.Context, *testing.T, storage.MetaStore)
|
||||
|
||||
type metaFactory func(context.Context, string) (storage.MetaStore, error)
|
||||
|
||||
type populateFunc func(context.Context, storage.MetaStore) error
|
||||
|
||||
// MetaStoreSuite runs a test suite on the metastore given a factory function.
|
||||
func MetaStoreSuite(t *testing.T, name string, meta func(ctx context.Context, root string) (storage.MetaStore, error)) {
|
||||
t.Run("Stat", makeTest(t, name, meta, inReadTransaction(testStat, basePopulate)))
|
||||
t.Run("StatNotExist", makeTest(t, name, meta, inReadTransaction(testStatNotExist, basePopulate)))
|
||||
t.Run("StatEmptyDB", makeTest(t, name, meta, inReadTransaction(testStatNotExist, nil)))
|
||||
t.Run("Walk", makeTest(t, name, meta, inReadTransaction(testWalk, basePopulate)))
|
||||
t.Run("GetActive", makeTest(t, name, meta, testGetActive))
|
||||
t.Run("GetActiveNotExist", makeTest(t, name, meta, inReadTransaction(testGetActiveNotExist, basePopulate)))
|
||||
t.Run("GetActiveCommitted", makeTest(t, name, meta, inReadTransaction(testGetActiveCommitted, basePopulate)))
|
||||
t.Run("GetActiveEmptyDB", makeTest(t, name, meta, inReadTransaction(testGetActiveNotExist, basePopulate)))
|
||||
t.Run("CreateActive", makeTest(t, name, meta, inWriteTransaction(testCreateActive)))
|
||||
t.Run("CreateActiveNotExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveNotExist)))
|
||||
t.Run("CreateActiveExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveExist)))
|
||||
t.Run("CreateActiveFromActive", makeTest(t, name, meta, inWriteTransaction(testCreateActiveFromActive)))
|
||||
t.Run("Commit", makeTest(t, name, meta, inWriteTransaction(testCommit)))
|
||||
t.Run("CommitNotExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist)))
|
||||
t.Run("CommitExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist)))
|
||||
t.Run("CommitCommitted", makeTest(t, name, meta, inWriteTransaction(testCommitCommitted)))
|
||||
t.Run("CommitReadonly", makeTest(t, name, meta, inWriteTransaction(testCommitReadonly)))
|
||||
t.Run("Remove", makeTest(t, name, meta, inWriteTransaction(testRemove)))
|
||||
t.Run("RemoveNotExist", makeTest(t, name, meta, inWriteTransaction(testRemoveNotExist)))
|
||||
t.Run("RemoveWithChildren", makeTest(t, name, meta, inWriteTransaction(testRemoveWithChildren)))
|
||||
}
|
||||
|
||||
// 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 := context.Background()
|
||||
tmpDir, err := ioutil.TempDir("", "metastore-test-"+name+"-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
ms, err := metaFn(ctx, tmpDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fn(ctx, t, ms)
|
||||
}
|
||||
}
|
||||
|
||||
func inReadTransaction(fn testFunc, pf populateFunc) testFunc {
|
||||
return func(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if pf != nil {
|
||||
ctx, tx, err := ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := pf(ctx, ms); err != nil {
|
||||
if rerr := tx.Rollback(); rerr != nil {
|
||||
t.Logf("Rollback failed: %+v", rerr)
|
||||
}
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatalf("Populate commit failed: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, tx, err := ms.TransactionContext(ctx, false)
|
||||
if err != nil {
|
||||
t.Fatal("Failed start transaction: %+v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Logf("Rollback failed: %+v", err)
|
||||
if !t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fn(ctx, t, ms)
|
||||
}
|
||||
}
|
||||
|
||||
func inWriteTransaction(fn testFunc) testFunc {
|
||||
return func(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
ctx, tx, err := ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to start transaction: %+v", err)
|
||||
}
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Logf("Rollback failed: %+v", err)
|
||||
}
|
||||
} else {
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal("Commit failed: %+v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
fn(ctx, t, ms)
|
||||
}
|
||||
}
|
||||
|
||||
// basePopulate creates 7 snapshots
|
||||
// - "committed-1": committed without parent
|
||||
// - "committed-2": committed with parent "committed-1"
|
||||
// - "active-1": active without parent
|
||||
// - "active-2": active with parent "committed-1"
|
||||
// - "active-3": active with parent "committed-2"
|
||||
// - "active-4": readonly active without parent"
|
||||
// - "active-5": readonly active with parent "committed-2"
|
||||
func basePopulate(ctx context.Context, ms storage.MetaStore) error {
|
||||
if _, err := ms.CreateActive(ctx, "committed-tmp-1", "", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.Commit(ctx, "committed-tmp-1", "committed-1"); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "committed-tmp-2", "committed-1", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.Commit(ctx, "committed-tmp-2", "committed-2"); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "active-1", "", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "active-2", "committed-1", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "active-3", "committed-2", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "active-4", "", true); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.CreateActive(ctx, "active-5", "committed-2", true); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var baseInfo = map[string]snapshot.Info{
|
||||
"committed-1": {
|
||||
Name: "committed-1",
|
||||
Parent: "",
|
||||
Kind: snapshot.KindCommitted,
|
||||
Readonly: true,
|
||||
},
|
||||
"committed-2": {
|
||||
Name: "committed-2",
|
||||
Parent: "committed-1",
|
||||
Kind: snapshot.KindCommitted,
|
||||
Readonly: true,
|
||||
},
|
||||
"active-1": {
|
||||
Name: "active-1",
|
||||
Parent: "",
|
||||
Kind: snapshot.KindActive,
|
||||
Readonly: false,
|
||||
},
|
||||
"active-2": {
|
||||
Name: "active-2",
|
||||
Parent: "committed-1",
|
||||
Kind: snapshot.KindActive,
|
||||
Readonly: false,
|
||||
},
|
||||
"active-3": {
|
||||
Name: "active-3",
|
||||
Parent: "committed-2",
|
||||
Kind: snapshot.KindActive,
|
||||
Readonly: false,
|
||||
},
|
||||
"active-4": {
|
||||
Name: "active-4",
|
||||
Parent: "",
|
||||
Kind: snapshot.KindActive,
|
||||
Readonly: true,
|
||||
},
|
||||
"active-5": {
|
||||
Name: "active-5",
|
||||
Parent: "committed-2",
|
||||
Kind: snapshot.KindActive,
|
||||
Readonly: true,
|
||||
},
|
||||
}
|
||||
|
||||
func assertNotExist(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatal("Expected not exist error")
|
||||
}
|
||||
if !snapshot.IsNotExist(err) {
|
||||
t.Fatalf("Expected not exist error, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotActive(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatal("Expected not active error")
|
||||
}
|
||||
if !snapshot.IsNotActive(err) {
|
||||
t.Fatalf("Expected not active error, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotCommitted(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatal("Expected active error")
|
||||
}
|
||||
if !snapshot.IsNotCommitted(err) {
|
||||
t.Fatalf("Expected active error, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertExist(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatal("Expected exist error")
|
||||
}
|
||||
if !snapshot.IsExist(err) {
|
||||
t.Fatalf("Expected exist error, got %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testStat(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
for key, expected := range baseInfo {
|
||||
info, err := ms.Stat(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat on %v failed: %+v", key, err)
|
||||
}
|
||||
assert.Equal(t, expected, info)
|
||||
}
|
||||
}
|
||||
|
||||
func testStatNotExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, err := ms.Stat(ctx, "active-not-exist")
|
||||
assertNotExist(t, err)
|
||||
}
|
||||
|
||||
func testWalk(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
found := map[string]snapshot.Info{}
|
||||
err := ms.Walk(ctx, func(ctx context.Context, info snapshot.Info) error {
|
||||
if _, ok := found[info.Name]; ok {
|
||||
return errors.Errorf("entry already encountered")
|
||||
}
|
||||
found[info.Name] = info
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Walk failed: %+v", err)
|
||||
}
|
||||
assert.Equal(t, baseInfo, found)
|
||||
}
|
||||
|
||||
func testGetActive(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
activeMap := map[string]storage.Active{}
|
||||
populate := func(ctx context.Context, ms storage.MetaStore) error {
|
||||
if _, err := ms.CreateActive(ctx, "committed-tmp-1", "", false); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
if _, err := ms.Commit(ctx, "committed-tmp-1", "committed-1"); err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
|
||||
for _, opts := range []struct {
|
||||
Name string
|
||||
Parent string
|
||||
Readonly bool
|
||||
}{
|
||||
{
|
||||
Name: "active-1",
|
||||
},
|
||||
{
|
||||
Name: "active-2",
|
||||
Parent: "committed-1",
|
||||
},
|
||||
{
|
||||
Name: "active-3",
|
||||
Readonly: true,
|
||||
},
|
||||
{
|
||||
Name: "active-4",
|
||||
Parent: "committed-1",
|
||||
Readonly: true,
|
||||
},
|
||||
} {
|
||||
active, err := ms.CreateActive(ctx, opts.Name, opts.Parent, opts.Readonly)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create active")
|
||||
}
|
||||
activeMap[opts.Name] = active
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
test := func(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
for key, expected := range activeMap {
|
||||
active, err := ms.GetActive(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to get active: %+v", err)
|
||||
}
|
||||
assert.Equal(t, expected, active)
|
||||
}
|
||||
}
|
||||
|
||||
inReadTransaction(test, populate)(ctx, t, ms)
|
||||
}
|
||||
|
||||
func testGetActiveCommitted(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, err := ms.GetActive(ctx, "committed-1")
|
||||
assertNotActive(t, err)
|
||||
}
|
||||
|
||||
func testGetActiveNotExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, err := ms.GetActive(ctx, "active-not-exist")
|
||||
assertNotExist(t, err)
|
||||
}
|
||||
|
||||
func testCreateActive(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
a1, err := ms.CreateActive(ctx, "active-1", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a1.Readonly {
|
||||
t.Fatal("Expected writable active")
|
||||
}
|
||||
|
||||
a2, err := ms.CreateActive(ctx, "active-2", "", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a2.ID == a1.ID {
|
||||
t.Fatal("Returned active identifiers must be unique")
|
||||
}
|
||||
if !a2.Readonly {
|
||||
t.Fatal("Expected readonly active")
|
||||
}
|
||||
|
||||
commitID, err := ms.Commit(ctx, "active-1", "committed-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if commitID != a1.ID {
|
||||
t.Fatal("Snapshot identifier must not change on commit")
|
||||
}
|
||||
|
||||
a3, err := ms.CreateActive(ctx, "active-3", "committed-1", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a3.ID == a1.ID {
|
||||
t.Fatal("Returned active identifiers must be unique")
|
||||
}
|
||||
if len(a3.ParentIDs) != 1 {
|
||||
t.Fatal("Expected 1 parent, got %d", len(a3.ParentIDs))
|
||||
}
|
||||
if a3.ParentIDs[0] != commitID {
|
||||
t.Fatal("Expected active parent to be same as commit ID")
|
||||
}
|
||||
if a3.Readonly {
|
||||
t.Fatal("Expected writable active")
|
||||
}
|
||||
|
||||
a4, err := ms.CreateActive(ctx, "active-4", "committed-1", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a4.ID == a1.ID {
|
||||
t.Fatal("Returned active identifiers must be unique")
|
||||
}
|
||||
if len(a3.ParentIDs) != 1 {
|
||||
t.Fatal("Expected 1 parent, got %d", len(a3.ParentIDs))
|
||||
}
|
||||
if a3.ParentIDs[0] != commitID {
|
||||
t.Fatal("Expected active parent to be same as commit ID")
|
||||
}
|
||||
if !a4.Readonly {
|
||||
t.Fatal("Expected readonly active")
|
||||
}
|
||||
}
|
||||
|
||||
func testCreateActiveExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, err := ms.CreateActive(ctx, "active-1", "", false)
|
||||
assertExist(t, err)
|
||||
_, err = ms.CreateActive(ctx, "committed-1", "", false)
|
||||
assertExist(t, err)
|
||||
}
|
||||
|
||||
func testCreateActiveNotExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, err := ms.CreateActive(ctx, "active-1", "does-not-exist", false)
|
||||
assertNotExist(t, err)
|
||||
}
|
||||
|
||||
func testCreateActiveFromActive(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, err := ms.CreateActive(ctx, "active-new", "active-1", false)
|
||||
assertNotCommitted(t, err)
|
||||
}
|
||||
|
||||
func testCommit(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
a1, err := ms.CreateActive(ctx, "active-1", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a1.Readonly {
|
||||
t.Fatal("Expected writable active")
|
||||
}
|
||||
|
||||
commitID, err := ms.Commit(ctx, "active-1", "committed-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if commitID != a1.ID {
|
||||
t.Fatal("Snapshot identifier must not change on commit")
|
||||
}
|
||||
|
||||
_, err = ms.GetActive(ctx, "active-1")
|
||||
assertNotExist(t, err)
|
||||
_, err = ms.GetActive(ctx, "committed-1")
|
||||
assertNotActive(t, err)
|
||||
}
|
||||
|
||||
func testCommitNotExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, err := ms.Commit(ctx, "active-not-exist", "committed-1")
|
||||
assertNotExist(t, err)
|
||||
}
|
||||
|
||||
func testCommitExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, err := ms.Commit(ctx, "active-1", "committed-1")
|
||||
assertExist(t, err)
|
||||
}
|
||||
|
||||
func testCommitCommitted(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, err := ms.Commit(ctx, "committed-1", "committed-3")
|
||||
assertNotActive(t, err)
|
||||
}
|
||||
|
||||
func testCommitReadonly(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, err := ms.Commit(ctx, "active-5", "committed-3")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error committing readonly active")
|
||||
}
|
||||
}
|
||||
|
||||
func testRemove(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
a1, err := ms.CreateActive(ctx, "active-1", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
commitID, err := ms.Commit(ctx, "active-1", "committed-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if commitID != a1.ID {
|
||||
t.Fatal("Snapshot identifier must not change on commit")
|
||||
}
|
||||
|
||||
a2, err := ms.CreateActive(ctx, "active-2", "committed-1", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a3, err := ms.CreateActive(ctx, "active-3", "committed-1", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, _, err = ms.Remove(ctx, "active-1")
|
||||
assertNotExist(t, err)
|
||||
|
||||
r3, k3, err := ms.Remove(ctx, "active-3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r3 != a3.ID {
|
||||
t.Fatal("Expected remove ID to match create ID")
|
||||
}
|
||||
if k3 != snapshot.KindActive {
|
||||
t.Fatal("Expected active kind, got %v", k3)
|
||||
}
|
||||
|
||||
r2, k2, err := ms.Remove(ctx, "active-2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r2 != a2.ID {
|
||||
t.Fatal("Expected remove ID to match create ID")
|
||||
}
|
||||
if k2 != snapshot.KindActive {
|
||||
t.Fatal("Expected active kind, got %v", k2)
|
||||
}
|
||||
|
||||
r1, k1, err := ms.Remove(ctx, "committed-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r1 != commitID {
|
||||
t.Fatal("Expected remove ID to match commit ID")
|
||||
}
|
||||
if k1 != snapshot.KindCommitted {
|
||||
t.Fatal("Expected committed kind, got %v", k1)
|
||||
}
|
||||
}
|
||||
|
||||
func testRemoveWithChildren(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
if err := basePopulate(ctx, ms); err != nil {
|
||||
t.Fatalf("Populate failed: %+v", err)
|
||||
}
|
||||
_, _, err := ms.Remove(ctx, "committed-1")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected removal of snapshot with children to error")
|
||||
}
|
||||
_, _, err = ms.Remove(ctx, "committed-1")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected removal of snapshot with children to error")
|
||||
}
|
||||
}
|
||||
|
||||
func testRemoveNotExist(ctx context.Context, t *testing.T, ms storage.MetaStore) {
|
||||
_, _, err := ms.Remove(ctx, "does-not-exist")
|
||||
assertNotExist(t, err)
|
||||
}
|
|
@ -17,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
// SnapshotterSuite runs a test suite on the snapshotter given a factory function.
|
||||
func SnapshotterSuite(t *testing.T, name string, snapshotterFn func(root string) (snapshot.Snapshotter, func(), error)) {
|
||||
func SnapshotterSuite(t *testing.T, name string, snapshotterFn func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error)) {
|
||||
t.Run("Basic", makeTest(t, name, snapshotterFn, checkSnapshotterBasic))
|
||||
t.Run("StatActive", makeTest(t, name, snapshotterFn, checkSnapshotterStatActive))
|
||||
t.Run("StatComitted", makeTest(t, name, snapshotterFn, checkSnapshotterStatCommitted))
|
||||
|
@ -25,8 +25,9 @@ func SnapshotterSuite(t *testing.T, name string, snapshotterFn func(root string)
|
|||
t.Run("PreareViewFailingtest", makeTest(t, name, snapshotterFn, checkSnapshotterPrepareView))
|
||||
}
|
||||
|
||||
func makeTest(t *testing.T, name string, snapshotterFn func(root string) (snapshot.Snapshotter, func(), error), fn func(t *testing.T, snapshotter snapshot.Snapshotter, work string)) func(t *testing.T) {
|
||||
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 := context.Background()
|
||||
oldumask := syscall.Umask(0)
|
||||
defer syscall.Umask(oldumask)
|
||||
// Make two directories: a snapshotter root and a play area for the tests:
|
||||
|
@ -46,7 +47,7 @@ func makeTest(t *testing.T, name string, snapshotterFn func(root string) (snapsh
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snapshotter, cleanup, err := snapshotterFn(root)
|
||||
snapshotter, cleanup, err := snapshotterFn(ctx, root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -58,14 +59,12 @@ func makeTest(t *testing.T, name string, snapshotterFn func(root string) (snapsh
|
|||
}
|
||||
|
||||
defer testutil.DumpDir(t, tmpDir)
|
||||
fn(t, snapshotter, work)
|
||||
fn(ctx, t, snapshotter, work)
|
||||
}
|
||||
}
|
||||
|
||||
// checkSnapshotterBasic tests the basic workflow of a snapshot snapshotter.
|
||||
func checkSnapshotterBasic(t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
func checkSnapshotterBasic(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
initialApplier := fstest.Apply(
|
||||
fstest.CreateFile("/foo", []byte("foo\n"), 0777),
|
||||
fstest.CreateDir("/a", 0755),
|
||||
|
@ -189,11 +188,15 @@ func checkSnapshotterBasic(t *testing.T, snapshotter snapshot.Snapshotter, work
|
|||
fstest.Apply(initialApplier, diffApplier)); err != nil {
|
||||
t.Fatalf("failure reason: %+v", err)
|
||||
}
|
||||
|
||||
assert.NoError(t, snapshotter.Remove(ctx, nextnext))
|
||||
assert.Error(t, snapshotter.Remove(ctx, committed))
|
||||
assert.NoError(t, snapshotter.Remove(ctx, nextCommitted))
|
||||
assert.NoError(t, snapshotter.Remove(ctx, committed))
|
||||
}
|
||||
|
||||
// Create a New Layer on top of base layer with Prepare, Stat on new layer, should return Active layer.
|
||||
func checkSnapshotterStatActive(t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
ctx := context.TODO()
|
||||
func checkSnapshotterStatActive(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
preparing := filepath.Join(work, "preparing")
|
||||
if err := os.MkdirAll(preparing, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -227,8 +230,7 @@ func checkSnapshotterStatActive(t *testing.T, snapshotter snapshot.Snapshotter,
|
|||
}
|
||||
|
||||
// Commit a New Layer on top of base layer with Prepare & Commit , Stat on new layer, should return Committed layer.
|
||||
func checkSnapshotterStatCommitted(t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
ctx := context.TODO()
|
||||
func checkSnapshotterStatCommitted(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
preparing := filepath.Join(work, "preparing")
|
||||
if err := os.MkdirAll(preparing, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -289,8 +291,7 @@ func snapshotterPrepareMount(ctx context.Context, snapshotter snapshot.Snapshott
|
|||
}
|
||||
|
||||
// Given A <- B <- C, B is the parent of C and A is a transitive parent of C (in this case, a "grandparent")
|
||||
func checkSnapshotterTransitivity(t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
ctx := context.TODO()
|
||||
func checkSnapshotterTransitivity(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
preparing, err := snapshotterPrepareMount(ctx, snapshotter, "preparing", "", work)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -344,9 +345,7 @@ func checkSnapshotterTransitivity(t *testing.T, snapshotter snapshot.Snapshotter
|
|||
}
|
||||
|
||||
// Creating two layers with Prepare or View with same key must fail.
|
||||
func checkSnapshotterPrepareView(t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
ctx := context.TODO()
|
||||
|
||||
func checkSnapshotterPrepareView(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string) {
|
||||
preparing, err := snapshotterPrepareMount(ctx, snapshotter, "preparing", "", work)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
Loading…
Reference in a new issue