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 static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_PLUGGED;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_SNAPABLE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.ConcurrentChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
import org.ovirt.engine.core.bll.UpdateVmCommand;
import org.ovirt.engine.core.bll.VmCommand;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.network.VmInterfaceManager;
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.storage.ovfstore.OvfHelper;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.storage.CinderDisksValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskSnapshotsValidator;
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.FeatureSupported;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.CreateCinderSnapshotParameters;
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.TryBackToAllSnapshotsOfVmParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.action.VmManagementParametersBase;
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.VmStatic;
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.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.compat.Version;
import org.ovirt.engine.core.dao.DiskDao;
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;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.utils.lock.LockManager;
import org.ovirt.engine.core.utils.ovf.OvfReaderException;
import org.ovirt.engine.core.utils.transaction.TransactionMethod;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
@NonTransactiveCommandAttribute(forceCompensation = true)
public class TryBackToAllSnapshotsOfVmCommand<T extends TryBackToAllSnapshotsOfVmParameters> extends VmCommand<T> {
private Snapshot cachedSnapshot;
private List<DiskImage> imagesToPreview;
@Inject
private LockManager lockManager;
@Inject
private OvfHelper ovfHelper;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private ImageDao imageDao;
@Inject
private DiskDao diskDao;
@Inject
private SnapshotDao snapshotDao;
/**
* Constructor for command creation when compensation is applied on startup
*/
public TryBackToAllSnapshotsOfVmCommand(Guid commandId) {
super(commandId);
}
public TryBackToAllSnapshotsOfVmCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
parameters.setEntityInfo(new EntityInfo(VdcObjectType.VM, getVmId()));
}
@Override
public void init() {
// No need to filter the images for partial preview as being done in the execute phase since the callback can
// also support no child commands, this should be changed once all commands will facilitate the CoCo
// infrastructure.
getParameters().setUseCinderCommandCallback(!DisksFilter.filterCinderDisks(getImagesToPreview()).isEmpty());
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Execution);
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
if (getSnapshotName() != null) {
jobProperties.put(VdcObjectType.Snapshot.name().toLowerCase(), getSnapshotName());
}
}
return jobProperties;
}
@Override
protected void endWithFailure() {
Snapshot previouslyActiveSnapshot =
snapshotDao.get(getVmId(), SnapshotType.PREVIEW, SnapshotStatus.LOCKED);
snapshotDao.remove(previouslyActiveSnapshot.getId());
snapshotDao.remove(snapshotDao.getId(getVmId(), SnapshotType.ACTIVE));
getSnapshotsManager().addActiveSnapshot(previouslyActiveSnapshot.getId(), getVm(),
previouslyActiveSnapshot.getMemoryVolume(), getCompensationContext());
super.endWithFailure();
}
@Override
protected void endSuccessfully() {
vmStaticDao.incrementDbGeneration(getVm().getId());
endActionOnDisks();
if (getVm() != null) {
vmHandler.unlockVm(getVm(), getCompensationContext());
restoreVmConfigFromSnapshot();
// disks and configuration is restored, let's set CCV if the snapshot originates in older Cluster version
if (!updateClusterCompatibilityVersionToOldCluster(false)) {
log.warn("Failed to set the Cluster Compatibility Version to the cluster version the snapshot originates from.");
}
} else {
setCommandShouldBeLogged(false);
log.warn("VM is null, not performing endAction");
}
setSucceeded(true);
}
private void restoreVmConfigFromSnapshot() {
snapshotDao.updateStatus(getParameters().getDstSnapshotId(), SnapshotStatus.IN_PREVIEW);
snapshotDao.updateStatus(snapshotDao.getId(getVm().getId(),
SnapshotType.PREVIEW,
SnapshotStatus.LOCKED),
SnapshotStatus.OK);
getSnapshotsManager().attempToRestoreVmConfigurationFromSnapshot(getVm(),
getDstSnapshot(),
snapshotDao.getId(getVm().getId(), SnapshotType.ACTIVE),
getImagesToPreview(),
getCompensationContext(),
getCurrentUser(),
new VmInterfaceManager(getMacPool()));
}
@Override
protected void executeVmCommand() {
final boolean restoreMemory = isRestoreMemory();
final Guid newActiveSnapshotId = Guid.newGuid();
final Snapshot snapshotToBePreviewed = getDstSnapshot();
final Snapshot previousActiveSnapshot = snapshotDao.get(getVmId(), SnapshotType.ACTIVE);
final Guid previousActiveSnapshotId = previousActiveSnapshot.getId();
final List<DiskImage> images = getImagesToPreview();
// Images list without those that are excluded from preview
final List<DiskImage> filteredImages = (List<DiskImage>) CollectionUtils.subtract(
images, getImagesExcludedFromPreview(images, previousActiveSnapshotId, newActiveSnapshotId));
final List<CinderDisk> cinderDisks = new ArrayList<>();
TransactionSupport.executeInNewTransaction(() -> {
getCompensationContext().snapshotEntity(previousActiveSnapshot);
snapshotDao.remove(previousActiveSnapshotId);
getSnapshotsManager().addSnapshot(previousActiveSnapshotId,
"Active VM before the preview",
SnapshotType.PREVIEW,
getVm(),
previousActiveSnapshot.getMemoryVolume(),
getCompensationContext());
getSnapshotsManager().addActiveSnapshot(newActiveSnapshotId,
getVm(),
restoreMemory ? snapshotToBePreviewed.getMemoryVolume() : StringUtils.EMPTY,
images,
getCompensationContext());
// if there are no images there's no reason to save the compensation data to DB as the update is
// being executed in the same transaction so we can restore the vm config and end the command.
if (!filteredImages.isEmpty()) {
getCompensationContext().stateChanged();
} else {
vmStaticDao.incrementDbGeneration(getVm().getId());
restoreVmConfigFromSnapshot();
}
return null;
});
if (!filteredImages.isEmpty()) {
vmHandler.lockVm(getVm().getDynamicData(), getCompensationContext());
freeLock();
TransactionSupport.executeInNewTransaction(new TransactionMethod<Void>() {
@Override
public Void runInTransaction() {
for (DiskImage image : filteredImages) {
if (image.getDiskStorageType() == DiskStorageType.CINDER) {
cinderDisks.add((CinderDisk)image);
continue;
}
VdcReturnValueBase vdcReturnValue =
runInternalActionWithTasksContext(VdcActionType.TryBackToSnapshot,
buildTryBackToSnapshotParameters(newActiveSnapshotId, image));
if (vdcReturnValue.getSucceeded()) {
getTaskIdList().addAll(vdcReturnValue.getInternalVdsmTaskIdList());
} else if (vdcReturnValue.getFault() != null) {
// if we have a fault, forward it to the user
throw new EngineException(vdcReturnValue.getFault().getError(),
vdcReturnValue.getFault().getMessage());
} else {
log.error("Cannot create snapshot");
throw new EngineException(EngineError.IRS_IMAGE_STATUS_ILLEGAL);
}
}
if (!cinderDisks.isEmpty() &&
!tryBackAllCinderDisks(cinderDisks, newActiveSnapshotId)) {
throw new EngineException(EngineError.CINDER_ERROR, "Failed to preview a snapshot!");
}
return null;
}
private ImagesContainterParametersBase buildTryBackToSnapshotParameters(
final Guid newActiveSnapshotId, DiskImage image) {
ImagesContainterParametersBase params = new ImagesContainterParametersBase(image.getImageId());
params.setParentCommand(VdcActionType.TryBackToAllSnapshotsOfVm);
params.setVmSnapshotId(newActiveSnapshotId);
params.setEntityInfo(getParameters().getEntityInfo());
params.setParentParameters(getParameters());
params.setQuotaId(image.getQuotaId());
return params;
}
});
} else {
// if there are no disks to restore, no compensation context is saved and the VM Configuration
// (including clusterCompatibilityVersionOrigin) is already restored at this point. Otherwise,
// if disks are being restored, the VM Configuration is restored later in endSuccessfully()
updateClusterCompatibilityVersionToOldCluster(true);
}
setSucceeded(true);
}
private boolean isRestoreMemory() {
return getParameters().isRestoreMemory() &&
FeatureSupported.isMemorySnapshotSupportedByArchitecture(
getVm().getClusterArch(), getVm().getCompatibilityVersion());
}
private boolean updateClusterCompatibilityVersionToOldCluster(boolean disableLock) {
Version oldClusterVersion = getVm().getClusterCompatibilityVersionOrigin();
if (isRestoreMemory() && getVm().getCustomCompatibilityVersion() == null &&
oldClusterVersion.less(getVm().getClusterCompatibilityVersion())) {
// the snapshot was taken before cluster version change, call the UpdateVmCommand
// vm_static of the getVm() is just updated by the previewed OVF config, so reload before UpdateVmCommand
VmStatic vmFromDb = vmStaticDao.get(getVmId());
return updateVm(vmFromDb, oldClusterVersion, disableLock);
}
return true;
}
private boolean updateVm(VmStatic vm, Version oldClusterVersion, boolean disableLock) {
VmManagementParametersBase updateParams = new VmManagementParametersBase(vm);
updateParams.setClusterLevelChangeFromVersion(oldClusterVersion);
CommandContext context;
if (disableLock) {
updateParams.setLockProperties(LockProperties.create(LockProperties.Scope.None));
context = cloneContextAndDetachFromParent();
} else {
// Wait for VM lock
EngineLock updateVmLock = createUpdateVmLock();
lockManager.acquireLockWait(updateVmLock); // will be released by UpdateVmCommand
context = ExecutionHandler.createInternalJobContext(updateVmLock);
}
VdcReturnValueBase result = runInternalAction(
VdcActionType.UpdateVm,
updateParams,
context);
if (!result.getSucceeded()) {
getReturnValue().setFault(result.getFault());
return false;
}
return true;
}
private EngineLock createUpdateVmLock() {
return new EngineLock(
UpdateVmCommand.getExclusiveLocksForUpdateVm(getVm()),
UpdateVmCommand.getSharedLocksForUpdateVm(getVm()));
}
protected boolean tryBackAllCinderDisks( List<CinderDisk> cinderDisks, Guid newSnapshotId) {
for (CinderDisk disk : cinderDisks) {
ImagesContainterParametersBase params = buildCinderChildCommandParameters(disk, newSnapshotId);
VdcReturnValueBase vdcReturnValueBase = runInternalAction(
VdcActionType.TryBackToCinderSnapshot,
params,
cloneContextAndDetachFromParent());
if (!vdcReturnValueBase.getSucceeded()) {
log.error("Error cloning Cinder disk for preview. '{}': {}", disk.getDiskAlias());
getReturnValue().setFault(vdcReturnValueBase.getFault());
return false;
}
}
return true;
}
private CreateCinderSnapshotParameters buildCinderChildCommandParameters(CinderDisk cinderDisk, Guid newSnapshotId) {
CreateCinderSnapshotParameters createParams = new CreateCinderSnapshotParameters(cinderDisk.getImageId());
createParams.setContainerId(cinderDisk.getId());
createParams.setStorageDomainId(cinderDisk.getStorageIds().get(0));
createParams.setDestinationImageId(cinderDisk.getImageId());
createParams.setVmSnapshotId(newSnapshotId);
createParams.setParentCommand(getActionType());
createParams.setParentParameters(getParameters());
return createParams;
}
private List<DiskImage> getImagesToPreview() {
if (imagesToPreview == null) {
imagesToPreview = getParameters().getDisks() != null ? getParameters().getDisks() :
diskImageDao.getAllSnapshotsForVmSnapshot(getDstSnapshot().getId());
// Filter out shareable/nonsnapable disks
List<CinderDisk> CinderImagesToPreview = DisksFilter.filterCinderDisks(imagesToPreview);
imagesToPreview = DisksFilter.filterImageDisks(imagesToPreview, ONLY_NOT_SHAREABLE, ONLY_SNAPABLE);
imagesToPreview.addAll(CinderImagesToPreview);
}
return imagesToPreview;
}
/**
* Returns the list of images that haven't been selected for preview (remain the images from current active VM).
*/
private List<DiskImage> getImagesExcludedFromPreview(List<DiskImage> images, Guid previousActiveSnapshotId, Guid newActiveSnapshotId) {
List<DiskImage> excludedImages = new ArrayList<>();
for (DiskImage image : images) {
if (image.getDiskStorageType().isInternal() && image.getVmSnapshotId().equals(previousActiveSnapshotId)) {
// Image is already active, hence only update snapshot ID.
imageDao.updateImageVmSnapshotId(image.getImageId(), newActiveSnapshotId);
excludedImages.add(image);
}
}
return excludedImages;
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
return getSucceeded() ? AuditLogType.USER_TRY_BACK_TO_SNAPSHOT
: AuditLogType.USER_FAILED_TRY_BACK_TO_SNAPSHOT;
case END_SUCCESS:
return getSucceeded() ? AuditLogType.USER_TRY_BACK_TO_SNAPSHOT_FINISH_SUCCESS
: AuditLogType.USER_TRY_BACK_TO_SNAPSHOT_FINISH_FAILURE;
default:
return AuditLogType.USER_TRY_BACK_TO_SNAPSHOT_FINISH_FAILURE;
}
}
@Override
protected boolean validate() {
if (Guid.Empty.equals(getParameters().getDstSnapshotId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_CORRUPTED_VM_SNAPSHOT_ID);
}
VmValidator vmValidator = new VmValidator(getVm());
if (!validate(vmValidator.isVmExists())
|| !validate(vmValidator.vmDown())
|| !validate(snapshotsValidator.snapshotExists(getVmId(), getParameters().getDstSnapshotId()))
|| !validate(snapshotsValidator.vmNotDuringSnapshot(getVmId()))
|| !validate(snapshotsValidator.vmNotInPreview(getVmId()))
|| !validate(snapshotsValidator.snapshotVmConfigurationBroken(getDstSnapshot(), getVmName()))) {
return false;
}
updateVmDisksFromDb();
List<DiskImage> diskImages =
DisksFilter.filterImageDisks(getVm().getDiskMap().values(), ONLY_NOT_SHAREABLE,
ONLY_SNAPABLE, ONLY_ACTIVE);
diskImages.addAll(DisksFilter.filterCinderDisks(getVm().getDiskMap().values(), ONLY_PLUGGED));
if (!diskImages.isEmpty()) {
if (!validate(new StoragePoolValidator(getStoragePool()).isUp())) {
return false;
}
DiskImagesValidator diskImagesValidator = new DiskImagesValidator(diskImages);
if (!validate(diskImagesValidator.diskImagesNotIllegal()) ||
!validate(diskImagesValidator.diskImagesNotLocked())) {
return false;
}
DiskImagesValidator diskImagesToPreviewValidator = new DiskImagesValidator(getImagesToPreview());
if (!validate(diskImagesToPreviewValidator.diskImagesNotIllegal()) ||
!validate(diskImagesToPreviewValidator.diskImagesNotLocked())) {
return false;
}
Set<Guid> storageIds = ImagesHandler.getAllStorageIdsForImageIds(diskImages);
MultipleStorageDomainsValidator storageValidator =
new MultipleStorageDomainsValidator(getVm().getStoragePoolId(), storageIds);
if (!validate(new StoragePoolValidator(getStoragePool()).isUp())
|| !validate(storageValidator.allDomainsExistAndActive())
|| !validate(storageValidator.allDomainsWithinThresholds())
|| !validateCinder()) {
return false;
}
}
DiskSnapshotsValidator diskSnapshotsValidator = new DiskSnapshotsValidator(getParameters().getDisks());
if (!validate(diskSnapshotsValidator.canDiskSnapshotsBePreviewed(getParameters().getDstSnapshotId()))) {
return false;
}
if (isRestoreMemory() && !validateMemoryTakenInSupportedVersion()) {
return false;
}
return true;
}
private boolean validateMemoryTakenInSupportedVersion() {
VM vmFromSnapshot = null;
try {
vmFromSnapshot = ovfHelper.readVmFromOvf(getDstSnapshot().getVmConfiguration());
} catch (OvfReaderException e) {
// should never happen since the OVF was created by us
log.error("Failed to parse a given ovf configuration: {}", e.getMessage());
return false;
}
Version originalClusterVersion = vmFromSnapshot.getClusterCompatibilityVersionOrigin();
if (Version.getLowest().greater(originalClusterVersion)) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_MEMORY_TOO_OLD,
String.format("$Cv %s", originalClusterVersion != null ? originalClusterVersion : "N/A"));
}
return true;
}
private boolean validateCinder() {
List<CinderDisk> cinderDisks = DisksFilter.filterCinderDisks(diskDao.getAllForVm(getVmId()));
if (!cinderDisks.isEmpty()) {
CinderDisksValidator cinderDisksValidator = getCinderDisksValidator(cinderDisks);
return validate(cinderDisksValidator.validateCinderDiskLimits());
}
return true;
}
protected CinderDisksValidator getCinderDisksValidator(List<CinderDisk> cinderDisks) {
return new CinderDisksValidator(cinderDisks);
}
private Snapshot getDstSnapshot() {
if (cachedSnapshot == null) {
cachedSnapshot = snapshotDao.get(getParameters().getDstSnapshotId());
}
return cachedSnapshot;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__PREVIEW);
addValidationMessage(EngineMessage.VAR__TYPE__SNAPSHOT);
}
protected void updateVmDisksFromDb() {
vmHandler.updateDisksFromDb(getVm());
}
@Override
protected VdcActionType getChildActionType() {
return VdcActionType.TryBackToSnapshot;
}
@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 String getSnapshotName() {
if (super.getSnapshotName() == null) {
final Snapshot snapshot = getDstSnapshot();
if (snapshot != null) {
setSnapshotName(snapshot.getDescription());
}
}
return super.getSnapshotName();
}
@Override
public CommandCallback getCallback() {
return getParameters().isUseCinderCommandCallback() ? new ConcurrentChildCommandsExecutionCallback() : null;
}
}