linstor: use sparse/discard qemu-img convert on thin devices (#11787)

This commit is contained in:
Rene Peinthor 2025-10-06 09:10:53 +02:00 committed by GitHub
parent 2e113e5ed7
commit a208db54ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 40 deletions

View File

@ -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

View File

@ -26,13 +26,16 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
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)
@ -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);
}
@ -73,8 +87,9 @@ public final class LinstorRevertBackupSnapshotCommandWrapper
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();

View File

@ -30,7 +30,6 @@ import javax.annotation.Nonnull;
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;
@ -56,7 +55,6 @@ import com.linbit.linstor.api.model.ResourceGroupSpawn;
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;
@ -570,40 +568,6 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
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
@ -674,7 +638,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
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);

View File

@ -42,6 +42,7 @@ import java.util.Map;
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;
@ -430,4 +431,37 @@ public class LinstorUtil {
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;
}
}