diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-08-01 14:11:37 +0200 |
---|---|---|
committer | toby <smorgrav@yahoo-inc.com> | 2017-08-14 11:27:09 +0200 |
commit | 6dd58aca8b714b9768802b7b83164a120e946621 (patch) | |
tree | 44a9d58b60f87d68a85b20acef97b005f2798bc6 /node-repository | |
parent | fcf22b663fa4850604f72c1a5769b5902e54aa36 (diff) |
Minor refactoring and cleanup (only)
Diffstat (limited to 'node-repository')
8 files changed, 168 insertions, 170 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java index a7a0eadf515..5f81fed2a04 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java @@ -29,6 +29,7 @@ import java.util.Set; * @author freva */ public class FlavorSpareChecker { + private final SpareNodesPolicy spareNodesPolicy; private final Map<Flavor, FlavorSpareCount> spareCountByFlavor; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java index 2a3f44df42f..217f4999bfb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; * @author freva */ public class FlavorSpareCount { + private final Flavor flavor; private Set<FlavorSpareCount> possibleWantedFlavors; private Set<FlavorSpareCount> immediateReplacees; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index 1733554365b..c5d60c30356 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -19,6 +19,7 @@ import java.util.function.BiConsumer; * @author bratseth */ class GroupPreparer { + private final NodeRepository nodeRepository; private final Clock clock; @@ -52,47 +53,41 @@ class GroupPreparer { try (Mutex readyLock = nodeRepository.lockUnallocated()) { // Create a prioritized set of nodes - NodePrioritizer prioritizer = new NodePrioritizer( - nodeRepository.getNodes(), - application, - cluster, - requestedNodes, - nodeRepository.getAvailableFlavors(), - nofSpares); + NodePrioritizer prioritizer = new NodePrioritizer(nodeRepository.getNodes(), + application, + cluster, + requestedNodes, + nodeRepository.getAvailableFlavors(), + nofSpares); prioritizer.addApplicationNodes(); prioritizer.addSurplusNodes(surplusActiveNodes); prioritizer.addReadyNodes(); - if (nodeRepository.dynamicAllocationEnabled()) { + if (nodeRepository.dynamicAllocationEnabled()) prioritizer.addNewDockerNodes(); - } // Allocate from the prioritized list NodeAllocation allocation = new NodeAllocation(application, cluster, requestedNodes, highestIndex, clock); allocation.offer(prioritizer.prioritize()); - - // Book-keeping - if (allocation.fullfilled()) { - nodeRepository.reserve(allocation.getAcceptedInactiveAndReadyNodes()); - nodeRepository.addDockerNodes(allocation.getAcceptedNewNodes()); - surplusActiveNodes.removeAll(allocation.getAcceptedSurplusNodes()); - List<Node> result = allocation.finalNodes(surplusActiveNodes); - return result; - } else { + if (! allocation.fullfilled()) throw new OutOfCapacityException("Could not satisfy " + requestedNodes + " for " + cluster + - outOfCapacityDetails(allocation)); - } + outOfCapacityDetails(allocation)); + + // Carry out and return allocation + nodeRepository.reserve(allocation.acceptedInactiveAndReadyNodes()); + nodeRepository.addDockerNodes(allocation.acceptedNewNodes()); + surplusActiveNodes.removeAll(allocation.acceptedSurplusNodes()); + return allocation.finalNodes(surplusActiveNodes); } } } private String outOfCapacityDetails(NodeAllocation allocation) { - if (allocation.wouldBeFulfilledWithClashingParentHost()) { + if (allocation.wouldBeFulfilledWithClashingParentHost()) return ": Not enough nodes available on separate physical hosts."; - } - if (allocation.wouldBeFulfilledWithRetiredNodes()) { + if (allocation.wouldBeFulfilledWithRetiredNodes()) return ": Not enough nodes available due to retirement."; - } - return "."; + else + return "."; } }
\ No newline at end of file diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index 02ecd182d54..b9f682bc79f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -21,8 +21,10 @@ import java.util.stream.Collectors; /** * Used to manage a list of nodes during the node reservation process * in order to fulfill the nodespec. + * + * @author bratseth */ -public class NodeAllocation { +class NodeAllocation { /** The application this list is for */ private final ApplicationId application; @@ -34,7 +36,7 @@ public class NodeAllocation { private final NodeSpec requestedNodes; /** The nodes this has accepted so far */ - private final Set<NodePriority> nodes = new LinkedHashSet<>(); + private final Set<PrioritizableNode> nodes = new LinkedHashSet<>(); /** The number of nodes in the accepted nodes which are of the requested flavor */ private int acceptedOfRequestedFlavor = 0; @@ -54,7 +56,7 @@ public class NodeAllocation { /** Used to record event timestamps **/ private final Clock clock; - public NodeAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, MutableInteger highestIndex, Clock clock) { + NodeAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, MutableInteger highestIndex, Clock clock) { this.application = application; this.cluster = cluster; this.requestedNodes = requestedNodes; @@ -65,17 +67,16 @@ public class NodeAllocation { /** * Offer some nodes to this. The nodes may have an allocation to a different application or cluster, * an allocation to this cluster, or no current allocation (in which case one is assigned). - * <p> + * * Note that if unallocated nodes are offered before allocated nodes, this will unnecessarily * reject allocated nodes due to index duplicates. * * @param nodesPrioritized the nodes which are potentially on offer. These may belong to a different application etc. * @return the subset of offeredNodes which was accepted, with the correct allocation assigned */ - public List<Node> offer(List<NodePriority> nodesPrioritized) { - + List<Node> offer(List<PrioritizableNode> nodesPrioritized) { List<Node> accepted = new ArrayList<>(); - for (NodePriority offeredPriority : nodesPrioritized) { + for (PrioritizableNode offeredPriority : nodesPrioritized) { Node offered = offeredPriority.node; if (offered.allocation().isPresent()) { @@ -108,8 +109,7 @@ public class NodeAllocation { if (offered.status().wantToRetire()) { continue; } - Node alloc = offered.allocate(application, ClusterMembership.from(cluster, highestIndex.add(1)), clock.instant()); - offeredPriority.node = alloc; + offeredPriority.node = offered.allocate(application, ClusterMembership.from(cluster, highestIndex.add(1)), clock.instant()); accepted.add(acceptNode(offeredPriority, false)); } } @@ -117,8 +117,8 @@ public class NodeAllocation { return accepted; } - private boolean offeredNodeHasParentHostnameAlreadyAccepted(Collection<NodePriority> accepted, Node offered) { - for (NodePriority acceptedNode : accepted) { + private boolean offeredNodeHasParentHostnameAlreadyAccepted(Collection<PrioritizableNode> accepted, Node offered) { + for (PrioritizableNode acceptedNode : accepted) { if (acceptedNode.node.parentHostname().isPresent() && offered.parentHostname().isPresent() && acceptedNode.node.parentHostname().get().equals(offered.parentHostname().get())) { return true; @@ -154,29 +154,29 @@ public class NodeAllocation { return requestedNodes.isCompatible(node.flavor()); } - private Node acceptNode(NodePriority nodePriority, boolean wantToRetire) { - Node node = nodePriority.node; + private Node acceptNode(PrioritizableNode prioritizableNode, boolean wantToRetire) { + Node node = prioritizableNode.node; if (! wantToRetire) { if ( ! node.state().equals(Node.State.active)) { // reactivated node - make sure its not retired node = node.unretire(); - nodePriority.node= node; + prioritizableNode.node= node; } acceptedOfRequestedFlavor++; } else { ++wasRetiredJustNow; // Retire nodes which are of an unwanted flavor, retired flavor or have an overlapping parent host node = node.retire(clock.instant()); - nodePriority.node= node; + prioritizableNode.node= node; } if ( ! node.allocation().get().membership().cluster().equals(cluster)) { // group may be different node = setCluster(cluster, node); - nodePriority.node= node; + prioritizableNode.node= node; } indexes.add(node.allocation().get().membership().index()); highestIndex.set(Math.max(highestIndex.get(), node.allocation().get().membership().index())); - nodes.add(nodePriority); + nodes.add(prioritizableNode); return node; } @@ -186,20 +186,20 @@ public class NodeAllocation { } /** Returns true if no more nodes are needed in this list */ - public boolean saturated() { + private boolean saturated() { return requestedNodes.saturatedBy(acceptedOfRequestedFlavor); } /** Returns true if the content of this list is sufficient to meet the request */ - public boolean fullfilled() { + boolean fullfilled() { return requestedNodes.fulfilledBy(acceptedOfRequestedFlavor); } - public boolean wouldBeFulfilledWithRetiredNodes() { + boolean wouldBeFulfilledWithRetiredNodes() { return requestedNodes.fulfilledBy(acceptedOfRequestedFlavor + wasRetiredJustNow); } - public boolean wouldBeFulfilledWithClashingParentHost() { + boolean wouldBeFulfilledWithClashingParentHost() { return requestedNodes.fulfilledBy(acceptedOfRequestedFlavor + rejectedWithClashingParentHost); } @@ -217,17 +217,16 @@ public class NodeAllocation { long surplus = requestedNodes.surplusGiven(nodes.size()) - currentRetired; if (surplus > 0) { // retire until surplus is 0, prefer to retire higher indexes to minimize redistribution - for (NodePriority node : byDecreasingIndex(nodes)) { + for (PrioritizableNode node : byDecreasingIndex(nodes)) { if ( ! node.node.allocation().get().membership().retired() && node.node.state().equals(Node.State.active)) { - Node retiredNode = node.node.retire(Agent.application, clock.instant()); - node.node = retiredNode; + node.node = node.node.retire(Agent.application, clock.instant()); surplusNodes.add(node.node); // offer this node to other groups if (--surplus == 0) break; } } } else if (surplus < 0) { // unretire until surplus is 0 - for (NodePriority node : byIncreasingIndex(nodes)) { + for (PrioritizableNode node : byIncreasingIndex(nodes)) { if ( node.node.allocation().get().membership().retired() && hasCompatibleFlavor(node.node)) { node.node = node.node.unretire(); if (++surplus == 0) break; @@ -238,36 +237,36 @@ public class NodeAllocation { return nodes.stream().map(n -> n.node).collect(Collectors.toList()); } - List<Node> getAcceptedInactiveAndReadyNodes() { + List<Node> acceptedInactiveAndReadyNodes() { return nodes.stream().map(n -> n.node) .filter(n -> n.state().equals(Node.State.inactive) || n.state().equals(Node.State.ready)) .collect(Collectors.toList()); } - List<Node> getAcceptedSurplusNodes() { + List<Node> acceptedSurplusNodes() { return nodes.stream() .filter(n -> n.isSurplusNode) .map(n -> n.node) .collect(Collectors.toList()); } - List<Node> getAcceptedNewNodes() { + List<Node> acceptedNewNodes() { return nodes.stream() .filter(n -> n.isNewNode) .map(n -> n.node) .collect(Collectors.toList()); } - private List<NodePriority> byDecreasingIndex(Set<NodePriority> nodes) { + private List<PrioritizableNode> byDecreasingIndex(Set<PrioritizableNode> nodes) { return nodes.stream().sorted(nodeIndexComparator().reversed()).collect(Collectors.toList()); } - private List<NodePriority> byIncreasingIndex(Set<NodePriority> nodes) { + private List<PrioritizableNode> byIncreasingIndex(Set<PrioritizableNode> nodes) { return nodes.stream().sorted(nodeIndexComparator()).collect(Collectors.toList()); } - private Comparator<NodePriority> nodeIndexComparator() { - return Comparator.comparing((NodePriority n) -> n.node.allocation().get().membership().index()); + private Comparator<PrioritizableNode> nodeIndexComparator() { + return Comparator.comparing((PrioritizableNode n) -> n.node.allocation().get().membership().index()); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java index efa46dedce5..24e33d31a7a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java @@ -23,15 +23,15 @@ import java.util.stream.Collectors; /** * Builds up data structures necessary for node prioritization. It wraps each node - * up in a NodePriority object with attributes used in sorting. + * up in a PrioritizableNode object with attributes used in sorting. * - * The actual sorting/prioritization is implemented in the NodePriority class as a compare method. + * The actual sorting/prioritization is implemented in the PrioritizableNode class as a compare method. * * @author smorgrav */ public class NodePrioritizer { - private final Map<Node, NodePriority> nodes = new HashMap<>(); + private final Map<Node, PrioritizableNode> nodes = new HashMap<>(); private final List<Node> allNodes; private final DockerHostCapacity capacity; private final NodeSpec requestedNodes; @@ -155,11 +155,11 @@ public class NodePrioritizer { } /** - * @return The list of nodes sorted by NodePriority::compare + * @return The list of nodes sorted by PrioritizableNode::compare */ - List<NodePriority> prioritize() { - List<NodePriority> priorityList = new ArrayList<>(nodes.values()); - Collections.sort(priorityList, (a, b) -> NodePriority.compare(a, b)); + List<PrioritizableNode> prioritize() { + List<PrioritizableNode> priorityList = new ArrayList<>(nodes.values()); + Collections.sort(priorityList); return priorityList; } @@ -169,7 +169,7 @@ public class NodePrioritizer { */ void addSurplusNodes(List<Node> surplusNodes) { for (Node node : surplusNodes) { - NodePriority nodePri = toNodePriority(node, true, false); + PrioritizableNode nodePri = toNodePriority(node, true, false); if (!nodePri.violatesSpares || isAllocatingForReplacement) { nodes.put(node, nodePri); } @@ -204,7 +204,7 @@ public class NodePrioritizer { if (hostname == null) continue; Node newNode = Node.createDockerNode("fake-" + hostname, Collections.singleton(ipAddress), Collections.emptySet(), hostname, Optional.of(node.hostname()), getFlavor(), NodeType.tenant); - NodePriority nodePri = toNodePriority(newNode, false, true); + PrioritizableNode nodePri = toNodePriority(newNode, false, true); if (!nodePri.violatesSpares || isAllocatingForReplacement) { nodes.put(newNode, nodePri); } @@ -224,7 +224,7 @@ public class NodePrioritizer { .filter(node -> node.allocation().isPresent()) .filter(node -> node.allocation().get().owner().equals(appId)) .map(node -> toNodePriority(node, false, false)) - .forEach(nodePriority -> nodes.put(nodePriority.node, nodePriority)); + .forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode)); } /** @@ -236,15 +236,15 @@ public class NodePrioritizer { .filter(node -> node.state().equals(Node.State.ready)) .map(node -> toNodePriority(node, false, false)) .filter(n -> !n.violatesSpares || isAllocatingForReplacement) - .forEach(nodePriority -> nodes.put(nodePriority.node, nodePriority)); + .forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode)); } /** * Convert a list of nodes to a list of node priorities. This includes finding, calculating * parameters to the priority sorting procedure. */ - private NodePriority toNodePriority(Node node, boolean isSurplusNode, boolean isNewNode) { - NodePriority pri = new NodePriority(); + private PrioritizableNode toNodePriority(Node node, boolean isSurplusNode, boolean isNewNode) { + PrioritizableNode pri = new PrioritizableNode(); pri.node = node; pri.isSurplusNode = isSurplusNode; pri.isNewNode = isNewNode; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePriority.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePriority.java deleted file mode 100644 index b5a87fffa81..00000000000 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePriority.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.yahoo.vespa.hosted.provision.provisioning; - -import com.yahoo.vespa.hosted.provision.Node; - -import java.util.Optional; - -/** - * Encapsulates all the information necessary to prioritize node for allocation. - * - * @author smorgrav - */ -class NodePriority { - - Node node; - - /** The free capacity excluding headroom, including retired allocations */ - ResourceCapacity freeParentCapacity = new ResourceCapacity(); - - /** The parent host (docker or hypervisor) */ - Optional<Node> parent = Optional.empty(); - - /** True if the node is allocated to a host that should be dedicated as a spare */ - boolean violatesSpares; - - /** True if the node is allocated on slots that should be dedicated to headroom */ - boolean violatesHeadroom; - - /** True if this is a node that has been retired earlier in the allocation process */ - boolean isSurplusNode; - - /** This node does not exist in the node repository yet */ - boolean isNewNode; - - /** True if exact flavor is specified by the allocation request and this node has this flavor */ - boolean preferredOnFlavor; - - /** - * Compare two node priorities. - * - * @return negative if first priority is higher than second node - */ - static int compare(NodePriority n1, NodePriority n2) { - // First always pick nodes without violation above nodes with violations - if (!n1.violatesSpares && n2.violatesSpares) return -1; - if (!n2.violatesSpares && n1.violatesSpares) return 1; - if (!n1.violatesHeadroom && n2.violatesHeadroom) return -1; - if (!n2.violatesHeadroom && n1.violatesHeadroom) return 1; - - // Choose active nodes - if (n1.node.state().equals(Node.State.active) && !n2.node.state().equals(Node.State.active)) return -1; - if (n2.node.state().equals(Node.State.active) && !n1.node.state().equals(Node.State.active)) return 1; - - // Choose active node that is not retired first (surplus is active but retired) - if (!n1.isSurplusNode && n2.isSurplusNode) return -1; - if (!n2.isSurplusNode && n1.isSurplusNode) return 1; - - // Choose inactive nodes - if (n1.node.state().equals(Node.State.inactive) && !n2.node.state().equals(Node.State.inactive)) return -1; - if (n2.node.state().equals(Node.State.inactive) && !n1.node.state().equals(Node.State.inactive)) return 1; - - // Choose reserved nodes from a previous allocation attempt (the exist in node repo) - if (isInNodeRepoAndReserved(n1) && !isInNodeRepoAndReserved(n2)) return -1; - if (isInNodeRepoAndReserved(n2) && !isInNodeRepoAndReserved(n1)) return 1; - - // Choose ready nodes - if (n1.node.state().equals(Node.State.ready) && !n2.node.state().equals(Node.State.ready)) return -1; - if (n2.node.state().equals(Node.State.ready) && !n1.node.state().equals(Node.State.ready)) return 1; - - // The node state should be equal here - if (!n1.node.state().equals(n2.node.state())) { - throw new RuntimeException( - String.format("Error during node priority comparison. Node states are not equal as expected. Got %s and %s.", - n1.node.state(), n2.node.state())); - } - - // Choose exact flavor - if (n1.preferredOnFlavor && !n2.preferredOnFlavor) return -1; - if (n2.preferredOnFlavor && !n1.preferredOnFlavor) return 1; - - // Choose docker node over non-docker node (is this to differentiate between docker replaces non-docker flavors?) - if (n1.parent.isPresent() && !n2.parent.isPresent()) return -1; - if (n2.parent.isPresent() && !n1.parent.isPresent()) return 1; - - // Choose the node with parent node with the least capacity (TODO parameterize this as this is pretty much the core of the algorithm) - int freeCapacity = n1.freeParentCapacity.compare(n2.freeParentCapacity); - if (freeCapacity != 0) return freeCapacity; - - // Choose cheapest node - if (n1.node.flavor().cost() < n2.node.flavor().cost()) return -1; - if (n2.node.flavor().cost() < n1.node.flavor().cost()) return 1; - - // All else equal choose hostname alphabetically - return n1.node.hostname().compareTo(n2.node.hostname()); - } - - private static boolean isInNodeRepoAndReserved(NodePriority nodePri) { - if (nodePri.isNewNode) return false; - return nodePri.node.state().equals(Node.State.reserved); - } -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java new file mode 100644 index 00000000000..219778ab7a0 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java @@ -0,0 +1,101 @@ +package com.yahoo.vespa.hosted.provision.provisioning; + +import com.yahoo.vespa.hosted.provision.Node; + +import java.util.Optional; + +/** + * A node with additional information required to prioritize it for allocation. + * + * @author smorgrav + */ +class PrioritizableNode implements Comparable<PrioritizableNode> { + + Node node; + + /** The free capacity excluding headroom, including retired allocations */ + ResourceCapacity freeParentCapacity = new ResourceCapacity(); + + /** The parent host (docker or hypervisor) */ + Optional<Node> parent = Optional.empty(); + + /** True if the node is allocated to a host that should be dedicated as a spare */ + boolean violatesSpares; + + /** True if the node is allocated on slots that should be dedicated to headroom */ + boolean violatesHeadroom; + + /** True if this is a node that has been retired earlier in the allocation process */ + boolean isSurplusNode; + + /** This node does not exist in the node repository yet */ + boolean isNewNode; + + /** True if exact flavor is specified by the allocation request and this node has this flavor */ + boolean preferredOnFlavor; + + /** + * Compares two prioritizable nodes + * + * @return negative if first priority is higher than second node + */ + @Override + public int compareTo(PrioritizableNode other) { + // First always pick nodes without violation above nodes with violations + if (!this.violatesSpares && other.violatesSpares) return -1; + if (!other.violatesSpares && this.violatesSpares) return 1; + if (!this.violatesHeadroom && other.violatesHeadroom) return -1; + if (!other.violatesHeadroom && this.violatesHeadroom) return 1; + + // Choose active nodes + if (this.node.state().equals(Node.State.active) && !other.node.state().equals(Node.State.active)) return -1; + if (other.node.state().equals(Node.State.active) && !this.node.state().equals(Node.State.active)) return 1; + + // Choose active node that is not retired first (surplus is active but retired) + if (!this.isSurplusNode && other.isSurplusNode) return -1; + if (!other.isSurplusNode && this.isSurplusNode) return 1; + + // Choose inactive nodes + if (this.node.state().equals(Node.State.inactive) && !other.node.state().equals(Node.State.inactive)) return -1; + if (other.node.state().equals(Node.State.inactive) && !this.node.state().equals(Node.State.inactive)) return 1; + + // Choose reserved nodes from a previous allocation attempt (the exist in node repo) + if (isInNodeRepoAndReserved(this) && !isInNodeRepoAndReserved(other)) return -1; + if (isInNodeRepoAndReserved(other) && !isInNodeRepoAndReserved(this)) return 1; + + // Choose ready nodes + if (this.node.state().equals(Node.State.ready) && !other.node.state().equals(Node.State.ready)) return -1; + if (other.node.state().equals(Node.State.ready) && !this.node.state().equals(Node.State.ready)) return 1; + + // The node state should be equal here + if (!this.node.state().equals(other.node.state())) { + throw new RuntimeException( + String.format("Error during node priority comparison. Node states are not equal as expected. Got %s and %s.", + this.node.state(), other.node.state())); + } + + // Choose exact flavor + if (this.preferredOnFlavor && !other.preferredOnFlavor) return -1; + if (other.preferredOnFlavor && !this.preferredOnFlavor) return 1; + + // Choose docker node over non-docker node (is this to differentiate between docker replaces non-docker flavors?) + if (this.parent.isPresent() && !other.parent.isPresent()) return -1; + if (other.parent.isPresent() && !this.parent.isPresent()) return 1; + + // Choose the node with parent node with the least capacity (TODO parameterize this as this is pretty much the core of the algorithm) + int freeCapacity = this.freeParentCapacity.compare(other.freeParentCapacity); + if (freeCapacity != 0) return freeCapacity; + + // Choose cheapest node + if (this.node.flavor().cost() < other.node.flavor().cost()) return -1; + if (other.node.flavor().cost() < this.node.flavor().cost()) return 1; + + // All else equal choose hostname alphabetically + return this.node.hostname().compareTo(other.node.hostname()); + } + + private static boolean isInNodeRepoAndReserved(PrioritizableNode nodePri) { + if (nodePri.isNewNode) return false; + return nodePri.node.state().equals(Node.State.reserved); + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java index e56409d47b9..fdec29d5b97 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java @@ -91,4 +91,5 @@ public class ResourceCapacity { b.addFlavor("spareflavor", cpu, memory, disk, Flavor.Type.DOCKER_CONTAINER).idealHeadroom(1); return new Flavor(b.build().flavor(0)); } + } |