package org.ovirt.engine.core.bll.storage.disk.image;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
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.domain.PostDeleteActionHandler;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.RemoveImageParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.businessentities.storage.ImageStorageDomainMapId;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.vdscommands.DeleteImageGroupVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.GetImagesListVDSCommandParameters;
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.compat.TransactionScopeOption;
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.dao.VmDeviceDao;
import org.ovirt.engine.core.utils.ovf.OvfManager;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
/**
* This command responsible to removing image, contains all created snapshots.
*/
@InternalCommandAttribute
@NonTransactiveCommandAttribute(forceCompensation=true)
public class RemoveImageCommand<T extends RemoveImageParameters> extends BaseImagesCommand<T> {
@Inject
private PostDeleteActionHandler postDeleteActionHandler;
@Inject
protected OvfManager ovfManager;
@Inject
private DiskImageDynamicDao diskImageDynamicDao;
@Inject
private BaseDiskDao baseDiskDao;
@Inject
private VmDeviceDao vmDeviceDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private ImageStorageDomainMapDao imageStorageDomainMapDao;
@Inject
private ImageDao imageDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDao vmDao;
public RemoveImageCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
@Override
public void init() {
super.init();
initImage();
initStoragePoolId();
initStorageDomainId();
}
public RemoveImageCommand(Guid commandId) {
super(commandId);
}
protected void initImage() {
setDiskImage((getParameters().getDiskImage() != null) ? getParameters().getDiskImage() : getImage());
}
protected void initStoragePoolId() {
if (getStoragePoolId() == null || Guid.Empty.equals(getStoragePoolId())) {
setStoragePoolId(getDiskImage() != null && getDiskImage().getStoragePoolId() != null ? getDiskImage()
.getStoragePoolId() : Guid.Empty);
}
}
protected void initStorageDomainId() {
if ((getParameters().getStorageDomainId() == null || Guid.Empty.equals(getParameters().getStorageDomainId()))
&& getDiskImage() != null) {
setStorageDomainId(getDiskImage().getStorageIds().get(0));
}
}
@Override
protected void executeCommand() {
if (getDiskImage() != null) {
try {
Guid taskId = persistAsyncTaskPlaceHolder(getParameters().getParentCommand());
VDSReturnValue vdsReturnValue = performDeleteImageVdsmOperation();
getTaskIdList().add(
createTask(taskId,
vdsReturnValue.getCreationInfo(),
getParameters().getParentCommand(),
VdcObjectType.Storage,
getStorageDomainId()));
} catch (EngineException e) {
if (e.getErrorCode() == EngineError.ImageDoesNotExistInDomainError) {
log.info("Disk '{}' doesn't exist on storage domain '{}', rolling forward",
getDiskImage().getId(), getStorageDomainId());
}
// VDSM renames the image before deleting it, so technically the image doesn't exist after renaming,
// but the actual delete can still fail with ImageDeleteError.
// In this case, Engine has to check whether image still exists on the storage or not.
else if (e.getErrorCode() == EngineError.ImageDeleteError && isImageRemovedFromStorage()) {
log.info("Disk '{}' was deleted from storage domain '{}'", getDiskImage().getId(),
getStorageDomainId());
} else {
throw e;
}
}
if (getParameters().getParentCommand() != VdcActionType.RemoveVmFromImportExport
&& getParameters().getParentCommand() != VdcActionType.RemoveVmTemplateFromImportExport) {
performImageDbOperations();
}
} else {
log.warn("DiskImage is null, nothing to remove");
}
setSucceeded(true);
}
protected boolean isImageRemovedFromStorage() {
VDSReturnValue retValue = runVdsCommand(VDSCommandType.GetImagesList,
new GetImagesListVDSCommandParameters(getStorageDomainId(), getDiskImage().getStoragePoolId()));
if (retValue.getSucceeded()) {
List<Guid> ids = (List<Guid>) retValue.getReturnValue();
for (Guid id : ids) {
if (id.equals(getDiskImage().getId())) {
return false;
}
}
return true;
} else {
log.warn("Could not retrieve image list from storage domain '{}' '{}', disk '{}' might "
+ "not have been deleted",
getStorageDomainId(),
getStorageDomain().getName(),
getDiskImage().getId());
return false;
}
}
@Override
protected AsyncTaskType getTaskType() {
return AsyncTaskType.deleteImage;
}
private void removeImageFromDB() {
final DiskImage diskImage = getDiskImage();
final List<Snapshot> updatedSnapshots;
try {
VM vm = getVmForNonShareableDiskImage(diskImage);
// if the disk is not part of a vm (floating), there are no snapshots to update
// so no lock is required.
if (getParameters().isRemoveFromSnapshots() && vm != null) {
lockVmSnapshotsWithWait(vm);
updatedSnapshots = prepareSnapshotConfigWithoutImage(diskImage.getId());
} else {
updatedSnapshots = Collections.emptyList();
}
TransactionSupport.executeInScope(TransactionScopeOption.Required,
() -> {
diskImageDynamicDao.remove(diskImage.getImageId());
Guid imageTemplate = diskImage.getImageTemplateId();
Guid currentGuid = diskImage.getImageId();
// next 'while' statement removes snapshots from DB only (the
// 'DeleteImageGroup'
// VDS Command should take care of removing all the snapshots from
// the storage).
while (!currentGuid.equals(imageTemplate) && !currentGuid.equals(Guid.Empty)) {
removeChildren(currentGuid);
DiskImage image = diskImageDao.getSnapshotById(currentGuid);
if (image != null) {
removeSnapshot(image);
currentGuid = image.getParentId();
} else {
currentGuid = Guid.Empty;
log.warn(
"'image' (snapshot of image '{}') is null, cannot remove it.",
diskImage.getImageId());
}
}
baseDiskDao.remove(diskImage.getId());
vmDeviceDao.remove(new VmDeviceId(diskImage.getId(), null));
for (Snapshot s : updatedSnapshots) {
snapshotDao.update(s);
}
return null;
});
} finally {
if (getSnapshotsEngineLock() != null) {
lockManager.releaseLock(getSnapshotsEngineLock());
}
}
}
/**
* this method returns the vm that a non shareable disk active snapshot is attached to
* or null is the disk is unattached to any vm,
*/
protected VM getVmForNonShareableDiskImage(DiskImage disk) {
if (!disk.isShareable()) {
List<VM> vms = vmDao.getVmsListForDisk(disk.getId(), false);
if (!vms.isEmpty()) {
return vms.get(0);
}
}
return null;
}
private void getImageChildren(Guid snapshot, List<Guid> children) {
List<Guid> list = new ArrayList<>();
for (DiskImage image : diskImageDao.getAllSnapshotsForParent(snapshot)) {
list.add(image.getImageId());
}
children.addAll(list);
for (Guid snapshotId : list) {
getImageChildren(snapshotId, children);
}
}
private void removeChildren(Guid snapshot) {
List<Guid> children = new ArrayList<>();
getImageChildren(snapshot, children);
Collections.reverse(children);
for (Guid child : children) {
removeSnapshot(diskImageDao.getSnapshotById(child));
}
}
/**
* Prepare a {@link List} of {@link Snapshot} objects with the given disk (image group) removed from it.
*/
protected List<Snapshot> prepareSnapshotConfigWithoutImage(Guid imageGroupToRemove) {
List<Snapshot> result = new LinkedList<>();
List<DiskImage> snapshotDisks = diskImageDao.getAllSnapshotsForImageGroup(imageGroupToRemove);
for (DiskImage snapshotDisk : snapshotDisks) {
Guid vmSnapshotId = snapshotDisk.getVmSnapshotId();
if (vmSnapshotId != null && !Guid.Empty.equals(vmSnapshotId)) {
Snapshot snapshot = snapshotDao.get(vmSnapshotId);
Snapshot updated =
ImagesHandler.prepareSnapshotConfigWithoutImageSingleImage(snapshot,
snapshotDisk.getImageId(), ovfManager);
if (updated != null) {
result.add(updated);
}
}
}
return result;
}
@Override
protected void endSuccessfully() {
setSucceeded(true);
}
@Override
protected void endWithFailure() {
setSucceeded(true);
}
private void removeImageMapping() {
TransactionSupport.executeInNewTransaction(() -> {
imageStorageDomainMapDao.remove(
new ImageStorageDomainMapId(getParameters().getImageId(),
getParameters().getStorageDomainId()));
imageDao.updateStatusOfImagesByImageGroupId(getRelevantDiskImage().getId(),
getRelevantDiskImage().getImageStatus());
return null;
});
}
private void performImageDbOperations() {
switch (getParameters().getDbOperationScope()) {
case IMAGE:
removeImageFromDB();
break;
case MAPPING:
removeImageMapping();
break;
case NONE:
break;
}
}
protected VDSReturnValue performDeleteImageVdsmOperation() {
if (getParameters().isShouldLockImage()) {
// the image status should be set to ILLEGAL, so that in case compensation runs the image status will
// be revert to be ILLEGAL, as we can't tell whether the task started on vdsm side or not.
ImagesHandler.updateAllDiskImageSnapshotsStatusWithCompensation(getRelevantDiskImage().getId(),
ImageStatus.LOCKED,
ImageStatus.ILLEGAL,
getCompensationContext());
}
return runVdsCommand(VDSCommandType.DeleteImageGroup,
postDeleteActionHandler.fixParameters(
new DeleteImageGroupVDSCommandParameters(getDiskImage().getStoragePoolId(),
getStorageDomainId(), getDiskImage().getId(),
getDiskImage().isWipeAfterDelete(), getStorageDomain().isDiscardAfterDelete(),
getParameters().getForceDelete())));
}
}