summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2022-06-30 15:49:03 +0200
committerMartin Polden <mpolden@mpolden.no>2022-06-30 16:02:30 +0200
commitc30c1ef77a330c8127f407947fb9cbff34404a0a (patch)
tree090de47049c1885718863c58027c87de4480d3b2 /controller-server
parent63d415951811b71dc94dec840673160f76e1a0a0 (diff)
Support retrieval of latest deployed package in API
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java10
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)