From e4833e7380f6a18adda7eaaa5a6ad017f46c6761 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 1 Dec 2021 08:49:52 +0100 Subject: Revert "Merge pull request #20302 from vespa-engine/revert-20298-bratseth/autoscaling-in-dev" This reverts commit aa3ffac0caaba2c60fabc585be8376cfe132c17d, reversing changes made to 318edd10866b25b06419ed6069227eafbe63d685. --- .../hosted/provision/applications/Application.java | 9 +- .../hosted/provision/applications/Cluster.java | 33 +++-- .../autoscale/AllocatableClusterResources.java | 26 ++-- .../provision/autoscale/AllocationOptimizer.java | 4 +- .../hosted/provision/autoscale/ClusterModel.java | 5 + .../persistence/ApplicationSerializer.java | 4 +- .../provision/provisioning/CapacityPolicies.java | 12 +- .../provisioning/NodeRepositoryProvisioner.java | 10 +- .../autoscale/AutoscalingIntegrationTest.java | 3 +- .../provision/autoscale/AutoscalingTest.java | 157 +++++++++++++++------ .../provision/autoscale/AutoscalingTester.java | 18 ++- .../provision/autoscale/ClusterModelTest.java | 14 +- .../persistence/ApplicationSerializerTest.java | 4 + .../provision/provisioning/ProvisioningTest.java | 8 +- .../provision/provisioning/ProvisioningTester.java | 4 + 15 files changed, 209 insertions(+), 102 deletions(-) (limited to 'node-repository') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java index 3c2ab8cead2..df5044de05c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java @@ -2,10 +2,9 @@ package com.yahoo.vespa.hosted.provision.applications; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.Optional; @@ -59,12 +58,12 @@ public class Application { * Returns an application with the given cluster having the min and max resource limits of the given cluster. * If the cluster has a target which is not inside the new limits, the target is removed. */ - public Application withCluster(ClusterSpec.Id id, boolean exclusive, ClusterResources min, ClusterResources max) { + public Application withCluster(ClusterSpec.Id id, boolean exclusive, Capacity requested) { Cluster cluster = clusters.get(id); if (cluster == null) - cluster = new Cluster(id, exclusive, min, max, Optional.empty(), Optional.empty(), List.of(), AutoscalingStatus.empty()); + cluster = Cluster.create(id, exclusive, requested); else - cluster = cluster.withConfiguration(exclusive, min, max); + cluster = cluster.withConfiguration(exclusive, requested); return with(cluster); } 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 5478999e4fe..e0ccbe10b10 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.applications; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler; @@ -25,6 +26,7 @@ public class Cluster { private final ClusterSpec.Id id; private final boolean exclusive; private final ClusterResources min, max; + private boolean required; private final Optional suggested; private final Optional target; @@ -36,6 +38,7 @@ public class Cluster { boolean exclusive, ClusterResources minResources, ClusterResources maxResources, + boolean required, Optional suggestedResources, Optional targetResources, List scalingEvents, @@ -44,6 +47,7 @@ public class Cluster { this.exclusive = exclusive; this.min = Objects.requireNonNull(minResources); this.max = Objects.requireNonNull(maxResources); + this.required = required; this.suggested = Objects.requireNonNull(suggestedResources); Objects.requireNonNull(targetResources); if (targetResources.isPresent() && ! targetResources.get().isWithin(minResources, maxResources)) @@ -56,14 +60,20 @@ public class Cluster { public ClusterSpec.Id id() { return id; } + /** Returns whether the nodes allocated to this cluster must be on host exclusively dedicated to this application */ + public boolean exclusive() { return exclusive; } + /** Returns the configured minimal resources in this cluster */ public ClusterResources minResources() { return min; } /** Returns the configured maximal resources in this cluster */ public ClusterResources maxResources() { return max; } - /** Returns whether the nodes allocated to this cluster must be on host exclusively dedicated to this application */ - public boolean exclusive() { return exclusive; } + /** + * Returns whether the resources of this cluster are required to be within the specified min and max. + * Otherwise they may be adjusted by capacity policies. + */ + public boolean required() { return required; } /** * Returns the computed resources (between min and max, inclusive) this cluster should @@ -97,16 +107,18 @@ public class Cluster { /** The latest autoscaling status of this cluster, or unknown (never null) if none */ public AutoscalingStatus autoscalingStatus() { return autoscalingStatus; } - public Cluster withConfiguration(boolean exclusive, ClusterResources min, ClusterResources max) { - return new Cluster(id, exclusive, min, max, suggested, target, scalingEvents, autoscalingStatus); + public Cluster withConfiguration(boolean exclusive, Capacity capacity) { + return new Cluster(id, exclusive, + capacity.minResources(), capacity.maxResources(), capacity.isRequired(), + suggested, target, scalingEvents, autoscalingStatus); } public Cluster withSuggested(Optional suggested) { - return new Cluster(id, exclusive, min, max, suggested, target, scalingEvents, autoscalingStatus); + return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus); } public Cluster withTarget(Optional target) { - return new Cluster(id, exclusive, min, max, suggested, target, scalingEvents, autoscalingStatus); + return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus); } /** Add or update (based on "at" time) a scaling event */ @@ -120,12 +132,12 @@ public class Cluster { scalingEvents.add(scalingEvent); prune(scalingEvents); - return new Cluster(id, exclusive, min, max, suggested, target, scalingEvents, autoscalingStatus); + 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, suggested, target, scalingEvents, autoscalingStatus); + return new Cluster(id, exclusive, min, max, required, suggested, target, scalingEvents, autoscalingStatus); } @Override @@ -156,6 +168,11 @@ public class Cluster { return -1; } + public static Cluster create(ClusterSpec.Id id, boolean exclusive, Capacity requested) { + return new Cluster(id, exclusive, requested.minResources(), requested.maxResources(), requested.isRequired(), + Optional.empty(), Optional.empty(), List.of(), AutoscalingStatus.empty()); + } + public static class Suggestion { private final ClusterResources resources; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java index f1e707be7b4..078b0621a99 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.NodeResources; 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.provisioning.CapacityPolicies; import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits; import java.util.List; @@ -54,14 +55,14 @@ public class AllocatableClusterResources { public AllocatableClusterResources(ClusterResources realResources, NodeResources advertisedResources, - NodeResources idealResources, + ClusterResources idealResources, ClusterSpec clusterSpec) { this.nodes = realResources.nodes(); this.groups = realResources.groups(); this.realResources = realResources.nodeResources(); this.advertisedResources = advertisedResources; this.clusterSpec = clusterSpec; - this.fulfilment = fulfilment(realResources.nodeResources(), idealResources); + this.fulfilment = fulfilment(realResources, idealResources); } /** @@ -99,10 +100,10 @@ public class AllocatableClusterResources { */ public double fulfilment() { return fulfilment; } - private static double fulfilment(NodeResources realResources, NodeResources idealResources) { - double vcpuFulfilment = Math.min(1, realResources.vcpu() / idealResources.vcpu()); - double memoryGbFulfilment = Math.min(1, realResources.memoryGb() / idealResources.memoryGb()); - double diskGbFulfilment = Math.min(1, realResources.diskGb() / idealResources.diskGb()); + private static double fulfilment(ClusterResources realResources, ClusterResources idealResources) { + double vcpuFulfilment = Math.min(1, realResources.totalResources().vcpu() / idealResources.totalResources().vcpu()); + double memoryGbFulfilment = Math.min(1, realResources.totalResources().memoryGb() / idealResources.totalResources().memoryGb()); + double diskGbFulfilment = Math.min(1, realResources.totalResources().diskGb() / idealResources.totalResources().diskGb()); return (vcpuFulfilment + memoryGbFulfilment + diskGbFulfilment) / 3; } @@ -138,21 +139,25 @@ public class AllocatableClusterResources { public static Optional from(ClusterResources wantedResources, ClusterSpec clusterSpec, Limits applicationLimits, + boolean required, NodeList hosts, NodeRepository nodeRepository) { + var capacityPolicies = new CapacityPolicies(nodeRepository); var systemLimits = new NodeResourceLimits(nodeRepository); boolean exclusive = clusterSpec.isExclusive(); + int actualNodes = capacityPolicies.decideSize(wantedResources.nodes(), required, true, false, clusterSpec); if ( !clusterSpec.isExclusive() && !nodeRepository.zone().getCloud().dynamicProvisioning()) { // We decide resources: Add overhead to what we'll request (advertised) to make sure real becomes (at least) cappedNodeResources var advertisedResources = nodeRepository.resourcesCalculator().realToRequest(wantedResources.nodeResources(), exclusive); advertisedResources = systemLimits.enlargeToLegal(advertisedResources, clusterSpec.type(), exclusive); // Ask for something legal advertisedResources = applicationLimits.cap(advertisedResources); // Overrides other conditions, even if it will then fail + advertisedResources = capacityPolicies.decideNodeResources(advertisedResources, required, clusterSpec); // Adjust to what we can request var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive); // What we'll really get if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) return Optional.empty(); if (matchesAny(hosts, advertisedResources)) - return Optional.of(new AllocatableClusterResources(wantedResources.with(realResources), + return Optional.of(new AllocatableClusterResources(wantedResources.withNodes(actualNodes).with(realResources), advertisedResources, - wantedResources.nodeResources(), + wantedResources, clusterSpec)); else return Optional.empty(); @@ -163,6 +168,7 @@ public class AllocatableClusterResources { for (Flavor flavor : nodeRepository.flavors().getFlavors()) { // Flavor decide resources: Real resources are the worst case real resources we'll get if we ask for these advertised resources NodeResources advertisedResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor); + advertisedResources = capacityPolicies.decideNodeResources(advertisedResources, required, clusterSpec); // Adjust to what we can get NodeResources realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive); // Adjust where we don't need exact match to the flavor @@ -178,9 +184,9 @@ public class AllocatableClusterResources { if ( ! between(applicationLimits.min().nodeResources(), applicationLimits.max().nodeResources(), advertisedResources)) continue; if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) continue; - var candidate = new AllocatableClusterResources(wantedResources.with(realResources), + var candidate = new AllocatableClusterResources(wantedResources.withNodes(actualNodes).with(realResources), advertisedResources, - wantedResources.nodeResources(), + wantedResources, clusterSpec); if (best.isEmpty() || candidate.preferableTo(best.get())) best = Optional.of(candidate); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java index 6fd9801164a..b8a80a9bd2b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java @@ -65,7 +65,9 @@ public class AllocationOptimizer { nodeResourcesWith(nodesAdjustedForRedundancy, groupsAdjustedForRedundancy, limits, target, current, clusterModel)); - var allocatableResources = AllocatableClusterResources.from(next, current.clusterSpec(), limits, hosts, nodeRepository); + var allocatableResources = AllocatableClusterResources.from(next, current.clusterSpec(), limits, + clusterModel.cluster().required(), + hosts, nodeRepository); if (allocatableResources.isEmpty()) continue; if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get())) 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 35aafd3e0f4..1001ab83cc0 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 @@ -32,6 +32,7 @@ public class ClusterModel { static final double idealDiskLoad = 0.6; private final Application application; + private final Cluster cluster; /** The current nodes of this cluster, or empty if this models a new cluster not yet deployed */ private final NodeList nodes; private final Clock clock; @@ -50,6 +51,7 @@ public class ClusterModel { MetricsDb metricsDb, Clock clock) { this.application = application; + this.cluster = cluster; this.nodes = clusterNodes; this.clock = clock; this.scalingDuration = computeScalingDuration(cluster, clusterSpec); @@ -65,6 +67,7 @@ public class ClusterModel { ClusterTimeseries clusterTimeseries, ClusterNodesTimeseries nodeTimeseries) { this.application = application; + this.cluster = cluster; this.nodes = null; this.clock = clock; @@ -73,6 +76,8 @@ public class ClusterModel { this.nodeTimeseries = nodeTimeseries; } + public Cluster cluster() { return cluster; } + /** Returns the predicted duration of a rescaling of this cluster */ public Duration scalingDuration() { return scalingDuration; } 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 9cdb4c69b97..2289ba4a0ea 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 @@ -48,6 +48,7 @@ public class ApplicationSerializer { private static final String exclusiveKey = "exclusive"; private static final String minResourcesKey = "min"; private static final String maxResourcesKey = "max"; + private static final String requiredKey = "required"; private static final String suggestedKey = "suggested"; private static final String resourcesKey = "resources"; private static final String targetResourcesKey = "target"; @@ -99,7 +100,6 @@ public class ApplicationSerializer { } private static Status statusFromSlime(Inspector statusObject) { - if ( ! statusObject.valid()) return Status.initial(); // TODO: Remove this line after March 2021 return new Status(statusObject.field(currentReadShareKey).asDouble(), statusObject.field(maxReadShareKey).asDouble()); } @@ -118,6 +118,7 @@ public class ApplicationSerializer { clusterObject.setBool(exclusiveKey, cluster.exclusive()); toSlime(cluster.minResources(), clusterObject.setObject(minResourcesKey)); toSlime(cluster.maxResources(), clusterObject.setObject(maxResourcesKey)); + clusterObject.setBool(requiredKey, cluster.required()); cluster.suggestedResources().ifPresent(suggested -> toSlime(suggested, clusterObject.setObject(suggestedKey))); cluster.targetResources().ifPresent(target -> toSlime(target, clusterObject.setObject(targetResourcesKey))); scalingEventsToSlime(cluster.scalingEvents(), clusterObject.setArray(scalingEventsKey)); @@ -130,6 +131,7 @@ public class ApplicationSerializer { clusterObject.field(exclusiveKey).asBool(), clusterResourcesFromSlime(clusterObject.field(minResourcesKey)), clusterResourcesFromSlime(clusterObject.field(maxResourcesKey)), + clusterObject.field(requiredKey).asBool(), optionalSuggestionFromSlime(clusterObject.field(suggestedKey)), optionalClusterResourcesFromSlime(clusterObject.field(targetResourcesKey)), scalingEventsFromSlime(clusterObject.field(scalingEventsKey)), diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 839bc21827c..0c2c3c48df1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -29,11 +29,11 @@ public class CapacityPolicies { this.sharedHosts = type -> PermanentFlags.SHARED_HOST.bindTo(nodeRepository.flagSource()).value().isEnabled(type.name()); } - public int decideSize(int requested, Capacity capacity, ClusterSpec cluster, ApplicationId application) { - if (application.instance().isTester()) return 1; + public int decideSize(int requested, boolean required, boolean canFail, boolean isTester, ClusterSpec cluster) { + if (isTester) return 1; - ensureRedundancy(requested, cluster, capacity.canFail()); - if (capacity.isRequired()) return requested; + ensureRedundancy(requested, cluster, canFail); + if (required) return requested; switch(zone.environment()) { case dev : case test : return 1; case perf : return Math.min(requested, 3); @@ -43,11 +43,11 @@ public class CapacityPolicies { } } - public NodeResources decideNodeResources(NodeResources target, Capacity capacity, ClusterSpec cluster) { + public NodeResources decideNodeResources(NodeResources target, boolean required, ClusterSpec cluster) { if (target.isUnspecified()) target = defaultNodeResources(cluster.type()); - if (capacity.isRequired()) return target; + if (required) return target; // Dev does not cap the cpu or network of containers since usage is spotty: Allocate just a small amount exclusively if (zone.environment() == Environment.dev && !zone.getCloud().dynamicProvisioning()) diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index 0ab04a1a73d..b35b0a5e301 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -97,9 +97,13 @@ public class NodeRepositoryProvisioner implements Provisioner { NodeSpec nodeSpec; if (requested.type() == NodeType.tenant) { ClusterResources target = decideTargetResources(application, cluster, requested); - int nodeCount = capacityPolicies.decideSize(target.nodes(), requested, cluster, application); + int nodeCount = capacityPolicies.decideSize(target.nodes(), + requested.isRequired(), + requested.canFail(), + application.instance().isTester(), + cluster); groups = Math.min(target.groups(), nodeCount); // cannot have more groups than nodes - resources = capacityPolicies.decideNodeResources(target.nodeResources(), requested, cluster); + resources = capacityPolicies.decideNodeResources(target.nodeResources(), requested.isRequired(), cluster); boolean exclusive = capacityPolicies.decideExclusivity(requested, cluster.isExclusive()); nodeSpec = NodeSpec.from(nodeCount, resources, exclusive, requested.canFail()); logIfDownscaled(target.nodes(), nodeCount, cluster, logger); @@ -141,7 +145,7 @@ public class NodeRepositoryProvisioner implements Provisioner { private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec clusterSpec, Capacity requested) { try (Mutex lock = nodeRepository.nodes().lock(applicationId)) { var application = nodeRepository.applications().get(applicationId).orElse(Application.empty(applicationId)) - .withCluster(clusterSpec.id(), clusterSpec.isExclusive(), requested.minResources(), requested.maxResources()); + .withCluster(clusterSpec.id(), clusterSpec.isExclusive(), requested); nodeRepository.applications().put(application, lock); var cluster = application.cluster(clusterSpec.id()).get(); return cluster.targetResources().orElseGet(() -> currentResources(application, clusterSpec, cluster, requested)); 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 4a0c2012ae4..667dffef2a6 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 @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.provision.autoscale; 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.HostSpec; @@ -55,7 +56,7 @@ public class AutoscalingIntegrationTest { ClusterResources max = new ClusterResources(2, 1, nodes); Application application = tester.nodeRepository().applications().get(application1).orElse(Application.empty(application1)) - .withCluster(cluster1.id(), false, min, max); + .withCluster(cluster1.id(), false, Capacity.from(min, max)); try (Mutex lock = tester.nodeRepository().nodes().lock(application1)) { tester.nodeRepository().applications().put(application, lock); } 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 a3c7b7d2d2b..601a7109533 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 @@ -41,6 +41,7 @@ public class AutoscalingTest { new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -50,10 +51,10 @@ public class AutoscalingTest { tester.deploy(application1, cluster1, 5, 1, hostResources); tester.clock().advance(Duration.ofDays(1)); - assertTrue("No measurements -> No change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + assertTrue("No measurements -> No change", tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); tester.addCpuMeasurements(0.25f, 1f, 59, application1); - assertTrue("Too few measurements -> No change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + assertTrue("Too few measurements -> No change", tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); tester.clock().advance(Duration.ofDays(1)); tester.addCpuMeasurements(0.25f, 1f, 120, application1); @@ -61,10 +62,10 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only ClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high", 15, 1, 1.2, 28.6, 28.6, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.deploy(application1, cluster1, scaledResources); - assertTrue("Cluster in flux -> No further change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + assertTrue("Cluster in flux -> No further change", tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); tester.deactivateRetired(application1, cluster1, scaledResources); @@ -73,19 +74,19 @@ public class AutoscalingTest { tester.clock().advance(Duration.ofMinutes(-10 * 5)); tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only assertTrue("Load change is large, but insufficient measurements for new config -> No change", - tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); tester.addCpuMeasurements(0.19f, 1f, 100, application1); tester.clock().advance(Duration.ofMinutes(-10 * 5)); tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - assertEquals("Load change is small -> No change", Optional.empty(), tester.autoscale(application1, cluster1.id(), min, max).target()); + assertEquals("Load change is small -> No change", Optional.empty(), tester.autoscale(application1, cluster1.id(), capacity).target()); tester.addCpuMeasurements(0.1f, 1f, 120, application1); tester.clock().advance(Duration.ofMinutes(-10 * 5)); tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling down to minimum since usage has gone down significantly", 7, 1, 1.0, 66.7, 66.7, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); var events = tester.nodeRepository().applications().get(application1).get().cluster(cluster1.id()).get().scalingEvents(); } @@ -96,6 +97,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -108,7 +110,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only ClusterResources scaledResources = tester.assertResources("Scaling up since cpu usage is too high", 7, 1, 2.5, 80.0, 80.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.deploy(application1, cluster1, scaledResources); tester.deactivateRetired(application1, cluster1, scaledResources); @@ -118,7 +120,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling down since cpu usage has gone down", 4, 1, 2.5, 68.6, 68.6, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -142,9 +144,10 @@ public class AutoscalingTest { new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)); + var capacity = Capacity.from(min, max); ClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high", 14, 1, 1.4, 30.8, 30.8, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); assertEquals("Disk speed from min/max is used", NodeResources.DiskSpeed.any, scaledResources.nodeResources().diskSpeed()); tester.deploy(application1, cluster1, scaledResources); @@ -164,6 +167,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(1, 10, 10, 1); var min = new ClusterResources( 2, 1, resources.with(NodeResources.DiskSpeed.any)); var max = new ClusterResources( 10, 1, resources.with(NodeResources.DiskSpeed.any)); + var capacity = Capacity.from(min, max); tester.deploy(application1, cluster1, Capacity.from(min, max)); // Redeployment without target: Uses current resource numbers with *requested* non-numbers (i.e disk-speed any) @@ -176,7 +180,7 @@ public class AutoscalingTest { // Autoscaling: Uses disk-speed any as well tester.clock().advance(Duration.ofDays(2)); tester.addCpuMeasurements(0.8f, 1f, 120, application1); - Autoscaler.Advice advice = tester.autoscale(application1, cluster1.id(), min, max); + Autoscaler.Advice advice = tester.autoscale(application1, cluster1.id(), capacity); assertEquals(NodeResources.DiskSpeed.any, advice.target().get().nodeResources().diskSpeed()); @@ -187,6 +191,7 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(6, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -200,7 +205,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up to limit since resource usage is too high", 6, 1, 2.4, 78.0, 79.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -208,6 +213,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 4, 1, new NodeResources(1.8, 7.4, 8.5, 1)); ClusterResources max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -218,7 +224,7 @@ public class AutoscalingTest { tester.addMeasurements(0.05f, 0.05f, 0.05f, 0, 120, application1); tester.assertResources("Scaling down to limit since resource usage is low", 4, 1, 1.8, 7.7, 10.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -226,6 +232,7 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(6, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, NodeResources.unspecified()); ClusterResources max = new ClusterResources( 6, 1, NodeResources.unspecified()); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -244,7 +251,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up to limit since resource usage is too high", 4, 1, defaultResources, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -252,6 +259,7 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(30.0, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(18, 6, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -264,7 +272,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up since resource usage is too high", 6, 6, 3.6, 8.0, 10.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -272,6 +280,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = min; + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources); ApplicationId application1 = tester.applicationId("application1"); @@ -281,13 +290,13 @@ public class AutoscalingTest { tester.deploy(application1, cluster1, 5, 1, resources); tester.clock().advance(Duration.ofDays(1)); tester.addCpuMeasurements(0.25f, 1f, 120, application1); - assertTrue(tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + assertTrue(tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); } @Test public void prefers_remote_disk_when_no_local_match() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, new NodeResources(3, 100, 50, 1)); + NodeResources resources = new NodeResources(3, 100, 50, 1); + ClusterResources min = new ClusterResources( 2, 1, resources); ClusterResources max = min; // AutoscalingTester hardcodes 3Gb memory overhead: Flavor localFlavor = new Flavor("local", new NodeResources(3, 97, 75, 1, DiskSpeed.fast, StorageType.local)); @@ -341,6 +350,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources(2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(5, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -350,7 +360,7 @@ public class AutoscalingTest { tester.deploy(application1, cluster1, 2, 1, resources); tester.addMeasurements(0.5f, 0.6f, 0.7f, 1, false, true, 120, application1); assertTrue("Not scaling up since nodes were measured while cluster was unstable", - tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); } @Test @@ -358,6 +368,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources(2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(5, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -367,7 +378,7 @@ public class AutoscalingTest { tester.deploy(application1, cluster1, 2, 1, resources); tester.addMeasurements(0.5f, 0.6f, 0.7f, 1, true, false, 120, application1); assertTrue("Not scaling up since nodes were measured while cluster was unstable", - tester.autoscale(application1, cluster1.id(), min, max).isEmpty()); + tester.autoscale(application1, cluster1.id(), capacity).isEmpty()); } @Test @@ -375,6 +386,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(20, 20, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -387,7 +399,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up since resource usage is too high", 7, 7, 2.5, 80.0, 80.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -395,6 +407,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -409,7 +422,7 @@ public class AutoscalingTest { t -> 1.0); tester.assertResources("Scaling up since resource usage is too high, changing to 1 group is cheaper", 8, 1, 2.6, 83.3, 83.3, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } /** Same as above but mostly write traffic, which favors smaller groups */ @@ -418,6 +431,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -432,14 +446,15 @@ public class AutoscalingTest { t -> 100.0); tester.assertResources("Scaling down since resource usage is too high, changing to 1 group is cheaper", 4, 1, 2.1, 83.3, 83.3, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test public void test_autoscaling_group_size() { NodeResources hostResources = new NodeResources(100, 1000, 1000, 100); - ClusterResources min = new ClusterResources( 3, 2, new NodeResources(1, 1, 1, 1)); + ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(30, 30, new NodeResources(100, 100, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -453,7 +468,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Increase group size to reduce memory load", 8, 2, 12.4, 96.2, 62.5, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -461,6 +476,7 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(6, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -473,7 +489,7 @@ public class AutoscalingTest { tester.addMemMeasurements(0.02f, 0.95f, 120, application1); tester.assertResources("Scaling down", 6, 1, 2.9, 4.0, 95.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -481,6 +497,7 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(6, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(hostResources); ApplicationId application1 = tester.applicationId("application1"); @@ -492,7 +509,7 @@ public class AutoscalingTest { tester.addMemMeasurements(0.02f, 0.95f, 120, application1); tester.clock().advance(Duration.ofMinutes(-10 * 5)); tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - assertTrue(tester.autoscale(application1, cluster1.id(), min, max).target().isEmpty()); + assertTrue(tester.autoscale(application1, cluster1.id(), capacity).target().isEmpty()); // Trying the same later causes autoscaling tester.clock().advance(Duration.ofDays(2)); @@ -501,7 +518,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling down", 6, 1, 1.4, 4.0, 95.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -509,9 +526,11 @@ public class AutoscalingTest { NodeResources hostResources = new NodeResources(60, 100, 1000, 10); ClusterResources min = new ClusterResources(2, 1, new NodeResources( 2, 20, 200, 1)); ClusterResources max = new ClusterResources(4, 1, new NodeResources(60, 100, 1000, 1)); + var capacity = Capacity.from(min, max); { // No memory tax - AutoscalingTester tester = new AutoscalingTester(hostResources, new OnlySubtractingWhenForecastingCalculator(0)); + AutoscalingTester tester = new AutoscalingTester(Environment.prod, hostResources, + new OnlySubtractingWhenForecastingCalculator(0)); ApplicationId application1 = tester.applicationId("app1"); ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); @@ -522,11 +541,12 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up", 4, 1, 6.7, 20.5, 200, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } { // 15 Gb memory tax - AutoscalingTester tester = new AutoscalingTester(hostResources, new OnlySubtractingWhenForecastingCalculator(15)); + AutoscalingTester tester = new AutoscalingTester(Environment.prod, hostResources, + new OnlySubtractingWhenForecastingCalculator(15)); ApplicationId application1 = tester.applicationId("app1"); ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); @@ -537,7 +557,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling up", 4, 1, 6.7, 35.5, 200, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } } @@ -545,6 +565,7 @@ public class AutoscalingTest { public void test_autoscaling_with_dynamic_provisioning() { ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); + var capacity = Capacity.from(min, max); List flavors = new ArrayList<>(); flavors.add(new Flavor("aws-xlarge", new NodeResources(3, 200, 100, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote))); flavors.add(new Flavor("aws-large", new NodeResources(3, 150, 100, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote))); @@ -567,7 +588,7 @@ public class AutoscalingTest { tester.addMemMeasurements(0.9f, 0.6f, 120, application1); ClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high.", 8, 1, 3, 83, 34.3, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.deploy(application1, cluster1, scaledResources); tester.deactivateRetired(application1, cluster1, scaledResources); @@ -578,7 +599,7 @@ public class AutoscalingTest { tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only tester.assertResources("Scaling down since resource usage has gone down", 5, 1, 3, 83, 36.0, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -586,6 +607,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 1, 1, resources); ClusterResources max = new ClusterResources(10, 1, resources); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -599,17 +621,17 @@ public class AutoscalingTest { // (no read share stored) tester.assertResources("Advice to scale up since we set aside for bcp by default", 7, 1, 3, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.storeReadShare(0.25, 0.5, application1); tester.assertResources("Half of global share is the same as the default assumption used above", 7, 1, 3, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.storeReadShare(0.5, 0.5, application1); tester.assertResources("Advice to scale down since we don't need room for bcp", 4, 1, 3, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @@ -620,6 +642,7 @@ public class AutoscalingTest { NodeResources maxResources = new NodeResources(10, 100, 100, 1); ClusterResources min = new ClusterResources(5, 1, minResources); ClusterResources max = new ClusterResources(5, 1, maxResources); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(maxResources.withVcpu(maxResources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -633,7 +656,7 @@ public class AutoscalingTest { // (no query rate data) tester.assertResources("Scale up since we assume we need 2x cpu for growth when no data scaling time data", 5, 1, 6.3, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.setScalingDuration(application1, cluster1.id(), Duration.ofMinutes(5)); tester.addQueryRateMeasurements(application1, cluster1.id(), @@ -643,7 +666,7 @@ public class AutoscalingTest { tester.addCpuMeasurements(0.25f, 1f, 100, application1); tester.assertResources("Scale down since observed growth is slower than scaling time", 5, 1, 3.4, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.clearQueryRateMeasurements(application1, cluster1.id()); @@ -655,7 +678,7 @@ public class AutoscalingTest { tester.addCpuMeasurements(0.25f, 1f, 100, application1); tester.assertResources("Scale up since observed growth is faster than scaling time", 5, 1, 10.0, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -665,6 +688,7 @@ public class AutoscalingTest { NodeResources maxResources = new NodeResources(10, 100, 100, 1); ClusterResources min = new ClusterResources(5, 1, minResources); ClusterResources max = new ClusterResources(5, 1, maxResources); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(maxResources.withVcpu(maxResources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); @@ -681,35 +705,35 @@ public class AutoscalingTest { tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t -> 10.0); tester.assertResources("Query and write load is equal -> scale up somewhat", 5, 1, 7.3, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.addCpuMeasurements(0.4f, 1f, 100, application1); tester.clock().advance(Duration.ofMinutes(-100 * 5)); tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 80.0 : 40.0, t -> 10.0); tester.assertResources("Query load is 4x write load -> scale up more", 5, 1, 9.5, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.addCpuMeasurements(0.3f, 1f, 100, application1); tester.clock().advance(Duration.ofMinutes(-100 * 5)); tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t -> 100.0); tester.assertResources("Write load is 10x query load -> scale down", 5, 1, 2.9, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.addCpuMeasurements(0.4f, 1f, 100, application1); tester.clock().advance(Duration.ofMinutes(-100 * 5)); tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t-> 0.0); tester.assertResources("Query only -> largest possible", 5, 1, 10.0, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); tester.addCpuMeasurements(0.4f, 1f, 100, application1); tester.clock().advance(Duration.ofMinutes(-100 * 5)); tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> 0.0, t -> 10.0); tester.assertResources("Write only -> smallest possible", 5, 1, 2.1, 100, 100, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); } @Test @@ -717,6 +741,7 @@ public class AutoscalingTest { NodeResources resources = new NodeResources(1, 4, 50, 1); ClusterResources min = new ClusterResources( 2, 1, resources); ClusterResources max = new ClusterResources(3, 1, resources); + var capacity = Capacity.from(min, max); AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); ApplicationId application1 = tester.applicationId("application1"); ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); @@ -728,7 +753,47 @@ public class AutoscalingTest { tester.assertResources("Advice to scale up since observed growth is much faster than scaling time", 3, 1, 1, 4, 50, - tester.autoscale(application1, cluster1.id(), min, max).target()); + tester.autoscale(application1, cluster1.id(), capacity).target()); + } + + @Test + public void test_autoscaling_in_dev() { + NodeResources resources = new NodeResources(1, 4, 50, 1); + ClusterResources min = new ClusterResources( 1, 1, resources); + ClusterResources max = new ClusterResources(3, 1, resources); + Capacity capacity = Capacity.from(min, max, false, true); + + AutoscalingTester tester = new AutoscalingTester(Environment.dev, resources.withVcpu(resources.vcpu() * 2)); + ApplicationId application1 = tester.applicationId("application1"); + ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + + tester.deploy(application1, cluster1, capacity); + tester.addQueryRateMeasurements(application1, cluster1.id(), + 500, t -> 100.0); + tester.addCpuMeasurements(1.0f, 1f, 10, application1); + assertTrue("Not attempting to scale up because policies dictate we'll only get one node", + tester.autoscale(application1, cluster1.id(), capacity).target().isEmpty()); + } + + /** Same setup as test_autoscaling_in_dev(), just with required = true */ + @Test + public void test_autoscaling_in_dev_with_required_resources() { + NodeResources resources = new NodeResources(1, 4, 50, 1); + ClusterResources min = new ClusterResources( 1, 1, resources); + ClusterResources max = new ClusterResources(3, 1, resources); + Capacity capacity = Capacity.from(min, max, true, true); + + AutoscalingTester tester = new AutoscalingTester(Environment.dev, resources.withVcpu(resources.vcpu() * 2)); + ApplicationId application1 = tester.applicationId("application1"); + ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + + tester.deploy(application1, cluster1, capacity); + tester.addQueryRateMeasurements(application1, cluster1.id(), + 500, t -> 100.0); + tester.addCpuMeasurements(1.0f, 1f, 10, application1); + tester.assertResources("We scale up even in dev because resources are required", + 3, 1, 1.0, 4, 50, + tester.autoscale(application1, cluster1.id(), capacity).target()); } /** 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 c45b6caf14c..8d59181a027 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 @@ -48,11 +48,15 @@ class AutoscalingTester { /** Creates an autoscaling tester with a single host type ready */ public AutoscalingTester(NodeResources hostResources) { - this(hostResources, null); + this(Environment.prod, hostResources); } - public AutoscalingTester(NodeResources hostResources, HostResourcesCalculator resourcesCalculator) { - this(new Zone(Environment.prod, RegionName.from("us-east")), List.of(new Flavor("hostFlavor", hostResources)), resourcesCalculator); + public AutoscalingTester(Environment environment, NodeResources hostResources) { + this(environment, hostResources, null); + } + + public AutoscalingTester(Environment environment, NodeResources hostResources, HostResourcesCalculator resourcesCalculator) { + this(new Zone(environment, RegionName.from("us-east")), List.of(new Flavor("hostFlavor", hostResources)), resourcesCalculator); provisioningTester.makeReadyNodes(20, "hostFlavor", NodeType.host, 8); provisioningTester.activateTenantHosts(); } @@ -251,6 +255,7 @@ class AutoscalingTester { cluster.exclusive(), cluster.minResources(), cluster.maxResources(), + cluster.required(), cluster.suggestedResources(), cluster.targetResources(), List.of(), // Remove scaling events @@ -295,10 +300,9 @@ class AutoscalingTester { ((MemoryMetricsDb)nodeMetricsDb()).clearClusterMetrics(application, cluster); } - public Autoscaler.Advice autoscale(ApplicationId applicationId, ClusterSpec.Id clusterId, - ClusterResources min, ClusterResources max) { + public Autoscaler.Advice autoscale(ApplicationId applicationId, ClusterSpec.Id clusterId, Capacity capacity) { Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId)) - .withCluster(clusterId, false, min, max); + .withCluster(clusterId, false, capacity); try (Mutex lock = nodeRepository().nodes().lock(applicationId)) { nodeRepository().applications().put(application, lock); } @@ -309,7 +313,7 @@ class AutoscalingTester { public Autoscaler.Advice suggest(ApplicationId applicationId, ClusterSpec.Id clusterId, ClusterResources min, ClusterResources max) { Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId)) - .withCluster(clusterId, false, min, max); + .withCluster(clusterId, false, Capacity.from(min, max)); try (Mutex lock = nodeRepository().nodes().lock(applicationId)) { nodeRepository().applications().put(application, lock); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java index 0e37d953d2d..bd7300ad6bf 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java @@ -2,12 +2,12 @@ package com.yahoo.vespa.hosted.provision.autoscale; 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.test.ManualClock; 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.Status; import org.junit.Test; @@ -15,7 +15,6 @@ import org.junit.Test; import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.function.IntFunction; import static org.junit.Assert.assertEquals; @@ -73,14 +72,9 @@ public class ClusterModelTest { } private Cluster cluster(NodeResources resources) { - return new Cluster(ClusterSpec.Id.from("test"), - false, - new ClusterResources(5, 1, resources), - new ClusterResources(5, 1, resources), - Optional.empty(), - Optional.empty(), - List.of(), - AutoscalingStatus.empty()); + return Cluster.create(ClusterSpec.Id.from("test"), + false, + Capacity.from(new ClusterResources(5, 1, resources))); } /** Creates the given number of measurements, spaced 5 minutes between, using the given function */ 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 7266da9ff46..e34f63d8062 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,6 +2,7 @@ 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; @@ -33,6 +34,7 @@ public class ApplicationSerializerTest { false, new ClusterResources( 8, 4, new NodeResources(1, 2, 3, 4)), new ClusterResources(12, 6, new NodeResources(3, 6, 21, 24)), + true, Optional.empty(), Optional.empty(), List.of(), @@ -42,6 +44,7 @@ public class ApplicationSerializerTest { true, new ClusterResources( 8, 4, minResources), new ClusterResources(14, 7, new NodeResources(3, 6, 21, 24)), + false, Optional.of(new Cluster.Suggestion(new ClusterResources(20, 10, new NodeResources(0.5, 4, 14, 16)), Instant.ofEpochMilli(1234L))), @@ -72,6 +75,7 @@ public class ApplicationSerializerTest { assertEquals(originalCluster.exclusive(), serializedCluster.exclusive()); assertEquals(originalCluster.minResources(), serializedCluster.minResources()); assertEquals(originalCluster.maxResources(), serializedCluster.maxResources()); + assertEquals(originalCluster.required(), serializedCluster.required()); assertEquals(originalCluster.suggestedResources(), serializedCluster.suggestedResources()); assertEquals(originalCluster.targetResources(), serializedCluster.targetResources()); assertEquals(originalCluster.scalingEvents(), serializedCluster.scalingEvents()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java index 20546cc5bd9..db165aae919 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java @@ -1015,10 +1015,10 @@ public class ProvisioningTest { allHosts.addAll(content1); Function capacity = count -> Capacity.from(new ClusterResources(count, 1, NodeResources.unspecified()), required, true); - int expectedContainer0Size = tester.capacityPolicies().decideSize(container0Size, capacity.apply(container0Size), containerCluster0, application); - int expectedContainer1Size = tester.capacityPolicies().decideSize(container1Size, capacity.apply(container1Size), containerCluster1, application); - int expectedContent0Size = tester.capacityPolicies().decideSize(content0Size, capacity.apply(content0Size), contentCluster0, application); - int expectedContent1Size = tester.capacityPolicies().decideSize(content1Size, capacity.apply(content1Size), contentCluster1, application); + int expectedContainer0Size = tester.decideSize(container0Size, capacity.apply(container0Size), containerCluster0, application); + int expectedContainer1Size = tester.decideSize(container1Size, capacity.apply(container1Size), containerCluster1, application); + int expectedContent0Size = tester.decideSize(content0Size, capacity.apply(content0Size), contentCluster0, application); + int expectedContent1Size = tester.decideSize(content1Size, capacity.apply(content1Size), contentCluster1, application); assertEquals("Hosts in each group cluster is disjunct and the total number of unretired nodes is correct", expectedContainer0Size + expectedContainer1Size + expectedContent0Size + expectedContent1Size, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java index 6d525762ecc..6ca93671087 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java @@ -152,6 +152,10 @@ public class ProvisioningTester { public NodeList getNodes(ApplicationId id, Node.State ... inState) { return nodeRepository.nodes().list(inState).owner(id); } public InMemoryFlagSource flagSource() { return (InMemoryFlagSource) nodeRepository.flagSource(); } + public int decideSize(int size, Capacity capacity, ClusterSpec cluster, ApplicationId application) { + return capacityPolicies.decideSize(size, capacity.isRequired(), capacity.canFail(), application.instance().isTester(), cluster); + } + public Node patchNode(Node node, UnaryOperator patcher) { return patchNodes(List.of(node), patcher).get(0); } -- cgit v1.2.3