snapshot/btrfs: update btrfs to snappshotter
Updates the btrfs snapshotter to meet the interface and current tests. Mostly, we merge the keyspace into a common index. Like with the overlay driver, we will still need to do more verification work to ensure idempotence of key collisions. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
e6c1bb0ff2
commit
9c4b235954
2 changed files with 177 additions and 102 deletions
|
@ -6,47 +6,33 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/containerd"
|
"github.com/docker/containerd"
|
||||||
"github.com/docker/containerd/log"
|
"github.com/docker/containerd/snapshot"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stevvooe/go-btrfs"
|
"github.com/stevvooe/go-btrfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Snapshotter struct {
|
||||||
device string // maybe we can resolve it with path?
|
device string // maybe we can resolve it with path?
|
||||||
root string // root provides paths for internal storage.
|
root string // root provides paths for internal storage.
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDriver(device, root string) (*Driver, error) {
|
func NewSnapshotter(device, root string) (*Snapshotter, error) {
|
||||||
return &Driver{device: device, root: root}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Driver) Prepare(key, parent string) ([]containerd.Mount, error) {
|
|
||||||
return b.makeActive(key, parent, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Driver) View(key, parent string) ([]containerd.Mount, error) {
|
|
||||||
return b.makeActive(key, parent, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Driver) makeActive(key, parent string, readonly bool) ([]containerd.Mount, error) {
|
|
||||||
var (
|
var (
|
||||||
active = filepath.Join(b.root, "active")
|
active = filepath.Join(root, "active")
|
||||||
parents = filepath.Join(b.root, "parents")
|
snapshots = filepath.Join(root, "snapshots")
|
||||||
snapshots = filepath.Join(b.root, "snapshots")
|
parents = filepath.Join(root, "parents")
|
||||||
names = filepath.Join(b.root, "names")
|
index = filepath.Join(root, "index")
|
||||||
keyh = hash(key)
|
names = filepath.Join(root, "names")
|
||||||
parenth = hash(parent)
|
|
||||||
dir = filepath.Join(active, keyh)
|
|
||||||
namep = filepath.Join(names, keyh)
|
|
||||||
parentlink = filepath.Join(parents, keyh)
|
|
||||||
parentp = filepath.Join(snapshots, parenth)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, path := range []string{
|
for _, path := range []string{
|
||||||
active,
|
active,
|
||||||
|
snapshots,
|
||||||
parents,
|
parents,
|
||||||
|
index,
|
||||||
names,
|
names,
|
||||||
} {
|
} {
|
||||||
if err := os.MkdirAll(path, 0755); err != nil {
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
|
@ -54,15 +40,118 @@ func (b *Driver) makeActive(key, parent string, readonly bool) ([]containerd.Mou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &Snapshotter{device: device, root: root}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns the info for an active or committed snapshot by name or
|
||||||
|
// key.
|
||||||
|
//
|
||||||
|
// Should be used for parent resolution, existence checks and to discern
|
||||||
|
// the kind of snapshot.
|
||||||
|
func (b *Snapshotter) Stat(key string) (snapshot.Info, error) {
|
||||||
|
// resolve the snapshot out of the index.
|
||||||
|
target, err := os.Readlink(filepath.Join(b.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.stat(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return snapshot.Info{}, 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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Snapshotter) Prepare(key, parent string) ([]containerd.Mount, error) {
|
||||||
|
return b.makeActive(key, parent, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Snapshotter) View(key, parent string) ([]containerd.Mount, error) {
|
||||||
|
return b.makeActive(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 == "" {
|
if parent == "" {
|
||||||
// create new subvolume
|
// create new subvolume
|
||||||
// btrfs subvolume create /dir
|
// btrfs subvolume create /dir
|
||||||
if err := btrfs.SubvolCreate(dir); err != nil {
|
if err := btrfs.SubvolCreate(target); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// btrfs subvolume snapshot /parent /subvol
|
// btrfs subvolume snapshot /parent /subvol
|
||||||
if err := btrfs.SubvolSnapshot(dir, parentp, readonly); err != nil {
|
if err := btrfs.SubvolSnapshot(target, parentp, readonly); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,14 +160,19 @@ func (b *Driver) makeActive(key, parent string, readonly bool) ([]containerd.Mou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write in the name
|
||||||
if err := ioutil.WriteFile(namep, []byte(key), 0644); err != nil {
|
if err := ioutil.WriteFile(namep, []byte(key), 0644); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.mounts(dir)
|
if err := os.Symlink(target, indexlink); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Driver) mounts(dir string) ([]containerd.Mount, error) {
|
return b.mounts(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Snapshotter) mounts(dir string) ([]containerd.Mount, error) {
|
||||||
var options []string
|
var options []string
|
||||||
|
|
||||||
// get the subvolume id back out for the mount
|
// get the subvolume id back out for the mount
|
||||||
|
@ -104,12 +198,13 @@ func (b *Driver) mounts(dir string) ([]containerd.Mount, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Driver) Commit(name, key string) error {
|
func (b *Snapshotter) Commit(name, key string) error {
|
||||||
var (
|
var (
|
||||||
active = filepath.Join(b.root, "active")
|
active = filepath.Join(b.root, "active")
|
||||||
snapshots = filepath.Join(b.root, "snapshots")
|
snapshots = filepath.Join(b.root, "snapshots")
|
||||||
names = filepath.Join(b.root, "names")
|
index = filepath.Join(b.root, "index")
|
||||||
parents = filepath.Join(b.root, "parents")
|
parents = filepath.Join(b.root, "parents")
|
||||||
|
names = filepath.Join(b.root, "names")
|
||||||
keyh = hash(key)
|
keyh = hash(key)
|
||||||
nameh = hash(name)
|
nameh = hash(name)
|
||||||
dir = filepath.Join(active, keyh)
|
dir = filepath.Join(active, keyh)
|
||||||
|
@ -118,6 +213,8 @@ func (b *Driver) Commit(name, key string) error {
|
||||||
namep = filepath.Join(names, nameh)
|
namep = filepath.Join(names, nameh)
|
||||||
keyparentlink = filepath.Join(parents, keyh)
|
keyparentlink = filepath.Join(parents, keyh)
|
||||||
parentlink = filepath.Join(parents, nameh)
|
parentlink = filepath.Join(parents, nameh)
|
||||||
|
keyindexlink = filepath.Join(index, keyh)
|
||||||
|
indexlink = filepath.Join(index, nameh)
|
||||||
)
|
)
|
||||||
|
|
||||||
info, err := btrfs.SubvolInfo(dir)
|
info, err := btrfs.SubvolInfo(dir)
|
||||||
|
@ -143,9 +240,7 @@ func (b *Driver) Commit(name, key string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("commit snapshot", target)
|
|
||||||
if err := btrfs.SubvolSnapshot(target, dir, true); err != nil {
|
if err := btrfs.SubvolSnapshot(target, dir, true); err != nil {
|
||||||
fmt.Println("snapshot error")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +249,10 @@ func (b *Driver) Commit(name, key string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(keyindexlink); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(namep, []byte(name), 0755); err != nil {
|
if err := ioutil.WriteFile(namep, []byte(name), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -169,6 +268,10 @@ func (b *Driver) Commit(name, key string) error {
|
||||||
// into common storage, so let's not fret over it for now.
|
// 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 {
|
if err := btrfs.SubvolDelete(dir); err != nil {
|
||||||
return errors.Wrapf(err, "delete subvol failed on %v", dir)
|
return errors.Wrapf(err, "delete subvol failed on %v", dir)
|
||||||
}
|
}
|
||||||
|
@ -180,82 +283,54 @@ func (b *Driver) Commit(name, key string) error {
|
||||||
// called on an read-write or readonly transaction.
|
// called on an read-write or readonly transaction.
|
||||||
//
|
//
|
||||||
// This can be used to recover mounts after calling View or Prepare.
|
// This can be used to recover mounts after calling View or Prepare.
|
||||||
func (b *Driver) Mounts(key string) ([]containerd.Mount, error) {
|
func (b *Snapshotter) Mounts(key string) ([]containerd.Mount, error) {
|
||||||
dir := filepath.Join(b.root, "active", hash(key))
|
dir := filepath.Join(b.root, "active", hash(key))
|
||||||
return b.mounts(dir)
|
return b.mounts(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove abandons the transaction identified by key. All resources
|
// Remove abandons the transaction identified by key. All resources
|
||||||
// associated with the key will be removed.
|
// associated with the key will be removed.
|
||||||
func (b *Driver) Remove(key string) error {
|
func (b *Snapshotter) Remove(key string) error {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent returns the parent of snapshot identified by name.
|
|
||||||
func (b *Driver) Parent(name string) (string, error) {
|
|
||||||
var (
|
|
||||||
parents = filepath.Join(b.root, "parents")
|
|
||||||
names = filepath.Join(b.root, "names")
|
|
||||||
parentlink = filepath.Join(parents, hash(name))
|
|
||||||
)
|
|
||||||
|
|
||||||
parentp, err := os.Readlink(parentlink)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil // no parent!
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns true if the snapshot with name exists.
|
|
||||||
func (b *Driver) Exists(name string) bool {
|
|
||||||
target := filepath.Join(b.root, "snapshots", hash(name))
|
|
||||||
|
|
||||||
if _, err := os.Stat(target); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
// TODO(stevvooe): Very rare condition when this fails horribly,
|
|
||||||
// such as an access error. Ideally, Exists is simple, but we may
|
|
||||||
// consider returning an error.
|
|
||||||
log.L.WithError(err).Fatal("error encountered checking for snapshot existence")
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the snapshot idenfitied by name.
|
|
||||||
//
|
|
||||||
// If name has children, the operation will fail.
|
|
||||||
func (b *Driver) Delete(name string) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(stevvooe): The methods below are still in flux. We'll need to work
|
|
||||||
// out the roles of active and committed snapshots for this to become more
|
|
||||||
// clear.
|
|
||||||
|
|
||||||
// Walk the committed snapshots.
|
// Walk the committed snapshots.
|
||||||
func (b *Driver) Walk(fn func(name string) error) error {
|
func (b *Snapshotter) Walk(fn func(snapshot.Info) error) error {
|
||||||
panic("not implemented")
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active will call fn for each active transaction.
|
if path == root {
|
||||||
func (b *Driver) Active(fn func(key string) error) error {
|
return nil
|
||||||
panic("not implemented")
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
si, err := b.stat(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(si); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(k string) string {
|
func hash(k string) string {
|
||||||
|
|
|
@ -20,14 +20,14 @@ const (
|
||||||
|
|
||||||
func TestBtrfs(t *testing.T) {
|
func TestBtrfs(t *testing.T) {
|
||||||
testutil.RequiresRoot(t)
|
testutil.RequiresRoot(t)
|
||||||
snapshot.DriverSuite(t, "Btrfs", func(root string) (snapshot.Driver, func(), error) {
|
snapshot.SnapshotterSuite(t, "Btrfs", func(root string) (snapshot.Snapshotter, func(), error) {
|
||||||
device := setupBtrfsLoopbackDevice(t, root)
|
device := setupBtrfsLoopbackDevice(t, root)
|
||||||
driver, err := NewDriver(device.deviceName, root)
|
snapshotter, err := NewSnapshotter(device.deviceName, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return driver, func() {
|
return snapshotter, func() {
|
||||||
device.remove(t)
|
device.remove(t)
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
@ -53,7 +53,7 @@ func TestBtrfsMounts(t *testing.T) {
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
target := filepath.Join(root, "test")
|
target := filepath.Join(root, "test")
|
||||||
b, err := NewDriver(device.deviceName, root)
|
b, err := NewSnapshotter(device.deviceName, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue