package org.ovirt.engine.core.bll; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CompensationContext; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.snapshots.SnapshotsManager; import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.utils.VmDeviceUtils; import org.ovirt.engine.core.bll.validator.VmValidationUtils; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.BackendService; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VmManagementParametersBase; import org.ovirt.engine.core.common.backendinterfaces.BaseHandler; import org.ovirt.engine.core.common.businessentities.ActionGroup; import org.ovirt.engine.core.common.businessentities.ArchitectureType; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.CopyOnNewVersion; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.EditableDeviceOnVmStatusField; import org.ovirt.engine.core.common.businessentities.EditableVmField; import org.ovirt.engine.core.common.businessentities.GraphicsDevice; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.GuestAgentStatus; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.TransientField; import org.ovirt.engine.core.common.businessentities.UsbPolicy; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmBase; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType; import org.ovirt.engine.core.common.businessentities.VmDeviceId; import org.ovirt.engine.core.common.businessentities.VmDynamic; import org.ovirt.engine.core.common.businessentities.VmInit; import org.ovirt.engine.core.common.businessentities.VmNumaNode; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; 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.DiskInterface; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.interfaces.VDSBrokerFrontend; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.queries.NameQueryParameters; import org.ovirt.engine.core.common.queries.VdcQueryReturnValue; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.common.utils.CompatibilityVersionUtils; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.utils.SimpleDependencyInjector; import org.ovirt.engine.core.common.utils.VmCommonUtils; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.common.utils.VmDeviceUpdate; import org.ovirt.engine.core.common.validation.VmActionByVmOriginTypeValidator; import org.ovirt.engine.core.common.vdscommands.SetVmStatusVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.UpdateVmDynamicDataVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.RpmVersion; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.DiskVmElementDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmDynamicDao; import org.ovirt.engine.core.dao.VmInitDao; import org.ovirt.engine.core.dao.VmNumaNodeDao; import org.ovirt.engine.core.dao.network.VmNetworkInterfaceDao; import org.ovirt.engine.core.utils.ObjectIdentityChecker; import org.ovirt.engine.core.utils.ReplacementUtils; import org.ovirt.engine.core.utils.lock.LockManager; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.ovirt.engine.core.vdsbroker.VmManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ApplicationScoped public class VmHandler implements BackendService { private static final Logger log = LoggerFactory.getLogger(VmHandler.class); @Inject private CpuFlagsManagerHandler cpuFlagsManagerHandler; @Inject private VmDeviceUtils vmDeviceUtils; @Inject private LockManager lockManager; @Inject private AuditLogDirector auditLogDirector; @Inject private ResourceManager resourceManager; @Inject private VDSBrokerFrontend vdsBrokerFrontend; @Inject private VdsDao vdsDao; @Inject private VmDynamicDao vmDynamicDao; @Inject private VmNumaNodeDao vmNumaNodeDao; @Inject private VmDao vmDao; @Inject private VmInitDao vmInitDao; @Inject private VmNetworkInterfaceDao vmNetworkInterfaceDao; @Inject private DiskDao diskDao; @Inject private DiskVmElementDao diskVmElementDao; @Inject private SnapshotDao snapshotDao; @Inject private SnapshotsManager snapshotsManager; private ObjectIdentityChecker updateVmsStatic; private OsRepository osRepository; /** * Initialize list containers, for identity and permission check. The initialization should be executed * before calling ObjectIdentityChecker. * * @see Backend#initHandlers */ @PostConstruct public void init() { Class<?>[] inspectedClassNames = new Class<?>[] { VmBase.class, VM.class, VmStatic.class, VmDynamic.class, VmManagementParametersBase.class }; osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class); updateVmsStatic = new ObjectIdentityChecker(VmHandler.class, Arrays.asList(inspectedClassNames)); for (Pair<EditableDeviceOnVmStatusField, Field> pair : BaseHandler.extractAnnotatedFields(EditableDeviceOnVmStatusField.class, inspectedClassNames)) { updateVmsStatic.addField(Arrays.asList(pair.getFirst().statuses()), pair.getSecond().getName()); } for (Pair<TransientField, Field> pair : BaseHandler.extractAnnotatedFields(TransientField.class, inspectedClassNames)) { updateVmsStatic.addTransientFields(pair.getSecond().getName()); } for (Pair<EditableVmField, Field> pair : BaseHandler.extractAnnotatedFields(EditableVmField.class, inspectedClassNames)) { EditableVmField annotation = pair.getFirst(); List<VMStatus> statusList = Arrays.asList(annotation.onStatuses()); String fieldName = pair.getSecond().getName(); if (statusList.isEmpty()) { updateVmsStatic.addPermittedFields(fieldName); } else { updateVmsStatic.addField(statusList, fieldName); if (annotation.hotsetAllowed()) { updateVmsStatic.addHotsetFields(fieldName); } } if (annotation.onHostedEngine()) { updateVmsStatic.addHostedEngineFields(fieldName); } } } public boolean isUpdateValid(VmStatic source, VmStatic destination, VMStatus status) { return source.isManagedHostedEngine() ? updateVmsStatic.isHostedEngineUpdateValid(source, destination) : updateVmsStatic.isUpdateValid(source, destination, status); } public List<String> getChangedFieldsForStatus(VmStatic source, VmStatic destination, VMStatus status) { return updateVmsStatic.getChangedFieldsForStatus(source, destination, status); } public boolean isUpdateValid(VmStatic source, VmStatic destination, VMStatus status, boolean hotsetEnabled) { return source.isManagedHostedEngine() ? updateVmsStatic.isHostedEngineUpdateValid(source, destination) : updateVmsStatic.isUpdateValid(source, destination, status, hotsetEnabled); } public boolean isUpdateValid(VmStatic source, VmStatic destination) { return source.isManagedHostedEngine() ? updateVmsStatic.isHostedEngineUpdateValid(source, destination) : updateVmsStatic.isUpdateValid(source, destination); } public boolean isUpdateValidForVmDevice(String fieldName, VMStatus status) { return updateVmsStatic.isFieldUpdatable(status, fieldName, null); } public boolean copyNonEditableFieldsToDestination(VmStatic source, VmStatic destination, boolean hotSetEnabled) { return updateVmsStatic.copyNonEditableFieldsToDestination(source, destination, hotSetEnabled); } /** * Verifies the add vm command . * * @param reasons * The reasons. * @param nicsCount * How many vNICs need to be allocated. */ public static boolean verifyAddVm(List<String> reasons, int nicsCount, int vmPriority, MacPool macPool) { boolean returnValue = true; if (macPool.getAvailableMacsCount() < nicsCount) { if (reasons != null) { reasons.add(EngineMessage.MAC_POOL_NOT_ENOUGH_MAC_ADDRESSES.toString()); } returnValue = false; } else if (!isVmPriorityValueLegal(vmPriority, reasons)) { returnValue = false; } return returnValue; } /** * Determines whether VM priority value is in the correct range. * * @param value * The value. * @param reasons * The reasons in case of failure (output parameter). * @return <code>true</code> if VM priority value is in the correct range; otherwise, <code>false</code>. */ public static boolean isVmPriorityValueLegal(int value, List<String> reasons) { boolean res = false; if (value >= 0 && value <= Config.<Integer> getValue(ConfigValues.VmPriorityMaxValue)) { res = true; } else { reasons.add(EngineMessage.VM_OR_TEMPLATE_ILLEGAL_PRIORITY_VALUE.toString()); reasons.add(String.format("$MaxValue %1$s", Config.<Integer> getValue(ConfigValues.VmPriorityMaxValue))); } return res; } /** * Checks if VM with same name exists in the given DC. If no DC provided, check all VMs in the database. */ public static boolean isVmWithSameNameExistStatic(String vmName, Guid storagePoolId) { NameQueryParameters params = new NameQueryParameters(vmName); params.setDatacenterId(storagePoolId); VdcQueryReturnValue result = Backend.getInstance().runInternalQuery(VdcQueryType.IsVmWithSameNameExist, params); return (Boolean) result.getReturnValue(); } /** * Lock the VM in a new transaction, saving compensation data of the old status. * * @param vm * The VM to lock. * @param compensationContext * Used to save the old VM status, for compensation purposes. */ public void lockVm(final VmDynamic vm, final CompensationContext compensationContext) { TransactionSupport.executeInNewTransaction(() -> { compensationContext.snapshotEntityStatus(vm); lockVm(vm.getId()); compensationContext.stateChanged(); return null; }); } /** * Check VM status before locking it, If VM status is not down, we throw an exception. * * @param status * - The status of the VM */ private static void checkStatusBeforeLock(VMStatus status) { if (status == VMStatus.ImageLocked) { log.error("VM status cannot change to image locked, since it is already locked"); throw new EngineException(EngineError.IRS_IMAGE_STATUS_ILLEGAL); } } /** * Lock VM after check its status, If VM status is locked, we throw an exception. * * @param vmId * - The ID of the VM. */ public void checkStatusAndLockVm(Guid vmId) { VmDynamic vmDynamic = vmDynamicDao.get(vmId); checkStatusBeforeLock(vmDynamic.getStatus()); lockVm(vmId); } /** * Lock VM with compensation, after checking its status, If VM status is locked, we throw an exception. * * @param vmId * - The ID of the VM, which we want to lock. * @param compensationContext * - Used to save the old VM status for compensation purposes. */ public void checkStatusAndLockVm(Guid vmId, CompensationContext compensationContext) { VmDynamic vmDynamic = vmDynamicDao.get(vmId); checkStatusBeforeLock(vmDynamic.getStatus()); lockVm(vmDynamic, compensationContext); } public void lockVm(Guid vmId) { vdsBrokerFrontend.runVdsCommand(VDSCommandType.SetVmStatus, new SetVmStatusVDSCommandParameters(vmId, VMStatus.ImageLocked)); } /** * Unlock the VM in a new transaction, saving compensation data of the old status. * * @param vm * The VM to unlock. * @param compensationContext * Used to save the old VM status, for compensation purposes. */ public void unlockVm(final VM vm, final CompensationContext compensationContext) { TransactionSupport.executeInNewTransaction(() -> { compensationContext.snapshotEntityStatus(vm.getDynamicData()); unLockVm(vm); compensationContext.stateChanged(); return null; }); } public void unLockVm(VM vm) { vdsBrokerFrontend.runVdsCommand(VDSCommandType.SetVmStatus, new SetVmStatusVDSCommandParameters(vm.getId(), VMStatus.Down)); vm.setStatus(VMStatus.Down); } public void updateDisksFromDb(VM vm) { List<Disk> imageList = diskDao.getAllForVm(vm.getId()); vm.clearDisks(); updateDisksForVm(vm, imageList); updateDisksVmDataForVm(vm); } public void updateDisksForVm(VM vm, Collection<? extends Disk> disks) { for (Disk disk : disks) { if (disk.isAllowSnapshot() && !disk.isDiskSnapshot()) { DiskImage image = (DiskImage) disk; vm.getDiskMap().put(image.getId(), image); vm.getDiskList().add(image); } else { vm.getDiskMap().put(disk.getId(), disk); } } } public void updateDisksVmDataForVm(VM vm) { for (Disk disk : vm.getDiskMap().values()) { DiskVmElement dve = diskVmElementDao.get(new VmDeviceId(disk.getId(), vm.getId())); disk.setDiskVmElements(Collections.singletonList(dve)); } } /** * Fetch VmInit from Database * @param vm VmBase to set the VmInit into * @param secure if true don't return any password field * We want to set false only when running VM becase the VmInitDao * decrypt the password. */ public void updateVmInitFromDB(VmBase vm, boolean secure) { vm.setVmInit(vmInitDao.get(vm.getId())); if (vm.getVmInit() != null) { if (secure) { vm.getVmInit().setPasswordAlreadyStored(!StringUtils.isEmpty(vm.getVmInit().getRootPassword())); vm.getVmInit().setRootPassword(null); } else { vm.getVmInit().setPasswordAlreadyStored(false); } } } public void addVmInitToDB(VmBase vm) { if (vm.getVmInit() != null) { vm.getVmInit().setId(vm.getId()); VmInit oldVmInit = vmInitDao.get(vm.getId()); if (oldVmInit == null) { vmInitDao.save(vm.getVmInit()); } else { if (vm.getVmInit().isPasswordAlreadyStored()) { // since we are not always returning the password in // updateVmInitFromDB() // method (we don't want to display it in the UI/API) we // don't want to override // the password if the flag is on vm.getVmInit().setRootPassword(oldVmInit.getRootPassword()); } vmInitDao.update(vm.getVmInit()); } } } public void updateVmInitToDB(VmBase vm) { if (vm.getVmInit() != null) { addVmInitToDB(vm); } else { removeVmInitFromDB(vm); } } public void removeVmInitFromDB(VmBase vm) { vmInitDao.remove(vm.getId()); } public List<VmInit> getVmInitWithoutPasswordByIds(List<Guid> ids) { List<VmInit> all = vmInitDao.getVmInitByIds(ids); for (VmInit vmInit: all) { vmInit.setPasswordAlreadyStored(!StringUtils.isEmpty(vmInit.getRootPassword())); vmInit.setRootPassword(null); } return all; } /** * Filters the vm image disks/disk devices.<BR/> * note: luns will be filtered, only active image disks will be return. */ public void filterImageDisksForVM(VM vm) { List<DiskImage> filteredDisks = DisksFilter.filterImageDisks(vm.getDiskMap().values(), ONLY_ACTIVE); List<CinderDisk> filteredCinderDisks = DisksFilter.filterCinderDisks(vm.getDiskMap().values()); filteredDisks.addAll(filteredCinderDisks); @SuppressWarnings("unchecked") Collection<? extends Disk> vmDisksToRemove = CollectionUtils.subtract(vm.getDiskMap().values(), filteredDisks); vm.clearDisks(); updateDisksForVm(vm, filteredDisks); for (Disk diskToRemove : vmDisksToRemove) { vm.getManagedVmDeviceMap().remove(diskToRemove.getId()); } } public void updateNetworkInterfacesFromDb(VM vm) { List<VmNetworkInterface> interfaces = vmNetworkInterfaceDao.getAllForVm(vm.getId()); vm.setInterfaces(interfaces); } private static Version getApplicationVersion(final String part, final String appName) { try { return new RpmVersion(part, getAppName(part, appName), true); } catch (Exception e) { log.debug("Failed to create rpm version object, part '{}' appName '{}': {}", part, appName, e.getMessage()); log.debug("Exception", e); return null; } } private static String getAppName(final String part, final String appName) { if (StringUtils.contains(part, appName + "64")) { // 64 bit Agent has extension // to its name. return appName + "64"; } return appName; } /** * Updates the {@link VM}'s {@link VM#getGuestAgentVersion()} and {@link VM#getSpiceDriverVersion()} based on the * VM's {@link VM#getAppList()} property. * * @param vm * the VM */ public void updateVmGuestAgentVersion(final VM vm) { if (vm.getAppList() != null) { final String[] parts = vm.getAppList().split("[,]", -1); if (parts.length != 0) { final List<String> possibleAgentAppNames = Config.getValue(ConfigValues.AgentAppName); final Map<String, String> spiceDriversInGuest = Config.getValue(ConfigValues.SpiceDriverNameInGuest); final String spiceDriverInGuest = spiceDriversInGuest.get(osRepository.getOsFamily(vm.getOs()).toLowerCase()); for (final String part : parts) { for (String agentName : possibleAgentAppNames) { if (StringUtils.containsIgnoreCase(part, agentName)) { vm.setGuestAgentVersion(getApplicationVersion(part, agentName)); } if (StringUtils.containsIgnoreCase(part, spiceDriverInGuest)) { vm.setSpiceDriverVersion(getApplicationVersion(part, spiceDriverInGuest)); } } } } } } public void updateVmLock(final VM vm) { vm.setLockInfo(lockManager.getLockInfo(String.format("%s%s", vm.getId(), LockingGroup.VM.name()))); } public void updateOperationProgress(final VM vm) { VmManager vmManager = resourceManager.getVmManager(vm.getId(), false); if (vmManager != null) { vm.setBackgroundOperationDescription(vmManager.getConvertOperationDescription()); vm.setBackgroundOperationProgress(vmManager.getConvertOperationProgress()); } else { vm.setBackgroundOperationDescription(null); vm.setBackgroundOperationProgress(-1); } } public void updateVmStatistics(final VM vm) { VmManager vmManager = resourceManager.getVmManager(vm.getId(), false); if (vmManager != null) { vm.setStatisticsData(vmManager.getStatistics()); } } /** * Checks the validity of the given memory size according to OS type. * * @param vm * a vm|template. * @param clusterVersion * the vm's cluster version. */ public void warnMemorySizeLegal(VmBase vm, Version clusterVersion) { if (! VmValidationUtils.isMemorySizeLegal(vm.getOsId(), vm.getMemSizeMb(), clusterVersion)) { AuditLogable logable = new AuditLogableImpl(); logable.setVmId(vm.getId()); logable.setVmName(vm.getName()); logable.addCustomValue("VmMemInMb", String.valueOf(vm.getMemSizeMb())); logable.addCustomValue("VmMinMemInMb", String.valueOf(VmValidationUtils.getMinMemorySizeInMb(vm.getOsId(), clusterVersion))); logable.addCustomValue("VmMaxMemInMb", String.valueOf(VmValidationUtils.getMaxMemorySizeInMb(vm.getOsId(), clusterVersion))); auditLogDirector.log(logable, AuditLogType.VM_MEMORY_NOT_IN_RECOMMENDED_RANGE); } } public boolean isWindowsVm(VM vm) { return osRepository.isWindows(vm.getOs()); } /** * Check if the OS type is supported. * * @param osId * Type of the OS. * @param architectureType * The architecture type. * @param reasons * The reasons.Cluster */ public boolean isOsTypeSupported(int osId, ArchitectureType architectureType, List<String> reasons) { boolean result = VmValidationUtils.isOsTypeSupported(osId, architectureType); if (!result) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_OS_TYPE_IS_NOT_SUPPORTED_BY_ARCHITECTURE_TYPE .toString()); } return result; } /** * Check if the graphics and display types are supported. * * @param osId * Type of the OS. * @param graphics * Collection of graphics types (SPICE, VNC). * @param displayType * Display type. * @param reasons * The reasons.Cluster * @param clusterVersion * The cluster version. */ public boolean isGraphicsAndDisplaySupported(int osId, Collection<GraphicsType> graphics, DisplayType displayType, List<String> reasons, Version clusterVersion) { boolean result = VmValidationUtils.isGraphicsAndDisplaySupported(osId, clusterVersion, graphics, displayType); if (!result) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_VM_DISPLAY_TYPE_IS_NOT_SUPPORTED_BY_OS.name()); } return result; } /** * Check if the OS type is supported for VirtIO-SCSI. * * @param osId * Type of the OS * @param clusterVersion * Cluster's version * @param reasons * Reasons List */ public static boolean isOsTypeSupportedForVirtioScsi(int osId, Version clusterVersion, List<String> reasons) { boolean result = VmValidationUtils.isDiskInterfaceSupportedByOs(osId, clusterVersion, DiskInterface.VirtIO_SCSI); if (!result) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_OS_TYPE_DOES_NOT_SUPPORT_VIRTIO_SCSI.name()); } return result; } /** * Check if the interface name is not duplicate in the list of interfaces. * * @param interfaces * - List of interfaces the VM/Template got. * @param candidateInterfaceName * - Candidate for interface name. * @param messages * - Messages for Validate(). * @return - True , if name is valid, false, if name already exist. */ public static boolean isNotDuplicateInterfaceName(List<VmNic> interfaces, final String candidateInterfaceName, List<String> messages) { boolean candidateNameUsed = interfaces.stream().anyMatch(i -> i.getName().equals(candidateInterfaceName)); if (candidateNameUsed) { messages.add(EngineMessage.NETWORK_INTERFACE_NAME_ALREADY_IN_USE.name()); return false; } return true; } /** * Checks number of monitors validation according to VM and Graphics types. * * @param graphicsTypes * Collection of graphics types of a VM. * @param numOfMonitors * Number of monitors * @param reasons * Messages for Validate(). */ public boolean isNumOfMonitorsLegal(Collection<GraphicsType> graphicsTypes, int numOfMonitors, List<String> reasons) { boolean legal = false; if (graphicsTypes.contains(GraphicsType.VNC)) { legal = numOfMonitors <= 1; } else if (graphicsTypes.contains(GraphicsType.SPICE)) { // contains spice and doesn't contain vnc legal = numOfMonitors <= getMaxNumberOfMonitors(); } if (!legal) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_NUM_OF_MONITORS.toString()); } return legal; } public boolean isSingleQxlDeviceLegal(DisplayType displayType, int osId, List<String> reasons) { if (displayType != DisplayType.qxl) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_SINGLE_DEVICE_DISPLAY_TYPE.toString()); return false; } if (!osRepository.isSingleQxlDeviceEnabled(osId)) { reasons.add(EngineMessage.ACTION_TYPE_FAILED_ILLEGAL_SINGLE_DEVICE_OS_TYPE.toString()); return false; } return true; } /** * get max of allowed monitors from config config value is a comma separated list of integers */ private static int getMaxNumberOfMonitors() { int max = 0; String numOfMonitorsStr = Config.getValue(ConfigValues.ValidNumOfMonitors).toString().replaceAll("[\\[\\]]", ""); String[] values = numOfMonitorsStr.split(","); for (String text : values) { text = text.trim(); int val = Integer.parseInt(text); if (val > max) { max = val; } } return max; } /** * Returns the vm's active snapshot, or null if one doesn't exist. * Note that this method takes into consideration that the vm snapshots are already loaded from DB. * @param vm The vm to get the active snapshot from. * @return the vm's active snapshot, or null if one doesn't exist. */ public static Snapshot getActiveSnapshot(VM vm) { for (Snapshot snapshot : vm.getSnapshots()) { if (snapshot.getType() == SnapshotType.ACTIVE) { return snapshot; } } return null; } public static ValidationResult canRunActionOnNonManagedVm(VM vm, VdcActionType actionType) { ValidationResult validationResult = ValidationResult.VALID; if (!VmActionByVmOriginTypeValidator.isCommandAllowed(vm, actionType)) { validationResult = new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_CANNOT_RUN_ACTION_ON_NON_MANAGED_VM); } return validationResult; } public void updateCurrentCd(VM vm, String currentCd) { VmDynamic vmDynamic = vm.getDynamicData(); vmDynamic.setCurrentCd(currentCd); vdsBrokerFrontend.runVdsCommand(VDSCommandType.UpdateVmDynamicData, new UpdateVmDynamicDataVDSCommandParameters(vmDynamic)); } public void updateDefaultTimeZone(VmBase vmBase) { if (vmBase.getTimeZone() == null) { if (osRepository.isWindows(vmBase.getOsId())) { vmBase.setTimeZone(Config.getValue(ConfigValues.DefaultWindowsTimeZone)); } else { vmBase.setTimeZone(Config.getValue(ConfigValues.DefaultGeneralTimeZone)); } } } public boolean isUpdateValidForVmDevices(Guid vmId, VMStatus vmStatus, Object objectWithEditableDeviceFields) { if (objectWithEditableDeviceFields == null) { return true; } return getVmDevicesFieldsToUpdateOnNextRun(vmId, vmStatus, objectWithEditableDeviceFields).isEmpty(); } public List<VmDeviceUpdate> getVmDevicesFieldsToUpdateOnNextRun( Guid vmId, VMStatus vmStatus, Object objectWithEditableDeviceFields) { List<VmDeviceUpdate> fieldList = new ArrayList<>(); if (objectWithEditableDeviceFields == null) { return fieldList; } List<Pair<EditableDeviceOnVmStatusField , Field>> pairList = BaseHandler.extractAnnotatedFields( EditableDeviceOnVmStatusField.class, objectWithEditableDeviceFields.getClass()); for (Pair<EditableDeviceOnVmStatusField, Field> pair : pairList) { EditableDeviceOnVmStatusField annotation = pair.getFirst(); Field field = pair.getSecond(); field.setAccessible(true); if (isUpdateValidForVmDevice(field.getName(), vmStatus)) { // field may be updated on the current run, so not including for the next run continue; } try { Object value = field.get(objectWithEditableDeviceFields); if (value == null) { // preserve current configuration } else if (value instanceof Boolean) { addDeviceUpdateOnNextRun(vmId, annotation, null, value, fieldList); } else if (value instanceof VmManagementParametersBase.Optional) { VmManagementParametersBase.Optional<?> optional = (VmManagementParametersBase.Optional<?>) value; if (optional.isUpdate()) { addDeviceUpdateOnNextRun(vmId, annotation, null, optional.getValue(), fieldList); } } else if (value instanceof Map) { Map<?, ?> map = (Map<?, ?>) value; for (Map.Entry<?, ?> entry : map.entrySet()) { boolean success = addDeviceUpdateOnNextRun(vmId, annotation, entry.getKey(), entry.getValue(), fieldList); if (!success) { break; } } } else { log.warn("getVmDevicesFieldsToUpdateOnNextRun: Unsupported field type: " + value.getClass().getName()); } } catch (IllegalAccessException | ClassCastException e) { log.warn("getVmDevicesFieldsToUpdateOnNextRun: Reflection error"); log.debug("Original exception was:", e); } } return fieldList; } private boolean addDeviceUpdateOnNextRun(Guid vmId, EditableDeviceOnVmStatusField annotation, Object key, Object value, List<VmDeviceUpdate> updates) { return addDeviceUpdateOnNextRun(vmId, annotation.generalType(), annotation.type(), annotation.isReadOnly(), annotation.name(), key, value, updates); } private boolean addDeviceUpdateOnNextRun(Guid vmId, VmDeviceGeneralType generalType, VmDeviceType type, boolean readOnly, String name, Object key, Object value, List<VmDeviceUpdate> updates) { if (key != null) { VmDeviceGeneralType keyGeneralType = VmDeviceGeneralType.UNKNOWN; VmDeviceType keyType = VmDeviceType.UNKNOWN; if (key instanceof VmDeviceGeneralType) { keyGeneralType = (VmDeviceGeneralType) key; } else if (key instanceof VmDeviceType) { keyType = (VmDeviceType) key; } else if (key instanceof GraphicsType) { keyType = ((GraphicsType) key).getCorrespondingDeviceType(); } else { log.warn("addDeviceUpdateOnNextRun: Unsupported map key type: " + key.getClass().getName()); return false; } if (keyGeneralType != VmDeviceGeneralType.UNKNOWN) { generalType = keyGeneralType; } if (keyType != VmDeviceType.UNKNOWN) { type = keyType; } } // if device type is set to unknown, search by general type only // because some devices have more than one type, like sound can be ac97/ich6 String typeName = type != VmDeviceType.UNKNOWN ? type.getName() : null; if (value == null) { if (vmDeviceUtils.vmDeviceChanged(vmId, generalType, typeName, false)) { updates.add(new VmDeviceUpdate(generalType, type, readOnly, name, false)); } } else if (value instanceof Boolean) { if (vmDeviceUtils.vmDeviceChanged(vmId, generalType, typeName, (Boolean) value)) { updates.add(new VmDeviceUpdate(generalType, type, readOnly, name, (Boolean) value)); } } else if (value instanceof VmDevice) { if (vmDeviceUtils.vmDeviceChanged(vmId, generalType, typeName, (VmDevice) value)) { updates.add(new VmDeviceUpdate(generalType, type, readOnly, name, (VmDevice) value)); } } else { log.warn("addDeviceUpdateOnNextRun: Unsupported value type: " + value.getClass().getName()); return false; } return true; } public boolean isCpuSupported(int osId, Version version, String cpuName, ArrayList<String> validationMessages) { String cpuId = cpuFlagsManagerHandler.getCpuId(cpuName, version); if (cpuId == null) { validationMessages.add(EngineMessage.CPU_TYPE_UNKNOWN.name()); return false; } if (!osRepository.isCpuSupported( osId, version, cpuId)) { String unsupportedCpus = osRepository.getUnsupportedCpus(osId, version).toString(); validationMessages.add(EngineMessage.CPU_TYPE_UNSUPPORTED_FOR_THE_GUEST_OS.name()); validationMessages.add("$unsupportedCpus " + StringUtils.strip(unsupportedCpus, "[]")); return false; } return true; } public void updateNumaNodesFromDb(VM vm){ List<VmNumaNode> nodes = vmNumaNodeDao.getAllVmNumaNodeByVmId(vm.getId()); vm.setvNumaNodeList(nodes); } public static List<PermissionSubject> getPermissionsNeededToChangeCluster(Guid vmId, Guid clusterId) { List<PermissionSubject> permissionList = new ArrayList<>(); permissionList.add(new PermissionSubject(vmId, VdcObjectType.VM, ActionGroup.EDIT_VM_PROPERTIES)); permissionList.add(new PermissionSubject(clusterId, VdcObjectType.Cluster, ActionGroup.CREATE_VM)); return permissionList; } /** * Returns graphics types of devices VM/Template is supposed to have after adding/updating. * <p/> * When adding, VM/Template inherits graphics devices from the template by default. * When updating, VM/Template has already some graphics devices set. * However - these devices can be customized (overriden) in params * (i.e. params can prevent a device to be inherited from a template). * <p/> * * @return graphics types of devices VM/Template is supposed to have after adding/updating. */ public Set<GraphicsType> getResultingVmGraphics(List<GraphicsType> srcEntityGraphics, Map<GraphicsType, GraphicsDevice> graphicsDevices) { Set<GraphicsType> result = new HashSet<>(); for (GraphicsType type : GraphicsType.values()) { if (graphicsDevices.get(type) != null) { result.add(type); } } if (result.isEmpty()) {// if graphics are set in params, do not use template graphics for (GraphicsType type : GraphicsType.values()) { if (srcEntityGraphics.contains(type) && !graphicsResetInParams(type, graphicsDevices)) { // graphics is in template and is not nulled in params result.add(type); } } } return result; } /** * Returns true if given graphics type was reset in params (that means params contain given graphics device which * is set to null). */ private static boolean graphicsResetInParams(GraphicsType type, Map<GraphicsType, GraphicsDevice> graphicsDevices) { return graphicsDevices.containsKey(type) && graphicsDevices.get(type) == null; } /** * Checks that dedicated host exists on the same cluster as the VM * * @param vm - the VM to check * @param validationMessages - Action messages - used for error reporting. null value indicates that no error messages are required. */ public boolean validateDedicatedVdsExistOnSameCluster(VmBase vm, ArrayList<String> validationMessages) { boolean result = true; for (Guid vdsId : vm.getDedicatedVmForVdsList()) { // get dedicated host, checks if exists and compare its cluster to the VM cluster VDS vds = vdsDao.get(vdsId); if (vds == null) { if (validationMessages != null) { validationMessages.add(EngineMessage.ACTION_TYPE_FAILED_DEDICATED_VDS_DOES_NOT_EXIST.toString()); } result = false; } else if (!Objects.equals(vm.getClusterId(), vds.getClusterId())) { if (validationMessages != null) { validationMessages.add(EngineMessage.ACTION_TYPE_FAILED_DEDICATED_VDS_NOT_IN_SAME_CLUSTER.toString()); } result = false; } } return result; } private static final Pattern TOOLS_PATTERN = Pattern.compile(".*rhev-tools\\s+([\\d\\.]+).*"); // FIXME: currently oVirt-ToolsSetup is not present in app_list when it does // ISO_VERSION_PATTERN should address this pattern as well as the TOOLS_PATTERN // if the name will be different. private static final Pattern ISO_VERSION_PATTERN = Pattern.compile(".*rhev-toolssetup_(\\d\\.\\d\\_\\d).*"); private void updateGuestAgentStatus(VM vm, GuestAgentStatus guestAgentStatus) { if (vm.getGuestAgentStatus() != guestAgentStatus) { vm.setGuestAgentStatus(guestAgentStatus); vmDynamicDao.updateGuestAgentStatus(vm.getId(), vm.getGuestAgentStatus()); } } /** * Looking for "RHEV_Tools x.x.x" in VMs app_list if found we look if there is a newer version in the isoList - if * so we update the GuestAgentStatus of VmDynamic to UpdateNeeded * * @param poolId * storage pool id * @param isoList * list of iso file names */ public void refreshVmsToolsVersion(Guid poolId, Set<String> isoList) { String latestVersion = getLatestGuestToolsVersion(isoList); if (latestVersion == null) { return; } List<VM> vms = vmDao.getAllForStoragePool(poolId); for (VM vm : vms) { if (vm.getAppList() != null && vm.getAppList().toLowerCase().contains("rhev-tools")) { Matcher m = TOOLS_PATTERN.matcher(vm.getAppList().toLowerCase()); if (m.matches() && m.groupCount() > 0) { String toolsVersion = m.group(1); if (toolsVersion.compareTo(latestVersion) < 0) { updateGuestAgentStatus(vm, GuestAgentStatus.UpdateNeeded); } else { updateGuestAgentStatus(vm, GuestAgentStatus.Exists); } } } else { updateGuestAgentStatus(vm, GuestAgentStatus.DoesntExist); } } } /** * iso file name that we are looking for: RHEV_toolsSetup_x.x_x.iso returning latest version only: xxx (ie 3.1.2) * * @param isoList * list of iso file names * @return latest iso version or null if no iso tools was found */ private static String getLatestGuestToolsVersion(Set<String> isoList) { String latestVersion = null; for (String iso: isoList) { if (iso.toLowerCase().contains("rhev-toolssetup")) { Matcher m = ISO_VERSION_PATTERN.matcher(iso.toLowerCase()); if (m.matches() && m.groupCount() > 0) { String isoVersion = m.group(1).replace('_', '.'); if (latestVersion == null) { latestVersion = isoVersion; } else if (latestVersion.compareTo(isoVersion) < 0) { latestVersion = isoVersion; } } } } return latestVersion; } /** * Copy fields that annotated with {@link org.ovirt.engine.core.common.businessentities.CopyOnNewVersion} from the new template version to the vm * * @param source * - template to copy data from * @param dest * - vm to copy data to */ public static boolean copyData(VmBase source, VmBase dest) { for (Field srcFld : VmBase.class.getDeclaredFields()) { try { if (srcFld.getAnnotation(CopyOnNewVersion.class) != null) { srcFld.setAccessible(true); Field dstFld = VmBase.class.getDeclaredField(srcFld.getName()); dstFld.setAccessible(true); dstFld.set(dest, srcFld.get(source)); } } catch (Exception exp) { log.error("Failed to copy field '{}' of new version to VM '{}' ({}): {}", srcFld.getName(), source.getName(), source.getId(), exp.getMessage()); log.debug("Exception", exp); return false; } } return true; } public void autoSelectUsbPolicy(VmBase fromParams) { if (fromParams.getUsbPolicy() == null) { fromParams.setUsbPolicy(UsbPolicy.ENABLED_NATIVE); } } /** * Automatic selection of display type based on its graphics types in parameters. * This method preserves backward compatibility for REST API - legacy REST API doesn't allow to set display and * graphics separately. */ public void autoSelectDefaultDisplayType(Guid srcEntityId, VmBase parametersStaticData, Cluster cluster, Map<GraphicsType, GraphicsDevice> graphicsDevices) { if (parametersStaticData.getOsId() == OsRepository.AUTO_SELECT_OS) { return; } List<Pair<GraphicsType, DisplayType>> graphicsAndDisplays = osRepository.getGraphicsAndDisplays( parametersStaticData.getOsId(), CompatibilityVersionUtils.getEffective(parametersStaticData, cluster)); if (parametersStaticData.getDefaultDisplayType() != null && (parametersStaticData.getDefaultDisplayType() == DisplayType.none || isDisplayTypeSupported(parametersStaticData.getDefaultDisplayType(), graphicsAndDisplays))) { return; } DisplayType defaultDisplayType = null; // map holding display type -> set of supported graphics types for this display type Map<DisplayType, Set<GraphicsType>> displayGraphicsSupport = new LinkedHashMap<>(); for (Pair<GraphicsType, DisplayType> graphicsAndDisplay : graphicsAndDisplays) { DisplayType display = graphicsAndDisplay.getSecond(); if (!displayGraphicsSupport.containsKey(display)) { displayGraphicsSupport.put(display, new HashSet<>()); } displayGraphicsSupport.get(display).add(graphicsAndDisplay.getFirst()); } for (Map.Entry<DisplayType, Set<GraphicsType>> entry : displayGraphicsSupport.entrySet()) { final List<GraphicsType> graphicsTypes = vmDeviceUtils.getGraphicsTypesOfEntity(srcEntityId); final Set<GraphicsType> resultingVmGraphics = getResultingVmGraphics(graphicsTypes, graphicsDevices); if (entry.getValue().containsAll(resultingVmGraphics)) { defaultDisplayType = entry.getKey(); break; } } if (defaultDisplayType == null) { if (!displayGraphicsSupport.isEmpty()) {// when not found otherwise, let's take osinfo's record as the default Map.Entry<DisplayType, Set<GraphicsType>> entry = displayGraphicsSupport.entrySet().iterator().next(); defaultDisplayType = entry.getKey(); } else {// no osinfo record defaultDisplayType = DisplayType.qxl; } } parametersStaticData.setDefaultDisplayType(defaultDisplayType); } public void autoSelectGraphicsDevice(Guid srcEntityId, VmStatic parametersStaticData, Cluster cluster, Map<GraphicsType, GraphicsDevice> graphicsDevices, Version compatibilityVersion) { if (graphicsDevices.isEmpty() // if not set by user in params && cluster != null) { // and Cluster is known DisplayType defaultDisplayType = parametersStaticData.getDefaultDisplayType(); int osId = parametersStaticData.getOsId(); List<GraphicsType> sourceGraphics = vmDeviceUtils.getGraphicsTypesOfEntity(srcEntityId); // if the source graphics device is supported then use it // otherwise choose the first supported graphics device if (!VmValidationUtils.isGraphicsAndDisplaySupported(osId, compatibilityVersion, sourceGraphics, defaultDisplayType)) { GraphicsType defaultGraphicsType = null; List<Pair<GraphicsType, DisplayType>> pairs = osRepository.getGraphicsAndDisplays(osId, compatibilityVersion); for (Pair<GraphicsType, DisplayType> pair : pairs) { if (pair.getSecond().equals(defaultDisplayType)) { defaultGraphicsType = pair.getFirst(); break; } } if (defaultGraphicsType != null) { for (GraphicsType graphicsType : GraphicsType.values()) {// reset graphics devices graphicsDevices.put(graphicsType, null); } VmDeviceType vmDisplayType = defaultGraphicsType.getCorrespondingDeviceType(); GraphicsDevice defaultGraphicsDevice = new GraphicsDevice(vmDisplayType); graphicsDevices.put(defaultGraphicsType, defaultGraphicsDevice); } } } } private static boolean isDisplayTypeSupported(DisplayType displayType, List<Pair<GraphicsType, DisplayType>> graphicsAndDisplays) { for (Pair<GraphicsType, DisplayType> pair : graphicsAndDisplays) { if (displayType.equals(pair.getSecond())) { return true; } } return false; } public static ValidationResult validateMaxMemorySize(VmBase vmBase, Version effectiveCompatibilityVersion) { if (vmBase.getMaxMemorySizeMb() < vmBase.getMemSizeMb()) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_MEMORY_CANNOT_BE_SMALLER_THAN_MEMORY_SIZE, ReplacementUtils.createSetVariableString("maxMemory", vmBase.getMaxMemorySizeMb()), ReplacementUtils.createSetVariableString("memory", vmBase.getMemSizeMb())); } final int maxMemoryUpperBound = VmCommonUtils.maxMemorySizeWithHotplugInMb( vmBase.getOsId(), effectiveCompatibilityVersion); if (vmBase.getMaxMemorySizeMb() > maxMemoryUpperBound) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAX_MEMORY_CANNOT_EXCEED_PLATFORM_LIMIT, ReplacementUtils.createSetVariableString("maxMemory", vmBase.getMaxMemorySizeMb()), ReplacementUtils.createSetVariableString("platformLimit", maxMemoryUpperBound)); } return ValidationResult.VALID; } /** * OvfReader can't provide proper value of {@link VmBase#maxMemorySizeMb} since it depends on effective * compatibility version of target cluster. */ public static void updateMaxMemorySize(VmBase vmBase, Version effectiveCompatibilityVersion) { if (vmBase == null) { return; } final int maxOfMaxMemorySize = VmCommonUtils.maxMemorySizeWithHotplugInMb(vmBase.getOsId(), effectiveCompatibilityVersion); if (vmBase.getMaxMemorySizeMb() > maxOfMaxMemorySize) { vmBase.setMaxMemorySizeMb(maxOfMaxMemorySize); return; } if (vmBase.getMaxMemorySizeMb() == 0) { final int maxMemorySize = Math.min( VmCommonUtils.getMaxMemorySizeDefault(vmBase.getMemSizeMb()), maxOfMaxMemorySize); vmBase.setMaxMemorySizeMb(maxMemorySize); } } /** * @param objectWithEditableDeviceFields object with fields annotated with {@link EditableDeviceOnVmStatusField}, * usually a command parameters object */ public void createNextRunSnapshot( VM existingVm, VmStatic newVmStatic, Object objectWithEditableDeviceFields, CompensationContext compensationContext) { // first remove existing snapshot Snapshot runSnap = snapshotDao.get(existingVm.getId(), Snapshot.SnapshotType.NEXT_RUN); if (runSnap != null) { snapshotDao.remove(runSnap.getId()); } final VM newVm = new VM(); newVm.setStaticData(newVmStatic); // create new snapshot with new configuration snapshotsManager.addSnapshot(Guid.newGuid(), "Next Run configuration snapshot", Snapshot.SnapshotStatus.OK, Snapshot.SnapshotType.NEXT_RUN, newVm, true, StringUtils.EMPTY, Collections.emptyList(), vmDeviceUtils.getVmDevicesForNextRun(existingVm, objectWithEditableDeviceFields, existingVm.getDefaultDisplayType()), compensationContext); } }