summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2020-02-17 12:21:08 +0100
committerGitHub <noreply@github.com>2020-02-17 12:21:08 +0100
commitb1c702d576acc700d2c47bd7dd84d222cd3b8e6e (patch)
tree51cb16d66482f71b82db08fd5f4f7b43166a8923
parent8a4d28d0f50dd6e9bad197d524cefd9dfc594e5c (diff)
parent488a225e8e479ac23a562b5389756d6ff2711c5f (diff)
Merge pull request #12213 from vespa-engine/mpolden/discoverable-routing-v1
Make /routing/v1 discoverable
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/ResourceResponse.java39
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java205
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/testdata/application-without-project-id.json19
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json315
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json153
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java82
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/application.json7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/environment.json43
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/instance.json10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/root.json10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/tenant.json7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/application.json22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/environment.json108
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/tenant.json40
15 files changed, 512 insertions, 552 deletions
diff --git a/container-core/src/main/java/com/yahoo/restapi/ResourceResponse.java b/container-core/src/main/java/com/yahoo/restapi/ResourceResponse.java
index ff301d44798..0188136addb 100644
--- a/container-core/src/main/java/com/yahoo/restapi/ResourceResponse.java
+++ b/container-core/src/main/java/com/yahoo/restapi/ResourceResponse.java
@@ -1,46 +1,41 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
import com.yahoo.container.jdisc.HttpRequest;
-import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.JsonFormat;
import com.yahoo.slime.Slime;
-import java.io.IOException;
-import java.io.OutputStream;
import java.net.URI;
+import java.util.List;
/**
* Returns a response containing an array of links to sub-resources
*
* @author bratseth
*/
-public class ResourceResponse extends HttpResponse {
+public class ResourceResponse extends SlimeJsonResponse {
- private final Slime slime = new Slime();
+ public ResourceResponse(URI parentUrl, List<String> subResources) {
+ super(200, toSlime(parentUrl, subResources));
+ }
public ResourceResponse(URI parentUrl, String ... subResources) {
- super(200);
- Cursor resourceArray = slime.setObject().setArray("resources");
- for (String subResource : subResources) {
- Cursor resourceEntry = resourceArray.addObject();
- resourceEntry.setString("url", new Uri(parentUrl).append(subResource)
- .withTrailingSlash()
- .toString());
- }
+ this(parentUrl, List.of(subResources));
}
public ResourceResponse(HttpRequest request, String ... subResources) {
this(request.getUri(), subResources);
}
- @Override
- public void render(OutputStream stream) throws IOException {
- new JsonFormat(true).encode(stream, slime);
+ private static Slime toSlime(URI parentUrl, List<String> subResources) {
+ var slime = new Slime();
+ var resourceArray = slime.setObject().setArray("resources");
+ for (var subResource : subResources) {
+ var resourceEntry = resourceArray.addObject();
+ resourceEntry.setString("url", new Uri(parentUrl).append(subResource)
+ .withTrailingSlash()
+ .toString());
+ }
+ return slime;
}
- @Override
- public String getContentType() { return "application/json"; }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
index 1815628a1ee..673cb7e82e9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
@@ -2,6 +2,9 @@
package com.yahoo.vespa.hosted.controller.restapi.routing;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
@@ -9,21 +12,27 @@ import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.MessageResponse;
import com.yahoo.restapi.Path;
+import com.yahoo.restapi.ResourceResponse;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.yolean.Exceptions;
+import java.net.URI;
import java.time.Instant;
+import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
+import java.util.stream.Collectors;
/**
* This implements the /routing/v1 API, which provides operator with global routing control at both zone- and
@@ -45,7 +54,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
try {
var path = new Path(request.getUri());
switch (request.getMethod()) {
- case GET: return get(path);
+ case GET: return get(path, request);
case POST: return post(path);
case DELETE: return delete(path);
default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
@@ -70,12 +79,92 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
return ErrorResponse.notFoundError("Nothing at " + path);
}
- private HttpResponse get(Path path) {
- if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deploymentStatus(path);
- if (path.matches("/routing/v1/status/environment/{environment}/region/{region}")) return zoneStatus(path);
+ private HttpResponse get(Path path, HttpRequest request) {
+ if (path.matches("/routing/v1/")) return status(request.getUri());
+ if (path.matches("/routing/v1/status/tenant/{tenant}")) return tenant(path, request);
+ if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}")) return application(path, request);
+ if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path, request);
+ if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deployment(path);
+ if (path.matches("/routing/v1/status/environment")) return environment(request);
+ if (path.matches("/routing/v1/status/environment/{environment}/region/{region}")) return zone(path);
return ErrorResponse.notFoundError("Nothing at " + path);
}
+ private HttpResponse environment(HttpRequest request) {
+ var zones = controller.zoneRegistry().zones().all().ids();
+ if (isRecursive(request)) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ var zonesArray = root.setArray("zones");
+ for (var zone : zones) {
+ toSlime(zone, zonesArray.addObject());
+ }
+ return new SlimeJsonResponse(slime);
+ }
+ var resources = controller.zoneRegistry().zones().all().ids().stream()
+ .map(zone -> zone.environment().value() +
+ "/region/" + zone.region().value())
+ .sorted()
+ .collect(Collectors.toList());
+ return new ResourceResponse(request.getUri(), resources);
+ }
+
+ private HttpResponse status(URI requestUrl) {
+ return new ResourceResponse(requestUrl, "status/tenant", "status/environment");
+ }
+
+ private HttpResponse tenant(Path path, HttpRequest request) {
+ var tenantName = tenantFrom(path);
+ if (isRecursive(request)) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ toSlime(controller.applications().asList(tenantName), null, null, root);
+ return new SlimeJsonResponse(slime);
+ }
+ var resources = controller.applications().asList(tenantName).stream()
+ .map(Application::id)
+ .map(TenantAndApplicationId::application)
+ .map(ApplicationName::value)
+ .map(application -> "application/" + application)
+ .sorted()
+ .collect(Collectors.toList());
+ return new ResourceResponse(request.getUri(), resources);
+ }
+
+ private HttpResponse application(Path path, HttpRequest request) {
+ var tenantAndApplicationId = tenantAndApplicationIdFrom(path);
+ if (isRecursive(request)) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ toSlime(List.of(controller.applications().requireApplication(tenantAndApplicationId)), null,
+ null, root);
+ return new SlimeJsonResponse(slime);
+ }
+ var resources = controller.applications().requireApplication(tenantAndApplicationId).instances().keySet().stream()
+ .map(InstanceName::value)
+ .map(instance -> "instance/" + instance)
+ .sorted()
+ .collect(Collectors.toList());
+ return new ResourceResponse(request.getUri(), resources);
+ }
+
+ private HttpResponse instance(Path path, HttpRequest request) {
+ var instanceId = instanceFrom(path);
+ if (isRecursive(request)) {
+ var slime = new Slime();
+ var root = slime.setObject();
+ toSlime(List.of(controller.applications().requireApplication(TenantAndApplicationId.from(instanceId))),
+ instanceId, null, root);
+ return new SlimeJsonResponse(slime);
+ }
+ var resources = controller.applications().requireInstance(instanceId).deployments().keySet().stream()
+ .map(zone -> "environment/" + zone.environment().value() +
+ "/region/" + zone.region().value())
+ .sorted()
+ .collect(Collectors.toList());
+ return new ResourceResponse(request.getUri(), resources);
+ }
+
private HttpResponse setZoneStatus(Path path, boolean in) {
var zone = zoneFrom(path);
if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) {
@@ -88,21 +177,25 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
(in ? "IN" : "OUT"));
}
- private HttpResponse zoneStatus(Path path) {
+ private HttpResponse zone(Path path) {
var zone = zoneFrom(path);
var slime = new Slime();
var root = slime.setObject();
+ toSlime(zone, root);
+ return new SlimeJsonResponse(slime);
+ }
+
+ private void toSlime(ZoneId zone, Cursor zoneObject) {
if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) {
var zonePolicy = controller.routingController().policies().get(zone);
- zoneStatusToSlime(root, zonePolicy.zone(), zonePolicy.globalRouting(), RoutingMethod.exclusive);
+ zoneStatusToSlime(zoneObject, zonePolicy.zone(), zonePolicy.globalRouting(), RoutingMethod.exclusive);
} else {
// Rotation status per zone only exposes in/out status, no agent or time of change.
var in = controller.serviceRegistry().configServer().getGlobalRotationStatus(zone);
var globalRouting = new GlobalRouting(in ? GlobalRouting.Status.in : GlobalRouting.Status.out,
GlobalRouting.Agent.operator, Instant.EPOCH);
- zoneStatusToSlime(root, zone, globalRouting, RoutingMethod.shared);
+ zoneStatusToSlime(zoneObject, zone, globalRouting, RoutingMethod.shared);
}
- return new SlimeJsonResponse(slime);
}
private HttpResponse setDeploymentStatus(Path path, boolean in) {
@@ -124,41 +217,56 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
return new MessageResponse("Set global routing status for " + deployment + " to " + (in ? "IN" : "OUT"));
}
- private HttpResponse deploymentStatus(Path path) {
- var deployment = deploymentFrom(path);
- var instance = controller.applications().requireInstance(deployment.applicationId());
+ private HttpResponse deployment(Path path) {
var slime = new Slime();
- var deploymentsObject = slime.setObject().setArray("deployments");
+ var root = slime.setObject();
+ var deploymentId = deploymentFrom(path);
+ var application = controller.applications().requireApplication(TenantAndApplicationId.from(deploymentId.applicationId()));
+ toSlime(List.of(application), deploymentId.applicationId(), deploymentId.zoneId(), root);
+ return new SlimeJsonResponse(slime);
+ }
- // Include status from rotation
- if (rotationCanRouteTo(deployment.zoneId(), instance)) {
- var rotationStatus = controller.routingController().globalRotationStatus(deployment);
- // Status is equal across all global endpoints, as the status is per deployment, not per endpoint.
- var endpointStatus = rotationStatus.values().stream().findFirst();
- if (endpointStatus.isPresent()) {
- var changedAt = Instant.ofEpochSecond(endpointStatus.get().getEpoch());
- GlobalRouting.Agent agent;
- try {
- agent = GlobalRouting.Agent.valueOf(endpointStatus.get().getAgent());
- } catch (IllegalArgumentException e) {
- agent = GlobalRouting.Agent.unknown;
+ private void toSlime(List<Application> applications, ApplicationId instanceId, ZoneId zoneId, Cursor root) {
+ var deploymentsArray = root.setArray("deployments");
+ for (var application : applications) {
+ var instances = instanceId == null
+ ? application.instances().values()
+ : List.of(application.instances().get(instanceId.instance()));
+ for (var instance : instances) {
+ var zones = zoneId == null ? instance.deployments().keySet() : List.of(zoneId);
+ for (var zone : zones) {
+ var deploymentId = new DeploymentId(instance.id(), zone);
+ // Include status from rotation
+ if (rotationCanRouteTo(zone, instance)) {
+ var rotationStatus = controller.routingController().globalRotationStatus(deploymentId);
+ // Status is equal across all global endpoints, as the status is per deployment, not per endpoint.
+ var endpointStatus = rotationStatus.values().stream().findFirst();
+ if (endpointStatus.isPresent()) {
+ var changedAt = Instant.ofEpochSecond(endpointStatus.get().getEpoch());
+ GlobalRouting.Agent agent;
+ try {
+ agent = GlobalRouting.Agent.valueOf(endpointStatus.get().getAgent());
+ } catch (IllegalArgumentException e) {
+ agent = GlobalRouting.Agent.unknown;
+ }
+ var status = endpointStatus.get().getStatus() == EndpointStatus.Status.in
+ ? GlobalRouting.Status.in
+ : GlobalRouting.Status.out;
+ deploymentStatusToSlime(deploymentsArray.addObject(), deploymentId,
+ new GlobalRouting(status, agent, changedAt),
+ RoutingMethod.shared);
+ }
+ }
+
+ // Include status from routing policies
+ var routingPolicies = controller.routingController().policies().get(deploymentId);
+ for (var policy : routingPolicies.values()) {
+ deploymentStatusToSlime(deploymentsArray.addObject(), policy);
+ }
}
- var status = endpointStatus.get().getStatus() == EndpointStatus.Status.in
- ? GlobalRouting.Status.in
- : GlobalRouting.Status.out;
- deploymentStatusToSlime(deploymentsObject.addObject(), deployment,
- new GlobalRouting(status, agent, changedAt),
- RoutingMethod.shared);
}
}
- // Include status from routing policies
- var routingPolicies = controller.routingController().policies().get(deployment);
- for (var policy : routingPolicies.values()) {
- deploymentStatusToSlime(deploymentsObject.addObject(), policy);
- }
-
- return new SlimeJsonResponse(slime);
}
/** Returns whether instance has an assigned rotation and a deployment in given zone */
@@ -190,9 +298,24 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
policy.status().globalRouting(), RoutingMethod.exclusive);
}
+ private TenantName tenantFrom(Path path) {
+ return TenantName.from(path.get("tenant"));
+ }
+
+ private ApplicationName applicationFrom(Path path) {
+ return ApplicationName.from(path.get("application"));
+ }
+
+ private TenantAndApplicationId tenantAndApplicationIdFrom(Path path) {
+ return TenantAndApplicationId.from(tenantFrom(path), applicationFrom(path));
+ }
+
+ private ApplicationId instanceFrom(Path path) {
+ return ApplicationId.from(tenantFrom(path), applicationFrom(path), InstanceName.from(path.get("instance")));
+ }
+
private DeploymentId deploymentFrom(Path path) {
- return new DeploymentId(ApplicationId.from(path.get("tenant"), path.get("application"), path.get("instance")),
- zoneFrom(path));
+ return new DeploymentId(instanceFrom(path), zoneFrom(path));
}
private ZoneId zoneFrom(Path path) {
@@ -203,6 +326,10 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
return zone;
}
+ private static boolean isRecursive(HttpRequest request) {
+ return "true".equals(request.getProperty("recursive"));
+ }
+
private static String asString(GlobalRouting.Status status) {
switch (status) {
case in: return "in";
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/testdata/application-without-project-id.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/testdata/application-without-project-id.json
deleted file mode 100644
index 31949cce282..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/testdata/application-without-project-id.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "id": "tenant1:app1:default",
- "deploymentSpecField": "<deployment version='1.0'>\n <test />\n <staging />\n <prod>\n <region active=\"true\">us-central-1</region>\n <region active=\"true\">us-west-1</region>\n </prod>\n</deployment>\n",
- "validationOverrides": "<deployment version='1.0'/>",
- "deployments": [],
- "deploymentJobs": {
- "jobStatus": [
- {
- "jobType": "system-test",
- "lastTriggered": {
- "version": "6.42.1",
- "upgrade": false,
- "at": 1506330088050
- }
- }
- ]
- },
- "outstandingChangeField": false
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json
deleted file mode 100644
index d37e9120837..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-job-accepted.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "message": "Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.",
- "run": 1
-} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json
deleted file mode 100644
index 6338306897c..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json
+++ /dev/null
@@ -1,315 +0,0 @@
-{
- "tenant": "tenant1",
- "application": "application1",
- "instance": "instance1",
- "deployments": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1",
- "projectId": 1000,
- "deploymentJobs": [
- {
- "type": "system-test",
- "success": true,
- "lastTriggered": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastCompleted": {
- "id": 2,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastSuccess": {
- "id": 2,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- }
- },
- {
- "type": "staging-test",
- "success": true,
- "lastTriggered": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastCompleted": {
- "id": 3,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastSuccess": {
- "id": 3,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- }
- },
- {
- "type": "production-us-west-1",
- "success": true,
- "lastTriggered": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastCompleted": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastSuccess": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- }
- },
- {
- "type": "production-us-east-3",
- "success": true,
- "lastTriggered": {
- "id": 1,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastCompleted": {
- "id": 2,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- },
- "lastSuccess": {
- "id": 2,
- "version": "(ignore)",
- "revision": {
- "buildNumber": "(ignore)",
- "hash": "(ignore)",
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "reason": "unknown reason",
- "at": "(ignore)"
- }
- }
- ],
- "changeBlockers": [],
- "compileVersion": "(ignore)",
- "globalRotations": [
- "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"
- ],
- "rotationId": "rotation-id-1",
- "instances": [
- {
- "bcpStatus": {
- "rotationStatus": "IN"
- },
- "endpointStatus": [
- {
- "endpointId": "default",
- "rotationId": "rotation-id-1",
- "clusterId": "foo",
- "status": "IN",
- "lastUpdated": "(ignore)"
- }
- ],
- "applicationVersion": {
- "hash": "1.0.1-commit1",
- "build": 1,
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "status": "complete",
- "environment": "prod",
- "region": "us-west-1",
- "instance": "instance1",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1"
- },
- {
- "bcpStatus": {
- "rotationStatus": "UNKNOWN"
- },
- "endpointStatus": [
- {
- "endpointId": "default",
- "rotationId": "rotation-id-1",
- "clusterId": "foo",
- "status": "UNKNOWN",
- "lastUpdated": "(ignore)"
- }
- ],
- "applicationVersion": {
- "hash": "1.0.1-commit1",
- "build": 1,
- "source": {
- "gitRepository": "repository1",
- "gitBranch": "master",
- "gitCommit": "commit1"
- },
- "sourceUrl": "repository1/tree/commit1",
- "commit": "commit1"
- },
- "status": "complete",
- "environment": "prod",
- "region": "us-east-3",
- "instance": "instance1",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3"
- }
- ],
- "pemDeployKeys": [],
- "metrics": {
- "queryServiceQuality": 0.5,
- "writeServiceQuality": 0.7
- },
- "activity": {
- "lastQueried": 1527848130000,
- "lastWritten": 1527848130000,
- "lastQueriesPerSecond": 1.0,
- "lastWritesPerSecond": 2.0
- }
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json
deleted file mode 100644
index dbaa6623fae..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/responses/versions-initial.json
+++ /dev/null
@@ -1,153 +0,0 @@
-{
- "versions": [
- {
- "version": "0.0.0",
- "targetVersion": false,
- "cloud": "cloud1",
- "nodes": [
- {
- "hostname": "node-2-configserver-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-1-configserver-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-3-configserver-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-1-configserver-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-2-configserver-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-3-configserver-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-2-proxy-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-3-proxy-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-1-proxy-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-2-proxy-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-1-proxy-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-3-proxy-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-3-tenant-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-2-tenant-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-1-tenant-host-prod.us-east-3",
- "environment": "prod",
- "region": "us-east-3"
- },
- {
- "hostname": "node-3-tenant-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-2-tenant-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- },
- {
- "hostname": "node-1-tenant-host-prod.us-west-1",
- "environment": "prod",
- "region": "us-west-1"
- }
- ]
- },
- {
- "version": "0.0.0",
- "targetVersion": false,
- "cloud": "cloud2",
- "nodes": [
- {
- "hostname": "node-1-configserver-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-2-configserver-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-3-configserver-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-1-proxy-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-3-proxy-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-2-proxy-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-1-tenant-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-3-tenant-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- },
- {
- "hostname": "node-2-tenant-host-prod.eu-west-1",
- "environment": "prod",
- "region": "eu-west-1"
- }
- ]
- }
- ]
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
index 635adc73d1d..d191b460697 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
@@ -35,6 +35,88 @@ public class RoutingApiTest extends ControllerContainerTest {
}
@Test
+ public void discovery() {
+ // Deploy
+ var context = deploymentTester.newDeploymentContext("t1", "a1", "default");
+ var westZone = ZoneId.from("prod", "us-west-1");
+ var eastZone = ZoneId.from("prod", "us-east-3");
+ var applicationPackage = new ApplicationPackageBuilder()
+ .region(westZone.region())
+ .region(eastZone.region())
+ .endpoint("default", "default", eastZone.region().value(), westZone.region().value())
+ .build();
+ context.submit(applicationPackage).deploy();
+
+ // GET root
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/", "",
+ Request.Method.GET),
+ new File("discovery/root.json"));
+
+ // GET tenant
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1", "",
+ Request.Method.GET),
+ new File("discovery/tenant.json"));
+
+ // GET application
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1/application/a1/",
+ "",
+ Request.Method.GET),
+ new File("discovery/application.json"));
+
+ // GET instance
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default/",
+ "",
+ Request.Method.GET),
+ new File("discovery/instance.json"));
+
+ // GET environment
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/environment/", "",
+ Request.Method.GET),
+ new File("discovery/environment.json"));
+ }
+
+ @Test
+ public void recursion() {
+ var context1 = deploymentTester.newDeploymentContext("t1", "a1", "default");
+ var westZone = ZoneId.from("prod", "us-west-1");
+ var eastZone = ZoneId.from("prod", "us-east-3");
+ var package1 = new ApplicationPackageBuilder()
+ .region(westZone.region())
+ .region(eastZone.region())
+ .endpoint("default", "default", eastZone.region().value(), westZone.region().value())
+ .build();
+ context1.submit(package1).deploy();
+
+ var context2 = deploymentTester.newDeploymentContext("t1", "a2", "default");
+ var package2 = new ApplicationPackageBuilder()
+ .region(westZone.region())
+ .region(eastZone.region())
+ .endpoint("default", "default", eastZone.region().value(), westZone.region().value())
+ .build();
+ context2.submit(package2).deploy();
+
+ // GET tenant recursively
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1?recursive=true", "",
+ Request.Method.GET),
+ new File("recursion/tenant.json"));
+
+ // GET application recursively
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1/application/a1?recursive=true", "",
+ Request.Method.GET),
+ new File("recursion/application.json"));
+
+ // GET instance recursively
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default?recursive=true", "",
+ Request.Method.GET),
+ new File("recursion/application.json"));
+
+ // GET environment recursively
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/environment?recursive=true", "",
+ Request.Method.GET),
+ new File("recursion/environment.json"));
+ }
+
+ @Test
public void exclusive_routing() {
var context = deploymentTester.newDeploymentContext();
// Zones support direct routing
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/application.json
new file mode 100644
index 00000000000..deda734cbbf
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/application.json
@@ -0,0 +1,7 @@
+{
+ "resources": [
+ {
+ "url": "http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default/"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/environment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/environment.json
new file mode 100644
index 00000000000..1e06b279873
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/environment.json
@@ -0,0 +1,43 @@
+{
+ "resources": [
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/dev/region/aws-us-east-2a/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/dev/region/us-east-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/perf/region/us-east-3/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/ap-northeast-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/ap-northeast-2/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/ap-southeast-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/aws-us-east-1a/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/eu-west-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/us-central-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/us-east-3/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/prod/region/us-west-1/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/staging/region/us-east-3/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/test/region/us-east-1/"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/instance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/instance.json
new file mode 100644
index 00000000000..1a3ad823e14
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/instance.json
@@ -0,0 +1,10 @@
+{
+ "resources": [
+ {
+ "url": "http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default/environment/prod/region/us-east-3/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default/environment/prod/region/us-west-1/"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/root.json
new file mode 100644
index 00000000000..9b5630335aa
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/root.json
@@ -0,0 +1,10 @@
+{
+ "resources": [
+ {
+ "url": "http://localhost:8080/routing/v1/status/tenant/"
+ },
+ {
+ "url": "http://localhost:8080/routing/v1/status/environment/"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/tenant.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/tenant.json
new file mode 100644
index 00000000000..acd05d35c8d
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/discovery/tenant.json
@@ -0,0 +1,7 @@
+{
+ "resources": [
+ {
+ "url": "http://localhost:8080/routing/v1/status/tenant/t1/application/a1/"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/application.json
new file mode 100644
index 00000000000..9a5d919e9b4
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/application.json
@@ -0,0 +1,22 @@
+{
+ "deployments": [
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-west-1",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ },
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/environment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/environment.json
new file mode 100644
index 00000000000..f0dd0b7310d
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/environment.json
@@ -0,0 +1,108 @@
+{
+ "zones": [
+ {
+ "routingMethod": "shared",
+ "environment": "test",
+ "region": "us-east-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "staging",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "dev",
+ "region": "us-east-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "dev",
+ "region": "aws-us-east-2a",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "perf",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "aws-us-east-1a",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "ap-northeast-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "ap-northeast-2",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "ap-southeast-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "us-west-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "us-central-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ },
+ {
+ "routingMethod": "shared",
+ "environment": "prod",
+ "region": "eu-west-1",
+ "status": "in",
+ "agent": "operator",
+ "changedAt": 0
+ }
+ ]
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/tenant.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/tenant.json
new file mode 100644
index 00000000000..85db7411c40
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/recursion/tenant.json
@@ -0,0 +1,40 @@
+{
+ "deployments": [
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-west-1",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ },
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ },
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a2:default",
+ "environment": "prod",
+ "region": "us-west-1",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ },
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a2:default",
+ "environment": "prod",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": "(ignore)"
+ }
+ ]
+}