aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2020-02-14 12:38:05 +0100
committerJon Marius Venstad <venstad@gmail.com>2020-02-14 13:21:26 +0100
commit762244b91fa0b5b035d217bfb44cc6533adeed32 (patch)
treedc9d79c171eb3d1b6e2ae650f8fc805c9b7cf4e4 /controller-server
parentfbaec83f26263d16265f39a6057fe80ae3fae6f3 (diff)
Allow production-only filter on recursive listings
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java57
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-instances.json13
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json2
6 files changed, 73 insertions, 18 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 e37d6accd89..9815fbca093 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
@@ -107,6 +107,12 @@ public class Application {
/** Returns the instances of this application */
public Map<InstanceName, Instance> instances() { return instances; }
+ /** Returns the instances of this application which are defined in its deployment spec. */
+ public Map<InstanceName, Instance> productionInstances() {
+ return deploymentSpec.instanceNames().stream()
+ .collect(Collectors.toUnmodifiableMap(Function.identity(), instances::get));
+ }
+
/** Returns the instance with the given name, if it exists. */
public Optional<Instance> get(InstanceName instance) { return Optional.ofNullable(instances.get(instance)); }
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 c42bbf7c1b7..e24a5b92b90 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
@@ -225,7 +225,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri());
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri().normalize());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/test-config")) return testConfig(appIdFromPath(path), jobTypeFromPath(path));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/run/{number}")) return JobControllerApiHandlerHelper.runDetailsResponse(controller.jobController(), runIdFromPath(path), request.getProperty("after"));
@@ -733,7 +733,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
application.majorVersion().ifPresent(majorVersion -> object.setLong("majorVersion", majorVersion));
Cursor instancesArray = object.setArray("instances");
- for (Instance instance : application.instances().values())
+ for (Instance instance : showOnlyProductionInstances(request) ? application.productionInstances().values()
+ : application.instances().values())
toSlime(instancesArray.addObject(), status, instance, application.deploymentSpec(), request);
application.deployKeys().stream().map(KeyUtils::toPem).forEach(object.setArray("pemDeployKeys")::addString);
@@ -1779,7 +1780,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Cursor applicationArray = object.setArray("applications");
for (Application application : applications) {
DeploymentStatus status = controller.jobController().deploymentStatus(application);
- for (Instance instance : application.instances().values())
+ for (Instance instance : showOnlyProductionInstances(request) ? application.productionInstances().values()
+ : application.instances().values())
if (recurseOverApplications(request))
toSlime(applicationArray.addObject(), instance, status, request);
else
@@ -2011,6 +2013,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return ImmutableSet.of("all", "true", "deployment").contains(request.getProperty("recursive"));
}
+ private static boolean showOnlyProductionInstances(HttpRequest request) {
+ return "true".equals(request.getProperty("production"));
+ }
+
private static String tenantType(Tenant tenant) {
switch (tenant.type()) {
case user: return "USER";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 0b96d8a7307..17190be8f0f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -69,7 +69,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.Step.installReal;
import static com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence.broken;
import static com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence.high;
import static com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence.normal;
-import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
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 fb7d54759f8..9e6d0bc1111 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
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.MultiPartStreamer;
import ai.vespa.hosted.api.Signatures;
+import com.yahoo.application.container.handler.Headers;
import com.yahoo.application.container.handler.Request;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.ValidationId;
@@ -75,6 +76,7 @@ import org.junit.Test;
import java.io.File;
import java.math.BigDecimal;
import java.net.URI;
+import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
@@ -90,12 +92,16 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import static com.yahoo.application.container.handler.Request.Method.DELETE;
import static com.yahoo.application.container.handler.Request.Method.GET;
import static com.yahoo.application.container.handler.Request.Method.PATCH;
import static com.yahoo.application.container.handler.Request.Method.POST;
import static com.yahoo.application.container.handler.Request.Method.PUT;
+import static java.net.URLEncoder.encode;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -235,6 +241,18 @@ public class ApplicationApiTest extends ControllerContainerTest {
// GET tenant applications (instances of "application1" only)
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/", GET).userIdentity(USER_ID),
new File("instance-list.json"));
+ // GET at a tenant, with "&recursive=true&production=true", recurses over no instances yet, as they are not in deployment spec.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/", GET)
+ .userIdentity(USER_ID)
+ .properties(Map.of("recursive", "true",
+ "production", "true")),
+ new File("tenant-without-applications.json"));
+ // GET at an application, with "&recursive=true&production=true", recurses over no instances yet, as they are not in deployment spec.
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET)
+ .userIdentity(USER_ID)
+ .properties(Map.of("recursive", "true",
+ "production", "true")),
+ new File("application-without-instances.json"));
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
@@ -482,7 +500,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
assertTrue("Action is logged to audit log",
tester.controller().auditLogger().readLog().entries().stream()
- .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin")));
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin?")));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", GET)
@@ -558,7 +576,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"message\":\"Requested restart of tenant1.application1.instance1 in dev.us-central-1\"}");
// POST a 'restart application' command with a host filter (other filters not supported yet)
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart?hostname=node-1-tenant-host-prod.us-central-1", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/restart", POST)
+ .properties(Map.of("hostname", "node-1-tenant-host-prod.us-central-1"))
.screwdriverIdentity(SCREWDRIVER_ID),
"{\"message\":\"Requested restart of tenant1.application1.instance1 in prod.us-central-1\"}", 200);
@@ -662,7 +681,9 @@ public class ApplicationApiTest extends ControllerContainerTest {
200);
// GET application package for previous build
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package?build=1", GET).userIdentity(HOSTED_VESPA_OPERATOR),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET)
+ .properties(Map.of("build", "1"))
+ .userIdentity(HOSTED_VESPA_OPERATOR),
(response) -> {
assertEquals("attachment; filename=\"tenant1.application1-build1.zip\"", response.getHeaders().getFirst("Content-Disposition"));
assertArrayEquals(applicationPackageInstance1.zippedContent(), response.getBody());
@@ -862,19 +883,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
400);
// GET global rotation status for us-west-1 in default endpoint
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation?endpointId=default", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation", GET)
+ .properties(Map.of("endpointId", "default"))
.userIdentity(USER_ID),
"{\"bcpStatus\":{\"rotationStatus\":\"IN\"}}",
200);
// GET global rotation status for us-west-1 in eu endpoint
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation?endpointId=eu", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/global-rotation", GET)
+ .properties(Map.of("endpointId", "eu"))
.userIdentity(USER_ID),
"{\"bcpStatus\":{\"rotationStatus\":\"UNKNOWN\"}}",
200);
// GET global rotation status for eu-west-1 in eu endpoint
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/eu-west-1/global-rotation?endpointId=eu", GET)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/eu-west-1/global-rotation", GET)
+ .properties(Map.of("endpointId", "eu"))
.userIdentity(USER_ID),
"{\"bcpStatus\":{\"rotationStatus\":\"IN\"}}",
200);
@@ -1089,12 +1113,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
404);
// GET non-existent application package of specific build
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package?build=42", GET).userIdentity(HOSTED_VESPA_OPERATOR),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET)
+ .properties(Map.of("build", "42"))
+ .userIdentity(HOSTED_VESPA_OPERATOR),
"{\"error-code\":\"NOT_FOUND\",\"message\":\"No application package found for 'tenant1.application1' with build number 42\"}",
404);
// GET non-existent application package of invalid build
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package?build=foobar", GET).userIdentity(HOSTED_VESPA_OPERATOR),
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET)
+ .properties(Map.of("build", "foobar"))
+ .userIdentity(HOSTED_VESPA_OPERATOR),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"Invalid build number: For input string: \\\"foobar\\\"\"}",
400);
@@ -1628,7 +1656,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
private OktaAccessToken oktaAccessToken;
private String contentType = "application/json";
private Map<String, List<String>> headers = new HashMap<>();
- private String recursive;
+ private Map<String, String> properties = new HashMap<>();
private RequestBuilder(String path, Request.Method method) {
this.path = path;
@@ -1636,7 +1664,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private RequestBuilder data(byte[] data) { this.data = data; return this; }
- private RequestBuilder data(String data) { return data(data.getBytes(StandardCharsets.UTF_8)); }
+ private RequestBuilder data(String data) { return data(data.getBytes(UTF_8)); }
private RequestBuilder data(MultiPartStreamer streamer) {
return Exceptions.uncheck(() -> data(streamer.data().readAllBytes()).contentType(streamer.contentType()));
}
@@ -1646,7 +1674,8 @@ public class ApplicationApiTest extends ControllerContainerTest {
private RequestBuilder oktaIdentityToken(OktaIdentityToken oktaIdentityToken) { this.oktaIdentityToken = oktaIdentityToken; return this; }
private RequestBuilder oktaAccessToken(OktaAccessToken oktaAccessToken) { this.oktaAccessToken = oktaAccessToken; return this; }
private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; }
- private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; }
+ private RequestBuilder recursive(String recursive) {return properties(Map.of("recursive", recursive)); }
+ private RequestBuilder properties(Map<String, String> properties) { this.properties.putAll(properties); return this; }
private RequestBuilder header(String name, String value) {
this.headers.putIfAbsent(name, new ArrayList<>());
this.headers.get(name).add(value);
@@ -1656,11 +1685,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
@Override
public Request get() {
Request request = new Request("http://localhost:8080" + path +
- // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters
- (recursive == null ? "" : "?recursive=" + recursive),
+ properties.entrySet().stream()
+ .map(entry -> encode(entry.getKey(), UTF_8) + "=" + encode(entry.getValue(), UTF_8))
+ .collect(joining("&", "?", "")),
data, method);
request.getHeaders().addAll(headers);
request.getHeaders().put("Content-Type", contentType);
+ // user and domain parameters are translated to a Principal by MockAuthorizer as we do not run HTTP filters
if (identity != null) {
addIdentityToRequest(request, identity);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-instances.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-instances.json
new file mode 100644
index 00000000000..411f9074582
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-instances.json
@@ -0,0 +1,13 @@
+{
+ "tenant": "tenant1",
+ "application": "application1",
+ "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/job/",
+ "compileVersion": "6.1.0",
+ "instances": [],
+ "pemDeployKeys": [],
+ "metrics": {
+ "queryServiceQuality": 0.0,
+ "writeServiceQuality": 0.0
+ },
+ "activity": {}
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
index 60b7c568b19..d9d229ecee3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json
@@ -335,7 +335,7 @@
"runs": [
{
"id": 1,
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/run/run 1 of dev-us-east-1 for tenant1.application1.instance1",
+ "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job?/run/run 1 of dev-us-east-1 for tenant1.application1.instance1",
"start": "(ignore)",
"end": "(ignore)",
"status": "success",