package org.ovirt.engine.core.bll.snapshots;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.InternalCommandAttribute;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.disk.image.BaseImagesCommand;
import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.ImagesActionsParametersBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.VolumeClassification;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
import org.ovirt.engine.core.common.businessentities.storage.VolumeType;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.vdscommands.CreateSnapshotVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.ImageDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
/**
* This command responsible to creating snapshot from existing image and replace it to VM, holds the image. This command
* legal only for images, appeared in Db
*/
@InternalCommandAttribute
@NonTransactiveCommandAttribute
public class CreateSnapshotCommand<T extends ImagesActionsParametersBase> extends BaseImagesCommand<T> {
@Inject
private ImageDao imageDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDao vmDao;
protected DiskImage newDiskImage;
public CreateSnapshotCommand(Guid commandId) {
super(commandId);
}
public CreateSnapshotCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
setSnapshotName(parameters.getDescription());
}
public CreateSnapshotCommand(T parameters) {
this(parameters, null);
}
@Override
protected void executeCommand() {
if (performImageVdsmOperation()) {
TransactionSupport.executeInNewTransaction(() -> {
processOldImageFromDb();
addDiskImageToDb(newDiskImage, getCompensationContext(), Boolean.TRUE);
setActionReturnValue(newDiskImage);
setSucceeded(true);
return null;
});
}
}
protected Guid getDestinationStorageDomainId() {
return newDiskImage.getStorageIds() != null ? newDiskImage.getStorageIds().get(0)
: Guid.Empty;
}
@Override
protected boolean performImageVdsmOperation() {
setDestinationImageId(Guid.newGuid());
persistCommandIfNeeded();
newDiskImage = cloneDiskImage(getDestinationImageId());
newDiskImage.setStorageIds(new ArrayList<>(Arrays.asList(getDestinationStorageDomainId())));
setStoragePoolId(newDiskImage.getStoragePoolId() != null ? newDiskImage.getStoragePoolId()
: Guid.Empty);
getParameters().setStoragePoolId(getStoragePoolId());
// override volume type and volume format to sparse and cow according to
// storage team request
newDiskImage.setVolumeType(VolumeType.Sparse);
newDiskImage.setVolumeFormat(VolumeFormat.COW);
try {
Guid taskId = persistAsyncTaskPlaceHolder(getParameters().getParentCommand());
VDSReturnValue vdsReturnValue =
runVdsCommand(
VDSCommandType.CreateSnapshot,
new CreateSnapshotVDSCommandParameters(getStoragePoolId(),
getDestinationStorageDomainId(),
getImageGroupId(),
getImage().getImageId(),
getDiskImage().getSize(),
newDiskImage.getVolumeType(),
newDiskImage.getVolumeFormat(),
getDiskImage().getId(),
getDestinationImageId(),
""));
if (vdsReturnValue != null && vdsReturnValue.getSucceeded()) {
getParameters().setVdsmTaskIds(new ArrayList<>());
getParameters().getVdsmTaskIds().add(
createTask(taskId,
vdsReturnValue.getCreationInfo(),
getParameters().getParentCommand(),
VdcObjectType.Storage,
getParameters().getStorageDomainId(),
getParameters().getDestinationImageId()));
getReturnValue().getInternalVdsmTaskIdList().add(getParameters().getVdsmTaskIds().get(0));
// Shouldn't happen anymore:
if (getDestinationImageId().equals(Guid.Empty)) {
throw new RuntimeException();
}
return true;
}
} catch (Exception e) {
log.error("Failed creating snapshot from image id '{}'", getImage().getImageId());
CommandCoordinatorUtil.logAndFailTaskOfCommandWithEmptyVdsmId(getAsyncTaskId(),
"Create snapshot failed at VDSM. DB task ID is " + getAsyncTaskId());
throw new EngineException(EngineError.VolumeCreationError);
}
return false;
}
@Override
protected AsyncTaskType getTaskType() {
return AsyncTaskType.createVolume;
}
/**
* By default old image must be replaced by new one
*/
protected void processOldImageFromDb() {
getCompensationContext().snapshotEntity(getDiskImage().getImage());
getParameters().setOldLastModifiedValue(getDiskImage().getLastModified());
getDiskImage().setLastModified(new Date());
getDiskImage().setActive(false);
getDiskImage().setVolumeClassification(VolumeClassification.Snapshot);
imageDao.update(getDiskImage().getImage());
getCompensationContext().stateChanged();
}
@Override
protected void endWithFailure() {
revertTasks();
if (getDestinationDiskImage() != null
&& !vmDao.getVmsListForDisk(getDestinationDiskImage().getId(), false).isEmpty()) {
// Empty Guid, means new disk rather than snapshot, so no need to add a map to the db for new disk.
if (!getDestinationDiskImage().getParentId().equals(Guid.Empty)) {
if (!getDestinationDiskImage().getParentId().equals(getDestinationDiskImage().getImageTemplateId())) {
DiskImage previousSnapshot = diskImageDao.getSnapshotById(getDestinationDiskImage().getParentId());
previousSnapshot.setActive(true);
// If the old description of the snapshot got overriden, we should restore the previous description
if (getParameters().getOldLastModifiedValue() != null) {
previousSnapshot.setLastModified(getParameters().getOldLastModifiedValue());
}
imageDao.update(previousSnapshot.getImage());
}
}
}
super.endWithFailure();
}
}