From 90396dc0fc8a88fbb248ea169b22a8089f109ea1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 18 Nov 2013 12:12:04 +0100 Subject: [PATCH] devicemapper tool: Add support for pool resizing Upstream-commit: a0224e61b48e1dd1378cb955596fba1937e99068 Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 66 ++++++++++ .../engine/graphdriver/devmapper/devmapper.go | 113 +++++++++++++++--- .../devmapper/devmapper_wrapper.go | 12 ++ .../docker-device-tool/device_tool.go | 57 ++++++++- 4 files changed, 228 insertions(+), 20 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index 4e34a92e62..e24840634d 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -367,6 +367,72 @@ func minor(device uint64) uint64 { return (device & 0xff) | ((device >> 12) & 0xfff00) } +func (devices *DeviceSet) ResizePool(size int64) error { + dirname := devices.loopbackDir() + datafilename := path.Join(dirname, "data") + metadatafilename := path.Join(dirname, "metadata") + + datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0) + if datafile == nil { + return err + } + defer datafile.Close() + + fi, err := datafile.Stat() + if fi == nil { + return err + } + + if fi.Size() > size { + return fmt.Errorf("Can't shrink file") + } + + dataloopback := FindLoopDeviceFor(datafile) + if dataloopback == nil { + return fmt.Errorf("Unable to find loopback mount for: %s", datafilename) + } + defer dataloopback.Close() + + metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0) + if metadatafile == nil { + return err + } + defer metadatafile.Close() + + metadataloopback := FindLoopDeviceFor(metadatafile) + if metadataloopback == nil { + return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename) + } + defer metadataloopback.Close() + + // Grow loopback file + if err := datafile.Truncate(size); err != nil { + return fmt.Errorf("Unable to grow loopback file: %s", err) + } + + // Reload size for loopback device + if err := LoopbackSetCapacity(dataloopback); err != nil { + return fmt.Errorf("Unable to update loopback capacity: %s", err) + } + + // Suspend the pool + if err := suspendDevice(devices.getPoolName()); err != nil { + return fmt.Errorf("Unable to suspend pool: %s", err) + } + + // Reload with the new block sizes + if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil { + return fmt.Errorf("Unable to reload pool: %s", err) + } + + // Resume the pool + if err := resumeDevice(devices.getPoolName()); err != nil { + return fmt.Errorf("Unable to resume pool: %s", err) + } + + return nil +} + func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 54e21adddb..6901ba9a6e 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "os" "runtime" + "syscall" ) type DevmapperLogger interface { @@ -40,25 +41,27 @@ const ( ) var ( - ErrTaskRun = errors.New("dm_task_run failed") - ErrTaskSetName = errors.New("dm_task_set_name failed") - ErrTaskSetMessage = errors.New("dm_task_set_message failed") - ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") - ErrTaskSetRo = errors.New("dm_task_set_ro failed") - ErrTaskAddTarget = errors.New("dm_task_add_target failed") - ErrTaskSetSector = errors.New("dm_task_set_sector failed") - ErrTaskGetInfo = errors.New("dm_task_get_info failed") - ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") - ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") - ErrNilCookie = errors.New("cookie ptr can't be nil") - ErrAttachLoopbackDevice = errors.New("loopback mounting failed") - ErrGetBlockSize = errors.New("Can't get block size") - ErrUdevWait = errors.New("wait on udev cookie failed") - ErrSetDevDir = errors.New("dm_set_dev_dir failed") - ErrGetLibraryVersion = errors.New("dm_get_library_version failed") - ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") - ErrRunRemoveDevice = errors.New("running removeDevice failed") - ErrInvalidAddNode = errors.New("Invalide AddNoce type") + ErrTaskRun = errors.New("dm_task_run failed") + ErrTaskSetName = errors.New("dm_task_set_name failed") + ErrTaskSetMessage = errors.New("dm_task_set_message failed") + ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") + ErrTaskSetRo = errors.New("dm_task_set_ro failed") + ErrTaskAddTarget = errors.New("dm_task_add_target failed") + ErrTaskSetSector = errors.New("dm_task_set_sector failed") + ErrTaskGetInfo = errors.New("dm_task_get_info failed") + ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") + ErrNilCookie = errors.New("cookie ptr can't be nil") + ErrAttachLoopbackDevice = errors.New("loopback mounting failed") + ErrGetBlockSize = errors.New("Can't get block size") + ErrUdevWait = errors.New("wait on udev cookie failed") + ErrSetDevDir = errors.New("dm_set_dev_dir failed") + ErrGetLibraryVersion = errors.New("dm_get_library_version failed") + ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") + ErrRunRemoveDevice = errors.New("running removeDevice failed") + ErrInvalidAddNode = errors.New("Invalide AddNoce type") + ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file") + ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity") ) type ( @@ -186,6 +189,55 @@ func AttachLoopDevice(filename string) (*os.File, error) { return os.NewFile(uintptr(fd), res), nil } +func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { + dev, inode, err := dmGetLoopbackBackingFile(file.Fd()) + if err != 0 { + return 0, 0, ErrGetLoopbackBackingFile + } + return dev, inode, nil +} + +func LoopbackSetCapacity(file *os.File) error { + err := dmLoopbackSetCapacity(file.Fd()) + if err != 0 { + return ErrLoopbackSetCapacity + } + return nil +} + +func FindLoopDeviceFor(file *os.File) *os.File { + stat, err := file.Stat() + if err != nil { + return nil + } + targetInode := stat.Sys().(*syscall.Stat_t).Ino + targetDevice := stat.Sys().(*syscall.Stat_t).Dev + + for i := 0; true; i++ { + path := fmt.Sprintf("/dev/loop%d", i) + + file, err := os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + // Ignore all errors until the first not-exist + // we want to continue looking for the file + continue + } + + dev, inode, err := getLoopbackBackingFile(file) + if err == nil && dev == targetDevice && inode == targetInode { + return file + } + + file.Close() + } + + return nil +} + func UdevWait(cookie uint) error { if res := DmUdevWait(cookie); res != 1 { utils.Debugf("Failed to wait on udev cookie %d", cookie) @@ -276,6 +328,29 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error return nil } +func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error { + task, err := createTask(DeviceReload, poolName) + if task == nil { + return err + } + + size, err := GetBlockDeviceSize(dataFile) + if err != nil { + return fmt.Errorf("Can't get data size") + } + + params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" + if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { + return fmt.Errorf("Can't add target") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + return nil +} + func createTask(t TaskType, name string) (*Task, error) { task := TaskCreate(t) if task == nil { diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index fcd125e6c5..f9a7d039cf 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -239,6 +239,18 @@ func dmTaskAddTargetFct(task *CDmTask, C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } +func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) { + var lo64 C.struct_loop_info64 + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64, + uintptr(unsafe.Pointer(&lo64))) + return uint64(lo64.lo_device), uint64(lo64.lo_inode), err +} + +func dmLoopbackSetCapacity(fd uintptr) syscall.Errno { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0) + return err +} + func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) { var size int64 _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, diff --git a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go index bd7831c0c5..4d1ee0cea5 100644 --- a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go +++ b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go @@ -7,14 +7,51 @@ import ( "os" "path" "sort" + "strconv" + "strings" ) func usage() { - fmt.Fprintf(os.Stderr, "Usage: %s [status] | [list] | [device id] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [status] | [list] | [device id] | [resize new-pool-size] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) flag.PrintDefaults() os.Exit(1) } +func byteSizeFromString(arg string) (int64, error) { + digits := "" + rest := "" + last := strings.LastIndexAny(arg, "0123456789") + if last >= 0 { + digits = arg[:last+1] + rest = arg[last+1:] + } + + val, err := strconv.ParseInt(digits, 10, 64) + if err != nil { + return val, err + } + + rest = strings.ToLower(strings.TrimSpace(rest)) + + var multiplier int64 = 1 + switch rest { + case "": + multiplier = 1 + case "k", "kb": + multiplier = 1024 + case "m", "mb": + multiplier = 1024 * 1024 + case "g", "gb": + multiplier = 1024 * 1024 * 1024 + case "t", "tb": + multiplier = 1024 * 1024 * 1024 * 1024 + default: + return 0, fmt.Errorf("Unknown size unit: %s", rest) + } + + return val * multiplier, nil +} + func main() { root := flag.String("r", "/var/lib/docker", "Docker root dir") flDebug := flag.Bool("D", false, "Debug mode") @@ -70,6 +107,24 @@ func main() { fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors) fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors) fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector) + break + case "resize": + if flag.NArg() < 2 { + usage() + } + + size, err := byteSizeFromString(args[1]) + if err != nil { + fmt.Println("Invalid size: ", err) + os.Exit(1) + } + + err = devices.ResizePool(size) + if err != nil { + fmt.Println("Error resizeing pool: ", err) + os.Exit(1) + } + break case "snap": if flag.NArg() < 3 {