aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@gmail.com>2023-01-21 17:50:10 +0100
committerJon Bratseth <bratseth@gmail.com>2023-01-21 17:50:10 +0100
commit7a6af9caa065b3ab63b094d78b7347d7df6bea0f (patch)
tree48e80650a9cd88016ff8e618d85d727a28f3542c /node-repository
parent00d86602a88c66486c8f4c68a1c8bdff096c7273 (diff)
Support a group size constraint in content clusters
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java18
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java27
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java19
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java26
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java62
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java8
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json2
17 files changed, 166 insertions, 48 deletions
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 0a731f66418..60a5682101f 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.applications;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -28,6 +29,7 @@ public class Cluster {
private final ClusterSpec.Id id;
private final boolean exclusive;
private final ClusterResources min, max;
+ private final IntRange groupSize;
private final boolean required;
private final Autoscaling suggested;
private final Autoscaling target;
@@ -39,6 +41,7 @@ public class Cluster {
boolean exclusive,
ClusterResources minResources,
ClusterResources maxResources,
+ IntRange groupSize,
boolean required,
Autoscaling suggested,
Autoscaling target,
@@ -47,6 +50,7 @@ public class Cluster {
this.exclusive = exclusive;
this.min = Objects.requireNonNull(minResources);
this.max = Objects.requireNonNull(maxResources);
+ this.groupSize = Objects.requireNonNull(groupSize);
this.required = required;
this.suggested = Objects.requireNonNull(suggested);
Objects.requireNonNull(target);
@@ -68,6 +72,9 @@ public class Cluster {
/** Returns the configured maximal resources in this cluster */
public ClusterResources maxResources() { return max; }
+ /** Returns the configured group size range in this cluster */
+ public IntRange groupSize() { return groupSize; }
+
/**
* Returns whether the resources of this cluster are required to be within the specified min and max.
* Otherwise they may be adjusted by capacity policies.
@@ -105,16 +112,16 @@ public class Cluster {
public Cluster withConfiguration(boolean exclusive, Capacity capacity) {
return new Cluster(id, exclusive,
- capacity.minResources(), capacity.maxResources(), capacity.isRequired(),
+ capacity.minResources(), capacity.maxResources(), capacity.groupSize(), capacity.isRequired(),
suggested, target, scalingEvents);
}
public Cluster withSuggested(Autoscaling suggested) {
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
+ return new Cluster(id, exclusive, min, max, groupSize, required, suggested, target, scalingEvents);
}
public Cluster withTarget(Autoscaling target) {
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
+ return new Cluster(id, exclusive, min, max, groupSize, required, suggested, target, scalingEvents);
}
/** Add or update (based on "at" time) a scaling event */
@@ -128,7 +135,7 @@ public class Cluster {
scalingEvents.add(scalingEvent);
prune(scalingEvents);
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
+ return new Cluster(id, exclusive, min, max, groupSize, required, suggested, target, scalingEvents);
}
@Override
@@ -158,7 +165,8 @@ public class Cluster {
}
public static Cluster create(ClusterSpec.Id id, boolean exclusive, Capacity requested) {
- return new Cluster(id, exclusive, requested.minResources(), requested.maxResources(), requested.isRequired(),
+ return new Cluster(id, exclusive,
+ requested.minResources(), requested.maxResources(), requested.groupSize(), requested.isRequired(),
Autoscaling.empty(), Autoscaling.empty(), List.of());
}
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 56a6d61ebee..c3dc0ff250f 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. 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.collections.IntRange;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.NodeRepository;
@@ -38,7 +39,8 @@ public class AllocationOptimizer {
int minimumNodes = AllocationOptimizer.minimumNodes;
if (limits.isEmpty())
limits = Limits.of(new ClusterResources(minimumNodes, 1, NodeResources.unspecified()),
- new ClusterResources(maximumNodes, maximumNodes, NodeResources.unspecified()));
+ new ClusterResources(maximumNodes, maximumNodes, NodeResources.unspecified()),
+ IntRange.empty());
else
limits = atLeast(minimumNodes, limits).fullySpecified(current.clusterSpec(), nodeRepository, clusterModel.application().id());
Optional<AllocatableClusterResources> bestAllocation = Optional.empty();
@@ -50,6 +52,7 @@ public class AllocationOptimizer {
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;
+ if ( ! limits.groupSize().includes(nodes / groups)) continue;
var resources = new ClusterResources(nodes,
groups,
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
index cb5d8dd5042..9de7134a6d7 100644
--- 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. 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.collections.IntRange;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
@@ -19,13 +20,15 @@ import java.util.Objects;
*/
public class Limits {
- private static final Limits empty = new Limits(null, null);
+ private static final Limits empty = new Limits(null, null, IntRange.empty());
private final ClusterResources min, max;
+ private final IntRange groupSize;
- private Limits(ClusterResources min, ClusterResources max) {
+ private Limits(ClusterResources min, ClusterResources max, IntRange groupSize) {
this.min = min;
this.max = max;
+ this.groupSize = groupSize;
}
public static Limits empty() { return empty; }
@@ -42,12 +45,14 @@ public class Limits {
return max;
}
+ public IntRange groupSize() { return groupSize; }
+
public Limits withMin(ClusterResources min) {
- return new Limits(min, max);
+ return new Limits(min, max, groupSize);
}
public Limits withMax(ClusterResources max) {
- return new Limits(min, max);
+ return new Limits(min, max, groupSize);
}
/** Caps the given resources at the limits of this. If it is empty the node resources are returned as-is */
@@ -66,7 +71,7 @@ public class Limits {
var defaultResources = new CapacityPolicies(nodeRepository).defaultNodeResources(clusterSpec, applicationId);
var specifiedMin = min.nodeResources().isUnspecified() ? min.with(defaultResources) : min;
var specifiedMax = max.nodeResources().isUnspecified() ? max.with(defaultResources) : max;
- return new Limits(specifiedMin, specifiedMax);
+ return new Limits(specifiedMin, specifiedMax, groupSize);
}
private double between(double min, double max, double value) {
@@ -76,21 +81,23 @@ public class Limits {
}
public static Limits of(Cluster cluster) {
- return new Limits(cluster.minResources(), cluster.maxResources());
+ return new Limits(cluster.minResources(), cluster.maxResources(), cluster.groupSize());
}
public static Limits of(Capacity capacity) {
- return new Limits(capacity.minResources(), capacity.maxResources());
+ return new Limits(capacity.minResources(), capacity.maxResources(), capacity.groupSize());
}
- public static Limits of(ClusterResources min, ClusterResources max) {
- return new Limits(Objects.requireNonNull(min, "min"), Objects.requireNonNull(max, "max"));
+ public static Limits of(ClusterResources min, ClusterResources max, IntRange groupSize) {
+ return new Limits(Objects.requireNonNull(min, "min"),
+ Objects.requireNonNull(max, "max"),
+ Objects.requireNonNull(groupSize, "groupSize"));
}
@Override
public String toString() {
if (isEmpty()) return "no limits";
- return "limits: from " + min + " to " + max;
+ return "limits: from " + min + " to " + max + ( groupSize.isEmpty() ? "" : " with group size " + groupSize);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java
index 2f5b057e927..4d2fae8a4d7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.persistence;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -23,6 +24,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import java.util.OptionalInt;
/**
* Application JSON serializer
@@ -48,6 +50,7 @@ public class ApplicationSerializer {
private static final String exclusiveKey = "exclusive";
private static final String minResourcesKey = "min";
private static final String maxResourcesKey = "max";
+ private static final String groupSizeKey = "groupSize";
private static final String requiredKey = "required";
private static final String suggestedKey = "suggested";
private static final String resourcesKey = "resources";
@@ -122,6 +125,7 @@ public class ApplicationSerializer {
clusterObject.setBool(exclusiveKey, cluster.exclusive());
toSlime(cluster.minResources(), clusterObject.setObject(minResourcesKey));
toSlime(cluster.maxResources(), clusterObject.setObject(maxResourcesKey));
+ toSlime(cluster.groupSize(), clusterObject.setObject(groupSizeKey));
clusterObject.setBool(requiredKey, cluster.required());
toSlime(cluster.suggested(), clusterObject.setObject(suggestedKey));
toSlime(cluster.target(), clusterObject.setObject(targetKey));
@@ -133,6 +137,7 @@ public class ApplicationSerializer {
clusterObject.field(exclusiveKey).asBool(),
clusterResourcesFromSlime(clusterObject.field(minResourcesKey)),
clusterResourcesFromSlime(clusterObject.field(maxResourcesKey)),
+ intRangeFromSlime(clusterObject.field(groupSizeKey)),
clusterObject.field(requiredKey).asBool(),
autoscalingFromSlime(clusterObject.field(suggestedKey), clusterObject.field("nonExisting")),
autoscalingFromSlime(clusterObject.field(targetKey), clusterObject.field(autoscalingStatusObjectKey)),
@@ -165,6 +170,16 @@ public class ApplicationSerializer {
NodeResourcesSerializer.resourcesFromSlime(clusterResourcesObject.field(nodeResourcesKey)));
}
+ private static void toSlime(IntRange range, Cursor rangeObject) {
+ range.from().ifPresent(from -> rangeObject.setLong(fromKey, from));
+ range.to().ifPresent(from -> rangeObject.setLong(toKey, from));
+ }
+
+ private static IntRange intRangeFromSlime(Inspector rangeObject) {
+ if ( ! rangeObject.valid()) return IntRange.empty();
+ return new IntRange(optionalInt(rangeObject.field(fromKey)), optionalInt(rangeObject.field(toKey)));
+ }
+
private static void toSlime(Load load, Cursor loadObject) {
loadObject.setDouble(cpuKey, load.cpu());
loadObject.setDouble(memoryKey, load.memory());
@@ -257,4 +272,8 @@ public class ApplicationSerializer {
return inspector.valid() ? Optional.of(Instant.ofEpochMilli(inspector.asLong())) : Optional.empty();
}
+ private static OptionalInt optionalInt(Inspector inspector) {
+ return inspector.valid() ? OptionalInt.of((int)inspector.asLong()) : OptionalInt.empty();
+ }
+
}
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 a1400626658..081de1fd8dc 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
@@ -39,8 +39,11 @@ public class CapacityPolicies {
}
public Capacity applyOn(Capacity capacity, ApplicationId application, boolean exclusive) {
- return capacity.withLimits(applyOn(capacity.minResources(), capacity, application, exclusive),
- applyOn(capacity.maxResources(), capacity, application, exclusive));
+ var min = applyOn(capacity.minResources(), capacity, application, exclusive);
+ var max = applyOn(capacity.maxResources(), capacity, application, exclusive);
+ var groupSize = capacity.groupSize().fromAtMost(max.nodes() / max.groups())
+ .toAtLeast(min.nodes() / min.groups());
+ return capacity.withLimits(min, max, groupSize);
}
private ClusterResources applyOn(ClusterResources resources, Capacity capacity, ApplicationId application, boolean exclusive) {
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 59e67f65f8f..bc623b495c8 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.provisioning;
+import com.yahoo.collections.IntRange;
import com.yahoo.component.annotation.Inject;
import com.yahoo.config.provision.ActivationContext;
import com.yahoo.config.provision.ApplicationId;
@@ -98,7 +99,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
cluster = capacityPolicies.decideExclusivity(requested, cluster);
Capacity actual = capacityPolicies.applyOn(requested, application, cluster.isExclusive());
ClusterResources target = decideTargetResources(application, cluster, actual);
- ensureRedundancy(target.nodes(), cluster, actual.canFail(), application);
+ validate(actual, target, cluster, application);
logIfDownscaled(requested.minResources().nodes(), actual.minResources().nodes(), cluster, logger);
groups = target.groups();
@@ -204,18 +205,17 @@ public class NodeRepositoryProvisioner implements Provisioner {
.advertisedResources();
}
- /**
- * Throw if the node count is 1 for container and content clusters and we're in a production zone
- *
- * @throws IllegalArgumentException if only one node is requested and we can fail
- */
- private void ensureRedundancy(int nodeCount, ClusterSpec cluster, boolean canFail, ApplicationId application) {
- if (! application.instance().isTester() &&
- canFail &&
- nodeCount == 1 &&
- requiresRedundancy(cluster.type()) &&
- zone.environment().isProduction())
- throw new IllegalArgumentException("Deployments to prod require at least 2 nodes per cluster for redundancy. Not fulfilled for " + cluster);
+ private void validate(Capacity actual, ClusterResources target, ClusterSpec cluster, ApplicationId application) {
+ if ( ! actual.canFail()) return;
+
+ if (! application.instance().isTester() && zone.environment().isProduction() &&
+ requiresRedundancy(cluster.type()) && target.nodes() == 1)
+ throw new IllegalArgumentException("In " + cluster +
+ ": Deployments to prod require at least 2 nodes per cluster for redundancy.");
+
+ if ( ! actual.groupSize().includes(target.nodes() / target.groups()))
+ throw new IllegalArgumentException("In " + cluster + ": Group size with " + target +
+ " is not within allowed group size " + actual.groupSize());
}
private static boolean requiresRedundancy(ClusterSpec.Type clusterType) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
index cb927c72eb5..6936a767936 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.restapi;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
@@ -62,6 +63,8 @@ public class ApplicationSerializer {
Limits limits = Limits.of(cluster).fullySpecified(nodes.clusterSpec(), nodeRepository, application.id());
toSlime(limits.min(), clusterObject.setObject("min"));
toSlime(limits.max(), clusterObject.setObject("max"));
+ if ( ! cluster.groupSize().isEmpty())
+ toSlime(cluster.groupSize(), clusterObject.setObject("groupSize"));
toSlime(currentResources, clusterObject.setObject("current"));
if (cluster.shouldSuggestResources(currentResources))
toSlime(cluster.suggested(), clusterObject.setObject("suggested"));
@@ -85,6 +88,11 @@ public class ApplicationSerializer {
NodeResourcesSerializer.toSlime(resources.nodeResources(), clusterResourcesObject.setObject("resources"));
}
+ private static void toSlime(IntRange range, Cursor rangeObject) {
+ range.from().ifPresent(from -> rangeObject.setLong("from", range.from().getAsInt()));
+ range.to().ifPresent(to -> rangeObject.setLong("to", range.to().getAsInt()));
+ }
+
private static void toSlime(Load load, Cursor utilizationObject) {
utilizationObject.setDouble("cpu", load.cpu());
utilizationObject.setDouble("memory", load.memory());
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 92ffe9828c3..e50db5c0d55 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.testutils;
+import com.yahoo.collections.IntRange;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ActivationContext;
import com.yahoo.config.provision.ApplicationId;
@@ -193,7 +194,11 @@ public class MockNodeRepository extends NodeRepository {
activate(provisioner.prepare(app1Id,
cluster1Id,
Capacity.from(new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1)),
- new ClusterResources(8, 2, new NodeResources(4, 16, 1000, 1)), false, true),
+ new ClusterResources(8, 2, new NodeResources(4, 16, 1000, 1)),
+ IntRange.empty(),
+ false,
+ true,
+ Optional.empty()),
null), app1Id, provisioner);
Application app1 = applications().get(app1Id).get();
Cluster cluster1 = app1.cluster(cluster1Id.id()).get();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
index e1b32726070..35197da4315 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. 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.collections.IntRange;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -353,6 +354,23 @@ public class AutoscalingTest {
}
@Test
+ public void autoscaling_respects_group_size_limit() {
+ var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1));
+ var now = new ClusterResources(5, 5, new NodeResources(3.0, 10, 10, 1));
+ var max = new ClusterResources(18, 6, new NodeResources(100, 1000, 1000, 1));
+ var fixture = AutoscalingTester.fixture()
+ .awsProdSetup(true)
+ .initialResources(Optional.of(now))
+ .capacity(Capacity.from(min, max, IntRange.of(2, 3), false, true, Optional.empty()))
+ .build();
+ fixture.tester().clock().advance(Duration.ofDays(2));
+ fixture.loader().applyCpuLoad(0.4, 240);
+ fixture.tester().assertResources("Scaling cpu up",
+ 12, 6, 2.8, 4.3, 10.0,
+ fixture.autoscale());
+ }
+
+ @Test
public void test_autoscaling_limits_when_min_equals_max() {
ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1));
var fixture = AutoscalingTester.fixture().awsProdSetup(true).capacity(Capacity.from(min, min)).build();
@@ -427,7 +445,7 @@ public class AutoscalingTest {
}
@Test
- public void test_autoscaling_group_size_1() {
+ public void test_autoscaling_group_size_unconstrained() {
var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1));
var now = new ClusterResources(5, 5, new NodeResources(3, 100, 100, 1));
var max = new ClusterResources(20, 20, new NodeResources(10, 1000, 1000, 1));
@@ -444,6 +462,23 @@ public class AutoscalingTest {
}
@Test
+ public void test_autoscaling_group_size_1() {
+ var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1));
+ var now = new ClusterResources(5, 5, new NodeResources(3, 100, 100, 1));
+ var max = new ClusterResources(20, 20, new NodeResources(10, 1000, 1000, 1));
+ var fixture = AutoscalingTester.fixture()
+ .awsProdSetup(true)
+ .initialResources(Optional.of(now))
+ .capacity(Capacity.from(min, max, IntRange.of(1), false, true, Optional.empty()))
+ .build();
+ fixture.tester().clock().advance(Duration.ofDays(2));
+ fixture.loader().applyCpuLoad(0.9, 120);
+ fixture.tester().assertResources("Scaling up to 2 nodes, scaling memory and disk down at the same time",
+ 7, 7, 9.4, 80.8, 85.2,
+ fixture.autoscale());
+ }
+
+ @Test
public void test_autoscaling_groupsize_by_cpu_read_dominated() {
var min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1));
var now = new ClusterResources(6, 2, new NodeResources(3, 100, 100, 1));
@@ -672,6 +707,23 @@ public class AutoscalingTest {
fixture.autoscale().resources().isEmpty());
}
+ @Test
+ public void test_autoscaling_in_dev_with_cluster_size_constraint() {
+ var min = new ClusterResources(4, 1,
+ new NodeResources(1, 4, 10, 1, NodeResources.DiskSpeed.any));
+ var max = new ClusterResources(20, 20,
+ new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any));
+ var fixture = AutoscalingTester.fixture()
+ .awsSetup(true, Environment.dev)
+ .capacity(Capacity.from(min, max, IntRange.of(3, 5), false, true, Optional.empty()))
+ .build();
+ fixture.tester().clock().advance(Duration.ofDays(2));
+ fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 200);
+ fixture.tester().assertResources("Scale only to a single node and group since this is dev",
+ 1, 1, 0.1, 24.8, 131.1,
+ fixture.autoscale());
+ }
+
/** Same setup as test_autoscaling_in_dev(), just with required = true */
@Test
public void test_autoscaling_in_dev_with_required_resources_preprovisioned() {
@@ -680,8 +732,10 @@ public class AutoscalingTest {
new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)),
new ClusterResources(20, 1,
new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)),
+ IntRange.empty(),
true,
- true);
+ true,
+ Optional.empty());
var fixture = AutoscalingTester.fixture()
.hostCount(5)
@@ -700,8 +754,10 @@ public class AutoscalingTest {
var requiredCapacity =
Capacity.from(new ClusterResources(1, 1, NodeResources.unspecified()),
new ClusterResources(3, 1, NodeResources.unspecified()),
+ IntRange.empty(),
+ true,
true,
- true);
+ Optional.empty());
var fixture = AutoscalingTester.fixture()
.hostCount(5)
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
index 4e6b8dec9ef..05d0822758d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java
@@ -131,6 +131,7 @@ class AutoscalingTester {
cluster.exclusive(),
cluster.minResources(),
cluster.maxResources(),
+ cluster.groupSize(),
cluster.required(),
cluster.suggested(),
cluster.target(),
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java
index 2da65fc1a2f..af47001cd84 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
@@ -61,10 +62,10 @@ public class AutoscalingMaintainerTest {
tester.deploy(app1, cluster1, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
- false, true));
+ IntRange.empty(), false, true, Optional.empty()));
tester.deploy(app2, cluster2, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
new ClusterResources(10, 1, new NodeResources(6.5, 9, 20, 0.1)),
- false, true));
+ IntRange.empty(), false, true, Optional.empty()));
tester.clock().advance(Duration.ofMinutes(10));
tester.maintainer().maintain(); // noop
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 27a944a471e..58589f540cf 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.collections.IntRange;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
@@ -466,7 +467,7 @@ public class HostCapacityMaintainerTest {
ClusterSpec spec = ProvisioningTester.contentClusterSpec();
ClusterResources resources = new ClusterResources(2, 1, new NodeResources(16, 24, 100, 1));
CloudAccount cloudAccount0 = CloudAccount.from("000000000000");
- Capacity capacity0 = Capacity.from(resources, resources, false, true, Optional.of(cloudAccount0));
+ Capacity capacity0 = Capacity.from(resources, resources, IntRange.empty(), false, true, Optional.of(cloudAccount0));
List<HostSpec> prepared = provisioningTester.prepare(applicationId, spec, capacity0);
// Hosts are provisioned in requested account
@@ -476,7 +477,7 @@ public class HostCapacityMaintainerTest {
// Redeployment in different account provisions a new set of hosts
CloudAccount cloudAccount1 = CloudAccount.from("100000000000");
- Capacity capacity1 = Capacity.from(resources, resources, false, true, Optional.of(cloudAccount1));
+ Capacity capacity1 = Capacity.from(resources, resources, IntRange.empty(), false, true, Optional.of(cloudAccount1));
prepared = provisioningTester.prepare(applicationId, spec, capacity1);
provisionHostsIn(cloudAccount1, 2, tester);
assertEquals(2, provisioningTester.activate(applicationId, prepared).size());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
index c94d864ba9d..36c9819e10d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;
+import com.yahoo.collections.IntRange;
import com.yahoo.collections.Pair;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
@@ -56,10 +57,10 @@ public class ScalingSuggestionsMaintainerTest {
tester.deploy(app1, cluster1, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
- false, true));
+ IntRange.empty(), false, true, Optional.empty()));
tester.deploy(app2, cluster2, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
new ClusterResources(10, 1, new NodeResources(6.5, 5, 15, 0.1)),
- false, true));
+ IntRange.empty(), false, true, Optional.empty()));
tester.clock().advance(Duration.ofHours(13));
Duration timeAdded = addMeasurements(0.90f, 0.90f, 0.90f, 0, 500, app1, tester.nodeRepository());
@@ -96,7 +97,8 @@ public class ScalingSuggestionsMaintainerTest {
addMeasurements(0.7f, 0.7f, 0.7f, 0, 500, app1, tester.nodeRepository());
maintainer.maintain();
var suggested = tester.nodeRepository().applications().get(app1).get().cluster(cluster1.id()).get().suggested().resources().get();
- tester.deploy(app1, cluster1, Capacity.from(suggested, suggested, false, true));
+ tester.deploy(app1, cluster1, Capacity.from(suggested, suggested,
+ IntRange.empty(), false, true, Optional.empty()));
tester.clock().advance(Duration.ofDays(2));
addMeasurements(0.2f, 0.65f, 0.6f,
0, 500, app1, tester.nodeRepository());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java
index a05cc388bea..51f775c7286 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.persistence;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -34,6 +35,7 @@ public class ApplicationSerializerTest {
false,
new ClusterResources( 8, 4, new NodeResources(1, 2, 3, 4)),
new ClusterResources(12, 6, new NodeResources(3, 6, 21, 24)),
+ IntRange.empty(),
true,
Autoscaling.empty(),
Autoscaling.empty(),
@@ -43,6 +45,7 @@ public class ApplicationSerializerTest {
true,
new ClusterResources( 8, 4, minResources),
new ClusterResources(14, 7, new NodeResources(3, 6, 21, 24)),
+ IntRange.of(3, 5),
false,
new Autoscaling(Autoscaling.Status.unavailable,
"",
@@ -79,11 +82,11 @@ public class ApplicationSerializerTest {
Cluster serializedCluster = serialized.clusters().get(originalCluster.id());
assertNotNull(serializedCluster);
assertNotSame(originalCluster, serializedCluster);
- assertEquals(originalCluster, serializedCluster);
assertEquals(originalCluster.id(), serializedCluster.id());
assertEquals(originalCluster.exclusive(), serializedCluster.exclusive());
assertEquals(originalCluster.minResources(), serializedCluster.minResources());
assertEquals(originalCluster.maxResources(), serializedCluster.maxResources());
+ assertEquals(originalCluster.groupSize(), serializedCluster.groupSize());
assertEquals(originalCluster.required(), serializedCluster.required());
assertEquals(originalCluster.suggested(), serializedCluster.suggested());
assertEquals(originalCluster.target(), serializedCluster.target());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
index 3653e20d848..9a18bbb76ee 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import ai.vespa.http.DomainName;
import com.google.common.collect.Iterators;
+import com.yahoo.collections.IntRange;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.CloudAccount;
@@ -316,7 +317,7 @@ public class LoadBalancerProvisionerTest {
@Test
public void load_balancer_with_custom_settings() {
ClusterResources resources = new ClusterResources(3, 1, nodeResources);
- Capacity capacity = Capacity.from(resources, resources, false, true, Optional.of(CloudAccount.empty));
+ Capacity capacity = Capacity.from(resources, resources, IntRange.empty(), false, true, Optional.of(CloudAccount.empty));
tester.activate(app1, prepare(app1, capacity, clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("c1"))));
LoadBalancerList loadBalancers = tester.nodeRepository().loadBalancers().list();
assertEquals(1, loadBalancers.size());
@@ -336,7 +337,7 @@ public class LoadBalancerProvisionerTest {
ClusterResources resources = new ClusterResources(3, 1, nodeResources);
CloudAccount cloudAccount0 = CloudAccount.empty;
{
- Capacity capacity = Capacity.from(resources, resources, false, true, Optional.of(cloudAccount0));
+ Capacity capacity = Capacity.from(resources, resources, IntRange.empty(), false, true, Optional.of(cloudAccount0));
tester.activate(app1, prepare(app1, capacity, clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("c1"))));
}
LoadBalancerList loadBalancers = tester.nodeRepository().loadBalancers().list();
@@ -345,7 +346,7 @@ public class LoadBalancerProvisionerTest {
// Changing account fails if there is an existing LB in the previous account.
CloudAccount cloudAccount1 = CloudAccount.from("111111111111");
- Capacity capacity = Capacity.from(resources, resources, false, true, Optional.of(cloudAccount1));
+ Capacity capacity = Capacity.from(resources, resources, IntRange.empty(), false, true, Optional.of(cloudAccount1));
try {
prepare(app1, capacity, clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("c1")));
fail("Expected exception");
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json
index ead4e5fd06a..c9046998e91 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json
@@ -103,7 +103,7 @@
{
"from": {
"nodes": 0,
- "groups": 0,
+ "groups": 1,
"resources": {
"vcpu" : 0.0,
"memoryGb": 0.0,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json
index f60fdf3e602..4166d20ad8d 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json
@@ -62,7 +62,7 @@
{
"from": {
"nodes": 0,
- "groups": 0,
+ "groups": 1,
"resources": {
"vcpu" : 0.0,
"memoryGb": 0.0,