瀏覽代碼

Merge pull request #7 from crosbymichael/updated-interfaces

Updated aufs driver for new interface
Solomon Hykes 11 年之前
父節點
當前提交
0a9df6bc1a
共有 7 個文件被更改,包括 565 次插入59 次删除
  1. 180 54
      aufs/aufs.go
  2. 332 0
      aufs/aufs_test.go
  3. 47 0
      aufs/dirs.go
  4. 1 0
      aufs/mount.go
  5. 3 3
      graph_test.go
  6. 2 0
      runtime.go
  7. 0 2
      utils.go

+ 180 - 54
aufs/aufs.go

@@ -1,9 +1,30 @@
+/*
+
+aufs driver directory structure
+
+.
+├── layers // Metadata of layers
+│   ├── 1
+│   ├── 2
+│   └── 3
+├── diffs  // Content of the layer
+│   ├── 1  // Contains layers that need to be mounted for the id
+│   ├── 2
+│   └── 3
+└── mnt    // Mount points for the rw layers to be mounted
+    ├── 1
+    ├── 2
+    └── 3
+
+*/
+
 package aufs
 
 import (
 	"fmt"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/graphdriver"
+	"github.com/dotcloud/docker/utils"
 	"log"
 	"os"
 	"os/exec"
@@ -25,105 +46,210 @@ func Init(root string) (graphdriver.Driver, error) {
 	if err := exec.Command("modprobe", "aufs").Run(); err != nil {
 		return nil, err
 	}
+	paths := []string{
+		"mnt",
+		"diff",
+		"layers",
+	}
+
+	// Create the root aufs driver dir and return
+	// if it already exists
+	// If not populate the dir structure
+	aufsPath := path.Join(root, "aufs")
+	if err := os.Mkdir(aufsPath, 0755); err != nil {
+		if os.IsExist(err) {
+			return &AufsDriver{root}, nil
+		}
+		return nil, err
+	}
+
+	for _, p := range paths {
+		if err := os.MkdirAll(path.Join(aufsPath, p), 0755); err != nil {
+			return nil, err
+		}
+	}
 	return &AufsDriver{root}, nil
 }
 
-func (a *AufsDriver) OnCreate(dir graphdriver.Dir, layer archive.Archive) error {
-	tmp := path.Join(os.TempDir(), dir.ID())
-	if err := os.MkdirAll(tmp, 0755); err != nil {
+func (a *AufsDriver) rootPath() string {
+	return path.Join(a.root, "aufs")
+}
+
+func (a *AufsDriver) String() string {
+	return "aufs"
+}
+
+// Three folders are created for each id
+// mnt, layers, and diff
+func (a *AufsDriver) Create(id, parent string) error {
+	if err := a.createDirsFor(id); err != nil {
 		return err
 	}
-	defer os.RemoveAll(tmp)
-
-	layerRoot := path.Join(a.root, dir.ID())
-	if err := os.MkdirAll(layerRoot, 0755); err != nil {
+	// Write the layers metadata
+	f, err := os.Create(path.Join(a.rootPath(), "layers", id))
+	if err != nil {
 		return err
 	}
+	defer f.Close()
 
-	if layer != nil {
-		if err := archive.Untar(layer, tmp); err != nil {
+	if parent != "" {
+		ids, err := getParentIds(a.rootPath(), parent)
+		if err != nil {
 			return err
 		}
-	}
 
-	if err := os.Rename(tmp, layerRoot); err != nil {
-		return err
+		fmt.Fprintln(f, parent)
+		for _, i := range ids {
+			fmt.Fprintln(f, i)
+		}
 	}
 	return nil
 }
 
-func (a *AufsDriver) OnRemove(dir graphdriver.Dir) error {
-	tmp := path.Join(os.TempDir(), dir.ID())
+func (a *AufsDriver) createDirsFor(id string) error {
+	paths := []string{
+		"mnt",
+		"diff",
+	}
 
-	if err := os.MkdirAll(tmp, 0755); err != nil {
-		return err
+	for _, p := range paths {
+		dir := path.Join(a.rootPath(), p, id)
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			return err
+		}
 	}
+	return nil
+}
 
-	if err := os.Rename(path.Join(a.root, dir.ID()), tmp); err != nil {
+// Unmount and remove the dir information
+func (a *AufsDriver) Remove(id string) error {
+	// Make sure the dir is umounted first
+	mntPoint := path.Join(a.rootPath(), "mnt", id)
+	if err := a.unmount(mntPoint); err != nil {
 		return err
 	}
-	return os.RemoveAll(tmp)
+	tmpDirs := []string{
+		"mnt",
+		"diff",
+	}
+
+	// Remove the dirs atomically
+	for _, p := range tmpDirs {
+		tmp := path.Join(os.TempDir(), p, id)
+		if err := os.MkdirAll(tmp, 0755); err != nil {
+			return err
+		}
+		realPath := path.Join(a.rootPath(), p, id)
+		if err := os.Rename(realPath, tmp); err != nil {
+			return err
+		}
+		defer os.RemoveAll(tmp)
+	}
+
+	// Remove the layers file for the id
+	return os.Remove(path.Join(a.rootPath(), "layers", id))
 }
 
-func (a *AufsDriver) OnMount(dir graphdriver.Dir, dest string) error {
-	layers, err := a.getLayers(dir)
+// Return the rootfs path for the id
+// This will mount the dir at it's given path
+func (a *AufsDriver) Get(id string) (string, error) {
+	ids, err := getParentIds(a.rootPath(), id)
 	if err != nil {
-		return err
+		if !os.IsNotExist(err) {
+			return "", err
+		}
+		ids = []string{}
 	}
 
-	target := path.Join(dest, "rootfs")
-	rw := path.Join(dest, "rw")
+	// If a dir does not have a parent ( no layers )do not try to mount
+	// just return the diff path to the data
+	out := path.Join(a.rootPath(), "diff", id)
+	if len(ids) > 0 {
+		out = path.Join(a.rootPath(), "mnt", id)
+		if err := a.mount(id); err != nil {
+			return "", err
+		}
+	}
+	return out, nil
+}
+
+// Returns an archive of the contents for the id
+func (a *AufsDriver) Diff(id string) (archive.Archive, error) {
+	p, err := a.Get(id)
+	if err != nil {
+		return nil, err
+	}
+	return archive.Tar(p, archive.Uncompressed)
+}
 
-	// Create the target directories if they don't exist
-	if err := os.Mkdir(target, 0755); err != nil && !os.IsExist(err) {
+// Returns the size of the contents for the id
+func (a *AufsDriver) DiffSize(id string) (int64, error) {
+	p, err := a.Get(id)
+	if err != nil {
+		return -1, err
+	}
+	return utils.TreeSize(p)
+}
+
+func (a *AufsDriver) Changes(id string) ([]archive.Change, error) {
+	return nil, nil
+}
+
+func (a *AufsDriver) mount(id string) error {
+	// If the id is mounted or we get an error return
+	if mounted, err := a.mounted(id); err != nil || mounted {
 		return err
 	}
-	if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
+
+	parentIds, err := getParentIds(a.rootPath(), id)
+	if err != nil {
 		return err
 	}
+	if len(parentIds) == 0 {
+		return fmt.Errorf("Dir %s does not have any parent layers", id)
+	}
+	var (
+		target = path.Join(a.rootPath(), "mnt", id)
+		rw     = path.Join(a.rootPath(), "diff", id)
+		layers = make([]string, len(parentIds))
+	)
+
+	// Get the diff paths for all the parent ids
+	for i, p := range parentIds {
+		layers[i] = path.Join(a.rootPath(), "diff", p)
+	}
+
 	if err := a.aufsMount(layers, rw, target); err != nil {
 		return err
 	}
 	return nil
 }
 
-func (a *AufsDriver) OnUnmount(dest string) error {
-	target := path.Join(dest, "rootfs")
-	if _, err := os.Stat(target); err != nil {
-		if os.IsNotExist(err) {
-			return nil
-		}
+func (a *AufsDriver) unmount(id string) error {
+	if mounted, err := a.mounted(id); err != nil || !mounted {
 		return err
 	}
+	target := path.Join(a.rootPath(), "mnt", id)
 	return Unmount(target)
 }
 
-func (a *AufsDriver) Mounted(dest string) (bool, error) {
-	return Mounted(path.Join(dest, "rootfs"))
-}
-
-func (a *AufsDriver) Layer(dir graphdriver.Dir, dest string) (archive.Archive, error) {
-	return nil, fmt.Errorf("not implemented")
+func (a *AufsDriver) mounted(id string) (bool, error) {
+	target := path.Join(a.rootPath(), "mnt", id)
+	return Mounted(target)
 }
 
+// During cleanup aufs needs to unmount all mountpoints
 func (a *AufsDriver) Cleanup() error {
-	return nil
-}
-
-func (a *AufsDriver) getLayers(dir graphdriver.Dir) ([]string, error) {
-	var (
-		err     error
-		layers  = []string{}
-		current = dir
-	)
-
-	for current != nil {
-		layers = append(layers, current.Path())
-		if current, err = current.Parent(); err != nil {
-			return nil, err
+	ids, err := loadIds(path.Join(a.rootPath(), "layers"))
+	if err != nil {
+		return err
+	}
+	for _, id := range ids {
+		if err := a.unmount(id); err != nil {
+			return err
 		}
 	}
-	return layers, nil
+	return nil
 }
 
 func (a *AufsDriver) aufsMount(ro []string, rw, target string) error {
@@ -142,7 +268,7 @@ func (a *AufsDriver) aufsMount(ro []string, rw, target string) error {
 		}
 		log.Printf("...module loaded.")
 		if err := mount("none", target, "aufs", 0, branches); err != nil {
-			return fmt.Errorf("Unable to mount using aufs")
+			return fmt.Errorf("Unable to mount using aufs %s", err)
 		}
 	}
 	return nil

+ 332 - 0
aufs/aufs_test.go

@@ -0,0 +1,332 @@
+package aufs
+
+import (
+	"os"
+	"path"
+	"testing"
+)
+
+var (
+	tmp = path.Join(os.TempDir(), "aufs-tests")
+)
+
+func newDriver(t *testing.T) *AufsDriver {
+	if err := os.MkdirAll(tmp, 0755); err != nil {
+		t.Fatal(err)
+	}
+
+	d, err := Init(tmp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return d.(*AufsDriver)
+}
+
+func TestNewAufsDriver(t *testing.T) {
+	if err := os.MkdirAll(tmp, 0755); err != nil {
+		t.Fatal(err)
+	}
+
+	d, err := Init(tmp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+	if d == nil {
+		t.Fatalf("Driver should not be nil")
+	}
+}
+
+func TestAufsString(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if d.String() != "aufs" {
+		t.Fatalf("Expected aufs got %s", d.String())
+	}
+}
+
+func TestCreateDirStructure(t *testing.T) {
+	newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	paths := []string{
+		"mnt",
+		"layers",
+		"diff",
+	}
+
+	for _, p := range paths {
+		if _, err := os.Stat(path.Join(tmp, "aufs", p)); err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+// We should be able to create two drivers with the same dir structure
+func TestNewDriverFromExistingDir(t *testing.T) {
+	if err := os.MkdirAll(tmp, 0755); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := Init(tmp); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := Init(tmp); err != nil {
+		t.Fatal(err)
+	}
+	os.RemoveAll(tmp)
+}
+
+func TestCreateNewDir(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestCreateNewDirStructure(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	paths := []string{
+		"mnt",
+		"diff",
+		"layers",
+	}
+
+	for _, p := range paths {
+		if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func TestRemoveImage(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := d.Remove("1"); err != nil {
+		t.Fatal(err)
+	}
+
+	paths := []string{
+		"mnt",
+		"diff",
+		"layers",
+	}
+
+	for _, p := range paths {
+		if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err == nil {
+			t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p)
+		}
+	}
+}
+
+func TestGetWithoutParent(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	diffPath, err := d.Get("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := path.Join(tmp, "aufs", "diff", "1")
+	if diffPath != expected {
+		t.Fatalf("Expected path %s got %s", expected, diffPath)
+	}
+}
+
+func TestCleanupWithNoDirs(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Cleanup(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestCleanupWithDir(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := d.Cleanup(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestMountedFalseResponse(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	response, err := d.mounted("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if response != false {
+		t.Fatalf("Response if dir id 1 is mounted should be false")
+	}
+}
+
+func TestMountedTrueReponse(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+	defer d.Cleanup()
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+	if err := d.Create("2", "1"); err != nil {
+		t.Fatal(err)
+	}
+
+	_, err := d.Get("2")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	response, err := d.mounted("2")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if response != true {
+		t.Fatalf("Response if dir id 2 is mounted should be true")
+	}
+}
+
+func TestMountWithParent(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+	if err := d.Create("2", "1"); err != nil {
+		t.Fatal(err)
+	}
+
+	mntPath, err := d.Get("2")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if mntPath == "" {
+		t.Fatal("mntPath should not be empty string")
+	}
+
+	expected := path.Join(tmp, "aufs", "mnt", "2")
+	if mntPath != expected {
+		t.Fatalf("Expected %s got %s", expected, mntPath)
+	}
+
+	if err := d.Cleanup(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestCreateWithInvalidParent(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", "docker"); err == nil {
+		t.Fatalf("Error should not be nil with parent does not exist")
+	}
+}
+
+func TestGetDiff(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	diffPath, err := d.Get("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Add a file to the diff path with a fixed size
+	size := int64(1024)
+
+	f, err := os.Create(path.Join(diffPath, "test_file"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := f.Truncate(size); err != nil {
+		t.Fatal(err)
+	}
+	f.Close()
+
+	a, err := d.Diff("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if a == nil {
+		t.Fatalf("Archive should not be nil")
+	}
+}
+
+/* FIXME: How to properly test this?
+func TestDiffSize(t *testing.T) {
+	d := newDriver(t)
+	defer os.RemoveAll(tmp)
+
+	if err := d.Create("1", ""); err != nil {
+		t.Fatal(err)
+	}
+
+	diffPath, err := d.Get("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Add a file to the diff path with a fixed size
+	size := int64(1024)
+
+	f, err := os.Create(path.Join(diffPath, "test_file"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	f.Truncate(size)
+	s, err := f.Stat()
+	if err != nil {
+		t.Fatal(err)
+	}
+	size = s.Size()
+	if err := f.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	diffSize, err := d.DiffSize("1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if diffSize != size {
+		t.Fatalf("Expected size to be %d got %d", size, diffSize)
+	}
+}
+*/

+ 47 - 0
aufs/dirs.go

@@ -0,0 +1,47 @@
+package aufs
+
+import (
+	"bufio"
+	"io/ioutil"
+	"os"
+	"path"
+)
+
+// Return all the directories
+func loadIds(root string) ([]string, error) {
+	dirs, err := ioutil.ReadDir(root)
+	if err != nil {
+		return nil, err
+	}
+	out := []string{}
+	for _, d := range dirs {
+		if !d.IsDir() {
+			out = append(out, d.Name())
+		}
+	}
+	return out, nil
+}
+
+// Read the layers file for the current id and return all the
+// layers represented by new lines in the file
+//
+// If there are no lines in the file then the id has no parent
+// and an empty slice is returned.
+func getParentIds(root, id string) ([]string, error) {
+	f, err := os.Open(path.Join(root, "layers", id))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	out := []string{}
+	s := bufio.NewScanner(f)
+
+	for s.Scan() {
+		if err := s.Err(); err != nil {
+			return nil, err
+		}
+		out = append(out, s.Text())
+	}
+	return out, nil
+}

+ 1 - 0
aufs/mount.go

@@ -49,5 +49,6 @@ func Mounted(mountpoint string) (bool, error) {
 	}
 	mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
 	parentSt := parent.Sys().(*syscall.Stat_t)
+
 	return mntpointSt.Dev != parentSt.Dev, nil
 }

+ 3 - 3
graph_test.go

@@ -146,12 +146,12 @@ func TestMount(t *testing.T) {
 	if err := os.MkdirAll(rw, 0700); err != nil {
 		t.Fatal(err)
 	}
-	if err := graph.driver.Mount(image, tmp); err != nil {
+	if _, err := graph.driver.Get(image.ID); err != nil {
 		t.Fatal(err)
 	}
 	// FIXME: test for mount contents
 	defer func() {
-		if err := graph.driver.Unmount(tmp); err != nil {
+		if err := graph.driver.Cleanup(); err != nil {
 			t.Error(err)
 		}
 	}()
@@ -296,7 +296,7 @@ func tempGraph(t *testing.T) *Graph {
 	if err != nil {
 		t.Fatal(err)
 	}
-	backend, err := aufs.New()
+	backend, err := aufs.Init(path.Join(tmp, "driver"))
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 0
runtime.go

@@ -6,6 +6,7 @@ import (
 	"database/sql"
 	"fmt"
 	"github.com/dotcloud/docker/archive"
+	_ "github.com/dotcloud/docker/aufs"
 	_ "github.com/dotcloud/docker/devmapper"
 	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/graphdriver"
@@ -635,6 +636,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
 	if err != nil {
 		return nil, err
 	}
+	utils.Debugf("Using graph driver %s", driver)
 
 	runtimeRepo := path.Join(config.Root, "containers")
 

+ 0 - 2
utils.go

@@ -34,12 +34,10 @@ import (
 	"syscall"
 )
 
-
 type Change struct {
 	archive.Change
 }
 
-
 // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
 // If OpenStdin is set, then it differs
 func CompareConfig(a, b *Config) bool {