snapshot/overlay: add snapshot test suite to overlay
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
68fd25221d
commit
d0b4ce8d17
3 changed files with 124 additions and 31 deletions
|
@ -2,15 +2,17 @@ package overlay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/containerd"
|
"github.com/docker/containerd"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewOverlay(root string) (*Overlay, error) {
|
func NewDriver(root string) (*Overlay, error) {
|
||||||
if err := os.MkdirAll(root, 0700); err != nil {
|
if err := os.MkdirAll(root, 0700); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -33,31 +35,87 @@ type Overlay struct {
|
||||||
cache *cache
|
cache *cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Overlay) Prepare(key string, parentName string) ([]containerd.Mount, error) {
|
func (o *Overlay) Prepare(key, parent string) ([]containerd.Mount, error) {
|
||||||
if err := validKey(key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
active, err := o.newActiveDir(key)
|
active, err := o.newActiveDir(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if parentName != "" {
|
if parent != "" {
|
||||||
if err := active.setParent(parentName); err != nil {
|
if err := active.setParent(parent); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return o.Mounts(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Overlay) View(key, parent string) ([]containerd.Mount, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mounts returns the mounts for the transaction identified by key. Can be
|
||||||
|
// called on an read-write or readonly transaction.
|
||||||
|
//
|
||||||
|
// This can be used to recover mounts after calling View or Prepare.
|
||||||
|
func (o *Overlay) Mounts(key string) ([]containerd.Mount, error) {
|
||||||
|
active := o.getActive(key)
|
||||||
return active.mounts(o.cache)
|
return active.mounts(o.cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Overlay) Commit(name, key string) error {
|
func (o *Overlay) Commit(name, key string) error {
|
||||||
active := o.getActive(key)
|
active := o.getActive(key)
|
||||||
return active.commit(name)
|
return active.commit(name, o.cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove abandons the transaction identified by key. All resources
|
||||||
|
// associated with the key will be removed.
|
||||||
|
func (o *Overlay) Remove(key string) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent returns the parent of snapshot identified by name.
|
||||||
|
func (o *Overlay) Parent(name string) (string, error) {
|
||||||
|
ppath, err := o.cache.get(filepath.Join(o.root, "snapshots", hash(name)))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil // no parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ioutil.ReadFile(filepath.Join(ppath, "name"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns true if the snapshot with name exists.
|
||||||
|
func (o *Overlay) Exists(name string) bool {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the snapshot idenfitied by name.
|
||||||
|
//
|
||||||
|
// If name has children, the operation will fail.
|
||||||
|
func (o *Overlay) Delete(name string) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the committed snapshots.
|
||||||
|
func (o *Overlay) Walk(fn func(name string) error) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active will call fn for each active transaction.
|
||||||
|
func (o *Overlay) Active(fn func(key string) error) error {
|
||||||
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Overlay) newActiveDir(key string) (*activeDir, error) {
|
func (o *Overlay) newActiveDir(key string) (*activeDir, error) {
|
||||||
var (
|
var (
|
||||||
hash = hash(key)
|
path = filepath.Join(o.root, "active", hash(key))
|
||||||
path = filepath.Join(o.root, "active", hash)
|
|
||||||
)
|
)
|
||||||
a := &activeDir{
|
a := &activeDir{
|
||||||
path: path,
|
path: path,
|
||||||
|
@ -82,15 +140,8 @@ func (o *Overlay) getActive(key string) *activeDir {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validKey(key string) error {
|
|
||||||
_, err := filepath.Abs(key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(k string) string {
|
func hash(k string) string {
|
||||||
h := md5.New()
|
return digest.FromString(k).Hex()
|
||||||
h.Write([]byte(k))
|
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type activeDir struct {
|
type activeDir struct {
|
||||||
|
@ -103,14 +154,26 @@ func (a *activeDir) delete() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *activeDir) setParent(name string) error {
|
func (a *activeDir) setParent(name string) error {
|
||||||
return os.Symlink(filepath.Join(a.snapshotsDir, name), filepath.Join(a.path, "parent"))
|
return os.Symlink(filepath.Join(a.snapshotsDir, hash(name)), filepath.Join(a.path, "parent"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *activeDir) commit(name string) error {
|
func (a *activeDir) commit(name string, c *cache) error {
|
||||||
|
// 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 {
|
if err := os.RemoveAll(filepath.Join(a.path, "work")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Rename(a.path, filepath.Join(a.snapshotsDir, name))
|
|
||||||
|
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.
|
||||||
|
return os.Rename(a.path, filepath.Join(a.snapshotsDir, hash(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *activeDir) mounts(c *cache) ([]containerd.Mount, error) {
|
func (a *activeDir) mounts(c *cache) ([]containerd.Mount, error) {
|
||||||
|
@ -180,3 +243,10 @@ func (c *cache) get(path string) (string, error) {
|
||||||
}
|
}
|
||||||
return parentRoot, nil
|
return parentRoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cache) invalidate(path string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
delete(c.parents, path)
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/containerd"
|
"github.com/docker/containerd"
|
||||||
"github.com/docker/containerd/snapshot/testutil"
|
"github.com/docker/containerd/snapshot"
|
||||||
|
"github.com/docker/containerd/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOverlay(t *testing.T) {
|
func TestOverlay(t *testing.T) {
|
||||||
|
@ -17,7 +18,7 @@ func TestOverlay(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
o, err := NewOverlay(root)
|
o, err := NewDriver(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -52,7 +53,7 @@ func TestOverlayCommit(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
o, err := NewOverlay(root)
|
o, err := NewDriver(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -80,7 +81,7 @@ func TestOverlayOverlayMount(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
o, err := NewOverlay(root)
|
o, err := NewDriver(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -110,10 +111,11 @@ func TestOverlayOverlayMount(t *testing.T) {
|
||||||
t.Errorf("expected source %q but received %q", "overlay", m.Source)
|
t.Errorf("expected source %q but received %q", "overlay", m.Source)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
hash = hash("/tmp/layer2")
|
ah = hash("/tmp/layer2")
|
||||||
work = "workdir=" + filepath.Join(root, "active", hash, "work")
|
sh = hash("base")
|
||||||
upper = "upperdir=" + filepath.Join(root, "active", hash, "fs")
|
work = "workdir=" + filepath.Join(root, "active", ah, "work")
|
||||||
lower = "lowerdir=" + filepath.Join(root, "snapshots", "base", "fs")
|
upper = "upperdir=" + filepath.Join(root, "active", ah, "fs")
|
||||||
|
lower = "lowerdir=" + filepath.Join(root, "snapshots", sh, "fs")
|
||||||
)
|
)
|
||||||
for i, v := range []string{
|
for i, v := range []string{
|
||||||
work,
|
work,
|
||||||
|
@ -133,7 +135,7 @@ func TestOverlayOverlayRead(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
o, err := NewOverlay(root)
|
o, err := NewDriver(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -177,3 +179,15 @@ func TestOverlayOverlayRead(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOverlayDriverSuite(t *testing.T) {
|
||||||
|
testutil.RequiresRoot(t)
|
||||||
|
snapshot.DriverSuite(t, "Overlay", func(root string) (snapshot.Driver, func(), error) {
|
||||||
|
driver, err := NewDriver(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, func() {}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,16 @@ func DumpDir(t *testing.T, root string) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log(fi.Mode(), path)
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err := os.Readlink(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Log(fi.Mode(), path, "->", target)
|
||||||
|
} else {
|
||||||
|
t.Log(fi.Mode(), path)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("error dumping directory: %v", err)
|
t.Fatalf("error dumping directory: %v", err)
|
||||||
|
|
Loading…
Add table
Reference in a new issue