package devmapper import ( "fmt" "github.com/dotcloud/docker/graphdriver" "io/ioutil" "path" "runtime" "strings" "syscall" "testing" ) func init() { // Reduce the size the the base fs and loopback for the tests DefaultDataLoopbackSize = 300 * 1024 * 1024 DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 DefaultBaseFsSize = 300 * 1024 * 1024 } // denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default func denyAllDevmapper() { // Hijack all calls to libdevmapper with default panics. // Authorized calls are selectively hijacked in each tests. DmTaskCreate = func(t int) *CDmTask { panic("DmTaskCreate: this method should not be called here") } DmTaskRun = func(task *CDmTask) int { panic("DmTaskRun: this method should not be called here") } DmTaskSetName = func(task *CDmTask, name string) int { panic("DmTaskSetName: this method should not be called here") } DmTaskSetMessage = func(task *CDmTask, message string) int { panic("DmTaskSetMessage: this method should not be called here") } DmTaskSetSector = func(task *CDmTask, sector uint64) int { panic("DmTaskSetSector: this method should not be called here") } DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { panic("DmTaskSetCookie: this method should not be called here") } DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { panic("DmTaskSetAddNode: this method should not be called here") } DmTaskSetRo = func(task *CDmTask) int { panic("DmTaskSetRo: this method should not be called here") } DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { panic("DmTaskAddTarget: this method should not be called here") } DmTaskGetInfo = func(task *CDmTask, info *Info) int { panic("DmTaskGetInfo: this method should not be called here") } DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { panic("DmGetNextTarget: this method should not be called here") } DmUdevWait = func(cookie uint) int { panic("DmUdevWait: this method should not be called here") } DmSetDevDir = func(dir string) int { panic("DmSetDevDir: this method should not be called here") } DmGetLibraryVersion = func(version *string) int { panic("DmGetLibraryVersion: this method should not be called here") } DmLogInitVerbose = func(level int) { panic("DmLogInitVerbose: this method should not be called here") } DmTaskDestroy = func(task *CDmTask) { panic("DmTaskDestroy: this method should not be called here") } LogWithErrnoInit = func() { panic("LogWithErrnoInit: this method should not be called here") } } func denyAllSyscall() { sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { panic("sysMount: this method should not be called here") } sysUnmount = func(target string, flags int) (err error) { panic("sysUnmount: this method should not be called here") } sysCloseOnExec = func(fd int) { panic("sysCloseOnExec: this method should not be called here") } sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { panic("sysSyscall: this method should not be called here") } // Not a syscall, but forbidding it here anyway Mounted = func(mnt string) (bool, error) { panic("devmapper.Mounted: this method should not be called here") } // osOpenFile = os.OpenFile // osNewFile = os.NewFile // osCreate = os.Create // osStat = os.Stat // osIsNotExist = os.IsNotExist // osIsExist = os.IsExist // osMkdirAll = os.MkdirAll // osRemoveAll = os.RemoveAll // osRename = os.Rename // osReadlink = os.Readlink // execRun = func(name string, args ...string) error { // return exec.Command(name, args...).Run() // } } func mkTestDirectory(t *testing.T) string { dir, err := ioutil.TempDir("", "docker-test-devmapper-") if err != nil { t.Fatal(err) } return dir } func newDriver(t *testing.T) *Driver { home := mkTestDirectory(t) d, err := Init(home) if err != nil { t.Fatal(err) } return d.(*Driver) } func cleanup(d *Driver) { d.Cleanup() osRemoveAll(d.home) } type Set map[string]bool func (r Set) Assert(t *testing.T, names ...string) { for _, key := range names { if _, exists := r[key]; !exists { t.Fatalf("Key not set: %s", key) } delete(r, key) } if len(r) != 0 { t.Fatalf("Unexpected keys: %v", r) } } func TestInit(t *testing.T) { var ( calls = make(Set) taskMessages = make(Set) taskTypes = make(Set) home = mkTestDirectory(t) ) defer osRemoveAll(home) func() { denyAllDevmapper() DmSetDevDir = func(dir string) int { calls["DmSetDevDir"] = true expectedDir := "/dev" if dir != expectedDir { t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir) } return 0 } LogWithErrnoInit = func() { calls["DmLogWithErrnoInit"] = true } var task1 CDmTask DmTaskCreate = func(taskType int) *CDmTask { calls["DmTaskCreate"] = true taskTypes[fmt.Sprintf("%d", taskType)] = true return &task1 } DmTaskSetName = func(task *CDmTask, name string) int { calls["DmTaskSetName"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task) } // FIXME: use Set.AssertRegexp() if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") || !strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name) } return 1 } DmTaskRun = func(task *CDmTask) int { calls["DmTaskRun"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task) } return 1 } DmTaskGetInfo = func(task *CDmTask, info *Info) int { calls["DmTaskGetInfo"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task) } // This will crash if info is not dereferenceable info.Exists = 0 return 1 } DmTaskSetSector = func(task *CDmTask, sector uint64) int { calls["DmTaskSetSector"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) } if expectedSector := uint64(0); sector != expectedSector { t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector) } return 1 } DmTaskSetMessage = func(task *CDmTask, message string) int { calls["DmTaskSetMessage"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) } taskMessages[message] = true return 1 } DmTaskDestroy = func(task *CDmTask) { calls["DmTaskDestroy"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) } } DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { calls["DmTaskSetTarget"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) } if start != 0 { t.Fatalf("Wrong start: %d != %d", start, 0) } if ttype != "thin" && ttype != "thin-pool" { t.Fatalf("Wrong ttype: %s", ttype) } // Quick smoke test if params == "" { t.Fatalf("Params should not be empty") } return 1 } fakeCookie := uint(4321) DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { calls["DmTaskSetCookie"] = true expectedTask := &task1 if task != expectedTask { t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) } if flags != 0 { t.Fatalf("Cookie flags should be 0 (not %x)", flags) } *cookie = fakeCookie return 1 } DmUdevWait = func(cookie uint) int { calls["DmUdevWait"] = true if cookie != fakeCookie { t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie) } return 1 } DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { if addNode != AddNodeOnCreate { t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate) } calls["DmTaskSetAddNode"] = true return 1 } execRun = func(name string, args ...string) error { calls["execRun"] = true if name != "mkfs.ext4" { t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name) } return nil } driver, err := Init(home) if err != nil { t.Fatal(err) } defer func() { if err := driver.Cleanup(); err != nil { t.Fatal(err) } }() }() // Put all tests in a funciton to make sure the garbage collection will // occur. // Call GC to cleanup runtime.Finalizers runtime.GC() calls.Assert(t, "DmSetDevDir", "DmLogWithErrnoInit", "DmTaskSetName", "DmTaskRun", "DmTaskGetInfo", "DmTaskDestroy", "execRun", "DmTaskCreate", "DmTaskSetTarget", "DmTaskSetCookie", "DmUdevWait", "DmTaskSetSector", "DmTaskSetMessage", "DmTaskSetAddNode", ) taskTypes.Assert(t, "0", "6", "17") taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1") } func fakeInit() func(home string) (graphdriver.Driver, error) { oldInit := Init Init = func(home string) (graphdriver.Driver, error) { return &Driver{ home: home, }, nil } return oldInit } func restoreInit(init func(home string) (graphdriver.Driver, error)) { Init = init } func mockAllDevmapper(calls Set) { DmSetDevDir = func(dir string) int { calls["DmSetDevDir"] = true return 0 } LogWithErrnoInit = func() { calls["DmLogWithErrnoInit"] = true } DmTaskCreate = func(taskType int) *CDmTask { calls["DmTaskCreate"] = true return &CDmTask{} } DmTaskSetName = func(task *CDmTask, name string) int { calls["DmTaskSetName"] = true return 1 } DmTaskRun = func(task *CDmTask) int { calls["DmTaskRun"] = true return 1 } DmTaskGetInfo = func(task *CDmTask, info *Info) int { calls["DmTaskGetInfo"] = true return 1 } DmTaskSetSector = func(task *CDmTask, sector uint64) int { calls["DmTaskSetSector"] = true return 1 } DmTaskSetMessage = func(task *CDmTask, message string) int { calls["DmTaskSetMessage"] = true return 1 } DmTaskDestroy = func(task *CDmTask) { calls["DmTaskDestroy"] = true } DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { calls["DmTaskSetTarget"] = true return 1 } DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { calls["DmTaskSetCookie"] = true return 1 } DmUdevWait = func(cookie uint) int { calls["DmUdevWait"] = true return 1 } DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { calls["DmTaskSetAddNode"] = true return 1 } execRun = func(name string, args ...string) error { calls["execRun"] = true return nil } } func TestDriverName(t *testing.T) { denyAllDevmapper() defer denyAllDevmapper() oldInit := fakeInit() defer restoreInit(oldInit) d := newDriver(t) if d.String() != "devicemapper" { t.Fatalf("Expected driver name to be devicemapper got %s", d.String()) } } func TestDriverCreate(t *testing.T) { denyAllDevmapper() denyAllSyscall() defer denyAllSyscall() defer denyAllDevmapper() calls := make(Set) mockAllDevmapper(calls) sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { calls["sysMount"] = true // FIXME: compare the exact source and target strings (inodes + devname) if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) } if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) } if expectedFstype := "ext4"; fstype != expectedFstype { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) } if expectedFlags := uintptr(3236757504); flags != expectedFlags { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) } return nil } Mounted = func(mnt string) (bool, error) { calls["Mounted"] = true if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") { t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt) } return false, nil } sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { calls["sysSyscall"] = true if trap != sysSysIoctl { t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap) } switch a2 { case LoopSetFd: calls["ioctl.loopsetfd"] = true case LoopCtlGetFree: calls["ioctl.loopctlgetfree"] = true case LoopGetStatus64: calls["ioctl.loopgetstatus"] = true case LoopSetStatus64: calls["ioctl.loopsetstatus"] = true case LoopClrFd: calls["ioctl.loopclrfd"] = true case LoopSetCapacity: calls["ioctl.loopsetcapacity"] = true case BlkGetSize64: calls["ioctl.blkgetsize"] = true default: t.Fatalf("Unexpected IOCTL. Received %d", a2) } return 0, 0, 0 } func() { d := newDriver(t) calls.Assert(t, "DmSetDevDir", "DmLogWithErrnoInit", "DmTaskSetName", "DmTaskRun", "DmTaskGetInfo", "execRun", "DmTaskCreate", "DmTaskSetTarget", "DmTaskSetCookie", "DmUdevWait", "DmTaskSetSector", "DmTaskSetMessage", "DmTaskSetAddNode", "sysSyscall", "ioctl.blkgetsize", "ioctl.loopsetfd", "ioctl.loopsetstatus", ) if err := d.Create("1", ""); err != nil { t.Fatal(err) } calls.Assert(t, "DmTaskCreate", "DmTaskGetInfo", "sysMount", "Mounted", "DmTaskRun", "DmTaskSetTarget", "DmTaskSetSector", "DmTaskSetCookie", "DmUdevWait", "DmTaskSetName", "DmTaskSetMessage", "DmTaskSetAddNode", ) }() runtime.GC() calls.Assert(t, "DmTaskDestroy", ) } func TestDriverRemove(t *testing.T) { denyAllDevmapper() denyAllSyscall() defer denyAllSyscall() defer denyAllDevmapper() calls := make(Set) mockAllDevmapper(calls) sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { calls["sysMount"] = true // FIXME: compare the exact source and target strings (inodes + devname) if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) } if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) } if expectedFstype := "ext4"; fstype != expectedFstype { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) } if expectedFlags := uintptr(3236757504); flags != expectedFlags { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) } return nil } sysUnmount = func(target string, flags int) (err error) { calls["sysUnmount"] = true // FIXME: compare the exact source and target strings (inodes + devname) if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) } if expectedFlags := 0; flags != expectedFlags { t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) } return nil } Mounted = func(mnt string) (bool, error) { calls["Mounted"] = true return false, nil } sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { calls["sysSyscall"] = true if trap != sysSysIoctl { t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap) } switch a2 { case LoopSetFd: calls["ioctl.loopsetfd"] = true case LoopCtlGetFree: calls["ioctl.loopctlgetfree"] = true case LoopGetStatus64: calls["ioctl.loopgetstatus"] = true case LoopSetStatus64: calls["ioctl.loopsetstatus"] = true case LoopClrFd: calls["ioctl.loopclrfd"] = true case LoopSetCapacity: calls["ioctl.loopsetcapacity"] = true case BlkGetSize64: calls["ioctl.blkgetsize"] = true default: t.Fatalf("Unexpected IOCTL. Received %d", a2) } return 0, 0, 0 } func() { d := newDriver(t) calls.Assert(t, "DmSetDevDir", "DmLogWithErrnoInit", "DmTaskSetName", "DmTaskRun", "DmTaskGetInfo", "execRun", "DmTaskCreate", "DmTaskSetTarget", "DmTaskSetCookie", "DmUdevWait", "DmTaskSetSector", "DmTaskSetMessage", "DmTaskSetAddNode", "sysSyscall", "ioctl.blkgetsize", "ioctl.loopsetfd", "ioctl.loopsetstatus", ) if err := d.Create("1", ""); err != nil { t.Fatal(err) } calls.Assert(t, "DmTaskCreate", "DmTaskGetInfo", "sysMount", "Mounted", "DmTaskRun", "DmTaskSetTarget", "DmTaskSetSector", "DmTaskSetCookie", "DmUdevWait", "DmTaskSetName", "DmTaskSetMessage", "DmTaskSetAddNode", ) Mounted = func(mnt string) (bool, error) { calls["Mounted"] = true return true, nil } if err := d.Remove("1"); err != nil { t.Fatal(err) } calls.Assert(t, "DmTaskRun", "DmTaskSetSector", "DmTaskSetName", "DmTaskSetMessage", "DmTaskCreate", "DmTaskGetInfo", "Mounted", "sysUnmount", ) }() runtime.GC() calls.Assert(t, "DmTaskDestroy", ) } func TestCleanup(t *testing.T) { t.Skip("FIXME: not a unit test") t.Skip("Unimplemented") d := newDriver(t) defer osRemoveAll(d.home) mountPoints := make([]string, 2) if err := d.Create("1", ""); err != nil { t.Fatal(err) } // Mount the id p, err := d.Get("1") if err != nil { t.Fatal(err) } mountPoints[0] = p if err := d.Create("2", "1"); err != nil { t.Fatal(err) } p, err = d.Get("2") if err != nil { t.Fatal(err) } mountPoints[1] = p // Ensure that all the mount points are currently mounted for _, p := range mountPoints { if mounted, err := Mounted(p); err != nil { t.Fatal(err) } else if !mounted { t.Fatalf("Expected %s to be mounted", p) } } // Ensure that devices are active for _, p := range []string{"1", "2"} { if !d.HasActivatedDevice(p) { t.Fatalf("Expected %s to have an active device", p) } } if err := d.Cleanup(); err != nil { t.Fatal(err) } // Ensure that all the mount points are no longer mounted for _, p := range mountPoints { if mounted, err := Mounted(p); err != nil { t.Fatal(err) } else if mounted { t.Fatalf("Expected %s to not be mounted", p) } } // Ensure that devices are no longer activated for _, p := range []string{"1", "2"} { if d.HasActivatedDevice(p) { t.Fatalf("Expected %s not be an active device", p) } } } func TestNotMounted(t *testing.T) { t.Skip("FIXME: not a unit test") t.Skip("Not implemented") d := newDriver(t) defer cleanup(d) if err := d.Create("1", ""); err != nil { t.Fatal(err) } mounted, err := Mounted(path.Join(d.home, "mnt", "1")) if err != nil { t.Fatal(err) } if mounted { t.Fatal("Id 1 should not be mounted") } } func TestMounted(t *testing.T) { t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) if err := d.Create("1", ""); err != nil { t.Fatal(err) } if _, err := d.Get("1"); err != nil { t.Fatal(err) } mounted, err := Mounted(path.Join(d.home, "mnt", "1")) if err != nil { t.Fatal(err) } if !mounted { t.Fatal("Id 1 should be mounted") } } func TestInitCleanedDriver(t *testing.T) { t.Skip("FIXME: not a unit test") d := newDriver(t) if err := d.Create("1", ""); err != nil { t.Fatal(err) } if _, err := d.Get("1"); err != nil { t.Fatal(err) } if err := d.Cleanup(); err != nil { t.Fatal(err) } driver, err := Init(d.home) if err != nil { t.Fatal(err) } d = driver.(*Driver) defer cleanup(d) if _, err := d.Get("1"); err != nil { t.Fatal(err) } } func TestMountMountedDriver(t *testing.T) { t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) if err := d.Create("1", ""); err != nil { t.Fatal(err) } // Perform get on same id to ensure that it will // not be mounted twice if _, err := d.Get("1"); err != nil { t.Fatal(err) } if _, err := d.Get("1"); err != nil { t.Fatal(err) } } func TestGetReturnsValidDevice(t *testing.T) { t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) if err := d.Create("1", ""); err != nil { t.Fatal(err) } if !d.HasDevice("1") { t.Fatalf("Expected id 1 to be in device set") } if _, err := d.Get("1"); err != nil { t.Fatal(err) } if !d.HasActivatedDevice("1") { t.Fatalf("Expected id 1 to be activated") } if !d.HasInitializedDevice("1") { t.Fatalf("Expected id 1 to be initialized") } } func TestDriverGetSize(t *testing.T) { t.Skip("FIXME: not a unit test") t.Skipf("Size is currently not implemented") d := newDriver(t) defer cleanup(d) if err := d.Create("1", ""); err != nil { t.Fatal(err) } mountPoint, err := d.Get("1") if err != nil { t.Fatal(err) } size := int64(1024) f, err := osCreate(path.Join(mountPoint, "test_file")) if err != nil { t.Fatal(err) } if err := f.Truncate(size); err != nil { t.Fatal(err) } f.Close() // diffSize, err := d.DiffSize("1") // if err != nil { // t.Fatal(err) // } // if diffSize != size { // t.Fatalf("Expected size %d got %d", size, diffSize) // } } func assertMap(t *testing.T, m map[string]bool, keys ...string) { for _, key := range keys { if _, exists := m[key]; !exists { t.Fatalf("Key not set: %s", key) } delete(m, key) } if len(m) != 0 { t.Fatalf("Unexpected keys: %v", m) } }