diff options
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 } } } |