[lxc-devel] [lxd/master] Fix custom block volume migration

monstermunchkin on Github lxc-bot at linuxcontainers.org
Fri Jul 3 15:19:58 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200703/8c0db14e/attachment.bin>
-------------- next part --------------
From 4d586376538a7a6575b789a89f9971b8e9bfcf8d Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 29 Jun 2020 12:17:16 +0200
Subject: [PATCH 1/2] lxd/storage: Fix block volume migration

This fixed an error where the target volume would be smaller than the
source volume when copying custom block volumes.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/backend_lxd.go                   | 17 +++++++++++++++
 lxd/storage/drivers/driver_btrfs_volumes.go  |  4 ++++
 lxd/storage/drivers/driver_ceph_utils.go     | 22 ++++++++++++++++++++
 lxd/storage/drivers/driver_ceph_volumes.go   |  9 ++++++++
 lxd/storage/drivers/driver_cephfs_volumes.go |  4 ++++
 lxd/storage/drivers/driver_dir_volumes.go    |  4 ++++
 lxd/storage/drivers/driver_lvm_volumes.go    |  6 ++++++
 lxd/storage/drivers/driver_zfs_volumes.go    | 18 ++++++++++++++++
 lxd/storage/drivers/drivers_mock.go          |  5 +++++
 lxd/storage/drivers/generic_vfs.go           | 14 +++++++++++++
 lxd/storage/drivers/interface.go             |  1 +
 11 files changed, 104 insertions(+)

diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index f33613e33e..cc8ab38e5a 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -2387,6 +2387,22 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 		return fmt.Errorf("Failed to negotiate copy migration type: %v", err)
 	}
 
+	// If we're copying block volumes, the target block volume needs to be
+	// at least the size of the source volume, otherwise we'll run into
+	// "no space left on device".
+	var volSize int64
+
+	if contentType == drivers.ContentTypeBlock {
+		// Get the src volume name on storage.
+		srcVolStorageName := project.StorageVolume(projectName, srcVolName)
+		srcVol := b.newVolume(drivers.VolumeTypeCustom, contentType, srcVolStorageName, srcVolRow.Config)
+
+		volSize, err = srcPool.driver.GetVolumeSize(srcVol)
+		if err != nil {
+			return err
+		}
+	}
+
 	ctx, cancel := context.WithCancel(context.Background())
 
 	// Use in-memory pipe pair to simulate a connection between the sender and receiver.
@@ -2419,6 +2435,7 @@ func (b *lxdBackend) CreateCustomVolumeFromCopy(projectName string, volName stri
 			MigrationType: migrationTypes[0],
 			TrackProgress: false, // Do not use a progress tracker on receiver.
 			ContentType:   string(contentType),
+			VolumeSize:    volSize,
 		}, op)
 
 		if err != nil {
diff --git a/lxd/storage/drivers/driver_btrfs_volumes.go b/lxd/storage/drivers/driver_btrfs_volumes.go
index 222efcd031..50e7239b57 100644
--- a/lxd/storage/drivers/driver_btrfs_volumes.go
+++ b/lxd/storage/drivers/driver_btrfs_volumes.go
@@ -1346,3 +1346,7 @@ func (d *btrfs) RestoreVolume(vol Volume, snapshotName string, op *operations.Op
 func (d *btrfs) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
 	return genericVFSRenameVolumeSnapshot(d, snapVol, newSnapshotName, op)
 }
+
+func (d *btrfs) GetVolumeSize(vol Volume) (int64, error) {
+	return genericVFSGetVolumeSize(vol)
+}
diff --git a/lxd/storage/drivers/driver_ceph_utils.go b/lxd/storage/drivers/driver_ceph_utils.go
index 6c399b48a2..bf4d0f6cd2 100644
--- a/lxd/storage/drivers/driver_ceph_utils.go
+++ b/lxd/storage/drivers/driver_ceph_utils.go
@@ -1200,3 +1200,25 @@ func (d *ceph) receiveVolume(volumeName string, conn io.ReadWriteCloser, writeWr
 
 	return nil
 }
+
+func (d *ceph) rbdGetVolumeSize(vol Volume) (string, error) {
+	msg, err := shared.RunCommand(
+		"rbd",
+		"--id", d.config["ceph.user.name"],
+		"--cluster", d.config["ceph.cluster_name"],
+		"--pool", d.config["ceph.osd.pool_name"],
+		"info",
+		d.getRBDVolumeName(vol, "", false, false))
+	if err != nil {
+		return "", err
+	}
+
+	regex := regexp.MustCompile(`size ([[:digit:]]+ [[:alpha:]]+)`)
+	match := regex.FindStringSubmatch(msg)
+
+	if match == nil {
+		return "", fmt.Errorf("Couldn't determine volume size")
+	}
+
+	return match[1], nil
+}
diff --git a/lxd/storage/drivers/driver_ceph_volumes.go b/lxd/storage/drivers/driver_ceph_volumes.go
index 6d0be809c9..803715b932 100644
--- a/lxd/storage/drivers/driver_ceph_volumes.go
+++ b/lxd/storage/drivers/driver_ceph_volumes.go
@@ -1502,3 +1502,12 @@ func (d *ceph) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *
 	revert.Success()
 	return nil
 }
+
+func (d *ceph) GetVolumeSize(vol Volume) (int64, error) {
+	size, err := d.rbdGetVolumeSize(vol)
+	if err != nil {
+		return -1, err
+	}
+
+	return units.ParseByteSizeString(size)
+}
diff --git a/lxd/storage/drivers/driver_cephfs_volumes.go b/lxd/storage/drivers/driver_cephfs_volumes.go
index 40f3b6eb95..2766b7093c 100644
--- a/lxd/storage/drivers/driver_cephfs_volumes.go
+++ b/lxd/storage/drivers/driver_cephfs_volumes.go
@@ -556,3 +556,7 @@ func (d *cephfs) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op
 
 	return nil
 }
+
+func (d *cephfs) GetVolumeSize(vol Volume) (int64, error) {
+	return -1, ErrNotSupported
+}
diff --git a/lxd/storage/drivers/driver_dir_volumes.go b/lxd/storage/drivers/driver_dir_volumes.go
index 797f480cb3..2cae241b0e 100644
--- a/lxd/storage/drivers/driver_dir_volumes.go
+++ b/lxd/storage/drivers/driver_dir_volumes.go
@@ -447,3 +447,7 @@ func (d *dir) RestoreVolume(vol Volume, snapshotName string, op *operations.Oper
 func (d *dir) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
 	return genericVFSRenameVolumeSnapshot(d, snapVol, newSnapshotName, op)
 }
+
+func (d *dir) GetVolumeSize(vol Volume) (int64, error) {
+	return genericVFSGetVolumeSize(vol)
+}
diff --git a/lxd/storage/drivers/driver_lvm_volumes.go b/lxd/storage/drivers/driver_lvm_volumes.go
index 176d2af58a..8c8c379c66 100644
--- a/lxd/storage/drivers/driver_lvm_volumes.go
+++ b/lxd/storage/drivers/driver_lvm_volumes.go
@@ -1034,3 +1034,9 @@ func (d *lvm) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *o
 
 	return nil
 }
+
+// GetGetVolumeSize returns a volume's size.
+func (d *lvm) GetVolumeSize(vol Volume) (int64, error) {
+	volDevPath := d.lvmDevPath(d.config["lvm.vg_name"], vol.volType, vol.contentType, vol.name)
+	return d.logicalVolumeSize(volDevPath)
+}
diff --git a/lxd/storage/drivers/driver_zfs_volumes.go b/lxd/storage/drivers/driver_zfs_volumes.go
index 3741c9f71b..c7335cddaa 100644
--- a/lxd/storage/drivers/driver_zfs_volumes.go
+++ b/lxd/storage/drivers/driver_zfs_volumes.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -600,6 +601,13 @@ func (d *zfs) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool
 func (d *zfs) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error {
 	// Handle simple rsync and block_and_rsync through generic.
 	if volTargetArgs.MigrationType.FSType == migration.MigrationFSType_RSYNC || volTargetArgs.MigrationType.FSType == migration.MigrationFSType_BLOCK_AND_RSYNC {
+		// Round up the volume size to avoid running into "no space left on device".
+		if volTargetArgs.VolumeSize > 0 {
+			volSizeBytes := math.Ceil(float64(volTargetArgs.VolumeSize) / MinBlockBoundary * MinBlockBoundary)
+
+			vol.SetConfigSize(fmt.Sprintf("%d", int(volSizeBytes)))
+		}
+
 		return genericVFSCreateVolumeFromMigration(d, nil, vol, conn, volTargetArgs, preFiller, op)
 	} else if volTargetArgs.MigrationType.FSType != migration.MigrationFSType_ZFS {
 		return ErrNotSupported
@@ -1821,3 +1829,13 @@ func (d *zfs) RenameVolumeSnapshot(vol Volume, newSnapshotName string, op *opera
 
 	return nil
 }
+
+// GetGetVolumeSize returns a volume's size.
+func (d *zfs) GetVolumeSize(vol Volume) (int64, error) {
+	size, err := d.getDatasetProperty(d.dataset(vol, false), "available")
+	if err != nil {
+		return -1, err
+	}
+
+	return strconv.ParseInt(size, 10, 64)
+}
diff --git a/lxd/storage/drivers/drivers_mock.go b/lxd/storage/drivers/drivers_mock.go
index 229ed179a4..62531d29fe 100644
--- a/lxd/storage/drivers/drivers_mock.go
+++ b/lxd/storage/drivers/drivers_mock.go
@@ -204,3 +204,8 @@ func (d *mock) RestoreVolume(vol Volume, snapshotName string, op *operations.Ope
 func (d *mock) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
 	return nil
 }
+
+// GetGetVolumeSize returns a volume's size.
+func (d *mock) GetVolumeSize(vol Volume) (int64, error) {
+	return -1, nil
+}
diff --git a/lxd/storage/drivers/generic_vfs.go b/lxd/storage/drivers/generic_vfs.go
index 8c2c7e12c9..e75327b73e 100644
--- a/lxd/storage/drivers/generic_vfs.go
+++ b/lxd/storage/drivers/generic_vfs.go
@@ -908,3 +908,17 @@ func genericVFSCopyVolume(d Driver, initVolume func(vol Volume) (func(), error),
 	revert.Success()
 	return nil
 }
+
+func genericVFSGetVolumeSize(vol Volume) (int64, error) {
+	path, err := genericVFSGetVolumeDiskPath(vol)
+	if err != nil {
+		return -1, err
+	}
+
+	stat, err := os.Lstat(path)
+	if err != nil {
+		return -1, err
+	}
+
+	return stat.Size(), nil
+}
diff --git a/lxd/storage/drivers/interface.go b/lxd/storage/drivers/interface.go
index 312e127f39..bea6b42e6c 100644
--- a/lxd/storage/drivers/interface.go
+++ b/lxd/storage/drivers/interface.go
@@ -55,6 +55,7 @@ type Driver interface {
 	GetVolumeUsage(vol Volume) (int64, error)
 	SetVolumeQuota(vol Volume, size string, op *operations.Operation) error
 	GetVolumeDiskPath(vol Volume) (string, error)
+	GetVolumeSize(vol Volume) (int64, error)
 
 	// MountVolume mounts a storage volume, returns true if we caused a new mount, false if
 	// already mounted.

From 951261a83e72b0576629013e478ffee3c8320301 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 3 Jul 2020 12:33:41 +0200
Subject: [PATCH 2/2] lxd/storage/drivers/lvm: Check minimum LV size

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/storage/drivers/driver_lvm_utils.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/lxd/storage/drivers/driver_lvm_utils.go b/lxd/storage/drivers/driver_lvm_utils.go
index e8ad0216cf..8f839ef6de 100644
--- a/lxd/storage/drivers/driver_lvm_utils.go
+++ b/lxd/storage/drivers/driver_lvm_utils.go
@@ -30,6 +30,12 @@ const lvmEscapedHyphen = "--"
 
 var errLVMNotFound = fmt.Errorf("Not found")
 
+// lvmMinimalLVSize describes the minimum LV size which is 1 physical extent (4 MiB) by default.
+// LVM won't complain if you pass a smaller value via --size, but will make it 4 MiB.
+// When provided a volume size, we need to check this otherwise the config value and actual size
+// will differ.
+const lvmMinimumLVSize = 4194304
+
 // usesThinpool indicates whether the config specifies to use a thin pool or not.
 func (d *lvm) usesThinpool() bool {
 	// Default is to use a thinpool.
@@ -308,6 +314,10 @@ func (d *lvm) createLogicalVolume(vgName, thinPoolName string, vol Volume, makeT
 		return err
 	}
 
+	if lvSizeBytes < lvmMinimumLVSize {
+		return fmt.Errorf("LV size cannot be smaller than %d bytes", lvmMinimumLVSize)
+	}
+
 	lvFullName := d.lvmFullVolumeName(vol.volType, vol.contentType, vol.name)
 
 	args := []string{


More information about the lxc-devel mailing list