package org.ovirt.engine.core.bll.utils; import static java.lang.String.format; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; 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.VmHandler; import org.ovirt.engine.core.bll.network.VmInterfaceManager; import org.ovirt.engine.core.bll.network.macpool.MacPoolPerCluster; import org.ovirt.engine.core.bll.network.macpool.ReadMacPool; import org.ovirt.engine.core.bll.validator.VirtIoRngValidator; import org.ovirt.engine.core.common.FeatureSupported; import org.ovirt.engine.core.common.action.VmManagementParametersBase; import org.ovirt.engine.core.common.businessentities.ChipsetType; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.UsbControllerModel; import org.ovirt.engine.core.common.businessentities.UsbPolicy; import org.ovirt.engine.core.common.businessentities.VM; 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.VmRngDevice; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.VmTemplate; import org.ovirt.engine.core.common.businessentities.VmType; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.BaseDisk; import org.ovirt.engine.core.common.businessentities.storage.Disk; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.utils.SimpleDependencyInjector; import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.common.utils.VmDeviceUpdate; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dao.ClusterDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.VmTemplateDao; import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties; public class VmDeviceUtils { private static final String EHCI_MODEL = "ich9-ehci"; private static final String UHCI_MODEL = "ich9-uhci"; private static final int SLOTS_PER_CONTROLLER = 6; private static final int COMPANION_USB_CONTROLLERS = 3; private static final int VNC_MIN_MONITORS = 1; private static final int SINGLE_QXL_MONITORS = 1; private final VmDao vmDao; private final VmDeviceDao vmDeviceDao; private final ClusterDao clusterDao; private final VmTemplateDao vmTemplateDao; private final VmHandler vmHandler; private final MacPoolPerCluster macPoolPerCluster; private OsRepository osRepository; @Inject VmDeviceUtils(VmDao vmDao, VmDeviceDao vmDeviceDao, ClusterDao clusterDao, VmTemplateDao vmTemplateDao, VmHandler vmHandler, MacPoolPerCluster macPoolPerCluster) { this.vmDao = vmDao; this.vmDeviceDao = vmDeviceDao; this.clusterDao = clusterDao; this.vmTemplateDao = vmTemplateDao; this.vmHandler = vmHandler; this.macPoolPerCluster = macPoolPerCluster; init(); } public void init() { osRepository = SimpleDependencyInjector.getInstance().get(OsRepository.class); } /* * CD-ROM device */ /** * Select the CD path to be used on the destination VM when copying CD device from source to * destination VM. If the CD path on the destination VM is already set to non-empty value, * it is used. Otherwise, the CD path on the source VM is used. If both paths are empty or null, * the result will be an empty string. * * @param srcCdPath CD path on the source VM * @param dstCdPath CD path on the destination VM * @return CD path to be used on the destination VM */ private String getCdPath(String srcCdPath, String dstCdPath) { if (!StringUtils.isEmpty(dstCdPath)) { return dstCdPath; } else if (!StringUtils.isEmpty(srcCdPath)) { return srcCdPath; } else { return ""; } } /** * Determine the bus interface (IDE, SCSI, SATA etc.) to be used for CD device in the given VM. */ public String getCdInterface(VM vm) { return osRepository.getCdInterface( vm.getOs(), vm.getCompatibilityVersion(), ChipsetType.fromMachineType(vm.getEmulatedMachine())); } /** * Copy CD path from old to new VM. * * <b>Note:</b> Only one CD is currently supported. */ private void updateCdPath(VmBase oldVmBase, VmBase newVmBase) { List<VmDevice> cdList = getCdDevices(oldVmBase.getId()); if (cdList.size() > 0) { // this is done only for safety, each VM must have at least an empty CD VmDevice cd = cdList.get(0); // only one managed CD is currently supported. cd.getSpecParams().putAll(getCdDeviceSpecParams("", newVmBase.getIsoPath())); vmDeviceDao.update(cd); } } /** * Get list of all CD-ROM devices in the VM. */ public List<VmDevice> getCdDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.DISK, VmDeviceType.CDROM.getName()); } /** * Check if the VM has a CD-ROM device. */ public boolean hasCdDevice(Guid vmId) { return !getCdDevices(vmId).isEmpty(); } /** * Get CD-ROM device spec params. */ private Map<String, Object> getCdDeviceSpecParams(String srcCdPath, String dstCdPath) { return Collections.singletonMap(VdsProperties.Path, getCdPath(srcCdPath, dstCdPath)); } /** * Add CD-ROM device with given CD path to the VM. */ public VmDevice addCdDevice(Guid vmId, String cdPath) { return addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.DISK, VmDeviceType.CDROM, getCdDeviceSpecParams("", cdPath), true, true); } /** * Add CD-ROM device with empty CD path to the VM. */ public VmDevice addCdDevice(Guid vmId) { return addCdDevice(vmId, ""); } /* * Smartcard device */ /** * Update smartcard device in the new VM, if its state should be different from the old VM. */ private void updateSmartcardDevice(VM oldVm, VmBase newVm) { if (newVm.isSmartcardEnabled() == oldVm.isSmartcardEnabled()) { return; } updateSmartcardDevice(newVm.getId(), newVm.isSmartcardEnabled()); } /** * Enable/disable smartcard device in a VM. * * @param vmId id of the VM to be modified * @param smartcardEnabled enable/disable flag */ public void updateSmartcardDevice(Guid vmId, boolean smartcardEnabled) { if (smartcardEnabled) { if (!hasSmartcardDevice(vmId)) { addSmartcardDevice(vmId); } } else { removeSmartcardDevices(vmId); } } /** * Get list of all smartcard devices in the VM. */ public List<VmDevice> getSmartcardDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.SMARTCARD, VmDeviceType.SMARTCARD.getName()); } /** * Remove all smartcard devices from the VM. */ public void removeSmartcardDevices(Guid vmId) { removeVmDevices(getSmartcardDevices(vmId)); } /** * Check if the VM has a smartcard device. */ public boolean hasSmartcardDevice(Guid vmId) { return !getSmartcardDevices(vmId).isEmpty(); } /** * Add new smartcard device to the VM. */ public VmDevice addSmartcardDevice(Guid vmId) { return addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.SMARTCARD, VmDeviceType.SMARTCARD, getSmartcardDeviceSpecParams(), true, false); } /** * Returns smartcard device spec params. */ private Map<String, Object> getSmartcardDeviceSpecParams() { Map<String, Object> specParams = new HashMap<>(); specParams.put("mode", "passthrough"); specParams.put("type", "spicevmc"); return specParams; } /* * Console device */ /** * Enable/disable console device in the VM. * * @param consoleEnabled true/false to enable/disable device respectively, null to leave it untouched */ public void updateConsoleDevice(Guid vmId, Boolean consoleEnabled) { if (consoleEnabled == null) { return; //we don't want to update the device } if (consoleEnabled) { if (!hasConsoleDevice(vmId)) { addConsoleDevice(vmId); } } else { removeConsoleDevices(vmId); } } /** * Get list of all console devices in the VM. */ public List<VmDevice> getConsoleDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.CONSOLE, VmDeviceType.CONSOLE.getName()); } /** * Remove all console devices from the VM. */ public void removeConsoleDevices(Guid vmId) { removeVmDevices(getConsoleDevices(vmId)); } /** * Check if the VM has a console device. */ public boolean hasConsoleDevice(Guid vmId) { return !getConsoleDevices(vmId).isEmpty(); } /** * Add new console device to the VM. */ public VmDevice addConsoleDevice(Guid vmId) { return addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.CONSOLE, VmDeviceType.CONSOLE, getConsoleDeviceSpecParams(), true, false); } /** * Returns console device spec params. */ private Map<String, Object> getConsoleDeviceSpecParams() { Map<String, Object> specParams = new HashMap<>(); specParams.put("enableSocket", "true"); specParams.put("consoleType", "serial"); return specParams; } /* * VirtIO-SCSI controller */ /** * Enable/disable VirtIO-SCSI controller in the VM. * * @param isVirtioScsiEnabled true/false to enable/disable device respectively, null to leave it untouched */ public void updateVirtioScsiController(VmBase vm, Boolean isVirtioScsiEnabled) { if (isVirtioScsiEnabled == null) { return; //we don't want to update the device } removeVirtioScsiControllers(vm.getId()); if (isVirtioScsiEnabled) { addVirtioScsiController(vm, getVmCompatibilityVersion(vm)); } } /** * Add new VirtIO-SCSI controllers to the VM. */ public void addVirtioScsiController(VmBase vm, Version version) { boolean hasIoThreads = vm.getNumOfIoThreads() > 0 && FeatureSupported.virtioScsiIoThread(version); int numOfScsiControllers = hasIoThreads ? vm.getNumOfIoThreads() : 1; for (int i = 0; i < numOfScsiControllers; i++) { Map<String, Object> specParams = new HashMap<>(); if (hasIoThreads) { // i + 1 because libvirt is indexing the io threads from 1 to N, not 0 to N - 1 specParams.put(VdsProperties.ioThreadId, i + 1); } VmDevice device = addManagedDevice( new VmDeviceId(Guid.newGuid(), vm.getId()), VmDeviceGeneralType.CONTROLLER, VmDeviceType.VIRTIOSCSI, specParams, true, false); } } /** * Get list of all VirtIO-SCSI controllers in the VM. */ public List<VmDevice> getVirtioScsiControllers(Guid vmId) { return getVirtioScsiControllers(vmId, null, false); } /** * Get list of VirtIO-SCSI controllers in the VM. */ public List<VmDevice> getVirtioScsiControllers(Guid vmId, Guid userID, boolean isFiltered) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.CONTROLLER, VmDeviceType.VIRTIOSCSI.getName(), userID, isFiltered); } /** * Remove all VirtIO-SCSI controllers from the VM. */ public void removeVirtioScsiControllers(Guid vmId) { removeVmDevices(getVirtioScsiControllers(vmId)); } /** * Check if the VM has a VirtIO-SCSI controller. */ public boolean hasVirtioScsiController(Guid vmId) { return !getVirtioScsiControllers(vmId).isEmpty(); } /* * Sound device */ /** * Update sound device in the new VM, if its state should be different from the old VM. Recreate the device in any * case, if OS has been changed. * * @param compatibilityVersion cluster compatibility version * @param isSoundDeviceEnabled true/false to enable/disable device respectively, null to leave it untouched */ public void updateSoundDevice(VmBase oldVmBase, VmBase newVmBase, Version compatibilityVersion, Boolean isSoundDeviceEnabled) { boolean osChanged = oldVmBase.getOsId() != newVmBase.getOsId(); updateSoundDevice(newVmBase.getId(), newVmBase.getOsId(), compatibilityVersion, isSoundDeviceEnabled, osChanged); } /** * Enable/disable sound device in the VM. * * @param compatibilityVersion cluster compatibility version * @param isSoundDeviceEnabled true/false to enable/disable device respectively, null to leave it untouched * @param recreate true to recreate the device even if it already exists */ public void updateSoundDevice(Guid vmId, int osId, Version compatibilityVersion, Boolean isSoundDeviceEnabled, boolean recreate) { boolean removeDevice = false; boolean createDevice = false; List<VmDevice> list = getSoundDevices(vmId); if (isSoundDeviceEnabled == null) { if (!list.isEmpty() && recreate) { removeDevice = createDevice = true; } } else { // if sound device is to be disabled or must be recreated, and the device exists, remove it removeDevice = (!isSoundDeviceEnabled || recreate) && !list.isEmpty(); // if sound device is to be enabled or must be recreated, and the device does not exist, create it createDevice = isSoundDeviceEnabled && (list.isEmpty() || recreate); } if (removeDevice) { removeVmDevices(list); } if (createDevice) { addSoundDevice(vmId, osId, compatibilityVersion); } } /** * Get list of all sound devices in the VM. */ public List<VmDevice> getSoundDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.SOUND); } /** * Add new sound device to the VM. */ public VmDevice addSoundDevice(VmBase vmBase) { return addSoundDevice(vmBase.getId(), vmBase.getOsId(), ClusterUtils.getCompatibilityVersion(vmBase)); } /** * Add new sound device to the VM. */ public VmDevice addSoundDevice(Guid vmId, int osId, Version compatibilityVersion) { String soundDevice = osRepository.getSoundDevice(osId, compatibilityVersion); return addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.SOUND, VmDeviceType.getSoundDeviceType(soundDevice), Collections.emptyMap(), true, true); } /** * Check if the VM has a sound device. */ public boolean hasSoundDevice(Guid vmId) { return !getSoundDevices(vmId).isEmpty(); } /** * Checks if a sound device must be added automatically. * * A sound device must be added automatically, if all of the following conditions * are true: * <ul> * <li>User has not specified explicitly to enable/disable the device</li> * <li>Sound device is supported in the given compatibility version</li> * <li>VM is of desktop type</li> * </ul> */ public boolean shouldOverrideSoundDevice(VmStatic vmStatic, Version compatibilityVersion, Boolean soundDeviceEnabled) { return soundDeviceEnabled == null && osRepository.isSoundDeviceEnabled(vmStatic.getOsId(), compatibilityVersion) && vmStatic.getVmType() == VmType.Desktop; } /* * Video device */ public void updateVideoDevices(VmBase oldVmBase, VmBase newVmBase) { boolean displayTypeChanged = oldVmBase.getDefaultDisplayType() != newVmBase.getDefaultDisplayType(); boolean numOfMonitorsChanged = newVmBase.getDefaultDisplayType() == DisplayType.qxl && oldVmBase.getNumOfMonitors() != newVmBase.getNumOfMonitors(); boolean singleQxlChanged = oldVmBase.getSingleQxlPci() != newVmBase.getSingleQxlPci(); boolean guestOsChanged = oldVmBase.getOsId() != newVmBase.getOsId(); if (displayTypeChanged || numOfMonitorsChanged || singleQxlChanged || guestOsChanged) { removeVideoDevices(oldVmBase.getId()); addVideoDevices(newVmBase, getNeededNumberOfVideoDevices(newVmBase)); } else { // fix vm's without video devices addVideoDevicesOnlyIfNoVideoDeviceExists(newVmBase); } } private int getNeededNumberOfVideoDevices(VmBase vmBase) { int maxMonitorsSpice = vmBase.getSingleQxlPci() ? SINGLE_QXL_MONITORS : vmBase.getNumOfMonitors(); int maxMonitorsVnc = Math.max(VNC_MIN_MONITORS, vmBase.getNumOfMonitors()); return Math.min(maxMonitorsSpice, maxMonitorsVnc); } /** * Add given number of video devices to the VM. */ public void addVideoDevices(VmBase vmBase, int numberOfVideoDevices) { if (vmBase.getDefaultDisplayType() != DisplayType.none) { for (int i = 0; i < numberOfVideoDevices; i++) { addManagedDevice( new VmDeviceId(Guid.newGuid(), vmBase.getId()), VmDeviceGeneralType.VIDEO, vmBase.getDefaultDisplayType().getDefaultVmDeviceType(), getVideoDeviceSpecParams(vmBase), true, false); } } } public void addVideoDevicesOnlyIfNoVideoDeviceExists(VmBase vmBase) { if (getVideoDevices(vmBase.getId()).isEmpty()) { addVideoDevices(vmBase, getNeededNumberOfVideoDevices(vmBase)); } } /** * Returns video device spec params. * * @return a map of device parameters */ private Map<String, Object> getVideoDeviceSpecParams(VmBase vmBase) { return VideoDeviceSettings.getVideoDeviceSpecParams(vmBase); } /** * Get list of all video devices in the VM. */ public List<VmDevice> getVideoDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.VIDEO); } /** * Remove all video devices from the VM. */ public void removeVideoDevices(Guid vmId) { removeVmDevices(getVideoDevices(vmId)); } /* * Network interface */ /** * Add new network interface to the VM. * * @param deviceId the NIC id in the VM * @param plugged is NIC plugged to the VM or not * @param hostDev true if NIC is a host device, false if it is a bridge * @return the device added */ public VmDevice addInterface(Guid vmId, Guid deviceId, boolean plugged, boolean hostDev) { return addInterface(vmId, deviceId, plugged, hostDev, null); } /** * Add new network interface to the VM. * * @param deviceId the NIC id in the VM * @param plugged is NIC plugged to the VM or not * @param hostDev true if NIC is a host device, false if it is a bridge * @return the device added */ public VmDevice addInterface(Guid vmId, Guid deviceId, boolean plugged, boolean hostDev, String address) { return addManagedDevice( new VmDeviceId(deviceId, vmId), VmDeviceGeneralType.INTERFACE, hostDev ? VmDeviceType.HOST_DEVICE : VmDeviceType.BRIDGE, Collections.emptyMap(), plugged, false, address, null); } private boolean canPlugInterface(VmNic iface, VmBase vmBase) { ReadMacPool macPool = macPoolPerCluster.getMacPoolForCluster(vmBase.getClusterId()); VmInterfaceManager vmIfaceManager = new VmInterfaceManager(); if (vmIfaceManager.tooManyPluggedInterfaceWithSameMac(iface, macPool)) { vmIfaceManager.auditLogMacInUseUnplug(iface, vmBase.getName()); return false; } else { return true; } } /* * USB controller */ /** * Add given number of sets of USB controllers suitable for SPICE USB redirection to the VM. */ public void addSpiceUsbControllers(Guid vmId, int numberOfControllers) { // For each controller we need to create one EHCI and companion UHCI controllers for (int index = 0; index < numberOfControllers; index++) { addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.CONTROLLER, VmDeviceType.USB, getSpiceUsbControllerSpecParams(EHCI_MODEL, 1, index), true, false); for (int companionIndex = 1; companionIndex <= COMPANION_USB_CONTROLLERS; companionIndex++) { addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.CONTROLLER, VmDeviceType.USB, getSpiceUsbControllerSpecParams(UHCI_MODEL, companionIndex, index), true, false); } } } private Map<String, Object> getSpiceUsbControllerSpecParams(String model, int controllerNumber, int index) { return createUsbControllerSpecParams(model + controllerNumber, index); } /** * Returns USB controller spec params. */ private Map<String, Object> createUsbControllerSpecParams(String model, int index) { final HashMap<String, Object> specParams = new HashMap<>(); specParams.put(VdsProperties.Model, model); specParams.put(VdsProperties.Index, Integer.toString(index)); return specParams; } /** * Get list of all USB controllers in the VM. */ public List<VmDevice> getUsbControllers(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.CONTROLLER, VmDeviceType.USB.getName()); } /** * Remove all USB controllers from the VM. */ public void removeUsbControllers(Guid vmId) { removeVmDevices(getUsbControllers(vmId)); } /* * USB slot */ /** * Update USB slots and controllers in the new VM, if USB policy of the new VM differs from one of the old VM. * @param oldVm old configuration, may not be null, won't be modified * @param newVm new configuration, may not be null, only devices if this entity will be modified */ public void updateUsbSlots(VmBase oldVm, VmBase newVm) { final UsbPolicy oldUsbPolicy = oldVm.getUsbPolicy(); final UsbPolicy newUsbPolicy = newVm.getUsbPolicy(); final int oldNumberOfSlots = getUsbSlots(oldVm.getId()).size(); final int newNumberOfUsbSlots = Config.<Integer> getValue(ConfigValues.NumberOfUSBSlots); if (UsbPolicy.DISABLED == oldUsbPolicy && UsbPolicy.ENABLED_NATIVE == newUsbPolicy) { disableNormalUsb(newVm.getId()); enableSpiceUsb(newVm.getId(), newNumberOfUsbSlots); return; } if (UsbPolicy.ENABLED_NATIVE == oldUsbPolicy && UsbPolicy.ENABLED_NATIVE == newUsbPolicy) { updateSpiceUsb(newVm.getId(), oldNumberOfSlots, newNumberOfUsbSlots); return; } if (UsbPolicy.ENABLED_NATIVE == oldUsbPolicy && UsbPolicy.DISABLED == newUsbPolicy) { disableSpiceUsb(newVm.getId()); enableNormalUsb(newVm); return; } if (UsbPolicy.DISABLED == oldUsbPolicy && UsbPolicy.DISABLED == newUsbPolicy) { updateNormalUsb(newVm); return; } throw new RuntimeException( format("Unexpected state: oldUsbPolicy=%s, newUsbPolicy=%s", oldUsbPolicy, newUsbPolicy)); } private void disableNormalUsb(Guid vmId) { removeUsbControllers(vmId); } private void enableSpiceUsb(Guid vmId, int newNumberOfUsbSlots) { if (newNumberOfUsbSlots > 0) { addSpiceUsbControllers(vmId, getNeededNumberOfUsbControllers(newNumberOfUsbSlots)); addUsbSlots(vmId, newNumberOfUsbSlots); } } private void updateSpiceUsb(Guid vmId, int oldNumberOfSlots, int newNumberOfUsbSlots) { if (oldNumberOfSlots > newNumberOfUsbSlots) { // Remove slots and controllers removeUsbSlots(vmId, oldNumberOfSlots - newNumberOfUsbSlots); if (newNumberOfUsbSlots == 0) { removeUsbControllers(vmId); } return; } if (oldNumberOfSlots < newNumberOfUsbSlots) { // Add slots and controllers if (oldNumberOfSlots == 0) { addSpiceUsbControllers(vmId, getNeededNumberOfUsbControllers(newNumberOfUsbSlots)); } addUsbSlots(vmId, newNumberOfUsbSlots - oldNumberOfSlots); return; } } private void disableSpiceUsb(Guid vmId) { removeUsbControllers(vmId); removeUsbSlots(vmId); removeUsbChannels(vmId); } private void enableNormalUsb(VmBase vmBase) { final UsbControllerModel controllerModel = getUsbControllerModel(vmBase); if (controllerModel == null) { return; } addManagedDevice( new VmDeviceId(Guid.newGuid(), vmBase.getId()), VmDeviceGeneralType.CONTROLLER, VmDeviceType.USB, createUsbControllerSpecParams(controllerModel.libvirtName, 0), true, false); } private void updateNormalUsb(VmBase vmBase) { final Collection<VmDevice> usbControllers = getUsbControllers(vmBase.getId()); final List<VmDevice> unmanagedControllers = usbControllers.stream().filter(d -> !d.isManaged()).collect(Collectors.toList()); final List<VmDevice> managedUsbControllers = usbControllers.stream().filter(VmDevice::isManaged).collect(Collectors.toList()); if (unmanagedControllers.size() > 0) { acquireUnmanagedUsbController(vmBase, managedUsbControllers, unmanagedControllers); return; } final UsbControllerModel controllerModel = getUsbControllerModel(vmBase); if ((managedUsbControllers.isEmpty() && controllerModel == null) || (managedUsbControllers.size() == 1 && controllerModel != null && controllerModel.libvirtName.equals( getUsbControllerModelName(managedUsbControllers.get(0))))) { return; } disableNormalUsb(vmBase.getId()); enableNormalUsb(vmBase); } /** * If there is an existing unmanaged usb controller, it drops all managed ones and acquires it. * * <p>This can be dropped together with support of USB controllers without specified model. Used till engine 3.6. * they may survive as part of long-running VMs and snapshots.</p> */ private void acquireUnmanagedUsbController( VmBase vmBase, List<VmDevice> managedUsbControllers, List<VmDevice> unmanagedControllers) { if (unmanagedControllers.size() > 1) { throw new IllegalStateException(format("At most one unmanaged USB controller expected for VM=%s(%s), found=%s", vmBase.getName(), vmBase.getId(), unmanagedControllers)); } final UsbControllerModel controllerModel = getUsbControllerModel(vmBase); if (unmanagedControllers.isEmpty()) { return; } // should not be here but due to https://bugzilla.redhat.com/1438188 can appear one // remove it removeVmDevices(managedUsbControllers); // has been created on pre 4.0 engine by VDSM, adopt it as ours VmDevice device = unmanagedControllers.iterator().next(); device.setManaged(true); device.setPlugged(true); device.setReadOnly(false); device.setSpecParams(createUsbControllerSpecParams(controllerModel.libvirtName, 0)); vmDeviceDao.update(device); } /** * @return usb controller model defined as defined in osinfo file for VM's OS and effective compatibility version */ /* * TODO: It would be cleaner to return a value denoting unknown model for instance type input since instance types * doesn't actually have any operating system set. Current solution works since no usb controller devices are * created for instance types. */ private UsbControllerModel getUsbControllerModel(VmBase vmBase) { final Version version = vmBase.getCustomCompatibilityVersion() != null ? vmBase.getCustomCompatibilityVersion() : vmBase.getClusterId() != null ? clusterDao.get(vmBase.getClusterId()).getCompatibilityVersion() : null; return osRepository.getOsUsbControllerModel(vmBase.getOsId(), version); } private String getUsbControllerModelName(VmDevice usbControllerDevice) { return (String) usbControllerDevice.getSpecParams().get(VdsProperties.Model); } /** * Returns number of USB controllers that needs to be created for the given number of USB slots. */ private int getNeededNumberOfUsbControllers(int numberOfSlots) { int numberOfControllers = numberOfSlots / SLOTS_PER_CONTROLLER; // Need to add another controller if we have a remainder if (numberOfSlots % SLOTS_PER_CONTROLLER != 0) { numberOfControllers++; } return numberOfControllers; } /** * Add given number of USB slots to the VM */ public void addUsbSlots(Guid vmId, int numberOfSlots) { for (int index = 1; index <= numberOfSlots; index++) { addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.REDIR, VmDeviceType.SPICEVMC, Collections.emptyMap(), true, false); } } /** * Get list of all USB slots in the VM or template. */ public List<VmDevice> getUsbSlots(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.REDIR, VmDeviceType.SPICEVMC.getName()); } public List<VmDevice> getUsbChannels(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.REDIRDEV, VmDeviceType.SPICEVMC.getName()); } /** * Remove all USB slots from the VM. */ public void removeUsbSlots(Guid vmId) { removeVmDevices(getUsbSlots(vmId)); } /** * Remove all USB redir channels. */ public void removeUsbChannels(Guid vmId) { removeVmDevices(getUsbChannels(vmId)); } /** * Remove the given number of USB slots from the VM. */ public void removeUsbSlots(Guid vmId, int numberOfSlotsToRemove) { removeVmDevices(getUsbSlots(vmId), numberOfSlotsToRemove); } /** * Remove unmanaged devices that are no longer necessary in the current configuration. */ public void removeLeftOverDevices(VmBase vmBase) { if (!hasGraphicsDevice(vmBase.getId(), GraphicsType.SPICE)) { // remove spice channel if we are no longer using spice List<VmDevice> spiceChannels = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmBase.getId(), VmDeviceGeneralType.CHANNEL, VmDeviceType.SPICEVMC.getName()); removeVmDevices(spiceChannels); } } /* * Memory balloon device */ /** * Enable/disable memory balloon device in the VM. * * @param isBalloonEnabled true/false to enable/disable device respectively, null to leave it untouched */ public void updateMemoryBalloon(Guid vmId, Boolean isBalloonEnabled) { if (isBalloonEnabled == null) { return; //we don't want to update the device } if (isBalloonEnabled) { if (!hasMemoryBalloon(vmId)) { addMemoryBalloon(vmId); } } else { removeMemoryBalloons(vmId); } } /** * Add new memory balloon device to the VM. */ public VmDevice addMemoryBalloon(Guid vmId) { return addManagedDevice( new VmDeviceId(Guid.newGuid(), vmId), VmDeviceGeneralType.BALLOON, VmDeviceType.MEMBALLOON, getMemoryBalloonSpecParams(), true, true); } private Map<String, Object> getMemoryBalloonSpecParams() { return Collections.singletonMap(VdsProperties.Model, VdsProperties.Virtio); } /** * Get list of all memory balloon devices in the VM. */ public List<VmDevice> getMemoryBalloons(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.BALLOON); } /** * Remove all memory balloon devices from the VM. */ public void removeMemoryBalloons(Guid vmId) { removeVmDevices(getMemoryBalloons(vmId), 1); } /** * Check if the VM has a memory balloon device. */ public boolean hasMemoryBalloon(Guid vmId) { return vmDeviceDao.isMemBalloonEnabled(vmId); } /* * RNG device */ /** * Get list of all RNG devices in the VM. */ public List<VmDevice> getRngDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.RNG); } /** * Check if the VM has an RNG device. */ public boolean hasRngDevice(Guid vmId) { return !getRngDevices(vmId).isEmpty(); } /* * Graphics device */ /** * Get list of graphics device types present in the VM. */ public List<GraphicsType> getGraphicsTypesOfEntity(Guid entityId) { List<GraphicsType> result = new ArrayList<>(); if (entityId != null) { List<VmDevice> devices = getGraphicsDevices(entityId); if (devices != null) { result.addAll(devices.stream().map(device -> GraphicsType.fromString(device.getDevice())).collect(Collectors.toList())); } } return result; } /** * Get list of all graphics devices in the VM. */ public List<VmDevice> getGraphicsDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.GRAPHICS); } /** * Get list of all memory devices in the VM. */ public List<VmDevice> getMemoryDevices(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.MEMORY); } /** * Get list of graphics devices of the given type present in the VM. */ public List<VmDevice> getGraphicsDevices(Guid vmId, GraphicsType type) { return vmDeviceDao.getVmDeviceByVmIdTypeAndDevice( vmId, VmDeviceGeneralType.GRAPHICS, type.name().toLowerCase()); } /** * Check if the VM has a graphics device of the given type. */ public boolean hasGraphicsDevice(Guid vmId, GraphicsType type) { return !getGraphicsDevices(vmId, type).isEmpty(); } /* * Watchdog */ /** * Get list of all watchdogs in the VM. */ public List<VmDevice> getWatchdogs(Guid vmId) { return vmDeviceDao.getVmDeviceByVmIdAndType(vmId, VmDeviceGeneralType.WATCHDOG); } /** * Check if the VM has a watchdog. */ public boolean hasWatchdog(Guid vmId) { return !getWatchdogs(vmId).isEmpty(); } /* * Disk device */ /** * Copy disk devices from the given list of devices to the destination VM. Device ids are changed in accordance * with the mapping given. */ public void copyDiskDevices(Guid dstId, List<VmDevice> srcDevices, Map<Guid, Guid> srcDeviceIdToDstDeviceIdMapping) { for (VmDevice device : srcDevices) { if (VmDeviceType.DISK.getName().equals(device.getDevice())) { if (srcDeviceIdToDstDeviceIdMapping.containsKey(device.getDeviceId())) { Guid dstDeviceId = srcDeviceIdToDstDeviceIdMapping.get(device.getDeviceId()); device.setId(new VmDeviceId(dstDeviceId, dstId)); device.setSpecParams(Collections.emptyMap()); vmDeviceDao.save(device); } } } } /** * Add a new disk device to the VM. */ public VmDevice addDiskDevice(Guid vmId, Guid deviceId) { return addDiskDevice(vmId, deviceId, true, false, ""); } /** * Add a new disk device to the VM. */ public VmDevice addDiskDevice(Guid vmId, Guid deviceId, String address) { return addDiskDevice(vmId, deviceId, true, false, address); } /** * Add a new disk device to the VM. */ public VmDevice addDiskDevice(Guid vmId, Guid deviceId, Boolean isPlugged, Boolean isReadOnly) { return addDiskDevice(vmId, deviceId, isPlugged, isReadOnly, ""); } /** * Add a new disk device to the VM. */ public VmDevice addDiskDevice(Guid vmId, Guid deviceId, Boolean isPlugged, Boolean isReadOnly, String address) { return addManagedDevice( new VmDeviceId(deviceId, vmId), VmDeviceGeneralType.DISK, VmDeviceType.DISK, Collections.emptyMap(), isPlugged, isReadOnly, address, null); } /** * Gets a set of disks from disk images. For VM with snapshots, several DiskImage elements may contain * the same Disk. * * @param diskImages collection of DiskImage objects to get the set of Disks from * @return the resulting set of disks */ public Set<BaseDisk> getDisks(Collection<DiskImage> diskImages) { Map<Guid, BaseDisk> diskMap = new HashMap<>(); for (Disk diskImage : diskImages) { diskMap.put(diskImage.getId(), diskImage); } return new HashSet<>(diskMap.values()); } /* * Generic device methods */ /** * Get address of the given managed device in the VM. If the device does not exist, returns empty string. */ public String getVmDeviceAddress(VmBase vmBase, final Guid deviceId) { VmDevice device = vmBase.getManagedDeviceMap().get(deviceId); if (device != null) { return device.getAddress(); } else { return StringUtils.EMPTY; } } /** * Remove all devices in the list. */ public void removeVmDevices(List<VmDevice> devices) { for (VmDevice device : devices) { vmDeviceDao.remove(device.getId()); } } /** * Remove the given number of devices starting from the end of the list. */ public void removeVmDevices(List<VmDevice> devices, int numberOfDevicesToRemove) { int size = devices.size(); for (int index = 1; index <= numberOfDevicesToRemove; index++) { if (size >= index) { vmDeviceDao.remove(devices.get(size - index).getId()); } } } /** * Read devices from the DB and set managed and unmanaged device lists in VmBase. */ public void setVmDevices(VmBase vmBase) { List<VmDevice> devices = vmDeviceDao.getVmDeviceByVmId(vmBase.getId()); vmBase.setUnmanagedDeviceList(vmDeviceDao.getUnmanagedDevicesByVmId(vmBase.getId())); Map<Guid, VmDevice> vmManagedDeviceMap = new HashMap<>(); for (VmDevice device : devices) { if (device.isManaged()) { vmManagedDeviceMap.put(device.getDeviceId(), device); } } vmBase.setManagedDeviceMap(vmManagedDeviceMap); } /** * Check if the given device is disk or network interface device. */ private boolean isDiskOrInterface(VmDevice vmDevice) { return VmDeviceCommonUtils.isDisk(vmDevice) || VmDeviceCommonUtils.isNetwork(vmDevice); } /** * Update the VM devices according to the changes made. * * @param params changes made * @param oldVm previous state of the VM being modified */ public void updateVmDevices(VmManagementParametersBase params, VM oldVm) { VmBase oldVmBase = oldVm.getStaticData(); VmBase newVmBase = params.getVmStaticData(); if (newVmBase == null) { return; } updateCdPath(oldVmBase, newVmBase); updateVideoDevices(oldVmBase, newVmBase); updateUsbSlots(oldVmBase, newVmBase); updateMemoryBalloon(newVmBase.getId(), params.isBalloonEnabled()); updateSoundDevice(oldVmBase, newVmBase, oldVm.getCompatibilityVersion(), params.isSoundDeviceEnabled()); updateSmartcardDevice(oldVm, newVmBase); updateConsoleDevice(newVmBase.getId(), params.isConsoleEnabled()); updateVirtioScsiController(newVmBase, params.isVirtioScsiEnabled()); } /** * Update the VM devices according to changes made in configuration. * * This method is executed before running the VM. */ public void updateVmDevicesOnRun(VmBase vmBase) { if (vmBase != null) { updateUsbSlots(vmBase, vmBase); removeLeftOverDevices(vmBase); } } /** * Copy devices from the given VmDevice list to the destination VM/VmBase. */ public void copyVmDevices(Guid srcId, Guid dstId, VmBase srcVmBase, VmBase dstVmBase, List<VmDevice> srcDevices, Map<Guid, Guid> srcDeviceIdToDstDeviceIdMapping, boolean isSoundEnabled, boolean isConsoleEnabled, Boolean isVirtioScsiEnabled, boolean isBalloonEnabled, Set<GraphicsType> graphicsToSkip, boolean copySnapshotDevices, boolean copyHostDevices, Version versionToUpdateRngDeviceWith) { if (graphicsToSkip == null) { graphicsToSkip = Collections.emptySet(); } String dstCdPath = dstVmBase.getIsoPath(); boolean dstIsVm = !(dstVmBase instanceof VmTemplate); boolean hasCd = hasCdDevice(dstVmBase.getId()); boolean hasSound = false; boolean hasConsole = false; boolean hasVirtioScsi = false; boolean hasBalloon = false; boolean hasRng = hasRngDevice(dstId); Cluster cluster = null; if (dstVmBase.getClusterId() != null) { cluster = clusterDao.get(dstVmBase.getClusterId()); } for (VmDevice device : srcDevices) { if (device.getSnapshotId() != null && !copySnapshotDevices) { continue; } Guid deviceId = Guid.newGuid(); Map<String, Object> specParams = new HashMap<>(); switch (device.getType()) { case DISK: if (VmDeviceType.DISK.getName().equals(device.getDevice())) { if (srcDeviceIdToDstDeviceIdMapping.containsKey(device.getDeviceId())) { deviceId = srcDeviceIdToDstDeviceIdMapping.get(device.getDeviceId()); } } else if (VmDeviceType.CDROM.getName().equals(device.getDevice())) { if (!hasCd) { hasCd = true; // check here is source VM had CD (VM from snapshot) String srcCdPath = (String) device.getSpecParams().get(VdsProperties.Path); specParams.putAll(getCdDeviceSpecParams(srcCdPath, dstCdPath)); } else { // CD already exists continue; } } break; case INTERFACE: if (srcDeviceIdToDstDeviceIdMapping.containsKey(device.getDeviceId())) { deviceId = srcDeviceIdToDstDeviceIdMapping.get(device.getDeviceId()); } break; case CONTROLLER: if (VmDeviceType.USB.getName().equals(device.getDevice())) { specParams = device.getSpecParams(); } else if (VmDeviceType.VIRTIOSCSI.getName().equals(device.getDevice())) { hasVirtioScsi = true; if (Boolean.FALSE.equals(isVirtioScsiEnabled)) { continue; } } break; case VIDEO: if (dstIsVm) { // Source is template and target is VM. Video devices will be created according // to the new Vm params. continue; } specParams.putAll(getVideoDeviceSpecParams(dstVmBase)); break; case BALLOON: if (!isBalloonEnabled) { continue; } hasBalloon = true; specParams.putAll(getMemoryBalloonSpecParams()); break; case SMARTCARD: specParams.putAll(getSmartcardDeviceSpecParams()); break; case WATCHDOG: specParams.putAll(device.getSpecParams()); break; case RNG: if (hasRng) { continue; } if (!new VirtIoRngValidator().canAddRngDevice( cluster, new VmRngDevice(device), dstVmBase.getCustomCompatibilityVersion()).isValid()) { continue; } final VmRngDevice rngDevice = new VmRngDevice(device); if (versionToUpdateRngDeviceWith != null) { rngDevice.updateSourceByVersion(versionToUpdateRngDeviceWith); } specParams.putAll(rngDevice.getSpecParams()); break; case CONSOLE: if (!isConsoleEnabled) { continue; } specParams.putAll(device.getSpecParams()); hasConsole = true; break; case SOUND: if (!isSoundEnabled) { continue; } hasSound = true; break; case GRAPHICS: GraphicsType type = GraphicsType.fromVmDeviceType(VmDeviceType.getByName(device.getDevice())); // don't add device from the template if it should be skipped (i.e. it's overridden in params) // OR if we already have it if (graphicsToSkip.contains(type) || hasGraphicsDevice(dstId, GraphicsType.fromString(device.getDevice()))) { continue; } break; case HOSTDEV: if (!copyHostDevices) { continue; } specParams.putAll(device.getSpecParams()); break; default: break; } device.setId(new VmDeviceId(deviceId, dstId)); device.setSpecParams(specParams); vmDeviceDao.save(device); } if (!hasCd) { addCdDevice(dstId, dstCdPath); } updateUsbSlots(srcVmBase, dstVmBase); if (isSoundEnabled && !hasSound) { if (dstIsVm) { addSoundDevice(dstVmBase); } else { addSoundDevice(dstVmBase.getId(), dstVmBase.getOsId(), cluster != null ? cluster.getCompatibilityVersion() : null); } } if (isConsoleEnabled && !hasConsole) { addConsoleDevice(dstId); } if (Boolean.TRUE.equals(isVirtioScsiEnabled) && !hasVirtioScsi) { addVirtioScsiController(dstVmBase, getVmCompatibilityVersion(dstVmBase)); } if (isBalloonEnabled && !hasBalloon) { addMemoryBalloon(dstId); } if (dstIsVm) { addVideoDevices(dstVmBase, getNeededNumberOfVideoDevices(dstVmBase)); } } /** * Copy devices from the source to the destination VM. */ public void copyVmDevices(Guid srcId, Guid dstId, Map<Guid, Guid> srcDeviceIdToDstDeviceIdMapping, boolean isSoundEnabled, boolean isConsoleEnabled, Boolean isVirtioScsiEnabled, boolean isBalloonEnabled, Set<GraphicsType> graphicsToSkip, boolean copySnapshotDevices, Version versionToUpdateRndDeviceWith) { VmBase srcVmBase = getVmBase(srcId); VmBase dstVmBase = getVmBase(dstId); List<VmDevice> srcDevices = vmDeviceDao.getVmDeviceByVmId(srcId); copyVmDevices(srcId, dstId, srcVmBase, dstVmBase, srcDevices, srcDeviceIdToDstDeviceIdMapping, isSoundEnabled, isConsoleEnabled, isVirtioScsiEnabled, isBalloonEnabled, graphicsToSkip, copySnapshotDevices, canCopyHostDevices(srcVmBase, dstVmBase), versionToUpdateRndDeviceWith); } /** @see #canCopyHostDevices(VmBase, VmBase) */ public boolean canCopyHostDevices(Guid srcId, VmBase dstVm) { return canCopyHostDevices(getVmBase(srcId), dstVm); } /** * Determines whether it is safe to copy host devices from source VM/Template to destination. * More specifically it checks that no discrepancy between source and destination 'dedicatedVmForVds' occurred, * since host device consistency depends on this value. */ public boolean canCopyHostDevices(VmBase srcVm, VmBase dstVm) { return new HashSet<>(srcVm.getDedicatedVmForVdsList()).equals(new HashSet<>(dstVm.getDedicatedVmForVdsList())); } /** * Returns VmBase object regardless if passed ID is of VM or Template. * * @param vmId ID of a VM or Template * @return VmStatic if a VM of given ID was found, VmTemplate otherwise. */ private VmBase getVmBase(Guid vmId) { VM vm = vmDao.get(vmId); VmBase vmBase = (vm != null) ? vm.getStaticData() : null; if (vmBase == null) { vmBase = vmTemplateDao.get(vmId); } return vmBase; } private Version getVmCompatibilityVersion(VmBase base) { if (base.getCustomCompatibilityVersion() != null) { return base.getCustomCompatibilityVersion(); } if (base.getClusterId() != null) { return clusterDao.get(base.getClusterId()).getCompatibilityVersion(); } return Version.getLast(); } /** * Create new managed device. * * @param id device id * @param generalType device general type * @param type device type * @param specParams device spec params * @param isPlugged is device plugged-in * @param isReadOnly is device read-only * @return newly created VmDevice instance */ public VmDevice addManagedDevice(VmDeviceId id, VmDeviceGeneralType generalType, VmDeviceType type, Map<String, Object> specParams, boolean isPlugged, Boolean isReadOnly) { return addManagedDevice(id, generalType, type, specParams, isPlugged, isReadOnly, "", null); } /** * Create new managed device. * * @param id device id * @param generalType device general type * @param type device type * @param specParams device spec params * @param isPlugged is device plugged-in * @param isReadOnly is device read-only * @param address device address * @param customProps device custom properties * @param isUsingScsiReservation is device using SCSI reservation * @return newly created VmDevice instance */ public VmDevice addManagedDevice(VmDeviceId id, VmDeviceGeneralType generalType, VmDeviceType type, Map<String, Object> specParams, Boolean isPlugged, Boolean isReadOnly, String address, Map<String, String> customProps) { VmDevice managedDevice = new VmDevice( id, generalType, type.getName(), StringUtils.isNotBlank(address) ? address : "", specParams, true, isPlugged, isReadOnly, "", customProps, null, null); vmDeviceDao.save(managedDevice); return managedDevice; } /** * Add devices to an imported VM or template. */ public void addImportedDevices(VmBase vmBase, boolean isImportAsNewEntity) { if (isImportAsNewEntity) { setNewIdInImportedCollections(vmBase); } List<VmDevice> vmDevicesToAdd = new ArrayList<>(); List<VmDevice> vmDevicesToUpdate = new ArrayList<>(); addImportedDiskDevices(vmBase, vmDevicesToUpdate); addImportedInterfaces(vmBase, vmDevicesToUpdate); addImportedOtherDevices(vmBase, vmDevicesToAdd); vmDeviceDao.saveAll(vmDevicesToAdd); vmDeviceDao.updateAll(vmDevicesToUpdate); } /** * Add disk devices to an imported VM or template. * * @param vmDevicesToUpdate list of devices to be updated in the DB */ private void addImportedDiskDevices(VmBase vmBase, List<VmDevice> vmDevicesToUpdate) { final Guid vmId = vmBase.getId(); for (BaseDisk disk : getDisks(vmBase.getImages())) { Guid deviceId = disk.getId(); VmDevice vmDevice = addDiskDevice(vmId, deviceId, getVmDeviceAddress(vmBase, vmId)); updateImportedVmDevice(vmBase, vmDevice, deviceId, vmDevicesToUpdate); } } /** * Add network interfaces to an imported VM or template. * * @param vmDevicesToUpdate list of devices to be updated in the DB */ private void addImportedInterfaces(VmBase vmBase, List<VmDevice> vmDevicesToUpdate) { for (VmNic iface : vmBase.getInterfaces()) { Guid deviceId = iface.getId(); VmDevice vmDevice = addInterface(vmBase.getId(), deviceId, true, iface.isPassthrough(), getVmDeviceAddress(vmBase, deviceId)); VmDevice exportedDevice = vmBase.getManagedDeviceMap().get(deviceId); if (exportedDevice == null) { vmBase.getManagedDeviceMap().put(deviceId, vmDevice); exportedDevice = vmDevice; } exportedDevice.setPlugged(exportedDevice.isPlugged() && canPlugInterface(iface, vmBase)); updateImportedVmDevice(vmBase, vmDevice, deviceId, vmDevicesToUpdate); } } /** * Add other managed and unmanaged devices to imported VM or template. * * @param vmDeviceToAdd list of devices to be added to the DB */ private void addImportedOtherDevices(VmBase vmBase, List<VmDevice> vmDeviceToAdd) { boolean hasCd = false; for (VmDevice vmDevice : vmBase.getManagedDeviceMap().values()) { switch (vmDevice.getType()) { case DISK: if (VmDeviceType.CDROM.getName().equals(vmDevice.getDevice())) { hasCd = true; } else { // disks are added separately continue; } break; case INTERFACE: // network interfaces are added separately continue; case VIDEO: vmDevice.setSpecParams(getVideoDeviceSpecParams(vmBase)); break; case HOSTDEV: // it is currently unsafe to import host devices, due to possibility of invalid dedicatedVmForVds continue; } vmDevice.setManaged(true); vmDeviceToAdd.add(vmDevice); } if (!hasCd) { // add an empty CD addCdDevice(vmBase.getId()); } // add unmanaged devices vmDeviceToAdd.addAll(vmBase.getUnmanagedDeviceList()); } /** * Set common properties in a device for imported VM or template and add the device * to the list of devices to be updated in the DB. * * @param vmDevicesToUpdate list of devices to be updated in the DB */ private void updateImportedVmDevice(VmBase vmBase, VmDevice vmDevice, Guid deviceId, List<VmDevice> vmDevicesToUpdate) { VmDevice exportedDevice = vmBase.getManagedDeviceMap().get(deviceId); if (exportedDevice != null) { vmDevice.setAddress(exportedDevice.getAddress()); vmDevice.setPlugged(exportedDevice.isPlugged()); vmDevice.setReadOnly(exportedDevice.getReadOnly()); vmDevicesToUpdate.add(vmDevice); } } /** * Set newly generated ids for all devices in the VM, except disks and network interfaces. */ private void setNewIdInImportedCollections(VmBase vmBase) { for (VmDevice managedDevice : vmBase.getManagedDeviceMap().values()) { if (!isDiskOrInterface(managedDevice)) { managedDevice.setId(new VmDeviceId(Guid.newGuid(), vmBase.getId())); } } for (VmDevice unmanagedDevice : vmBase.getUnmanagedDeviceList()) { unmanagedDevice.setId(new VmDeviceId(Guid.newGuid(), vmBase.getId())); } } /** * Determines whether a VM device change has been requested by the user. * * @param deviceGeneralType VmDeviceGeneralType. * @param deviceTypeName VmDeviceType name. * @param deviceEnabled indicates whether the user asked to enable the device. * @return true if a change has been requested; otherwise, false */ public boolean vmDeviceChanged(Guid vmId, VmDeviceGeneralType deviceGeneralType, String deviceTypeName, boolean deviceEnabled) { List<VmDevice> vmDevices = deviceTypeName != null ? vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(vmId, deviceGeneralType, deviceTypeName): vmDeviceDao.getVmDeviceByVmIdAndType(vmId, deviceGeneralType); return deviceEnabled == vmDevices.isEmpty(); } /** * Determines whether a VM device change has been requested by the user. * * @param deviceGeneralType VmDeviceGeneralType. * @param deviceTypeName VmDeviceType name. * @param device device object provided by user * @return true if a change has been requested; otherwise, false */ public boolean vmDeviceChanged(Guid vmId, VmDeviceGeneralType deviceGeneralType, String deviceTypeName, VmDevice device) { List<VmDevice> vmDevices = deviceTypeName != null ? vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(vmId, deviceGeneralType, deviceTypeName): vmDeviceDao.getVmDeviceByVmIdAndType(vmId, deviceGeneralType); if (device == null) { return !vmDevices.isEmpty(); } if (vmDevices.isEmpty()) { // && device != null return true; } if (device.getSpecParams() != null) { // if device.getSpecParams() == null, it is not used for comparison for (VmDevice vmDevice : vmDevices) { if (!vmDevice.getSpecParams().equals(device.getSpecParams())) { return true; } } } return false; } /** * Returns a map (device ID to VmDevice) of devices that are relevant for next run. * * The list of devices is built by examining properties that are annotated with EditableDeviceOnVmStatusField * annotation. * * @param vm the relevant VM * @param objectWithEditableDeviceFields object that contains properties which are annotated with * EditableDeviceOnVmStatusField (e.g. parameters file) * @param vmVideoDeviceTypeforNextRun VM video device type for next run * @return a map of device ID to VmDevice */ public Map<Guid, VmDevice> getVmDevicesForNextRun(VM vm, Object objectWithEditableDeviceFields, DisplayType vmVideoDeviceTypeforNextRun) { setVmDevices(vm.getStaticData()); Map<Guid, VmDevice> vmManagedDeviceMap = vm.getManagedVmDeviceMap(); List<VmDeviceUpdate> fieldList = vmHandler.getVmDevicesFieldsToUpdateOnNextRun(vm.getId(), vm.getStatus(), objectWithEditableDeviceFields); // Add the enabled devices and remove the disabled ones for (VmDeviceUpdate update : fieldList) { if (update.isEnable()) { VmDevice device; if (update.getDevice() == null) { device = new VmDevice( new VmDeviceId(Guid.newGuid(), vm.getId()), update.getGeneralType(), update.getType().getName(), "", Collections.emptyMap(), true, true, update.isReadOnly(), "", null, null, null); } else { device = update.getDevice(); if (device.getVmId() == null) { device.setVmId(vm.getId()); } if (device.getDeviceId() == null) { if (device.getType() == VmDeviceGeneralType.RNG) { // only one RNG device allowed VmDevice rng = VmDeviceCommonUtils.findVmDeviceByType(vmManagedDeviceMap, update.getType()); device.setDeviceId(rng.getDeviceId()); } else { device.setDeviceId(Guid.newGuid()); } } } vmManagedDeviceMap.put(device.getDeviceId(), device); } else { VmDevice device; if (update.getType() != VmDeviceType.UNKNOWN) { device = VmDeviceCommonUtils.findVmDeviceByType(vmManagedDeviceMap, update.getType()); } else { device = VmDeviceCommonUtils.findVmDeviceByGeneralType(vmManagedDeviceMap, update.getGeneralType()); } if (device != null) { vmManagedDeviceMap.remove(device.getDeviceId()); } } } // @TODO - this was added to handle the headless VM since the VIDEO devices were added anyway with the DB value instead of the // new configuration value. Should be handled correctly while the task of removing the static.displaytype and handling the VIDEO device // as all other devices will be done if (vmVideoDeviceTypeforNextRun == DisplayType.none) { vmManagedDeviceMap = vmManagedDeviceMap.entrySet() .stream().filter(entry -> !entry.getValue().getType().equals(VmDeviceGeneralType.VIDEO)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } else if (vm.getDefaultDisplayType() == DisplayType.none) { VmDevice videoDevice = new VmDevice( new VmDeviceId(Guid.newGuid(), vm.getId()), VmDeviceGeneralType.VIDEO, vmVideoDeviceTypeforNextRun.toString(), "", Collections.emptyMap(), true, true, false, "", null, null, null); vmManagedDeviceMap.put(videoDevice.getDeviceId(), videoDevice); } return vmManagedDeviceMap; } public <E extends VmDevice> Map<String, E> vmDevicesByDevice(Collection<E> deviceList) { return deviceList == null ? Collections.emptyMap() : deviceList.stream() .filter(dev -> dev.getDevice() != null) .collect(Collectors.toMap(VmDevice::getDevice, Function.identity())); } }