diff options
author | Jon Bratseth <bratseth@vespa.ai> | 2024-04-21 17:00:07 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@vespa.ai> | 2024-04-21 17:00:07 +0200 |
commit | 93762349e5eef8e8ff26d5a557d4d1eb758720d2 (patch) | |
tree | 47acb4a7ecc8b939818718197aaa9ed2f94bf241 /node-repository/src/main/java | |
parent | ad3f46af34fda17189e2cafd650c70b72a5804b4 (diff) |
Retire incrementally
Diffstat (limited to 'node-repository/src/main/java')
3 files changed, 16 insertions, 27 deletions
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 8c52f389daf..b149a9af2c2 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 @@ -179,6 +179,13 @@ class NodeAllocation { if (violatesExclusivity(candidate) != NodeCandidate.ExclusivityViolation.NONE) return Retirement.violatesExclusivity; if (requiredHostFlavor.isPresent() && ! candidate.parent.map(node -> node.flavor().name()).equals(requiredHostFlavor)) return Retirement.violatesHostFlavor; if (candidate.violatesSpares) return Retirement.violatesSpares; + + var group = candidate.allocation().get().membership().cluster().group(); + if (cluster.isStateful() && group.isPresent() && requested.count().isPresent()) { + long nodesInGroup = nodes.values().stream().filter(n -> groupOf(n).equals(group) && ! isRetired(n)).count(); + if (nodesInGroup >= requested.groupSize()) + return Retirement.groupSurplus; + } return Retirement.none; } @@ -290,6 +297,10 @@ class NodeAllocation { return candidate.allocation().flatMap(a -> a.membership().cluster().group()); } + private boolean isRetired(NodeCandidate candidate) { + return candidate.allocation().map(a -> a.membership().retired()).orElse(false); + } + private Node resize(Node node) { NodeResources hostResources = allNodes.parentOf(node).get().flavor().resources(); return node.with(new Flavor(requested.resources().get() @@ -463,6 +474,7 @@ class NodeAllocation { violatesHostFlavor("node violates host flavor"), violatesHostFlavorGeneration("node violates host flavor generation"), violatesSpares("node is assigned to a host we want to use as a spare"), + groupSurplus("group has enough nodes"), none(""); private final String description; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java index c599e7fd1f0..cb50f6dff2b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java @@ -80,11 +80,9 @@ public interface NodeSpec { return false; } - NodeSpec withExtra(int nodeCount); - static NodeSpec from(int nodeCount, int groupCount, NodeResources resources, boolean exclusive, boolean canFail, CloudAccount cloudAccount, Duration hostTTL) { - return new CountNodeSpec(nodeCount, groupCount, resources, 0, exclusive, canFail, canFail, cloudAccount, hostTTL); + return new CountNodeSpec(nodeCount, groupCount, resources, exclusive, canFail, canFail, cloudAccount, hostTTL); } static NodeSpec from(NodeType type, CloudAccount cloudAccount) { @@ -97,20 +95,17 @@ public interface NodeSpec { private final int count; private final int groups; private final NodeResources requestedNodeResources; - private final int extraNodesNeeded; private final boolean exclusive; private final boolean canFail; private final boolean considerRetiring; private final CloudAccount cloudAccount; private final Duration hostTTL; - private CountNodeSpec(int count, int groups, NodeResources resources, int extraNodesNeeded, - boolean exclusive, boolean canFail, + private CountNodeSpec(int count, int groups, NodeResources resources, boolean exclusive, boolean canFail, boolean considerRetiring, CloudAccount cloudAccount, Duration hostTTL) { this.count = count; this.groups = groups; this.requestedNodeResources = Objects.requireNonNull(resources, "Resources must be specified"); - this.extraNodesNeeded = extraNodesNeeded; // ... due to reducing group size this.exclusive = exclusive; this.canFail = canFail; this.considerRetiring = considerRetiring; @@ -153,11 +148,11 @@ public interface NodeSpec { @Override public int fulfilledDeficitCount(int count) { - return Math.max(this.count + extraNodesNeeded - count, 0); + return Math.max(this.count - count, 0); } public NodeSpec withoutRetiring() { - return new CountNodeSpec(count, groups, requestedNodeResources, extraNodesNeeded, exclusive, canFail, false, cloudAccount, hostTTL); + return new CountNodeSpec(count, groups, requestedNodeResources, exclusive, canFail, false, cloudAccount, hostTTL); } @Override @@ -191,10 +186,6 @@ public interface NodeSpec { @Override public Duration hostTTL() { return hostTTL; } - public NodeSpec withExtra(int nodeCount) { - return new CountNodeSpec(count, groups, requestedNodeResources, nodeCount, exclusive, canFail, considerRetiring, cloudAccount, hostTTL); - } - @Override public String toString() { return "request for " + count + " nodes" + @@ -268,8 +259,6 @@ public interface NodeSpec { return cloudAccount; } - public NodeSpec withExtra(int nodeCount) { return this; } - @Override public String toString() { return "request for nodes of type '" + type + "'"; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java index ea8e7d7db6e..0206c3a4a26 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java @@ -188,7 +188,6 @@ public class Preparer { private NodeAllocation prepareAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requested, Supplier<Integer> nextIndex, LockedNodeList allNodes) { validateAccount(requested.cloudAccount(), application, allNodes); - requested = requested.withExtra(extraNodesNeeded(application, cluster, requested, allNodes)); NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requested, nextIndex, nodeRepository); IP.Allocation.Context allocationContext = IP.Allocation.Context.from(nodeRepository.zone().cloud().name(), requested.cloudAccount().isExclave(nodeRepository.zone()), @@ -214,17 +213,6 @@ public class Preparer { return allocation; } - private int extraNodesNeeded(ApplicationId application, ClusterSpec cluster, NodeSpec requested, LockedNodeList allNodes) { - if ( ! cluster.isStateful()) return 0; - if ( ! requested.count().isPresent()) return 0; - int currentGroupSize = allNodes.owner(application).cluster(cluster.id()).state(Node.State.active).not().retired().group(0).size(); - long currentGroupCount = allNodes.owner(application).cluster(cluster.id()).state(Node.State.active).not().retired().stream() - .map(node -> node.allocation().get().membership().cluster().group().get()).distinct().count(); - int newGroupSize = requested.groupSize(); - int surplusPerGroup = Math.max(0, currentGroupSize - newGroupSize); - return surplusPerGroup * (int) currentGroupCount; - } - private void validateAccount(CloudAccount requestedAccount, ApplicationId application, LockedNodeList allNodes) { CloudAccount effectiveAccount = requestedAccount.isUnspecified() ? nodeRepository.zone().cloud().account() : requestedAccount; List<Node> nodesInOtherAccount = allNodes.owner(application).nodeType(NodeType.tenant).stream() |