diff --git a/aufs/aufs.go b/aufs/aufs.go index 4c14947a9..8258312de 100644 --- a/aufs/aufs.go +++ b/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()) - - if err := os.MkdirAll(tmp, 0755); err != nil { - return err +func (a *AufsDriver) createDirsFor(id string) error { + paths := []string{ + "mnt", + "diff", } - if err := os.Rename(path.Join(a.root, dir.ID()), tmp); 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 os.RemoveAll(tmp) + return nil } -func (a *AufsDriver) OnMount(dir graphdriver.Dir, dest string) error { - layers, err := a.getLayers(dir) +// 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 + } + 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)) +} + +// 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 { + if !os.IsNotExist(err) { + return "", err + } + ids = []string{} + } + + // 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) +} + +// 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 + } + + parentIds, err := getParentIds(a.rootPath(), id) if err != nil { return err } - - target := path.Join(dest, "rootfs") - rw := path.Join(dest, "rw") - - // Create the target directories if they don't exist - if err := os.Mkdir(target, 0755); err != nil && !os.IsExist(err) { - return err + if len(parentIds) == 0 { + return fmt.Errorf("Dir %s does not have any parent layers", id) } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err + 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 diff --git a/aufs/aufs_test.go b/aufs/aufs_test.go new file mode 100644 index 000000000..d3de559fb --- /dev/null +++ b/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) + } +} +*/ diff --git a/aufs/dirs.go b/aufs/dirs.go new file mode 100644 index 000000000..dc0007b90 --- /dev/null +++ b/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 +} diff --git a/aufs/mount.go b/aufs/mount.go index 15cb2da93..4355d3b77 100644 --- a/aufs/mount.go +++ b/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 } diff --git a/graph_test.go b/graph_test.go index 7e306c815..ab4168026 100644 --- a/graph_test.go +++ b/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) } diff --git a/runtime.go b/runtime.go index 75bae0293..b727cd65a 100644 --- a/runtime.go +++ b/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") diff --git a/utils.go b/utils.go index a5df3d59a..433d00ad6 100644 --- a/utils.go +++ b/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 {