diff options
author | andreer <andreer@verizonmedia.com> | 2020-10-25 20:09:27 +0100 |
---|---|---|
committer | andreer <andreer@verizonmedia.com> | 2020-10-25 20:09:27 +0100 |
commit | cb1f0e479fb23add4499b712cbd2db42c5453e8b (patch) | |
tree | 4de61209990d432eedcdf026a0f587d72924d21d | |
parent | 4fc57f5fca98a9a0f879c6afbfffd8a8c3ce8b39 (diff) |
divide application quota among deployments
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 } } |