[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