diff options
Diffstat (limited to 'node-repository')
4 files changed, 80 insertions, 30 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java index 25cfcf2cda9..c661cc6ae49 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java @@ -285,7 +285,13 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { NodePrioritizer prioritizer = new NodePrioritizer(allNodes, applicationId, clusterSpec, nodeSpec, true, allocationContext, nodeRepository().nodes(), nodeRepository().resourcesCalculator(), nodeRepository().spareCount()); - List<NodeCandidate> nodeCandidates = prioritizer.collect(); + List<NodeCandidate> nodeCandidates = prioritizer.collect().stream() + .filter(node -> ! node.violatesExclusivity(clusterSpec, + applicationId, + nodeRepository().exclusiveAllocation(clusterSpec), + nodeRepository().zone().cloud().allowHostSharing(), + allNodes)) + .toList(); MutableInteger index = new MutableInteger(0); return nodeCandidates .stream() 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 87e3bea1af9..9f6d4f159f6 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 @@ -117,21 +117,21 @@ class NodeAllocation { ClusterMembership membership = allocation.membership(); if ( ! allocation.owner().equals(application)) continue; // wrong application if ( ! membership.cluster().satisfies(cluster)) continue; // wrong cluster id/type - if ( candidate.state() == Node.State.active && allocation.removable()) continue; // don't accept; causes removal - if ( candidate.state() == Node.State.active && candidate.wantToFail()) continue; // don't accept; causes failing - if ( indexes.contains(membership.index())) continue; // duplicate index (just to be sure) + if (candidate.state() == Node.State.active && allocation.removable()) continue; // don't accept; causes removal + if (candidate.state() == Node.State.active && candidate.wantToFail()) continue; // don't accept; causes failing + if (indexes.contains(membership.index())) continue; // duplicate index (just to be sure) if (nodeRepository.zone().cloud().allowEnclave() && candidate.parent.isPresent() && ! candidate.parent.get().cloudAccount().equals(requested.cloudAccount())) continue; // wrong account boolean resizeable = requested.considerRetiring() && candidate.isResizable; - if ((! saturated() && hasCompatibleResources(candidate) && requested.acceptable(candidate)) || acceptIncompatible(candidate)) { + if (( ! saturated() && hasCompatibleResources(candidate) && requested.acceptable(candidate)) || acceptIncompatible(candidate)) { candidate = candidate.withNode(); if (candidate.isValid()) acceptNode(candidate, shouldRetire(candidate, candidates), resizeable); } } - else if (! saturated() && hasCompatibleResources(candidate)) { - if (! nodeRepository.nodeResourceLimits().isWithinRealLimits(candidate, application, cluster)) { + else if ( ! saturated() && hasCompatibleResources(candidate)) { + if ( ! nodeRepository.nodeResourceLimits().isWithinRealLimits(candidate, application, cluster)) { ++rejectedDueToInsufficientRealResources; continue; } @@ -196,29 +196,8 @@ class NodeAllocation { } private boolean violatesExclusivity(NodeCandidate candidate) { - if (candidate.parentHostname().isEmpty()) return false; - if (requested.type() != NodeType.tenant) return false; - - // In zones which does not allow host sharing, exclusivity is violated if... - if ( ! nodeRepository.zone().cloud().allowHostSharing()) { - // TODO: Write this in a way that is simple to read - // If either the parent is dedicated to a cluster type different from this cluster - return ! candidate.parent.flatMap(Node::exclusiveToClusterType).map(cluster.type()::equals).orElse(true) || - // or the parent is dedicated to a different application - ! candidate.parent.flatMap(Node::exclusiveToApplicationId).map(application::equals).orElse(true) || - // or this cluster requires exclusivity, but the host is not exclusive - (nodeRepository.exclusiveAllocation(cluster) && candidate.parent.flatMap(Node::exclusiveToApplicationId).isEmpty()); - } - - // In zones with shared hosts we require that if either of the nodes on the host requires exclusivity, - // then all the nodes on the host must have the same owner - for (Node nodeOnHost : allNodes.childrenOf(candidate.parentHostname().get())) { - if (nodeOnHost.allocation().isEmpty()) continue; - if (nodeRepository.exclusiveAllocation(cluster) || nodeOnHost.allocation().get().membership().cluster().isExclusive()) { - if ( ! nodeOnHost.allocation().get().owner().equals(application)) return true; - } - } - return false; + return candidate.violatesExclusivity(cluster, application, nodeRepository.exclusiveAllocation(cluster), + nodeRepository.zone().cloud().allowHostSharing(), allNodes); } /** diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java index 1efc35b416d..05aa986b9ff 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java @@ -4,11 +4,13 @@ package com.yahoo.vespa.hosted.provision.provisioning; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.Nodelike; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.IP; @@ -20,6 +22,8 @@ import java.util.Objects; import java.util.Optional; import java.util.logging.Logger; +import static com.yahoo.collections.Optionals.emptyOrEqual; + /** * A node candidate containing the details required to prioritize it for allocation. This is immutable. * @@ -559,4 +563,32 @@ public abstract class NodeCandidate implements Nodelike, Comparable<NodeCandidat } + public boolean violatesExclusivity(ClusterSpec cluster, ApplicationId application, + boolean exclusiveCluster, boolean hostSharing, NodeList allNodes) { + if (parentHostname().isEmpty()) return false; + if (type() != NodeType.tenant) return false; + + // We always violate exclusivity if the parent is exclusive to someone else that the requesting application. + if ( ! emptyOrEqual(parent.flatMap(Node::exclusiveToApplicationId), application)) return true; + + // In zones which do not allow host sharing, exclusivity is violated if... + if ( ! hostSharing) { + // If either the parent is dedicated to a cluster type different from this cluster + return ! emptyOrEqual(parent.flatMap(Node::exclusiveToClusterType), cluster.type()) || + // or this cluster requires exclusivity, but the host is not exclusive (to this, implicitly by the above). + exclusiveCluster && parent.flatMap(Node::exclusiveToApplicationId).isEmpty(); + } + + // In zones with shared hosts we require that if any node on the host requires exclusivity, + // then all the nodes on the host must have the same owner. + for (Node nodeOnHost : allNodes.childrenOf(parentHostname().get())) { + if (nodeOnHost.allocation().isEmpty()) continue; + if (exclusiveCluster || nodeOnHost.allocation().get().membership().cluster().isExclusive()) { + if ( ! nodeOnHost.allocation().get().owner().equals(application)) return true; + } + } + return false; + + } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java index bb30e0d985e..f960b122d24 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java @@ -278,6 +278,39 @@ public class HostCapacityMaintainerTest { } @Test + public void respects_exclusive_allocation() { + tester = new DynamicProvisioningTester(); + NodeResources resources1 = new NodeResources(24, 64, 100, 10); + setPreprovisionCapacityFlag(tester, + new ClusterCapacity(2, resources1.vcpu(), resources1.memoryGb(), resources1.diskGb(), + resources1.bandwidthGbps(), resources1.diskSpeed().name(), + resources1.storageType().name(), resources1.architecture().name(), + null)); + tester.maintain(); + + // Hosts are provisioned + assertEquals(2, tester.provisionedHostsMatching(resources1)); + assertEquals(0, tester.hostProvisioner.deprovisionedHosts()); + + // Next maintenance run does nothing + tester.assertNodesUnchanged(); + + // One host is allocated exclusively to some other application + tester.nodeRepository.nodes().write(tester.nodeRepository.nodes().list().node("host100").get() + .withExclusiveToApplicationId(ApplicationId.from("t", "a", "i")), + () -> { }); + + tester.maintain(); + + // New hosts are provisioned, and the empty exclusive host is deallocated + assertEquals(2, tester.provisionedHostsMatching(resources1)); + assertEquals(1, tester.hostProvisioner.deprovisionedHosts()); + + // Next maintenance run does nothing + tester.assertNodesUnchanged(); + } + + @Test public void test_minimum_capacity() { tester = new DynamicProvisioningTester(); NodeResources resources1 = new NodeResources(24, 64, 100, 10); |