Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.BackupOffering;

import org.apache.commons.lang3.BooleanUtils;

import javax.inject.Inject;
import java.util.List;
Expand Down Expand Up @@ -92,6 +92,10 @@ public class CreateBackupOfferingCmd extends BaseCmd {
description = "Restrict the backup offering to the Domains identified by these IDs.")
private List<Long> 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 ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -166,6 +170,10 @@ public Boolean getUserDrivenBackups() {
return userDrivenBackups;
}

public boolean isCompressAsync() {
return BooleanUtils.toBooleanDefaultIfNull(this.compressAsync, true);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> backupChainImageStoreUrls, List<KbossTO> 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<String> backupChainImageStoreUrls,
List<KbossTO> kbossTOS, boolean isolated) {
this.quiesceVm = quiesceVm;
this.runningVM = runningVM;
this.endChain = endChain;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,13 @@ public Pair<Boolean, Long> 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()) {
Expand All @@ -459,20 +463,34 @@ public Pair<Boolean, Long> 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());
}
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 ? Backup.CompressionLibrary.zstd : 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());
Expand Down Expand Up @@ -782,7 +800,7 @@ public boolean startBackupCompression(long backupId, long hostId) {
BackupOfferingDetailsVO detail = backupOfferingDetailsDao.findDetail(backupOfferingVO.getId(), ApiConstants.COMPRESSION_LIBRARY);
List<InternalBackupJoinVO> backupChain = getBackupJoinParents(backupVO, true);
List<String> 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()));
Expand Down Expand Up @@ -2084,7 +2102,8 @@ protected List<VolumeObjectTO> getVolumesThatAreNotPartOfTheBackup(List<VolumeOb

protected void processBackupSuccess(boolean runningVm, List<VolumeObjectTO> volumeTOs, HashMap<String, InternalBackupStoragePoolVO> volumeUuidToDeltaPrimaryRef,
HashMap<String, InternalBackupDataStoreVO> volumeUuidToDeltaSecondaryRef, TakeKbossBackupAnswer answer, List<InternalBackupStoragePoolVO> parentBackupDeltasOnPrimary,
List<VMSnapshotVO> succeedingVmSnapshots, BackupVO backupVO, boolean fullBackup, VirtualMachine userVm, Long hostId, boolean endChain, boolean isolated) {
List<VMSnapshotVO> 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) {
Expand All @@ -2094,6 +2113,9 @@ protected void processBackupSuccess(boolean runningVm, List<VolumeObjectTO> 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()))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean, Long> result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, true);
assertFalse(result.first());
Expand Down Expand Up @@ -815,17 +817,18 @@ 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<Boolean, Long> result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, true);
assertTrue(result.first());
assertEquals(backupId, result.second());
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);
}

Expand All @@ -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<Boolean, Long> result = kbossBackupProviderSpy.orchestrateTakeBackup(backupVoMock, false, false);
assertTrue(result.first());
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.CompressBackupCommand;
import org.apache.cloudstack.storage.formatinspector.Qcow2Inspector;
Expand Down Expand Up @@ -54,7 +53,7 @@ public Answer execute(CompressBackupCommand command, LibvirtComputingResource se
logger.info("Starting compression for backup deltas [{}].", deltas);
try {
QemuImg qemuImg = new QemuImg(command.getWait() * 1000);
Integer rateLimit = validateAndGetRateLimit(command, qemuImg);
Integer rateLimit = qemuImg.validateAndGetRateLimit(command.getRateLimit());

KVMStoragePool mainSecStorage = storagePoolManager.getStoragePoolByURI(deltas.stream().findFirst().orElseThrow().getChild().getDataStore().getUrl());
secondaryStorages.add(mainSecStorage);
Expand Down Expand Up @@ -82,7 +81,7 @@ public Answer execute(CompressBackupCommand command, LibvirtComputingResource se

HashMap<String, String> 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(),
Expand All @@ -99,30 +98,6 @@ public Answer execute(CompressBackupCommand command, LibvirtComputingResource se
return new Answer(command);
}

private Integer validateAndGetRateLimit(CompressBackupCommand command, QemuImg qemuImg) {
if (command.getRateLimit() < 1) {
return null;
}

if (qemuImg.getVersion() < QemuImg.QEMU_5_2) {
throw new CloudRuntimeException("Qemu version is lower than 5.2.0, unable to set the rate limit.");
}

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<String, String> 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)) {
Expand Down
Loading
Loading