From d0b4ce8d17c1903810a9317c124b708cd5dcc6db Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 2 Feb 2017 18:55:53 -0800 Subject: [PATCH] snapshot/overlay: add snapshot test suite to overlay Signed-off-by: Stephen J Day --- snapshot/overlay/overlay.go | 112 +++++++++++++++++++++++++------ snapshot/overlay/overlay_test.go | 32 ++++++--- testutil/helpers.go | 11 ++- 3 files changed, 124 insertions(+), 31 deletions(-) diff --git a/snapshot/overlay/overlay.go b/snapshot/overlay/overlay.go index 103d759..afcf67d 100644 --- a/snapshot/overlay/overlay.go +++ b/snapshot/overlay/overlay.go @@ -2,15 +2,17 @@ package overlay import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" "sync" "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 { return nil, err } @@ -33,31 +35,87 @@ type Overlay struct { cache *cache } -func (o *Overlay) Prepare(key string, parentName string) ([]containerd.Mount, error) { - if err := validKey(key); err != nil { - return nil, err - } +func (o *Overlay) Prepare(key, parent string) ([]containerd.Mount, error) { active, err := o.newActiveDir(key) if err != nil { return nil, err } - if parentName != "" { - if err := active.setParent(parentName); err != nil { + if parent != "" { + if err := active.setParent(parent); err != nil { 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) } func (o *Overlay) Commit(name, key string) error { 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) { var ( - hash = hash(key) - path = filepath.Join(o.root, "active", hash) + path = filepath.Join(o.root, "active", hash(key)) ) a := &activeDir{ 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 { - h := md5.New() - h.Write([]byte(k)) - return hex.EncodeToString(h.Sum(nil)) + return digest.FromString(k).Hex() } type activeDir struct { @@ -103,14 +154,26 @@ func (a *activeDir) delete() 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 { 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) { @@ -180,3 +243,10 @@ func (c *cache) get(path string) (string, error) { } return parentRoot, nil } + +func (c *cache) invalidate(path string) { + c.mu.Lock() + defer c.mu.Unlock() + + delete(c.parents, path) +} diff --git a/snapshot/overlay/overlay_test.go b/snapshot/overlay/overlay_test.go index 4b0697f..d27f981 100644 --- a/snapshot/overlay/overlay_test.go +++ b/snapshot/overlay/overlay_test.go @@ -8,7 +8,8 @@ import ( "testing" "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) { @@ -17,7 +18,7 @@ func TestOverlay(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) - o, err := NewOverlay(root) + o, err := NewDriver(root) if err != nil { t.Error(err) return @@ -52,7 +53,7 @@ func TestOverlayCommit(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) - o, err := NewOverlay(root) + o, err := NewDriver(root) if err != nil { t.Error(err) return @@ -80,7 +81,7 @@ func TestOverlayOverlayMount(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) - o, err := NewOverlay(root) + o, err := NewDriver(root) if err != nil { t.Error(err) return @@ -110,10 +111,11 @@ func TestOverlayOverlayMount(t *testing.T) { t.Errorf("expected source %q but received %q", "overlay", m.Source) } var ( - hash = hash("/tmp/layer2") - work = "workdir=" + filepath.Join(root, "active", hash, "work") - upper = "upperdir=" + filepath.Join(root, "active", hash, "fs") - lower = "lowerdir=" + filepath.Join(root, "snapshots", "base", "fs") + 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, "snapshots", sh, "fs") ) for i, v := range []string{ work, @@ -133,7 +135,7 @@ func TestOverlayOverlayRead(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) - o, err := NewOverlay(root) + o, err := NewDriver(root) if err != nil { t.Error(err) return @@ -177,3 +179,15 @@ func TestOverlayOverlayRead(t *testing.T) { 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 + }) +} diff --git a/testutil/helpers.go b/testutil/helpers.go index 5246318..9bf543a 100644 --- a/testutil/helpers.go +++ b/testutil/helpers.go @@ -49,7 +49,16 @@ func DumpDir(t *testing.T, root string) { 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 }); err != nil { t.Fatalf("error dumping directory: %v", err)