package org.ovirt.engine.core.bll.storage.disk.image;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.context.CompensationContext;
import org.ovirt.engine.core.bll.storage.domain.StorageDomainCommandBase;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.action.ImagesActionsParametersBase;
import org.ovirt.engine.core.common.action.ImagesContainterParametersBase;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatic;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskImageDynamic;
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.ImageStorageDomainMap;
import org.ovirt.engine.core.common.businessentities.storage.QcowCompat;
import org.ovirt.engine.core.common.businessentities.storage.QemuImageInfo;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
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.common.vdscommands.GetImageInfoVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
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.DiskImageDynamicDao;
import org.ovirt.engine.core.dao.ImageDao;
import org.ovirt.engine.core.dao.ImageStorageDomainMapDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.utils.lock.EngineLock;
/**
* Base class for all image handling commands
*/
public abstract class BaseImagesCommand<T extends ImagesActionsParametersBase> extends StorageDomainCommandBase<T> {
@Inject
private SnapshotDao snapshotDao;
@Inject
private ImageDao imageDao;
@Inject
private DiskImageDynamicDao diskImageDynamicDao;
@Inject
private ImageStorageDomainMapDao imageStorageDomainMapDao;
@Inject
private BaseDiskDao baseDiskDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDao vmDao;
private DiskImage destinationImage;
private DiskImage image;
private Guid imageId = Guid.Empty;
private EngineLock snapshotsEngineLock;
protected BaseImagesCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
imageId = parameters.getImageId();
}
@Override
public void init() {
super.init();
if (getParameters() instanceof ImagesContainterParametersBase) {
initContainerDetails((ImagesContainterParametersBase) getParameters());
}
}
protected BaseImagesCommand(Guid commandId) {
super(commandId);
}
private void initContainerDetails(ImagesContainterParametersBase parameters) {
super.setVmId(parameters.getContainerId());
if (parameters.getStoragePoolId() != null && !Guid.Empty.equals(parameters.getStoragePoolId())) {
setStoragePoolId(parameters.getStoragePoolId());
} else if (getDiskImage() != null && getDiskImage().getStoragePoolId() != null) {
setStoragePoolId(getDiskImage().getStoragePoolId());
}
}
protected DiskImage getImage() {
if (image == null) {
image = diskImageDao.get(getImageId());
if (image == null) {
image = diskImageDao.getSnapshotById(getImageId());
}
}
return image;
}
protected void setImage(DiskImage image) {
this.image = image;
}
protected Guid getImageId() {
return imageId;
}
protected void setImageId(Guid imageId) {
this.imageId = imageId;
}
private DiskImage diskImage;
protected DiskImage getDiskImage() {
if (diskImage == null) {
diskImage = getImage();
}
return diskImage;
}
protected void setDiskImage(DiskImage value) {
diskImage = value;
}
private Guid destinationImageId = Guid.Empty;
protected Guid getDestinationImageId() {
return getParameters() != null ? getParameters().getDestinationImageId() : destinationImageId;
}
protected void setDestinationImageId(Guid value) {
if (getParameters() != null) {
getParameters().setDestinationImageId(value);
} else {
destinationImageId = value;
}
}
protected boolean isDataOperationsBySpm(StorageDomainStatic targetSd) {
return !isDataOperationsByHSM(targetSd);
}
protected boolean isDataOperationsByHSM(StorageDomainStatic targetSd) {
return FeatureSupported.dataOperationsByHSM(getStoragePool().getCompatibilityVersion()) &&
targetSd.getStorageDomainType().isDataDomain();
}
protected boolean isDataOperationsByHSM() {
return isDataOperationsByHSM(getStorageDomain().getStorageStaticData());
}
protected boolean isDataOperationsBySpm() {
return isDataOperationsBySpm(getStorageDomain().getStorageStaticData());
}
protected boolean performImageVdsmOperation() {
throw new UnsupportedOperationException();
}
protected DiskImage getDestinationDiskImage() {
if (destinationImage == null) {
destinationImage = diskImageDao.get(getDestinationImageId());
if (destinationImage == null) {
destinationImage = diskImageDao.getSnapshotById(getDestinationImageId());
}
}
return destinationImage;
}
private Guid imageGroupId = Guid.Empty;
protected Guid getImageGroupId() {
if (imageGroupId.equals(Guid.Empty)) {
imageGroupId = getDiskImage().getId() != null ? getDiskImage().getId()
: Guid.Empty;
}
return imageGroupId;
}
protected void setImageGroupId(Guid value) {
imageGroupId = value;
}
protected EngineLock getSnapshotsEngineLock() {
return snapshotsEngineLock;
}
/**
* Find the image for the same drive by the snapshot type:<br>
* The image is the image from the snapshot of the given type, which represents the same drive.
* @param snapshotType
* The snapshot type for which the other image should exist.
*
* @return The ID of the image for the same drive, or null if none found.
*/
protected Guid findImageForSameDrive(SnapshotType snapshotType) {
return findImageForSameDrive(snapshotDao
.getId(vmDao.getVmsListForDisk(getImage().getId(), false).get(0).getId(), snapshotType));
}
/**
* Update the old image that represents the disk of the command's image to be in the given active state.
*
* @param snapshotType
* The type of snapshot to look for the same image in.
* @param active
* The active state.
*/
protected void updateOldImageAsActive(SnapshotType snapshotType, boolean active) {
Guid oldImageId = findImageForSameDrive(snapshotType);
if (oldImageId == null) {
log.error("Can't find image to update to active '{}', snapshot type '{}', original image id '{}'",
active,
snapshotType,
getImageId());
return;
}
DiskImage oldImage = diskImageDao.getSnapshotById(oldImageId);
oldImage.setActive(active);
imageDao.update(oldImage.getImage());
}
/**
* Find the image for the same drive by the snapshot ID:<br>
* The image is the image from the given snapshot, which represents the same drive.
*
* @param snapshotId
* The snapshot ID for which the other image should exist.
*
* @return The ID of the image for the same drive, or null if none found.
*/
protected Guid findImageForSameDrive(Guid snapshotId) {
List<DiskImage> imagesFromSnapshot = diskImageDao.getAllSnapshotsForVmSnapshot(snapshotId);
for (DiskImage diskImage : imagesFromSnapshot) {
if (getDiskImage().getId().equals(diskImage.getId())) {
return diskImage.getImageId();
}
}
return null;
}
/**
* Creates a copy of the source disk image ('DiskImage').
* @param newImageGuid
* the image id of the cloned disk image.
* @return the cloned disk image. Note that the cloned image's status is 'Locked'.
*/
protected DiskImage cloneDiskImage(Guid newImageGuid) {
return cloneDiskImage(newImageGuid, getDiskImage());
}
/**
* Creates a copy of the source disk image
*
* @param newImageGuid
* the image id of the cloned disk image.
* @param srcDiskImage
* the disk image to copy from
* @return the cloned disk image. Note that the cloned image's status is 'Locked'.
*/
protected DiskImage cloneDiskImage(Guid newImageGuid, DiskImage srcDiskImage) {
DiskImage retDiskImage = DiskImage.copyOf(srcDiskImage);
retDiskImage.setImageId(newImageGuid);
retDiskImage.setParentId(getDiskImage().getImageId());
retDiskImage.setVmSnapshotId(getParameters().getVmSnapshotId());
retDiskImage.setId(getImageGroupId());
retDiskImage.setLastModifiedDate(new Date());
retDiskImage.setQuotaId(getParameters().getQuotaId());
retDiskImage.setDiskProfileId(getParameters().getDiskProfileId());
retDiskImage.setDiskAlias(getParameters().getDiskAlias());
return retDiskImage;
}
/**
* Overrides the relevant fields of the destination disk image ('DestinationDiskImage') with some values of the IRS
* disk image.
* @param fromIRS
* the IRS disk image.
*/
protected void completeImageData(DiskImage fromIRS) {
getDestinationDiskImage().setCreationDate(fromIRS.getCreationDate());
getDestinationDiskImage().setLastModifiedDate(fromIRS.getLastModifiedDate());
getDestinationDiskImage().setLastModified(getDestinationDiskImage().getLastModifiedDate());
DiskImageDynamic destinationDiskDynamic = diskImageDynamicDao.get(getDestinationDiskImage().getImageId());
if (destinationDiskDynamic != null) {
destinationDiskDynamic.setActualSize(fromIRS.getActualSizeInBytes());
diskImageDynamicDao.update(destinationDiskDynamic);
}
}
protected void addDiskImageToDb(DiskImage image, CompensationContext compensationContext, boolean active) {
image.setActive(active);
imageDao.save(image.getImage());
DiskImageDynamic diskDynamic = updateDiskImageDynamicIntoDB(image);
ImageStorageDomainMap imageStorageDomainMap = new ImageStorageDomainMap(image.getImageId(),
image.getStorageIds().get(0), image.getQuotaId(), image.getDiskProfileId());
imageStorageDomainMapDao.save(imageStorageDomainMap);
boolean isDiskAdded = saveDiskIfNotExists(image);
if (compensationContext != null) {
compensationContext.snapshotNewEntity(image.getImage());
compensationContext.snapshotNewEntity(diskDynamic);
compensationContext.snapshotNewEntity(imageStorageDomainMap);
if (isDiskAdded) {
compensationContext.snapshotNewEntity(image);
}
compensationContext.stateChanged();
}
}
protected DiskImageDynamic updateDiskImageDynamicIntoDB(DiskImage image) {
DiskImageDynamic diskDynamic = new DiskImageDynamic();
diskDynamic.setId(image.getImageId());
diskDynamic.setActualSize(image.getActualSizeInBytes());
diskImageDynamicDao.save(diskDynamic);
return diskDynamic;
}
/**
* Save the disk from the given image info, only if the disk doesn't exist already.
* @param image
* The image to take the disk's details from.
*/
protected boolean saveDiskIfNotExists(DiskImage image) {
if (!baseDiskDao.exists(image.getId())) {
baseDiskDao.save(image);
return true;
}
return false;
}
protected void lockImage() {
setImageStatus(ImageStatus.LOCKED);
}
protected void unLockImage() {
setImageStatus(ImageStatus.OK);
}
protected void setImageStatus(ImageStatus imageStatus) {
setImageStatus(imageStatus, getRelevantDiskImage());
}
protected void setImageStatus(ImageStatus imageStatus, DiskImage diskImage) {
if (diskImage != null && diskImage.getImageStatus() != imageStatus) {
diskImage.setImageStatus(imageStatus);
ImagesHandler.updateImageStatus(diskImage.getImage().getId(), imageStatus);
}
}
protected DiskImage getRelevantDiskImage() {
return getParameters().isImportEntity() ? getDestinationDiskImage() : getDiskImage();
}
protected DiskImage getVolumeInfo(Guid storagePoolId, Guid newStorageDomainID, Guid newImageGroupId,
Guid newImageId) {
return (DiskImage) runVdsCommand(
VDSCommandType.GetImageInfo,
new GetImageInfoVDSCommandParameters(storagePoolId, newStorageDomainID, newImageGroupId,
newImageId)).getReturnValue();
}
@Override
protected void endSuccessfully() {
if (getDestinationDiskImage() != null) {
Guid storagePoolId = getDestinationDiskImage().getStoragePoolId() != null ? getDestinationDiskImage()
.getStoragePoolId() : Guid.Empty;
setStoragePoolId(storagePoolId);
Guid newImageGroupId = getDestinationDiskImage().getId() != null ? getDestinationDiskImage()
.getId() : Guid.Empty;
Guid newImageId = getDestinationDiskImage().getImageId();
Guid newStorageDomainID = getDestinationDiskImage().getStorageIds().get(0);
// complete IRS data to DB disk image:
try {
DiskImage newImageIRS = getVolumeInfo(storagePoolId, newStorageDomainID, newImageGroupId, newImageId);
if (newImageIRS != null) {
completeImageData(newImageIRS);
// Set volume type/format before updating DB in the 'finally' branch
getDestinationDiskImage().getImage().setVolumeType(newImageIRS.getVolumeType());
getDestinationDiskImage().getImage().setVolumeFormat(newImageIRS.getVolumeFormat());
if (newImageIRS.getVolumeFormat().equals(VolumeFormat.COW)) {
setQcowCompatByQemuImageInfo(storagePoolId,
newImageGroupId,
newImageId,
newStorageDomainID,
getDestinationDiskImage());
}
}
} catch (EngineException e) {
// Logging only
log.error("Unable to update the image info for image '{}' (image group: '{}') on domain '{}'",
newImageId, newImageGroupId, newStorageDomainID);
} finally {
if (!getParameters().isLeaveLocked()) {
getDestinationDiskImage().setImageStatus(ImageStatus.OK);
}
imageDao.update(getDestinationDiskImage().getImage());
}
}
if (!getParameters().isLeaveLocked()) {
unLockImage();
}
setSucceeded(true);
}
protected void setQcowCompatByQemuImageInfo(Guid storagePoolId,
Guid newImageGroupId,
Guid newImageId,
Guid newStorageDomainID,
DiskImage diskImage) {
// If the VM is running then the volume is already prepared in the guest's host so there
// is no need for prepare and teardown.
Guid hostIdToExecuteQemuImageInfo = null;
List<Pair<VM, VmDevice>> attachedVmsInfo =
vmDao.getVmsWithPlugInfo(diskImage.getId());
for (Pair<VM, VmDevice> pair : attachedVmsInfo) {
VM vm = pair.getFirst();
if (Boolean.TRUE.equals(pair.getSecond().isPlugged())) {
if (vm.isStartingOrUp()) {
hostIdToExecuteQemuImageInfo = vm.getRunOnVds();
break;
}
}
}
setQcowCompat(diskImage.getImage(),
storagePoolId,
newImageGroupId,
newImageId,
newStorageDomainID,
hostIdToExecuteQemuImageInfo);
}
protected void setQcowCompat(Image diskImage,
Guid storagePoolId,
Guid newImageGroupId,
Guid newImageId,
Guid newStorageDomainID,
Guid hostIdForExecution) {
diskImage.setQcowCompat(QcowCompat.QCOW2_V2);
if (FeatureSupported.qcowCompatSupported(getStoragePool().getCompatibilityVersion())) {
QemuImageInfo qemuImageInfo = ImagesHandler.getQemuImageInfoFromVdsm(storagePoolId,
newStorageDomainID,
newImageGroupId,
newImageId,
hostIdForExecution,
hostIdForExecution == null);
if (qemuImageInfo != null) {
diskImage.setQcowCompat(qemuImageInfo.getQcowCompat());
}
}
}
@Override
protected void endWithFailure() {
undoActionOnSourceAndDestination();
setSucceeded(true);
}
protected void undoActionOnSourceAndDestination() {
if (getDestinationDiskImage() != null) {
removeSnapshot(getDestinationDiskImage());
}
if (!getParameters().isLeaveLocked()) {
unLockImage();
}
}
/**
* TODO: move it other class in hierarchy
*/
protected void removeSnapshot(DiskImage snapshot) {
imageStorageDomainMapDao.remove(snapshot.getImageId());
imageDao.remove(snapshot.getImageId());
List<DiskImage> imagesForDisk =
diskImageDao.getAllSnapshotsForImageGroup(snapshot.getId());
if (imagesForDisk == null || imagesForDisk.isEmpty()) {
baseDiskDao.remove(snapshot.getId());
}
}
protected void lockVmSnapshotsWithWait(VM vm) {
snapshotsEngineLock = new EngineLock();
Map<String, Pair<String, String>> snapshotsExlusiveLockMap =
Collections.singletonMap(vm.getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_SNAPSHOTS, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
snapshotsEngineLock.setExclusiveLocks(snapshotsExlusiveLockMap);
lockManager.acquireLockWait(snapshotsEngineLock);
}
}