package org.ovirt.engine.core.bll.validator; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_PLUGGED; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_SNAPABLE; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.Backend; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.interfaces.BackendInternal; import org.ovirt.engine.core.bll.scheduling.SchedulingManager; import org.ovirt.engine.core.bll.snapshots.SnapshotsValidator; import org.ovirt.engine.core.bll.storage.disk.DiskHandler; 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.validator.storage.DiskImagesValidator; import org.ovirt.engine.core.bll.validator.storage.MultipleDiskVmElementValidator; import org.ovirt.engine.core.bll.validator.storage.MultipleStorageDomainsValidator; import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator; import org.ovirt.engine.core.common.VdcActionUtils; import org.ovirt.engine.core.common.action.RunVmParams; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.BootSequence; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.StoragePool; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VdsDynamic; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; 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.DiskVmElement; import org.ovirt.engine.core.common.businessentities.storage.ImageFileType; import org.ovirt.engine.core.common.businessentities.storage.RepoImage; import org.ovirt.engine.core.common.businessentities.storage.VolumeType; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.queries.GetImagesListParameters; import org.ovirt.engine.core.common.queries.VdcQueryReturnValue; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.common.utils.VmCommonUtils; import org.ovirt.engine.core.common.utils.customprop.VmPropertiesUtils; import org.ovirt.engine.core.common.vdscommands.IsVmDuringInitiatingVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.VdsDynamicDao; import org.ovirt.engine.core.dao.network.NetworkDao; import org.ovirt.engine.core.dao.network.VmNicDao; import org.ovirt.engine.core.di.Injector; import org.ovirt.engine.core.utils.NetworkUtils; public class RunVmValidator { private VM vm; private RunVmParams runVmParam; private boolean isInternalExecution; private Guid activeIsoDomainId; private List<Disk> cachedVmDisks; private List<DiskImage> cachedVmImageDisks; private Set<String> cachedInterfaceNetworkNames; private List<Network> cachedClusterNetworks; private Set<String> cachedClusterNetworksNames; private Map<Disk, DiskVmElement> cachedVmDveMap; @Inject private SnapshotsValidator snapshotsValidator; public RunVmValidator(VM vm, RunVmParams rumVmParam, boolean isInternalExecution, Guid activeIsoDomainId) { this.vm = vm; this.runVmParam = rumVmParam; this.isInternalExecution = isInternalExecution; this.activeIsoDomainId = activeIsoDomainId; } /** * Used for testings */ protected RunVmValidator() { } /** * A general method for run vm validations. used in runVmCommand and in VmPoolCommandBase * * @param vdsBlackList * - hosts that we already tried to run on * @param vdsWhiteList * - initial host list, mainly runOnSpecificHost (runOnce/migrateToHost) */ public boolean canRunVm(List<String> messages, StoragePool storagePool, List<Guid> vdsBlackList, List<Guid> vdsWhiteList, Cluster cluster, boolean runInUnknownStatus) { if (vm.getStatus() == VMStatus.Paused) { // if the VM is paused, we should only check the VDS status // as the rest of the checks were already checked before return validate(validateVdsStatus(vm), messages); } else if (vm.getStatus() == VMStatus.Suspended) { return validate(new VmValidator(vm).vmNotLocked(), messages) && validate(snapshotsValidator.vmNotDuringSnapshot(vm.getId()), messages) && validate(validateVmStatusUsingMatrix(vm), messages) && validate(validateStoragePoolUp(vm, storagePool, getVmImageDisks()), messages) && validate(vmDuringInitialization(vm), messages) && validate(validateStorageDomains(vm, isInternalExecution, filterReadOnlyAndPreallocatedDisks(getVmImageDisks())), messages) && validate(validateImagesForRunVm(vm, getVmImageDisks()), messages) && validate(validateDisksPassDiscard(vm), messages) && getSchedulingManager().canSchedule( cluster, vm, vdsBlackList, vdsWhiteList, messages); } return validateVmProperties(vm, messages) && validate(validateBootSequence(vm, getVmDisks(), activeIsoDomainId), messages) && validate(validateDisplayType(), messages) && validate(new VmValidator(vm).vmNotLocked(), messages) && validate(snapshotsValidator.vmNotDuringSnapshot(vm.getId()), messages) && ((runInUnknownStatus && vm.getStatus() == VMStatus.Unknown) || validate(validateVmStatusUsingMatrix(vm), messages)) && validate(validateStoragePoolUp(vm, storagePool, getVmImageDisks()), messages) && validate(validateIsoPath(vm, runVmParam.getDiskPath(), runVmParam.getFloppyPath(), activeIsoDomainId), messages) && validate(vmDuringInitialization(vm), messages) && validate(validateStatelessVm(vm, runVmParam.getRunAsStateless()), messages) && validate(validateFloppy(), messages) && validate(validateStorageDomains(vm, isInternalExecution, filterReadOnlyAndPreallocatedDisks(getVmImageDisks())), messages) && validate(validateImagesForRunVm(vm, getVmImageDisks()), messages) && validate(validateDisksPassDiscard(vm), messages) && validate(validateMemorySize(vm), messages) && getSchedulingManager().canSchedule( cluster, vm, vdsBlackList, vdsWhiteList, messages); } private List<DiskImage> filterReadOnlyAndPreallocatedDisks(List<DiskImage> vmImageDisks) { return vmImageDisks.stream() .filter(disk -> !(disk.getVolumeType() == VolumeType.Preallocated || getVmDiskVmElementMap().get(disk).isReadOnly())) .collect(Collectors.toList()); } private SchedulingManager getSchedulingManager() { return Injector.get(SchedulingManager.class); } protected ValidationResult validateMemorySize(VM vm) { int maxSize = VmCommonUtils.maxMemorySizeWithHotplugInMb(vm); if (vm.getMemSizeMb() > maxSize) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MEMORY_EXCEEDS_SUPPORTED_LIMIT); } return ValidationResult.VALID; } private ValidationResult validateFloppy() { if (StringUtils.isNotEmpty(runVmParam.getFloppyPath()) && !VmValidationUtils.isFloppySupported(vm.getOs(), vm.getCompatibilityVersion())) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_FLOPPY_IS_NOT_SUPPORTED_BY_OS); } return ValidationResult.VALID; } /** * @return true if all VM network interfaces are valid */ public ValidationResult validateNetworkInterfaces() { ValidationResult validationResult = validateInterfacesAttachedToClusterNetworks(getClusterNetworksNames(), getInterfaceNetworkNames()); if (!validationResult.isValid()) { return validationResult; } validationResult = validateInterfacesAttachedToVmNetworks(getClusterNetworks(), getInterfaceNetworkNames()); if (!validationResult.isValid()) { return validationResult; } return ValidationResult.VALID; } private ValidationResult validateDisplayType() { if (!VmValidationUtils.isGraphicsAndDisplaySupported(vm.getOs(), vm.getCompatibilityVersion(), getVmActiveGraphics(), vm.getDefaultDisplayType())) { return new ValidationResult( EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_VM_DISPLAY_TYPE_IS_NOT_SUPPORTED_BY_OS); } return ValidationResult.VALID; } private Set<GraphicsType> getVmActiveGraphics() { if (vm.getGraphicsInfos() != null && !vm.getGraphicsInfos().isEmpty()) { // graphics overriden in runonce return vm.getGraphicsInfos().keySet(); } else { List<VmDevice> graphicDevices = DbFacade.getInstance().getVmDeviceDao().getVmDeviceByVmIdAndType(vm.getId(), VmDeviceGeneralType.GRAPHICS); Set<GraphicsType> graphicsTypes = new HashSet<>(); for (VmDevice graphicDevice : graphicDevices) { GraphicsType type = GraphicsType.fromString(graphicDevice.getDevice()); graphicsTypes.add(type); } return graphicsTypes; } } protected boolean validateVmProperties(VM vm, List<String> messages) { return getVmPropertiesUtils().validateVmProperties( vm.getCompatibilityVersion(), vm.getCustomProperties(), messages); } protected ValidationResult validateBootSequence(VM vm, List<Disk> vmDisks, Guid activeIsoDomainId) { BootSequence bootSequence = vm.getBootSequence(); // Block from running a VM with no HDD when its first boot device is // HD and no other boot devices are configured if (bootSequence == BootSequence.C && vmDisks.isEmpty()) { return new ValidationResult(EngineMessage.VM_CANNOT_RUN_FROM_DISK_WITHOUT_DISK); } // If CD appears as first and there is no ISO in storage // pool/ISO inactive - you cannot run this VM if (bootSequence == BootSequence.CD && activeIsoDomainId == null) { return new ValidationResult(EngineMessage.VM_CANNOT_RUN_FROM_CD_WITHOUT_ACTIVE_STORAGE_DOMAIN_ISO); } // if there is network in the boot sequence, check that the // vm has network, otherwise the vm cannot be run in vdsm if (bootSequence == BootSequence.N && getVmNicDao().getAllForVm(vm.getId()).isEmpty()) { return new ValidationResult(EngineMessage.VM_CANNOT_RUN_FROM_NETWORK_WITHOUT_NETWORK); } return ValidationResult.VALID; } /** * Check storage domains. Storage domain status and disk space are checked only for non-HA VMs. * * @param vm * The VM to run * @param isInternalExecution * Command is internal? * @param vmImages * The VM's image disks * @return <code>true</code> if the VM can be run, <code>false</code> if not */ private ValidationResult validateStorageDomains(VM vm, boolean isInternalExecution, List<DiskImage> vmImages) { if (vmImages.isEmpty()) { return ValidationResult.VALID; } if (!vm.isAutoStartup() || !isInternalExecution) { Set<Guid> storageDomainIds = ImagesHandler.getAllStorageIdsForImageIds(vmImages); MultipleStorageDomainsValidator storageDomainValidator = new MultipleStorageDomainsValidator(vm.getStoragePoolId(), storageDomainIds); ValidationResult result = storageDomainValidator.allDomainsExistAndActive(); if (!result.isValid()) { return result; } result = !vm.isAutoStartup() ? storageDomainValidator.allDomainsWithinThresholds() : ValidationResult.VALID; if (!result.isValid()) { return result; } } return ValidationResult.VALID; } /** * Check isValid only if VM is not HA VM */ private ValidationResult validateImagesForRunVm(VM vm, List<DiskImage> vmDisks) { if (vmDisks.isEmpty()) { return ValidationResult.VALID; } return !vm.isAutoStartup() ? new DiskImagesValidator(vmDisks).diskImagesNotLocked() : ValidationResult.VALID; } protected ValidationResult validateDisksPassDiscard(VM vm) { Map<Guid, Guid> diskIdToDestSdId = getVmDisks().stream() .collect(Collectors.toMap(Disk::getId, disk -> disk.getDiskStorageType() == DiskStorageType.IMAGE ? ((DiskImage) disk).getStorageIds().get(0) : Guid.Empty)); MultipleDiskVmElementValidator multipleDiskVmElementValidator = createMultipleDiskVmElementValidator(getVmDiskVmElementMap()); return multipleDiskVmElementValidator.isPassDiscardSupportedForDestSds(diskIdToDestSdId); } protected MultipleDiskVmElementValidator createMultipleDiskVmElementValidator( Map<Disk, DiskVmElement> diskToDiskVmElement) { return new MultipleDiskVmElementValidator(diskToDiskVmElement); } private ValidationResult validateIsoPath(VM vm, String diskPath, String floppyPath, Guid activeIsoDomainId) { if (vm.isAutoStartup()) { return ValidationResult.VALID; } if (StringUtils.isEmpty(vm.getIsoPath()) && StringUtils.isEmpty(diskPath) && StringUtils.isEmpty(floppyPath)) { return ValidationResult.VALID; } if (activeIsoDomainId == null) { return new ValidationResult(EngineMessage.VM_CANNOT_RUN_FROM_CD_WITHOUT_ACTIVE_STORAGE_DOMAIN_ISO); } if (!StringUtils.isEmpty(diskPath) && !isRepoImageExists(diskPath, activeIsoDomainId, ImageFileType.ISO)) { return new ValidationResult(EngineMessage.ERROR_CANNOT_FIND_ISO_IMAGE_PATH); } if (!StringUtils.isEmpty(floppyPath) && !isRepoImageExists(floppyPath, activeIsoDomainId, ImageFileType.Floppy)) { return new ValidationResult(EngineMessage.ERROR_CANNOT_FIND_FLOPPY_IMAGE_PATH); } return ValidationResult.VALID; } protected ValidationResult vmDuringInitialization(VM vm) { if (vm.isRunning() || vm.getStatus() == VMStatus.NotResponding || isVmDuringInitiating(vm)) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_IS_RUNNING); } return ValidationResult.VALID; } private ValidationResult validateVdsStatus(VM vm) { if (vm.getStatus() == VMStatus.Paused && vm.getRunOnVds() != null && getVdsDynamic(vm.getRunOnVds()).getStatus() != VDSStatus.Up) { return new ValidationResult( EngineMessage.ACTION_TYPE_FAILED_VDS_STATUS_ILLEGAL, EngineMessage.VAR__HOST_STATUS__UP.toString()); } return ValidationResult.VALID; } protected ValidationResult validateStatelessVm(VM vm, Boolean stateless) { // if the VM is not stateless, there is nothing to check if (stateless != null ? !stateless : !vm.isStateless()) { return ValidationResult.VALID; } ValidationResult previewValidation = snapshotsValidator.vmNotInPreview(vm.getId()); if (!previewValidation.isValid()) { return previewValidation; } // if the VM itself is stateless or run once as stateless if (vm.isAutoStartup()) { return new ValidationResult(EngineMessage.VM_CANNOT_RUN_STATELESS_HA); } ValidationResult hasSpaceValidation = hasSpaceForSnapshots(); if (!hasSpaceValidation.isValid()) { return hasSpaceValidation; } return isImagesExceededVolumesInImageChain(); } private ValidationResult validateVmStatusUsingMatrix(VM vm) { if (!VdcActionUtils.canExecute(Collections.singletonList(vm), VM.class, VdcActionType.RunVm)) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL, LocalizedVmStatus.from(vm.getStatus())); } return ValidationResult.VALID; } /** * Validates the number of volumes have not exceeded the maximum limit of volumes in an image chain. */ private ValidationResult isImagesExceededVolumesInImageChain() { List<DiskImage> allImageDisks = DisksFilter.filterImageDisks(getDiskDao().getAllForVm(vm.getId()), ONLY_SNAPABLE); DiskImagesValidator diskImagesValidatorForChain = createDiskImageValidator(allImageDisks); return diskImagesValidatorForChain.diskImagesHaveNotExceededMaxNumberOfVolumesInImageChain(); } /** * check that we can create snapshots for all disks * return true if all storage domains have enough space to create snapshots for this VM plugged disks */ protected ValidationResult hasSpaceForSnapshots() { List<Disk> disks = DbFacade.getInstance().getDiskDao().getAllForVm(vm.getId()); List<DiskImage> allDisks = DisksFilter.filterImageDisks(disks, ONLY_SNAPABLE); Set<Guid> sdIds = ImagesHandler.getAllStorageIdsForImageIds(allDisks); MultipleStorageDomainsValidator msdValidator = getStorageDomainsValidator(sdIds); ValidationResult retVal = msdValidator.allDomainsWithinThresholds(); if (retVal == ValidationResult.VALID) { return msdValidator.allDomainsHaveSpaceForNewDisks(allDisks); } return retVal; } private MultipleStorageDomainsValidator getStorageDomainsValidator(Collection<Guid> sdIds) { Guid spId = vm.getStoragePoolId(); return new MultipleStorageDomainsValidator(spId, sdIds); } private DiskImagesValidator createDiskImageValidator(List<DiskImage> disksList) { return new DiskImagesValidator(disksList); } private ValidationResult validateStoragePoolUp(VM vm, StoragePool storagePool, List<DiskImage> vmImages) { if (vmImages.isEmpty() || vm.isAutoStartup()) { return ValidationResult.VALID; } return new StoragePoolValidator(storagePool).isUp(); } /** * @param clusterNetworkNames cluster logical networks names * @param interfaceNetworkNames VM interface network names * @return true if all VM network interfaces are attached to existing cluster networks */ private ValidationResult validateInterfacesAttachedToClusterNetworks( final Set<String> clusterNetworkNames, final Set<String> interfaceNetworkNames) { Set<String> result = new HashSet<>(interfaceNetworkNames); result.removeAll(clusterNetworkNames); result.remove(null); // If after removing the cluster network names we still have objects, then we have interface on networks that // aren't attached to the cluster return result.isEmpty() ? ValidationResult.VALID : new ValidationResult( EngineMessage.ACTION_TYPE_FAILED_NETWORK_NOT_IN_CLUSTER, String.format("$networks %1$s", StringUtils.join(result, ","))); } /** * @param clusterNetworks * cluster logical networks * @param interfaceNetworkNames * VM interface network names * @return true if all VM network interfaces are attached to VM networks */ private ValidationResult validateInterfacesAttachedToVmNetworks(final List<Network> clusterNetworks, Set<String> interfaceNetworkNames) { List<String> nonVmNetworkNames = NetworkUtils.filterNonVmNetworkNames(clusterNetworks, interfaceNetworkNames); return nonVmNetworkNames.isEmpty() ? ValidationResult.VALID : new ValidationResult( EngineMessage.ACTION_TYPE_FAILED_NOT_A_VM_NETWORK, String.format("$networks %1$s", StringUtils.join(nonVmNetworkNames, ","))); } /////////////////////// /// Utility methods /// /////////////////////// private boolean validate(ValidationResult validationResult, List<String> message) { if (!validationResult.isValid()) { message.addAll(validationResult.getMessagesAsStrings()); message.addAll(validationResult.getVariableReplacements()); } return validationResult.isValid(); } private NetworkDao getNetworkDao() { return DbFacade.getInstance().getNetworkDao(); } private VdsDynamicDao getVdsDynamicDao() { return DbFacade.getInstance().getVdsDynamicDao(); } private BackendInternal getBackend() { return Backend.getInstance(); } protected VmNicDao getVmNicDao() { return DbFacade.getInstance().getVmNicDao(); } protected VmPropertiesUtils getVmPropertiesUtils() { return VmPropertiesUtils.getInstance(); } private DiskDao getDiskDao() { return DbFacade.getInstance().getDiskDao(); } private boolean isRepoImageExists(String repoImagePath, Guid storageDomainId, ImageFileType imageFileType) { VdcQueryReturnValue ret = getBackend().runInternalQuery( VdcQueryType.GetImagesList, new GetImagesListParameters(storageDomainId, imageFileType)); if (ret != null && ret.getReturnValue() != null && ret.getSucceeded()) { for (RepoImage isoFileMetaData : ret.<List<RepoImage>>getReturnValue()) { if (repoImagePath.equals(isoFileMetaData.getRepoImageId())) { return true; } } } return false; } protected boolean isVmDuringInitiating(VM vm) { return (Boolean) getBackend() .getResourceManager() .runVdsCommand(VDSCommandType.IsVmDuringInitiating, new IsVmDuringInitiatingVDSCommandParameters(vm.getId())) .getReturnValue(); } private VdsDynamic getVdsDynamic(Guid vdsId) { return getVdsDynamicDao().get(vdsId); } protected List<Disk> getVmDisks() { if (cachedVmDisks == null) { cachedVmDisks = getDiskDao().getAllForVm(vm.getId(), true); } return cachedVmDisks; } protected Map<Disk, DiskVmElement> getVmDiskVmElementMap() { if (cachedVmDveMap == null) { Map<Guid, Disk> disksMap = getVmDisks().stream().collect(Collectors.toMap(Disk::getId, Function.identity())); cachedVmDveMap = Injector.get(DiskHandler.class).getDiskToDiskVmElementMap(vm.getId(), disksMap); } return cachedVmDveMap; } private List<DiskImage> getVmImageDisks() { if (cachedVmImageDisks == null) { cachedVmImageDisks = DisksFilter.filterImageDisks(getVmDisks(), ONLY_NOT_SHAREABLE); cachedVmImageDisks.addAll(DisksFilter.filterCinderDisks(getVmDisks(), ONLY_PLUGGED)); } return cachedVmImageDisks; } private Set<String> getInterfaceNetworkNames() { if (cachedInterfaceNetworkNames == null) { cachedInterfaceNetworkNames = vm.getInterfaces().stream().map(VmNetworkInterface::getNetworkName).collect(Collectors.toSet()); } return cachedInterfaceNetworkNames; } private List<Network> getClusterNetworks() { if (cachedClusterNetworks == null) { cachedClusterNetworks = getNetworkDao().getAllForCluster(vm.getClusterId()); } return cachedClusterNetworks; } private Set<String> getClusterNetworksNames() { if (cachedClusterNetworksNames == null) { cachedClusterNetworksNames = getClusterNetworks().stream().map(Network::getName).collect(Collectors.toSet()); } return cachedClusterNetworksNames; } }