diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-02-17 12:21:08 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-17 12:21:08 +0100 |
commit | b1c702d576acc700d2c47bd7dd84d222cd3b8e6e (patch) | |
tree | 51cb16d66482f71b82db08fd5f4f7b43166a8923 | |
parent | 8a4d28d0f50dd6e9bad197d524cefd9dfc594e5c (diff) | |
parent | 488a225e8e479ac23a562b5389756d6ff2711c5f (diff) |
Merge pull request #12213 from vespa-engine/mpolden/discoverable-routing-v1
Make /routing/v1 discoverable
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)" + } + ] +} |