From 71625a8ba1b31651af36060303cb4154b817a5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:33:37 -0300 Subject: [PATCH 1/3] add sync backup flag --- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/backup/CreateBackupOfferingCmd.java | 8 ++++ .../backup/TakeKbossBackupCommand.java | 43 ++++++++++++++++++- .../backup/KbossBackupProvider.java | 28 ++++++++++-- .../backup/KbossBackupProviderTest.java | 16 ++++--- .../LibvirtCompressBackupCommandWrapper.java | 14 +----- .../LibvirtTakeKbossBackupCommandWrapper.java | 18 +++++--- .../apache/cloudstack/utils/qemu/QemuImg.java | 15 +++++++ ...bvirTakeKbossBackupCommandWrapperTest.java | 16 ++++--- .../cloudstack/backup/BackupManagerImpl.java | 3 ++ 10 files changed, 126 insertions(+), 36 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index ac6acdf42516..0cc112f8078f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1489,6 +1489,7 @@ public class ApiConstants { public static final String SCHEDULED = "scheduled"; public static final String SCHEDULED_DATE = "scheduleddate"; public static final String BACKUP_PROVIDER = "backupprovider"; + public static final String COMPRESS_ASYNC = "compressasync"; /** * This enum specifies IO Drivers, each option controls specific policies on I/O. diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java index c5d29b615439..155011f1c1ef 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java @@ -92,6 +92,10 @@ public class CreateBackupOfferingCmd extends BaseCmd { description = "Restrict the backup offering to the Domains identified by these IDs.") private List domainIds; + @Parameter(name = ApiConstants.COMPRESS_ASYNC, type = CommandType.BOOLEAN, description = "Whether to compress synchronously during backup creation, or asynchronously later. " + + "Default true.") + private Boolean compressAsync; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -166,6 +170,10 @@ public Boolean getUserDrivenBackups() { return userDrivenBackups; } + public boolean isCompressAsync() { + return Boolean.TRUE.equals(this.compressAsync); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java index b145fa6257b1..e7f8b1ddfaa6 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java @@ -42,7 +42,16 @@ public class TakeKbossBackupCommand extends Command { private boolean isolated; - public TakeKbossBackupCommand(boolean quiesceVm, boolean runningVM, boolean endChain, String vmName, String imageStoreUrl, List backupChainImageStoreUrls, List kbossTOS, boolean isolated) { + private boolean compress; + + private Backup.CompressionLibrary compressionLib; + + private Integer coroutines; + + private Integer rateLimit; + + public TakeKbossBackupCommand(boolean quiesceVm, boolean runningVM, boolean endChain, String vmName, String imageStoreUrl, List backupChainImageStoreUrls, + List kbossTOS, boolean isolated) { this.quiesceVm = quiesceVm; this.runningVM = runningVM; this.endChain = endChain; @@ -85,6 +94,38 @@ public boolean isIsolated() { return isolated; } + public void setRateLimit(Integer rateLimit) { + this.rateLimit = rateLimit; + } + + public void setCoroutines(Integer coroutines) { + this.coroutines = coroutines; + } + + public void setCompressionLib(Backup.CompressionLibrary compressionLib) { + this.compressionLib = compressionLib; + } + + public void setCompress(boolean compress) { + this.compress = compress; + } + + public boolean isCompress() { + return compress; + } + + public Backup.CompressionLibrary getCompressionLib() { + return compressionLib; + } + + public Integer getCoroutines() { + return coroutines; + } + + public Integer getRateLimit() { + return rateLimit; + } + @Override public boolean executeInSequence() { return false; diff --git a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java index 596dc62b7206..b90982608bb4 100644 --- a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java +++ b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java @@ -448,9 +448,13 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV parentBackupDeltasOnPrimary, volumeUuidToDeltaPrimaryRef, volumeUuidToDeltaSecondaryRef, succeedingVmSnapshot, kbossTO); } + boolean supportsCompression = offeringSupportsCompression(newBackupJoin); + TakeKbossBackupCommand command = new TakeKbossBackupCommand(quiesceVm, runningVm, newBackupJoin.getEndOfChain(), userVm.getInstanceName(), imageStore.getUri(), chainImageStoreUrls, kbossTOs, isolated); + boolean compressNow = checkSyncCompressionAndConfigureCommand(backupOfferingVO, supportsCompression, command, hostVO); + Answer answer = sendBackupCommand(hostId, command); if (answer == null || !answer.getResult()) { @@ -459,13 +463,13 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV } processBackupSuccess(runningVm, volumeTOs, volumeUuidToDeltaPrimaryRef, volumeUuidToDeltaSecondaryRef, (TakeKbossBackupAnswer)answer, parentBackupDeltasOnPrimary, - succeedingVmSnapshotList, backupVO, fullBackup, userVm, hostId, newBackupJoin.getEndOfChain(), isolated); + succeedingVmSnapshotList, backupVO, fullBackup, userVm, hostId, newBackupJoin.getEndOfChain(), isolated, compressNow); if (!isolated) { updateCurrentBackup(newBackupJoin); } - if (offeringSupportsCompression(newBackupJoin)) { + if (supportsCompression && !compressNow) { compressBackupAsync(newBackupJoin, backup.getZoneId(), userVm.getAccountId()); } else { validateBackupAsyncIfHasOfferingSupport(newBackupJoin, backup.getZoneId(), userVm.getAccountId()); @@ -473,6 +477,20 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV return new Pair<>(Boolean.TRUE, backupVO.getId()); } + protected boolean checkSyncCompressionAndConfigureCommand(BackupOfferingVO backupOfferingVO, boolean supportsCompression, TakeKbossBackupCommand command, HostVO hostVO) { + BackupOfferingDetailsVO compressAsync = backupOfferingDetailsDao.findDetail(backupOfferingVO.getId(), ApiConstants.COMPRESS_ASYNC); + boolean compressNow = compressAsync != null && !Boolean.parseBoolean(compressAsync.getValue()) && supportsCompression; + + if (compressNow) { + BackupOfferingDetailsVO detail = backupOfferingDetailsDao.findDetail(backupOfferingVO.getId(), ApiConstants.COMPRESSION_LIBRARY); + command.setCompress(true); + command.setCompressionLib(detail == null ? null : Backup.CompressionLibrary.valueOf(detail.getValue())); + command.setCoroutines(backupCompressionCoroutines.valueIn(hostVO.getClusterId())); + command.setRateLimit(backupCompressionRateLimit.valueIn(hostVO.getClusterId())); + } + return compressNow; + } + @Override public boolean deleteBackup(Backup backup, boolean forced) { logger.debug("Queueing backup [{}] deletion.", backup.getUuid()); @@ -2084,7 +2102,8 @@ protected List getVolumesThatAreNotPartOfTheBackup(List volumeTOs, HashMap volumeUuidToDeltaPrimaryRef, HashMap volumeUuidToDeltaSecondaryRef, TakeKbossBackupAnswer answer, List parentBackupDeltasOnPrimary, - List succeedingVmSnapshots, BackupVO backupVO, boolean fullBackup, VirtualMachine userVm, Long hostId, boolean endChain, boolean isolated) { + List succeedingVmSnapshots, BackupVO backupVO, boolean fullBackup, VirtualMachine userVm, Long hostId, boolean endChain, boolean isolated, + boolean compressNow) { long physicalBackupSize = 0; logger.debug("Processing backup [{}] success.", backupVO.getUuid()); for (VolumeObjectTO volumeObjectTO : volumeTOs) { @@ -2094,6 +2113,9 @@ protected void processBackupSuccess(boolean runningVm, List volu expungeOldDeltasAndUpdateVmSnapshotIfNeeded(parentBackupDeltasOnPrimary, succeedingVmSnapshots.isEmpty() ? null : succeedingVmSnapshots.get(0)); + if (compressNow) { + backupVO.setCompressionStatus(Backup.CompressionStatus.Compressed); + } backupVO.setSize(physicalBackupSize); backupVO.setStatus(Backup.Status.BackedUp); backupVO.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(new ArrayList<>(volumeDao.findByInstance(userVm.getId())))); diff --git a/plugins/backup/kboss/src/test/java/org/apache/cloudstack/backup/KbossBackupProviderTest.java b/plugins/backup/kboss/src/test/java/org/apache/cloudstack/backup/KbossBackupProviderTest.java index d16e0bd948fc..7a271074e4bc 100644 --- a/plugins/backup/kboss/src/test/java/org/apache/cloudstack/backup/KbossBackupProviderTest.java +++ b/plugins/backup/kboss/src/test/java/org/apache/cloudstack/backup/KbossBackupProviderTest.java @@ -787,6 +787,8 @@ public void orchestrateTakeBackupTestIsolatedBackupFailed() { doNothing().when(kbossBackupProviderSpy).validateStorages(any(), any()); doReturn(internalBackupJoinVoMock).when(internalBackupJoinDaoMock).findById(any()); doReturn(dataStoreMock).when(kbossBackupProviderSpy).getImageStoreForBackup(any(), any()); + doReturn(false).when(kbossBackupProviderSpy).offeringSupportsCompression(any()); + doReturn(false).when(kbossBackupProviderSpy).checkSyncCompressionAndConfigureCommand(any(), anyBoolean(), any(), any()); Pair result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, true); assertFalse(result.first()); @@ -815,9 +817,10 @@ public void orchestrateTakeBackupTestIsolatedBackupSuccessWithCompression() { doReturn(takeKbossBackupAnswerMock).when(kbossBackupProviderSpy).sendBackupCommand(anyLong(), any()); doReturn(true).when(takeKbossBackupAnswerMock).getResult(); doNothing().when(kbossBackupProviderSpy).processBackupSuccess(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), - anyLong(), anyBoolean(), anyBoolean()); - doReturn(true).when(kbossBackupProviderSpy).offeringSupportsCompression(internalBackupJoinVoMock); + anyLong(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(kbossBackupProviderSpy).compressBackupAsync(internalBackupJoinVoMock, 0, 0); + doReturn(true).when(kbossBackupProviderSpy).offeringSupportsCompression(any()); + doReturn(false).when(kbossBackupProviderSpy).checkSyncCompressionAndConfigureCommand(any(), anyBoolean(), any(), any()); Pair result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, true); assertTrue(result.first()); @@ -825,7 +828,7 @@ public void orchestrateTakeBackupTestIsolatedBackupSuccessWithCompression() { verify(kbossBackupProviderSpy, Mockito.times(1)).setBackupAsIsolated(backupVoMock); verify(kbossBackupProviderSpy, Mockito.times(2)).createDeltaReferences(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), any(), any(), any(), any(), any(), any(), any()); verify(kbossBackupProviderSpy, Mockito.times(1)).processBackupSuccess(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), - anyLong(), anyBoolean(), anyBoolean()); + anyLong(), anyBoolean(), anyBoolean(), anyBoolean()); verify(kbossBackupProviderSpy, Mockito.times(1)).compressBackupAsync(internalBackupJoinVoMock, 0, 0); } @@ -850,9 +853,10 @@ public void orchestrateTakeBackupTestBackupSuccessWithValidation() { doReturn(takeKbossBackupAnswerMock).when(kbossBackupProviderSpy).sendBackupCommand(anyLong(), any()); doReturn(true).when(takeKbossBackupAnswerMock).getResult(); doNothing().when(kbossBackupProviderSpy).processBackupSuccess(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), - anyLong(), anyBoolean(), anyBoolean()); - doReturn(false).when(kbossBackupProviderSpy).offeringSupportsCompression(internalBackupJoinVoMock); + anyLong(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(kbossBackupProviderSpy).validateBackupAsyncIfHasOfferingSupport(any(), anyLong(), anyLong()); + doReturn(false).when(kbossBackupProviderSpy).offeringSupportsCompression(any()); + doReturn(false).when(kbossBackupProviderSpy).checkSyncCompressionAndConfigureCommand(any(), anyBoolean(), any(), any()); Pair result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, false); assertTrue(result.first()); @@ -861,7 +865,7 @@ public void orchestrateTakeBackupTestBackupSuccessWithValidation() { verify(internalBackupDataStoreDaoMock).listByBackupId(0); verify(kbossBackupProviderSpy, Mockito.times(2)).createDeltaReferences(Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), any(), any(), any(), any(), any(), any(), any()); verify(kbossBackupProviderSpy, Mockito.times(1)).processBackupSuccess(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), anyBoolean(), any(), - anyLong(), anyBoolean(), anyBoolean()); + anyLong(), anyBoolean(), anyBoolean(), anyBoolean()); verify(kbossBackupProviderSpy, Mockito.times(1)).validateBackupAsyncIfHasOfferingSupport(internalBackupJoinVoMock, 0, 0); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCompressBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCompressBackupCommandWrapper.java index 2cc08311cbce..dc1f1a66b268 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCompressBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCompressBackupCommandWrapper.java @@ -82,7 +82,7 @@ public Answer execute(CompressBackupCommand command, LibvirtComputingResource se HashMap options = new HashMap<>(); Backup.CompressionLibrary compressionLib = getCompressionLibrary(command, fullDeltaPath); - setCompressionTypeOptionIfAvailable(qemuImg, options, compressionLib); + qemuImg.setCompressionTypeOptionIfAvailable(options, compressionLib); int coroutines = command.getCoroutines(); logger.info("Starting compression for backup delta [{}] with parent [{}] using [{}] coroutines.", child, parent, coroutines); qemuImg.convert(originalBackup, compressedBackup, backingFile, options, null, new QemuImageOptions(originalBackup.getFormat(), originalBackup.getFileName(), @@ -111,18 +111,6 @@ private Integer validateAndGetRateLimit(CompressBackupCommand command, QemuImg q return command.getRateLimit(); } - /** - * Sets the compression type option if qemu-img is at least in version 5.1. Otherwise, will not set it and qemu will use zlib. - * */ - private void setCompressionTypeOptionIfAvailable(QemuImg qemuImg, HashMap options, Backup.CompressionLibrary compressionLib) { - if (qemuImg.getVersion() >= QemuImg.QEMU_5_1) { - options.put(COMPRESSION_TYPE, compressionLib.name()); - return; - } - logger.warn("Qemu is at a lower version than 5.1, we will not be able to use zstd to compress backups. Only zlib is supported for this version. Current version is [{}].", - qemuImg.getVersion()); - } - private Backup.CompressionLibrary getCompressionLibrary(CompressBackupCommand command, String fullDeltaPath) { Backup.CompressionLibrary compressionLib = command.getCompressionLib(); if (compressionLib == Backup.CompressionLibrary.zlib || !Qcow2Inspector.validateQcow2Version(fullDeltaPath, MIN_QCOW_2_VERSION_FOR_ZSTD)) { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeKbossBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeKbossBackupCommandWrapper.java index d2332f4f99b1..4b0f321e5e94 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeKbossBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeKbossBackupCommandWrapper.java @@ -105,7 +105,7 @@ protected void backupVolumes(TakeKbossBackupCommand command, LibvirtComputingRes logger.debug("Backing up volume [{}].", volumeUuid); Pair deltaPathOnSecondaryAndSize = copyBackupDeltaToSecondary(storagePoolManager, kbossTO, command.getBackupChainImageStoreUrls(), - command.getImageStoreUrl(), maxWaitInMillis); + command.getImageStoreUrl(), maxWaitInMillis, command); mapVolumeUuidToDeltaPathOnSecondaryAndDeltaSize.put(volumeUuid, deltaPathOnSecondaryAndSize); maxWaitInMillis = calculateRemainingTime(maxWaitInMillis, startTimeMillis); @@ -165,7 +165,7 @@ protected void cleanupVm(TakeKbossBackupCommand command, LibvirtComputingResourc * If there were snapshots created after the last backup, they'll be copied alongside and merged in the secondary storage. * */ protected Pair copyBackupDeltaToSecondary(KVMStoragePoolManager storagePoolManager, KbossTO kbossTO, List chainImageStoreUrls, String imageStoreUrl, - int waitInMillis) { + int waitInMillis, TakeKbossBackupCommand command) { VolumeObjectTO delta = kbossTO.getVolumeObjectTO(); String parentDeltaPathOnSecondary = kbossTO.getPathBackupParentOnSecondary(); List deltaPathsToCopy = CollectionUtils.isEmpty(kbossTO.getVmSnapshotDeltaPaths()) ? new ArrayList<>() : new ArrayList<>(kbossTO.getVmSnapshotDeltaPaths()); @@ -199,7 +199,7 @@ protected Pair copyBackupDeltaToSecondary(KVMStoragePoolManager st } String backupDeltaFullPathOnPrimary = primaryPool.getLocalPathFor(deltaPathsToCopy.remove(0)); - convertDeltaToSecondary(backupDeltaFullPathOnPrimary, backupDeltaFullPathOnSecondary, parentBackupFullPath, delta.getUuid(), waitInMillis); + convertDeltaToSecondary(backupDeltaFullPathOnPrimary, backupDeltaFullPathOnSecondary, parentBackupFullPath, delta.getUuid(), waitInMillis, command); if (!deltaPathsToCopy.isEmpty()) { parentDeltaPathOnSecondary = topDelta; @@ -282,7 +282,8 @@ protected void removeTemporaryDeltas(List temporaryDeltasToRemove, boole * @param volumeUuid volume uuid, used for logging. * @param waitInMillis timeout in milliseconds. * */ - protected void convertDeltaToSecondary(String pathDeltaOnPrimary, String pathDeltaOnSecondary, String pathParentOnSecondary, String volumeUuid, int waitInMillis) + protected void convertDeltaToSecondary(String pathDeltaOnPrimary, String pathDeltaOnSecondary, String pathParentOnSecondary, String volumeUuid, int waitInMillis, + TakeKbossBackupCommand command) throws QemuImgException, LibvirtException { QemuImgFile backupDestination = new QemuImgFile(pathDeltaOnSecondary, QemuImg.PhysicalDiskFormat.QCOW2); QemuImgFile backupOrigin = new QemuImgFile(pathDeltaOnPrimary, QemuImg.PhysicalDiskFormat.QCOW2); @@ -297,8 +298,13 @@ protected void convertDeltaToSecondary(String pathDeltaOnPrimary, String pathDel createDirsIfNeeded(pathDeltaOnSecondary, volumeUuid); QemuImg qemuImg = new QemuImg(waitInMillis); - qemuImg.convert(backupOrigin, backupDestination, parentBackup, null, null, new QemuImageOptions(backupOrigin.getFormat(), backupOrigin.getFileName(), null), null, - true, false, false, false, null, null); + Map options = new HashMap<>(); + if (command.isCompress()) { + qemuImg.setCompressionTypeOptionIfAvailable(options, command.getCompressionLib()); + } + qemuImg.convert(backupOrigin, backupDestination, parentBackup, options, null, new QemuImageOptions(backupOrigin.getFormat(), backupOrigin.getFileName(), null), null, + true, false, false, command.isCompress(), command.getCoroutines(), command.getRateLimit()); + } diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index cae6832999eb..dfc87dad2c03 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.regex.Pattern; +import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtCompressBackupCommandWrapper; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.storage.formatinspector.Qcow2Inspector; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.NotImplementedException; @@ -1073,4 +1075,17 @@ public long getVersion() { return this.version; } + /** + * Sets the compression type option if qemu-img is at least in version 5.1. Otherwise, will not set it and qemu will use zlib. + * */ + public void setCompressionTypeOptionIfAvailable(Map options, Backup.CompressionLibrary compressionLib) { + if (getVersion() >= QemuImg.QEMU_5_1) { + options.put(LibvirtCompressBackupCommandWrapper.COMPRESSION_TYPE, compressionLib.name()); + return; + } + logger.warn("Qemu is at a lower version than 5.1, we will not be able to use zstd to compress backups. Only zlib is supported for this version. Current version is [{}].", + getVersion()); + } + + } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirTakeKbossBackupCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirTakeKbossBackupCommandWrapperTest.java index 8354993e61a1..eca4f72b2808 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirTakeKbossBackupCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirTakeKbossBackupCommandWrapperTest.java @@ -189,10 +189,10 @@ public void backupVolumesTestHappyPath() { doReturn(secondaryUrl).when(takeKbossBackupCommandMock).getImageStoreUrl(); Pair pair1 = new Pair<>("p1", 10L); doReturn(pair1).when(libvirtTakeKbossBackupCommandWrapperSpy).copyBackupDeltaToSecondary(eq(kvmStoragePoolManagerMock), eq(kbossTO1), anyList(), - eq(secondaryUrl), anyInt()); + eq(secondaryUrl), anyInt(), any()); Pair pair2 = new Pair<>("p2", 13L); doReturn(pair2).when(libvirtTakeKbossBackupCommandWrapperSpy).copyBackupDeltaToSecondary(eq(kvmStoragePoolManagerMock), eq(kbossTO2), anyList(), - eq(secondaryUrl), anyInt()); + eq(secondaryUrl), anyInt(), any()); libvirtTakeKbossBackupCommandWrapperSpy.backupVolumes(takeKbossBackupCommandMock, libvirtComputingResourceMock, kvmStoragePoolManagerMock, List.of(kbossTO1, kbossTO2), volumeTosAndNewPaths, "tst", false, mapVolumeUuidToDeltaPathOnSecondaryAndDeltaSize); @@ -276,13 +276,14 @@ public void copyBackupDeltaToSecondaryTest() throws LibvirtException, QemuImgExc doReturn(parentBackupFullPath).when(kvmStoragePool2).getLocalPathFor(parentPath); doReturn(backupDeltaFullPathOnPrimary1).when(kvmStoragePool3).getLocalPathFor(deltaPath2); doReturn(backupDeltaFullPathOnSecondary1).when(kvmStoragePool1).getLocalPathFor(deltaPath1); - doNothing().when(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(backupDeltaFullPathOnPrimary1, backupDeltaFullPathOnSecondary1, parentBackupFullPath, volUuid1, 100000); + doNothing().when(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(backupDeltaFullPathOnPrimary1, backupDeltaFullPathOnSecondary1, parentBackupFullPath, + volUuid1, 100000, takeKbossBackupCommandMock); doReturn(randomPath1).when(libvirtTakeKbossBackupCommandWrapperSpy).getRelativePathOnSecondaryForBackup(anyLong(), anyLong(), any()); doReturn("random2").when(kvmStoragePool1).getLocalPathFor(randomPath1); doReturn(backupDeltaFullPathOnPrimary2).when(kvmStoragePool3).getLocalPathFor(volumePath); doNothing().when(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(eq(backupDeltaFullPathOnPrimary2), eq("random2"), eq(backupDeltaFullPathOnSecondary1), - any(), anyInt()); + any(), anyInt(), any()); doNothing().when(libvirtTakeKbossBackupCommandWrapperSpy).commitTopDeltaOnBaseBackupOnSecondaryIfNeeded(randomPath1, deltaPath1, kvmStoragePool1, backupDeltaFullPathOnSecondary1, 100000); @@ -291,15 +292,16 @@ public void copyBackupDeltaToSecondaryTest() throws LibvirtException, QemuImgExc try(MockedStatic filesMockedStatic = Mockito.mockStatic(Files.class)) { filesMockedStatic.when(() -> Files.size(any())).thenReturn(1000L); Pair result = libvirtTakeKbossBackupCommandWrapperSpy.copyBackupDeltaToSecondary(kvmStoragePoolManagerMock, kbossTO1, List.of(secondaryUrl2), - secondaryUrl, 100000); + secondaryUrl, 100000, takeKbossBackupCommandMock); assertEquals(deltaPath1, result.first()); assertEquals(Long.valueOf(1000L), result.second()); } - verify(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(backupDeltaFullPathOnPrimary1, backupDeltaFullPathOnSecondary1, parentBackupFullPath, volUuid1, 100000); + verify(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(backupDeltaFullPathOnPrimary1, backupDeltaFullPathOnSecondary1, parentBackupFullPath, volUuid1, + 100000, takeKbossBackupCommandMock); verify(libvirtTakeKbossBackupCommandWrapperSpy).convertDeltaToSecondary(eq(backupDeltaFullPathOnPrimary2), eq("random2"), eq(backupDeltaFullPathOnSecondary1), - any(), anyInt()); + any(), anyInt(), any()); verify(libvirtTakeKbossBackupCommandWrapperSpy).commitTopDeltaOnBaseBackupOnSecondaryIfNeeded(randomPath1, deltaPath1, kvmStoragePool1, backupDeltaFullPathOnSecondary1, 100000); verify(libvirtTakeKbossBackupCommandWrapperSpy).removeTemporaryDeltas(any(), anyBoolean()); diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 58e435b6406d..f74a7e7e404c 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -406,6 +406,9 @@ public BackupOffering createBackupOffering(CreateBackupOfferingCmd cmd) { if (cmd.isAllowQuickRestore()) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.ALLOW_QUICK_RESTORE, "true", true)); } + if (cmd.isCompressAsync()) { + detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.COMPRESS_ASYNC, "true", true)); + } if (cmd.getBackupChainSize() != null) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.BACKUP_CHAIN_SIZE, cmd.getBackupChainSize().toString(), true)); } From b8cffe200b159488956492914799794c5421b2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:10:51 -0300 Subject: [PATCH 2/3] fix --- .../org/apache/cloudstack/backup/KbossBackupProvider.java | 4 ++-- .../main/java/org/apache/cloudstack/utils/qemu/QemuImg.java | 4 ++++ .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 4 +--- ui/public/locales/en.json | 1 + ui/public/locales/pt_BR.json | 1 + ui/src/views/offering/CreateBackupOffering.vue | 4 ++++ 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java index b90982608bb4..46966b18dde8 100644 --- a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java +++ b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java @@ -484,7 +484,7 @@ protected boolean checkSyncCompressionAndConfigureCommand(BackupOfferingVO backu if (compressNow) { BackupOfferingDetailsVO detail = backupOfferingDetailsDao.findDetail(backupOfferingVO.getId(), ApiConstants.COMPRESSION_LIBRARY); command.setCompress(true); - command.setCompressionLib(detail == null ? null : Backup.CompressionLibrary.valueOf(detail.getValue())); + command.setCompressionLib(detail == null ? Backup.CompressionLibrary.zstd : Backup.CompressionLibrary.valueOf(detail.getValue())); command.setCoroutines(backupCompressionCoroutines.valueIn(hostVO.getClusterId())); command.setRateLimit(backupCompressionRateLimit.valueIn(hostVO.getClusterId())); } @@ -800,7 +800,7 @@ public boolean startBackupCompression(long backupId, long hostId) { BackupOfferingDetailsVO detail = backupOfferingDetailsDao.findDetail(backupOfferingVO.getId(), ApiConstants.COMPRESSION_LIBRARY); List backupChain = getBackupJoinParents(backupVO, true); List chainImageStoreUrls = getChainImageStoreUrls(backupChain); - CompressBackupCommand cmd = new CompressBackupCommand(deltasToCompressAndParents, chainImageStoreUrls, minFreeStorage, detail == null ? null : + CompressBackupCommand cmd = new CompressBackupCommand(deltasToCompressAndParents, chainImageStoreUrls, minFreeStorage, detail == null ? Backup.CompressionLibrary.zstd : Backup.CompressionLibrary.valueOf(detail.getValue()), backupCompressionCoroutines.valueIn(hostVO.getClusterId()), backupCompressionRateLimit.valueIn(hostVO.getClusterId())); cmd.setWait(backupCompressionTimeout.valueIn(hostVO.getClusterId())); diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index dfc87dad2c03..9a79b0762642 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -1079,6 +1079,10 @@ public long getVersion() { * Sets the compression type option if qemu-img is at least in version 5.1. Otherwise, will not set it and qemu will use zlib. * */ public void setCompressionTypeOptionIfAvailable(Map options, Backup.CompressionLibrary compressionLib) { + if (compressionLib == null) { + logger.debug("No compression lib informed, using the default."); + return; + } if (getVersion() >= QemuImg.QEMU_5_1) { options.put(LibvirtCompressBackupCommandWrapper.COMPRESSION_TYPE, compressionLib.name()); return; diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index f74a7e7e404c..ebf4f20d68f3 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -396,6 +396,7 @@ public BackupOffering createBackupOffering(CreateBackupOfferingCmd cmd) { } if (cmd.isCompress()) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.COMPRESS, "true", true)); + detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.COMPRESS_ASYNC, Boolean.toString(cmd.isCompressAsync()), true)); } if (cmd.isValidate()) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.VALIDATE, "true", true)); @@ -406,9 +407,6 @@ public BackupOffering createBackupOffering(CreateBackupOfferingCmd cmd) { if (cmd.isAllowQuickRestore()) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.ALLOW_QUICK_RESTORE, "true", true)); } - if (cmd.isCompressAsync()) { - detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.COMPRESS_ASYNC, "true", true)); - } if (cmd.getBackupChainSize() != null) { detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.BACKUP_CHAIN_SIZE, cmd.getBackupChainSize().toString(), true)); } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 775de26103a0..1fff375c10d7 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -629,6 +629,7 @@ "label.complete": "Complete", "label.completed": "Completed", "label.compress": "Compress", +"label.compressasync": "Compress asynchronously", "label.compressionstatus": "Compression status", "label.compressionlibrary": "Compression library", "label.compute": "Compute", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index b3eae6eb11ce..b44d973e5e59 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -563,6 +563,7 @@ "label.community": "Comunidade", "label.complete": "Complete", "label.compress": "Comprimir", +"label.compressasync": "Comprimir assincronamente", "label.compressionstatus": "Estado de compress\u00e3o", "label.compressionlibrary": "Biblioteca de compress\u00e3o", "label.compute": "Computa\u00e7\u00e3o", diff --git a/ui/src/views/offering/CreateBackupOffering.vue b/ui/src/views/offering/CreateBackupOffering.vue index c9e22949f061..2d9c32b0957b 100644 --- a/ui/src/views/offering/CreateBackupOffering.vue +++ b/ui/src/views/offering/CreateBackupOffering.vue @@ -96,6 +96,9 @@ + + +