ソースを参照

devicemapper tool: Add support for pool resizing

Alexander Larsson 11 年 前
コミット
a0224e61b4

+ 66 - 0
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)
 

+ 94 - 19
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 {

+ 12 - 0
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,

+ 56 - 1
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 <flags>  [status] | [list] | [device id] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
+	fmt.Fprintf(os.Stderr, "Usage: %s <flags>  [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 {