package org.ovirt.engine.core.bll; 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.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.enterprise.event.Event; import javax.inject.Inject; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.memory.MemoryUtils; import org.ovirt.engine.core.bll.network.ExternalNetworkManager; 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.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.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.RemoveAllVmCinderDisksParameters; import org.ovirt.engine.core.common.action.RemoveAllVmImagesParameters; import org.ovirt.engine.core.common.action.RemoveMemoryVolumesParameters; import org.ovirt.engine.core.common.action.RemoveVmParameters; 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.AsyncTaskCreationInfo; import org.ovirt.engine.core.common.asynctasks.EntityInfo; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.CinderDisk; import org.ovirt.engine.core.common.businessentities.storage.Disk; 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.LunDisk; 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.VmIconDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @DisableInPrepareMode @NonTransactiveCommandAttribute(forceCompensation = true) public class RemoveVmCommand<T extends RemoveVmParameters> extends VmCommand<T> implements QuotaStorageDependent { @Inject private Event<Guid> vmDeleted; @Inject private ImageDao imageDao; @Inject private DiskImageDao diskImageDao; @Inject private VmIconDao vmIconDao; @Inject private SnapshotDao snapshotDao; private List<CinderDisk> cinderDisks; /** * Constructor for command creation when compensation is applied on startup */ protected RemoveVmCommand(Guid commandId) { super(commandId); } public RemoveVmCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected void init() { getParameters().setEntityInfo(new EntityInfo(VdcObjectType.VM, getVmId())); if (getVm() != null) { setStoragePoolId(getVm().getStoragePoolId()); } } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution); } @Override protected void executeVmCommand() { if (getVm().getStatus() != VMStatus.ImageLocked) { vmHandler.lockVm(getVm().getDynamicData(), getCompensationContext()); } freeLock(); removeVmLease(getVm().getLeaseStorageDomainId(), getVm().getId()); setSucceeded(removeVm()); } private boolean removeVm() { final List<DiskImage> diskImages = DisksFilter.filterImageDisks(getVm().getDiskList(), ONLY_NOT_SHAREABLE, ONLY_ACTIVE); final List<LunDisk> lunDisks = DisksFilter.filterLunDisks(getVm().getDiskMap().values(), ONLY_NOT_SHAREABLE); for (VmNic nic : getInterfaces()) { new ExternalNetworkManager(nic).deallocateIfExternal(); } removeMemoryVolumes(); TransactionSupport.executeInNewTransaction(() -> { removeVmFromDb(); if (getParameters().isRemoveDisks()) { for (DiskImage image : diskImages) { getCompensationContext().snapshotEntityStatus(image.getImage(), ImageStatus.ILLEGAL); ImagesHandler.updateImageStatus(image.getImage().getId(), ImageStatus.LOCKED); } for (LunDisk lunDisk : lunDisks) { ImagesHandler.removeLunDisk(lunDisk); } getCompensationContext().stateChanged(); } else { for (DiskImage image : diskImages) { imageDao.updateImageVmSnapshotId(image.getImageId(), null); } } return null; }); Collection<DiskImage> unremovedDisks = Collections.emptyList(); if (getParameters().isRemoveDisks()) { if (!diskImages.isEmpty()) { unremovedDisks = removeVmImages(diskImages).getActionReturnValue(); } unremovedDisks.addAll(removeCinderDisks()); if (!unremovedDisks.isEmpty()) { processUnremovedDisks(unremovedDisks); return false; } } vmDeleted.fire(getVmId()); return true; } private void removeMemoryVolumes() { Set<String> memoryStates = MemoryUtils.getMemoryVolumesFromSnapshots(snapshotDao.getAll(getVmId())); for (String memoryState : memoryStates) { VdcReturnValueBase retVal = runInternalAction( VdcActionType.RemoveMemoryVolumes, buildRemoveMemoryVolumesParameters(memoryState, getVmId()), cloneContextAndDetachFromParent()); if (!retVal.getSucceeded()) { log.error("Failed to remove memory volumes while removing vm '{}' (volumes: '{}')", getVmId(), memoryState); } } } private RemoveMemoryVolumesParameters buildRemoveMemoryVolumesParameters(String memoryState, Guid vmId) { RemoveMemoryVolumesParameters params = new RemoveMemoryVolumesParameters(memoryState, vmId, true); params.setEntityInfo(getParameters().getEntityInfo()); return params; } @Override protected boolean validate() { if (getVm() == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND); } if (!canRunActionOnNonManagedVm()) { return false; } if (getVm().isDeleteProtected()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_DELETE_PROTECTION_ENABLED); } vmHandler.updateDisksFromDb(getVm()); getParameters().setUseCinderCommandCallback(getParameters().isRemoveDisks() && !getCinderDisks().isEmpty()); if (!getParameters().isRemoveDisks() && !canRemoveVmWithDetachDisks()) { return false; } switch (getVm().getStatus()) { case Unassigned: case Down: case ImageIllegal: case ImageLocked: break; case Suspended: return failValidation(EngineMessage.VM_CANNOT_REMOVE_VM_WHEN_STATUS_IS_NOT_DOWN); default: return (getVm().isHostedEngine() && isInternalExecution()) || failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_RUNNING); } if (getVm().getVmPoolId() != null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_ATTACHED_TO_POOL); } // enable to remove vms without images if (!validate(snapshotsValidator.vmNotDuringSnapshot(getVmId()))) { return false; } if (!getVm().getDiskMap().isEmpty() && !validate(new StoragePoolValidator(getStoragePool()).isUp())) { return false; } Collection<Disk> vmDisks = getVm().getDiskMap().values(); List<DiskImage> vmImages = DisksFilter.filterImageDisks(vmDisks, ONLY_NOT_SHAREABLE, ONLY_ACTIVE); vmImages.addAll(DisksFilter.filterCinderDisks(vmDisks)); if (!vmImages.isEmpty()) { Set<Guid> storageIds = ImagesHandler.getAllStorageIdsForImageIds(vmImages); MultipleStorageDomainsValidator storageValidator = new MultipleStorageDomainsValidator(getVm().getStoragePoolId(), storageIds); if (!validate(storageValidator.allDomainsExistAndActive())) { return false; } DiskImagesValidator diskImagesValidator = new DiskImagesValidator(vmImages); if (!getParameters().getForce() && !validate(diskImagesValidator.diskImagesNotLocked())) { return false; } } // Handle VM status with ImageLocked VmValidator vmValidator = new VmValidator(getVm()); ValidationResult vmLockedValidatorResult = vmValidator.vmNotLocked(); if (!vmLockedValidatorResult.isValid()) { // without force remove, we can't remove the VM if (!getParameters().getForce()) { return failValidation(vmLockedValidatorResult.getMessages()); } // If it is force, we cannot remove if there are task if (CommandCoordinatorUtil.hasTasksByStoragePoolId(getVm().getStoragePoolId())) { return failValidation(EngineMessage.VM_CANNOT_REMOVE_HAS_RUNNING_TASKS); } } if (getParameters().isRemoveDisks() && !validate(vmValidator.vmNotHavingDeviceSnapshotsAttachedToOtherVms(false))) { return false; } return true; } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__REMOVE); addValidationMessage(EngineMessage.VAR__TYPE__VM); } private boolean canRemoveVmWithDetachDisks() { if (!Guid.Empty.equals(getVm().getVmtGuid())) { return failValidation(EngineMessage.VM_CANNOT_REMOVE_WITH_DETACH_DISKS_BASED_ON_TEMPLATE); } for (Disk disk : getVm().getDiskList()) { List<DiskImage> diskImageList = diskImageDao.getAllSnapshotsForImageGroup(disk.getId()); if (diskImageList.size() > 1) { return failValidation(EngineMessage.VM_CANNOT_REMOVE_WITH_DETACH_DISKS_SNAPSHOTS_EXIST); } } return true; } protected VdcReturnValueBase removeVmImages(List<DiskImage> images) { VdcReturnValueBase vdcRetValue = runInternalActionWithTasksContext(VdcActionType.RemoveAllVmImages, buildRemoveAllVmImagesParameters(images)); if (vdcRetValue.getSucceeded()) { getTaskIdList().addAll(vdcRetValue.getInternalVdsmTaskIdList()); } return vdcRetValue; } private RemoveAllVmImagesParameters buildRemoveAllVmImagesParameters(List<DiskImage> images) { RemoveAllVmImagesParameters params = new RemoveAllVmImagesParameters(getVmId(), images); if (!isExecutedAsChildCommand()) { params.setParentCommand(getActionType()); params.setEntityInfo(getParameters().getEntityInfo()); params.setParentParameters(getParameters()); } else { params.setParentCommand(getParameters().getParentCommand()); params.setEntityInfo(getParameters().getParentParameters().getEntityInfo()); params.setParentParameters(getParameters().getParentParameters()); } return params; } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_REMOVE_VM_FINISHED : AuditLogType.USER_REMOVE_VM_FINISHED_WITH_ILLEGAL_DISKS; } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getVmId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } protected void removeVmFromDb() { removeVmUsers(); removeVmNetwork(); removeVmSnapshots(); removeVmStatic(getParameters().isRemovePermissions()); removeIcons(); } /** * It does just best effort service. There is also global icon cleanup during startup {@link Backend#iconCleanup()} */ private void removeIcons() { if (getVm() != null) { vmIconDao.removeIfUnused(getVm().getStaticData().getLargeIconId()); vmIconDao.removeIfUnused(getVm().getStaticData().getSmallIconId()); } } /** * The following method will perform a removing of all cinder disks from vm. These is only DB operation */ private Collection<CinderDisk> removeCinderDisks() { Collection<CinderDisk> failedRemoveCinderDisks = null; if (getParameters().isRemoveDisks()) { List<CinderDisk> cinderDisks = getCinderDisks(); if (cinderDisks.isEmpty()) { return Collections.emptyList(); } RemoveAllVmCinderDisksParameters param = new RemoveAllVmCinderDisksParameters(getVmId(), cinderDisks); param.setEndProcedure(EndProcedure.COMMAND_MANAGED); Future<VdcReturnValueBase> future = CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.RemoveAllVmCinderDisks, withRootCommandInfo(param), cloneContextAndDetachFromParent()); try { failedRemoveCinderDisks = future.get().getActionReturnValue(); } catch (InterruptedException | ExecutionException e) { failedRemoveCinderDisks = cinderDisks; log.error("Exception", e); } } return failedRemoveCinderDisks; } private List<CinderDisk> getCinderDisks() { if (cinderDisks == null) { cinderDisks = DisksFilter.filterCinderDisks(getVm().getDiskMap().values()); } return cinderDisks; } @Override protected void endVmCommand() { // no audit log print here as the vm was already removed during the execute phase. setCommandShouldBeLogged(false); setSucceeded(true); } private void processUnremovedDisks(Collection<? extends DiskImage> diskImages) { addCustomValue("DisksNames", diskImages.stream().map(DiskImage::getDiskAlias).collect(Collectors.joining(","))); } @Override public void addQuotaPermissionSubject(List<PermissionSubject> quotaPermissionList) { // } @Override public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() { if (getParameters().isRemoveDisks()) { List<QuotaConsumptionParameter> list = new ArrayList<>(); ImagesHandler.fillImagesBySnapshots(getVm()); for (DiskImage disk : getVm().getDiskList()) { for (DiskImage snapshot : disk.getSnapshots()) { if (snapshot.getQuotaId() != null && !Guid.Empty.equals(snapshot.getQuotaId())) { if (snapshot.getActive()) { list.add(new QuotaStorageConsumptionParameter( snapshot.getQuotaId(), null, QuotaStorageConsumptionParameter.QuotaAction.RELEASE, disk.getStorageIds().get(0), (double) snapshot.getSizeInGigabytes())); } else { list.add(new QuotaStorageConsumptionParameter( snapshot.getQuotaId(), null, QuotaStorageConsumptionParameter.QuotaAction.RELEASE, disk.getStorageIds().get(0), snapshot.getActualSize())); } } } } return list; } return Collections.emptyList(); } @Override public Guid createTask(Guid taskId, AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand, VdcObjectType entityType, Guid... entityIds) { return super.createTaskInCurrentTransaction(taskId, asyncTaskCreationInfo, parentCommand, entityType, entityIds); } @Override public CommandCallback getCallback() { return getParameters().isUseCinderCommandCallback() ? new ConcurrentChildCommandsExecutionCallback() : null; } }