summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java159
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java12
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/AutoscalingData.java44
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java1
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterUtilizationData.java37
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java50
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json47
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json47
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/AutoscalingStatus.java67
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java62
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java76
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java107
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java57
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializer.java107
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java57
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java20
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java31
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java15
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java34
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/ApplicationSerializerTest.java31
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json80
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json27
30 files changed, 704 insertions, 582 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java
index cab32c7b5ce..b7a71cc7b91 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Cluster.java
@@ -20,30 +20,20 @@ public class Cluster {
private final ClusterResources min;
private final ClusterResources max;
private final ClusterResources current;
- private final Optional<ClusterResources> target;
- private final Optional<ClusterResources> suggested;
- private final Utilization utilization;
+ private final Autoscaling target;
+ private final Autoscaling suggested;
private final List<ScalingEvent> scalingEvents;
- private final String autoscalingStatusCode;
- private final String autoscalingStatus;
private final Duration scalingDuration;
- private final double maxQueryGrowthRate;
- private final double currentQueryFractionOfMax;
public Cluster(ClusterSpec.Id id,
ClusterSpec.Type type,
ClusterResources min,
ClusterResources max,
ClusterResources current,
- Optional<ClusterResources> target,
- Optional<ClusterResources> suggested,
- Utilization utilization,
+ Autoscaling target,
+ Autoscaling suggested,
List<ScalingEvent> scalingEvents,
- String autoscalingStatusCode,
- String autoscalingStatus,
- Duration scalingDuration,
- double maxQueryGrowthRate,
- double currentQueryFractionOfMax) {
+ Duration scalingDuration) {
this.id = id;
this.type = type;
this.min = min;
@@ -51,69 +41,33 @@ public class Cluster {
this.current = current;
this.target = target;
this.suggested = suggested;
- this.utilization = utilization;
this.scalingEvents = scalingEvents;
- this.autoscalingStatusCode = autoscalingStatusCode;
- this.autoscalingStatus = autoscalingStatus;
this.scalingDuration = scalingDuration;
- this.maxQueryGrowthRate = maxQueryGrowthRate;
- this.currentQueryFractionOfMax = currentQueryFractionOfMax;
}
public ClusterSpec.Id id() { return id; }
+
public ClusterSpec.Type type() { return type; }
+
public ClusterResources min() { return min; }
+
public ClusterResources max() { return max; }
+
public ClusterResources current() { return current; }
- public Optional<ClusterResources> target() { return target; }
- public Optional<ClusterResources> suggested() { return suggested; }
- public Utilization utilization() { return utilization; }
+
+ public Autoscaling target() { return target; }
+
+ public Autoscaling suggested() { return suggested; }
+
public List<ScalingEvent> scalingEvents() { return scalingEvents; }
- public String autoscalingStatusCode() { return autoscalingStatusCode; }
- public String autoscalingStatus() { return autoscalingStatus; }
+
public Duration scalingDuration() { return scalingDuration; }
- public double maxQueryGrowthRate() { return maxQueryGrowthRate; }
- public double currentQueryFractionOfMax() { return currentQueryFractionOfMax; }
@Override
public String toString() {
return "cluster '" + id + "'";
}
- public static class Utilization {
-
- private final double idealCpu, peakCpu;
- private final double idealMemory, peakMemory;
- private final double idealDisk, peakDisk;
-
- public Utilization(double idealCpu, double peakCpu,
- double idealMemory, double peakMemory,
- double idealDisk, double peakDisk) {
- this.idealCpu = idealCpu;
- this.peakCpu = peakCpu;
-
- this.idealMemory = idealMemory;
- this.peakMemory = peakMemory;
-
- this.idealDisk = idealDisk;
- this.peakDisk = peakDisk;
- }
-
- public double idealCpu() { return idealCpu; }
- public double peakCpu() { return peakCpu; }
-
- public double idealMemory() { return idealMemory; }
- public double peakMemory() { return peakMemory; }
-
- public double idealDisk() { return idealDisk; }
- public double peakDisk() { return peakDisk; }
-
- public static Utilization empty() { return new Utilization(0, 0,
- 0, 0,
- 0, 0); }
-
- }
-
public static class ScalingEvent {
private final ClusterResources from, to;
@@ -127,10 +81,13 @@ public class Cluster {
this.completion = completion;
}
- public ClusterResources from() { return from; }
- public ClusterResources to() { return to; }
- public Instant at() { return at; }
- public Optional<Instant> completion() { return completion; }
+ public ClusterResources from() {return from;}
+
+ public ClusterResources to() {return to;}
+
+ public Instant at() {return at;}
+
+ public Optional<Instant> completion() {return completion;}
@Override
public boolean equals(Object o) {
@@ -148,12 +105,74 @@ public class Cluster {
@Override
public String toString() {
return "ScalingEvent{" +
- "from=" + from +
- ", to=" + to +
- ", at=" + at +
- ", completion=" + completion +
- '}';
+ "from=" + from +
+ ", to=" + to +
+ ", at=" + at +
+ ", completion=" + completion +
+ '}';
}
}
+ public static class Autoscaling {
+
+ private final String status;
+ private final String description;
+ private final Optional<ClusterResources> resources;
+ private final Instant at;
+ private final Load peak;
+ private final Load ideal;
+
+ public Autoscaling(String status, String description, Optional<ClusterResources> resources, Instant at,
+ Load peak, Load ideal) {
+ this.status = status;
+ this.description = description;
+ this.resources = resources;
+ this.at = at;
+ this.peak = peak;
+ this.ideal = ideal;
+ }
+
+ public String status() {return status;}
+ public String description() {return description;}
+ public Optional<ClusterResources> resources() {
+ return resources;
+ }
+ public Instant at() {return at;}
+ public Load peak() {return peak;}
+ public Load ideal() {return ideal;}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Autoscaling other)) return false;
+ if (!this.status.equals(other.status)) return false;
+ if (!this.description.equals(other.description)) return false;
+ if (!this.resources.equals(other.resources)) return false;
+ if (!this.at.equals(other.at)) return false;
+ if (!this.peak.equals(other.peak)) return false;
+ if (!this.ideal.equals(other.ideal)) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, description, at, peak, ideal);
+ }
+
+ @Override
+ public String toString() {
+ return (resources.isPresent() ? "Autoscaling to " + resources : "Don't autoscale") +
+ (description.isEmpty() ? "" : ": " + description);
+ }
+
+ public static Autoscaling empty() {
+ return new Autoscaling("unavailable",
+ "",
+ Optional.empty(),
+ Instant.EPOCH,
+ Load.zero(),
+ Load.zero());
+ }
+
+ }
+
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java
index 548fac7d11b..f954d0c8392 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/Load.java
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.configserver;
+import java.util.Objects;
+
/**
* @author bratseth
*/
@@ -20,10 +22,20 @@ public class Load {
public double memory() { return memory; }
public double disk() { return disk; }
+ @Override
public String toString() {
return "load: cpu " + cpu + ", memory " + memory + ", disk " + disk;
}
+ @Override
+ public int hashCode() { return Objects.hash(cpu, memory, disk); }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( ! (o instanceof Load other)) return false;
+ return cpu == other.cpu && memory == other.memory && disk == other.disk;
+ }
+
public static Load zero() { return new Load(0, 0, 0); }
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/AutoscalingData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/AutoscalingData.java
new file mode 100644
index 00000000000..3541799b0d0
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/AutoscalingData.java
@@ -0,0 +1,44 @@
+package com.yahoo.vespa.hosted.controller.api.integration.noderepository;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.Load;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AutoscalingData {
+
+ @JsonProperty("status")
+ public String status;
+
+ @JsonProperty("description")
+ public String description;
+
+ @JsonProperty("resources")
+ public ClusterResourcesData resources;
+
+ @JsonProperty("at")
+ public Long at;
+
+ @JsonProperty("peak")
+ public LoadData peak;
+
+ @JsonProperty("ideal")
+ public LoadData ideal;
+
+ public Cluster.Autoscaling toAutoscaling() {
+ return new Cluster.Autoscaling(status == null ? "" : status,
+ description == null ? "" : description,
+ resources == null ? Optional.empty() : Optional.ofNullable(resources.toClusterResources()),
+ at == null ? Instant.EPOCH : Instant.ofEpochMilli(at),
+ peak == null ? Load.zero() : peak.toLoad(),
+ ideal == null ? Load.zero() : ideal.toLoad());
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java
index 9c2104232f1..539f0545c88 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterData.java
@@ -21,30 +21,27 @@ public class ClusterData {
@JsonProperty("type")
public String type;
+
@JsonProperty("min")
public ClusterResourcesData min;
+
@JsonProperty("max")
public ClusterResourcesData max;
+
@JsonProperty("current")
public ClusterResourcesData current;
+
@JsonProperty("suggested")
- public ClusterResourcesData suggested;
+ public AutoscalingData suggested;
+
@JsonProperty("target")
- public ClusterResourcesData target;
- @JsonProperty("utilization")
- public ClusterUtilizationData utilization;
+ public AutoscalingData target;
+
@JsonProperty("scalingEvents")
public List<ScalingEventData> scalingEvents;
- @JsonProperty("autoscalingStatusCode")
- public String autoscalingStatusCode;
- @JsonProperty("autoscalingStatus")
- public String autoscalingStatus;
+
@JsonProperty("scalingDuration")
public Long scalingDuration;
- @JsonProperty("maxQueryGrowthRate")
- public Double maxQueryGrowthRate;
- @JsonProperty("currentQueryFractionOfMax")
- public Double currentQueryFractionOfMax;
public Cluster toCluster(String id) {
return new Cluster(ClusterSpec.Id.from(id),
@@ -52,16 +49,11 @@ public class ClusterData {
min.toClusterResources(),
max.toClusterResources(),
current.toClusterResources(),
- target == null ? Optional.empty() : Optional.of(target.toClusterResources()),
- suggested == null ? Optional.empty() : Optional.of(suggested.toClusterResources()),
- utilization == null ? Cluster.Utilization.empty() : utilization.toClusterUtilization(),
+ target == null ? Cluster.Autoscaling.empty() : target.toAutoscaling(),
+ suggested == null ? Cluster.Autoscaling.empty() : suggested.toAutoscaling(),
scalingEvents == null ? List.of()
: scalingEvents.stream().map(data -> data.toScalingEvent()).toList(),
- autoscalingStatusCode,
- autoscalingStatus,
- scalingDuration == null ? Duration.ofMillis(0) : Duration.ofMillis(scalingDuration),
- maxQueryGrowthRate == null ? -1 : maxQueryGrowthRate,
- currentQueryFractionOfMax == null ? -1 : currentQueryFractionOfMax);
+ scalingDuration == null ? Duration.ofMillis(0) : Duration.ofMillis(scalingDuration));
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java
index 801ee4ee853..2a9ab1e3a55 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterResourcesData.java
@@ -23,6 +23,7 @@ public class ClusterResourcesData {
public NodeResources resources;
public ClusterResources toClusterResources() {
+ if (resources == null) return null; // TODO: Compatibility, remove after January 2023
return new ClusterResources(nodes, groups, resources.toNodeResources());
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterUtilizationData.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterUtilizationData.java
deleted file mode 100644
index b4fee25d1ad..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/noderepository/ClusterUtilizationData.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.api.integration.noderepository;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
-
-/**
- * Utilization ratios
- *
- * @author bratseth
- */
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class ClusterUtilizationData {
-
- @JsonProperty("idealCpu")
- public Double idealCpu;
- @JsonProperty("peakCpu")
- public Double peakCpu;
-
- @JsonProperty("idealMemory")
- public Double idealMemory;
- @JsonProperty("peakMemory")
- public Double peakMemory;
-
- @JsonProperty("idealDisk")
- public Double idealDisk;
- @JsonProperty("peakDisk")
- public Double peakDisk;
-
- public Cluster.Utilization toClusterUtilization() {
- return new Cluster.Utilization(idealCpu, peakCpu, idealMemory, peakMemory, idealDisk, peakDisk);
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 2fffdc25875..c8597cff405 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -64,6 +64,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.DeploymentResult;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.DeploymentResult.LogEntry;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.Load;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.NodeFilter;
@@ -74,7 +75,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision;
-import com.yahoo.vespa.hosted.controller.api.integration.dns.VpcEndpointService.VpcEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartFilter;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.api.role.Role;
@@ -1348,17 +1348,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
toSlime(cluster.min(), clusterObject.setObject("min"));
toSlime(cluster.max(), clusterObject.setObject("max"));
toSlime(cluster.current(), clusterObject.setObject("current"));
- if (cluster.target().isPresent()
- && ! cluster.target().get().justNumbers().equals(cluster.current().justNumbers()))
- toSlime(cluster.target().get(), clusterObject.setObject("target"));
- cluster.suggested().ifPresent(suggested -> toSlime(suggested, clusterObject.setObject("suggested")));
- utilizationToSlime(cluster.utilization(), clusterObject.setObject("utilization"));
+ toSlime(cluster.target(), cluster, clusterObject.setObject("target"));
+ toSlime(cluster.suggested(), cluster, clusterObject.setObject("suggested"));
+ legacyUtilizationToSlime(cluster.target().peak(), cluster.target().ideal(), clusterObject.setObject("utilization")); // TODO: Remove after January 2023
scalingEventsToSlime(cluster.scalingEvents(), clusterObject.setArray("scalingEvents"));
- clusterObject.setString("autoscalingStatusCode", cluster.autoscalingStatusCode());
- clusterObject.setString("autoscalingStatus", cluster.autoscalingStatus());
+ clusterObject.setString("autoscalingStatusCode", cluster.target().status()); // TODO: Remove after January 2023
+ clusterObject.setString("autoscalingStatus", cluster.target().description()); // TODO: Remove after January 2023
clusterObject.setLong("scalingDuration", cluster.scalingDuration().toMillis());
- clusterObject.setDouble("maxQueryGrowthRate", cluster.maxQueryGrowthRate());
- clusterObject.setDouble("currentQueryFractionOfMax", cluster.currentQueryFractionOfMax());
}
return new SlimeJsonResponse(slime);
}
@@ -2704,15 +2700,35 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
object.setDouble("cost", cost);
}
- private void utilizationToSlime(Cluster.Utilization utilization, Cursor utilizationObject) {
- utilizationObject.setDouble("idealCpu", utilization.idealCpu());
- utilizationObject.setDouble("peakCpu", utilization.peakCpu());
+ private void toSlime(Cluster.Autoscaling autoscaling, Cluster cluster, Cursor autoscalingObject) {
+ // TODO: Remove after January 2023
+ if (autoscaling.resources().isPresent()
+ && ! autoscaling.resources().get().justNumbers().equals(cluster.current().justNumbers()))
+ toSlime(autoscaling.resources().get(), autoscalingObject);
- utilizationObject.setDouble("idealMemory", utilization.idealMemory());
- utilizationObject.setDouble("peakMemory", utilization.peakMemory());
+ autoscalingObject.setString("status", autoscaling.status());
+ autoscalingObject.setString("description", autoscaling.description());
+ autoscaling.resources().ifPresent(resources -> toSlime(resources, autoscalingObject.setObject("resources")));
+ autoscalingObject.setLong("at", autoscaling.at().toEpochMilli());
+ toSlime(autoscaling.peak(), autoscalingObject.setObject("peak"));
+ toSlime(autoscaling.ideal(), autoscalingObject.setObject("ideal"));
+ }
+
+ private void toSlime(Load load, Cursor loadObject) {
+ loadObject.setDouble("cpu", load.cpu());
+ loadObject.setDouble("memory", load.memory());
+ loadObject.setDouble("disk", load.disk());
+ }
+
+ private void legacyUtilizationToSlime(Load peak, Load ideal, Cursor utilizationObject) {
+ utilizationObject.setDouble("idealCpu", ideal.cpu());
+ utilizationObject.setDouble("peakCpu", peak.cpu());
+
+ utilizationObject.setDouble("idealMemory", ideal.memory());
+ utilizationObject.setDouble("peakMemory", peak.memory());
- utilizationObject.setDouble("idealDisk", utilization.idealDisk());
- utilizationObject.setDouble("peakDisk", utilization.peakDisk());
+ utilizationObject.setDouble("idealDisk", ideal.disk());
+ utilizationObject.setDouble("peakDisk", peak.disk());
}
private void scalingEventsToSlime(List<Cluster.ScalingEvent> scalingEvents, Cursor scalingEventsArray) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json
index e4d0de9eb9f..37da498b6ec 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json
@@ -56,9 +56,7 @@
"architecture": "x86_64"
}
},
- "scalingDuration": 400000,
- "maxQueryGrowthRate": 0.7,
- "currentQueryFractionOfMax": 0.3
+ "scalingDuration": 400000
},
"logserver": {
"type": "admin",
@@ -102,21 +100,34 @@
}
},
"suggested": {
- "nodes": 2,
- "groups": 1,
- "resources": {
- "vcpu": 2.0,
- "memoryGb": 4.0,
- "diskGb": 50.0,
- "bandwidthGbps": 0.3,
- "diskSpeed": "fast",
- "storageType": "local",
- "architecture": "x86_64"
+ "status" : "unavailable",
+ "description" : "",
+ "resources" : {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 4.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "local",
+ "architecture": "x86_64"
+ },
+ "at" : 123,
+ "peak" : {
+ "cpu" : 0.1,
+ "memory" : 0.2,
+ "disk" : 0.3
+ },
+ "ideal" : {
+ "cpu" : 0.4,
+ "memory" : 0.5,
+ "disk" : 0.6
+ }
}
},
- "scalingDuration": 90000,
- "maxQueryGrowthRate": 0.7,
- "currentQueryFractionOfMax": 0.3
+ "scalingDuration": 90000
},
"music": {
"type": "content",
@@ -172,9 +183,7 @@
"architecture": "x86_64"
}
},
- "scalingDuration": 1000000,
- "maxQueryGrowthRate": 0.7,
- "currentQueryFractionOfMax": 0.3
+ "scalingDuration": 1000000
}
}
} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index f576c90e195..448bb9ac15f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -30,6 +30,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.DeploymentResult;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.Load;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.LoadBalancer.PrivateServiceInfo;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
@@ -43,7 +44,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.noderepository.RestartF
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
-import wiremock.org.checkerframework.checker.units.qual.A;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -116,20 +116,18 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
new ClusterResources(2, 1, new NodeResources(1, 4, 20, 1, slow, remote)),
new ClusterResources(2, 1, new NodeResources(4, 16, 90, 1, slow, remote)),
current,
- Optional.of(new ClusterResources(2, 1, new NodeResources(3, 8, 50, 1, slow, remote))),
- Optional.empty(),
- new Cluster.Utilization(0.2, 0.35,
- 0.5, 0.65,
- 0.8, 1.0),
+ new Cluster.Autoscaling("ideal",
+ "Cluster is ideally scaled",
+ Optional.of(new ClusterResources(2, 1, new NodeResources(3, 8, 50, 1, slow, remote))),
+ Instant.ofEpochMilli(123),
+ new Load(0.35, 0.65, 1.0),
+ new Load(0.2, 0.5, 0.8)),
+ Cluster.Autoscaling.empty(),
List.of(new Cluster.ScalingEvent(new ClusterResources(0, 0, NodeResources.unspecified()),
current,
Instant.ofEpochMilli(1234),
Optional.of(Instant.ofEpochMilli(2234)))),
- "ideal",
- "Cluster is ideally scaled",
- Duration.ofMinutes(6),
- 0.7,
- 0.3);
+ Duration.ofMinutes(6));
nodeRepository.putApplication(zone,
new com.yahoo.vespa.hosted.controller.api.integration.configserver.Application(application,
List.of(cluster)));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json
index 6527237840e..c013ccb00fe 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-clusters.json
@@ -52,7 +52,48 @@
"diskSpeed": "slow",
"storageType": "remote"
},
- "cost": 0.29
+ "cost": 0.29,
+ "status": "ideal",
+ "description": "Cluster is ideally scaled",
+ "resources": {
+ "nodes": 2,
+ "groups": 1,
+ "nodeResources": {
+ "vcpu": 3.0,
+ "memoryGb": 8.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 1.0,
+ "diskSpeed": "slow",
+ "storageType": "remote"
+ },
+ "cost": 0.29
+ },
+ "at" : 123,
+ "peak": {
+ "cpu": 0.35,
+ "memory": 0.65,
+ "disk": 1.0
+ },
+ "ideal": {
+ "cpu": 0.2,
+ "memory": 0.5,
+ "disk": 0.8
+ }
+ },
+ "suggested": {
+ "status": "unavailable",
+ "description": "",
+ "at": 0,
+ "peak": {
+ "cpu": 0.0,
+ "memory": 0.0,
+ "disk": 0.0
+ },
+ "ideal": {
+ "cpu": 0.0,
+ "memory": 0.0,
+ "disk": 0.0
+ }
},
"utilization": {
"idealCpu": 0.2,
@@ -96,9 +137,7 @@
],
"autoscalingStatusCode": "ideal",
"autoscalingStatus": "Cluster is ideally scaled",
- "scalingDuration": 360000,
- "maxQueryGrowthRate": 0.7,
- "currentQueryFractionOfMax": 0.3
+ "scalingDuration": 360000
}
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/AutoscalingStatus.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/AutoscalingStatus.java
deleted file mode 100644
index ea15b6a42cb..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/AutoscalingStatus.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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 java.util.Objects;
-
-/**
- * The current autoscaling status of a cluster.
- * A value object.
- *
- * @author bratseth
- */
-public class AutoscalingStatus {
-
- public enum Status {
-
- /** No status is available: Aautoscaling is disabled, or a brand new application. */
- unavailable,
-
- /** Autoscaling is not taking any action at the moment due to recent changes or a lack of data */
- waiting,
-
- /** The cluster is ideally scaled to the current load */
- ideal,
-
- /** The cluster should be rescaled further, but no better configuration is allowed by the current limits */
- insufficient,
-
- /** Rescaling of this cluster has been scheduled */
- rescaling
-
- };
-
- private final Status status;
- private final String description;
-
- public AutoscalingStatus(Status status, String description) {
- this.status = status;
- this.description = description;
- }
-
- public Status status() { return status; }
- public String description() { return description; }
-
- public static AutoscalingStatus empty() { return new AutoscalingStatus(Status.unavailable, ""); }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if ( ! ( o instanceof AutoscalingStatus other)) return false;
-
- if ( other.status != this.status ) return false;
- if ( ! other.description.equals(this.description) ) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(status, description);
- }
-
- @Override
- public String toString() {
- return "autoscaling status: " + status +
- ( description.isEmpty() ? "" : " (" + description + ")");
- }
-
-}
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 eb317c62776..0a731f66418 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
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
+import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@@ -33,7 +34,6 @@ public class Cluster {
/** The maxScalingEvents last scaling events of this, sorted by increasing time (newest last) */
private final List<ScalingEvent> scalingEvents;
- private final AutoscalingStatus autoscalingStatus;
public Cluster(ClusterSpec.Id id,
boolean exclusive,
@@ -42,8 +42,7 @@ public class Cluster {
boolean required,
Autoscaling suggested,
Autoscaling target,
- List<ScalingEvent> scalingEvents,
- AutoscalingStatus autoscalingStatus) {
+ List<ScalingEvent> scalingEvents) {
this.id = Objects.requireNonNull(id);
this.exclusive = exclusive;
this.min = Objects.requireNonNull(minResources);
@@ -56,7 +55,6 @@ public class Cluster {
else
this.target = target;
this.scalingEvents = List.copyOf(scalingEvents);
- this.autoscalingStatus = autoscalingStatus;
}
public ClusterSpec.Id id() { return id; }
@@ -105,21 +103,18 @@ public class Cluster {
return Optional.of(scalingEvents.get(scalingEvents.size() - 1));
}
- /** The latest autoscaling status of this cluster, or unknown (never null) if none */
- public AutoscalingStatus autoscalingStatus() { return autoscalingStatus; }
-
public Cluster withConfiguration(boolean exclusive, Capacity capacity) {
return new Cluster(id, exclusive,
capacity.minResources(), capacity.maxResources(), capacity.isRequired(),
- suggested, target, scalingEvents, autoscalingStatus);
+ suggested, target, scalingEvents);
}
public Cluster withSuggested(Autoscaling suggested) {
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus);
+ return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
}
public Cluster withTarget(Autoscaling target) {
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus);
+ return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
}
/** Add or update (based on "at" time) a scaling event */
@@ -133,12 +128,7 @@ public class Cluster {
scalingEvents.add(scalingEvent);
prune(scalingEvents);
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus);
- }
-
- public Cluster with(AutoscalingStatus autoscalingStatus) {
- if (autoscalingStatus.equals(this.autoscalingStatus)) return this;
- return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus);
+ return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents);
}
@Override
@@ -169,7 +159,45 @@ 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(),
- Autoscaling.empty(), Autoscaling.empty(), List.of(), AutoscalingStatus.empty());
+ Autoscaling.empty(), Autoscaling.empty(), List.of());
+ }
+
+ /** The predicted time it will take to rescale this cluster. */
+ public Duration scalingDuration(ClusterSpec clusterSpec) {
+ int completedEventCount = 0;
+ Duration totalDuration = Duration.ZERO;
+ for (ScalingEvent event : scalingEvents()) {
+ if (event.duration().isEmpty()) continue;
+ completedEventCount++;
+ // Assume we have missed timely recording completion if it is longer than 4 days
+ totalDuration = totalDuration.plus(maximum(Duration.ofDays(4), event.duration().get()));
+ }
+ if (completedEventCount == 0) { // Use defaults
+ if (clusterSpec.isStateful()) return Duration.ofHours(12);
+ return Duration.ofMinutes(10);
+ }
+ else {
+ Duration predictedDuration = totalDuration.dividedBy(completedEventCount);
+
+ if ( clusterSpec.isStateful() ) // TODO: Remove when we have reliable completion for content clusters
+ predictedDuration = minimum(Duration.ofHours(12), predictedDuration);
+
+ predictedDuration = minimum(Duration.ofMinutes(5), predictedDuration);
+
+ return predictedDuration;
+ }
+ }
+
+ private static Duration minimum(Duration smallestAllowed, Duration duration) {
+ if (duration.minus(smallestAllowed).isNegative())
+ return smallestAllowed;
+ return duration;
+ }
+
+ private static Duration maximum(Duration largestAllowed, Duration duration) {
+ if ( ! duration.minus(largestAllowed).isNegative())
+ return largestAllowed;
+ return duration;
}
}
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 c816abc060c..eacafb444b5 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
@@ -6,12 +6,11 @@ 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.applications.AutoscalingStatus;
-import com.yahoo.vespa.hosted.provision.applications.AutoscalingStatus.Status;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
+import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling.Status;
import java.time.Duration;
-import java.util.Objects;
+import java.time.Instant;
import java.util.Optional;
/**
@@ -41,7 +40,7 @@ public class Autoscaler {
* @param clusterNodes the list of all the active nodes in a cluster
* @return scaling advice for this cluster
*/
- public Advice suggest(Application application, Cluster cluster, NodeList clusterNodes) {
+ public Autoscaling suggest(Application application, Cluster cluster, NodeList clusterNodes) {
return autoscale(application, cluster, clusterNodes, Limits.empty());
}
@@ -51,13 +50,11 @@ public class Autoscaler {
* @param clusterNodes the list of all the active nodes in a cluster
* @return scaling advice for this cluster
*/
- public Advice autoscale(Application application, Cluster cluster, NodeList clusterNodes) {
- if (cluster.minResources().equals(cluster.maxResources()))
- return Advice.none(Status.unavailable, "Autoscaling is not enabled");
+ public Autoscaling autoscale(Application application, Cluster cluster, NodeList clusterNodes) {
return autoscale(application, cluster, clusterNodes, Limits.of(cluster));
}
- private Advice autoscale(Application application, Cluster cluster, NodeList clusterNodes, Limits limits) {
+ private Autoscaling autoscale(Application application, Cluster cluster, NodeList clusterNodes, Limits limits) {
ClusterModel clusterModel = new ClusterModel(nodeRepository.zone(),
application,
clusterNodes.clusterSpec(),
@@ -65,24 +62,28 @@ public class Autoscaler {
clusterNodes,
nodeRepository.metricsDb(),
nodeRepository.clock());
+ if (clusterModel.isEmpty()) return Autoscaling.empty();
+
+ if (! limits.isEmpty() && cluster.minResources().equals(cluster.maxResources()))
+ return Autoscaling.dontScale(Autoscaling.Status.unavailable, "Autoscaling is not enabled", clusterModel);
if ( ! clusterIsStable(clusterNodes, nodeRepository))
- return Advice.none(Status.waiting, "Cluster change in progress");
+ return Autoscaling.dontScale(Status.waiting, "Cluster change in progress", clusterModel);
var currentAllocation = new AllocatableClusterResources(clusterNodes, nodeRepository);
Optional<AllocatableClusterResources> bestAllocation =
allocationOptimizer.findBestAllocation(clusterModel.loadAdjustment(), currentAllocation, clusterModel, limits);
if (bestAllocation.isEmpty())
- return Advice.dontScale(Status.insufficient, "No allocations are possible within configured limits");
+ return Autoscaling.dontScale(Status.insufficient, "No allocations are possible within configured limits", clusterModel);
if (! worthRescaling(currentAllocation.realResources(), bestAllocation.get().realResources())) {
if (bestAllocation.get().fulfilment() < 1)
- return Advice.dontScale(Status.insufficient, "Configured limits prevents better scaling of this cluster");
+ return Autoscaling.dontScale(Status.insufficient, "Configured limits prevents better scaling of this cluster", clusterModel);
else
- return Advice.dontScale(Status.ideal, "Cluster is ideally scaled");
+ return Autoscaling.dontScale(Status.ideal, "Cluster is ideally scaled", clusterModel);
}
- return Advice.scaleTo(bestAllocation.get().advertisedResources());
+ return Autoscaling.scaleTo(bestAllocation.get().advertisedResources(), clusterModel);
}
public static boolean clusterIsStable(NodeList clusterNodes, NodeRepository nodeRepository) {
@@ -122,53 +123,4 @@ public class Autoscaler {
return Duration.ofHours(48);
}
- public static class Advice {
-
- private final boolean present;
- private final Optional<ClusterResources> target;
- private final AutoscalingStatus reason;
-
- private Advice(Optional<ClusterResources> target, boolean present, AutoscalingStatus reason) {
- this.target = target;
- this.present = present;
- this.reason = Objects.requireNonNull(reason);
- }
-
- /**
- * Returns the autoscaling target that should be set by this advice.
- * This is empty if the advice is to keep the current allocation.
- */
- public Optional<ClusterResources> target() { return target; }
-
- /** True if this does not provide any advice */
- public boolean isEmpty() { return ! present; }
-
- /** True if this provides advice (which may be to keep the current allocation) */
- public boolean isPresent() { return present; }
-
- /** The reason for this advice */
- public AutoscalingStatus reason() { return reason; }
-
- private static Advice none(Status status, String description) {
- return new Advice(Optional.empty(), false, new AutoscalingStatus(status, description));
- }
-
- private static Advice dontScale(Status status, String description) {
- return new Advice(Optional.empty(), true, new AutoscalingStatus(status, description));
- }
-
- private static Advice scaleTo(ClusterResources target) {
- return new Advice(Optional.of(target), true,
- new AutoscalingStatus(AutoscalingStatus.Status.rescaling,
- "Rescaling initiated due to load changes"));
- }
-
- @Override
- public String toString() {
- return "autoscaling advice: " +
- (present ? (target.isPresent() ? "Scale to " + target.get() : "Don't scale") : "None");
- }
-
- }
-
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java
index 9da17a39242..ebab2efbaa6 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaling.java
@@ -5,53 +5,134 @@ import com.yahoo.config.provision.ClusterResources;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
/**
- * An autoscaling result.
+ * An autoscaling conclusion and the context that led to it.
*
* @author bratseth
*/
public class Autoscaling {
+ private final Status status;
+ private final String description;
private final Optional<ClusterResources> resources;
private final Instant at;
+ private final Load peak;
+ private final Load ideal;
- public Autoscaling(ClusterResources resources, Instant at) {
- this(Optional.of(resources), at);
- }
-
- public Autoscaling(Optional<ClusterResources> resources, Instant at) {
+ public Autoscaling(Status status, String description, Optional<ClusterResources> resources, Instant at,
+ Load peak, Load ideal) {
+ this.status = status;
+ this.description = description;
this.resources = resources;
this.at = at;
+ this.peak = peak;
+ this.ideal = ideal;
}
- /** Returns the resource target of this, or empty if non target. */
+ public Status status() { return status; }
+
+ public String description() { return description; }
+
+ /** Returns the resource target of this, or empty if none (meaning keep the current allocation). */
public Optional<ClusterResources> resources() {
return resources;
}
- /** Returns the time this target was decided. */
+ /** Returns the time this was decided. */
public Instant at() { return at; }
+ /** Returns the peak load seen in the period considered in this. */
+ public Load peak() { return peak; }
+
+ /** Returns the ideal load the cluster in question should have. */
+ public Load ideal() { return ideal; }
+
+ public Autoscaling with(Status status, String description) {
+ return new Autoscaling(status, description, resources, at, peak, ideal);
+ }
+
+ /** Converts this autoscaling into an ideal one at the completion of it. */
+ public Autoscaling asIdeal(Instant at) {
+ return new Autoscaling(Status.ideal,
+ "Cluster is ideally scaled within configured limits",
+ Optional.empty(),
+ at,
+ peak,
+ ideal);
+ }
+
+ public boolean isEmpty() { return this.equals(empty()); }
+
@Override
public boolean equals(Object o) {
if ( ! (o instanceof Autoscaling other)) return false;
- if ( ! this.at.equals(other.at)) return false;
+ if ( ! this.status.equals(other.status)) return false;
+ if ( ! this.description.equals(other.description)) return false;
if ( ! this.resources.equals(other.resources)) return false;
+ if ( ! this.at.equals(other.at)) return false;
+ if ( ! this.peak.equals(other.peak)) return false;
+ if ( ! this.ideal.equals(other.ideal)) return false;
return true;
}
@Override
public int hashCode() {
- return Objects.hash(resources, at);
+ return Objects.hash(status, description, at, peak, ideal);
}
@Override
public String toString() {
- return "autoscaling to " + resources + ", made at " + at;
+ return (resources.isPresent() ? "Autoscaling to " + resources : "Don't autoscale") +
+ (description.isEmpty() ? "" : ": " + description);
}
- public static Autoscaling empty() { return new Autoscaling(Optional.empty(), Instant.EPOCH); }
+ public static Autoscaling empty() {
+ return new Autoscaling(Status.unavailable,
+ "",
+ Optional.empty(),
+ Instant.EPOCH,
+ Load.zero(),
+ Load.zero());
+ }
+
+ /** Creates an autoscaling conclusion which does not change the current allocation for a specified reason. */
+ public static Autoscaling dontScale(Status status, String description, ClusterModel clusterModel) {
+ return new Autoscaling(status,
+ description,
+ Optional.empty(),
+ clusterModel.at(),
+ clusterModel.peakLoad(),
+ clusterModel.idealLoad());
+ }
+
+ /** Creates an autoscaling conclusion to scale. */
+ public static Autoscaling scaleTo(ClusterResources target, ClusterModel clusterModel) {
+ return new Autoscaling(Status.rescaling,
+ "Rescaling initiated due to load changes",
+ Optional.of(target),
+ clusterModel.at(),
+ clusterModel.peakLoad(),
+ clusterModel.idealLoad());
+ }
+
+ public enum Status {
+
+ /** No status is available: Aautoscaling is disabled, or a brand new application. */
+ unavailable,
+
+ /** Autoscaling is not taking any action at the moment due to recent changes or a lack of data */
+ waiting,
+
+ /** The cluster is ideally scaled to the current load */
+ ideal,
+
+ /** The cluster should be rescaled further, but no better configuration is allowed by the current limits */
+ insufficient,
+
+ /** Rescaling of this cluster has been scheduled */
+ rescaling
+
+ };
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
index 1928a784763..388c77e4e5d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java
@@ -53,6 +53,7 @@ public class ClusterModel {
private final Duration scalingDuration;
private final ClusterTimeseries clusterTimeseries;
private final ClusterNodesTimeseries nodeTimeseries;
+ private final Instant at;
// Lazily initialized members
private Double queryFractionOfMax = null;
@@ -72,9 +73,10 @@ public class ClusterModel {
this.cluster = cluster;
this.nodes = clusterNodes;
this.clock = clock;
- this.scalingDuration = computeScalingDuration(cluster, clusterSpec);
+ this.scalingDuration = cluster.scalingDuration(clusterSpec);
this.clusterTimeseries = metricsDb.getClusterTimeseries(application.id(), cluster.id());
this.nodeTimeseries = new ClusterNodesTimeseries(scalingDuration(), cluster, nodes, metricsDb);
+ this.at = clock.instant();
}
ClusterModel(Zone zone,
@@ -95,12 +97,17 @@ public class ClusterModel {
this.scalingDuration = scalingDuration;
this.clusterTimeseries = clusterTimeseries;
this.nodeTimeseries = nodeTimeseries;
+ this.at = clock.instant();
}
public Application application() { return application; }
public ClusterSpec clusterSpec() { return clusterSpec; }
public Cluster cluster() { return cluster; }
+ public boolean isEmpty() {
+ return nodeTimeseries().isEmpty();
+ }
+
/** Returns the relative load adjustment that should be made to this cluster given available measurements. */
public Load loadAdjustment() {
if (nodeTimeseries().isEmpty()) return Load.one();
@@ -151,12 +158,6 @@ public class ClusterModel {
return averageQueryRate = clusterTimeseries().queryRate(scalingDuration(), clock);
}
- /** Returns the average of the last load measurement from each node. */
- public Load currentLoad() { return nodeTimeseries().currentLoad(); }
-
- /** Returns the average of all load measurements from all nodes*/
- public Load averageLoad() { return nodeTimeseries().averageLoad(); }
-
/** Returns the average of the peak load measurement in each dimension, from each node. */
public Load peakLoad() { return nodeTimeseries().peakLoad(); }
@@ -239,6 +240,9 @@ public class ClusterModel {
(1 - queryCpuFraction) * idealWriteCpuLoad;
}
+ /** Returns the instant this model was created. */
+ public Instant at() { return at;}
+
/** Returns the headroom for growth during organic traffic growth as a multiple of current resources. */
private double growthRateHeadroom() {
if ( ! zone.environment().isProduction()) return 1;
@@ -288,43 +292,6 @@ public class ClusterModel {
return queryRateFraction * relativeQueryCost / (queryRateFraction * relativeQueryCost + writeFraction);
}
- private static Duration computeScalingDuration(Cluster cluster, ClusterSpec clusterSpec) {
- int completedEventCount = 0;
- Duration totalDuration = Duration.ZERO;
- for (ScalingEvent event : cluster.scalingEvents()) {
- if (event.duration().isEmpty()) continue;
- completedEventCount++;
- // Assume we have missed timely recording completion if it is longer than 4 days
- totalDuration = totalDuration.plus(maximum(Duration.ofDays(4), event.duration().get()));
- }
- if (completedEventCount == 0) { // Use defaults
- if (clusterSpec.isStateful()) return Duration.ofHours(12);
- return Duration.ofMinutes(10);
- }
- else {
- Duration predictedDuration = totalDuration.dividedBy(completedEventCount);
-
- if ( clusterSpec.isStateful() ) // TODO: Remove when we have reliable completion for content clusters
- predictedDuration = minimum(Duration.ofHours(12), predictedDuration);
-
- predictedDuration = minimum(Duration.ofMinutes(5), predictedDuration);
-
- return predictedDuration;
- }
- }
-
- private static Duration minimum(Duration smallestAllowed, Duration duration) {
- if (duration.minus(smallestAllowed).isNegative())
- return smallestAllowed;
- return duration;
- }
-
- private static Duration maximum(Duration largestAllowed, Duration duration) {
- if ( ! duration.minus(largestAllowed).isNegative())
- return largestAllowed;
- return duration;
- }
-
private double idealMemoryLoad() {
if (clusterSpec.type().isContainer()) return idealContainerMemoryLoad;
if (clusterSpec.type() == ClusterSpec.Type.admin) return idealContainerMemoryLoad; // Not autoscaled, but ideal shown in console
@@ -339,7 +306,7 @@ public class ClusterModel {
/**
* Create a cluster model if possible and logs a warning and returns empty otherwise.
- * This is useful in cases where it's possible to continue without the cluser model,
+ * This is useful in cases where it's possible to continue without the cluster model,
* as QuestDb is known to temporarily fail during reading of data.
*/
public static Optional<ClusterModel> create(Zone zone,
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 c3838dcee75..4aa54b7f6fa 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
@@ -70,9 +70,9 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer {
if (cluster.isEmpty()) return;
Cluster updatedCluster = updateCompletion(cluster.get(), clusterNodes);
- var advice = autoscaler.autoscale(application.get(), updatedCluster, clusterNodes);
+ var autoscaling = autoscaler.autoscale(application.get(), updatedCluster, clusterNodes);
- if ( ! anyChanges(advice, cluster.get(), updatedCluster, clusterNodes)) return;
+ if ( ! anyChanges(autoscaling, cluster.get(), updatedCluster, clusterNodes)) return;
try (var lock = nodeRepository().applications().lock(applicationId)) {
application = nodeRepository().applications().get(applicationId);
@@ -82,30 +82,30 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer {
clusterNodes = nodeRepository().nodes().list(Node.State.active).owner(applicationId).cluster(clusterId);
// 1. Update cluster info
- updatedCluster = updateCompletion(cluster.get(), clusterNodes)
- .with(advice.reason())
- .withTarget(new Autoscaling(advice.target(), nodeRepository().clock().instant()));
+ updatedCluster = updateCompletion(cluster.get(), clusterNodes);
+ if ( ! autoscaling.isEmpty()) // Ignore empties we'll get from servers recently started
+ updatedCluster = updatedCluster.withTarget(autoscaling);
applications().put(application.get().with(updatedCluster), lock);
var current = new AllocatableClusterResources(clusterNodes, nodeRepository()).advertisedResources();
- if (advice.isPresent() && advice.target().isPresent() && !current.equals(advice.target().get())) {
+ if (autoscaling.resources().isPresent() && !current.equals(autoscaling.resources().get())) {
// 2. Also autoscale
try (MaintenanceDeployment deployment = new MaintenanceDeployment(applicationId, deployer, metric, nodeRepository())) {
if (deployment.isValid()) {
deployment.activate();
- logAutoscaling(current, advice.target().get(), applicationId, clusterNodes);
+ logAutoscaling(current, autoscaling.resources().get(), applicationId, clusterNodes);
}
}
}
}
}
- private boolean anyChanges(Autoscaler.Advice advice, Cluster cluster, Cluster updatedCluster, NodeList clusterNodes) {
- if (advice.isPresent() && !cluster.target().resources().equals(advice.target())) return true;
+ private boolean anyChanges(Autoscaling autoscaling, Cluster cluster, Cluster updatedCluster, NodeList clusterNodes) {
if (updatedCluster != cluster) return true;
- if ( ! advice.reason().equals(cluster.autoscalingStatus())) return true;
- if (advice.target().isPresent() &&
- !advice.target().get().equals(new AllocatableClusterResources(clusterNodes, nodeRepository()).advertisedResources())) return true;
+ if ( ! cluster.target().resources().equals(autoscaling.resources())) return true;
+ if ( ! cluster.target().status().equals(autoscaling.status())) return true;
+ if (autoscaling.resources().isPresent() &&
+ !autoscaling.resources().get().equals(new AllocatableClusterResources(clusterNodes, nodeRepository()).advertisedResources())) return true;
return false;
}
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 536a1917eb1..af368934188 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
@@ -14,7 +14,6 @@ import com.yahoo.vespa.hosted.provision.applications.Applications;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
-import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
import java.time.Duration;
import java.util.Map;
@@ -63,12 +62,12 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer {
Optional<Cluster> cluster = application.cluster(clusterId);
if (cluster.isEmpty()) return true;
var suggestion = autoscaler.suggest(application, cluster.get(), clusterNodes);
- if (suggestion.isEmpty()) return true;
+ if (suggestion.status() == Autoscaling.Status.waiting) return true;
+ if ( ! shouldUpdateSuggestion(cluster.get().suggested(), suggestion)) return true;
+
// Wait only a short time for the lock to avoid interfering with change deployments
try (Mutex lock = nodeRepository().applications().lock(applicationId, Duration.ofSeconds(1))) {
- // empty suggested resources == keep the current allocation, so we record that
- var suggestedResources = suggestion.target().orElse(clusterNodes.not().retired().toResources());
- applications().get(applicationId).ifPresent(a -> updateSuggestion(suggestedResources, clusterId, a, lock));
+ applications().get(applicationId).ifPresent(a -> updateSuggestion(suggestion, clusterId, a, lock));
return true;
}
catch (ApplicationLockException e) {
@@ -76,18 +75,19 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer {
}
}
- private void updateSuggestion(ClusterResources suggestion,
+ private boolean shouldUpdateSuggestion(Autoscaling currentSuggestion, Autoscaling newSuggestion) {
+ return currentSuggestion.resources().isEmpty()
+ || currentSuggestion.at().isBefore(nodeRepository().clock().instant().minus(Duration.ofDays(7)))
+ || (newSuggestion.resources().isPresent() && isHigher(newSuggestion.resources().get(), currentSuggestion.resources().get()));
+ }
+
+ private void updateSuggestion(Autoscaling autoscaling,
ClusterSpec.Id clusterId,
Application application,
Mutex lock) {
Optional<Cluster> cluster = application.cluster(clusterId);
if (cluster.isEmpty()) return;
- var at = nodeRepository().clock().instant();
- var currentSuggestion = cluster.get().suggested();
- if (currentSuggestion.resources().isEmpty()
- || currentSuggestion.at().isBefore(at.minus(Duration.ofDays(7)))
- || isHigher(suggestion, currentSuggestion.resources().get()))
- applications().put(application.with(cluster.get().withSuggested(new Autoscaling(suggestion, at))), lock);
+ applications().put(application.with(cluster.get().withSuggested(autoscaling)), lock);
}
private boolean isHigher(ClusterResources r1, ClusterResources r2) {
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 f57c811b6d5..2f5b057e927 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
@@ -10,11 +10,11 @@ import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.provision.applications.Application;
-import com.yahoo.vespa.hosted.provision.applications.AutoscalingStatus;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
import com.yahoo.vespa.hosted.provision.applications.Status;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -56,9 +56,13 @@ public class ApplicationSerializer {
private static final String groupsKey = "groups";
private static final String nodeResourcesKey = "resources";
private static final String scalingEventsKey = "scalingEvents";
- private static final String autoscalingStatusKey = "autoscalingStatus";
private static final String autoscalingStatusObjectKey = "autoscalingStatusObject";
private static final String descriptionKey = "description";
+ private static final String peakKey = "peak";
+ private static final String idealKey = "ideal";
+ private static final String cpuKey = "cpu";
+ private static final String memoryKey = "memory";
+ private static final String diskKey = "disk";
private static final String fromKey = "from";
private static final String toKey = "to";
private static final String generationKey = "generation";
@@ -122,7 +126,6 @@ public class ApplicationSerializer {
toSlime(cluster.suggested(), clusterObject.setObject(suggestedKey));
toSlime(cluster.target(), clusterObject.setObject(targetKey));
scalingEventsToSlime(cluster.scalingEvents(), clusterObject.setArray(scalingEventsKey));
- toSlime(cluster.autoscalingStatus(), clusterObject.setObject(autoscalingStatusObjectKey));
}
private static Cluster clusterFromSlime(String id, Inspector clusterObject) {
@@ -131,15 +134,18 @@ public class ApplicationSerializer {
clusterResourcesFromSlime(clusterObject.field(minResourcesKey)),
clusterResourcesFromSlime(clusterObject.field(maxResourcesKey)),
clusterObject.field(requiredKey).asBool(),
- autoscalingFromSlime(clusterObject.field(suggestedKey)),
- autoscalingFromSlime(clusterObject.field(targetKey)),
- scalingEventsFromSlime(clusterObject.field(scalingEventsKey)),
- autoscalingStatusFromSlime(clusterObject.field(autoscalingStatusObjectKey), clusterObject));
+ autoscalingFromSlime(clusterObject.field(suggestedKey), clusterObject.field("nonExisting")),
+ autoscalingFromSlime(clusterObject.field(targetKey), clusterObject.field(autoscalingStatusObjectKey)),
+ scalingEventsFromSlime(clusterObject.field(scalingEventsKey)));
}
private static void toSlime(Autoscaling autoscaling, Cursor autoscalingObject) {
+ autoscalingObject.setString(statusKey, toAutoscalingStatusCode(autoscaling.status()));
+ autoscalingObject.setString(descriptionKey, autoscaling.description());
autoscaling.resources().ifPresent(resources -> toSlime(resources, autoscalingObject.setObject(resourcesKey)));
autoscalingObject.setLong(atKey, autoscaling.at().toEpochMilli());
+ toSlime(autoscaling.peak(), autoscalingObject.setObject(peakKey));
+ toSlime(autoscaling.ideal(), autoscalingObject.setObject(idealKey));
}
private static void toSlime(ClusterResources resources, Cursor clusterResourcesObject) {
@@ -159,15 +165,46 @@ public class ApplicationSerializer {
NodeResourcesSerializer.resourcesFromSlime(clusterResourcesObject.field(nodeResourcesKey)));
}
- private static Autoscaling autoscalingFromSlime(Inspector autoscalingObject) {
+ private static void toSlime(Load load, Cursor loadObject) {
+ loadObject.setDouble(cpuKey, load.cpu());
+ loadObject.setDouble(memoryKey, load.memory());
+ loadObject.setDouble(diskKey, load.disk());
+ }
+
+ private static Load loadFromSlime(Inspector loadObject) {
+ return new Load(loadObject.field(cpuKey).asDouble(),
+ loadObject.field(memoryKey).asDouble(),
+ loadObject.field(diskKey).asDouble());
+ }
+
+ private static Autoscaling autoscalingFromSlime(Inspector autoscalingObject,
+ Inspector legacyAutoscalingStatusObject) {
if ( ! autoscalingObject.valid()) return Autoscaling.empty();
- if ( ! autoscalingObject.field(atKey).valid()) { // TODO: Remove clause after January 2023
- return new Autoscaling(optionalClusterResourcesFromSlime(autoscalingObject), Instant.EPOCH);
+ if ( ! autoscalingObject.field(atKey).valid()) { // TODO: Remove after January 2023
+ return new Autoscaling(fromAutoscalingStatusCode(legacyAutoscalingStatusObject.field(statusKey).asString()),
+ legacyAutoscalingStatusObject.field(descriptionKey).asString(),
+ optionalClusterResourcesFromSlime(autoscalingObject),
+ Instant.EPOCH,
+ Load.zero(),
+ Load.zero());
+ }
+
+ if (legacyAutoscalingStatusObject.valid()) { // TODO: Remove after January 2023
+ return new Autoscaling(fromAutoscalingStatusCode(legacyAutoscalingStatusObject.field(statusKey).asString()),
+ legacyAutoscalingStatusObject.field(descriptionKey).asString(),
+ optionalClusterResourcesFromSlime(autoscalingObject.field(resourcesKey)),
+ Instant.ofEpochMilli(autoscalingObject.field(atKey).asLong()),
+ loadFromSlime(autoscalingObject.field(peakKey)),
+ loadFromSlime(autoscalingObject.field(idealKey)));
}
- return new Autoscaling(optionalClusterResourcesFromSlime(autoscalingObject.field(resourcesKey)),
- Instant.ofEpochMilli(autoscalingObject.field(atKey).asLong()));
+ return new Autoscaling(fromAutoscalingStatusCode(autoscalingObject.field(statusKey).asString()),
+ autoscalingObject.field(descriptionKey).asString(),
+ optionalClusterResourcesFromSlime(autoscalingObject.field(resourcesKey)),
+ Instant.ofEpochMilli(autoscalingObject.field(atKey).asLong()),
+ loadFromSlime(autoscalingObject.field(peakKey)),
+ loadFromSlime(autoscalingObject.field(idealKey)));
}
private static void scalingEventsToSlime(List<ScalingEvent> scalingEvents, Cursor eventArray) {
@@ -194,36 +231,26 @@ public class ApplicationSerializer {
optionalInstant(inspector.field(completionKey)));
}
- private static void toSlime(AutoscalingStatus status, Cursor object) {
- object.setString(statusKey, toAutoscalingStatusCode(status.status()));
- object.setString(descriptionKey, status.description());
+ private static String toAutoscalingStatusCode(Autoscaling.Status status) {
+ return switch (status) {
+ case unavailable -> "unavailable";
+ case waiting -> "waiting";
+ case ideal -> "ideal";
+ case insufficient -> "insufficient";
+ case rescaling -> "rescaling";
+ };
}
- private static AutoscalingStatus autoscalingStatusFromSlime(Inspector object, Inspector parent) {
- return new AutoscalingStatus(fromAutoscalingStatusCode(object.field(statusKey).asString()),
- object.field(descriptionKey).asString());
- }
-
- private static String toAutoscalingStatusCode(AutoscalingStatus.Status status) {
- switch (status) {
- case unavailable : return "unavailable";
- case waiting : return "waiting";
- case ideal : return "ideal";
- case insufficient : return "insufficient";
- case rescaling : return "rescaling";
- default : throw new IllegalArgumentException("Unknown autoscaling status " + status);
- }
- }
-
- private static AutoscalingStatus.Status fromAutoscalingStatusCode(String code) {
- switch (code) {
- case "unavailable" : return AutoscalingStatus.Status.unavailable;
- case "waiting" : return AutoscalingStatus.Status.waiting;
- case "ideal" : return AutoscalingStatus.Status.ideal;
- case "insufficient" : return AutoscalingStatus.Status.insufficient;
- case "rescaling" : return AutoscalingStatus.Status.rescaling;
- default : throw new IllegalArgumentException("Unknown autoscaling status '" + code + "'");
- }
+ private static Autoscaling.Status fromAutoscalingStatusCode(String code) {
+ return switch (code) {
+ case "" -> Autoscaling.Status.unavailable;
+ case "unavailable" -> Autoscaling.Status.unavailable;
+ case "waiting" -> Autoscaling.Status.waiting;
+ case "ideal" -> Autoscaling.Status.ideal;
+ case "insufficient" -> Autoscaling.Status.insufficient;
+ case "rescaling" -> Autoscaling.Status.rescaling;
+ default -> throw new IllegalArgumentException("Unknown autoscaling status '" + code + "'");
+ };
}
private static Optional<Instant> optionalInstant(Inspector inspector) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
index a85377229b4..caf936e8aeb 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Activator.java
@@ -13,8 +13,8 @@ import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeMutex;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
-import com.yahoo.vespa.hosted.provision.applications.AutoscalingStatus;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
+import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;
@@ -120,8 +120,7 @@ class Activator {
}
if (cluster.target().resources().isPresent()
&& cluster.target().resources().get().justNumbers().equals(currentResources.justNumbers())) {
- cluster = cluster.with(new AutoscalingStatus(AutoscalingStatus.Status.ideal,
- "Cluster is ideally scaled within configured limits"));
+ cluster = cluster.withTarget(cluster.target().asIdeal(nodeRepository.clock().instant()));
}
if (cluster != modified.cluster(clusterEntry.getKey()).get())
modified = modified.with(cluster);
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 9479696143b..cb927c72eb5 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
@@ -9,14 +9,12 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
-import com.yahoo.vespa.hosted.provision.autoscale.ClusterModel;
+import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
import com.yahoo.vespa.hosted.provision.autoscale.Limits;
import com.yahoo.vespa.hosted.provision.autoscale.Load;
-import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
import java.net.URI;
import java.util.List;
-import java.util.Optional;
/**
* Serializes application information for nodes/v2/application responses
@@ -27,49 +25,38 @@ public class ApplicationSerializer {
public static Slime toSlime(Application application,
NodeList applicationNodes,
- MetricsDb metricsDb,
NodeRepository nodeRepository,
URI applicationUri) {
Slime slime = new Slime();
- toSlime(application, applicationNodes, metricsDb, nodeRepository, slime.setObject(), applicationUri);
+ toSlime(application, applicationNodes, nodeRepository, slime.setObject(), applicationUri);
return slime;
}
private static void toSlime(Application application,
NodeList applicationNodes,
- MetricsDb metricsDb,
NodeRepository nodeRepository,
Cursor object,
URI applicationUri) {
object.setString("url", applicationUri.toString());
object.setString("id", application.id().toFullString());
- clustersToSlime(application, applicationNodes, metricsDb, nodeRepository, object.setObject("clusters"));
+ clustersToSlime(application, applicationNodes, nodeRepository, object.setObject("clusters"));
}
private static void clustersToSlime(Application application,
NodeList applicationNodes,
- MetricsDb metricsDb,
NodeRepository nodeRepository,
Cursor clustersObject) {
- application.clusters().values().forEach(cluster -> toSlime(application, cluster, applicationNodes, metricsDb, nodeRepository, clustersObject));
+ application.clusters().values().forEach(cluster -> toSlime(application, cluster, applicationNodes, nodeRepository, clustersObject));
}
private static void toSlime(Application application,
Cluster cluster,
NodeList applicationNodes,
- MetricsDb metricsDb,
NodeRepository nodeRepository,
Cursor clustersObject) {
NodeList nodes = applicationNodes.not().retired().cluster(cluster.id());
if (nodes.isEmpty()) return;
ClusterResources currentResources = nodes.toResources();
- Optional<ClusterModel> clusterModel = ClusterModel.create(nodeRepository.zone(),
- application,
- nodes.clusterSpec(),
- cluster,
- nodes,
- metricsDb,
- nodeRepository.clock());
Cursor clusterObject = clustersObject.setObject(cluster.id().value());
clusterObject.setString("type", nodes.clusterSpec().type().name());
Limits limits = Limits.of(cluster).fullySpecified(nodes.clusterSpec(), nodeRepository, application.id());
@@ -77,15 +64,19 @@ public class ApplicationSerializer {
toSlime(limits.max(), clusterObject.setObject("max"));
toSlime(currentResources, clusterObject.setObject("current"));
if (cluster.shouldSuggestResources(currentResources))
- cluster.suggested().resources().ifPresent(suggested -> toSlime(suggested, clusterObject.setObject("suggested")));
- cluster.target().resources().ifPresent(target -> toSlime(target, clusterObject.setObject("target")));
- clusterModel.ifPresent(model -> clusterUtilizationToSlime(model, clusterObject.setObject("utilization")));
+ toSlime(cluster.suggested(), clusterObject.setObject("suggested"));
+ toSlime(cluster.target(), clusterObject.setObject("target"));
scalingEventsToSlime(cluster.scalingEvents(), clusterObject.setArray("scalingEvents"));
- clusterObject.setString("autoscalingStatusCode", cluster.autoscalingStatus().status().name());
- clusterObject.setString("autoscalingStatus", cluster.autoscalingStatus().description());
- clusterModel.ifPresent(model -> clusterObject.setLong("scalingDuration", model.scalingDuration().toMillis()));
- clusterModel.ifPresent(model -> clusterObject.setDouble("maxQueryGrowthRate", model.maxQueryGrowthRate()));
- clusterModel.ifPresent(model -> clusterObject.setDouble("currentQueryFractionOfMax", model.queryFractionOfMax()));
+ clusterObject.setLong("scalingDuration", cluster.scalingDuration(nodes.clusterSpec()).toMillis());
+ }
+
+ private static void toSlime(Autoscaling autoscaling, Cursor autoscalingObject) {
+ autoscalingObject.setString("status", autoscaling.status().name());
+ autoscalingObject.setString("description", autoscaling.description());
+ autoscaling.resources().ifPresent(resources -> toSlime(resources, autoscalingObject.setObject("resources")));
+ autoscalingObject.setLong("at", autoscaling.at().toEpochMilli());
+ toSlime(autoscaling.peak(), autoscalingObject.setObject("peak"));
+ toSlime(autoscaling.ideal(), autoscalingObject.setObject("ideal"));
}
private static void toSlime(ClusterResources resources, Cursor clusterResourcesObject) {
@@ -94,18 +85,10 @@ public class ApplicationSerializer {
NodeResourcesSerializer.toSlime(resources.nodeResources(), clusterResourcesObject.setObject("resources"));
}
- private static void clusterUtilizationToSlime(ClusterModel clusterModel, Cursor utilizationObject) {
- Load idealLoad = clusterModel.idealLoad();
- Load peakLoad = clusterModel.peakLoad();
-
- utilizationObject.setDouble("idealCpu", idealLoad.cpu());
- utilizationObject.setDouble("peakCpu", peakLoad.cpu());
-
- utilizationObject.setDouble("idealMemory", idealLoad.memory());
- utilizationObject.setDouble("peakMemory", peakLoad.memory());
-
- utilizationObject.setDouble("idealDisk", idealLoad.disk());
- utilizationObject.setDouble("peakDisk", peakLoad.disk());
+ private static void toSlime(Load load, Cursor utilizationObject) {
+ utilizationObject.setDouble("cpu", load.cpu());
+ utilizationObject.setDouble("memory", load.memory());
+ utilizationObject.setDouble("disk", load.disk());
}
private static void scalingEventsToSlime(List<ScalingEvent> scalingEvents, Cursor scalingEventsArray) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
index 83372f25d6e..fce5ccbdddc 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
@@ -33,7 +33,6 @@ import com.yahoo.vespa.hosted.provision.NodeMutex;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
import com.yahoo.vespa.hosted.provision.autoscale.Load;
-import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb;
import com.yahoo.vespa.hosted.provision.node.Address;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.IP;
@@ -75,16 +74,14 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler {
private final Orchestrator orchestrator;
private final NodeRepository nodeRepository;
- private final MetricsDb metricsDb;
private final NodeFlavors nodeFlavors;
@Inject
public NodesV2ApiHandler(ThreadedHttpRequestHandler.Context parentCtx, Orchestrator orchestrator,
- NodeRepository nodeRepository, MetricsDb metricsDb, NodeFlavors flavors) {
+ NodeRepository nodeRepository, NodeFlavors flavors) {
super(parentCtx);
this.orchestrator = orchestrator;
this.nodeRepository = nodeRepository;
- this.metricsDb = metricsDb;
this.nodeFlavors = flavors;
}
@@ -454,7 +451,6 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler {
return ErrorResponse.notFoundError("No application '" + id + "'");
Slime slime = ApplicationSerializer.toSlime(application.get(),
nodeRepository.nodes().list(Node.State.active).owner(id),
- metricsDb,
nodeRepository,
withPath("/nodes/v2/applications/" + id, uri));
return new SlimeJsonResponse(slime);
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 5bd53a2f8af..91c8f803429 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
@@ -30,6 +30,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Application;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.autoscale.MemoryMetricsDb;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.IP;
@@ -196,11 +197,20 @@ public class MockNodeRepository extends NodeRepository {
null), app1Id, provisioner);
Application app1 = applications().get(app1Id).get();
Cluster cluster1 = app1.cluster(cluster1Id.id()).get();
- cluster1 = cluster1.withSuggested(new Autoscaling(new ClusterResources(6, 2,
- new NodeResources(3, 20, 100, 1)),
- clock().instant()));
- cluster1 = cluster1.withTarget(new Autoscaling(new ClusterResources(4, 1,
- new NodeResources(3, 16, 100, 1)), clock().instant()));
+ cluster1 = cluster1.withSuggested(new Autoscaling(Autoscaling.Status.unavailable,
+ "",
+ Optional.of(new ClusterResources(6, 2,
+ new NodeResources(3, 20, 100, 1))),
+ clock().instant(),
+ Load.zero(),
+ Load.zero()));
+ cluster1 = cluster1.withTarget(new Autoscaling(Autoscaling.Status.unavailable,
+ "",
+ Optional.of(new ClusterResources(4, 1,
+ new NodeResources(3, 16, 100, 1))),
+ clock().instant(),
+ Load.zero(),
+ Load.zero()));
try (Mutex lock = applications().lock(app1Id)) {
applications().put(app1.with(cluster1), lock);
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java
index c65ebae9b3b..158c5116e19 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java
@@ -44,7 +44,6 @@ public class AutoscalingIntegrationTest {
}
var scaledResources = autoscaler.suggest(fixture.application(), fixture.cluster(), fixture.nodes());
- assertTrue(scaledResources.isPresent());
}
private static class MockHttpClient implements MetricsV2MetricsFetcher.AsyncHttpClient {
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 abe25b05955..4ce07d53ea9 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
@@ -36,15 +36,15 @@ public class AutoscalingTest {
fixture.autoscale());
fixture.deploy(Capacity.from(scaledResources));
- assertTrue("Cluster in flux -> No further change", fixture.autoscale().isEmpty());
+ assertEquals("Cluster in flux -> No further change", Autoscaling.Status.waiting, fixture.autoscale().status());
fixture.deactivateRetired(Capacity.from(scaledResources));
fixture.loader().applyCpuLoad(0.19f, 10);
- assertEquals("Load change is small -> No change", Optional.empty(), fixture.autoscale().target());
+ assertEquals("Load change is small -> No change", Optional.empty(), fixture.autoscale().resources());
fixture.loader().applyCpuLoad(0.1f, 10);
- assertEquals("Too little time passed for downscaling -> No change", Optional.empty(), fixture.autoscale().target());
+ assertEquals("Too little time passed for downscaling -> No change", Optional.empty(), fixture.autoscale().resources());
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyCpuLoad(0.1f, 10);
@@ -57,13 +57,13 @@ public class AutoscalingTest {
@Test
public void test_no_autoscaling_with_no_measurements() {
var fixture = AutoscalingTester.fixture().awsProdSetup(true).build();
- assertTrue(fixture.autoscale().target().isEmpty());
+ assertTrue(fixture.autoscale().resources().isEmpty());
}
@Test
public void test_no_autoscaling_with_no_measurements_exclusive() {
var fixture = AutoscalingTester.fixture().awsProdSetup(false).build();
- assertTrue(fixture.autoscale().target().isEmpty());
+ assertTrue(fixture.autoscale().resources().isEmpty());
}
/** Using too many resources for a short period is proof we should scale up regardless of the time that takes. */
@@ -277,7 +277,7 @@ public class AutoscalingTest {
fixture.deactivateRetired(capacity);
fixture.tester().clock().advance(Duration.ofDays(1));
fixture.loader().applyCpuLoad(0.8, 120);
- assertEquals(DiskSpeed.any, fixture.autoscale(capacity).target().get().nodeResources().diskSpeed());
+ assertEquals(DiskSpeed.any, fixture.autoscale(capacity).resources().get().nodeResources().diskSpeed());
}
@Test
@@ -357,10 +357,9 @@ public class AutoscalingTest {
ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1));
var fixture = AutoscalingTester.fixture().awsProdSetup(true).capacity(Capacity.from(min, min)).build();
- // deploy
fixture.tester().clock().advance(Duration.ofDays(1));
fixture.loader().applyCpuLoad(0.25, 120);
- assertTrue(fixture.autoscale().isEmpty());
+ assertEquals(Autoscaling.Status.unavailable, fixture.autoscale().status());
}
@Test
@@ -379,12 +378,12 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(0.01, 0.01, 0.01), 120);
- Autoscaler.Advice suggestion = fixture.suggest();
+ Autoscaling suggestion = fixture.suggest();
fixture.tester().assertResources("Choosing the remote disk flavor as it has less disk",
2, 1, 3.0, 100.0, 10.0,
suggestion);
assertEquals("Choosing the remote disk flavor as it has less disk",
- StorageType.remote, suggestion.target().get().nodeResources().storageType());
+ StorageType.remote, suggestion.resources().get().nodeResources().storageType());
}
@Test
@@ -415,7 +414,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(0.9, 0.6, 0.7), 1, false, true, 120);
assertTrue("Not scaling up since nodes were measured while cluster was out of service",
- fixture.autoscale().target().isEmpty());
+ fixture.autoscale().resources().isEmpty());
}
@Test
@@ -424,7 +423,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(0.9, 0.6, 0.7), 1, true, false, 120);
assertTrue("Not scaling up since nodes were measured while cluster was unstable",
- fixture.autoscale().target().isEmpty());
+ fixture.autoscale().resources().isEmpty());
}
@Test
@@ -521,7 +520,7 @@ public class AutoscalingTest {
public void scaling_down_only_after_delay() {
var fixture = AutoscalingTester.fixture().awsProdSetup(true).build();
fixture.loader().applyCpuLoad(0.02, 120);
- assertTrue("Too soon after initial deployment", fixture.autoscale().target().isEmpty());
+ assertTrue("Too soon after initial deployment", fixture.autoscale().resources().isEmpty());
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyCpuLoad(0.02, 120);
fixture.tester().assertResources("Scaling down since enough time has passed",
@@ -670,7 +669,7 @@ public class AutoscalingTest {
fixture.tester().clock().advance(Duration.ofDays(2));
fixture.loader().applyLoad(new Load(1.0, 1.0, 1.0), 200);
assertTrue("Not attempting to scale up because policies dictate we'll only get one node",
- fixture.autoscale().target().isEmpty());
+ fixture.autoscale().resources().isEmpty());
}
/** Same setup as test_autoscaling_in_dev(), just with required = true */
@@ -731,12 +730,14 @@ public class AutoscalingTest {
fixture.currentResources().advertisedResources());
fixture.tester().deploy(fixture.applicationId(), clusterSpec(false), fixture.capacity());
+ fixture.loader().applyLoad(new Load(0.1, 0.1, 0.1), 100);
fixture.tester().assertResources("With non-exclusive nodes, a better solution is " +
"50% more nodes with half the cpu",
- 3, 1, 1, 4, 145.6,
+ 3, 1, 1, 4, 100.0,
fixture.autoscale());
fixture.tester().deploy(fixture.applicationId(), clusterSpec(true), fixture.capacity());
+ fixture.loader().applyLoad(new Load(0.1, 0.1, 0.1), 100);
fixture.tester().assertResources("Reverts to the initial resources",
2, 1, 2, 4, 100,
fixture.currentResources().advertisedResources());
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 fd98af21134..4e6b8dec9ef 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
@@ -134,8 +134,7 @@ class AutoscalingTester {
cluster.required(),
cluster.suggested(),
cluster.target(),
- List.of(), // Remove scaling events
- cluster.autoscalingStatus());
+ List.of()); // Remove scaling events
cluster = cluster.with(ScalingEvent.create(cluster.minResources(), cluster.minResources(),
0,
clock().instant().minus(Duration.ofDays(1).minus(duration))).withCompletion(clock().instant().minus(Duration.ofDays(1))));
@@ -143,7 +142,7 @@ class AutoscalingTester {
nodeRepository().applications().put(application, nodeRepository().applications().lock(applicationId));
}
- public Autoscaler.Advice autoscale(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity) {
+ public Autoscaling autoscale(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity) {
capacity = capacityPolicies.applyOn(capacity, applicationId, capacityPolicies.decideExclusivity(capacity, cluster).isExclusive());
Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId))
.withCluster(cluster.id(), false, capacity);
@@ -154,7 +153,7 @@ class AutoscalingTester {
nodeRepository().nodes().list(Node.State.active).owner(applicationId));
}
- public Autoscaler.Advice suggest(ApplicationId applicationId, ClusterSpec.Id clusterId,
+ public Autoscaling suggest(ApplicationId applicationId, ClusterSpec.Id clusterId,
ClusterResources min, ClusterResources max) {
Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId))
.withCluster(clusterId, false, Capacity.from(min, max));
@@ -177,10 +176,10 @@ class AutoscalingTester {
public ClusterResources assertResources(String message,
int nodeCount, int groupCount,
double approxCpu, double approxMemory, double approxDisk,
- Autoscaler.Advice advice) {
- assertTrue("Resources are present: " + message + " (" + advice + ": " + advice.reason() + ")",
- advice.target().isPresent());
- var resources = advice.target().get();
+ Autoscaling autoscaling) {
+ assertTrue("Resources are present: " + message + " (" + autoscaling + ": " + autoscaling.status() + ")",
+ autoscaling.resources().isPresent());
+ var resources = autoscaling.resources().get();
assertResources(message, nodeCount, groupCount, approxCpu, approxMemory, approxDisk, resources);
return resources;
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java
index 311428de8ff..a43746db6d9 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java
@@ -95,17 +95,17 @@ public class Fixture {
public Loader loader() { return loader; }
/** Autoscale within the deployed capacity of this. */
- public Autoscaler.Advice autoscale() {
+ public Autoscaling autoscale() {
return autoscale(capacity);
}
/** Autoscale within the given capacity. */
- public Autoscaler.Advice autoscale(Capacity capacity) {
+ public Autoscaling autoscale(Capacity capacity) {
return tester().autoscale(applicationId, clusterSpec, capacity);
}
/** Compute an autoscaling suggestion for this. */
- public Autoscaler.Advice suggest() {
+ public Autoscaling suggest() {
return tester().suggest(applicationId, clusterSpec.id(), capacity.minResources(), capacity.maxResources());
}
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 214d842e4bb..3084ce9215a 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
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
import com.yahoo.vespa.hosted.provision.autoscale.ClusterModel;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.hosted.provision.testutils.MockDeployer;
@@ -28,6 +29,7 @@ import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -53,7 +55,6 @@ public class AutoscalingMaintainerTest {
new MockDeployer.ApplicationContext(app1, cluster1, Capacity.from(new ClusterResources(2, 1, lowResources))),
new MockDeployer.ApplicationContext(app2, cluster2, Capacity.from(new ClusterResources(2, 1, highResources))));
-
tester.maintainer().maintain(); // noop
assertTrue(tester.deployer().lastDeployTime(app1).isEmpty());
assertTrue(tester.deployer().lastDeployTime(app2).isEmpty());
@@ -218,7 +219,7 @@ public class AutoscalingMaintainerTest {
tester.deploy(app1, cluster1, capacity);
tester.addMeasurements(1.0f, 0.3f, 0.3f, 0, 4, app1, cluster1.id());
tester.maintainer().maintain();
- assertEquals("Scale up: " + tester.cluster(app1, cluster1).autoscalingStatus(),
+ assertEquals("Scale up: " + tester.cluster(app1, cluster1).target().status(),
1,
tester.cluster(app1, cluster1).lastScalingEvent().get().generation());
@@ -299,6 +300,33 @@ public class AutoscalingMaintainerTest {
.size());
}
+ @Test
+ public void empty_autoscaling_is_ignored() {
+ ApplicationId app1 = AutoscalingMaintainerTester.makeApplicationId("app1");
+ ClusterSpec cluster1 = AutoscalingMaintainerTester.containerClusterSpec();
+ NodeResources resources = new NodeResources(4, 4, 10, 1);
+ ClusterResources min = new ClusterResources(2, 1, resources);
+ ClusterResources max = new ClusterResources(20, 1, resources);
+ var capacity = Capacity.from(min, max);
+ var tester = new AutoscalingMaintainerTester(new MockDeployer.ApplicationContext(app1, cluster1, capacity));
+
+ // Add a scaling event
+ tester.deploy(app1, cluster1, capacity);
+ tester.addMeasurements(1.0f, 0.3f, 0.3f, 0, 4, app1, cluster1.id());
+ tester.maintainer().maintain();
+ assertEquals("Scale up: " + tester.cluster(app1, cluster1).target().status(),
+ 1,
+ tester.cluster(app1, cluster1).lastScalingEvent().get().generation());
+ Load peak = tester.cluster(app1, cluster1).target().peak();
+ assertNotEquals(Load.zero(), peak);
+
+ // Old measurements go out of scope and no new ones are made
+ tester.clock().advance(Duration.ofDays(1));
+ tester.maintainer().maintain();
+ Load newPeak = tester.cluster(app1, cluster1).target().peak();
+ assertEquals("Old measurements are retained", peak, newPeak);
+ }
+
private void autoscale(boolean down, Duration completionTime, Duration expectedWindow,
ManualClock clock, ApplicationId application, ClusterSpec cluster,
AutoscalingMaintainerTester tester) {
@@ -322,7 +350,7 @@ public class AutoscalingMaintainerTest {
tester.addMeasurements(load, load, load, generation, 200, application, cluster.id());
tester.maintainer().maintain();
assertEquals("We passed window duration so a new autoscaling is started: " +
- tester.cluster(application, cluster).autoscalingStatus(),
+ tester.cluster(application, cluster).target().status(),
generation + 1,
tester.cluster(application, cluster).lastScalingEvent().get().generation());
}
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 e6460b4a610..a05cc388bea 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
@@ -2,16 +2,15 @@
package com.yahoo.vespa.hosted.provision.persistence;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.applications.Application;
-import com.yahoo.vespa.hosted.provision.applications.AutoscalingStatus;
import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
import com.yahoo.vespa.hosted.provision.applications.Status;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaling;
+import com.yahoo.vespa.hosted.provision.autoscale.Load;
import org.junit.Test;
import java.time.Instant;
@@ -38,26 +37,33 @@ public class ApplicationSerializerTest {
true,
Autoscaling.empty(),
Autoscaling.empty(),
- List.of(),
- AutoscalingStatus.empty()));
+ List.of()));
var minResources = new NodeResources(1, 2, 3, 4);
clusters.add(new Cluster(ClusterSpec.Id.from("c2"),
true,
new ClusterResources( 8, 4, minResources),
new ClusterResources(14, 7, new NodeResources(3, 6, 21, 24)),
false,
- new Autoscaling(new ClusterResources(20, 10,
- new NodeResources(0.5, 4, 14, 16)),
- Instant.ofEpochMilli(1234L)),
- new Autoscaling(new ClusterResources(10, 5,
- new NodeResources(2, 4, 14, 16)),
- Instant.ofEpochMilli(5678L)),
+ new Autoscaling(Autoscaling.Status.unavailable,
+ "",
+ Optional.of(new ClusterResources(20, 10,
+ new NodeResources(0.5, 4, 14, 16))),
+ Instant.ofEpochMilli(1234L),
+ new Load(0.1, 0.2, 0.3),
+ new Load(0.4, 0.5, 0.6)),
+ new Autoscaling(Autoscaling.Status.insufficient,
+ "Autoscaling status",
+ Optional.of(new ClusterResources(10, 5,
+ new NodeResources(2, 4, 14, 16))),
+ Instant.ofEpochMilli(5678L),
+ Load.zero(),
+ Load.one()),
List.of(new ScalingEvent(new ClusterResources(10, 5, minResources),
new ClusterResources(12, 6, minResources),
7L,
Instant.ofEpochMilli(12345L),
- Optional.of(Instant.ofEpochMilli(67890L)))),
- new AutoscalingStatus(AutoscalingStatus.Status.insufficient, "Autoscaling status")));
+ Optional.of(Instant.ofEpochMilli(67890L))))
+ ));
Application original = new Application(ApplicationId.from("myTenant", "myApplication", "myInstance"),
Status.initial().withCurrentReadShare(0.3).withMaxReadShare(0.5),
clusters);
@@ -82,7 +88,6 @@ public class ApplicationSerializerTest {
assertEquals(originalCluster.suggested(), serializedCluster.suggested());
assertEquals(originalCluster.target(), serializedCluster.target());
assertEquals(originalCluster.scalingEvents(), serializedCluster.scalingEvents());
- assertEquals(originalCluster.autoscalingStatus(), serializedCluster.autoscalingStatus());
}
}
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 02aef6fa9c4..ead4e5fd06a 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
@@ -44,39 +44,61 @@
}
},
"suggested" : {
- "nodes" : 6,
- "groups" : 2,
+ "status" : "unavailable",
+ "description" : "",
"resources" : {
- "vcpu" : 3.0,
- "memoryGb" : 20.0,
- "diskGb" : 100.0,
- "bandwidthGbps" : 1.0,
- "diskSpeed" : "fast",
- "storageType" : "any",
- "architecture":"x86_64"
+ "nodes": 6,
+ "groups": 2,
+ "resources": {
+ "vcpu": 3.0,
+ "memoryGb": 20.0,
+ "diskGb": 100.0,
+ "bandwidthGbps": 1.0,
+ "diskSpeed": "fast",
+ "storageType": "any",
+ "architecture": "x86_64"
+ }
+ },
+ "at" : 123,
+ "peak" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
+ },
+ "ideal" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
}
},
"target" : {
- "nodes" : 4,
- "groups" : 1,
+ "status" : "unavailable",
+ "description" : "",
"resources" : {
- "vcpu" : 3.0,
- "memoryGb" : 16.0,
- "diskGb" : 100.0,
- "bandwidthGbps" : 1.0,
- "diskSpeed" : "fast",
- "storageType" : "any",
- "architecture":"x86_64"
+ "nodes" : 4,
+ "groups" : 1,
+ "resources" : {
+ "vcpu": 3.0,
+ "memoryGb": 16.0,
+ "diskGb": 100.0,
+ "bandwidthGbps": 1.0,
+ "diskSpeed": "fast",
+ "storageType": "any",
+ "architecture": "x86_64"
+ }
+ },
+ "at" : 123,
+ "peak" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
+ },
+ "ideal" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
}
},
- "utilization" : {
- "idealCpu": 0.40750000000000003,
- "peakCpu": 0.0,
- "idealMemory": 0.8,
- "peakMemory": 0.0,
- "idealDisk": 0.95,
- "peakDisk": 0.0
- },
"scalingEvents" : [
{
"from": {
@@ -108,11 +130,7 @@
"at" : 123
}
],
- "autoscalingStatusCode": "unavailable",
- "autoscalingStatus": "",
- "scalingDuration": 600000,
- "maxQueryGrowthRate": 0.1,
- "currentQueryFractionOfMax": 0.5
+ "scalingDuration": 600000
}
}
}
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 341fa5f6031..f60fdf3e602 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
@@ -43,13 +43,20 @@
"architecture":"x86_64"
}
},
- "utilization" : {
- "idealCpu": 0.42670157068062825,
- "peakCpu": 0.0,
- "idealMemory": 0.325,
- "peakMemory": 0.0,
- "idealDisk": 0.3,
- "peakDisk": 0.0
+ "target" : {
+ "status" : "unavailable",
+ "description" : "",
+ "at" : 0,
+ "peak" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
+ },
+ "ideal" : {
+ "cpu" : 0.0,
+ "memory" : 0.0,
+ "disk" : 0.0
+ }
},
"scalingEvents" : [
{
@@ -82,11 +89,7 @@
"at" : 123
}
],
- "autoscalingStatusCode": "unavailable",
- "autoscalingStatus" : "",
- "scalingDuration": 43200000,
- "maxQueryGrowthRate": 0.1,
- "currentQueryFractionOfMax": 0.5
+ "scalingDuration": 43200000
}
}
}