diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2022-07-01 10:03:07 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-01 10:03:07 +0200 |
commit | 98627aeb25294a9c425e4f176238433d410ad71a (patch) | |
tree | f689a6e82b80e11885e0c4c0b4c8acfccbaac707 /controller-server | |
parent | f165fa8dfe653a8c3bacd9dffb94c807fc730993 (diff) | |
parent | 44a41591b7b800eea6f6c04c0d0f938376e8c49f (diff) |
Merge pull request #23296 from vespa-engine/mpolden/latest-deployed-pkg
Support retrieval of latest deployed package
Diffstat (limited to 'controller-server')
3 files changed, 49 insertions, 24 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 af8965bdeff..bdb68f655ff 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 @@ -33,6 +33,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * An application. Belongs to a {@link Tenant}, and may have multiple {@link Instance}s. @@ -161,7 +162,7 @@ public class Application { public ApplicationActivity activity() { return ApplicationActivity.from(instances.values().stream() .flatMap(instance -> instance.deployments().values().stream()) - .collect(Collectors.toUnmodifiableList())); + .toList()); } public Map<InstanceName, List<Deployment>> productionDeployments() { @@ -183,33 +184,44 @@ public class Application { .min(Comparator.naturalOrder()); } - /** - * Returns the oldest application version this has deployed in a permanent zone (not test or staging). - */ + /** Returns the oldest application version this has deployed in a permanent zone (not test or staging) */ public Optional<RevisionId> oldestDeployedRevision() { + return productionRevisions().min(Comparator.naturalOrder()); + } + + /** Returns the latest application version this has deployed in a permanent zone (not test or staging) */ + public Optional<RevisionId> latestDeployedRevision() { + return productionRevisions().max(Comparator.naturalOrder()); + } + + private Stream<RevisionId> productionRevisions() { return productionDeployments().values().stream().flatMap(List::stream) .map(Deployment::revision) - .filter(RevisionId::isProduction) - .min(Comparator.naturalOrder()); + .filter(RevisionId::isProduction); } /** Returns the total quota usage for this application, excluding temporary deployments */ public QuotaUsage quotaUsage() { return instances().values().stream() - .map(Instance::quotaUsage).reduce(QuotaUsage::add).orElse(QuotaUsage.none); + .map(Instance::quotaUsage) + .reduce(QuotaUsage::add) + .orElse(QuotaUsage.none); } /** Returns the total quota usage for manual deployments for this application */ public QuotaUsage manualQuotaUsage() { return instances().values().stream() - .map(Instance::manualQuotaUsage).reduce(QuotaUsage::add).orElse(QuotaUsage.none); + .map(Instance::manualQuotaUsage) + .reduce(QuotaUsage::add) + .orElse(QuotaUsage.none); } /** Returns the total quota usage for this application, excluding one specific deployment (and temporary deployments) */ public QuotaUsage quotaUsage(ApplicationId application, ZoneId zone) { return instances().values().stream() .map(instance -> instance.quotaUsageExcluding(application, zone)) - .reduce(QuotaUsage::add).orElse(QuotaUsage.none); + .reduce(QuotaUsage::add) + .orElse(QuotaUsage.none); } /** Returns the set of deploy keys for this application. */ 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 670cb775c69..cfb00db7b63 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 @@ -10,8 +10,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; -import com.yahoo.component.annotation.Inject; import com.yahoo.component.Version; +import com.yahoo.component.annotation.Inject; import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.ApplicationId; @@ -137,8 +137,6 @@ import java.security.PublicKey; import java.time.DayOfWeek; import java.time.Duration; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Base64; @@ -955,19 +953,24 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private HttpResponse applicationPackage(String tenantName, String applicationName, HttpRequest request) { TenantAndApplicationId tenantAndApplication = TenantAndApplicationId.from(tenantName, applicationName); - long build; - String parameter = request.getProperty("build"); - if (parameter != null) - try { - build = Validation.requireAtLeast(Long.parseLong(request.getProperty("build")), "build number", 1L); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid value for request parameter 'build'", e); - } - else { + final long build; + String requestedBuild = request.getProperty("build"); + if (requestedBuild != null) { + if (requestedBuild.equals("latestDeployed")) { + build = controller.applications().requireApplication(tenantAndApplication).latestDeployedRevision() + .map(RevisionId::number) + .orElseThrow(() -> new NotExistsException("no application package has been deployed in production for " + tenantAndApplication)); + } else { + try { + build = Validation.requireAtLeast(Long.parseLong(request.getProperty("build")), "build number", 1L); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid value for request parameter 'build'", e); + } + } + } else { build = controller.applications().requireApplication(tenantAndApplication).revisions().last() - .map(version -> version.id().number()) - .orElseThrow(() -> new NotExistsException("no application package has been submitted for " + tenantAndApplication)); + .map(version -> version.id().number()) + .orElseThrow(() -> new NotExistsException("no application package has been submitted for " + tenantAndApplication)); } RevisionId revision = RevisionId.forProduction(build); boolean tests = request.getBooleanProperty("tests"); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 2c0ab97c00e..2435bb2efb8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -705,6 +705,16 @@ public class ApplicationApiTest extends ControllerContainerTest { .userIdentity(USER_ID), "{\"json\":\"thank you very much\"}"); + // GET application package which has been deployed to production + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET) + .properties(Map.of("build", "latestDeployed")) + .userIdentity(HOSTED_VESPA_OPERATOR), + (response) -> { + assertEquals("attachment; filename=\"tenant1.application1-build1.zip\"", response.getHeaders().getFirst("Content-Disposition")); + assertArrayEquals(applicationPackageInstance1.zippedContent(), response.getBody()); + }, + 200); + // DELETE application with active deployments fails tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE) .userIdentity(USER_ID) |