package org.ovirt.engine.core.bll.scheduling.policyunits; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.ovirt.engine.core.bll.scheduling.PolicyUnitImpl; import org.ovirt.engine.core.bll.scheduling.SchedulingUnit; import org.ovirt.engine.core.bll.scheduling.pending.PendingResourceManager; import org.ovirt.engine.core.bll.scheduling.selector.SelectorInstance; import org.ovirt.engine.core.common.scheduling.PolicyUnit; import org.ovirt.engine.core.common.scheduling.PolicyUnitType; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.Guid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SchedulingUnit( guid = "b280e7de-5df8-401e-b004-a1414d79a687", name = "RankSelector", type = PolicyUnitType.SELECTOR, description = "The host with lowest accumulated rank wins." ) public class RankSelectorPolicyUnit extends PolicyUnitImpl { public static final Logger log = LoggerFactory.getLogger(RankSelectorPolicyUnit.class); public RankSelectorPolicyUnit(PolicyUnit policyUnit, PendingResourceManager pendingResourceManager) { super(policyUnit, pendingResourceManager); } @Override public SelectorInstance selector(Map<String, String> parameters) { return new Selector(); } public static class Selector implements SelectorInstance { // { PolicyUnit: [{ Host: weight }] } final Map<Guid, List<Pair<Guid, Integer>>> weightTable = new HashMap<>(); // { PolicyUnit: factor } final Map<Guid, Integer> factorTable = new HashMap<>(); List<Guid> hosts; @Override public void init(List<Pair<Guid, Integer>> policyUnits, List<Guid> hosts) { for (Pair<Guid, Integer> pair: policyUnits) { factorTable.put(pair.getFirst(), pair.getSecond()); } this.hosts = Collections.unmodifiableList(hosts); } @Override public void record(Guid policyUnit, Guid host, Integer weight) { weightTable.putIfAbsent(policyUnit, new ArrayList<Pair<Guid, Integer>>()); weightTable.get(policyUnit).add(new Pair<>(host, weight)); } @Override public Optional<Guid> best() { Map<Guid, Integer> scores = new HashMap<>(); StringBuffer debug = new StringBuffer(); if (log.isDebugEnabled()) { // DEBUG header - columns are policy unit id, factor, host weight, host rank, .... debug.append("*;factor"); hosts.forEach(h -> debug.append(String.format(";%s;", h.toString()))); debug.append("\n"); } for (Map.Entry<Guid, List<Pair<Guid, Integer>>> unit: weightTable.entrySet()) { // Retrieve the factor for this policy unit's results Integer factor = factorTable.getOrDefault(unit.getKey(), 1); // Prepare a copy of weights for local purposes Map<Guid, Integer> weights = new HashMap<>(); for (Pair<Guid, Integer> record: unit.getValue()) { weights.put(record.getFirst(), record.getSecond()); } // Make sure all hosts are present in the list Set<Guid> visitedHosts = unit.getValue().stream() .map(Pair::getFirst) .collect(Collectors.toSet()); // Add default weight for all hosts that were not part // of the result for (Guid host: hosts) { if (!visitedHosts.contains(host)) { weights.put(host, 0); } } // Initialize rank for the best possible host // Single element arrays to avoid final limitation of lambdas final int[] currentRank = { weights.size() }; final int[] realRank = { weights.size() }; final int[] lastWeight = { Integer.MIN_VALUE }; final Integer finalFactor = factor; Map<Guid, Integer> scoreDebugMap = new HashMap<>(); // Sort according to the weight, lower weight (better) first // Assign rank, same weight has to have the same rank number // Rank = the number of hosts with the same or worse weight weights.entrySet().stream() .sorted(Comparator.comparingInt(Entry::getValue)) .forEach(entry -> { realRank[0]--; if (entry.getValue() > lastWeight[0]) { currentRank[0] = realRank[0]; lastWeight[0] = entry.getValue(); } scores.putIfAbsent(entry.getKey(), 0); scoreDebugMap.put(entry.getKey(), currentRank[0]); scores.put(entry.getKey(), scores.get(entry.getKey()) + finalFactor * currentRank[0]); }); if (log.isDebugEnabled()) { debug.append(Optional.ofNullable(unit.getKey()).map(Guid::toString).orElse("<unknown>")); debug.append(";"); debug.append(factorTable.getOrDefault(unit.getKey(), 1)); hosts.forEach(h -> debug.append(String.format(";%d;%d", scoreDebugMap.get(h), weights.get(h)))); debug.append("\n"); } } // Sort the scores - higher first List<Map.Entry<Guid, Integer>> sortedScores = new ArrayList<>(scores.entrySet()); sortedScores.sort((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue())); if (log.isDebugEnabled()) { log.debug("Ranking selector:\n{}", debug.toString()); } // Return the best host if (sortedScores.isEmpty()) { return Optional.empty(); } else { return Optional.of(sortedScores.get(0).getKey()); } } } }