package org.ovirt.engine.core.bll.snapshots; import java.util.Collections; import java.util.HashMap; import java.util.List; import javax.inject.Inject; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.SerialChildCommandsExecutionCallback; import org.ovirt.engine.core.bll.SerialChildExecutingCommand; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaStorageDependent; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback; import org.ovirt.engine.core.common.action.ColdMergeCommandParameters; import org.ovirt.engine.core.common.action.RemoveSnapshotSingleDiskParameters; import org.ovirt.engine.core.common.action.RemoveSnapshotSingleDiskStep; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.SubchainInfo; import org.ovirt.engine.core.common.businessentities.VmBlockJobType; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.ImageStatus; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.CommandStatus; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.ImageDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute public class ColdMergeSnapshotSingleDiskCommand<T extends RemoveSnapshotSingleDiskParameters> extends RemoveSnapshotSingleDiskCommandBase<T> implements SerialChildExecutingCommand, QuotaStorageDependent { @Inject private SnapshotDao snapshotDao; @Inject private ImageDao imageDao; public ColdMergeSnapshotSingleDiskCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); } @Override protected void executeCommand() { // Let doPolling() drive the execution; we don't have any guarantee that // executeCommand() will finish before doPolling() is called, and we don't // want to possibly run the first command twice. getParameters().setCommandStep(RemoveSnapshotSingleDiskStep.PREPARE_MERGE); getParameters().setChildCommands(new HashMap<>()); setSucceeded(true); } @Override public void handleFailure() { log.error("Command '{}' id '{}' failed executing step '{}'", getActionType(), getCommandId(), getParameters().getCommandStep()); } @Override public boolean performNextOperation(int completedChildCount) { // Upon recovery or after invoking a new child command, our map may be missing an entry syncChildCommandList(getParameters()); Guid currentChildId = getCurrentChildId(getParameters()); if (currentChildId != null) { getParameters().setCommandStep(getParameters().getNextCommandStep()); } log.info("Command '{}' id '{}' executing step '{}'", getActionType(), getCommandId(), getParameters().getCommandStep()); Pair<VdcActionType, ? extends VdcActionParametersBase> nextCommand = null; switch (getParameters().getCommandStep()) { case PREPARE_MERGE: nextCommand = new Pair<>(VdcActionType.PrepareMerge, buildColdMergeParameters(getImage(), getDestinationDiskImage())); getParameters().setNextCommandStep(RemoveSnapshotSingleDiskStep.MERGE); break; case MERGE: nextCommand = new Pair<>(VdcActionType.ColdMerge, buildColdMergeParameters(getImage(), getDestinationDiskImage())); getParameters().setNextCommandStep(RemoveSnapshotSingleDiskStep.FINALIZE_MERGE); break; case FINALIZE_MERGE: nextCommand = new Pair<>(VdcActionType.FinalizeMerge, buildColdMergeParameters(getImage(), getDestinationDiskImage())); getParameters().setNextCommandStep(RemoveSnapshotSingleDiskStep.DESTROY_IMAGE); break; case DESTROY_IMAGE: nextCommand = buildDestroyCommand(VdcActionType.DestroyImage, getActionType(), Collections.singletonList(getDestinationImageId())); getParameters().setNextCommandStep(RemoveSnapshotSingleDiskStep.DESTROY_IMAGE_CHECK); break; case DESTROY_IMAGE_CHECK: nextCommand = buildDestroyCommand(VdcActionType.DestroyImageCheck, getActionType(), Collections.singletonList(getDestinationImageId())); setCommandStatus(CommandStatus.SUCCEEDED); break; } persistCommandIfNeeded(); if (nextCommand != null) { runInternalActionWithTasksContext(nextCommand.getFirst(), nextCommand.getSecond()); // Add the child, but wait, it's a race! child will start, task may spawn, get polled, and we won't have the child id return true; } else { return false; } } @Override public void endSuccessfully() { syncDbRecords(VmBlockJobType.COMMIT, getImageInfoFromVdsm(getDiskImage()), Collections.singleton(getDestinationImageId()), true); if (getParameters().getVmSnapshotId() != null) { lockVmSnapshotsWithWait(getVm()); Snapshot snapshot = snapshotDao.get(getParameters().getVmSnapshotId()); Snapshot snapshotWithoutImage = ImagesHandler.prepareSnapshotConfigWithoutImageSingleImage(snapshot, getParameters().getImageId(), ovfManager); snapshotDao.update(snapshotWithoutImage); if (getSnapshotsEngineLock() != null) { lockManager.releaseLock(getSnapshotsEngineLock()); } } setSucceeded(true); } @Override public void endWithFailure() { setSucceeded(true); TransactionSupport.executeInNewTransaction(() -> { if (!getParameters().isLeaveLocked()) { DiskImage diskImage = getDestinationDiskImage(); if (diskImage != null) { imageDao.updateStatus(diskImage.getImage().getId(), ImageStatus.OK); } } return null; }); } @Override public CommandCallback getCallback() { return new SerialChildCommandsExecutionCallback(); } @Override public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() { return Collections.emptyList(); } private ColdMergeCommandParameters buildColdMergeParameters(DiskImage baseVolume, DiskImage topVolume) { SubchainInfo subchainInfo = new SubchainInfo(getDiskImage().getStorageIds().get(0), baseVolume, topVolume); ColdMergeCommandParameters parameters = new ColdMergeCommandParameters( getDiskImage().getStoragePoolId(), subchainInfo); parameters.setEndProcedure(VdcActionParametersBase.EndProcedure.COMMAND_MANAGED); parameters.setParentCommand(getActionType()); parameters.setParentParameters(getParameters()); return parameters; } }