Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions plugins/storage/volume/linstor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2025-10-03]

### Changed

- Revert qcow2 snapshot now use sparse/discard options to convert on thin devices.

## [2025-08-05]

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException;
import org.apache.cloudstack.utils.qemu.QemuImgFile;
import org.apache.log4j.Logger;
import org.joda.time.Duration;
import org.libvirt.LibvirtException;

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

private void convertQCow2ToRAW(final String srcPath, final String dstPath, int waitMilliSeconds)
private void convertQCow2ToRAW(
KVMStoragePool pool, final String srcPath, final String dstUuid, int waitMilliSeconds)
throws LibvirtException, QemuImgException
{
final String dstPath = pool.getPhysicalDisk(dstUuid).getPath();
final QemuImgFile srcQemuFile = new QemuImgFile(
srcPath, QemuImg.PhysicalDiskFormat.QCOW2);
final QemuImg qemu = new QemuImg(waitMilliSeconds);
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(pool, LinstorUtil.RSC_PREFIX + dstUuid);
if (zeroedDevice)
{
// blockdiscard the device to ensure the device is filled with zeroes
Script blkDiscardScript = new Script("blkdiscard", Duration.millis(waitMilliSeconds));
blkDiscardScript.add("-f");
blkDiscardScript.add(dstPath);
blkDiscardScript.execute();
}
final QemuImg qemu = new QemuImg(waitMilliSeconds, zeroedDevice, true);
final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW);
qemu.convert(srcQemuFile, dstFile);
}
Expand All @@ -73,8 +87,9 @@ public CopyCmdAnswer execute(LinstorRevertBackupSnapshotCommand cmd, LibvirtComp
srcDataStore.getUrl() + File.separator + srcFile.getParent());

convertQCow2ToRAW(
linstorPool,
secondaryPool.getLocalPath() + File.separator + srcFile.getName(),
linstorPool.getPhysicalDisk(dst.getPath()).getPath(),
dst.getPath(),
cmd.getWaitInMillSeconds());

final VolumeObjectTO dstVolume = new VolumeObjectTO();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.cloud.storage.Storage;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;

import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException;
Expand All @@ -56,7 +55,6 @@
import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.StoragePool;
import com.linbit.linstor.api.model.Volume;
import com.linbit.linstor.api.model.VolumeDefinition;

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

/**
* Checks if all diskful resource are on a zeroed block device.
* @param destPool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) {
final DevelopersApi api = getLinstorAPI(destPool);

try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);

if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
s_logger.error(apiExc.getMessage());
}
return false;
}

/**
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
* The initial systemvm template resource isn't created on the management server, but
Expand Down Expand Up @@ -674,7 +638,7 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt
destFile.setFormat(dstDisk.getFormat());
destFile.setSize(disk.getVirtualSize());

boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
try {
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
qemu.convert(srcFile, destFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.Optional;
import java.util.stream.Collectors;

import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.log4j.Logger;
Expand Down Expand Up @@ -430,4 +431,37 @@ public static ResourceDefinition findResourceDefinition(DevelopersApi api, Strin
public static boolean isRscDiskless(ResourceWithVolumes rsc) {
return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
}

/**
* Checks if all diskful resource are on a zeroed block device.
* @param pool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, String resName) {
final DevelopersApi api = getLinstorAPI(pool.getSourceHost());
try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);

if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
s_logger.error(apiExc.getMessage());
}
return false;
}
}