package org.ovirt.engine.core.bll.snapshots;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.commons.collections.CollectionUtils;
import org.ovirt.engine.core.bll.ConcurrentChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.VmCommand;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.interfaces.BackendInternal;
import org.ovirt.engine.core.bll.network.VmInterfaceManager;
import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaStorageConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaStorageDependent;
import org.ovirt.engine.core.bll.storage.connection.CINDERStorageHelper;
import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter;
import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler;
import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator;
import org.ovirt.engine.core.bll.validator.storage.MultipleStorageDomainsValidator;
import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.RemoveImageParameters;
import org.ovirt.engine.core.common.action.RestoreAllCinderSnapshotsParameters;
import org.ovirt.engine.core.common.action.RestoreAllSnapshotsParameters;
import org.ovirt.engine.core.common.action.RestoreFromSnapshotParameters;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.asynctasks.EntityInfo;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotStatus;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.storage.CinderDisk;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.utils.Pair;
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.SnapshotDao;
import org.ovirt.engine.core.dao.VmStaticDao;
/**
* Restores the given snapshot, including all the VM configuration that was stored in it.<br>
* Any obsolete snapshots will be deleted:<br>
* * If the restore is done to the {@link SnapshotType#STATELESS} snapshot then the stateless snapshot data is restored
* into the active snapshot, and the "old" active snapshot is deleted & replaced by the stateless snapshot.<br>
* * If the restore is done to a branch of a snapshot which is {@link SnapshotStatus#IN_PREVIEW}, then the other branch
* will be deleted (ie if the {@link SnapshotType#ACTIVE} snapshot is kept, then the branch of
* {@link SnapshotType#PREVIEW} is deleted up to the previewed snapshot, otherwise the active one is deleted).<br>
* <br>
* <b>Note:</b> It is <b>NOT POSSIBLE</b> to restore to a snapshot of any other type other than those stated above,
* since this command can only handle the aforementioned cases.
*/
public class RestoreAllSnapshotsCommand<T extends RestoreAllSnapshotsParameters> extends VmCommand<T> implements QuotaStorageDependent {
@Inject
private SnapshotVmConfigurationHelper snapshotVmConfigurationHelper;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private ImageDao imageDao;
private final Set<Guid> snapshotsToRemove = new HashSet<>();
private Snapshot snapshot;
List<DiskImage> imagesToRestore = new ArrayList<>();
List<DiskImage> imagesFromPreviewSnapshot = new ArrayList<>();
/**
* The snapshot which will be removed (the stateless/preview/active image).
*/
private Snapshot removedSnapshot;
/**
* Constructor for command creation when compensation is applied on startup
*/
public RestoreAllSnapshotsCommand(Guid commandId) {
super(commandId);
}
public RestoreAllSnapshotsCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
parameters.setEntityInfo(new EntityInfo(VdcObjectType.VM, getVmId()));
}
@Override
protected BackendInternal getBackend() {
return super.getBackend();
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Execution);
}
@Override
protected void executeVmCommand() {
if (!getImagesList().isEmpty()) {
lockVmWithCompensationIfNeeded();
if (!isInternalExecution()) {
freeLock();
}
}
restoreSnapshotAndRemoveObsoleteSnapshots(getSnapshot());
boolean succeeded = true;
List<CinderDisk> cinderDisksToRestore = new ArrayList<>();
for (DiskImage image : imagesToRestore) {
if (image.getImageStatus() != ImageStatus.ILLEGAL) {
if (image.getDiskStorageType() == DiskStorageType.CINDER) {
cinderDisksToRestore.add((CinderDisk) image);
continue;
}
ImagesContainterParametersBase params = new RestoreFromSnapshotParameters(image.getImageId(),
getVmId(), getSnapshot(), removedSnapshot.getId());
VdcReturnValueBase returnValue = runAsyncTask(VdcActionType.RestoreFromSnapshot, params);
// Save the first fault
if (succeeded && !returnValue.getSucceeded()) {
succeeded = false;
getReturnValue().setFault(returnValue.getFault());
}
}
}
List<CinderDisk> cinderVolumesToRemove = new ArrayList<>();
List<CinderDisk> cinderDisksToRemove = new ArrayList<>();
removeUnusedImages(cinderVolumesToRemove);
if (getSnapshot().getType() == SnapshotType.REGULAR) {
snapshotsToRemove.addAll(findSnapshotsWithOnlyIllegalDisks());
setNewerVmConfigurationsAsBroken();
}
removeSnapshotsFromDB();
if (!getTaskIdList().isEmpty() || !cinderDisksToRestore.isEmpty() || !cinderVolumesToRemove.isEmpty()) {
deleteOrphanedImages(cinderDisksToRemove);
if (!restoreCinderDisks(removedSnapshot.getId(),
cinderDisksToRestore,
cinderDisksToRemove,
cinderVolumesToRemove)) {
log.error("Error to restore Cinder volumes snapshots");
}
} else {
vmStaticDao.incrementDbGeneration(getVm().getId());
snapshotDao.updateStatus(getSnapshot().getId(), SnapshotStatus.OK);
unlockVm();
}
setSucceeded(succeeded);
}
protected boolean restoreAllCinderDisks(List<CinderDisk> cinderDisksToRestore,
List<CinderDisk> cinderDisksToRemove,
List<CinderDisk> cinderVolumesToRemove,
Guid removedSnapshotId) {
Future<VdcReturnValueBase> future = CommandCoordinatorUtil.executeAsyncCommand(
VdcActionType.RestoreAllCinderSnapshots,
buildCinderChildCommandParameters(cinderDisksToRestore,
cinderDisksToRemove,
cinderVolumesToRemove,
removedSnapshotId),
cloneContextAndDetachFromParent());
try {
VdcReturnValueBase vdcReturnValueBase = future.get();
if (!vdcReturnValueBase.getSucceeded()) {
getReturnValue().setFault(vdcReturnValueBase.getFault());
log.error("Error while restoring Cinder snapshot");
return false;
}
} catch (InterruptedException | ExecutionException e) {
log.error("Error deleting Cinder volumes for restore snapshot", e);
return false;
}
return true;
}
/**
* Returns the initial Cinder volume to delete all the Cinder volumes from. The restore process of the Cinder volume
* should use this volume to get all its descendants and remove them all. The initial volume is chosen by taking the
* previewed snapshot, fetch the parent volume, and retun the other volume.
*
* @param cinderVolume
* - The cinder volume we fetch the initial volume from (Most of the time it will be the active volume) -
* @return - The initial volume of the Cinder disk to delete from.
*/
private CinderDisk getInitialCinderVolumeToDelete(DiskImage cinderVolume) {
List<DiskImage> snapshotsForParent = diskImageDao.getAllSnapshotsForParent(cinderVolume.getParentId());
Optional<DiskImage> cinderVolumeToRemove = snapshotsForParent.stream().filter(snapshot ->
!snapshot.getImageId().equals(cinderVolume.getImageId())).map(Optional::ofNullable).findFirst().orElse(null);
return cinderVolumeToRemove != null ? (CinderDisk) cinderVolumeToRemove.get() : null;
}
private RestoreAllCinderSnapshotsParameters buildCinderChildCommandParameters(List<CinderDisk> cinderDisksToRestore,
List<CinderDisk> cinderDisksToRemove,
List<CinderDisk> cinderVolumesToRemove,
Guid removedSnapshotId) {
RestoreAllCinderSnapshotsParameters restoreParams =
new RestoreAllCinderSnapshotsParameters(getVmId(),
cinderDisksToRestore,
cinderDisksToRemove,
cinderVolumesToRemove);
restoreParams.setRemovedSnapshotId(removedSnapshotId);
restoreParams.setSnapshot(getSnapshot());
restoreParams.setParentHasTasks(!getReturnValue().getVdsmTaskIdList().isEmpty());
restoreParams.setParentCommand(getActionType());
restoreParams.setParentParameters(getParameters());
restoreParams.setEndProcedure(EndProcedure.COMMAND_MANAGED);
return withRootCommandInfo(restoreParams);
}
private Snapshot getSnapshot() {
if (snapshot == null) {
switch (getParameters().getSnapshotAction()) {
case UNDO:
snapshot = snapshotDao.get(getVmId(), SnapshotType.PREVIEW);
break;
case COMMIT:
snapshot = snapshotDao.get(getVmId(), SnapshotStatus.IN_PREVIEW);
break;
case RESTORE_STATELESS:
snapshot = snapshotDao.get(getVmId(), SnapshotType.STATELESS);
break;
default:
log.error("The Snapshot Action '{}' is not valid", getParameters().getSnapshotAction());
}
// We initialize the snapshotId in the parameters so we can use it in the endVmCommand
// to unlock the snapshot, after the task that creates the snapshot finishes.
if (snapshot != null) {
getParameters().setSnapshotId(snapshot.getId());
}
}
return snapshot;
}
protected void removeSnapshotsFromDB() {
for (Guid snapshotId : snapshotsToRemove) {
Snapshot snap = snapshotDao.get(snapshotId);
// Cinder volumes might not have correlated snapshot.
if (snap != null) {
String memoryVolume = snapshotDao.get(snapshotId).getMemoryVolume();
if (!memoryVolume.isEmpty() &&
snapshotDao.getNumOfSnapshotsByMemory(memoryVolume) == 1) {
boolean succeed = removeMemoryDisks(memoryVolume);
if (!succeed) {
log.error("Failed to remove memory '{}' of snapshot '{}'",
memoryVolume, snapshotId);
}
}
snapshotDao.remove(snapshotId);
}
}
}
private boolean isSnapshotEligibleToBeDeleted(Snapshot candidateSnapshotToRemove) {
return candidateSnapshotToRemove != null
&& (candidateSnapshotToRemove.getType() != SnapshotType.REGULAR ||
candidateSnapshotToRemove.getCreationDate().getTime() > removedSnapshot.getCreationDate().getTime());
}
protected void deleteOrphanedImages(List<CinderDisk> cinderDisksToRemove) {
VdcReturnValueBase returnValue;
boolean noImagesRemovedYet = getTaskIdList().isEmpty();
Set<Guid> deletedDisksIds = new HashSet<>();
for (DiskImage image : diskImageDao.getImagesWithNoDisk(getVm().getId())) {
if (!deletedDisksIds.contains(image.getId())) {
deletedDisksIds.add(image.getId());
if (image.getDiskStorageType() == DiskStorageType.CINDER) {
cinderDisksToRemove.add((CinderDisk) image);
continue;
}
returnValue = runAsyncTask(VdcActionType.RemoveImage,
new RemoveImageParameters(image.getImageId()));
if (!returnValue.getSucceeded() && noImagesRemovedYet) {
setSucceeded(false);
getReturnValue().setFault(returnValue.getFault());
return;
}
noImagesRemovedYet = false;
}
}
}
private boolean restoreCinderDisks(Guid removedSnapshotId, List<CinderDisk> cinderDisksToRestore,
List<CinderDisk> cinderDisksToRemove,
List<CinderDisk> cinderVolumesToRemove) {
if (!cinderDisksToRestore.isEmpty() || !cinderDisksToRemove.isEmpty() || !cinderVolumesToRemove.isEmpty()) {
return restoreAllCinderDisks(cinderDisksToRestore,
cinderDisksToRemove,
cinderVolumesToRemove,
removedSnapshotId);
}
return true;
}
private void removeUnusedImages(List<CinderDisk> cinderVolumesToRemove) {
Set<Guid> imageIdsUsedByActiveSnapshot = new HashSet<>();
for (DiskImage diskImage : getImagesList()) {
imageIdsUsedByActiveSnapshot.add(diskImage.getId());
}
List<DiskImage> imagesToRemove = new ArrayList<>();
for (Guid snapshotToRemove : snapshotsToRemove) {
List<DiskImage> snapshotDiskImages = diskImageDao.getAllSnapshotsForVmSnapshot(snapshotToRemove);
imagesToRemove.addAll(snapshotDiskImages);
}
Set<Guid> removeInProcessImageIds = new HashSet<>();
for (DiskImage diskImage : imagesToRemove) {
if (imageIdsUsedByActiveSnapshot.contains(diskImage.getId()) ||
removeInProcessImageIds.contains(diskImage.getId())) {
continue;
}
List<DiskImage> diskImagesFromPreviewSnap = imagesFromPreviewSnapshot.stream().filter(diskImageFromPreview ->
diskImageFromPreview.getImageId().equals(diskImage.getImageId())).collect(Collectors.toList());
if (!diskImagesFromPreviewSnap.isEmpty() &&
diskImagesFromPreviewSnap.get(0).getDiskStorageType() == DiskStorageType.CINDER) {
cinderVolumesToRemove.add((CinderDisk) diskImagesFromPreviewSnap.get(0));
continue;
}
VdcReturnValueBase retValue = runAsyncTask(VdcActionType.RemoveImage,
new RemoveImageParameters(diskImage.getImageId()));
if (retValue.getSucceeded()) {
removeInProcessImageIds.add(diskImage.getImageId());
} else {
log.error("Failed to remove image '{}'", diskImage.getImageId());
}
}
}
/**
* Run the given command as async task, which includes these steps:
* <ul>
* <li>Add parent info to task parameters.</li>
* <li>Run with current command's {@link org.ovirt.engine.core.bll.job.ExecutionContext}.</li>
* <li>Add son parameters to saved image parameters.</li>
* <li>Add son task IDs to list of task IDs.</li>
* </ul>
*
* @param taskType
* The type of the command to run as async task.
* @param params
* The command parameters.
* @return The return value from the task.
*/
private VdcReturnValueBase runAsyncTask(VdcActionType taskType, ImagesContainterParametersBase params) {
VdcReturnValueBase returnValue;
params.setEntityInfo(getParameters().getEntityInfo());
params.setParentCommand(getActionType());
params.setParentParameters(getParameters());
params.setCommandType(taskType);
returnValue = runInternalActionWithTasksContext(
taskType,
params);
getTaskIdList().addAll(returnValue.getInternalVdsmTaskIdList());
return returnValue;
}
/**
* Restore the snapshot - if it is not the active snapshot, then the VM configuration will be restored.<br>
* Additionally, remove all obsolete snapshots (The one after stateless, or the preview chain which was not chosen).
*/
protected void restoreSnapshotAndRemoveObsoleteSnapshots(Snapshot targetSnapshot) {
Guid activeSnapshotId = snapshotDao.getId(getVmId(), SnapshotType.ACTIVE);
List<DiskImage> imagesFromActiveSnapshot = diskImageDao.getAllSnapshotsForVmSnapshot(activeSnapshotId);
Snapshot previewedSnapshot = snapshotDao.get(getVmId(), SnapshotType.PREVIEW);
if (previewedSnapshot != null) {
VM vmFromConf = snapshotVmConfigurationHelper.getVmFromConfiguration(
previewedSnapshot.getVmConfiguration(), previewedSnapshot.getVmId(), previewedSnapshot.getId());
List<DiskImage> previewedImagesFromDB = diskImageDao.getAllSnapshotsForVmSnapshot(previewedSnapshot.getId());
imagesFromPreviewSnapshot.addAll(ImagesHandler.imagesIntersection(vmFromConf.getImages(), previewedImagesFromDB));
}
List<DiskImage> intersection = ImagesHandler.imagesIntersection(imagesFromActiveSnapshot, imagesFromPreviewSnapshot);
switch (targetSnapshot.getType()) {
case PREVIEW:
snapshotDao.updateStatus(
snapshotDao.getId(getVmId(), SnapshotType.REGULAR, SnapshotStatus.IN_PREVIEW),
SnapshotStatus.OK);
getParameters().setImages((List<DiskImage>) CollectionUtils.union(imagesFromPreviewSnapshot, intersection));
imagesFromPreviewSnapshot.forEach(image -> {
if (image.getDiskStorageType() != DiskStorageType.CINDER) {
imagesToRestore.add(image);
} else {
List<DiskImage> cinderDiskFromPreviewSnapshot = intersection.stream().filter(diskImage->
diskImage.getId().equals(image.getId())).collect(Collectors.toList());
if (!cinderDiskFromPreviewSnapshot.isEmpty()) {
imagesToRestore.add(cinderDiskFromPreviewSnapshot.get(0));
}
}
});
updateSnapshotIdForSkipRestoreImages(
ImagesHandler.imagesSubtract(imagesFromActiveSnapshot, imagesToRestore), targetSnapshot.getId());
restoreConfiguration(targetSnapshot);
break;
case STATELESS:
imagesToRestore = getParameters().getImages();
restoreConfiguration(targetSnapshot);
break;
case REGULAR:
prepareToDeletePreviewBranch(imagesFromActiveSnapshot);
// Set the active snapshot's images as target images for restore, because they are what we keep.
getParameters().setImages(imagesFromActiveSnapshot);
imagesFromActiveSnapshot.forEach(image -> {
List<DiskImage> cinderDiskFromPreviewSnapshot = imagesFromPreviewSnapshot.stream().filter(diskImage->
diskImage.getId().equals(image.getId())).collect(Collectors.toList());
if (!cinderDiskFromPreviewSnapshot.isEmpty()) {
if (image.getDiskStorageType() == DiskStorageType.IMAGE) {
imagesToRestore.add(image);
} else if (image.getDiskStorageType() == DiskStorageType.CINDER){
CinderDisk cinderVolume = getInitialCinderVolumeToDelete(image);
if (cinderVolume != null) {
imagesToRestore.add(cinderVolume);
}
}
}
});
updateSnapshotIdForSkipRestoreImages(
ImagesHandler.imagesSubtract(imagesFromActiveSnapshot, imagesToRestore), activeSnapshotId);
break;
default:
throw new EngineException(EngineError.ENGINE, "No support for restoring to snapshot type: "
+ targetSnapshot.getType());
}
}
private void updateSnapshotIdForSkipRestoreImages(List<DiskImage> skipRestoreImages, Guid snapshotId) {
for (DiskImage image : skipRestoreImages) {
imageDao.updateImageVmSnapshotId(image.getImageId(), snapshotId);
}
}
/**
* Prepare to remove the active snapshot & restore the given snapshot to be the active one, including the
* configuration.
*
* @param targetSnapshot
* The snapshot to restore to.
*/
private void restoreConfiguration(Snapshot targetSnapshot) {
removedSnapshot = snapshotDao.get(getVmId(), SnapshotType.ACTIVE);
snapshotsToRemove.add(removedSnapshot.getId());
getSnapshotsManager().removeAllIllegalDisks(removedSnapshot.getId(), getVmId());
getSnapshotsManager().attempToRestoreVmConfigurationFromSnapshot(getVm(),
targetSnapshot,
targetSnapshot.getId(),
null,
getCompensationContext(),
getCurrentUser(),
new VmInterfaceManager(getMacPool()));
snapshotDao.remove(targetSnapshot.getId());
// add active snapshot with status locked, so that other commands that depend on the VM's snapshots won't run in parallel
getSnapshotsManager().addActiveSnapshot(targetSnapshot.getId(),
getVm(),
SnapshotStatus.LOCKED,
targetSnapshot.getMemoryVolume(),
getCompensationContext());
}
/**
* All snapshots who derive from the snapshot which is {@link SnapshotStatus#IN_PREVIEW}, up to it (excluding), will
* be queued for deletion.<br>
* The traversal between snapshots is done according to the {@link DiskImage} level.
*/
protected void prepareToDeletePreviewBranch(List<DiskImage> imagesFromActiveSnapshot) {
removedSnapshot = snapshotDao.get(getVmId(), SnapshotType.PREVIEW);
Guid previewedSnapshotId =
snapshotDao.getId(getVmId(), SnapshotType.REGULAR, SnapshotStatus.IN_PREVIEW);
snapshotDao.updateStatus(previewedSnapshotId, SnapshotStatus.OK);
snapshotsToRemove.add(removedSnapshot.getId());
addRedundantCinderSnapshots(previewedSnapshotId, imagesFromActiveSnapshot);
}
private void addRedundantCinderSnapshots(Guid previewedSnapshotId, List<DiskImage> imagesFromActiveSnapshot) {
List<CinderDisk> cinderImagesForPreviewedSnapshot =
DisksFilter.filterCinderDisks(diskImageDao.getAllSnapshotsForVmSnapshot(previewedSnapshotId));
Set<Guid> criticalSnapshotsChain = getCriticalSnapshotsChain(imagesFromActiveSnapshot, cinderImagesForPreviewedSnapshot);
for (DiskImage image : cinderImagesForPreviewedSnapshot) {
List<Guid> redundantSnapshotIdsToDelete = CINDERStorageHelper.getRedundantVolumesToDeleteAfterCommitSnapshot(
image.getId(), criticalSnapshotsChain);
snapshotsToRemove.addAll(redundantSnapshotIdsToDelete.stream()
.filter(snapIdToDelete -> isSnapshotEligibleToBeDeleted(snapshotDao.get(snapIdToDelete)))
.collect(Collectors.toList()));
}
}
private Set<Guid> getCriticalSnapshotsChain(List<DiskImage> imagesFromActiveSnapshot, List<CinderDisk> cinderImagesForPreviewedSnapshot) {
Set<Guid> criticalSnapshotsChain = new HashSet<>();
for (DiskImage image : cinderImagesForPreviewedSnapshot) {
List<DiskImage> cinderDiskFromSnapshot = imagesFromActiveSnapshot.stream().filter(diskImage->
diskImage.getId().equals(image.getId())).collect(Collectors.toList());
for (DiskImage diskImage : diskImageDao.getAllSnapshotsForLeaf(cinderDiskFromSnapshot.get(0).getImageId())) {
criticalSnapshotsChain.add(diskImage.getVmSnapshotId());
}
}
return criticalSnapshotsChain;
}
private Set<Guid> findSnapshotsWithOnlyIllegalDisks() {
List<Snapshot> newerSnapshots = getNewerSnapshots(snapshot);
Set<Guid> snapshotsToRemove = new HashSet<>();
newerSnapshots.forEach(snapshot -> {
VM vm = snapshotVmConfigurationHelper.getVmFromConfiguration(
snapshot.getVmConfiguration(), snapshot.getVmId(), snapshot.getId());
if (vm != null) {
boolean shouldRemove = vm.getImages().isEmpty() || vm.getImages().stream().allMatch(
diskImage -> diskImage.getImageStatus() == ImageStatus.ILLEGAL);
if (shouldRemove) {
snapshotsToRemove.add(snapshot.getId());
}
}
});
return snapshotsToRemove;
}
private void setNewerVmConfigurationsAsBroken() {
List<Snapshot> newerSnapshots = getNewerSnapshots(snapshot);
newerSnapshots.forEach(newerSnapshot -> {
newerSnapshot.setVmConfigurationBroken(true);
snapshotDao.update(newerSnapshot);
});
}
private List<Snapshot> getNewerSnapshots(Snapshot snapshot) {
return snapshotDao.getAllWithConfiguration(getVmId()).stream().filter(
snapshotFromDao ->
snapshotFromDao.getType() == SnapshotType.REGULAR &&
snapshotFromDao.getCreationDate().getTime() > snapshot.getCreationDate().getTime())
.collect(Collectors.toList());
}
@Override
protected VdcActionType getChildActionType() {
return VdcActionType.RestoreFromSnapshot;
}
private List<DiskImage> getImagesList() {
if (getParameters().getImages() == null && !getSnapshot().getId().equals(Guid.Empty)) {
getParameters().setImages(diskImageDao.getAllSnapshotsForVmSnapshot(getSnapshot().getId()));
}
return getParameters().getImages();
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
return getSucceeded() ? AuditLogType.USER_RESTORE_FROM_SNAPSHOT_START
: AuditLogType.USER_FAILED_RESTORE_FROM_SNAPSHOT;
default:
return AuditLogType.USER_RESTORE_FROM_SNAPSHOT_FINISH_SUCCESS;
}
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
if (getSnapshot() != null) {
jobProperties.put(VdcObjectType.Snapshot.name().toLowerCase(), snapshot.getDescription());
}
}
return jobProperties;
}
@Override
protected boolean validate() {
if (getVm() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND);
}
if (!canRunActionOnNonManagedVm()) {
return false;
}
if (!validate(snapshotsValidator.snapshotExists(getSnapshot()))
|| !validate(snapshotsValidator.snapshotExists(getVmId(), getSnapshot().getId())) ||
!validate(new StoragePoolValidator(getStoragePool()).isUp())) {
return false;
}
if (Guid.Empty.equals(getSnapshot().getId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_CORRUPTED_VM_SNAPSHOT_ID);
}
VmValidator vmValidator = createVmValidator(getVm());
MultipleStorageDomainsValidator storageValidator = createStorageDomainValidator();
if (!validate(storageValidator.allDomainsExistAndActive()) ||
!performImagesChecks() ||
!validate(vmValidator.vmDown()) ||
// if the user choose to commit a snapshot the vm can't have disk snapshots attached to other vms.
getSnapshot().getType() == SnapshotType.REGULAR && !validate(vmValidator.vmNotHavingDeviceSnapshotsAttachedToOtherVms(false))) {
return false;
}
if (getSnapshot().getType() == SnapshotType.REGULAR
&& getSnapshot().getStatus() != SnapshotStatus.IN_PREVIEW) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_SNAPSHOT_NOT_IN_PREVIEW);
}
return true;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__REVERT_TO);
addValidationMessage(EngineMessage.VAR__TYPE__SNAPSHOT);
}
protected VmValidator createVmValidator(VM vm) {
return new VmValidator(vm);
}
protected MultipleStorageDomainsValidator createStorageDomainValidator() {
Set<Guid> storageIds = ImagesHandler.getAllStorageIdsForImageIds(getImagesList());
return new MultipleStorageDomainsValidator(getStoragePoolId(), storageIds);
}
protected boolean performImagesChecks() {
List<DiskImage> diskImagesToCheck =
DisksFilter.filterImageDisks(getImagesList(), ONLY_NOT_SHAREABLE, ONLY_ACTIVE);
DiskImagesValidator diskImagesValidator = new DiskImagesValidator(diskImagesToCheck);
return validate(diskImagesValidator.diskImagesNotLocked());
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
return Collections.singletonMap(getVmId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
@Override
public void addQuotaPermissionSubject(List<PermissionSubject> quotaPermissionList) {
//
}
@Override
public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() {
List<QuotaConsumptionParameter> list = new ArrayList<>();
List<DiskImage> disks = getImagesList();
if (disks != null && !disks.isEmpty()) {
// TODO: need to be fixed. sp id should be available
setStoragePoolId(disks.get(0).getStoragePoolId());
for (DiskImage image : disks) {
if (!image.getImage().isActive() && image.getQuotaId() != null
&& !Guid.Empty.equals(image.getQuotaId())) {
list.add(new QuotaStorageConsumptionParameter(image.getQuotaId(), null,
QuotaConsumptionParameter.QuotaAction.RELEASE,
image.getStorageIds().get(0),
image.getActualSize()));
}
}
}
return list;
}
@Override
protected void endSuccessfully() {
unlockSnapshot(getParameters().getSnapshotId());
super.endVmCommand();
}
@Override
protected void endWithFailure() {
super.endVmCommand();
}
@Override
public CommandCallback getCallback() {
return new ConcurrentChildCommandsExecutionCallback();
}
}