package org.ovirt.engine.core.bll.snapshots;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.disk.image.BaseImagesCommand;
import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler;
import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.DestroyImageParameters;
import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
import org.ovirt.engine.core.common.action.RemoveSnapshotSingleDiskParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.businessentities.Snapshot;
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.Image;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.businessentities.storage.VolumeClassification;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters;
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.BaseDiskDao;
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.utils.ovf.OvfManager;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
public abstract class RemoveSnapshotSingleDiskCommandBase<T extends ImagesContainterParametersBase> extends BaseImagesCommand<T> {
@Inject
protected OvfManager ovfManager;
@Inject
private SnapshotDao snapshotDao;
@Inject
private ImageDao imageDao;
@Inject
private BaseDiskDao baseDiskDao;
@Inject
private DiskImageDao diskImageDao;
protected RemoveSnapshotSingleDiskCommandBase(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put(VdcObjectType.Disk.name().toLowerCase(), getDiskImage().getDiskAlias());
jobProperties.put("sourcesnapshot",
getSnapshotDescriptionById(getDiskImage().getVmSnapshotId()));
jobProperties.put("destinationsnapshot",
getSnapshotDescriptionById(getDestinationDiskImage().getVmSnapshotId()));
}
return jobProperties;
}
protected DiskImage getImageInfoFromVdsm(final DiskImage targetImage) {
try {
VDSReturnValue ret = runVdsCommand(
VDSCommandType.GetImageInfo,
new GetImageInfoVDSCommandParameters(targetImage.getStoragePoolId(),
targetImage.getStorageIds().get(0),
targetImage.getId(),
targetImage.getImageId()));
return (DiskImage) ret.getReturnValue();
} catch (EngineException e) {
log.warn("Failed to get info of volume '{}' using GetImageInfo", targetImage.getImageId(), e);
return null;
}
}
protected void updateDiskImageDynamic(final DiskImage imageFromVdsm, final DiskImage targetImage) {
// Update image's actual size in DB
if (imageFromVdsm != null) {
completeImageData(imageFromVdsm);
} else {
log.warn("Could not update DiskImage's size with ID '{}'",
targetImage.getImageId());
}
}
protected DestroyImageParameters buildDestroyImageParameters(Guid imageGroupId, List<Guid> imageList, VdcActionType actionType) {
DestroyImageParameters parameters = new DestroyImageParameters(
getVdsId(),
getVmId(),
getDiskImage().getStoragePoolId(),
getDiskImage().getStorageIds().get(0),
imageGroupId,
imageList,
getDiskImage().isWipeAfterDelete(),
false);
parameters.setParentCommand(actionType);
parameters.setParentParameters(getParameters());
return parameters;
}
/**
* Updates (but does not persist) the parameters.childCommands list to ensure the current
* child command is present. This is necessary in various entry points called externally
* (e.g. by endAction()), which can be called after a child command is started but before
* the main proceedCommandExecution() loop has persisted the updated child list.
*/
protected void syncChildCommandList(RemoveSnapshotSingleDiskParameters parameters) {
List<Guid> childCommandIds = CommandCoordinatorUtil.getChildCommandIds(getCommandId());
if (childCommandIds.size() != parameters.getChildCommands().size()) {
for (Guid id : childCommandIds) {
if (!parameters.getChildCommands().containsValue(id)) {
parameters.getChildCommands().put(parameters.getCommandStep(), id);
break;
}
}
}
}
protected Guid getCurrentChildId(RemoveSnapshotSingleDiskParameters parameters) {
return parameters.getChildCommands().get(parameters.getCommandStep());
}
protected DiskImage getActiveDiskImage() {
Guid snapshotId = snapshotDao.getId(getVmId(), Snapshot.SnapshotType.ACTIVE);
return diskImageDao.getDiskSnapshotForVmSnapshot(getDiskImage().getId(), snapshotId);
}
/**
* After merging the snapshots, update the image and snapshot records in the
* database to reflect the changes. This handles either forward or backwards
* merge (detected). It will either then remove the images, or mark them
* illegal (to handle the case where image deletion failed).
*
* @param removeImages Remove the images from the database, or if false, only
* mark them illegal
*/
protected void syncDbRecords(VmBlockJobType blockJobType, DiskImage imageFromVdsm, Set<Guid> imagesToUpdate, boolean removeImages) {
TransactionSupport.executeInNewTransaction(() -> {
// If deletion failed after a backwards merge, the snapshots' images need to be swapped
// as they would upon success. Instead of removing them, mark them illegal.
DiskImage baseImage = getDiskImage();
DiskImage topImage = getDestinationDiskImage();
// The vdsm merge verb may decide to perform a forward or backward merge.
if (topImage == null) {
log.info("No merge destination image, not updating image/snapshot association");
} else if (blockJobType == VmBlockJobType.PULL) {
handleForwardMerge(topImage, baseImage, imageFromVdsm);
} else {
handleBackwardMerge(topImage, baseImage, imageFromVdsm);
}
if (imagesToUpdate == null) {
log.error("Failed to update orphaned images in db: image list could not be retrieved");
return null;
}
for (Guid imageId : imagesToUpdate) {
if (removeImages) {
imageDao.remove(imageId);
} else {
// The (illegal && no-parent && no-children) status indicates an orphaned image.
Image image = imageDao.get(imageId);
image.setStatus(ImageStatus.ILLEGAL);
image.setParentId(Guid.Empty);
imageDao.update(image);
}
}
return null;
});
}
protected Pair<VdcActionType, DestroyImageParameters> buildDestroyCommand(VdcActionType actionToRun,
VdcActionType parentCommand,
List<Guid> images) {
return new Pair<>(actionToRun, buildDestroyImageParameters(getActiveDiskImage().getId(),
images, parentCommand));
}
private void handleForwardMerge(DiskImage topImage, DiskImage baseImage, DiskImage imageFromVdsm) {
// For forward merge, the volume format and type may change.
topImage.setVolumeFormat(baseImage.getVolumeFormat());
topImage.setVolumeType(baseImage.getVolumeType());
topImage.setParentId(baseImage.getParentId());
getDestinationDiskImage().setSize(baseImage.getSize());
getDestinationDiskImage().setActualSizeInBytes(getImageInfoFromVdsm(getDestinationDiskImage()).getActualSizeInBytes());
baseDiskDao.update(topImage);
imageDao.update(topImage.getImage());
updateDiskImageDynamic(imageFromVdsm, topImage);
updateVmConfigurationForImageChange(getDestinationDiskImage().getImage().getSnapshotId(),
getDestinationDiskImage().getImageId(), getDestinationDiskImage());
}
private void handleBackwardMerge(DiskImage topImage, DiskImage baseImage, DiskImage imageFromVdsm) {
// For backwards merge, the prior base image now has the data associated with the newer
// snapshot we want to keep. Re-associate this older image with the newer snapshot.
// The base snapshot is deleted if everything went well. In case it's not deleted, we
// hijack it to preserve a link to the broken image. This makes the image discoverable
// so that we can retry the deletion later, yet doesn't corrupt the VM image chain.
List<DiskImage> children = diskImageDao.getAllSnapshotsForParent(topImage.getImageId());
if (!children.isEmpty()) {
DiskImage childImage = children.get(0);
childImage.setParentId(baseImage.getImageId());
imageDao.update(childImage.getImage());
}
Image oldTopImage = topImage.getImage();
topImage.setImage(baseImage.getImage());
baseImage.setImage(oldTopImage);
Guid oldTopSnapshotId = topImage.getImage().getSnapshotId();
topImage.getImage().setSnapshotId(baseImage.getImage().getSnapshotId());
baseImage.getImage().setSnapshotId(oldTopSnapshotId);
boolean oldTopIsActive = topImage.getImage().isActive();
topImage.getImage().setActive(baseImage.getImage().isActive());
VolumeClassification baseImageVolumeClassification =
VolumeClassification.getVolumeClassificationByActiveFlag(baseImage.getImage().isActive());
topImage.getImage().setVolumeClassification(baseImageVolumeClassification);
baseImage.getImage().setActive(oldTopIsActive);
VolumeClassification oldTopVolumeClassification =
VolumeClassification.getVolumeClassificationByActiveFlag(oldTopIsActive);
topImage.getImage().setVolumeClassification(oldTopVolumeClassification);
topImage.setSize(baseImage.getSize());
topImage.setActualSizeInBytes(imageFromVdsm.getActualSizeInBytes());
topImage.setImageStatus(ImageStatus.OK);
baseDiskDao.update(topImage);
imageDao.update(topImage.getImage());
updateDiskImageDynamic(imageFromVdsm, topImage);
baseDiskDao.update(baseImage);
imageDao.update(baseImage.getImage());
updateVmConfigurationForImageChange(topImage.getImage().getSnapshotId(),
baseImage.getImageId(), topImage);
updateVmConfigurationForImageRemoval(baseImage.getImage().getSnapshotId(),
topImage.getImageId());
}
private void updateVmConfigurationForImageChange(final Guid snapshotId, final Guid oldImageId, final DiskImage newImage) {
try {
lockVmSnapshotsWithWait(getVm());
TransactionSupport.executeInNewTransaction(() -> {
Snapshot s = snapshotDao.get(snapshotId);
s = ImagesHandler.prepareSnapshotConfigWithAlternateImage(s, oldImageId, newImage, ovfManager);
snapshotDao.update(s);
return null;
});
} finally {
if (getSnapshotsEngineLock() != null) {
lockManager.releaseLock(getSnapshotsEngineLock());
}
}
}
private void updateVmConfigurationForImageRemoval(final Guid snapshotId, final Guid imageId) {
try {
lockVmSnapshotsWithWait(getVm());
TransactionSupport.executeInNewTransaction(() -> {
Snapshot s = snapshotDao.get(snapshotId);
s = ImagesHandler.prepareSnapshotConfigWithoutImageSingleImage(s, imageId, ovfManager);
snapshotDao.update(s);
return null;
});
} finally {
if (getSnapshotsEngineLock() != null) {
lockManager.releaseLock(getSnapshotsEngineLock());
}
}
}
@Override
protected void endWithFailure() {
// TODO: FILL! We should determine what to do in case of
// failure (is everything rolled-backed? rolled-forward?
// some and some?).
setSucceeded(true);
}
private String getSnapshotDescriptionById(Guid snapshotId) {
Snapshot snapshot = snapshotDao.get(snapshotId);
return snapshot != null ? snapshot.getDescription() : StringUtils.EMPTY;
}
}