snapshot/overlay: add snapshot test suite to overlay

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2017-02-02 18:55:53 -08:00
parent 68fd25221d
commit d0b4ce8d17
No known key found for this signature in database
GPG key ID: 67B3DED84EDC823F
3 changed files with 124 additions and 31 deletions

View file

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

View file

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

View file

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