summaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2022-11-16 15:19:21 +0100
committerJon Bratseth <bratseth@gmail.com>2022-11-16 15:19:21 +0100
commite595c0cf98f1caecd70dbc5ed14ff03967a3ced0 (patch)
tree213362b289fe7bd260c69c5ad41d925b92d85db9 /node-repository/src/main/java/com/yahoo
parentb88c7d56c33166d7f77c68ca2d5d0d9c684c4017 (diff)
Support autoscaling in dynamic shared zones
Diffstat (limited to 'node-repository/src/main/java/com/yahoo')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java16
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java16
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java14
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java17
5 files changed, 42 insertions, 33 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
index fc6ff3d0c56..0e9683a1a78 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
@@ -156,7 +156,7 @@ public class AllocatableClusterResources {
public static Optional<AllocatableClusterResources> from(ClusterResources wantedResources,
ClusterSpec clusterSpec,
Limits applicationLimits,
- NodeList hosts,
+ List<NodeResources> availableRealHostResources,
NodeRepository nodeRepository) {
var systemLimits = new NodeResourceLimits(nodeRepository);
boolean exclusive = nodeRepository.exclusiveAllocation(clusterSpec);
@@ -168,8 +168,7 @@ public class AllocatableClusterResources {
var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive); // What we'll really get
if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type()))
return Optional.empty();
-
- if (matchesAny(hosts, advertisedResources))
+ if (anySatisfies(realResources, availableRealHostResources))
return Optional.of(new AllocatableClusterResources(wantedResources.with(realResources),
advertisedResources,
wantedResources,
@@ -212,11 +211,8 @@ public class AllocatableClusterResources {
}
/** Returns true if the given resources could be allocated on any of the given host flavors */
- private static boolean matchesAny(NodeList hosts, NodeResources advertisedResources) {
- // Tenant nodes should not consume more than half the resources of the biggest hosts
- // to make it easier to shift them between hosts.
- return hosts.stream().anyMatch(host -> host.resources().withVcpu(host.resources().vcpu() / 2)
- .satisfies(advertisedResources));
+ private static boolean anySatisfies(NodeResources realResources, List<NodeResources> availableRealHostResources) {
+ return availableRealHostResources.stream().anyMatch(realHostResources -> realHostResources.satisfies(realResources));
}
private static boolean between(NodeResources min, NodeResources max, NodeResources r) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
index 9a8b01f33af..1c99ea7dc08 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import java.util.List;
import java.util.Optional;
/**
@@ -43,7 +44,11 @@ public class AllocationOptimizer {
else
limits = atLeast(minimumNodes, limits).fullySpecified(current.clusterSpec(), nodeRepository, clusterModel.application().id());
Optional<AllocatableClusterResources> bestAllocation = Optional.empty();
- NodeList hosts = nodeRepository.nodes().list().hosts();
+ var availableRealHostResources = nodeRepository.zone().cloud().dynamicProvisioning()
+ ? nodeRepository.flavors().getFlavors().stream().map(flavor -> flavor.resources()).toList()
+ : nodeRepository.nodes().list().hosts().stream().map(host -> host.flavor().resources())
+ .map(hostResources -> maxResourcesOf(hostResources, clusterModel))
+ .toList();
for (int groups = limits.min().groups(); groups <= limits.max().groups(); groups++) {
for (int nodes = limits.min().nodes(); nodes <= limits.max().nodes(); nodes++) {
if (nodes % groups != 0) continue;
@@ -53,7 +58,7 @@ public class AllocationOptimizer {
nodeResourcesWith(nodes, groups,
limits, targetLoad, current, clusterModel));
var allocatableResources = AllocatableClusterResources.from(resources, current.clusterSpec(), limits,
- hosts, nodeRepository);
+ availableRealHostResources, nodeRepository);
if (allocatableResources.isEmpty()) continue;
if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get()))
bestAllocation = allocatableResources;
@@ -62,6 +67,13 @@ public class AllocationOptimizer {
return bestAllocation;
}
+ /** Returns the max resources of a host one node may allocate. */
+ private NodeResources maxResourcesOf(NodeResources hostResources, ClusterModel clusterModel) {
+ if (nodeRepository.exclusiveAllocation(clusterModel.clusterSpec())) return hostResources;
+ // static, shared hosts: Allocate at most half of the host cpu to simplify management
+ return hostResources.withVcpu(hostResources.vcpu() / 2);
+ }
+
/**
* For the observed load this instance is initialized with, returns the resources needed per node to be at
* the target relative load, given a target node and group count.
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 46dd88b78d0..5ee4bdddd02 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
@@ -127,14 +127,14 @@ class NodeAllocation {
boolean resizeable = requestedNodes.considerRetiring() && candidate.isResizable;
boolean acceptToRetire = acceptToRetire(candidate);
- if ((! saturated() && hasCompatibleFlavor(candidate) && requestedNodes.acceptable(candidate)) || acceptToRetire) {
+ if ((! saturated() && hasCompatibleResources(candidate) && requestedNodes.acceptable(candidate)) || acceptToRetire) {
candidate = candidate.withNode();
if (candidate.isValid()) {
acceptNode(candidate, shouldRetire(candidate, candidates), resizeable);
}
}
}
- else if (! saturated() && hasCompatibleFlavor(candidate)) {
+ else if (! saturated() && hasCompatibleResources(candidate)) {
if (! nodeResourceLimits.isWithinRealLimits(candidate, cluster)) {
++rejectedDueToInsufficientRealResources;
continue;
@@ -169,7 +169,7 @@ class NodeAllocation {
}
if ( ! nodeResourceLimits.isWithinRealLimits(candidate, cluster)) return Retirement.outsideRealLimits;
if (violatesParentHostPolicy(candidate)) return Retirement.violatesParentHostPolicy;
- if ( ! hasCompatibleFlavor(candidate)) return Retirement.incompatibleFlavor;
+ if ( ! hasCompatibleResources(candidate)) return Retirement.incompatibleResources;
if (candidate.wantToRetire()) return Retirement.hardRequest;
if (candidate.preferToRetire() && candidate.replaceableBy(candidates)) return Retirement.softRequest;
if (violatesExclusivity(candidate)) return Retirement.violatesExclusivity;
@@ -241,11 +241,11 @@ class NodeAllocation {
if (! requestedNodes.considerRetiring()) return false;
return cluster.isStateful() ||
- (cluster.type() == ClusterSpec.Type.container && !hasCompatibleFlavor(candidate));
+ (cluster.type() == ClusterSpec.Type.container && !hasCompatibleResources(candidate));
}
- private boolean hasCompatibleFlavor(NodeCandidate candidate) {
- return requestedNodes.isCompatible(candidate.flavor(), nodeRepository.flavors()) || candidate.isResizable;
+ private boolean hasCompatibleResources(NodeCandidate candidate) {
+ return requestedNodes.isCompatible(candidate.resources()) || candidate.isResizable;
}
private Node acceptNode(NodeCandidate candidate, Retirement retirement, boolean resizeable) {
@@ -391,7 +391,7 @@ class NodeAllocation {
}
else if (deltaRetiredCount < 0) { // unretire until deltaRetiredCount is 0
for (NodeCandidate candidate : byUnretiringPriority(nodes.values())) {
- if ( candidate.allocation().get().membership().retired() && hasCompatibleFlavor(candidate) ) {
+ if (candidate.allocation().get().membership().retired() && hasCompatibleResources(candidate) ) {
candidate = candidate.withNode();
if (candidate.isResizable)
candidate = candidate.withNode(resize(candidate.toNode()));
@@ -482,7 +482,7 @@ class NodeAllocation {
alreadyRetired("node is already retired"),
outsideRealLimits("node real resources is outside limits"),
violatesParentHostPolicy("node violates parent host policy"),
- incompatibleFlavor("node flavor is incompatible"),
+ incompatibleResources("node resources are incompatible"),
hardRequest("node is requested to retire"),
softRequest("node is requested to retire (soft)"),
violatesExclusivity("node violates host exclusivity"),
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 a2f5eabf447..54c24977fef 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
@@ -28,8 +28,8 @@ public interface NodeSpec {
/** Returns whether the hosts running the nodes of this application can also run nodes of other applications. */
boolean isExclusive();
- /** Returns whether the given flavor is compatible with this spec */
- boolean isCompatible(Flavor flavor, NodeFlavors flavors);
+ /** Returns whether the given node resources is compatible with this spec */
+ boolean isCompatible(NodeResources resources);
/** Returns whether the given node count is sufficient to consider this spec fulfilled to the maximum amount */
boolean saturatedBy(int count);
@@ -115,12 +115,8 @@ public interface NodeSpec {
public NodeType type() { return NodeType.tenant; }
@Override
- public boolean isCompatible(Flavor flavor, NodeFlavors flavors) {
- if (flavor.isDocker()) { // Docker nodes can satisfy a request for parts of their resources
- return flavor.resources().compatibleWith(requestedNodeResources);
- } else { // Other nodes must be matched exactly
- return requestedNodeResources.equals(flavor.resources());
- }
+ public boolean isCompatible(NodeResources resources) {
+ return requestedNodeResources.compatibleWith(resources);
}
@Override
@@ -208,7 +204,7 @@ public interface NodeSpec {
public boolean isExclusive() { return false; }
@Override
- public boolean isCompatible(Flavor flavor, NodeFlavors flavors) { return true; }
+ public boolean isCompatible(NodeResources resources) { return true; }
@Override
public boolean saturatedBy(int count) { return false; }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
index aa4c763fcdc..5a8c5221c47 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockHostProvisioner.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.testutils;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Cloud;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
@@ -45,19 +46,21 @@ public class MockHostProvisioner implements HostProvisioner {
private int deprovisionedHosts = 0;
private EnumSet<Behaviour> behaviours = EnumSet.noneOf(Behaviour.class);
private Optional<Flavor> hostFlavor = Optional.empty();
+ private Cloud cloud;
- public MockHostProvisioner(List<Flavor> flavors, MockNameResolver nameResolver, int memoryTaxGb) {
+ public MockHostProvisioner(List<Flavor> flavors, MockNameResolver nameResolver, int memoryTaxGb, Cloud cloud) {
this.flavors = List.copyOf(flavors);
this.nameResolver = nameResolver;
this.memoryTaxGb = memoryTaxGb;
+ this.cloud = cloud;
}
- public MockHostProvisioner(List<Flavor> flavors) {
- this(flavors, 0);
+ public MockHostProvisioner(List<Flavor> flavors, Cloud cloud) {
+ this(flavors, 0, cloud);
}
- public MockHostProvisioner(List<Flavor> flavors, int memoryTaxGb) {
- this(flavors, new MockNameResolver().mockAnyLookup(), memoryTaxGb);
+ public MockHostProvisioner(List<Flavor> flavors, int memoryTaxGb, Cloud cloud) {
+ this(flavors, new MockNameResolver().mockAnyLookup(), memoryTaxGb, cloud);
}
@Override
@@ -65,8 +68,10 @@ public class MockHostProvisioner implements HostProvisioner {
ApplicationId applicationId, Version osVersion, HostSharing sharing,
Optional<ClusterSpec.Type> clusterType, CloudAccount cloudAccount,
Consumer<List<ProvisionedHost>> provisionedHostsConsumer) {
+ boolean exclusive = sharing == HostSharing.exclusive || ! cloud.allowHostSharing();
Flavor hostFlavor = this.hostFlavor.orElseGet(() -> flavors.stream()
- .filter(f -> compatible(f, resources))
+ .filter(f -> exclusive ? compatible(f, resources)
+ : f.resources().satisfies(resources))
.findFirst()
.orElseThrow(() -> new NodeAllocationException("No host flavor matches " + resources, true)));
List<ProvisionedHost> hosts = new ArrayList<>();