aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2020-10-28 16:59:28 +0100
committerGitHub <noreply@github.com>2020-10-28 16:59:28 +0100
commite445b848262442f0e5f96ad36d3fcd22b0b78bf9 (patch)
treeb9a369460e8e8460d8157a17b832ee135484d141 /controller-server
parent3e9c8d6b0e88d9bdebf4b25f6c2a5f9a5ae5cae3 (diff)
parent66fee91d625e1afb0f8508678d537a48a5c46564 (diff)
Merge pull request #15063 from vespa-engine/ogronnedby/dont-trust-max-resource-use
Compare .max() and .current() when calculating use
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json156
4 files changed, 201 insertions, 7 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index a6b3d2e43c1..a97bf55e17d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -368,12 +369,8 @@ public class ApplicationController {
}
private QuotaUsage deploymentQuotaUsage(ZoneId zoneId, ApplicationId applicationId) {
- var quotaUsage = configServer.nodeRepository().getApplication(zoneId, applicationId)
- .clusters().values().stream()
- .map(Cluster::max)
- .mapToDouble(max -> max.nodes() * max.nodeResources().cost())
- .sum();
- return QuotaUsage.create(quotaUsage);
+ var application = configServer.nodeRepository().getApplication(zoneId, applicationId);
+ return DeploymentQuotaCalculator.calculateQuotaUsage(application);
}
private ApplicationPackage getApplicationPackage(ApplicationId application, ZoneId zone, ApplicationVersion revision) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
index 82d83ea3585..4bef2ef7648 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
@@ -1,7 +1,9 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
@@ -10,7 +12,12 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
-/** Calculates the quota to allocate to a deployment. */
+/**
+ * Calculates the quota to allocate to a deployment.
+ *
+ * @author ogronnesby
+ * @author andreer
+ */
public class DeploymentQuotaCalculator {
public static Quota calculate(Quota tenantQuota,
@@ -25,6 +32,23 @@ public class DeploymentQuotaCalculator {
return getMaximumAllowedQuota(tenantQuota, tenantApps, deployingApp, deployingZone);
}
+ public static QuotaUsage calculateQuotaUsage(com.yahoo.vespa.hosted.controller.api.integration.configserver.Application application) {
+ // the .max() resources are only specified when the user has specified a max. to make sure we enforce quotas
+ // correctly we retrieve the maximum of .current() and .max() - otherwise we would keep adding 0s for those
+ // that are not using autoscaling.
+ var quotaUsageRate = application.clusters().values().stream()
+ .map(cluster -> largestQuotaUsage(cluster.current(), cluster.max()))
+ .mapToDouble(resources -> resources.nodes() * resources.nodeResources().cost())
+ .sum();
+ return QuotaUsage.create(quotaUsageRate);
+ }
+
+ private static ClusterResources largestQuotaUsage(ClusterResources a, ClusterResources b) {
+ var usageA = a.nodes() * a.nodeResources().cost();
+ var usageB = b.nodes() * b.nodeResources().cost();
+ return usageA < usageB ? b : a;
+ }
+
/** Just get the maximum quota we are allowed to use. */
private static Quota getMaximumAllowedQuota(Quota tenantQuota, List<Application> applications,
ApplicationId application, ZoneId zone) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
index 675cb2f3f76..fd719ab4619 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
@@ -1,11 +1,20 @@
package com.yahoo.vespa.hosted.controller.application;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.io.IOUtils;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
+import com.yahoo.vespa.hosted.controller.api.integration.noderepository.ApplicationData;
import org.junit.Test;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.List;
import static org.junit.Assert.*;
@@ -48,4 +57,12 @@ public class DeploymentQuotaCalculatorTest {
assertEquals(calculated.budget().get().doubleValue(), 0, 1e-5);
}
+ @Test
+ public void using_highest_resource_use() throws IOException, URISyntaxException {
+ var content = new String(Files.readAllBytes(Paths.get("src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json")));
+ var mapper = new ObjectMapper();
+ var application = mapper.readValue(content, ApplicationData.class).toApplication();
+ var usage = DeploymentQuotaCalculator.calculateQuotaUsage(application);
+ assertEquals(1.164, usage.rate(), 0.001);
+ }
} \ No newline at end of file
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
new file mode 100644
index 00000000000..ccfb6af1635
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/response/application.json
@@ -0,0 +1,156 @@
+{
+ "url": "...",
+ "id": "vespa.album-recommendation.default",
+ "clusters": {
+ "default": {
+ "min": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "max": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "current": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 8.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "remote"
+ }
+ },
+ "suggested": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 8.0,
+ "diskGb": 75.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "local"
+ }
+ }
+ },
+ "logserver": {
+ "min": {
+ "nodes": 1,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "max": {
+ "nodes": 1,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "current": {
+ "nodes": 1,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.5,
+ "memoryGb": 4.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "remote"
+ }
+ },
+ "suggested": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 4.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "local"
+ }
+ }
+ },
+ "music": {
+ "min": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "max": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 0.0,
+ "memoryGb": 0.0,
+ "diskGb": 0.0,
+ "bandwidthGbps": 0.0,
+ "diskSpeed": "fast",
+ "storageType": "any"
+ }
+ },
+ "current": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 8.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "remote"
+ }
+ },
+ "suggested": {
+ "nodes": 2,
+ "groups": 1,
+ "resources": {
+ "vcpu": 2.0,
+ "memoryGb": 4.0,
+ "diskGb": 50.0,
+ "bandwidthGbps": 0.3,
+ "diskSpeed": "fast",
+ "storageType": "local"
+ }
+ }
+ }
+ }
+} \ No newline at end of file