aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandreer <andreer@verizonmedia.com>2020-10-25 20:09:27 +0100
committerandreer <andreer@verizonmedia.com>2020-10-25 20:09:27 +0100
commitcb1f0e479fb23add4499b712cbd2db42c5453e8b (patch)
tree4de61209990d432eedcdf026a0f587d72924d21d
parent4fc57f5fca98a9a0f879c6afbfffd8a8c3ce8b39 (diff)
divide application quota among deployments
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java51
4 files changed, 64 insertions, 11 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index 7700e35d0af..84ca2fb1a8e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -182,8 +182,14 @@ public class Application {
.min(Comparator.naturalOrder());
}
+ /** Returns the total quota usage for this application */
+ public QuotaUsage quotaUsage() {
+ return instances().values().stream()
+ .map(Instance::quotaUsage).reduce(QuotaUsage::add).orElse(QuotaUsage.none);
+ }
+
/** Returns the total quota usage for this application, excluding one specific deployment */
- public QuotaUsage quotaUsageExcluding(ApplicationId application, ZoneId zone) {
+ public QuotaUsage quotaUsage(ApplicationId application, ZoneId zone) {
return instances().values().stream()
.map(instance -> instance.quotaUsageExcluding(application, zone))
.reduce(QuotaUsage::add).orElse(QuotaUsage.none);
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 728ed47187a..216ed6d4624 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
@@ -534,8 +534,8 @@ public class ApplicationController {
clock.instant(),
applicationPackage.metaDataZip());
- Quota deploymentQuota = DeploymentQuotaCalculator.calculate(
- billingController.getQuota(application.tenant()), asList(application.tenant()), application, zone);
+ Quota deploymentQuota = DeploymentQuotaCalculator.calculate(billingController.getQuota(application.tenant()),
+ asList(application.tenant()), application, zone, applicationPackage.deploymentSpec());
ConfigServer.PreparedApplication preparedApplication =
configServer.deploy(new DeploymentData(application, zone, applicationPackage.zippedContent(), platform,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
index cfed19bc2a8..dcfc1cbc606 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
@@ -166,12 +166,17 @@ public class Instance {
return change;
}
- /** Returns the total quota usage for this instance, excluding one zone */
+ /** Returns the total quota usage for this instance **/
+ public QuotaUsage quotaUsage() {
+ return deployments.values().stream()
+ .map(Deployment::quota).reduce(QuotaUsage::add).orElse(QuotaUsage.none);
+ }
+
+ /** Returns the total quota usage for this instance, excluding one deployment */
public QuotaUsage quotaUsageExcluding(ApplicationId application, ZoneId zone) {
return deployments.values().stream()
.filter(d -> !(application.equals(id) && d.zone().equals(zone)))
- .map(Deployment::quota)
- .reduce(QuotaUsage::add).orElse(QuotaUsage.none);
+ .map(Deployment::quota).reduce(QuotaUsage::add).orElse(QuotaUsage.none);
}
@Override
@@ -193,5 +198,4 @@ public class Instance {
public String toString() {
return "application '" + id + "'";
}
-
}
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 039a186b9d5..939aec5116f 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,20 +1,63 @@
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.Environment;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.List;
+/** Calculates the quota to allocate to a deployment. */
public class DeploymentQuotaCalculator {
- public static Quota calculate(Quota tenantQuota, List<Application> applications, ApplicationId application, ZoneId zone) {
+ public static Quota calculate(Quota tenantQuota,
+ List<Application> tenantApps,
+ ApplicationId deployingApp, ZoneId deployingZone,
+ DeploymentSpec deploymentSpec) {
- var tenantUsage = applications.stream()
- .map(a -> a.quotaUsageExcluding(application, zone))
+ if (tenantQuota.budget().isEmpty()) return tenantQuota; // Shortcut if there is no budget limit to care about.
+
+ if (deployingZone.environment().isProduction()) return probablyEnoughForAll(tenantQuota, tenantApps, deployingApp, deploymentSpec);
+
+ return getMaximumAllowedQuota(tenantQuota, tenantApps, deployingApp, deployingZone);
+ }
+
+ /** Just get the maximum quota we are allowed to use. */
+ private static Quota getMaximumAllowedQuota(Quota tenantQuota, List<Application> applications,
+ ApplicationId application, ZoneId zone) {
+ var usageOutsideDeployment = applications.stream()
+ .map(app -> app.quotaUsage(application, zone))
.reduce(QuotaUsage::add).orElse(QuotaUsage.none);
+ return tenantQuota.subtractUsage(usageOutsideDeployment.rate());
+ }
+
+ /**
+ * We want to avoid applying a resource change to an instance in production when it seems likely
+ * that there will not be enough quota to apply this change to _all_ production instances.
+ * <p>
+ * To achieve this, we must make the assumption that all production instances will use
+ * the same amount of resources, and so equally divide the quota among them.
+ */
+ private static Quota probablyEnoughForAll(Quota tenantQuota, List<Application> tenantApps,
+ ApplicationId application, DeploymentSpec deploymentSpec) {
+
+ TenantAndApplicationId deployingApp = TenantAndApplicationId.from(application);
+
+ var usageOutsideApplication = tenantApps.stream()
+ .filter(app -> !app.id().equals(deployingApp))
+ .map(Application::quotaUsage).reduce(QuotaUsage::add).orElse(QuotaUsage.none);
+
+ long productionInstances = deploymentSpec.instances().stream()
+ .filter(instance -> instance.concerns(Environment.prod))
+ .count();
- return tenantQuota.subtractUsage(tenantUsage.rate());
+ return tenantQuota.withBudget(
+ tenantQuota.subtractUsage(usageOutsideApplication.rate())
+ .budget().get().divide(BigDecimal.valueOf(productionInstances),
+ 5, RoundingMode.HALF_UP)); // 1/1000th of a cent should be accurate enough
}
}