aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java37
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeCandidate.java32
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java33
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);