aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo/vespa/hosted/provision
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2020-05-02 10:49:56 +0200
committerJon Bratseth <bratseth@gmail.com>2020-05-02 10:49:56 +0200
commitec9d641119a6d41656c3902ed375702628ef5c9f (patch)
treefc1ea9b6c24ff127a9022df06614d3452600d657 /node-repository/src/main/java/com/yahoo/vespa/hosted/provision
parent7c0f9bb894b037c9dcd577cf74e4c5e216530d9c (diff)
Revert "Merge pull request #13131 from vespa-engine/revert-13128-bratseth/allow-non-allocatable-limits-take-2"
This reverts commit 78d7d808067a9189960f3cc2ad83237ba00efec5, reversing changes made to e1d09983f5e952f44358ae5b247e5b76b2898d82.
Diffstat (limited to 'node-repository/src/main/java/com/yahoo/vespa/hosted/provision')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java38
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java83
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java172
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java103
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java69
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java155
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java74
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java22
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java68
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java2
28 files changed, 520 insertions, 360 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
index d612e8b102f..a459cc2826f 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
@@ -38,6 +38,8 @@ import com.yahoo.vespa.hosted.provision.persistence.DnsNameResolver;
import com.yahoo.vespa.hosted.provision.persistence.NameResolver;
import com.yahoo.vespa.hosted.provision.provisioning.DockerImages;
import com.yahoo.vespa.hosted.provision.provisioning.FirmwareChecks;
+import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
+import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider;
import com.yahoo.vespa.hosted.provision.restapi.NotFoundException;
import java.time.Clock;
@@ -92,6 +94,7 @@ public class NodeRepository extends AbstractComponent {
private final Clock clock;
private final Zone zone;
private final NodeFlavors flavors;
+ private final HostResourcesCalculator resourcesCalculator;
private final NameResolver nameResolver;
private final OsVersions osVersions;
private final InfrastructureVersions infrastructureVersions;
@@ -105,20 +108,37 @@ public class NodeRepository extends AbstractComponent {
* This will use the system time to make time-sensitive decisions
*/
@Inject
- public NodeRepository(NodeRepositoryConfig config, NodeFlavors flavors, Curator curator, Zone zone) {
- this(flavors, curator, Clock.systemUTC(), zone, new DnsNameResolver(), DockerImage.fromString(config.dockerImage()), config.useCuratorClientCache());
+ public NodeRepository(NodeRepositoryConfig config,
+ NodeFlavors flavors,
+ ProvisionServiceProvider provisionServiceProvider,
+ Curator curator,
+ Zone zone) {
+ this(flavors,
+ provisionServiceProvider.getHostResourcesCalculator(),
+ curator,
+ Clock.systemUTC(),
+ zone,
+ new DnsNameResolver(),
+ DockerImage.fromString(config.dockerImage()), config.useCuratorClientCache());
}
/**
* Creates a node repository from a zookeeper provider and a clock instance
* which will be used for time-sensitive decisions.
*/
- public NodeRepository(NodeFlavors flavors, Curator curator, Clock clock, Zone zone, NameResolver nameResolver,
- DockerImage dockerImage, boolean useCuratorClientCache) {
+ public NodeRepository(NodeFlavors flavors,
+ HostResourcesCalculator resourcesCalculator,
+ Curator curator,
+ Clock clock,
+ Zone zone,
+ NameResolver nameResolver,
+ DockerImage dockerImage,
+ boolean useCuratorClientCache) {
this.db = new CuratorDatabaseClient(flavors, curator, clock, zone, useCuratorClientCache);
this.zone = zone;
this.clock = clock;
this.flavors = flavors;
+ this.resourcesCalculator = resourcesCalculator;
this.nameResolver = nameResolver;
this.osVersions = new OsVersions(this);
this.infrastructureVersions = new InfrastructureVersions(db);
@@ -162,6 +182,12 @@ public class NodeRepository extends AbstractComponent {
/** Returns this node repo's view of the applications deployed to it */
public Applications applications() { return applications; }
+ public NodeFlavors flavors() {
+ return flavors;
+ }
+
+ public HostResourcesCalculator resourcesCalculator() { return resourcesCalculator; }
+
// ---------------- Query API ----------------------------------------------------------------
/**
@@ -328,10 +354,6 @@ public class NodeRepository extends AbstractComponent {
return Collections.singletonList(getNodeAcl(node, candidates));
}
- public NodeFlavors getAvailableFlavors() {
- return flavors;
- }
-
// ----------------- Node lifecycle -----------------------------------------------------------
/** Creates a new node object, without adding it to the node repo. If no IP address is given, it will be resolved */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java
index 15a5545bc2c..847ec1290f6 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java
@@ -72,19 +72,6 @@ public class Cluster {
return new Cluster(id, min, max, suggested, target);
}
- public NodeResources capAtLimits(NodeResources resources) {
- resources = resources.withVcpu(between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), resources.vcpu()));
- resources = resources.withMemoryGb(between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), resources.memoryGb()));
- resources = resources.withDiskGb(between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), resources.diskGb()));
- return resources;
- }
-
- private double between(double min, double max, double value) {
- value = Math.max(min, value);
- value = Math.min(max, value);
- return value;
- }
-
@Override
public int hashCode() { return id.hashCode(); }
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 2414bd95b85..6c143ab4bbd 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
@@ -1,14 +1,20 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.autoscale;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
+import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits;
import java.util.List;
+import java.util.Optional;
/**
* @author bratseth
@@ -33,9 +39,19 @@ public class AllocatableClusterResources {
private final double fulfilment;
- public AllocatableClusterResources(List<Node> nodes, HostResourcesCalculator calculator) {
+ /** Fake allocatable resources from requested capacity */
+ public AllocatableClusterResources(ClusterResources requested, ClusterSpec.Type clusterType) {
+ this.advertisedResources = requested.nodeResources();
+ this.realResources = requested.nodeResources(); // we don't know
+ this.nodes = requested.nodes();
+ this.groups = requested.groups();
+ this.clusterType = clusterType;
+ this.fulfilment = 1;
+ }
+
+ public AllocatableClusterResources(List<Node> nodes, NodeRepository nodeRepository) {
this.advertisedResources = nodes.get(0).flavor().resources();
- this.realResources = calculator.realResourcesOf(nodes.get(0));
+ this.realResources = nodeRepository.resourcesCalculator().realResourcesOf(nodes.get(0), nodeRepository);
this.nodes = nodes.size();
this.groups = (int)nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
this.clusterType = nodes.get(0).allocation().get().membership().cluster().type();
@@ -85,6 +101,12 @@ public class AllocatableClusterResources {
public int nodes() { return nodes; }
public int groups() { return groups; }
+
+ public int groupSize() {
+ // ceil: If the division does not produce a whole number we assume some node is missing
+ return (int)Math.ceil((double)nodes / groups);
+ }
+
public ClusterSpec.Type clusterType() { return clusterType; }
public double cost() { return nodes * costOf(advertisedResources); }
@@ -121,4 +143,61 @@ public class AllocatableClusterResources {
(fulfilment < 1.0 ? " (fulfilment " + fulfilment + ")" : "");
}
+ /**
+ * Returns the best matching allocatable node resources given ideal node resources,
+ * or empty if none available within the limits.
+ */
+ public static Optional<AllocatableClusterResources> from(ClusterResources resources,
+ ClusterSpec.Type clusterType,
+ Limits limits,
+ NodeRepository nodeRepository) {
+ NodeResources cappedNodeResources = limits.cap(resources.nodeResources());
+ cappedNodeResources = new NodeResourceLimits(nodeRepository.zone()).enlargeToLegal(cappedNodeResources, clusterType);
+
+ if (allowsHostSharing(nodeRepository.zone().cloud())) {
+ // return the requested resources, or empty if they cannot fit on existing hosts
+ for (Flavor flavor : nodeRepository.flavors().getFlavors()) {
+ if (flavor.resources().satisfies(cappedNodeResources))
+ return Optional.of(new AllocatableClusterResources(resources.with(cappedNodeResources),
+ cappedNodeResources,
+ resources.nodeResources(),
+ clusterType));
+ }
+ return Optional.empty();
+ }
+ else {
+ // return the cheapest flavor satisfying the target resources, if any
+ Optional<AllocatableClusterResources> best = Optional.empty();
+ for (Flavor flavor : nodeRepository.flavors().getFlavors()) {
+ NodeResources flavorResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor);
+ if (flavor.resources().storageType() == NodeResources.StorageType.remote) {
+ flavor = flavor.with(FlavorOverrides.ofDisk(cappedNodeResources.diskGb()));
+ flavorResources = flavorResources.withDiskGb(cappedNodeResources.diskGb()); // TODO: Do this in resourcesCalculator
+ }
+ if ( ! between(limits.min().nodeResources(), limits.max().nodeResources(), flavorResources)) continue;
+
+ var candidate = new AllocatableClusterResources(resources.with(flavor.resources()),
+ flavor,
+ resources.nodeResources(),
+ clusterType,
+ nodeRepository.resourcesCalculator());
+ if (best.isEmpty() || candidate.preferableTo(best.get()))
+ best = Optional.of(candidate);
+ }
+ return best;
+ }
+ }
+
+ private static boolean between(NodeResources min, NodeResources max, NodeResources r) {
+ if ( ! min.isUnspecified() && ! r.justNumbers().satisfies(min.justNumbers())) return false;
+ if ( ! max.isUnspecified() && ! max.justNumbers().satisfies(r.justNumbers())) return false;
+ return true;
+ }
+
+ // TODO: Put this in zone config instead?
+ private static boolean allowsHostSharing(CloudName cloudName) {
+ if (cloudName.value().equals("aws")) return false;
+ return true;
+ }
+
}
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
new file mode 100644
index 00000000000..8d26bb89959
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java
@@ -0,0 +1,172 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.autoscale;
+
+import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.NodeResources;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+
+import java.util.Optional;
+
+/**
+ * A searcher of the space of possible allocation
+ *
+ * @author bratseth
+ */
+public class AllocationOptimizer {
+
+ private final NodeRepository nodeRepository;
+
+ public AllocationOptimizer(NodeRepository nodeRepository) {
+ this.nodeRepository = nodeRepository;
+ }
+
+ /**
+ * An AllocationSearcher searches the space of possible allocations given a target
+ * and (optionally) cluster limits and returns the best alternative.
+ *
+ * @return the best allocation, if there are any possible legal allocations, fulfilling the target
+ * fully or partially, within the limits
+ */
+ public Optional<AllocatableClusterResources> findBestAllocation(ResourceTarget target,
+ AllocatableClusterResources current,
+ Limits limits) {
+ Optional<AllocatableClusterResources> bestAllocation = Optional.empty();
+ for (ResourceIterator i = new ResourceIterator(target, current, limits); i.hasNext(); ) {
+ var allocatableResources = AllocatableClusterResources.from(i.next(), current.clusterType(), limits, nodeRepository);
+ if (allocatableResources.isEmpty()) continue;
+ if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get()))
+ bestAllocation = allocatableResources;
+ }
+ return bestAllocation;
+ }
+
+ /**
+ * Provides iteration over possible cluster resource allocations given a target total load
+ * and current groups/nodes allocation.
+ */
+ private static class ResourceIterator {
+
+ // The min and max nodes to consider when not using application supplied limits
+ private static final int minimumNodes = 3; // Since this number includes redundancy it cannot be lower than 2
+ private static final int maximumNodes = 150;
+
+ // When a query is issued on a node the cost is the sum of a fixed cost component and a cost component
+ // proportional to document count. We must account for this when comparing configurations with more or fewer nodes.
+ // TODO: Measure this, and only take it into account with queries
+ private static final double fixedCpuCostFraction = 0.1;
+
+ // Given state
+ private final Limits limits;
+ private final AllocatableClusterResources current;
+ private final ResourceTarget target;
+
+ // Derived from the observed state
+ private final int nodeIncrement;
+ private final boolean singleGroupMode;
+
+ // Iterator state
+ private int currentNodes;
+
+ public ResourceIterator(ResourceTarget target, AllocatableClusterResources current, Limits limits) {
+ this.target = target;
+ this.current = current;
+ this.limits = limits;
+
+ // What number of nodes is it effective to add or remove at the time from this cluster?
+ // This is the group size, since we (for now) assume the group size is decided by someone wiser than us
+ // and we decide the number of groups.
+ // The exception is when we only have one group, where we can add and remove single nodes in it.
+ singleGroupMode = current.groups() == 1;
+ nodeIncrement = singleGroupMode ? 1 : current.groupSize();
+
+ // Step to the right starting point
+ currentNodes = current.nodes();
+ if (currentNodes < minNodes()) { // step up
+ while (currentNodes < minNodes()
+ && (singleGroupMode || currentNodes + nodeIncrement > current.groupSize())) // group level redundancy
+ currentNodes += nodeIncrement;
+ }
+ else { // step down
+ while (currentNodes - nodeIncrement >= minNodes()
+ && (singleGroupMode || currentNodes - nodeIncrement > current.groupSize())) // group level redundancy
+ currentNodes -= nodeIncrement;
+ }
+ }
+
+ public ClusterResources next() {
+ ClusterResources next = resourcesWith(currentNodes);
+ currentNodes += nodeIncrement;
+ return next;
+ }
+
+ public boolean hasNext() {
+ return currentNodes <= maxNodes();
+ }
+
+ private int minNodes() {
+ if (limits.isEmpty()) return minimumNodes;
+ if (singleGroupMode) return limits.min().nodes();
+ return Math.max(limits.min().nodes(), limits.min().groups() * current.groupSize() );
+ }
+
+ private int maxNodes() {
+ if (limits.isEmpty()) return maximumNodes;
+ if (singleGroupMode) return limits.max().nodes();
+ return Math.min(limits.max().nodes(), limits.max().groups() * current.groupSize() );
+ }
+
+ private ClusterResources resourcesWith(int nodes) {
+ int nodesAdjustedForRedundancy = nodes;
+ if (target.adjustForRedundancy())
+ nodesAdjustedForRedundancy = nodes - (singleGroupMode ? 1 : current.groupSize());
+ return new ClusterResources(nodes,
+ singleGroupMode ? 1 : nodes / current.groupSize(),
+ nodeResourcesWith(nodesAdjustedForRedundancy));
+ }
+
+ /**
+ * For the observed load this instance is initialized with, returns the resources needed per node to be at
+ * ideal load given a target node count
+ */
+ private NodeResources nodeResourcesWith(int nodeCount) {
+ // Cpu: Scales with cluster size (TODO: Only reads, writes scales with group size)
+ // Memory and disk: Scales with group size
+
+ double cpu, memory, disk;
+ if (singleGroupMode) {
+ // The fixed cost portion of cpu does not scale with changes to the node count
+ // TODO: Only for the portion of cpu consumed by queries
+ cpu = fixedCpuCostFraction * target.clusterCpu() / current.groupSize() +
+ (1 - fixedCpuCostFraction) * target.clusterCpu() / nodeCount;
+
+ if (current.clusterType().isContent()) { // load scales with node share of content
+ memory = target.groupMemory() / nodeCount;
+ disk = target.groupDisk() / nodeCount;
+ }
+ else {
+ memory = target.nodeMemory();
+ disk = target.nodeDisk();
+ }
+ }
+ else {
+ cpu = target.clusterCpu() / nodeCount;
+ if (current.clusterType().isContent()) { // load scales with node share of content
+ memory = target.groupMemory() / current.groupSize();
+ disk = target.groupDisk() / current.groupSize();
+ }
+ else {
+ memory = target.nodeMemory();
+ disk = target.nodeDisk();
+ }
+ }
+
+ // Combine the scaled resource values computed here
+ // with the currently configured non-scaled values, given in the limits, if any
+ NodeResources nonScaled = limits.isEmpty() || limits.min().nodeResources().isUnspecified()
+ ? current.toAdvertisedClusterResources().nodeResources()
+ : limits.min().nodeResources(); // min=max for non-scaled
+ return nonScaled.withVcpu(cpu).withMemoryGb(memory).withDiskGb(disk);
+ }
+
+ }
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
index 6dca9c9a796..8930bf34f4a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java
@@ -1,17 +1,11 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.autoscale;
-import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeResources;
-import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
-import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
-import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits;
import java.time.Duration;
import java.util.List;
@@ -41,18 +35,14 @@ public class Autoscaler {
/** What difference factor for a resource is worth a reallocation? */
private static final double resourceDifferenceWorthReallocation = 0.1;
- private final HostResourcesCalculator resourcesCalculator;
private final NodeMetricsDb metricsDb;
private final NodeRepository nodeRepository;
- private final NodeResourceLimits nodeResourceLimits;
+ private final AllocationOptimizer allocationOptimizer;
- public Autoscaler(HostResourcesCalculator resourcesCalculator,
- NodeMetricsDb metricsDb,
- NodeRepository nodeRepository) {
- this.resourcesCalculator = resourcesCalculator;
+ public Autoscaler(NodeMetricsDb metricsDb, NodeRepository nodeRepository) {
this.metricsDb = metricsDb;
this.nodeRepository = nodeRepository;
- this.nodeResourceLimits = new NodeResourceLimits(nodeRepository.zone());
+ this.allocationOptimizer = new AllocationOptimizer(nodeRepository);
}
/**
@@ -63,61 +53,41 @@ public class Autoscaler {
* @return a new suggested allocation for this cluster, or empty if it should not be rescaled at this time
*/
public Optional<ClusterResources> suggest(Cluster cluster, List<Node> clusterNodes) {
- return autoscale(cluster, clusterNodes, false)
+ return autoscale(clusterNodes, Limits.empty())
.map(AllocatableClusterResources::toAdvertisedClusterResources);
}
/**
- * Autoscale a cluster. This returns a better allocation (if found) inside the min and max limits.
+ * Autoscale a cluster by load. This returns a better allocation (if found) inside the min and max limits.
*
* @param clusterNodes the list of all the active nodes in a cluster
* @return a new suggested allocation for this cluster, or empty if it should not be rescaled at this time
*/
public Optional<ClusterResources> autoscale(Cluster cluster, List<Node> clusterNodes) {
if (cluster.minResources().equals(cluster.maxResources())) return Optional.empty(); // Shortcut
- return autoscale(cluster, clusterNodes, true)
+ return autoscale(clusterNodes, Limits.of(cluster))
.map(AllocatableClusterResources::toAdvertisedClusterResources);
}
- private Optional<AllocatableClusterResources> autoscale(Cluster cluster, List<Node> clusterNodes, boolean respectLimits) {
+ private Optional<AllocatableClusterResources> autoscale(List<Node> clusterNodes, Limits limits) {
if (unstable(clusterNodes)) return Optional.empty();
ClusterSpec.Type clusterType = clusterNodes.get(0).allocation().get().membership().cluster().type();
- AllocatableClusterResources currentAllocation = new AllocatableClusterResources(clusterNodes, resourcesCalculator);
+ AllocatableClusterResources currentAllocation = new AllocatableClusterResources(clusterNodes, nodeRepository);
Optional<Double> cpuLoad = averageLoad(Resource.cpu, clusterNodes, clusterType);
Optional<Double> memoryLoad = averageLoad(Resource.memory, clusterNodes, clusterType);
Optional<Double> diskLoad = averageLoad(Resource.disk, clusterNodes, clusterType);
if (cpuLoad.isEmpty() || memoryLoad.isEmpty() || diskLoad.isEmpty()) return Optional.empty();
+ var target = ResourceTarget.idealLoad(cpuLoad.get(), memoryLoad.get(), diskLoad.get(), currentAllocation);
- Optional<AllocatableClusterResources> bestAllocation = findBestAllocation(cpuLoad.get(),
- memoryLoad.get(),
- diskLoad.get(),
- currentAllocation,
- cluster,
- respectLimits);
+ Optional<AllocatableClusterResources> bestAllocation =
+ allocationOptimizer.findBestAllocation(target, currentAllocation, limits);
if (bestAllocation.isEmpty()) return Optional.empty();
if (similar(bestAllocation.get(), currentAllocation)) return Optional.empty();
return bestAllocation;
}
- private Optional<AllocatableClusterResources> findBestAllocation(double cpuLoad, double memoryLoad, double diskLoad,
- AllocatableClusterResources currentAllocation,
- Cluster cluster, boolean respectLimits) {
- Optional<AllocatableClusterResources> bestAllocation = Optional.empty();
- for (ResourceIterator i = new ResourceIterator(cpuLoad, memoryLoad, diskLoad, currentAllocation, cluster, respectLimits);
- i.hasNext(); ) {
- Optional<AllocatableClusterResources> allocatableResources = toAllocatableResources(i.next(),
- currentAllocation.clusterType(),
- cluster,
- respectLimits);
- if (allocatableResources.isEmpty()) continue;
- if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get()))
- bestAllocation = allocatableResources;
- }
- return bestAllocation;
- }
-
/** Returns true if both total real resources and total cost are similar */
private boolean similar(AllocatableClusterResources a, AllocatableClusterResources b) {
return similar(a.cost(), b.cost(), costDifferenceWorthReallocation) &&
@@ -135,51 +105,6 @@ public class Autoscaler {
}
/**
- * Returns the smallest allocatable node resources larger than the given node resources,
- * or empty if none available.
- */
- private Optional<AllocatableClusterResources> toAllocatableResources(ClusterResources resources,
- ClusterSpec.Type clusterType,
- Cluster cluster,
- boolean respectLimits) {
- NodeResources nodeResources = resources.nodeResources();
- if (respectLimits)
- nodeResources = cluster.capAtLimits(nodeResources);
- nodeResources = nodeResourceLimits.enlargeToLegal(nodeResources, clusterType); // enforce system limits
-
- if (allowsHostSharing(nodeRepository.zone().cloud())) {
- // return the requested resources, or empty if they cannot fit on existing hosts
- for (Flavor flavor : nodeRepository.getAvailableFlavors().getFlavors()) {
- if (flavor.resources().satisfies(nodeResources))
- return Optional.of(new AllocatableClusterResources(resources.with(nodeResources),
- nodeResources,
- resources.nodeResources(),
- clusterType));
- }
- return Optional.empty();
- }
- else {
- // return the cheapest flavor satisfying the target resources, if any
- Optional<AllocatableClusterResources> best = Optional.empty();
- for (Flavor flavor : nodeRepository.getAvailableFlavors().getFlavors()) {
- if ( ! flavor.resources().satisfies(nodeResources)) continue;
-
- if (flavor.resources().storageType() == NodeResources.StorageType.remote)
- flavor = flavor.with(FlavorOverrides.ofDisk(nodeResources.diskGb()));
- var candidate = new AllocatableClusterResources(resources.with(flavor.resources()),
- flavor,
- resources.nodeResources(),
- clusterType,
- resourcesCalculator);
-
- if (best.isEmpty() || candidate.cost() <= best.get().cost())
- best = Optional.of(candidate);
- }
- return best;
- }
- }
-
- /**
* Returns the average load of this resource in the measurement window,
* or empty if we are not in a position to make decisions from these measurements at this time.
*/
@@ -200,12 +125,6 @@ public class Autoscaler {
return Duration.ofHours(12); // TODO: Measure much more often to get this down to minutes. And, ideally we should take node startup time into account
}
- // TODO: Put this in zone config instead?
- private boolean allowsHostSharing(CloudName cloudName) {
- if (cloudName.value().equals("aws")) return false;
- return true;
- }
-
public static boolean unstable(List<Node> nodes) {
return nodes.stream().anyMatch(node -> node.status().wantToRetire() ||
node.allocation().get().membership().retired() ||
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java
new file mode 100644
index 00000000000..7ca60c4c86d
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java
@@ -0,0 +1,69 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.autoscale;
+
+import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.NodeResources;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
+
+/**
+ * Optional allocation limits
+ *
+ * @author bratseth
+ */
+public class Limits {
+
+ private static final Limits empty = new Limits(null, null);
+
+ private final ClusterResources min, max;
+
+ private Limits(ClusterResources min, ClusterResources max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ public static Limits empty() { return empty; }
+
+ public boolean isEmpty() { return this == empty; }
+
+ public ClusterResources min() {
+ if (isEmpty()) throw new IllegalStateException("Empty: No min");
+ return min;
+ }
+
+ public ClusterResources max() {
+ if (isEmpty()) throw new IllegalStateException("Empty: No max");
+ return max;
+ }
+
+ /** Caps the given resources at the limits of this. If it is empty the node resources are returned as-is */
+ public NodeResources cap(NodeResources resources) {
+ if (isEmpty()) return resources;
+ if (min.nodeResources().isUnspecified()) return resources; // means max is also unspecified
+ resources = resources.withVcpu(between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), resources.vcpu()));
+ resources = resources.withMemoryGb(between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), resources.memoryGb()));
+ resources = resources.withDiskGb(between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), resources.diskGb()));
+ return resources;
+ }
+
+ private double between(double min, double max, double value) {
+ value = Math.max(min, value);
+ value = Math.min(max, value);
+ return value;
+ }
+
+ public static Limits of(Cluster cluster) {
+ return new Limits(cluster.minResources(), cluster.maxResources());
+ }
+
+ public static Limits of(Capacity capacity) {
+ return new Limits(capacity.minResources(), capacity.maxResources());
+ }
+
+ @Override
+ public String toString() {
+ if (isEmpty()) return "no limits";
+ return "limits: from " + min + " to " + max;
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java
deleted file mode 100644
index 207eecc1871..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.provision.autoscale;
-
-import com.yahoo.config.provision.ClusterResources;
-import com.yahoo.config.provision.NodeResources;
-import com.yahoo.vespa.hosted.provision.applications.Cluster;
-
-/**
- * Provides iteration over possible cluster resource allocations given a target total load
- * and current groups/nodes allocation.
- */
-public class ResourceIterator {
-
- // The min and max nodes to consider when not using application supplied limits
- private static final int minimumNodes = 3; // Since this number includes redundancy it cannot be lower than 2
- private static final int maximumNodes = 150;
-
- // When a query is issued on a node the cost is the sum of a fixed cost component and a cost component
- // proportional to document count. We must account for this when comparing configurations with more or fewer nodes.
- // TODO: Measure this, and only take it into account with queries
- private static final double fixedCpuCostFraction = 0.1;
-
- // Prescribed state
- private final Cluster cluster;
- private final boolean respectLimits;
-
- // Observed state
- private final AllocatableClusterResources allocation;
- private final double cpuLoad;
- private final double memoryLoad;
- private final double diskLoad;
- private final int groupSize;
-
- // Derived from the observed state
- private final int nodeIncrement;
- private final boolean singleGroupMode;
-
- // Iterator state
- private int currentNodes;
-
- public ResourceIterator(double cpuLoad, double memoryLoad, double diskLoad,
- AllocatableClusterResources currentAllocation,
- Cluster cluster,
- boolean respectLimits) {
- this.cpuLoad = cpuLoad;
- this.memoryLoad = memoryLoad;
- this.diskLoad = diskLoad;
- this.respectLimits = respectLimits;
-
- // ceil: If the division does not produce a whole number we assume some node is missing
- groupSize = (int)Math.ceil((double)currentAllocation.nodes() / currentAllocation.groups());
- allocation = currentAllocation;
-
- this.cluster = cluster;
-
- // What number of nodes is it effective to add or remove at the time from this cluster?
- // This is the group size, since we (for now) assume the group size is decided by someone wiser than us
- // and we decide the number of groups.
- // The exception is when we only have one group, where we can add and remove single nodes in it.
- singleGroupMode = currentAllocation.groups() == 1;
- nodeIncrement = singleGroupMode ? 1 : groupSize;
-
- // Step down to the right starting point
- currentNodes = currentAllocation.nodes();
- while (currentNodes - nodeIncrement >= minNodes()
- && ( singleGroupMode || currentNodes - nodeIncrement > groupSize)) // group level redundancy
- currentNodes -= nodeIncrement;
- }
-
- public ClusterResources next() {
- ClusterResources next = resourcesWith(currentNodes);
- currentNodes += nodeIncrement;
- return next;
- }
-
- public boolean hasNext() {
- return currentNodes <= maxNodes();
- }
-
- private int minNodes() {
- if ( ! respectLimits) return minimumNodes;
- if (singleGroupMode) return cluster.minResources().nodes();
- return Math.max(cluster.minResources().nodes(), cluster.minResources().groups() * groupSize );
- }
-
- private int maxNodes() {
- if ( ! respectLimits) return maximumNodes;
- if (singleGroupMode) return cluster.maxResources().nodes();
- return Math.min(cluster.maxResources().nodes(), cluster.maxResources().groups() * groupSize );
- }
-
- private ClusterResources resourcesWith(int nodes) {
- int nodesWithRedundancy = nodes - (singleGroupMode ? 1 : groupSize);
- return new ClusterResources(nodes,
- singleGroupMode ? 1 : nodes / groupSize,
- nodeResourcesWith(nodesWithRedundancy));
- }
-
- /**
- * For the observed load this instance is initialized with, returns the resources needed per node to be at
- * ideal load given a target node count
- */
- private NodeResources nodeResourcesWith(int nodeCount) {
- // Cpu: Scales with cluster size (TODO: Only reads, writes scales with group size)
- // Memory and disk: Scales with group size
-
- double cpu, memory, disk;
- if (singleGroupMode) {
- // The fixed cost portion of cpu does not scale with changes to the node count
- // TODO: Only for the portion of cpu consumed by queries
- double totalCpu = clusterUsage(Resource.cpu, cpuLoad);
- cpu = fixedCpuCostFraction * totalCpu / groupSize / Resource.cpu.idealAverageLoad() +
- (1 - fixedCpuCostFraction) * totalCpu / nodeCount / Resource.cpu.idealAverageLoad();
- if (allocation.clusterType().isContent()) { // load scales with node share of content
- memory = groupUsage(Resource.memory, memoryLoad) / nodeCount / Resource.memory.idealAverageLoad();
- disk = groupUsage(Resource.disk, diskLoad) / nodeCount / Resource.disk.idealAverageLoad();
- }
- else {
- memory = nodeUsage(Resource.memory, memoryLoad) / Resource.memory.idealAverageLoad();
- disk = nodeUsage(Resource.disk, diskLoad) / Resource.disk.idealAverageLoad();
- }
- }
- else {
- cpu = clusterUsage(Resource.cpu, cpuLoad) / nodeCount / Resource.cpu.idealAverageLoad();
- if (allocation.clusterType().isContent()) { // load scales with node share of content
- memory = groupUsage(Resource.memory, memoryLoad) / groupSize / Resource.memory.idealAverageLoad();
- disk = groupUsage(Resource.disk, diskLoad) / groupSize / Resource.disk.idealAverageLoad();
- }
- else {
- memory = nodeUsage(Resource.memory, memoryLoad) / Resource.memory.idealAverageLoad();
- disk = nodeUsage(Resource.disk, diskLoad) / Resource.disk.idealAverageLoad();
- }
- }
-
- // Combine the scaled resource values computed here
- // and the currently combined values of non-scaled resources
- return new NodeResources(cpu, memory, disk,
- cluster.minResources().nodeResources().bandwidthGbps(),
- cluster.minResources().nodeResources().diskSpeed(),
- cluster.minResources().nodeResources().storageType());
- }
-
- private double clusterUsage(Resource resource, double load) {
- return nodeUsage(resource, load) * allocation.nodes();
- }
-
- private double groupUsage(Resource resource, double load) {
- return nodeUsage(resource, load) * groupSize;
- }
-
- private double nodeUsage(Resource resource, double load) {
- return load * resource.valueFrom(allocation.realResources());
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java
new file mode 100644
index 00000000000..287cd2ae86a
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java
@@ -0,0 +1,74 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.autoscale;
+
+import com.yahoo.config.provision.NodeResources;
+
+/**
+ * A resource target to hit for the allocation optimizer.
+ * The target is measured in cpu, memory and disk per node in the allocation given by current.
+ */
+public class ResourceTarget {
+
+ private final boolean adjustForRedundancy;
+
+ /** The target resources per node, assuming the node assignment in current */
+ private final double cpu, memory, disk;
+
+ /** The current allocation leading to this target */
+ private final AllocatableClusterResources current;
+
+ private ResourceTarget(double cpu, double memory, double disk,
+ boolean adjustForRedundancy,
+ AllocatableClusterResources current) {
+ this.cpu = cpu;
+ this.memory = memory;
+ this.disk = disk;
+ this.adjustForRedundancy = adjustForRedundancy;
+ this.current = current;
+ }
+
+ /** Are the target resources given by this including redundancy or not */
+ public boolean adjustForRedundancy() { return adjustForRedundancy; }
+
+ /** Returns the target total cpu to allocate to the entire cluster */
+ public double clusterCpu() { return nodeCpu() * current.nodes(); }
+
+ /** Returns the target total memory to allocate to each group */
+ public double groupMemory() { return nodeMemory() * current.groupSize(); }
+
+ /** Returns the target total disk to allocate to each group */
+ public double groupDisk() { return nodeDisk() * current.groupSize(); }
+
+ /** Returns the target cpu per node, in terms of the current allocation */
+ public double nodeCpu() { return cpu; }
+
+ /** Returns the target memory per node, in terms of the current allocation */
+ public double nodeMemory() { return memory; }
+
+ /** Returns the target disk per node, in terms of the current allocation */
+ public double nodeDisk() { return disk; }
+
+ private static double nodeUsage(Resource resource, double load, AllocatableClusterResources current) {
+ return load * resource.valueFrom(current.realResources());
+ }
+
+ /** Create a target of achieving ideal load given a current load */
+ public static ResourceTarget idealLoad(double currentCpuLoad, double currentMemoryLoad, double currentDiskLoad,
+ AllocatableClusterResources current) {
+ return new ResourceTarget(nodeUsage(Resource.cpu, currentCpuLoad, current) / Resource.cpu.idealAverageLoad(),
+ nodeUsage(Resource.memory, currentMemoryLoad, current) / Resource.memory.idealAverageLoad(),
+ nodeUsage(Resource.disk, currentDiskLoad, current) / Resource.disk.idealAverageLoad(),
+ true,
+ current);
+ }
+
+ /** Crete a target of preserving a current allocation */
+ public static ResourceTarget preserve(AllocatableClusterResources current) {
+ return new ResourceTarget(current.realResources().vcpu(),
+ current.realResources().memoryGb(),
+ current.realResources().diskGb(),
+ false,
+ current);
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
index f6398c04e61..09723d83e3e 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.lb;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.util.Set;
@@ -23,7 +24,8 @@ public interface LoadBalancerService {
* pre-existing load balancer).
* @return The provisioned load balancer instance
*/
- LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force);
+ LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force,
+ NodeRepository nodeRepository);
/** Permanently remove load balancer for given application cluster */
void remove(ApplicationId application, ClusterSpec.Id cluster);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
index 91f02a31f6b..9bd1189420a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.util.Collections;
import java.util.HashMap;
@@ -29,7 +30,8 @@ public class LoadBalancerServiceMock implements LoadBalancerService {
}
@Override
- public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force) {
+ public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force,
+ NodeRepository nodeRepository) {
var id = new LoadBalancerId(application, cluster);
var oldInstance = instances.get(id);
if (!force && oldInstance != null && !oldInstance.reals().isEmpty() && reals.isEmpty()) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java
index 891faceca7a..07074bc45af 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.lb;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.util.Comparator;
import java.util.Optional;
@@ -17,7 +18,8 @@ import java.util.Set;
public class PassthroughLoadBalancerService implements LoadBalancerService {
@Override
- public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force) {
+ public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force,
+ NodeRepository nodeRepository) {
var real = reals.stream()
.min(Comparator.naturalOrder())
.orElseThrow(() -> new IllegalArgumentException("No reals given"));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
index f670ff93073..42a52601527 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java
@@ -27,15 +27,13 @@ public class SharedLoadBalancerService implements LoadBalancerService {
private static final Comparator<Node> hostnameComparator = Comparator.comparing(Node::hostname);
- private final NodeRepository nodeRepository;
-
@Inject
- public SharedLoadBalancerService(NodeRepository nodeRepository) {
- this.nodeRepository = Objects.requireNonNull(nodeRepository, "nodeRepository must be non-null");
+ public SharedLoadBalancerService() {
}
@Override
- public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force) {
+ public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set<Real> reals, boolean force,
+ NodeRepository nodeRepository) {
var proxyNodes = new ArrayList<>(nodeRepository.getNodes(NodeType.proxy));
proxyNodes.sort(hostnameComparator);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
index da8a0a14171..fa8e8375e23 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
@@ -34,13 +34,12 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer {
private final Metric metric;
public AutoscalingMaintainer(NodeRepository nodeRepository,
- HostResourcesCalculator hostResourcesCalculator,
NodeMetricsDb metricsDb,
Deployer deployer,
Metric metric,
Duration interval) {
super(nodeRepository, interval);
- this.autoscaler = new Autoscaler(hostResourcesCalculator, metricsDb, nodeRepository);
+ this.autoscaler = new Autoscaler(metricsDb, nodeRepository);
this.metric = metric;
this.deployer = deployer;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
index 5c899f74bdb..85909daa240 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java
@@ -44,15 +44,15 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer {
private static final ApplicationId preprovisionAppId = ApplicationId.from("hosted-vespa", "tenant-host", "preprovision");
private final HostProvisioner hostProvisioner;
- private final HostResourcesCalculator hostResourcesCalculator;
private final BooleanFlag dynamicProvisioningEnabled;
private final ListFlag<PreprovisionCapacity> preprovisionCapacityFlag;
- DynamicProvisioningMaintainer(NodeRepository nodeRepository, Duration interval, HostProvisioner hostProvisioner,
- HostResourcesCalculator hostResourcesCalculator, FlagSource flagSource) {
+ DynamicProvisioningMaintainer(NodeRepository nodeRepository,
+ Duration interval,
+ HostProvisioner hostProvisioner,
+ FlagSource flagSource) {
super(nodeRepository, interval);
this.hostProvisioner = hostProvisioner;
- this.hostResourcesCalculator = hostResourcesCalculator;
this.dynamicProvisioningEnabled = Flags.ENABLE_DYNAMIC_PROVISIONING.bindTo(flagSource);
this.preprovisionCapacityFlag = Flags.PREPROVISION_CAPACITY.bindTo(flagSource);
}
@@ -111,7 +111,7 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer {
NodeResources resources = it.next();
removableHosts.stream()
.filter(nodeRepository()::canAllocateTenantNodeTo)
- .filter(host -> hostResourcesCalculator.advertisedResourcesOf(host.flavor()).satisfies(resources))
+ .filter(host -> nodeRepository().resourcesCalculator().advertisedResourcesOf(host.flavor()).satisfies(resources))
.min(Comparator.comparingInt(n -> n.flavor().cost()))
.ifPresent(host -> {
removableHosts.remove(host);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
index d793874119a..483b4dc8f84 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
@@ -99,7 +99,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
// Remove any real no longer allocated to this application
reals.removeIf(real -> !allocatedNodes.contains(real.hostname().value()));
try {
- service.create(lb.id().application(), lb.id().cluster(), reals, true);
+ service.create(lb.id().application(), lb.id().cluster(), reals, true, nodeRepository());
db.writeLoadBalancer(lb.with(lb.instance().withReals(reals)));
} catch (Exception e) {
failed.add(lb.id());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index a50e6d10696..d388fb5a967 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -87,13 +87,13 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
loadBalancerExpirer = provisionServiceProvider.getLoadBalancerService().map(lbService ->
new LoadBalancerExpirer(nodeRepository, defaults.loadBalancerExpirerInterval, lbService));
dynamicProvisioningMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner ->
- new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, provisionServiceProvider.getHostResourcesCalculator(), flagSource));
+ new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, flagSource));
capacityReportMaintainer = new CapacityReportMaintainer(nodeRepository, metric, defaults.capacityReportInterval);
osUpgradeActivator = new OsUpgradeActivator(nodeRepository, defaults.osUpgradeActivatorInterval);
- rebalancer = new Rebalancer(deployer, nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), provisionServiceProvider.getHostProvisioner(), metric, clock, defaults.rebalancerInterval);
+ rebalancer = new Rebalancer(deployer, nodeRepository, provisionServiceProvider.getHostProvisioner(), metric, clock, defaults.rebalancerInterval);
nodeMetricsDbMaintainer = new NodeMetricsDbMaintainer(nodeRepository, nodeMetrics, nodeMetricsDb, defaults.nodeMetricsCollectionInterval);
- autoscalingMaintainer = new AutoscalingMaintainer(nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), nodeMetricsDb, deployer, metric, defaults.autoscalingInterval);
- scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), nodeMetricsDb, defaults.scalingSuggestionsInterval);
+ autoscalingMaintainer = new AutoscalingMaintainer(nodeRepository, nodeMetricsDb, deployer, metric, defaults.autoscalingInterval);
+ scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, nodeMetricsDb, defaults.scalingSuggestionsInterval);
// The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now
infrastructureProvisioner.maintainButThrowOnException();
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
index 0ba3765f470..7ffb541be2a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java
@@ -25,21 +25,18 @@ import java.util.Optional;
public class Rebalancer extends NodeRepositoryMaintainer {
private final Deployer deployer;
- private final HostResourcesCalculator hostResourcesCalculator;
private final Optional<HostProvisioner> hostProvisioner;
private final Metric metric;
private final Clock clock;
public Rebalancer(Deployer deployer,
NodeRepository nodeRepository,
- HostResourcesCalculator hostResourcesCalculator,
Optional<HostProvisioner> hostProvisioner,
Metric metric,
Clock clock,
Duration interval) {
super(nodeRepository, interval);
this.deployer = deployer;
- this.hostResourcesCalculator = hostResourcesCalculator;
this.hostProvisioner = hostProvisioner;
this.metric = metric;
this.clock = clock;
@@ -64,7 +61,7 @@ public class Rebalancer extends NodeRepositoryMaintainer {
/** We do this here rather than in MetricsReporter because it is expensive and frequent updates are unnecessary */
private void updateSkewMetric(NodeList allNodes) {
- DockerHostCapacity capacity = new DockerHostCapacity(allNodes, hostResourcesCalculator);
+ DockerHostCapacity capacity = new DockerHostCapacity(allNodes, nodeRepository().resourcesCalculator());
double totalSkew = 0;
int hostCount = 0;
for (Node host : allNodes.nodeType((NodeType.host)).state(Node.State.active)) {
@@ -86,7 +83,7 @@ public class Rebalancer extends NodeRepositoryMaintainer {
* Returns Move.none if no moves can be made to reduce skew.
*/
private Move findBestMove(NodeList allNodes) {
- DockerHostCapacity capacity = new DockerHostCapacity(allNodes, hostResourcesCalculator);
+ DockerHostCapacity capacity = new DockerHostCapacity(allNodes, nodeRepository().resourcesCalculator());
Move bestMove = Move.none;
for (Node node : allNodes.nodeType(NodeType.tenant).state(Node.State.active)) {
if (node.parentHostname().isEmpty()) continue;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
index 332126690be..b68e8eacbaa 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
@@ -30,11 +30,10 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer {
private final Autoscaler autoscaler;
public ScalingSuggestionsMaintainer(NodeRepository nodeRepository,
- HostResourcesCalculator hostResourcesCalculator,
NodeMetricsDb metricsDb,
Duration interval) {
super(nodeRepository, interval);
- this.autoscaler = new Autoscaler(hostResourcesCalculator, metricsDb, nodeRepository);
+ this.autoscaler = new Autoscaler(metricsDb, nodeRepository);
}
@Override
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
index 648bf52f455..a2bf83eb6c3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
@@ -46,7 +46,7 @@ public class CapacityPolicies {
}
public NodeResources decideNodeResources(NodeResources requested, Capacity capacity, ClusterSpec cluster) {
- if (requested == NodeResources.unspecified)
+ if (requested.isUnspecified())
requested = defaultNodeResources(cluster.type());
ensureSufficientResources(requested, cluster);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java
index 9566213bc91..da54d89b4e5 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService;
import java.util.Optional;
@@ -13,7 +14,7 @@ import java.util.Optional;
*/
public class EmptyProvisionServiceProvider implements ProvisionServiceProvider {
- private final HostResourcesCalculator hostResourcesCalculator = new NoopHostResourcesCalculator();
+ private final HostResourcesCalculator hostResourcesCalculator = new IdentityHostResourcesCalculator();
@Override
public Optional<LoadBalancerService> getLoadBalancerService() {
@@ -30,10 +31,10 @@ public class EmptyProvisionServiceProvider implements ProvisionServiceProvider {
return hostResourcesCalculator;
}
- public static class NoopHostResourcesCalculator implements HostResourcesCalculator {
+ private static class IdentityHostResourcesCalculator implements HostResourcesCalculator {
@Override
- public NodeResources realResourcesOf(Node node) {
+ public NodeResources realResourcesOf(Node node, NodeRepository repository) {
return node.flavor().resources();
}
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 8143076a3b2..2a39cc333e0 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
@@ -30,15 +30,14 @@ public class GroupPreparer {
private final NodeRepository nodeRepository;
private final Optional<HostProvisioner> hostProvisioner;
- private final HostResourcesCalculator hostResourcesCalculator;
private final BooleanFlag dynamicProvisioningEnabledFlag;
private final ListFlag<PreprovisionCapacity> preprovisionCapacityFlag;
- public GroupPreparer(NodeRepository nodeRepository, Optional<HostProvisioner> hostProvisioner,
- HostResourcesCalculator hostResourcesCalculator, FlagSource flagSource) {
+ public GroupPreparer(NodeRepository nodeRepository,
+ Optional<HostProvisioner> hostProvisioner,
+ FlagSource flagSource) {
this.nodeRepository = nodeRepository;
this.hostProvisioner = hostProvisioner;
- this.hostResourcesCalculator = hostResourcesCalculator;
this.dynamicProvisioningEnabledFlag = Flags.ENABLE_DYNAMIC_PROVISIONING.bindTo(flagSource);
this.preprovisionCapacityFlag = Flags.PREPROVISION_CAPACITY.bindTo(flagSource);
}
@@ -72,18 +71,23 @@ public class GroupPreparer {
// Create a prioritized set of nodes
LockedNodeList nodeList = nodeRepository.list(allocationLock);
- NodePrioritizer prioritizer = new NodePrioritizer(nodeList, application, cluster, requestedNodes,
- spareCount, wantedGroups, nodeRepository.nameResolver(),
- hostResourcesCalculator, allocateFully);
+ NodePrioritizer prioritizer = new NodePrioritizer(nodeList,
+ application,
+ cluster,
+ requestedNodes,
+ spareCount,
+ wantedGroups,
+ nodeRepository.nameResolver(),
+ nodeRepository.resourcesCalculator(),
+ allocateFully);
prioritizer.addApplicationNodes();
prioritizer.addSurplusNodes(surplusActiveNodes);
prioritizer.addReadyNodes();
prioritizer.addNewDockerNodes(nodeRepository::canAllocateTenantNodeTo);
-
// Allocate from the prioritized list
NodeAllocation allocation = new NodeAllocation(nodeList, application, cluster, requestedNodes,
- highestIndex, nodeRepository.getAvailableFlavors(),
+ highestIndex, nodeRepository.flavors(),
nodeRepository.zone(), nodeRepository.clock());
allocation.offer(prioritizer.prioritize());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java
index b26351062e6..096e58e963e 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
/**
* Some cloud providers advertise that a certain amount of resources are available in a flavor
@@ -16,7 +17,7 @@ import com.yahoo.vespa.hosted.provision.Node;
public interface HostResourcesCalculator {
/** Nodes use advertised resources. This returns the real resources for the node. */
- NodeResources realResourcesOf(Node node);
+ NodeResources realResourcesOf(Node node, NodeRepository nodeRepository);
/** Flavors use real resources. This returns the advertised resources of the flavor. */
NodeResources advertisedResourcesOf(Flavor flavor);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
index 3532d989d85..026788e9d6c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
@@ -169,7 +169,7 @@ public class LoadBalancerProvisioner {
log.log(Level.FINE, "Creating load balancer for " + cluster + " in " + application.toShortString() +
", targeting: " + reals);
try {
- return service.create(application, cluster, reals, force);
+ return service.create(application, cluster, reals, force, nodeRepository);
} catch (Exception e) {
throw new LoadBalancerServiceException("Failed to (re)configure load balancer for " + cluster + " in " +
application + ", targeting: " + reals + ". The operation will be " +
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 6f3f83c3349..a7d83bbfad9 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
@@ -5,6 +5,8 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
+
+import java.util.ArrayList;
import java.util.logging.Level;
import com.yahoo.vespa.hosted.provision.LockedNodeList;
import com.yahoo.vespa.hosted.provision.Node;
@@ -195,6 +197,8 @@ public class NodePrioritizer {
.forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode));
}
+ public List<PrioritizableNode> nodes() { return new ArrayList<>(nodes.values()); }
+
/**
* Convert a list of nodes to a list of node priorities. This includes finding, calculating
* parameters to the priority sorting procedure.
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
index 4000354243f..6f026254bcd 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java
@@ -22,6 +22,10 @@ import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
+import com.yahoo.vespa.hosted.provision.autoscale.AllocatableClusterResources;
+import com.yahoo.vespa.hosted.provision.autoscale.AllocationOptimizer;
+import com.yahoo.vespa.hosted.provision.autoscale.Limits;
+import com.yahoo.vespa.hosted.provision.autoscale.ResourceTarget;
import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter;
import com.yahoo.vespa.hosted.provision.node.filter.NodeHostFilter;
@@ -31,7 +35,6 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
-import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -47,6 +50,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
private static final int SPARE_CAPACITY_NONPROD = 0;
private final NodeRepository nodeRepository;
+ private final AllocationOptimizer allocationOptimizer;
private final CapacityPolicies capacityPolicies;
private final Zone zone;
private final Preparer preparer;
@@ -61,6 +65,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
public NodeRepositoryProvisioner(NodeRepository nodeRepository, Zone zone,
ProvisionServiceProvider provisionServiceProvider, FlagSource flagSource) {
this.nodeRepository = nodeRepository;
+ this.allocationOptimizer = new AllocationOptimizer(nodeRepository);
this.capacityPolicies = new CapacityPolicies(zone);
this.zone = zone;
this.loadBalancerProvisioner = provisionServiceProvider.getLoadBalancerService().map(lbService -> new LoadBalancerProvisioner(nodeRepository, lbService));
@@ -94,7 +99,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
NodeResources resources;
NodeSpec nodeSpec;
if ( requested.type() == NodeType.tenant) {
- ClusterResources target = decideTargetResources(application, cluster.id(), requested);
+ ClusterResources target = decideTargetResources(application, cluster, requested);
int nodeCount = capacityPolicies.decideSize(target.nodes(), requested, cluster, application);
resources = capacityPolicies.decideNodeResources(target.nodeResources(), requested, cluster);
boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive());
@@ -131,66 +136,39 @@ public class NodeRepositoryProvisioner implements Provisioner {
* Returns the target cluster resources, a value between the min and max in the requested capacity,
* and updates the application store with the received min and max.
*/
- private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec.Id clusterId, Capacity requested) {
+ private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec clusterSpec, Capacity requested) {
try (Mutex lock = nodeRepository.lock(applicationId)) {
Application application = nodeRepository.applications().get(applicationId).orElse(new Application(applicationId));
- application = application.withClusterLimits(clusterId, requested.minResources(), requested.maxResources());
+ application = application.withClusterLimits(clusterSpec.id(), requested.minResources(), requested.maxResources());
nodeRepository.applications().put(application, lock);
- return application.clusters().get(clusterId).targetResources()
- .orElseGet(() -> currentResources(applicationId, clusterId, requested));
+ return application.clusters().get(clusterSpec.id()).targetResources()
+ .orElseGet(() -> currentResources(applicationId, clusterSpec, requested));
}
}
/** Returns the current resources of this cluster, or the closes */
private ClusterResources currentResources(ApplicationId applicationId,
- ClusterSpec.Id clusterId,
+ ClusterSpec clusterSpec,
Capacity requested) {
List<Node> nodes = NodeList.copyOf(nodeRepository.getNodes(applicationId, Node.State.active))
- .cluster(clusterId)
+ .cluster(clusterSpec.id())
.not().retired()
.not().removable()
.asList();
- if (nodes.isEmpty()) return requested.minResources(); // New deployment: Start at min
-
- long groups = nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
- var currentResources = new ClusterResources(nodes.size(), (int)groups, nodes.get(0).flavor().resources());
- return ensureWithin(requested.minResources(), requested.maxResources(), currentResources);
+ AllocatableClusterResources currentResources =
+ nodes.isEmpty() ? new AllocatableClusterResources(requested.minResources(), clusterSpec.type()) // new deployment: Use min
+ : new AllocatableClusterResources(nodes, nodeRepository);
+ return ensureWithin(Limits.of(requested), currentResources);
}
/** Make the minimal adjustments needed to the current resources to stay within the limits */
- private ClusterResources ensureWithin(ClusterResources min, ClusterResources max, ClusterResources current) {
- int nodes = between(min.nodes(), max.nodes(), current.nodes());
- int groups = between(min.groups(), max.groups(), current.groups());
- if (nodes % groups != 0) {
- // That didn't work - try to preserve current group size instead.
- // Rounding here is needed because a node may be missing due to node failing.
- int currentGroupsSize = Math.round((float)current.nodes() / current.groups());
- nodes = currentGroupsSize * groups;
- if (nodes != between(min.nodes(), max.nodes(), nodes)) {
- // Give up: Use max
- nodes = max.nodes();
- groups = max.groups();
- }
- }
- if (min.nodeResources() != NodeResources.unspecified && max.nodeResources() != NodeResources.unspecified) {
- double vcpu = between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), current.nodeResources().vcpu());
- double memoryGb = between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), current.nodeResources().memoryGb());
- double diskGb = between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), current.nodeResources().diskGb());
- // Combine computed scaled resources with requested non-scaled resources (for which min=max)
- NodeResources nodeResources = min.nodeResources().withVcpu(vcpu).withMemoryGb(memoryGb).withDiskGb(diskGb);
- return new ClusterResources(nodes, groups, nodeResources);
- }
- else {
- return new ClusterResources(nodes, groups, current.nodeResources());
- }
- }
-
- private int between(int min, int max, int n) {
- return Math.min(max, Math.max(min, n));
- }
+ private ClusterResources ensureWithin(Limits limits, AllocatableClusterResources current) {
+ if (limits.isEmpty()) return current.toAdvertisedClusterResources();
+ if (limits.min().equals(limits.max())) return limits.min();
- private double between(double min, double max, double n) {
- return Math.min(max, Math.max(min, n));
+ return allocationOptimizer.findBestAllocation(ResourceTarget.preserve(current), current, limits)
+ .orElseThrow(() -> new IllegalArgumentException("No allocation possible within " + limits))
+ .toAdvertisedClusterResources();
}
private void logIfDownscaled(int targetNodes, int actualNodes, ClusterSpec cluster, ProvisionLogger logger) {
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 91c15cdb61b..f88caffa6c6 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
@@ -33,7 +33,7 @@ class Preparer {
this.nodeRepository = nodeRepository;
this.spareCount = spareCount;
this.loadBalancerProvisioner = loadBalancerProvisioner;
- this.groupPreparer = new GroupPreparer(nodeRepository, hostProvisioner, hostResourcesCalculator, flagSource);
+ this.groupPreparer = new GroupPreparer(nodeRepository, hostProvisioner, flagSource);
}
/** Prepare all required resources for the given application and cluster */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 6fafd496174..f5a68f71a1c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -27,7 +27,9 @@ import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.IP;
import com.yahoo.vespa.hosted.provision.node.Status;
+import com.yahoo.vespa.hosted.provision.provisioning.EmptyProvisionServiceProvider;
import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner;
+import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider;
import java.time.Clock;
import java.time.Instant;
@@ -55,7 +57,11 @@ public class MockNodeRepository extends NodeRepository {
* @param flavors flavors to have in node repo
*/
public MockNodeRepository(MockCurator curator, NodeFlavors flavors) {
- super(flavors, curator, Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(),
+ super(flavors,
+ new EmptyProvisionServiceProvider().getHostResourcesCalculator(),
+ curator,
+ Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")),
+ Zone.defaultZone(),
new MockNameResolver().mockAnyLookup(),
DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"),
true);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java
index 0d5950fe33a..9a02f65daf9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java
@@ -26,7 +26,7 @@ public class MockProvisionServiceProvider implements ProvisionServiceProvider {
}
public MockProvisionServiceProvider(LoadBalancerService loadBalancerService, HostProvisioner hostProvisioner) {
- this(loadBalancerService, hostProvisioner, new EmptyProvisionServiceProvider.NoopHostResourcesCalculator());
+ this(loadBalancerService, hostProvisioner, new EmptyProvisionServiceProvider().getHostResourcesCalculator());
}
public MockProvisionServiceProvider(LoadBalancerService loadBalancerService, HostProvisioner hostProvisioner,