[lxc-devel] [lxd/master] lvm: implement lv resizing

brauner on Github lxc-bot at linuxcontainers.org
Thu May 4 15:45:32 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 468 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170504/2f5498fe/attachment.bin>
-------------- next part --------------
From cf05ca3c257e8a77b24fcdd0bde7851524f8ca01 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 4 May 2017 12:56:12 +0200
Subject: [PATCH 1/3] lvm: non-functional changes

Split into storage_lvm.go and storage_lvm_utils.go.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go       | 754 ----------------------------------------------
 lxd/storage_lvm_utils.go | 763 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 763 insertions(+), 754 deletions(-)
 create mode 100644 lxd/storage_lvm_utils.go

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 61849fc..9525d7f 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -3,11 +3,8 @@ package main
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"path/filepath"
-	"strconv"
 	"strings"
-	"syscall"
 
 	"github.com/gorilla/websocket"
 
@@ -16,210 +13,6 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
-func storageVGActivate(lvmVolumePath string) error {
-	output, err := shared.TryRunCommand("vgchange", "-ay", lvmVolumePath)
-	if err != nil {
-		return fmt.Errorf("could not activate volume group \"%s\": %s", lvmVolumePath, output)
-	}
-
-	return nil
-}
-
-func storageLVActivate(lvmVolumePath string) error {
-	output, err := shared.TryRunCommand("lvchange", "-ay", lvmVolumePath)
-	if err != nil {
-		return fmt.Errorf("could not activate logival volume \"%s\": %s", lvmVolumePath, output)
-	}
-
-	return nil
-}
-
-func storagePVExists(pvName string) (bool, error) {
-	err := exec.Command("pvs", "--noheadings", "-o", "lv_attr", pvName).Run()
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			waitStatus := exitError.Sys().(syscall.WaitStatus)
-			if waitStatus.ExitStatus() == 5 {
-				// physical volume not found
-				return false, nil
-			}
-		}
-		return false, fmt.Errorf("error checking for physical volume \"%s\"", pvName)
-	}
-
-	return true, nil
-}
-
-func storageVGExists(vgName string) (bool, error) {
-	err := exec.Command("vgs", "--noheadings", "-o", "lv_attr", vgName).Run()
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			waitStatus := exitError.Sys().(syscall.WaitStatus)
-			if waitStatus.ExitStatus() == 5 {
-				// volume group not found
-				return false, nil
-			}
-		}
-		return false, fmt.Errorf("error checking for volume group \"%s\"", vgName)
-	}
-
-	return true, nil
-}
-
-func storageLVExists(lvName string) (bool, error) {
-	err := exec.Command("lvs", "--noheadings", "-o", "lv_attr", lvName).Run()
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			waitStatus := exitError.Sys().(syscall.WaitStatus)
-			if waitStatus.ExitStatus() == 5 {
-				// logical volume not found
-				return false, nil
-			}
-		}
-		return false, fmt.Errorf("error checking for logical volume \"%s\"", lvName)
-	}
-
-	return true, nil
-}
-
-func lvmGetLVSize(lvPath string) (string, error) {
-	msg, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "size", "--nosuffix", "--units", "b", lvPath)
-	if err != nil {
-		return "", fmt.Errorf("failed to retrieve size of logical volume: %s: %s", string(msg), err)
-	}
-
-	sizeString := string(msg)
-	sizeString = strings.TrimSpace(sizeString)
-	size, err := strconv.ParseInt(sizeString, 10, 64)
-	if err != nil {
-		return "", err
-	}
-
-	detectedSize := shared.GetByteSizeString(size, 0)
-
-	return detectedSize, nil
-}
-
-func storageLVMThinpoolExists(vgName string, poolName string) (bool, error) {
-	output, err := exec.Command("vgs", "--noheadings", "-o", "lv_attr", fmt.Sprintf("%s/%s", vgName, poolName)).Output()
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			waitStatus := exitError.Sys().(syscall.WaitStatus)
-			if waitStatus.ExitStatus() == 5 {
-				// pool LV was not found
-				return false, nil
-			}
-		}
-		return false, fmt.Errorf("error checking for pool \"%s\"", poolName)
-	}
-	// Found LV named poolname, check type:
-	attrs := strings.TrimSpace(string(output[:]))
-	if strings.HasPrefix(attrs, "t") {
-		return true, nil
-	}
-
-	return false, fmt.Errorf("pool named \"%s\" exists but is not a thin pool", poolName)
-}
-
-func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
-	results := []string{}
-
-	cNames, err := dbContainersList(d.db, cTypeRegular)
-	if err != nil {
-		return results, err
-	}
-
-	for _, cName := range cNames {
-		var lvLinkPath string
-		if strings.Contains(cName, shared.SnapshotDelimiter) {
-			lvLinkPath = shared.VarPath("snapshots", fmt.Sprintf("%s.lv", cName))
-		} else {
-			lvLinkPath = shared.VarPath("containers", fmt.Sprintf("%s.lv", cName))
-		}
-
-		if shared.PathExists(lvLinkPath) {
-			results = append(results, cName)
-		}
-	}
-
-	imageNames, err := dbImagesGet(d.db, false)
-	if err != nil {
-		return results, err
-	}
-
-	for _, imageName := range imageNames {
-		imageLinkPath := shared.VarPath("images", fmt.Sprintf("%s.lv", imageName))
-		if shared.PathExists(imageLinkPath) {
-			results = append(results, imageName)
-		}
-	}
-
-	return results, nil
-}
-
-func storageLVMValidateThinPoolName(d *Daemon, vgName string, value string) error {
-	users, err := storageLVMGetThinPoolUsers(d)
-	if err != nil {
-		return fmt.Errorf("error checking if a pool is already in use: %v", err)
-	}
-
-	if len(users) > 0 {
-		return fmt.Errorf("can not change LVM config. Images or containers are still using LVs: %v", users)
-	}
-
-	if value != "" {
-		if vgName == "" {
-			return fmt.Errorf("can not set lvm.thinpool_name without lvm.vg_name set")
-		}
-
-		poolExists, err := storageLVMThinpoolExists(vgName, value)
-		if err != nil {
-			return fmt.Errorf("error checking for thin pool \"%s\" in \"%s\": %v", value, vgName, err)
-		}
-
-		if !poolExists {
-			return fmt.Errorf("pool \"'%s\" does not exist in Volume Group \"%s\"", value, vgName)
-		}
-	}
-
-	return nil
-}
-
-func lvmVGRename(oldName string, newName string) error {
-	output, err := shared.TryRunCommand("vgrename", oldName, newName)
-	if err != nil {
-		return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %s", oldName, newName, output)
-	}
-
-	return nil
-}
-
-func lvmLVRename(vgName string, oldName string, newName string) error {
-	output, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
-	if err != nil {
-		return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %s", oldName, newName, output)
-	}
-
-	return nil
-}
-
-func xfsGenerateNewUUID(lvpath string) error {
-	output, err := shared.RunCommand(
-		"xfs_admin",
-		"-U", "generate",
-		lvpath)
-	if err != nil {
-		return fmt.Errorf("Error generating new UUID: %v\noutput:'%s'", err, output)
-	}
-
-	return nil
-}
-
-func containerNameToLVName(containerName string) string {
-	lvName := strings.Replace(containerName, "-", "--", -1)
-	return strings.Replace(lvName, shared.SnapshotDelimiter, "-", -1)
-}
-
 type storageLvm struct {
 	vgName       string
 	thinPoolName string
@@ -228,86 +21,6 @@ type storageLvm struct {
 	storageShared
 }
 
-func (s *storageLvm) getLvmBlockMountOptions() string {
-	if s.volume.Config["block.mount_options"] != "" {
-		return s.volume.Config["block.mount_options"]
-	}
-
-	if s.pool.Config["volume.block.mount_options"] != "" {
-		return s.pool.Config["volume.block.mount_options"]
-	}
-
-	return "discard"
-}
-
-func (s *storageLvm) getLvmFilesystem() string {
-	if s.volume.Config["block.filesystem"] != "" {
-		return s.volume.Config["block.filesystem"]
-	}
-
-	if s.pool.Config["volume.block.filesystem"] != "" {
-		return s.pool.Config["volume.block.filesystem"]
-	}
-
-	return "ext4"
-}
-
-func (s *storageLvm) getLvmVolumeSize() (string, error) {
-	sz, err := shared.ParseByteSizeString(s.volume.Config["size"])
-	if err != nil {
-		return "", err
-	}
-
-	// Safety net: Set to default value.
-	if sz == 0 {
-		sz, _ = shared.ParseByteSizeString("10GB")
-	}
-
-	return fmt.Sprintf("%d", sz), nil
-}
-
-func (s *storageLvm) getLvmThinpoolName() string {
-	if s.pool.Config["lvm.thinpool_name"] != "" {
-		return s.pool.Config["lvm.thinpool_name"]
-	}
-
-	return "LXDThinpool"
-}
-
-func (s *storageLvm) usesThinpool() bool {
-	// Default is to use a thinpool.
-	if s.pool.Config["lvm.use_thinpool"] == "" {
-		return true
-	}
-
-	return shared.IsTrue(s.pool.Config["lvm.use_thinpool"])
-}
-
-func (s *storageLvm) setLvmThinpoolName(newThinpoolName string) {
-	s.pool.Config["lvm.thinpool_name"] = newThinpoolName
-}
-
-func (s *storageLvm) getOnDiskPoolName() string {
-	if s.vgName != "" {
-		return s.vgName
-	}
-
-	return s.pool.Name
-}
-
-func (s *storageLvm) setOnDiskPoolName(newName string) {
-	s.vgName = newName
-	s.pool.Config["source"] = newName
-}
-
-func getLvmDevPath(lvmPool string, volumeType string, lvmVolume string) string {
-	return fmt.Sprintf("/dev/%s/%s_%s", lvmPool, volumeType, lvmVolume)
-}
-
-func getPrefixedLvName(volumeType string, lvmVolume string) string {
-	return fmt.Sprintf("%s_%s", volumeType, lvmVolume)
-}
-
 // Only initialize the minimal information we need about a given storage type.
 func (s *storageLvm) StorageCoreInit() error {
 	s.sType = storageTypeLvm
@@ -389,47 +102,6 @@ func (s *storageLvm) StoragePoolCheck() error {
 	return nil
 }
 
-func versionSplit(versionString string) (int, int, int, error) {
-	fs := strings.Split(versionString, ".")
-	majs, mins, incs := fs[0], fs[1], fs[2]
-
-	maj, err := strconv.Atoi(majs)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-	min, err := strconv.Atoi(mins)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-	incs = strings.Split(incs, "(")[0]
-	inc, err := strconv.Atoi(incs)
-	if err != nil {
-		return 0, 0, 0, err
-	}
-
-	return maj, min, inc, nil
-}
-
-func lvmVersionIsAtLeast(sTypeVersion string, versionString string) (bool, error) {
-	lvmVersion := strings.Split(sTypeVersion, "/")[0]
-
-	lvmMaj, lvmMin, lvmInc, err := versionSplit(lvmVersion)
-	if err != nil {
-		return false, err
-	}
-
-	inMaj, inMin, inInc, err := versionSplit(versionString)
-	if err != nil {
-		return false, err
-	}
-
-	if lvmMaj < inMaj || lvmMin < inMin || lvmInc < inInc {
-		return false, nil
-	}
-
-	return true, nil
-}
-
 func (s *storageLvm) StoragePoolCreate() error {
 	logger.Infof("Creating LVM storage pool \"%s\".", s.pool.Name)
 	tryUndo := true
@@ -1090,76 +762,6 @@ func (s *storageLvm) ContainerCreate(container container) error {
 	return nil
 }
 
-func (s *storageLvm) containerCreateFromImageThinLv(c container, fp string) error {
-	poolName := s.getOnDiskPoolName()
-	// Check if the image already exists.
-	imageLvmDevPath := getLvmDevPath(poolName, storagePoolVolumeAPIEndpointImages, fp)
-
-	imageStoragePoolLockID := getImageCreateLockID(poolName, fp)
-	lxdStorageMapLock.Lock()
-	if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
-		lxdStorageMapLock.Unlock()
-		if _, ok := <-waitChannel; ok {
-			logger.Warnf("Received value over semaphore. This should not have happened.")
-		}
-	} else {
-		lxdStorageOngoingOperationMap[imageStoragePoolLockID] = make(chan bool)
-		lxdStorageMapLock.Unlock()
-
-		var imgerr error
-		ok, _ := storageLVExists(imageLvmDevPath)
-		if !ok {
-			imgerr = s.ImageCreate(fp)
-		}
-
-		lxdStorageMapLock.Lock()
-		if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
-			close(waitChannel)
-			delete(lxdStorageOngoingOperationMap, imageStoragePoolLockID)
-		}
-		lxdStorageMapLock.Unlock()
-
-		if imgerr != nil {
-			return imgerr
-		}
-	}
-
-	containerName := c.Name()
-	containerLvmName := containerNameToLVName(containerName)
-	_, err := s.createSnapshotLV(poolName, fp, storagePoolVolumeAPIEndpointImages, containerLvmName, storagePoolVolumeAPIEndpointContainers, false, s.useThinpool)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (s *storageLvm) containerCreateFromImageLv(c container, fp string) error {
-	err := s.ContainerCreate(c)
-	if err != nil {
-		return err
-	}
-
-	containerName := c.Name()
-	containerPath := c.Path()
-	_, err = s.ContainerMount(c)
-	if err != nil {
-		return err
-	}
-
-	imagePath := shared.VarPath("images", fp)
-	poolName := s.getOnDiskPoolName()
-	containerMntPoint := getContainerMountPoint(poolName, containerName)
-	err = unpackImage(s.d, imagePath, containerMntPoint, storageTypeLvm)
-	if err != nil {
-		return err
-	}
-
-	s.ContainerUmount(containerName, containerPath)
-
-	return nil
-}
-
 func (s *storageLvm) ContainerCreateFromImage(container container, fingerprint string) error {
 	logger.Debugf("Creating LVM storage volume for container \"%s\" on storage pool \"%s\".", s.volume.Name, s.pool.Name)
 
@@ -1291,135 +893,6 @@ func (s *storageLvm) ContainerDelete(container container) error {
 	return nil
 }
 
-// Copy an lvm container.
-func (s *storageLvm) copyContainer(target container, source container) error {
-	targetContainerMntPoint := getContainerMountPoint(s.pool.Name, target.Name())
-	err := createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
-	if err != nil {
-		return err
-	}
-
-	if s.useThinpool {
-		// If the storage pool uses a thinpool we can have snapshots of
-		// snapshots.
-		err = s.copyContainerThinpool(target, source, false)
-	} else {
-		// If the storage pools does not use a thinpool we need to
-		// perform full copies.
-		err = s.copyContainerLv(target, source, false)
-	}
-	if err != nil {
-		return err
-	}
-
-	err = s.setUnprivUserACL(source, targetContainerMntPoint)
-	if err != nil {
-		return err
-	}
-
-	err = target.TemplateApply("copy")
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// Copy a container on a storage pool that does not use a thinpool.
-func (s *storageLvm) copyContainerLv(target container, source container, readonly bool) error {
-	err := s.ContainerCreate(target)
-	if err != nil {
-		return err
-	}
-
-	targetName := target.Name()
-	targetStart, err := target.StorageStart()
-	if err != nil {
-		return err
-	}
-	if targetStart {
-		defer target.StorageStop()
-	}
-
-	sourceName := source.Name()
-	sourceStart, err := source.StorageStart()
-	if err != nil {
-		return err
-	}
-	if sourceStart {
-		defer source.StorageStop()
-	}
-
-	poolName := s.getOnDiskPoolName()
-	sourceContainerMntPoint := getContainerMountPoint(poolName, sourceName)
-	if source.IsSnapshot() {
-		sourceContainerMntPoint = getSnapshotMountPoint(poolName, sourceName)
-	}
-	targetContainerMntPoint := getContainerMountPoint(poolName, targetName)
-	if target.IsSnapshot() {
-		targetContainerMntPoint = getSnapshotMountPoint(poolName, targetName)
-	}
-
-	if source.IsRunning() {
-		err = source.Freeze()
-		if err != nil {
-			return err
-		}
-		defer source.Unfreeze()
-	}
-
-	bwlimit := s.pool.Config["rsync.bwlimit"]
-	output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
-	if err != nil {
-		return fmt.Errorf("failed to rsync container: %s: %s", string(output), err)
-	}
-
-	if readonly {
-		targetLvmName := containerNameToLVName(targetName)
-		output, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointContainers, targetLvmName))
-		if err != nil {
-			logger.Errorf("Failed to make LVM snapshot \"%s\" read-write: %s.", targetName, output)
-			return err
-		}
-	}
-
-	return nil
-}
-
-// Copy a container on a storage pool that does use a thinpool.
-func (s *storageLvm) copyContainerThinpool(target container, source container, readonly bool) error {
-	err := s.createSnapshotContainer(target, source, readonly)
-	if err != nil {
-		logger.Errorf("Error creating snapshot LV for copy: %s.", err)
-		return err
-	}
-
-	return nil
-}
-
-func (s *storageLvm) copySnapshot(target container, source container) error {
-	targetParentName, _, _ := containerGetParentAndSnapshotName(target.Name())
-	containersPath := getSnapshotMountPoint(s.pool.Name, targetParentName)
-	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", targetParentName)
-	snapshotMntPointSymlink := shared.VarPath("snapshots", targetParentName)
-	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
-	if err != nil {
-		return err
-	}
-
-	if s.useThinpool {
-		err = s.copyContainerThinpool(target, source, true)
-	} else {
-		err = s.copyContainerLv(target, source, true)
-	}
-	if err != nil {
-		logger.Errorf("Error creating snapshot LV for copy: %s.", err)
-		return err
-	}
-
-	return nil
-}
-
 func (s *storageLvm) ContainerCopy(target container, source container, containerOnly bool) error {
 	logger.Debugf("Copying LVM container storage for container %s -> %s.", source.Name(), target.Name())
 
@@ -1754,48 +1227,6 @@ func (s *storageLvm) ContainerSnapshotCreate(snapshotContainer container, source
 	return nil
 }
 
-func (s *storageLvm) createSnapshotContainer(snapshotContainer container, sourceContainer container, readonly bool) error {
-	tryUndo := true
-
-	sourceContainerName := sourceContainer.Name()
-	targetContainerName := snapshotContainer.Name()
-	sourceContainerLvmName := containerNameToLVName(sourceContainerName)
-	targetContainerLvmName := containerNameToLVName(targetContainerName)
-	logger.Debugf("Creating snapshot: %s -> %s.", sourceContainerName, targetContainerName)
-
-	poolName := s.getOnDiskPoolName()
-	_, err := s.createSnapshotLV(poolName, sourceContainerLvmName, storagePoolVolumeAPIEndpointContainers, targetContainerLvmName, storagePoolVolumeAPIEndpointContainers, readonly, s.useThinpool)
-	if err != nil {
-		return fmt.Errorf("Error creating snapshot LV: %s", err)
-	}
-	defer func() {
-		if tryUndo {
-			s.ContainerDelete(snapshotContainer)
-		}
-	}()
-
-	targetContainerMntPoint := ""
-	targetContainerPath := snapshotContainer.Path()
-	targetIsSnapshot := snapshotContainer.IsSnapshot()
-	if targetIsSnapshot {
-		targetContainerMntPoint = getSnapshotMountPoint(s.pool.Name, targetContainerName)
-		sourceName, _, _ := containerGetParentAndSnapshotName(sourceContainerName)
-		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "snapshots", sourceName)
-		snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
-		err = createSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
-	} else {
-		targetContainerMntPoint = getContainerMountPoint(s.pool.Name, targetContainerName)
-		err = createContainerMountpoint(targetContainerMntPoint, targetContainerPath, snapshotContainer.IsPrivileged())
-	}
-	if err != nil {
-		return err
-	}
-
-	tryUndo = false
-
-	return nil
-}
-
 func (s *storageLvm) ContainerSnapshotDelete(snapshotContainer container) error {
 	logger.Debugf("Deleting LVM storage volume for snapshot \"%s\" on storage pool \"%s\".", s.volume.Name, s.pool.Name)
 
@@ -2086,181 +1517,6 @@ func (s *storageLvm) ImageUmount(fingerprint string) (bool, error) {
 	return true, nil
 }
 
-func createDefaultThinPool(sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
-	isRecent, err := lvmVersionIsAtLeast(sTypeVersion, "2.02.99")
-	if err != nil {
-		return fmt.Errorf("Error checking LVM version: %s", err)
-	}
-
-	// Create the thin pool
-	lvmThinPool := fmt.Sprintf("%s/%s", vgName, thinPoolName)
-	var output string
-	if isRecent {
-		output, err = shared.TryRunCommand(
-			"lvcreate",
-			"--poolmetadatasize", "1G",
-			"-l", "100%FREE",
-			"--thinpool", lvmThinPool)
-	} else {
-		output, err = shared.TryRunCommand(
-			"lvcreate",
-			"--poolmetadatasize", "1G",
-			"-L", "1G",
-			"--thinpool", lvmThinPool)
-	}
-
-	if err != nil {
-		logger.Errorf("Could not create thin pool \"%s\": %s.", thinPoolName, string(output))
-		return fmt.Errorf("Could not create LVM thin pool named %s", thinPoolName)
-	}
-
-	if !isRecent {
-		// Grow it to the maximum VG size (two step process required by old LVM)
-		output, err = shared.TryRunCommand("lvextend", "--alloc", "anywhere", "-l", "100%FREE", lvmThinPool)
-
-		if err != nil {
-			logger.Errorf("Could not grow thin pool: \"%s\": %s.", thinPoolName, string(output))
-			return fmt.Errorf("Could not grow LVM thin pool named %s", thinPoolName)
-		}
-	}
-
-	return nil
-}
-
-func lvmCreateThinpool(d *Daemon, sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
-	exists, err := storageLVMThinpoolExists(vgName, thinPoolName)
-	if err != nil {
-		return err
-	}
-
-	if exists {
-		return nil
-	}
-
-	err = createDefaultThinPool(sTypeVersion, vgName, thinPoolName, lvFsType)
-	if err != nil {
-		return err
-	}
-
-	err = storageLVMValidateThinPoolName(d, vgName, thinPoolName)
-	if err != nil {
-		logger.Errorf("Setting thin pool name: %s.", err)
-		return fmt.Errorf("Error setting LVM thin pool config: %v", err)
-	}
-
-	return nil
-}
-
-func lvmCreateLv(vgName string, thinPoolName string, lvName string, lvFsType string, lvSize string, volumeType string, makeThinLv bool) error {
-	var output string
-	var err error
-
-	targetVg := vgName
-	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
-	if makeThinLv {
-		targetVg = fmt.Sprintf("%s/%s", vgName, thinPoolName)
-		output, err = shared.TryRunCommand("lvcreate", "--thin", "-n", lvmPoolVolumeName, "--virtualsize", lvSize+"B", targetVg)
-	} else {
-		output, err = shared.TryRunCommand("lvcreate", "-n", lvmPoolVolumeName, "--size", lvSize+"B", vgName)
-	}
-	if err != nil {
-		logger.Errorf("Could not create LV \"%s\": %s.", lvmPoolVolumeName, output)
-		return fmt.Errorf("Could not create thin LV named %s", lvmPoolVolumeName)
-	}
-
-	fsPath := getLvmDevPath(vgName, volumeType, lvName)
-	switch lvFsType {
-	case "xfs":
-		output, err = shared.TryRunCommand("mkfs.xfs", fsPath)
-	default:
-		// default = ext4
-		output, err = shared.TryRunCommand(
-			"mkfs.ext4",
-			"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0",
-			fsPath)
-	}
-
-	if err != nil {
-		logger.Errorf("Filesystem creation failed: %s.", output)
-		return fmt.Errorf("Error making filesystem on image LV: %v", err)
-	}
-
-	return nil
-}
-
-func (s *storageLvm) removeLV(vgName string, volumeType string, lvName string) error {
-	lvmVolumePath := getLvmDevPath(vgName, volumeType, lvName)
-	output, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
-
-	if err != nil {
-		logger.Errorf("Could not remove LV \"%s\": %s.", lvName, output)
-		return fmt.Errorf("Could not remove LV named %s", lvName)
-	}
-
-	return nil
-}
-
-func (s *storageLvm) createSnapshotLV(vgName string, origLvName string, origVolumeType string, lvName string, volumeType string, readonly bool, makeThinLv bool) (string, error) {
-	sourceLvmVolumePath := getLvmDevPath(vgName, origVolumeType, origLvName)
-	logger.Debugf("in createSnapshotLV: %s.", sourceLvmVolumePath)
-	isRecent, err := lvmVersionIsAtLeast(s.sTypeVersion, "2.02.99")
-	if err != nil {
-		return "", fmt.Errorf("Error checking LVM version: %v", err)
-	}
-
-	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
-	var output string
-	args := []string{"-n", lvmPoolVolumeName, "-s", sourceLvmVolumePath}
-	if isRecent {
-		args = append(args, "-kn")
-	}
-
-	// If the source is not a thin volume the size needs to be specified.
-	// According to LVM tools 15-20% of the original volume should be
-	// sufficient. However, let's not be stingy at first otherwise we might
-	// force users to fiddle around with lvextend.
-	if !makeThinLv {
-		lvSize, err := s.getLvmVolumeSize()
-		if lvSize == "" {
-			return "", err
-		}
-		args = append(args, "--size", lvSize+"B")
-	}
-
-	if readonly {
-		args = append(args, "-pr")
-	} else {
-		args = append(args, "-prw")
-	}
-
-	output, err = shared.TryRunCommand("lvcreate", args...)
-	if err != nil {
-		logger.Errorf("Could not create LV snapshot: %s -> %s: %s.", origLvName, lvName, output)
-		return "", fmt.Errorf("Could not create snapshot LV named %s", lvName)
-	}
-
-	targetLvmVolumePath := getLvmDevPath(vgName, volumeType, lvName)
-	if makeThinLv {
-		// Snapshots of thin logical volumes can be directly activated.
-		// Normal snapshots will complain about changing the origin
-		// (Which they never do.), so skip the activation since the
-		// logical volume will be automatically activated anyway.
-		err := storageLVActivate(targetLvmVolumePath)
-		if err != nil {
-			return "", err
-		}
-	}
-
-	return targetLvmVolumePath, nil
-}
-
-func (s *storageLvm) renameLVByPath(oldName string, newName string, volumeType string) error {
-	oldLvmName := getPrefixedLvName(volumeType, oldName)
-	newLvmName := getPrefixedLvName(volumeType, newName)
-	poolName := s.getOnDiskPoolName()
-	return lvmLVRename(poolName, oldLvmName, newLvmName)
-}
-
 func (s *storageLvm) MigrationType() MigrationFSType {
 	return MigrationFSType_RSYNC
 }
@@ -2276,13 +1532,3 @@ func (s *storageLvm) MigrationSource(container container, containerOnly bool) (M
 func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation, containerOnly bool) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op, containerOnly)
 }
-
-func lvmLvIsWritable(lvName string) (bool, error) {
-	output, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "lv_attr", lvName)
-	if err != nil {
-		return false, fmt.Errorf("Error retrieving attributes for logical volume \"%s\"", lvName)
-	}
-
-	output = strings.TrimSpace(output)
-	return rune(output[1]) == 'w', nil
-}
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
new file mode 100644
index 0000000..c2359d5
--- /dev/null
+++ b/lxd/storage_lvm_utils.go
@@ -0,0 +1,763 @@
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"strconv"
+	"strings"
+	"syscall"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+func (s *storageLvm) getLvmBlockMountOptions() string {
+	if s.volume.Config["block.mount_options"] != "" {
+		return s.volume.Config["block.mount_options"]
+	}
+
+	if s.pool.Config["volume.block.mount_options"] != "" {
+		return s.pool.Config["volume.block.mount_options"]
+	}
+
+	return "discard"
+}
+
+func (s *storageLvm) getLvmFilesystem() string {
+	if s.volume.Config["block.filesystem"] != "" {
+		return s.volume.Config["block.filesystem"]
+	}
+
+	if s.pool.Config["volume.block.filesystem"] != "" {
+		return s.pool.Config["volume.block.filesystem"]
+	}
+
+	return "ext4"
+}
+
+func (s *storageLvm) getLvmVolumeSize() (string, error) {
+	sz, err := shared.ParseByteSizeString(s.volume.Config["size"])
+	if err != nil {
+		return "", err
+	}
+
+	// Safety net: Set to default value.
+	if sz == 0 {
+		sz, _ = shared.ParseByteSizeString("10GB")
+	}
+
+	return fmt.Sprintf("%d", sz), nil
+}
+
+func (s *storageLvm) getLvmThinpoolName() string {
+	if s.pool.Config["lvm.thinpool_name"] != "" {
+		return s.pool.Config["lvm.thinpool_name"]
+	}
+
+	return "LXDThinpool"
+}
+
+func (s *storageLvm) usesThinpool() bool {
+	// Default is to use a thinpool.
+	if s.pool.Config["lvm.use_thinpool"] == "" {
+		return true
+	}
+
+	return shared.IsTrue(s.pool.Config["lvm.use_thinpool"])
+}
+
+func (s *storageLvm) setLvmThinpoolName(newThinpoolName string) {
+	s.pool.Config["lvm.thinpool_name"] = newThinpoolName
+}
+
+func (s *storageLvm) getOnDiskPoolName() string {
+	if s.vgName != "" {
+		return s.vgName
+	}
+
+	return s.pool.Name
+}
+
+func (s *storageLvm) setOnDiskPoolName(newName string) {
+	s.vgName = newName
+	s.pool.Config["source"] = newName
+}
+
+func (s *storageLvm) renameLVByPath(oldName string, newName string, volumeType string) error {
+	oldLvmName := getPrefixedLvName(volumeType, oldName)
+	newLvmName := getPrefixedLvName(volumeType, newName)
+	poolName := s.getOnDiskPoolName()
+	return lvmLVRename(poolName, oldLvmName, newLvmName)
+}
+
+func (s *storageLvm) removeLV(vgName string, volumeType string, lvName string) error {
+	lvmVolumePath := getLvmDevPath(vgName, volumeType, lvName)
+	output, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
+
+	if err != nil {
+		logger.Errorf("Could not remove LV \"%s\": %s.", lvName, output)
+		return fmt.Errorf("Could not remove LV named %s", lvName)
+	}
+
+	return nil
+}
+
+func (s *storageLvm) createSnapshotLV(vgName string, origLvName string, origVolumeType string, lvName string, volumeType string, readonly bool, makeThinLv bool) (string, error) {
+	sourceLvmVolumePath := getLvmDevPath(vgName, origVolumeType, origLvName)
+	logger.Debugf("in createSnapshotLV: %s.", sourceLvmVolumePath)
+	isRecent, err := lvmVersionIsAtLeast(s.sTypeVersion, "2.02.99")
+	if err != nil {
+		return "", fmt.Errorf("Error checking LVM version: %v", err)
+	}
+
+	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
+	var output string
+	args := []string{"-n", lvmPoolVolumeName, "-s", sourceLvmVolumePath}
+	if isRecent {
+		args = append(args, "-kn")
+	}
+
+	// If the source is not a thin volume the size needs to be specified.
+	// According to LVM tools 15-20% of the original volume should be
+	// sufficient. However, let's not be stingy at first otherwise we might
+	// force users to fiddle around with lvextend.
+	if !makeThinLv {
+		lvSize, err := s.getLvmVolumeSize()
+		if lvSize == "" {
+			return "", err
+		}
+		args = append(args, "--size", lvSize+"B")
+	}
+
+	if readonly {
+		args = append(args, "-pr")
+	} else {
+		args = append(args, "-prw")
+	}
+
+	output, err = shared.TryRunCommand("lvcreate", args...)
+	if err != nil {
+		logger.Errorf("Could not create LV snapshot: %s -> %s: %s.", origLvName, lvName, output)
+		return "", fmt.Errorf("Could not create snapshot LV named %s", lvName)
+	}
+
+	targetLvmVolumePath := getLvmDevPath(vgName, volumeType, lvName)
+	if makeThinLv {
+		// Snapshots of thin logical volumes can be directly activated.
+		// Normal snapshots will complain about changing the origin
+		// (Which they never do.), so skip the activation since the
+		// logical volume will be automatically activated anyway.
+		err := storageLVActivate(targetLvmVolumePath)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return targetLvmVolumePath, nil
+}
+
+func (s *storageLvm) createSnapshotContainer(snapshotContainer container, sourceContainer container, readonly bool) error {
+	tryUndo := true
+
+	sourceContainerName := sourceContainer.Name()
+	targetContainerName := snapshotContainer.Name()
+	sourceContainerLvmName := containerNameToLVName(sourceContainerName)
+	targetContainerLvmName := containerNameToLVName(targetContainerName)
+	logger.Debugf("Creating snapshot: %s -> %s.", sourceContainerName, targetContainerName)
+
+	poolName := s.getOnDiskPoolName()
+	_, err := s.createSnapshotLV(poolName, sourceContainerLvmName, storagePoolVolumeAPIEndpointContainers, targetContainerLvmName, storagePoolVolumeAPIEndpointContainers, readonly, s.useThinpool)
+	if err != nil {
+		return fmt.Errorf("Error creating snapshot LV: %s", err)
+	}
+	defer func() {
+		if tryUndo {
+			s.ContainerDelete(snapshotContainer)
+		}
+	}()
+
+	targetContainerMntPoint := ""
+	targetContainerPath := snapshotContainer.Path()
+	targetIsSnapshot := snapshotContainer.IsSnapshot()
+	if targetIsSnapshot {
+		targetContainerMntPoint = getSnapshotMountPoint(s.pool.Name, targetContainerName)
+		sourceName, _, _ := containerGetParentAndSnapshotName(sourceContainerName)
+		snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "snapshots", sourceName)
+		snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
+		err = createSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	} else {
+		targetContainerMntPoint = getContainerMountPoint(s.pool.Name, targetContainerName)
+		err = createContainerMountpoint(targetContainerMntPoint, targetContainerPath, snapshotContainer.IsPrivileged())
+	}
+	if err != nil {
+		return err
+	}
+
+	tryUndo = false
+
+	return nil
+}
+
+// Copy a container on a storage pool that does use a thinpool.
+func (s *storageLvm) copyContainerThinpool(target container, source container, readonly bool) error {
+	err := s.createSnapshotContainer(target, source, readonly)
+	if err != nil {
+		logger.Errorf("Error creating snapshot LV for copy: %s.", err)
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) copySnapshot(target container, source container) error {
+	targetParentName, _, _ := containerGetParentAndSnapshotName(target.Name())
+	containersPath := getSnapshotMountPoint(s.pool.Name, targetParentName)
+	snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "snapshots", targetParentName)
+	snapshotMntPointSymlink := shared.VarPath("snapshots", targetParentName)
+	err := createSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+	if err != nil {
+		return err
+	}
+
+	if s.useThinpool {
+		err = s.copyContainerThinpool(target, source, true)
+	} else {
+		err = s.copyContainerLv(target, source, true)
+	}
+	if err != nil {
+		logger.Errorf("Error creating snapshot LV for copy: %s.", err)
+		return err
+	}
+
+	return nil
+}
+
+// Copy a container on a storage pool that does not use a thinpool.
+func (s *storageLvm) copyContainerLv(target container, source container, readonly bool) error {
+	err := s.ContainerCreate(target)
+	if err != nil {
+		return err
+	}
+
+	targetName := target.Name()
+	targetStart, err := target.StorageStart()
+	if err != nil {
+		return err
+	}
+	if targetStart {
+		defer target.StorageStop()
+	}
+
+	sourceName := source.Name()
+	sourceStart, err := source.StorageStart()
+	if err != nil {
+		return err
+	}
+	if sourceStart {
+		defer source.StorageStop()
+	}
+
+	poolName := s.getOnDiskPoolName()
+	sourceContainerMntPoint := getContainerMountPoint(poolName, sourceName)
+	if source.IsSnapshot() {
+		sourceContainerMntPoint = getSnapshotMountPoint(poolName, sourceName)
+	}
+	targetContainerMntPoint := getContainerMountPoint(poolName, targetName)
+	if target.IsSnapshot() {
+		targetContainerMntPoint = getSnapshotMountPoint(poolName, targetName)
+	}
+
+	if source.IsRunning() {
+		err = source.Freeze()
+		if err != nil {
+			return err
+		}
+		defer source.Unfreeze()
+	}
+
+	bwlimit := s.pool.Config["rsync.bwlimit"]
+	output, err := rsyncLocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
+	if err != nil {
+		return fmt.Errorf("failed to rsync container: %s: %s", string(output), err)
+	}
+
+	if readonly {
+		targetLvmName := containerNameToLVName(targetName)
+		output, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointContainers, targetLvmName))
+		if err != nil {
+			logger.Errorf("Failed to make LVM snapshot \"%s\" read-write: %s.", targetName, output)
+			return err
+		}
+	}
+
+	return nil
+}
+
+// Copy an lvm container.
+func (s *storageLvm) copyContainer(target container, source container) error {
+	targetContainerMntPoint := getContainerMountPoint(s.pool.Name, target.Name())
+	err := createContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
+	if err != nil {
+		return err
+	}
+
+	if s.useThinpool {
+		// If the storage pool uses a thinpool we can have snapshots of
+		// snapshots.
+		err = s.copyContainerThinpool(target, source, false)
+	} else {
+		// If the storage pools does not use a thinpool we need to
+		// perform full copies.
+		err = s.copyContainerLv(target, source, false)
+	}
+	if err != nil {
+		return err
+	}
+
+	err = s.setUnprivUserACL(source, targetContainerMntPoint)
+	if err != nil {
+		return err
+	}
+
+	err = target.TemplateApply("copy")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *storageLvm) containerCreateFromImageLv(c container, fp string) error {
+	err := s.ContainerCreate(c)
+	if err != nil {
+		return err
+	}
+
+	containerName := c.Name()
+	containerPath := c.Path()
+	_, err = s.ContainerMount(c)
+	if err != nil {
+		return err
+	}
+
+	imagePath := shared.VarPath("images", fp)
+	poolName := s.getOnDiskPoolName()
+	containerMntPoint := getContainerMountPoint(poolName, containerName)
+	err = unpackImage(s.d, imagePath, containerMntPoint, storageTypeLvm)
+	if err != nil {
+		return err
+	}
+
+	s.ContainerUmount(containerName, containerPath)
+
+	return nil
+}
+
+func (s *storageLvm) containerCreateFromImageThinLv(c container, fp string) error {
+	poolName := s.getOnDiskPoolName()
+	// Check if the image already exists.
+	imageLvmDevPath := getLvmDevPath(poolName, storagePoolVolumeAPIEndpointImages, fp)
+
+	imageStoragePoolLockID := getImageCreateLockID(poolName, fp)
+	lxdStorageMapLock.Lock()
+	if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
+		lxdStorageMapLock.Unlock()
+		if _, ok := <-waitChannel; ok {
+			logger.Warnf("Received value over semaphore. This should not have happened.")
+		}
+	} else {
+		lxdStorageOngoingOperationMap[imageStoragePoolLockID] = make(chan bool)
+		lxdStorageMapLock.Unlock()
+
+		var imgerr error
+		ok, _ := storageLVExists(imageLvmDevPath)
+		if !ok {
+			imgerr = s.ImageCreate(fp)
+		}
+
+		lxdStorageMapLock.Lock()
+		if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
+			close(waitChannel)
+			delete(lxdStorageOngoingOperationMap, imageStoragePoolLockID)
+		}
+		lxdStorageMapLock.Unlock()
+
+		if imgerr != nil {
+			return imgerr
+		}
+	}
+
+	containerName := c.Name()
+	containerLvmName := containerNameToLVName(containerName)
+	_, err := s.createSnapshotLV(poolName, fp, storagePoolVolumeAPIEndpointImages, containerLvmName, storagePoolVolumeAPIEndpointContainers, false, s.useThinpool)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func lvmLvIsWritable(lvName string) (bool, error) {
+	output, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "lv_attr", lvName)
+	if err != nil {
+		return false, fmt.Errorf("Error retrieving attributes for logical volume \"%s\"", lvName)
+	}
+
+	output = strings.TrimSpace(output)
+	return rune(output[1]) == 'w', nil
+}
+
+func storageVGActivate(lvmVolumePath string) error {
+	output, err := shared.TryRunCommand("vgchange", "-ay", lvmVolumePath)
+	if err != nil {
+		return fmt.Errorf("could not activate volume group \"%s\": %s", lvmVolumePath, output)
+	}
+
+	return nil
+}
+
+func storageLVActivate(lvmVolumePath string) error {
+	output, err := shared.TryRunCommand("lvchange", "-ay", lvmVolumePath)
+	if err != nil {
+		return fmt.Errorf("could not activate logival volume \"%s\": %s", lvmVolumePath, output)
+	}
+
+	return nil
+}
+
+func storagePVExists(pvName string) (bool, error) {
+	err := exec.Command("pvs", "--noheadings", "-o", "lv_attr", pvName).Run()
+	if err != nil {
+		if exitError, ok := err.(*exec.ExitError); ok {
+			waitStatus := exitError.Sys().(syscall.WaitStatus)
+			if waitStatus.ExitStatus() == 5 {
+				// physical volume not found
+				return false, nil
+			}
+		}
+		return false, fmt.Errorf("error checking for physical volume \"%s\"", pvName)
+	}
+
+	return true, nil
+}
+
+func storageVGExists(vgName string) (bool, error) {
+	err := exec.Command("vgs", "--noheadings", "-o", "lv_attr", vgName).Run()
+	if err != nil {
+		if exitError, ok := err.(*exec.ExitError); ok {
+			waitStatus := exitError.Sys().(syscall.WaitStatus)
+			if waitStatus.ExitStatus() == 5 {
+				// volume group not found
+				return false, nil
+			}
+		}
+		return false, fmt.Errorf("error checking for volume group \"%s\"", vgName)
+	}
+
+	return true, nil
+}
+
+func storageLVExists(lvName string) (bool, error) {
+	err := exec.Command("lvs", "--noheadings", "-o", "lv_attr", lvName).Run()
+	if err != nil {
+		if exitError, ok := err.(*exec.ExitError); ok {
+			waitStatus := exitError.Sys().(syscall.WaitStatus)
+			if waitStatus.ExitStatus() == 5 {
+				// logical volume not found
+				return false, nil
+			}
+		}
+		return false, fmt.Errorf("error checking for logical volume \"%s\"", lvName)
+	}
+
+	return true, nil
+}
+
+func lvmGetLVSize(lvPath string) (string, error) {
+	msg, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "size", "--nosuffix", "--units", "b", lvPath)
+	if err != nil {
+		return "", fmt.Errorf("failed to retrieve size of logical volume: %s: %s", string(msg), err)
+	}
+
+	sizeString := string(msg)
+	sizeString = strings.TrimSpace(sizeString)
+	size, err := strconv.ParseInt(sizeString, 10, 64)
+	if err != nil {
+		return "", err
+	}
+
+	detectedSize := shared.GetByteSizeString(size, 0)
+
+	return detectedSize, nil
+}
+
+func storageLVMThinpoolExists(vgName string, poolName string) (bool, error) {
+	output, err := exec.Command("vgs", "--noheadings", "-o", "lv_attr", fmt.Sprintf("%s/%s", vgName, poolName)).Output()
+	if err != nil {
+		if exitError, ok := err.(*exec.ExitError); ok {
+			waitStatus := exitError.Sys().(syscall.WaitStatus)
+			if waitStatus.ExitStatus() == 5 {
+				// pool LV was not found
+				return false, nil
+			}
+		}
+		return false, fmt.Errorf("error checking for pool \"%s\"", poolName)
+	}
+	// Found LV named poolname, check type:
+	attrs := strings.TrimSpace(string(output[:]))
+	if strings.HasPrefix(attrs, "t") {
+		return true, nil
+	}
+
+	return false, fmt.Errorf("pool named \"%s\" exists but is not a thin pool", poolName)
+}
+
+func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
+	results := []string{}
+
+	cNames, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return results, err
+	}
+
+	for _, cName := range cNames {
+		var lvLinkPath string
+		if strings.Contains(cName, shared.SnapshotDelimiter) {
+			lvLinkPath = shared.VarPath("snapshots", fmt.Sprintf("%s.lv", cName))
+		} else {
+			lvLinkPath = shared.VarPath("containers", fmt.Sprintf("%s.lv", cName))
+		}
+
+		if shared.PathExists(lvLinkPath) {
+			results = append(results, cName)
+		}
+	}
+
+	imageNames, err := dbImagesGet(d.db, false)
+	if err != nil {
+		return results, err
+	}
+
+	for _, imageName := range imageNames {
+		imageLinkPath := shared.VarPath("images", fmt.Sprintf("%s.lv", imageName))
+		if shared.PathExists(imageLinkPath) {
+			results = append(results, imageName)
+		}
+	}
+
+	return results, nil
+}
+
+func storageLVMValidateThinPoolName(d *Daemon, vgName string, value string) error {
+	users, err := storageLVMGetThinPoolUsers(d)
+	if err != nil {
+		return fmt.Errorf("error checking if a pool is already in use: %v", err)
+	}
+
+	if len(users) > 0 {
+		return fmt.Errorf("can not change LVM config. Images or containers are still using LVs: %v", users)
+	}
+
+	if value != "" {
+		if vgName == "" {
+			return fmt.Errorf("can not set lvm.thinpool_name without lvm.vg_name set")
+		}
+
+		poolExists, err := storageLVMThinpoolExists(vgName, value)
+		if err != nil {
+			return fmt.Errorf("error checking for thin pool \"%s\" in \"%s\": %v", value, vgName, err)
+		}
+
+		if !poolExists {
+			return fmt.Errorf("pool \"'%s\" does not exist in Volume Group \"%s\"", value, vgName)
+		}
+	}
+
+	return nil
+}
+
+func lvmVGRename(oldName string, newName string) error {
+	output, err := shared.TryRunCommand("vgrename", oldName, newName)
+	if err != nil {
+		return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %s", oldName, newName, output)
+	}
+
+	return nil
+}
+
+func lvmLVRename(vgName string, oldName string, newName string) error {
+	output, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
+	if err != nil {
+		return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %s", oldName, newName, output)
+	}
+
+	return nil
+}
+
+func xfsGenerateNewUUID(lvpath string) error {
+	output, err := shared.RunCommand(
+		"xfs_admin",
+		"-U", "generate",
+		lvpath)
+	if err != nil {
+		return fmt.Errorf("Error generating new UUID: %v\noutput:'%s'", err, output)
+	}
+
+	return nil
+}
+
+func containerNameToLVName(containerName string) string {
+	lvName := strings.Replace(containerName, "-", "--", -1)
+	return strings.Replace(lvName, shared.SnapshotDelimiter, "-", -1)
+}
+
+func getLvmDevPath(lvmPool string, volumeType string, lvmVolume string) string {
+	return fmt.Sprintf("/dev/%s/%s_%s", lvmPool, volumeType, lvmVolume)
+}
+
+func getPrefixedLvName(volumeType string, lvmVolume string) string {
+	return fmt.Sprintf("%s_%s", volumeType, lvmVolume)
+}
+
+func lvmCreateLv(vgName string, thinPoolName string, lvName string, lvFsType string, lvSize string, volumeType string, makeThinLv bool) error {
+	var output string
+	var err error
+
+	targetVg := vgName
+	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
+	if makeThinLv {
+		targetVg = fmt.Sprintf("%s/%s", vgName, thinPoolName)
+		output, err = shared.TryRunCommand("lvcreate", "--thin", "-n", lvmPoolVolumeName, "--virtualsize", lvSize+"B", targetVg)
+	} else {
+		output, err = shared.TryRunCommand("lvcreate", "-n", lvmPoolVolumeName, "--size", lvSize+"B", vgName)
+	}
+	if err != nil {
+		logger.Errorf("Could not create LV \"%s\": %s.", lvmPoolVolumeName, output)
+		return fmt.Errorf("Could not create thin LV named %s", lvmPoolVolumeName)
+	}
+
+	fsPath := getLvmDevPath(vgName, volumeType, lvName)
+	switch lvFsType {
+	case "xfs":
+		output, err = shared.TryRunCommand("mkfs.xfs", fsPath)
+	default:
+		// default = ext4
+		output, err = shared.TryRunCommand(
+			"mkfs.ext4",
+			"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0",
+			fsPath)
+	}
+
+	if err != nil {
+		logger.Errorf("Filesystem creation failed: %s.", output)
+		return fmt.Errorf("Error making filesystem on image LV: %v", err)
+	}
+
+	return nil
+}
+
+func lvmCreateThinpool(d *Daemon, sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
+	exists, err := storageLVMThinpoolExists(vgName, thinPoolName)
+	if err != nil {
+		return err
+	}
+
+	if exists {
+		return nil
+	}
+
+	err = createDefaultThinPool(sTypeVersion, vgName, thinPoolName, lvFsType)
+	if err != nil {
+		return err
+	}
+
+	err = storageLVMValidateThinPoolName(d, vgName, thinPoolName)
+	if err != nil {
+		logger.Errorf("Setting thin pool name: %s.", err)
+		return fmt.Errorf("Error setting LVM thin pool config: %v", err)
+	}
+
+	return nil
+}
+
+func createDefaultThinPool(sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
+	isRecent, err := lvmVersionIsAtLeast(sTypeVersion, "2.02.99")
+	if err != nil {
+		return fmt.Errorf("Error checking LVM version: %s", err)
+	}
+
+	// Create the thin pool
+	lvmThinPool := fmt.Sprintf("%s/%s", vgName, thinPoolName)
+	var output string
+	if isRecent {
+		output, err = shared.TryRunCommand(
+			"lvcreate",
+			"--poolmetadatasize", "1G",
+			"-l", "100%FREE",
+			"--thinpool", lvmThinPool)
+	} else {
+		output, err = shared.TryRunCommand(
+			"lvcreate",
+			"--poolmetadatasize", "1G",
+			"-L", "1G",
+			"--thinpool", lvmThinPool)
+	}
+
+	if err != nil {
+		logger.Errorf("Could not create thin pool \"%s\": %s.", thinPoolName, string(output))
+		return fmt.Errorf("Could not create LVM thin pool named %s", thinPoolName)
+	}
+
+	if !isRecent {
+		// Grow it to the maximum VG size (two step process required by old LVM)
+		output, err = shared.TryRunCommand("lvextend", "--alloc", "anywhere", "-l", "100%FREE", lvmThinPool)
+
+		if err != nil {
+			logger.Errorf("Could not grow thin pool: \"%s\": %s.", thinPoolName, string(output))
+			return fmt.Errorf("Could not grow LVM thin pool named %s", thinPoolName)
+		}
+	}
+
+	return nil
+}
+
+func versionSplit(versionString string) (int, int, int, error) {
+	fs := strings.Split(versionString, ".")
+	majs, mins, incs := fs[0], fs[1], fs[2]
+
+	maj, err := strconv.Atoi(majs)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+	min, err := strconv.Atoi(mins)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+	incs = strings.Split(incs, "(")[0]
+	inc, err := strconv.Atoi(incs)
+	if err != nil {
+		return 0, 0, 0, err
+	}
+
+	return maj, min, inc, nil
+}
+
+func lvmVersionIsAtLeast(sTypeVersion string, versionString string) (bool, error) {
+	lvmVersion := strings.Split(sTypeVersion, "/")[0]
+
+	lvmMaj, lvmMin, lvmInc, err := versionSplit(lvmVersion)
+	if err != nil {
+		return false, err
+	}
+
+	inMaj, inMin, inInc, err := versionSplit(versionString)
+	if err != nil {
+		return false, err
+	}
+
+	if lvmMaj < inMaj || lvmMin < inMin || lvmInc < inInc {
+		return false, nil
+	}
+
+	return true, nil
+}

From d726d28f6e591eccea25a4c18cca9e9ee366c37c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 4 May 2017 16:52:58 +0200
Subject: [PATCH 2/3] lvm: implement lv resizing

- ext4: grow and shrink
- xfs: grow only (requires dump, mkfs, and restore)

Closes #1205.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md    |  4 +++
 doc/containers.md        |  1 +
 lxd/api_1.0.go           |  1 +
 lxd/container_lxc.go     | 52 +++++++++++++++++++++++------
 lxd/storage_lvm.go       | 46 +++++++++++++++++++++++--
 lxd/storage_lvm_utils.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++
 shared/container.go      |  1 +
 7 files changed, 180 insertions(+), 12 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index ee37780..26e075a 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -269,3 +269,7 @@ This key control what host network interface is used for a VXLAN tunnel.
 This introduces the btrfs.mount\_options property for btrfs storage pools.
 
 This key controls what mount options will be used for the btrfs storage pool.
+
+## storage\_lvm\_lv\_resizing
+This introduces the ability to resize logical volumes by setting the "size"
+property in the containers root disk device.
diff --git a/doc/containers.md b/doc/containers.md
index af2f354..caad260 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -59,6 +59,7 @@ Key                             | Type      | Default       | Description
 volatile.\<name\>.hwaddr        | string    | -             | Network device MAC address (when no hwaddr property is set on the device itself)
 volatile.\<name\>.name          | string    | -             | Network device name (when no name propery is set on the device itself)
 volatile.\<name\>.host\_name    | string    | -             | Network device name on the host (for nictype=bridged or nictype=p2p)
+volatile.apply_quota            | string    | -             | Disk quota to be applied on next container start
 volatile.apply\_template        | string    | -             | The name of a template hook which should be triggered upon next startup
 volatile.base\_image            | string    | -             | The hash of the image the container was created from, if any.
 volatile.idmap.base             | integer   | -             | The first id in the container's primary idmap range
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index beb4f79..9e844c8 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -105,6 +105,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"storage_rsync_bwlimit",
 			"network_vxlan_interface",
 			"storage_btrfs_mount_options",
+			"storage_lvm_lv_resizing",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 406368c..f26d1ec 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1549,6 +1549,34 @@ func (c *containerLXC) startCommon() (string, error) {
 		}
 	}
 
+	var ourStart bool
+	newSize, ok := c.LocalConfig()["volatile.apply_quota"]
+	if ok {
+		err := c.initStorage()
+		if err != nil {
+			return "", err
+		}
+
+		size, err := shared.ParseByteSizeString(newSize)
+		if err != nil {
+			return "", err
+		}
+		err = c.storage.ContainerSetQuota(c, size)
+		if err != nil {
+			return "", err
+		}
+
+		// Remove the volatile key from the DB
+		err = dbContainerConfigRemove(c.daemon.db, c.id, "volatile.apply_quota")
+		if err != nil {
+			return "", err
+		}
+
+		// Remove the volatile key from the in-memory configs
+		delete(c.localConfig, "volatile.apply_quota")
+		delete(c.expandedConfig, "volatile.apply_quota")
+	}
+
 	/* Deal with idmap changes */
 	idmap, err := c.IdmapSet()
 	if err != nil {
@@ -1574,7 +1602,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	if !reflect.DeepEqual(idmap, lastIdmap) {
 		logger.Debugf("Container idmap changed, remapping")
 
-		ourStart, err := c.StorageStart()
+		ourStart, err = c.StorageStart()
 		if err != nil {
 			return "", err
 		}
@@ -1872,7 +1900,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	// Storage is guaranteed to be mountable now.
-	ourStart, err := c.StorageStart()
+	ourStart, err = c.StorageStart()
 	if err != nil {
 		return "", err
 	}
@@ -3354,23 +3382,27 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	oldRootDiskDeviceSize := oldExpandedDevices[oldRootDiskDeviceKey]["size"]
 	newRootDiskDeviceSize := c.expandedDevices[newRootDiskDeviceKey]["size"]
 
+	isRunning := c.IsRunning()
 	// Apply disk quota changes
 	if newRootDiskDeviceSize != oldRootDiskDeviceSize {
-		size, err := shared.ParseByteSizeString(newRootDiskDeviceSize)
-		if err != nil {
-			return err
-		}
-
-		if c.storage.ContainerStorageReady(c.Name()) {
-			err = c.storage.ContainerSetQuota(c, size)
+		storageTypeName := c.storage.GetStorageTypeName()
+		storageIsReady := c.storage.ContainerStorageReady(c.Name())
+		if storageTypeName == "lvm" && isRunning || !storageIsReady {
+			err = c.ConfigKeySet("volatile.apply_quota", newRootDiskDeviceSize)
+		} else {
+			size, err := shared.ParseByteSizeString(newRootDiskDeviceSize)
 			if err != nil {
 				return err
 			}
+			err = c.storage.ContainerSetQuota(c, size)
+		}
+		if err != nil {
+			return err
 		}
 	}
 
 	// Apply the live changes
-	if c.IsRunning() {
+	if isRunning {
 		// Live update the container config
 		for _, key := range changedConfig {
 			value := c.expandedConfig[key]
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 9525d7f..dfba9d1 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -1207,8 +1207,50 @@ func (s *storageLvm) ContainerRestore(target container, source container) error
 	return nil
 }
 
-func (s *storageLvm) ContainerSetQuota(container container, size int64) error {
-	return fmt.Errorf("the LVM container backend doesn't support quotas")
+func (s *storageLvm) ContainerSetQuota(c container, size int64) error {
+	ctName := c.Name()
+	logger.Debugf("resizing LVM storage volume for container \"%s\"", ctName)
+
+	if c.IsRunning() {
+		logger.Errorf("cannot resize LVM storage volume for container \"%s\" when it is running", ctName)
+		return fmt.Errorf("cannot resize LVM storage volume for container \"%s\" when it is running", ctName)
+	}
+
+	oldSize, err := shared.ParseByteSizeString(s.volume.Config["size"])
+	if err != nil {
+		return err
+	}
+
+	// The right disjunct just means that someone unset the size property in
+	// the container's config. We obviously cannot resize to 0.
+	if oldSize == size || size == 0 {
+		return nil
+	}
+
+	poolName := s.getOnDiskPoolName()
+	ctLvmName := containerNameToLVName(ctName)
+	lvDevPath := getLvmDevPath(poolName, storagePoolVolumeAPIEndpointContainers, ctLvmName)
+	fsType := s.getLvmFilesystem()
+	ctMountpoint := getContainerMountPoint(s.pool.Name, ctName)
+	if size < oldSize {
+		err = s.lvReduce(c, lvDevPath, size, fsType, ctMountpoint)
+	} else if size > oldSize {
+		err = s.lvExtend(c, lvDevPath, size, fsType, ctMountpoint)
+	}
+	if err != nil {
+		logger.Errorf("failed to resize LVM storage volume for container \"%s\"", ctName)
+		return err
+	}
+
+	// Update the database
+	s.volume.Config["size"] = shared.GetByteSizeString(size, 0)
+	err = dbStoragePoolVolumeUpdate(s.d.db, ctName, storagePoolVolumeTypeContainer, s.poolID, s.volume.Config)
+	if err != nil {
+		return err
+	}
+
+	logger.Debugf("resized LVM storage volume for container \"%s\"", ctName)
+	return nil
 }
 
 func (s *storageLvm) ContainerGetUsage(container container) (int64, error) {
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
index c2359d5..5adbf2a 100644
--- a/lxd/storage_lvm_utils.go
+++ b/lxd/storage_lvm_utils.go
@@ -11,6 +11,92 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
+func (s *storageLvm) lvExtend(c container, lvPath string, lvSize int64, fsType string, fsMntPoint string) error {
+	lvSizeString := shared.GetByteSizeString(lvSize, 0)
+	msg, err := shared.TryRunCommand(
+		"lvextend",
+		"-L", lvSizeString,
+		"-f",
+		lvPath)
+	if err != nil {
+		logger.Errorf("could not extend LV \"%s\": %s", lvPath, msg)
+		return fmt.Errorf("could not extend LV \"%s\": %s", lvPath, msg)
+	}
+
+	ourMount, err := c.StorageStart()
+	if err != nil {
+		return err
+	}
+	if ourMount {
+		defer c.StorageStop()
+	}
+
+	switch fsType {
+	case "xfs":
+		msg, err = shared.TryRunCommand("xfs_growfs", fsMntPoint)
+	default:
+		// default = ext4
+		msg, err = shared.TryRunCommand("resize2fs", lvPath)
+	}
+	if err != nil {
+		logger.Errorf("could not extend underlying %s filesystem for LV \"%s\": %s", fsType, lvPath, msg)
+		return fmt.Errorf("could not extend underlying %s filesystem for LV \"%s\": %s", fsType, lvPath, msg)
+	}
+
+	logger.Debugf("extended underlying %s filesystem for LV \"%s\"", fsType, lvPath)
+	return nil
+}
+
+func (s *storageLvm) lvReduce(c container, lvPath string, lvSize int64, fsType string, fsMntPoint string) error {
+	var err error
+	var msg string
+
+	lvSizeString := strconv.FormatInt(lvSize, 10)
+	switch fsType {
+	case "xfs":
+		logger.Errorf("xfs filesystems cannot be shrunk: dump, mkfs, and restore are required")
+		return fmt.Errorf("xfs filesystems cannot be shrunk: dump, mkfs, and restore are required")
+	default:
+		// default = ext4
+		ourUmount, err := c.StorageStop()
+		if err != nil {
+			return err
+		}
+		if !ourUmount {
+			defer c.StorageStart()
+		}
+
+		msg, err = shared.TryRunCommand("e2fsck", "-f", "-y", lvPath)
+		if err != nil {
+			return err
+		}
+
+		// don't assume resize2fs semantics are sane (because they
+		// aren't)
+		kbSize := lvSize / 1024
+		ext4LvSizeString := strconv.FormatInt(kbSize, 10)
+		ext4LvSizeString += "K"
+		msg, err = shared.TryRunCommand("resize2fs", lvPath, ext4LvSizeString)
+	}
+	if err != nil {
+		logger.Errorf("could not reduce underlying %s filesystem for LV \"%s\": %s", fsType, lvPath, msg)
+		return fmt.Errorf("could not reduce underlying %s filesystem for LV \"%s\": %s", fsType, lvPath, msg)
+	}
+
+	msg, err = shared.TryRunCommand(
+		"lvreduce",
+		"-L", lvSizeString+"B",
+		"-f",
+		lvPath)
+	if err != nil {
+		logger.Errorf("could not reduce LV \"%s\": %s", lvPath, msg)
+		return fmt.Errorf("could not reduce LV \"%s\": %s", lvPath, msg)
+	}
+
+	logger.Debugf("reduce underlying %s filesystem for LV \"%s\"", fsType, lvPath)
+	return nil
+}
+
 func (s *storageLvm) getLvmBlockMountOptions() string {
 	if s.volume.Config["block.mount_options"] != "" {
 		return s.volume.Config["block.mount_options"]
@@ -637,6 +723,7 @@ func lvmCreateLv(vgName string, thinPoolName string, lvName string, lvFsType str
 	}
 
 	fsPath := getLvmDevPath(vgName, volumeType, lvName)
+
 	switch lvFsType {
 	case "xfs":
 		output, err = shared.TryRunCommand("mkfs.xfs", fsPath)
diff --git a/shared/container.go b/shared/container.go
index 11e78fd..fbbd7c1 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -190,6 +190,7 @@ var KnownContainerConfigKeys = map[string]func(value string) error{
 	"volatile.last_state.power": IsAny,
 	"volatile.idmap.next":       IsAny,
 	"volatile.idmap.base":       IsAny,
+	"volatile.apply_quota":      IsAny,
 }
 
 // ConfigKeyChecker returns a function that will check whether or not

From 1e230e5851e15a6f9ea61330f7b2052f577ace24 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 4 May 2017 17:00:32 +0200
Subject: [PATCH 3/3] test: add lv resizing tests

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/storage.sh | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/test/suites/storage.sh b/test/suites/storage.sh
index 2ddbb46..19a3c26 100644
--- a/test/suites/storage.sh
+++ b/test/suites/storage.sh
@@ -308,6 +308,12 @@ test_storage() {
 
       lxc launch testimage c12pool6 -s "lxdtest-$(basename "${LXD_DIR}")-pool6"
       lxc list -c b c12pool6 | grep "lxdtest-$(basename "${LXD_DIR}")-pool6"
+      # grow lv
+      lxc config device set c12pool6 root size 30MB
+      lxc restart c12pool6
+      # shrink lv
+      lxc config device set c12pool6 root size 25MB
+      lxc restart c12pool6
 
       lxc init testimage c10pool11 -s "lxdtest-$(basename "${LXD_DIR}")-pool11"
       lxc list -c b c10pool11 | grep "lxdtest-$(basename "${LXD_DIR}")-pool11"


More information about the lxc-devel mailing list