com.yahoo.vespa.hosted.provision.provisioning.FlavorSpareChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of node-repository Show documentation
Show all versions of node-repository Show documentation
Keeps track of node assignment in a multi-application setup.
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
import com.yahoo.vespa.hosted.provision.Node;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This class helps answer the question if there are enough nodes to retire a node with flavor f by:
*
* - Finding all the possible flavors that the replacement node could end up on
* - Making sure that regardless of which flavor it ends up on, there is still enough spare nodes
* to handle at unexpected node failures.
*
*
* Definitions:
*
* - Wanted flavor: The flavor that is the node prefers, for example by specifying in services.xml
* - Node-repo flavor: The flavor that the node actually has (Either the wanted flavor or a flavor that transitively
* replaces the wanted flavor)
* - Replacee flavor: Flavor x is replacee of y iff x transitively replaces y
* - Immediate replacee flavor: Flavor x is an immediate replacee of flavor y iff x directly replaces y.
*
*
* @author freva
*/
public class FlavorSpareChecker {
private final SpareNodesPolicy spareNodesPolicy;
private final Map spareCountByFlavor;
public FlavorSpareChecker(SpareNodesPolicy spareNodesPolicy, Map spareCountByFlavor) {
this.spareNodesPolicy = spareNodesPolicy;
this.spareCountByFlavor = spareCountByFlavor;
}
public void updateReadyAndActiveCountsByFlavor(Map> numberOfNodesByFlavorByState) {
spareCountByFlavor.forEach((flavor, flavorSpareCount) -> {
Map numberOfNodesByState = numberOfNodesByFlavorByState.getOrDefault(flavor, Collections.emptyMap());
flavorSpareCount.updateReadyAndActiveCounts(
numberOfNodesByState.getOrDefault(Node.State.ready, 0L),
numberOfNodesByState.getOrDefault(Node.State.active, 0L));
});
}
public boolean canRetireAllocatedNodeWithFlavor(Flavor flavor) {
Set possibleNewFlavors = findPossibleReplacementFlavorFor(spareCountByFlavor.get(flavor));
possibleNewFlavors.forEach(FlavorSpareCount::decrementNumberOfReady);
return !possibleNewFlavors.isEmpty();
}
public boolean canRetireUnallocatedNodeWithFlavor(Flavor flavor) {
FlavorSpareCount flavorSpareCount = spareCountByFlavor.get(flavor);
if (flavorSpareCount.hasReady() && spareNodesPolicy.hasSpare(flavorSpareCount)) {
flavorSpareCount.decrementNumberOfReady();
return true;
}
return false;
}
/**
* Returns a set of possible new flavors that can replace this flavor given current node allocation.
* If the set is empty, there are not enough spare nodes to safely retire this flavor.
*
* The algorithm is:
* for all possible wanted flavor, check:
*
* - 1: Sum of ready nodes of flavor f and all replacee flavors of f is > reserved (set by {@link SpareNodesPolicy}
* - 2a: Number of ready nodes of flavor f is > 0
* - 2b: Verify 1 & 2 for all immediate replacee of f, f_i, where sum of ready nodes of f_i and all
* replacee flavors of f_i is > 0
*
* Only 2a OR 2b need to be satisfied.
*/
private Set findPossibleReplacementFlavorFor(FlavorSpareCount flavorSpareCount) {
Set possibleReplacementFlavors = new HashSet<>();
for (FlavorSpareCount possibleWantedFlavor : flavorSpareCount.getPossibleWantedFlavors()) {
Set replacementFlavors = verifyReplacementConditions(possibleWantedFlavor);
if (replacementFlavors.isEmpty()) return Collections.emptySet();
else possibleReplacementFlavors.addAll(replacementFlavors);
}
return possibleReplacementFlavors;
}
private Set verifyReplacementConditions(FlavorSpareCount flavorSpareCount) {
Set possibleReplacementFlavors = new HashSet<>();
// Breaks condition 1, end
if (! spareNodesPolicy.hasSpare(flavorSpareCount)) return Collections.emptySet();
// Condition 2a
if (flavorSpareCount.hasReady()) {
possibleReplacementFlavors.add(flavorSpareCount);
// Condition 2b
} else {
for (FlavorSpareCount possibleNewFlavor : flavorSpareCount.getImmediateReplacees()) {
if (possibleNewFlavor.getNumReadyAmongReplacees() == 0) continue;
Set replacementFlavors = verifyReplacementConditions(possibleNewFlavor);
if (replacementFlavors.isEmpty()) return Collections.emptySet();
else possibleReplacementFlavors.addAll(replacementFlavors);
}
}
return possibleReplacementFlavors;
}
public interface SpareNodesPolicy {
boolean hasSpare(FlavorSpareCount flavorSpareCount);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy