package org.ovirt.engine.core.bll.storage.disk; 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.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.CommandBase; import org.ovirt.engine.core.bll.DisableInPrepareMode; import org.ovirt.engine.core.bll.LockMessage; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.context.CommandContext; 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.snapshots.SnapshotsValidator; 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.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator; import org.ovirt.engine.core.bll.validator.storage.DiskValidator; import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator; 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.RemoveCinderDiskParameters; import org.ovirt.engine.core.common.action.RemoveDiskParameters; import org.ovirt.engine.core.common.action.RemoveImageParameters; 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.EntityInfo; import org.ovirt.engine.core.common.businessentities.ActionGroup; import org.ovirt.engine.core.common.businessentities.StoragePool; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceId; import org.ovirt.engine.core.common.businessentities.VmTemplate; import org.ovirt.engine.core.common.businessentities.VmTemplateStatus; 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.DiskStorageType; import org.ovirt.engine.core.common.businessentities.storage.ImageDbOperationScope; 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.compat.TransactionScopeOption; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.StoragePoolDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.dao.VmTemplateDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @DisableInPrepareMode @NonTransactiveCommandAttribute public class RemoveDiskCommand<T extends RemoveDiskParameters> extends CommandBase<T> implements QuotaStorageDependent { @Inject private SnapshotsValidator snapshotsValidator; private Disk disk; private List<PermissionSubject> permsList = null; private List<VM> listVms; private String cachedDiskIsBeingRemovedLockMessage; @Inject private VmDeviceDao vmDeviceDao; @Inject private DiskImageDao diskImageDao; @Inject private VmTemplateDao vmTemplateDao; @Inject private DiskDao diskDao; @Inject private StoragePoolDao storagePoolDao; @Inject private VmStaticDao vmStaticDao; @Inject private VmDao vmDao; public RemoveDiskCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); setStorageDomainId(getParameters().getStorageDomainId()); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__REMOVE); addValidationMessage(EngineMessage.VAR__TYPE__DISK); } @Override protected boolean validate() { if (getDisk() == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IMAGE_DOES_NOT_EXIST); } return validateHostedEngineDisks() && validateAllVmsForDiskAreDown() && canRemoveDiskBasedOnStorageTypeCheck(); } private boolean validateHostedEngineDisks() { DiskValidator oldDiskValidator = new DiskValidator(getDisk()); if (getDisk().getVmEntityType() != null && getDisk().getVmEntityType().isVmType()) { for (VM vm : getVmsForDiskId()) { if (!validate(oldDiskValidator.validRemovableHostedEngineDisks(vm))) { return false; } } } return true; } /** * Validate that all vms containing the disk are down, except hosted engine vm */ private boolean validateAllVmsForDiskAreDown() { if (getDisk().getVmEntityType() != null && getDisk().getVmEntityType().isVmType()) { for (VM vm : getVmsForDiskId()) { if (vm.getStatus() != VMStatus.Down && !vm.isHostedEngine()) { VmDevice vmDevice = vmDeviceDao.get(new VmDeviceId(getDisk().getId(), vm.getId())); if (vmDevice.isPlugged()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN); return false; } } } } return true; } private boolean canRemoveDiskBasedOnStorageTypeCheck() { // currently, only images have specific checks. // In the future, if LUNs get specific checks, // or additional storage types are added, other else-if clauses should be added. if (getDisk().getDiskStorageType() == DiskStorageType.IMAGE || getDisk().getDiskStorageType() == DiskStorageType.CINDER) { return canRemoveDiskBasedOnImageStorageCheck(); } return true; } private boolean canRemoveDiskBasedOnImageStorageCheck() { boolean retValue = true; DiskImage diskImage = getDiskImage(); DiskImagesValidator diskImagesValidator = new DiskImagesValidator(diskImage); if (diskImage.isOvfStore() && !validate(diskImagesValidator.disksInStatus(ImageStatus.ILLEGAL, EngineMessage.ACTION_TYPE_FAILED_OVF_DISK_NOT_IN_APPLICABLE_STATUS))) { return false; } boolean isVmTemplateType = diskImage.getVmEntityType() != null && diskImage.getVmEntityType().isTemplateType(); if (Guid.isNullOrEmpty(getParameters().getStorageDomainId())) { if (isVmTemplateType) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANT_DELETE_TEMPLATE_DISK_WITHOUT_SPECIFYING_DOMAIN); } getParameters().setStorageDomainId(diskImage.getStorageIds().get(0)); setStorageDomainId(diskImage.getStorageIds().get(0)); } if (isVmTemplateType) { diskImage.setStorageIds(diskImageDao.get(diskImage.getImageId()).getStorageIds()); } if (!diskImage.getStorageIds().contains(getParameters().getStorageDomainId())) { retValue = false; addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_IS_WRONG); } StorageDomainValidator validator = new StorageDomainValidator(getStorageDomain()); retValue = retValue && validate(validator.isDomainExistAndActive()) && validate(validator.domainIsValidDestination()); if (retValue && diskImage.getImageStatus() == ImageStatus.LOCKED) { retValue = false; addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED); } if (retValue && getDisk().getVmEntityType() != null) { if (getDisk().getVmEntityType().isVmType()) { retValue = canRemoveVmImageDisk(); } else if (getDisk().getVmEntityType().isTemplateType()) { retValue = canRemoveTemplateDisk(); } } return retValue; } /** * Set the parent parameter vmTemplateId, based on the disk image id. */ private void setVmTemplateIdParameter() { Map<Boolean, VmTemplate> templateMap = // Disk image is the only disk type that can be part of the template disks. vmTemplateDao.getAllForImage(getDiskImage().getImageId()); if (!templateMap.isEmpty()) { setVmTemplateId(templateMap.values().iterator().next().getId()); } } /** * Cache method to retrieve all the VMs related to image * @return List of Vms. */ private List<VM> getVmsForDiskId() { if (listVms == null) { listVms = vmDao.getVmsListForDisk(getParameters().getDiskId(), true); } return listVms; } private void addAttachVmNamesCustomValue() { addCustomValue("VmNames", listVms.stream().map(VM::getName).collect(Collectors.joining(", "))); } private boolean canRemoveTemplateDisk() { if (getVmTemplate().getStatus() == VmTemplateStatus.Locked) { return failValidation(EngineMessage.VM_TEMPLATE_IMAGE_IS_LOCKED); } DiskImage diskImage = getDiskImage(); if (diskImage.getStorageIds().size() == 1) { return failValidation(EngineMessage.VM_TEMPLATE_IMAGE_LAST_DOMAIN); } if (!checkDerivedVmFromTemplateExists(diskImage) || !checkDerivedDisksFromDiskNotExist(diskImage)){ return false; } return true; } private boolean checkDerivedVmFromTemplateExists(DiskImage diskImage) { List<String> vmNames = getNamesOfDerivedVmsFromTemplate(diskImage); if (!vmNames.isEmpty()) { addValidationMessage(EngineMessage.VMT_CANNOT_REMOVE_DETECTED_DERIVED_VM); addValidationMessageVariable("vmsList", StringUtils.join(vmNames, ",")); return false; } return true; } private DiskImagesValidator createDiskImagesValidator(DiskImage disk) { return new DiskImagesValidator(disk); } protected boolean checkDerivedDisksFromDiskNotExist(DiskImage diskImage) { return validate(createDiskImagesValidator(diskImage).diskImagesHaveNoDerivedDisks(getParameters().getStorageDomainId())); } private List<String> getNamesOfDerivedVmsFromTemplate(DiskImage diskImage) { List<String> result = new ArrayList<>(); for (VM vm : vmDao.getAllWithTemplate(getVmTemplateId())) { for (Disk vmDisk : diskDao.getAllForVm(vm.getId())) { if (vmDisk.getDiskStorageType() == DiskStorageType.IMAGE) { DiskImage vmDiskImage = (DiskImage) vmDisk; if (vmDiskImage.getImageTemplateId().equals(diskImage.getImageId())) { if (vmDiskImage.getStorageIds().contains(getParameters().getStorageDomainId())) { result.add(vm.getName()); } break; } } } } return result; } private boolean canRemoveVmImageDisk() { if (!listVms.isEmpty()) { Guid storagePoolId = listVms.get(0).getStoragePoolId(); StoragePool sp = storagePoolDao.get(storagePoolId); if (!validate(new StoragePoolValidator(sp).isUp())) { return false; } List<DiskImage> diskList = DisksFilter.filterImageDisks(Collections.singletonList(getDisk()), ONLY_NOT_SHAREABLE, ONLY_ACTIVE); DiskImagesValidator diskImagesValidator = new DiskImagesValidator(diskList); if (!validate(diskImagesValidator.diskImagesNotLocked())) { return false; } } for (VM vm : listVms) { if (!validate(snapshotsValidator.vmNotDuringSnapshot(vm.getId())) || !validate(snapshotsValidator.vmNotInPreview(vm.getId()))) { return false; } } return true; } @Override protected void executeCommand() { switch (getDisk().getDiskStorageType()) { case IMAGE: VdcReturnValueBase vdcReturnValue = runInternalActionWithTasksContext(VdcActionType.RemoveImage, buildRemoveImageParameters(getDiskImage())); if (vdcReturnValue.getSucceeded()) { incrementVmsGeneration(); getReturnValue().getVdsmTaskIdList().addAll(vdcReturnValue.getInternalVdsmTaskIdList()); setSucceeded(true); } break; case LUN: removeLunDisk(); break; case CINDER: RemoveCinderDiskParameters params = new RemoveCinderDiskParameters(getParameters().getDiskId()); params.setEndProcedure(EndProcedure.COMMAND_MANAGED); Future<VdcReturnValueBase> future = CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.RemoveCinderDisk, params, cloneContextAndDetachFromParent()); try { setReturnValue(future.get()); setSucceeded(getReturnValue().getSucceeded()); } catch (InterruptedException | ExecutionException e) { log.error("Error removing Cinder disk '{}': {}", getDiskImage().getDiskAlias(), e.getMessage()); log.debug("Exception", e); } break; } } private RemoveImageParameters buildRemoveImageParameters(DiskImage diskImage) { RemoveImageParameters result = new RemoveImageParameters(diskImage.getImageId()); result.setTransactionScopeOption(TransactionScopeOption.Suppress); result.setDiskImage(diskImage); result.setParentCommand(VdcActionType.RemoveDisk); result.setEntityInfo(new EntityInfo(VdcObjectType.Disk, getParameters().getDiskId())); result.setParentParameters(getParameters()); result.setRemoveFromSnapshots(true); result.setStorageDomainId(getParameters().getStorageDomainId()); result.setForceDelete(getParameters().getForceDelete()); if (diskImage.getStorageIds().size() > 1) { result.setDbOperationScope(ImageDbOperationScope.MAPPING); } return result; } private void removeLunDisk() { TransactionSupport.executeInNewTransaction(() -> { ImagesHandler.removeLunDisk((LunDisk) getDisk()); return null; }); setSucceeded(true); } @Override protected void endSuccessfully() { setSucceeded(true); } @Override protected void endWithFailure() { setSucceeded(true); } private void incrementVmsGeneration() { List<VM> listVms = getVmsForDiskId(); for (VM vm : listVms) { vmStaticDao.incrementDbGeneration(vm.getId()); } } @Override public AuditLogType getAuditLogTypeValue() { switch (getActionState()) { case EXECUTE: if (getDisk().getDiskStorageType() == DiskStorageType.LUN) { if (getSucceeded()) { if (getVmsForDiskId().isEmpty()) { return AuditLogType.USER_FINISHED_REMOVE_DISK_NO_DOMAIN; } else { addAttachVmNamesCustomValue(); return AuditLogType.USER_FINISHED_REMOVE_DISK_ATTACHED_TO_VMS_NO_DOMAIN; } } else { return AuditLogType.USER_FINISHED_FAILED_REMOVE_DISK_NO_DOMAIN; } } else if (getDisk().getDiskStorageType() == DiskStorageType.CINDER) { if (getSucceeded()) { if (getVmsForDiskId().isEmpty()) { return AuditLogType.USER_REMOVE_DISK_INITIATED; } else { addAttachVmNamesCustomValue(); return AuditLogType.USER_REMOVE_DISK_ATTACHED_TO_VMS_INITIATED; } } else { return AuditLogType.USER_FINISHED_FAILED_REMOVE_DISK; } } if (getSucceeded()) { if (getVmsForDiskId().isEmpty()) { return AuditLogType.USER_FINISHED_REMOVE_DISK; } else { addAttachVmNamesCustomValue(); return AuditLogType.USER_FINISHED_REMOVE_DISK_ATTACHED_TO_VMS; } } else { return AuditLogType.USER_FINISHED_FAILED_REMOVE_DISK; } default: return AuditLogType.UNASSIGNED; } } @Override public List<PermissionSubject> getPermissionCheckSubjects() { if (permsList == null && getDisk() != null) { permsList = new ArrayList<>(); permsList.add(new PermissionSubject(getDisk().getId(), VdcObjectType.Disk, ActionGroup.DELETE_DISK)); } return permsList; } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getParameters().getDiskId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, getDiskIsBeingRemovedLockMessage())); } private String getDiskIsBeingRemovedLockMessage() { if (cachedDiskIsBeingRemovedLockMessage == null) { cachedDiskIsBeingRemovedLockMessage = new LockMessage(EngineMessage.ACTION_TYPE_FAILED_DISK_IS_BEING_REMOVED) .with("DiskName", getDiskAlias()) .toString(); } return cachedDiskIsBeingRemovedLockMessage; } @Override protected Map<String, Pair<String, String>> getSharedLocks() { if (getDisk() == null || getDisk().getVmEntityType() == null) { return null; } if (getDisk().getVmEntityType().isVmType()) { return createSharedLocksForVmDisk(); } if (getDisk().getVmEntityType().isTemplateType()) { return createSharedLocksForTemplateDisk(); } log.warn("No shared locks are taken while removing disk of entity: {}", getDisk().getVmEntityType()); return null; } private Map<String, Pair<String, String>> createSharedLocksForVmDisk() { List<VM> listVms = getVmsForDiskId(); if (listVms.isEmpty()) { return null; } Map<String, Pair<String, String>> result = new HashMap<>(); for (VM vm : listVms) { result.put(vm.getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, getDiskIsBeingRemovedLockMessage())); } return result; } private Map<String, Pair<String, String>> createSharedLocksForTemplateDisk() { setVmTemplateIdParameter(); return Collections.singletonMap(getVmTemplateId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.TEMPLATE, getDiskIsBeingRemovedLockMessage())); } protected Disk getDisk() { if (disk == null) { disk = diskDao.get(getParameters().getDiskId()); } return disk; } protected DiskImage getDiskImage() { return (DiskImage) getDisk(); } public String getDiskAlias() { if (getDisk() != null) { return getDisk().getDiskAlias(); } return ""; } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = super.getJobMessageProperties(); jobProperties.put("diskalias", getDiskAlias()); } return jobProperties; } @Override public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() { List<QuotaConsumptionParameter> list = new ArrayList<>(); if (getDisk() != null && getDisk().getDiskStorageType().isInternal() && getQuotaId() != null && !Guid.Empty.equals(getQuotaId())) { list.add(new QuotaStorageConsumptionParameter( getQuotaId(), null, QuotaConsumptionParameter.QuotaAction.RELEASE, getStorageDomainId(), (double) getDiskImage().getSizeInGigabytes())); } return list; } private Guid getQuotaId() { if (getDisk() != null && getDisk().getDiskStorageType().isInternal()) { return getDiskImage().getQuotaId(); } return null; } @Override public void addQuotaPermissionSubject(List<PermissionSubject> quotaPermissionList) { } }