Skip to content

Commit

Permalink
devicemapper: trim and resize from alexl
Browse files Browse the repository at this point in the history
Largely bringing in the Trim and resize logic by @alexlarsson and
moby#4202

Signed-off-by: Vincent Batts <vbatts@redhat.com>
  • Loading branch information
vbatts committed Jan 7, 2015
1 parent 11e4799 commit 29ec95f
Show file tree
Hide file tree
Showing 5 changed files with 520 additions and 1 deletion.
191 changes: 191 additions & 0 deletions daemon/graphdriver/devmapper/deviceset.go
Expand Up @@ -728,7 +728,102 @@ func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}

// TODO(vbatts) copied from https://github.com/docker/docker/pull/4202/files but need to ensure the logic stays sane when path is a block, not loopdevice
func (devices *DeviceSet) getBlockDevice(name string) (*os.File, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)

file, err := os.OpenFile(filename, os.O_RDWR, 0)
if file == nil {
return nil, err
}
defer file.Close()

loopback := devicemapper.FindLoopDeviceFor(file)
if loopback == nil {
return nil, fmt.Errorf("Unable to find loopback mount for: %s", filename)
}
return loopback, nil
}

// TrimPool discard blocks from the unused portions of this DeviceSet
func (devices *DeviceSet) TrimPool() error {
devices.Lock()
defer devices.Unlock()

totalSizeInSectors, _, _, dataTotal, _, _, err := devices.poolStatus()
if err != nil {
return err
}
blockSizeInSectors := totalSizeInSectors / dataTotal
SectorSize := blockSizeInSectors * 512

data, err := devices.getBlockDevice("data")
if err != nil {
return err
}
defer data.Close()

dataSize, err := devicemapper.GetBlockDeviceSize(data)
if err != nil {
return err
}

metadata, err := devices.getBlockDevice("metadata")
if err != nil {
return err
}
defer metadata.Close()

// Suspend the pool so the metadata doesn't change and new blocks
// are not loaded
if err := devicemapper.SuspendDevice(devices.getPoolName()); err != nil {
return fmt.Errorf("Unable to suspend pool: %s", err)
}

// Just in case, make sure everything is on disk
syscall.Sync()

ranges, err := devicemapper.ReadMetadataRanges(metadata.Name())
if err != nil {
devicemapper.ResumeDevice(devices.getPoolName())
return err
}

lastEnd := uint64(0)

for e := ranges.Front(); e != nil; e = e.Next() {
r := e.Value.(*devicemapper.Range)
// Convert to bytes
rBegin := r.Begin() * SectorSize
rEnd := r.End() * SectorSize

if rBegin > lastEnd {
if err := devicemapper.BlockDeviceDiscardFile(data, lastEnd, rBegin-lastEnd); err != nil {
return fmt.Errorf("Failing do discard block, leaving pool suspended: %v", err)
}
}
lastEnd = rEnd
}

if dataSize > lastEnd {
if err := devicemapper.BlockDeviceDiscardFile(data, lastEnd, dataSize-lastEnd); err != nil {
return fmt.Errorf("Failing do discard block, leaving pool suspended: %v", err)
}
}

// Resume the pool
if err := devicemapper.ResumeDevice(devices.getPoolName()); err != nil {
return fmt.Errorf("Unable to resume pool: %s", err)
}

return nil
}

func (devices *DeviceSet) ResizePool(size int64) error {
devices.Lock()
defer devices.Unlock()

dirname := devices.loopbackDir()
datafilename := path.Join(dirname, "data")
if len(devices.dataDevice) > 0 {
Expand Down Expand Up @@ -800,6 +895,102 @@ func (devices *DeviceSet) ResizePool(size int64) error {
return nil
}

// ResizeDevice of hash to provided size
func (devices *DeviceSet) ResizeDevice(hash string, size int64) error {
info, err := devices.lookupDevice(hash)
if err != nil {
return err
}

info.lock.Lock()
defer info.lock.Unlock()

if size < 0 || info.Size > uint64(size) {
return fmt.Errorf("Can't shrink devices")
}

devices.Lock()
defer devices.Unlock()

devinfo, err := devicemapper.GetInfo(info.Name())
if info == nil {
return err
}

if devinfo.OpenCount != 0 {
return fmt.Errorf("Device in use")
}

if devinfo.Exists != 0 {
if err := devices.deactivateDevice(info); err != nil {
return err
}
}
oldSize := info.Size
info.Size = uint64(size)

if err := devices.saveMetadata(info); err != nil {
info.Size = oldSize
return err
}

// Activate with new size
if err := devices.activateDeviceIfNeeded(info); err != nil {
return err
}

fstype, err := ProbeFsType(info.DevName())
if err != nil {
return err
}

switch fstype {
case "xfs":
dir, err := ioutil.TempDir(devices.root, "resizemnt")
if err != nil {
return err
}

defer os.Remove(dir)

err = syscall.Mount(info.DevName(), dir, "xfs", syscall.MS_MGC_VAL, "nouuid")
if err != nil {
return err
}

err = exec.Command("xfs_growfs", dir).Run()
if err != nil {
syscall.Unmount(dir, 0)
return fmt.Errorf("xfs_growfs failed: %v", err)
}

err = syscall.Unmount(dir, 0)
if err != nil {
return err
}

case "ext4":
err = exec.Command("e2fsck", "-f", "-y", info.DevName()).Run()
if err != nil {
return fmt.Errorf("e2fsck failed: %v", err)
}

err = exec.Command("resize2fs", info.DevName()).Run()
if err != nil {
return fmt.Errorf("resizee2fs failed: %v", err)
}

default:
return fmt.Errorf("Unsupported filesystem %s", fstype)
}

if err := devices.deactivateDevice(info); err != nil {
return err
}

return nil
}

func (devices *DeviceSet) loadTransactionMetaData() error {
jsonData, err := ioutil.ReadFile(devices.transactionMetaFile())
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion pkg/devicemapper/devmapper.go
Expand Up @@ -354,6 +354,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) {
return uint64(size), nil
}

// BlockDeviceDiscard will discard _all_ blocks from the device at path
func BlockDeviceDiscard(path string) error {
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
Expand All @@ -366,7 +367,7 @@ func BlockDeviceDiscard(path string) error {
return err
}

if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil {
if err := BlockDeviceDiscardFile(file, 0, size); err != nil {
return err
}

Expand All @@ -377,6 +378,11 @@ func BlockDeviceDiscard(path string) error {
return nil
}

// BlockDeviceDiscardFile allows specifying the offset and length of blocks to discard from file
func BlockDeviceDiscardFile(file *os.File, offset, length uint64) error {
return ioctlBlkDiscard(file.Fd(), offset, length)
}

// This is the programmatic example of "dmsetup create"
func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize uint32) error {
task, err := TaskCreateNamed(DeviceCreate, poolName)
Expand Down

0 comments on commit 29ec95f

Please sign in to comment.