diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-02-14 16:03:00 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2020-02-17 11:13:46 +0100 |
commit | 3a88329b8e68804c82a229e73608e441b49be15c (patch) | |
tree | ad87edcb7131d19d0f5e1c001fc7db502774467a /controller-server | |
parent | 3a538c64845c6ff42847483b3ad84b2afd1b5c97 (diff) |
Make /routing/v1 discoverable
Diffstat (limited to 'controller-server')
7 files changed, 199 insertions, 8 deletions
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..745316826cd 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,26 @@ 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.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 +53,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.getUri()); case POST: return post(path); case DELETE: return delete(path); default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); @@ -70,12 +78,62 @@ 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, URI requestUrl) { + if (path.matches("/routing/v1/")) return status(requestUrl); + if (path.matches("/routing/v1/status/tenant/{tenant}")) return tenant(path, requestUrl); + if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}")) return application(path, requestUrl); + if (path.matches("/routing/v1/status/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path, requestUrl); + 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(requestUrl); + if (path.matches("/routing/v1/status/environment/{environment}/region/{region}")) return zone(path); return ErrorResponse.notFoundError("Nothing at " + path); } + private HttpResponse environment(URI requestUrl) { + var resources = controller.zoneRegistry().zones().all().ids().stream() + .map(zone -> zone.environment().value() + + "/region/" + zone.region().value()) + .sorted() + .collect(Collectors.toList()); + return new ResourceResponse(requestUrl, resources); + } + + private HttpResponse status(URI requestUrl) { + return new ResourceResponse(requestUrl, "status/tenant", "status/environment"); + } + + private HttpResponse tenant(Path path, URI requestUrl) { + var tenantName = tenantFrom(path); + 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(requestUrl, resources); + } + + private HttpResponse application(Path path, URI requestUrl) { + var tenantAndApplicationId = tenantAndApplicationIdFrom(path); + var resources = controller.applications().requireApplication(tenantAndApplicationId).instances().keySet().stream() + .map(InstanceName::value) + .map(instance -> "instance/" + instance) + .sorted() + .collect(Collectors.toList()); + return new ResourceResponse(requestUrl, resources); + } + + private HttpResponse instance(Path path, URI requestUrl) { + var instanceId = instanceFrom(path); + 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(requestUrl, resources); + } + private HttpResponse setZoneStatus(Path path, boolean in) { var zone = zoneFrom(path); if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) { @@ -88,7 +146,7 @@ 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(); @@ -124,7 +182,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler { return new MessageResponse("Set global routing status for " + deployment + " to " + (in ? "IN" : "OUT")); } - private HttpResponse deploymentStatus(Path path) { + private HttpResponse deployment(Path path) { var deployment = deploymentFrom(path); var instance = controller.applications().requireInstance(deployment.applicationId()); var slime = new Slime(); @@ -190,9 +248,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) { 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..b368cb6dd5e 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,47 @@ 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 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/" + } + ] +} |