// +build linux package aufs import ( "crypto/sha256" "encoding/hex" "fmt" "io/ioutil" "os" "path" "testing" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/reexec" ) var ( tmpOuter = path.Join(os.TempDir(), "aufs-tests") tmp = path.Join(tmpOuter, "aufs") ) func init() { reexec.Init() } func testInit(dir string, t *testing.T) graphdriver.Driver { d, err := Init(dir, nil, nil, nil) if err != nil { if err == graphdriver.ErrNotSupported { t.Skip(err) } else { t.Fatal(err) } } return d } func newDriver(t *testing.T) *Driver { if err := os.MkdirAll(tmp, 0755); err != nil { t.Fatal(err) } d := testInit(tmp, t) return d.(*Driver) } func TestNewDriver(t *testing.T) { if err := os.MkdirAll(tmp, 0755); err != nil { t.Fatal(err) } d := testInit(tmp, t) 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, 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) } testInit(tmp, t) testInit(tmp, t) 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, 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, 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, "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(d.active["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(d.active["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) } defer func() { if err := d.Cleanup(); 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, "mnt", "2") if mntPath != expected { t.Fatalf("Expected %s got %s", expected, mntPath) } } func TestRemoveMountedDir(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) } defer func() { if err := d.Cleanup(); 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") } mounted, err := d.mounted(d.active["2"]) if err != nil { t.Fatal(err) } if !mounted { t.Fatalf("Dir id 2 should be mounted") } if err := d.Remove("2"); 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") } } func TestChanges(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) } defer func() { if err := d.Cleanup(); err != nil { t.Fatal(err) } }() mntPoint, err := d.Get("2", "") if err != nil { t.Fatal(err) } // Create a file to save in the mountpoint f, err := os.Create(path.Join(mntPoint, "test.txt")) if err != nil { t.Fatal(err) } if _, err := f.WriteString("testline"); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } changes, err := d.Changes("2", "") if err != nil { t.Fatal(err) } if len(changes) != 1 { t.Fatalf("Dir 2 should have one change from parent got %d", len(changes)) } change := changes[0] expectedPath := "/test.txt" if change.Path != expectedPath { t.Fatalf("Expected path %s got %s", expectedPath, change.Path) } if change.Kind != archive.ChangeAdd { t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) } if err := d.Create("3", "2", ""); err != nil { t.Fatal(err) } mntPoint, err = d.Get("3", "") if err != nil { t.Fatal(err) } // Create a file to save in the mountpoint f, err = os.Create(path.Join(mntPoint, "test2.txt")) if err != nil { t.Fatal(err) } if _, err := f.WriteString("testline"); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } changes, err = d.Changes("3", "") if err != nil { t.Fatal(err) } if len(changes) != 1 { t.Fatalf("Dir 2 should have one change from parent got %d", len(changes)) } change = changes[0] expectedPath = "/test2.txt" if change.Path != expectedPath { t.Fatalf("Expected path %s got %s", expectedPath, change.Path) } if change.Kind != archive.ChangeAdd { t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) } } 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) } if err := f.Truncate(size); err != nil { t.Fatal(err) } 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) } } func TestChildDiffSize(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) defer d.Cleanup() 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) } 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) } if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } diffSize, err = d.DiffSize("2", "") if err != nil { t.Fatal(err) } // The diff size for the child should be zero if diffSize != 0 { t.Fatalf("Expected size to be %d got %d", 0, diffSize) } } func TestExists(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) defer d.Cleanup() if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } if d.Exists("none") { t.Fatal("id name should not exist in the driver") } if !d.Exists("1") { t.Fatal("id 1 should exist in the driver") } } func TestStatus(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) defer d.Cleanup() if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } status := d.Status() if status == nil || len(status) == 0 { t.Fatal("Status should not be nil or empty") } rootDir := status[0] dirs := status[2] if rootDir[0] != "Root Dir" { t.Fatalf("Expected Root Dir got %s", rootDir[0]) } if rootDir[1] != d.rootPath() { t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1]) } if dirs[0] != "Dirs" { t.Fatalf("Expected Dirs got %s", dirs[0]) } if dirs[1] != "1" { t.Fatalf("Expected 1 got %s", dirs[1]) } } func TestApplyDiff(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) defer d.Cleanup() 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() diff, err := d.Diff("1", "") if err != nil { t.Fatal(err) } if err := d.Create("2", "", ""); err != nil { t.Fatal(err) } if err := d.Create("3", "2", ""); err != nil { t.Fatal(err) } if err := d.applyDiff("3", diff); err != nil { t.Fatal(err) } // Ensure that the file is in the mount point for id 3 mountPoint, err := d.Get("3", "") if err != nil { t.Fatal(err) } if _, err := os.Stat(path.Join(mountPoint, "test_file")); err != nil { t.Fatal(err) } } func TestHardlinks(t *testing.T) { // Copy 2 layers that have linked files to new layers and check if hardlink are preserved d := newDriver(t) defer os.RemoveAll(tmp) defer d.Cleanup() origFile := "test_file" linkedFile := "linked_file" if err := d.Create("source-1", "", ""); err != nil { t.Fatal(err) } mountPath, err := d.Get("source-1", "") if err != nil { t.Fatal(err) } f, err := os.Create(path.Join(mountPath, origFile)) if err != nil { t.Fatal(err) } f.Close() layerTar1, err := d.Diff("source-1", "") if err != nil { t.Fatal(err) } if err := d.Create("source-2", "source-1", ""); err != nil { t.Fatal(err) } mountPath, err = d.Get("source-2", "") if err != nil { t.Fatal(err) } if err := os.Link(path.Join(mountPath, origFile), path.Join(mountPath, linkedFile)); err != nil { t.Fatal(err) } layerTar2, err := d.Diff("source-2", "source-1") if err != nil { t.Fatal(err) } if err := d.Create("target-1", "", ""); err != nil { t.Fatal(err) } if _, err := d.ApplyDiff("target-1", "", layerTar1); err != nil { t.Fatal(err) } if err := d.Create("target-2", "target-1", ""); err != nil { t.Fatal(err) } if _, err := d.ApplyDiff("target-2", "target-1", layerTar2); err != nil { t.Fatal(err) } mountPath, err = d.Get("target-2", "") if err != nil { t.Fatal(err) } fi1, err := os.Lstat(path.Join(mountPath, origFile)) if err != nil { t.Fatal(err) } fi2, err := os.Lstat(path.Join(mountPath, linkedFile)) if err != nil { t.Fatal(err) } if !os.SameFile(fi1, fi2) { t.Fatal("Target files are not linked") } } func hash(c string) string { h := sha256.New() fmt.Fprint(h, c) return hex.EncodeToString(h.Sum(nil)) } func testMountMoreThan42Layers(t *testing.T, mountPath string) { if err := os.MkdirAll(mountPath, 0755); err != nil { t.Fatal(err) } defer os.RemoveAll(mountPath) d := testInit(mountPath, t).(*Driver) defer d.Cleanup() var last string var expected int for i := 1; i < 127; i++ { expected++ var ( parent = fmt.Sprintf("%d", i-1) current = fmt.Sprintf("%d", i) ) if parent == "0" { parent = "" } else { parent = hash(parent) } current = hash(current) if err := d.Create(current, parent, ""); err != nil { t.Logf("Current layer %d", i) t.Error(err) } point, err := d.Get(current, "") if err != nil { t.Logf("Current layer %d", i) t.Error(err) } f, err := os.Create(path.Join(point, current)) if err != nil { t.Logf("Current layer %d", i) t.Error(err) } f.Close() if i%10 == 0 { if err := os.Remove(path.Join(point, parent)); err != nil { t.Logf("Current layer %d", i) t.Error(err) } expected-- } last = current } // Perform the actual mount for the top most image point, err := d.Get(last, "") if err != nil { t.Error(err) } files, err := ioutil.ReadDir(point) if err != nil { t.Error(err) } if len(files) != expected { t.Errorf("Expected %d got %d", expected, len(files)) } } func TestMountMoreThan42Layers(t *testing.T) { os.RemoveAll(tmpOuter) testMountMoreThan42Layers(t, tmp) } func TestMountMoreThan42LayersMatchingPathLength(t *testing.T) { defer os.RemoveAll(tmpOuter) zeroes := "0" for { // This finds a mount path so that when combined into aufs mount options // 4096 byte boundary would be in between the paths or in permission // section. For '/tmp' it will use '/tmp/aufs-tests/00000000/aufs' mountPath := path.Join(tmpOuter, zeroes, "aufs") pathLength := 77 + len(mountPath) if mod := 4095 % pathLength; mod == 0 || mod > pathLength-2 { t.Logf("Using path: %s", mountPath) testMountMoreThan42Layers(t, mountPath) return } zeroes += "0" } }