Merge pull request #635 from dmcgowan/snapshot-metastore-context

Snapshot driver metastore
This commit is contained in:
Stephen Day 2017-03-22 15:29:52 -07:00 committed by GitHub
commit 44d2ef4db3
13 changed files with 2223 additions and 538 deletions

View File

@ -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
}

View File

@ -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
View 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
}

View File

@ -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")
}

View File

@ -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])
}

View 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
}

View 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"))
})
}

View 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,
}

View 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;
}

View 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
}

View 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)
}
}
}

View 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)
}

View File

@ -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)