Skip to content

Commit bc2c92c

Browse files
committed
linstor: use sparse/discard qemu-img convert on thin devices
This reduces revert time and also makes the devices not thick allocated anymore.
1 parent 2e113e5 commit bc2c92c

File tree

4 files changed

+59
-40
lines changed

4 files changed

+59
-40
lines changed

plugins/storage/volume/linstor/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2025-10-03]
9+
10+
### Changed
11+
12+
- Revert qcow2 snapshot now use sparse/discard options to convert on thin devices.
13+
814
## [2025-08-05]
915

1016
### Fixed

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
import com.cloud.resource.CommandWrapper;
2727
import com.cloud.resource.ResourceWrapper;
2828
import com.cloud.storage.Storage;
29+
import com.cloud.utils.script.Script;
2930
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
31+
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
3032
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
3133
import org.apache.cloudstack.storage.to.VolumeObjectTO;
3234
import org.apache.cloudstack.utils.qemu.QemuImg;
3335
import org.apache.cloudstack.utils.qemu.QemuImgException;
3436
import org.apache.cloudstack.utils.qemu.QemuImgFile;
3537
import org.apache.log4j.Logger;
38+
import org.joda.time.Duration;
3639
import org.libvirt.LibvirtException;
3740

3841
@ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class)
@@ -41,12 +44,23 @@ public final class LinstorRevertBackupSnapshotCommandWrapper
4144
{
4245
private static final Logger s_logger = Logger.getLogger(LinstorRevertBackupSnapshotCommandWrapper.class);
4346

44-
private void convertQCow2ToRAW(final String srcPath, final String dstPath, int waitMilliSeconds)
47+
private void convertQCow2ToRAW(
48+
KVMStoragePool pool, final String srcPath, final String dstUuid, int waitMilliSeconds)
4549
throws LibvirtException, QemuImgException
4650
{
51+
final String dstPath = pool.getPhysicalDisk(dstUuid).getPath();
4752
final QemuImgFile srcQemuFile = new QemuImgFile(
4853
srcPath, QemuImg.PhysicalDiskFormat.QCOW2);
49-
final QemuImg qemu = new QemuImg(waitMilliSeconds);
54+
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(pool, LinstorUtil.RSC_PREFIX + dstUuid);
55+
if (zeroedDevice)
56+
{
57+
// blockdiscard the device to ensure the device is filled with zeroes
58+
Script blkDiscardScript = new Script("blkdiscard", Duration.millis(waitMilliSeconds));
59+
blkDiscardScript.add("-f");
60+
blkDiscardScript.add(dstPath);
61+
blkDiscardScript.execute();
62+
}
63+
final QemuImg qemu = new QemuImg(waitMilliSeconds, zeroedDevice, true);
5064
final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW);
5165
qemu.convert(srcQemuFile, dstFile);
5266
}
@@ -73,8 +87,9 @@ public CopyCmdAnswer execute(LinstorRevertBackupSnapshotCommand cmd, LibvirtComp
7387
srcDataStore.getUrl() + File.separator + srcFile.getParent());
7488

7589
convertQCow2ToRAW(
90+
linstorPool,
7691
secondaryPool.getLocalPath() + File.separator + srcFile.getName(),
77-
linstorPool.getPhysicalDisk(dst.getPath()).getPath(),
92+
dst.getPath(),
7893
cmd.getWaitInMillSeconds());
7994

8095
final VolumeObjectTO dstVolume = new VolumeObjectTO();

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import com.cloud.storage.Storage;
3131
import com.cloud.utils.exception.CloudRuntimeException;
3232
import com.cloud.utils.script.Script;
33-
3433
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
3534
import org.apache.cloudstack.utils.qemu.QemuImg;
3635
import org.apache.cloudstack.utils.qemu.QemuImgException;
@@ -56,7 +55,6 @@
5655
import com.linbit.linstor.api.model.ResourceMakeAvailable;
5756
import com.linbit.linstor.api.model.ResourceWithVolumes;
5857
import com.linbit.linstor.api.model.StoragePool;
59-
import com.linbit.linstor.api.model.Volume;
6058
import com.linbit.linstor.api.model.VolumeDefinition;
6159

6260
import java.io.File;
@@ -570,40 +568,6 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt
570568
return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
571569
}
572570

573-
/**
574-
* Checks if all diskful resource are on a zeroed block device.
575-
* @param destPool Linstor pool to use
576-
* @param resName Linstor resource name
577-
* @return true if all resources are on a provider with zeroed blocks.
578-
*/
579-
private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) {
580-
final DevelopersApi api = getLinstorAPI(destPool);
581-
582-
try {
583-
List<ResourceWithVolumes> resWithVols = api.viewResources(
584-
Collections.emptyList(),
585-
Collections.singletonList(resName),
586-
Collections.emptyList(),
587-
Collections.emptyList(),
588-
null,
589-
null);
590-
591-
if (resWithVols != null) {
592-
return resWithVols.stream()
593-
.allMatch(res -> {
594-
Volume vol0 = res.getVolumes().get(0);
595-
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
596-
vol0.getProviderKind() == ProviderKind.ZFS ||
597-
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
598-
vol0.getProviderKind() == ProviderKind.DISKLESS);
599-
} );
600-
}
601-
} catch (ApiException apiExc) {
602-
s_logger.error(apiExc.getMessage());
603-
}
604-
return false;
605-
}
606-
607571
/**
608572
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
609573
* The initial systemvm template resource isn't created on the management server, but
@@ -674,7 +638,7 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt
674638
destFile.setFormat(dstDisk.getFormat());
675639
destFile.setSize(disk.getVirtualSize());
676640

677-
boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
641+
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
678642
try {
679643
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
680644
qemu.convert(srcFile, destFile);

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Optional;
4343
import java.util.stream.Collectors;
4444

45+
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
4546
import com.cloud.utils.Pair;
4647
import com.cloud.utils.exception.CloudRuntimeException;
4748
import org.apache.log4j.Logger;
@@ -430,4 +431,37 @@ public static ResourceDefinition findResourceDefinition(DevelopersApi api, Strin
430431
public static boolean isRscDiskless(ResourceWithVolumes rsc) {
431432
return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
432433
}
434+
435+
/**
436+
* Checks if all diskful resource are on a zeroed block device.
437+
* @param pool Linstor pool to use
438+
* @param resName Linstor resource name
439+
* @return true if all resources are on a provider with zeroed blocks.
440+
*/
441+
public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, String resName) {
442+
final DevelopersApi api = getLinstorAPI(pool.getSourceHost());
443+
try {
444+
List<ResourceWithVolumes> resWithVols = api.viewResources(
445+
Collections.emptyList(),
446+
Collections.singletonList(resName),
447+
Collections.emptyList(),
448+
Collections.emptyList(),
449+
null,
450+
null);
451+
452+
if (resWithVols != null) {
453+
return resWithVols.stream()
454+
.allMatch(res -> {
455+
Volume vol0 = res.getVolumes().get(0);
456+
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
457+
vol0.getProviderKind() == ProviderKind.ZFS ||
458+
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
459+
vol0.getProviderKind() == ProviderKind.DISKLESS);
460+
} );
461+
}
462+
} catch (ApiException apiExc) {
463+
s_logger.error(apiExc.getMessage());
464+
}
465+
return false;
466+
}
433467
}

0 commit comments

Comments
 (0)