package org.ovirt.engine.ui.uicommonweb.models.hosts.numa; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VmNumaNodeOperationParameters; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VdsNumaNode; import org.ovirt.engine.core.common.businessentities.VmNumaNode; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.ui.uicommonweb.UICommand; import org.ovirt.engine.ui.uicommonweb.dataprovider.AsyncDataProvider; import org.ovirt.engine.ui.uicommonweb.help.HelpTag; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicommonweb.models.Model; import org.ovirt.engine.ui.uicompat.ConstantsManager; import org.ovirt.engine.ui.uicompat.Event; import org.ovirt.engine.ui.uicompat.EventArgs; import org.ovirt.engine.ui.uicompat.EventDefinition; public class NumaSupportModel extends Model { public static final String SUBMIT_NUMA_SUPPORT = "SubmitNumaSupport"; //$NON-NLS-1$ private final Model parentModel; private ListModel<VDS> hosts; private List<VdsNumaNode> numaNodeList; private List<VM> vmsWithvNumaNodeList; private Set<VNodeModel> unassignedNumaNodes; protected Map<Integer, Set<VNodeModel>> assignedNumaNodes; private List<Pair<Integer, Set<VdsNumaNode>>> firstLevelDistanceSetList; private final Event modelReady = new Event(new EventDefinition("ModelReady", NumaSupportModel.class)); //$NON-NLS-1$ private Map<Integer, VdsNumaNode> indexNodeMap; private Map <Guid, Map<Integer, VNodeModel>> numaModelsPerVm = new HashMap<>(); private Set <Guid> vmsToUpdate = new HashSet<>(); public NumaSupportModel(List<VDS> hosts, VDS host, Model parentModel) { this.parentModel = parentModel; setHosts(new ListModel<VDS>()); setNumaNodeList(new ArrayList<VdsNumaNode>()); setTitle(ConstantsManager.getInstance().getMessages().numaTopologyTitle(host.getName())); setHelpTag(HelpTag.numa_support); setHashName("numa_support"); //$NON-NLS-1$ initCommands(); initHosts(hosts, host); } private void initHosts(List<VDS> hosts, VDS host) { getHosts().setItems(hosts); if (host == null) { host = hosts.get(0); } if (getHosts().getItems().size() <= 1) { getHosts().setIsChangeable(false); } getHosts().getSelectedItemChangedEvent().addListener((ev, sender, args) -> initHostNUMATopology()); getHosts().setSelectedItem(host); } protected void initCommands() { UICommand command = new UICommand(SUBMIT_NUMA_SUPPORT, this); command.setTitle(ConstantsManager.getInstance().getConstants().ok()); getCommands().add(command); getCommands().add(UICommand.createDefaultCancelUiCommand("Cancel", this)); //$NON-NLS-1$ } protected void initHostNUMATopology() { startProgress(); AsyncDataProvider.getInstance().getHostNumaTopologyByHostId(new AsyncQuery<>(returnValue -> { // TODO: host query can be skipped in case it was already fetched. getNumaNodeList().addAll(returnValue); initFirstLevelDistanceSetList(); AsyncDataProvider.getInstance().getVMsWithVNumaNodesByClusterId(new AsyncQuery<>(returnValue1 -> { setVmsWithvNumaNodeList(returnValue1); initVNumaNodes(); modelReady(); }), hosts.getSelectedItem().getClusterId()); }), hosts.getSelectedItem().getId()); } protected void initVNumaNodes() { unassignedNumaNodes = new LinkedHashSet<>(); assignedNumaNodes = new HashMap<>(); final Set<Integer> hostIndices = new HashSet<>(); for (VdsNumaNode numaNode : numaNodeList) { hostIndices.add(numaNode.getIndex()); } for (final VM vm : getVmsWithvNumaNodeList()) { numaModelsPerVm.put(vm.getId(), new HashMap<Integer, VNodeModel>()); for (VmNumaNode vmNumaNode : vm.getvNumaNodeList()) { VNodeModel vNodeModel = new VNodeModel(vm, vmNumaNode); numaModelsPerVm.get(vm.getId()).put(vNodeModel.getIndex(), vNodeModel); if (vNodeModel.isPinned()) { if (!hostIndices.contains(vNodeModel.getHostNodeIndex())) { // host numa node does not exist. Unpin the numa node and update the configuration vNodeModel.unpin(); vmsToUpdate.add(vm.getId()); } } if (!vNodeModel.isPinned()) { // virtual numa node is not assigned to any host numa node unassignedNumaNodes.add(vNodeModel); } else { // virtual numa node is assigned to a host numa node assignVNumaToPhysicalNuma(vNodeModel); } } } } private void assignVNumaToPhysicalNuma(VNodeModel vNodeModel) { final Integer hostNodeIndex = vNodeModel.getHostNodeIndex(); if (!assignedNumaNodes.containsKey(hostNodeIndex)) { assignedNumaNodes.put(hostNodeIndex, new LinkedHashSet<VNodeModel>()); } assignedNumaNodes.get(hostNodeIndex).add(vNodeModel); } private VdsNumaNode getNodeByIndex(Integer index) { if (indexNodeMap == null) { indexNodeMap = new HashMap<>(); for (VdsNumaNode node : getNumaNodeList()) { indexNodeMap.put(node.getIndex(), node); } } return indexNodeMap.get(index); } public Collection<VNodeModel> getVNumaNodeByNodeIndx(Integer nodeIdx) { return assignedNumaNodes.get(nodeIdx); } private void initFirstLevelDistanceSetList() { firstLevelDistanceSetList = new ArrayList<>(); for (VdsNumaNode node : getNumaNodeList()) { Map<Integer, Set<VdsNumaNode>> distances = new HashMap<>(); for (Entry<Integer, Integer> entry : node.getNumaNodeDistances().entrySet()) { Set<VdsNumaNode> sameDistanceNodes = distances.get(entry.getValue()); if (sameDistanceNodes == null) { sameDistanceNodes = new HashSet<>(); sameDistanceNodes.add(getNodeByIndex(entry.getKey())); distances.put(entry.getValue(), sameDistanceNodes); } sameDistanceNodes.add(node); } Entry<Integer, Set<VdsNumaNode>> minDistance = null; for (Entry<Integer, Set<VdsNumaNode>> entry : distances.entrySet()) { if (minDistance == null || entry.getKey() < minDistance.getKey()) { minDistance = entry; } } boolean found = false; for (Pair<Integer, Set<VdsNumaNode>> group : firstLevelDistanceSetList) { // 'true' if the two specified collections have no elements in common boolean isDisjoint = Collections.disjoint(group.getSecond(), minDistance.getValue()); if (group.getFirst().equals(minDistance.getKey()) && !isDisjoint) { group.getSecond().addAll(minDistance.getValue()); found = true; break; } } if (!found && minDistance != null) { firstLevelDistanceSetList.add(new Pair<>(minDistance.getKey(), minDistance.getValue())); } } } private void modelReady() { getModelReady().raise(this, EventArgs.EMPTY); stopProgress(); } public void cancel() { parentModel.setWindow(null); } public Model getParentModel() { return parentModel; } public List<VdsNumaNode> getNumaNodeList() { return numaNodeList; } public void setNumaNodeList(List<VdsNumaNode> numaNodeList) { this.numaNodeList = numaNodeList; } public List<VM> getVmsWithvNumaNodeList() { return vmsWithvNumaNodeList; } public void setVmsWithvNumaNodeList(List<VM> vmsWithvNumaNodeList) { for (Iterator<VM> vmIterator = vmsWithvNumaNodeList.iterator(); vmIterator.hasNext(); ) { final VM vm = vmIterator.next(); if (vm.getDedicatedVmForVdsList().isEmpty() || !vm.getDedicatedVmForVdsList().get(0).equals(getHosts().getSelectedItem().getId()) || vm.getvNumaNodeList() == null) { vmIterator.remove(); } } this.vmsWithvNumaNodeList = vmsWithvNumaNodeList; } public Collection<VNodeModel> getUnassignedNumaNodes() { return unassignedNumaNodes; } public List<Pair<Integer, Set<VdsNumaNode>>> getFirstLevelDistanceSetList() { return firstLevelDistanceSetList; } public Event getModelReady() { return modelReady; } public ListModel<VDS> getHosts() { return hosts; } public void setHosts(ListModel<VDS> hosts) { this.hosts = hosts; } @Override public void executeCommand(UICommand command) { super.executeCommand(command); if (SUBMIT_NUMA_SUPPORT.equals(command.getName())) { parentModel.executeCommand(command); parentModel.setWindow(null); } else if ("Cancel".equals(command.getName())) { //$NON-NLS-1$ cancel(); } } /** * Dragging virtual numa nodes to host numa nodes emits a pin event. The pin event calls this callback. * @param sourceVMGuid Guid of the VM the numa node belongs to * @param sourceVNumaIndex Index of the VM numa node * @param pNumaNodeIndex Index of the host numa node */ public void pinVNode(Guid sourceVMGuid, int sourceVNumaIndex, int pNumaNodeIndex) { VNodeModel vNodeModel = getNodeModel(sourceVMGuid, sourceVNumaIndex); if (vNodeModel.isPinned()) { assignedNumaNodes.get(vNodeModel.getHostNodeIndex()).remove(vNodeModel); } else { unassignedNumaNodes.remove(vNodeModel); } vmsToUpdate.add(vNodeModel.getVm().getId()); vNodeModel.pinTo(pNumaNodeIndex); assignVNumaToPhysicalNuma(vNodeModel); modelReady(); } /** * Moving an virtual numa node away from a host numa node triggers an unpin event. The unpin event calls this * method. * @param sourceVMGuid Guid of the VM * @param sourceVNumaIndex Index of the VM numa node */ public void unpinVNode(Guid sourceVMGuid, int sourceVNumaIndex) { VNodeModel vNodeModel = getNodeModel(sourceVMGuid, sourceVNumaIndex); if (vNodeModel.isPinned()) { assignedNumaNodes.get(vNodeModel.getHostNodeIndex()).remove(vNodeModel); unassignedNumaNodes.add(vNodeModel); } vNodeModel.unpin(); vmsToUpdate.add(vNodeModel.getVm().getId()); modelReady(); } /** * Get the current numa node pinning - as currently visible for the user - for a specific VM. * @param vmId guid of the VM * @return List of numa nodes with current mapping */ public List<VmNumaNode> getNumaNodes(Guid vmId) { final List<VmNumaNode> numaNodes = new ArrayList<>(); for (final VNodeModel model : numaModelsPerVm.get(vmId).values()) { numaNodes.add(model.toVmNumaNode()); } return numaNodes; } private VNodeModel getNodeModel(Guid vmId, int nodeIndex) { return numaModelsPerVm.get(vmId).get(nodeIndex); } /** * Return a list of action parameters which contain numa pinning updates for different VMs. * Used when accessing the numa support screen from the host list panel. * @return List of updated numa configurations */ public ArrayList<VdcActionParametersBase> getUpdateParameters() { final ArrayList<VdcActionParametersBase> parameters = new ArrayList<>(); for (Guid vmId : vmsToUpdate) { final List<VmNumaNode> numaNodes = new ArrayList<>(); for (final VNodeModel model : numaModelsPerVm.get(vmId).values()) { numaNodes.add(model.toVmNumaNode()); } parameters.add(new VmNumaNodeOperationParameters(vmId, numaNodes)); } return parameters; } }