Skip to content

Commit c4e2edb

Browse files
committed
linstor: use sparse/discard qemu-img convert on thin devices (apache#11787)
1 parent 7b750ea commit c4e2edb

4 files changed

Lines changed: 58 additions & 38 deletions

File tree

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: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import com.linbit.linstor.api.model.ResourceMakeAvailable;
5151
import com.linbit.linstor.api.model.ResourceWithVolumes;
5252
import com.linbit.linstor.api.model.StoragePool;
53-
import com.linbit.linstor.api.model.Volume;
5453
import com.linbit.linstor.api.model.VolumeDefinition;
5554
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
5655
import org.apache.cloudstack.utils.qemu.QemuImg;
@@ -564,40 +563,6 @@ public KVMPhysicalDisk createTemplateFromDisk(
564563
throw new UnsupportedOperationException("Copying a template from disk is not supported in this configuration.");
565564
}
566565

567-
/**
568-
* Checks if all diskful resource are on a zeroed block device.
569-
* @param destPool Linstor pool to use
570-
* @param resName Linstor resource name
571-
* @return true if all resources are on a provider with zeroed blocks.
572-
*/
573-
private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) {
574-
final DevelopersApi api = getLinstorAPI(destPool);
575-
576-
try {
577-
List<ResourceWithVolumes> resWithVols = api.viewResources(
578-
Collections.emptyList(),
579-
Collections.singletonList(resName),
580-
Collections.emptyList(),
581-
Collections.emptyList(),
582-
null,
583-
null);
584-
585-
if (resWithVols != null) {
586-
return resWithVols.stream()
587-
.allMatch(res -> {
588-
Volume vol0 = res.getVolumes().get(0);
589-
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
590-
vol0.getProviderKind() == ProviderKind.ZFS ||
591-
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
592-
vol0.getProviderKind() == ProviderKind.DISKLESS);
593-
} );
594-
}
595-
} catch (ApiException apiExc) {
596-
s_logger.error(apiExc.getMessage());
597-
}
598-
return false;
599-
}
600-
601566
/**
602567
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
603568
* The initial systemvm template resource isn't created on the management server, but

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)