summaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2017-08-01 14:11:37 +0200
committertoby <smorgrav@yahoo-inc.com>2017-08-14 11:27:09 +0200
commit6dd58aca8b714b9768802b7b83164a120e946621 (patch)
tree44a9d58b60f87d68a85b20acef97b005f2798bc6 /node-repository
parentfcf22b663fa4850604f72c1a5769b5902e54aa36 (diff)
Minor refactoring and cleanup (only)
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java1
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java45
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java63
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java26
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePriority.java100
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java101
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java1
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));
}
+
}