package org.ovirt.engine.core.bll.hostdev; import static org.ovirt.engine.core.utils.collections.MultiValueMapUtils.addToMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import javax.inject.Inject; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.RefreshHostInfoCommandBase; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.host.NetworkDeviceHelper; import org.ovirt.engine.core.common.action.VdsActionParameters; import org.ovirt.engine.core.common.businessentities.Entities; import org.ovirt.engine.core.common.businessentities.HostDevice; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.network.HostNicVfsConfig; import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.VdsIdAndVdsVDSCommandParametersBase; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.HostDeviceDao; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.network.HostNicVfsConfigDao; import org.ovirt.engine.core.dao.network.InterfaceDao; import org.ovirt.engine.core.utils.collections.MultiValueMapUtils; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties; @NonTransactiveCommandAttribute public class RefreshHostDevicesCommand<T extends VdsActionParameters> extends RefreshHostInfoCommandBase<T> { @Inject private ResourceManager resourceManager; @Inject private HostDeviceDao hostDeviceDao; @Inject private HostDeviceManager hostDeviceManager; @Inject private HostNicVfsConfigDao hostNicVfsConfigDao; @Inject private NetworkDeviceHelper networkDeviceHelper; @Inject private VmDeviceDao vmDeviceDao; @Inject private InterfaceDao interfaceDao; private Map<String, HostDevice> fetchedMap; private Map<String, List<VmDevice>> attachedVmDevicesMap; public RefreshHostDevicesCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected void executeCommand() { VDSReturnValue vdsReturnValue = resourceManager.runVdsCommand(VDSCommandType.HostDevListByCaps, new VdsIdAndVdsVDSCommandParametersBase(getVds())); if (!vdsReturnValue.getSucceeded()) { return; } List<HostDevice> fetchedDevices = (List<HostDevice>) vdsReturnValue.getReturnValue(); List<HostDevice> oldDevices = hostDeviceDao.getHostDevicesByHostId(getVdsId()); Map<String, HostDevice> oldMap = Entities.entitiesByName(oldDevices); fetchedMap = filterOrphanedDevices(Entities.entitiesByName(fetchedDevices)); final List<HostDevice> newDevices = new ArrayList<>(); final List<HostDevice> changedDevices = new ArrayList<>(); for (Map.Entry<String, HostDevice> entry : fetchedMap.entrySet()) { HostDevice device = entry.getValue(); if (oldMap.containsKey(entry.getKey())) { if (!oldMap.get(entry.getKey()).equals(device)) { changedDevices.add(device); } } else { newDevices.add(device); } } final List<HostDevice> removedDevices = new ArrayList<>(); final List<VmDevice> removedVmDevices = new ArrayList<>(); for (Map.Entry<String, HostDevice> entry : oldMap.entrySet()) { final String deviceName = entry.getKey(); if (!fetchedMap.containsKey(deviceName)) { removedDevices.add(entry.getValue()); if (getAttachedVmDevicesMap().containsKey(deviceName)) { List<VmDevice> vmDevices = getAttachedVmDevicesMap().get(deviceName); for (VmDevice vmDevice : vmDevices) { log.warn("Removing VM[{}]'s hostDevice[{}] because it no longer exists on host {}", vmDevice.getVmId(), deviceName, getVds()); removedVmDevices.add(vmDevice); } } } } try { hostDeviceManager.acquireHostDevicesLock(getVdsId()); TransactionSupport.executeInNewTransaction(() -> { hostDeviceDao.saveAllInBatch(newDevices); hostDeviceDao.updateAllInBatch(changedDevices); hostDeviceDao.removeAllInBatch(removedDevices); handleHostNicVfsConfigUpdate(); vmDeviceDao.removeAllInBatch(removedVmDevices); return null; }); } finally { hostDeviceManager.releaseHostDevicesLock(getVdsId()); } setSucceeded(true); } /** * Filters out devices which may be orphaned (their parent is no longer included in device list) * or otherwise invalid (parent is null or empty). * * This is done transitively by using DFS started in the root device (computer) and * adding only reachable devices via the "parent device" relationship. */ static Map<String, HostDevice> filterOrphanedDevices(Map<String, HostDevice> fetchedDevicesMap) { if (!fetchedDevicesMap.containsKey(VdsProperties.ROOT_HOST_DEVICE)) { // if there is no root, nothing can be reachable from root return Collections.emptyMap(); } Map<String, List<String>> childrenDeviceMap = new HashMap<>(); // aggregate inverse information: parent -> list of children for (Map.Entry<String, HostDevice> entry : fetchedDevicesMap.entrySet()) { String deviceName = entry.getKey(); HostDevice device = entry.getValue(); MultiValueMapUtils.addToMap(device.getParentDeviceName(), deviceName, childrenDeviceMap); } Stack<String> toTraverse = new Stack<>(); toTraverse.push(VdsProperties.ROOT_HOST_DEVICE); Map<String, HostDevice> result = new HashMap<>(); while (!toTraverse.empty()) { String deviceName = toTraverse.pop(); result.put(deviceName, fetchedDevicesMap.get(deviceName)); if (childrenDeviceMap.containsKey(deviceName)) { childrenDeviceMap.get(deviceName).stream() // prevent infinite loop by adding root again as a child of root .filter(child -> !VdsProperties.ROOT_HOST_DEVICE.equals(child)) .forEach(toTraverse::push); } } return result; } /** * Returns lazily computed map of device names -> list of vm devices representing device attachments for this host. */ private Map<String, List<VmDevice>> getAttachedVmDevicesMap() { if (attachedVmDevicesMap == null) { attachedVmDevicesMap = new HashMap<>(); List<VmDevice> vmDevices = hostDeviceDao.getVmDevicesAttachedToHost(getVdsId()); for (VmDevice vmDevice : vmDevices) { addToMap(vmDevice.getDevice(), vmDevice, attachedVmDevicesMap); } } return attachedVmDevicesMap; } private void handleHostNicVfsConfigUpdate() { removeInvalidHostNicVfsConfigsFromDb(); addMissingHostNicVfsConfigsToDb(); } private void removeInvalidHostNicVfsConfigsFromDb() { final List<HostNicVfsConfig> hostNicVfsConfigsToRemove = new ArrayList<>(); List<HostNicVfsConfig> hostNicVfsConfigs = hostNicVfsConfigDao.getAllVfsConfigByHostId(getVdsId()); for (HostNicVfsConfig hostNicVfsConfig : hostNicVfsConfigs) { VdsNetworkInterface nic = interfaceDao.get(hostNicVfsConfig.getNicId()); HostDevice pciDevice = null; if (nic != null) { String pciDeviceName = networkDeviceHelper.getPciDeviceNameByNic(nic); pciDevice = fetchedMap.get(pciDeviceName); } if (nic == null || pciDevice == null || !networkDeviceHelper.isSriovDevice(pciDevice)) { addToListIfNotNull(hostNicVfsConfig, hostNicVfsConfigsToRemove); } } if (!hostNicVfsConfigsToRemove.isEmpty()) { hostNicVfsConfigDao.removeAllInBatch(hostNicVfsConfigsToRemove); } } private void addMissingHostNicVfsConfigsToDb() { final List<HostNicVfsConfig> hostNicVfsConfigsToAdd = new ArrayList<>(); for (HostDevice device : fetchedMap.values()) { if (networkDeviceHelper.isSriovDevice(device)) { addToListIfNotNull(createHostNicVfsConfigToAddIfNotExist(device), hostNicVfsConfigsToAdd); } } if (!hostNicVfsConfigsToAdd.isEmpty()) { hostNicVfsConfigDao.saveAllInBatch(hostNicVfsConfigsToAdd); } } private <E> void addToListIfNotNull(E element, Collection<E> collection) { if (element != null) { collection.add(element); } } private HostNicVfsConfig createHostNicVfsConfigToAddIfNotExist(HostDevice device) { VdsNetworkInterface nic = networkDeviceHelper.getNicByPciDevice(device, fetchedMap.values()); if (nic == null) { return null; } HostNicVfsConfig existingHostNicVfsConfig = hostNicVfsConfigDao.getByNicId(nic.getId()); if (existingHostNicVfsConfig != null) { return null; } return new HostNicVfsConfig(Guid.newGuid(), nic.getId(), true); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__REFRESH); addValidationMessage(EngineMessage.VAR__TYPE__HOST_DEVICES); } }