summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2020-02-17 11:05:51 +0100
committerMartin Polden <mpolden@mpolden.no>2020-02-17 11:13:46 +0100
commita040164e702c2fc6fa38a5b25bc106efeb4d1742 (patch)
tree5a23adfce901b93605d5ad547252cf782852983b /controller-server
parent3a88329b8e68804c82a229e73608e441b49be15c (diff)
Add recursion support
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java146
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java41
-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
5 files changed, 311 insertions, 46 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 745316826cd..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
@@ -29,6 +29,7 @@ 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;
@@ -53,7 +54,7 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
try {
var path = new Path(request.getUri());
switch (request.getMethod()) {
- case GET: return get(path, request.getUri());
+ 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");
@@ -78,32 +79,48 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
return ErrorResponse.notFoundError("Nothing at " + 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);
+ 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(requestUrl);
+ 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(URI requestUrl) {
+ 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(requestUrl, resources);
+ return new ResourceResponse(request.getUri(), resources);
}
private HttpResponse status(URI requestUrl) {
return new ResourceResponse(requestUrl, "status/tenant", "status/environment");
}
- private HttpResponse tenant(Path path, URI requestUrl) {
+ 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)
@@ -111,27 +128,41 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
.map(application -> "application/" + application)
.sorted()
.collect(Collectors.toList());
- return new ResourceResponse(requestUrl, resources);
+ return new ResourceResponse(request.getUri(), resources);
}
- private HttpResponse application(Path path, URI requestUrl) {
+ 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(requestUrl, resources);
+ return new ResourceResponse(request.getUri(), resources);
}
- private HttpResponse instance(Path path, URI requestUrl) {
+ 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(requestUrl, resources);
+ return new ResourceResponse(request.getUri(), resources);
}
private HttpResponse setZoneStatus(Path path, boolean in) {
@@ -150,17 +181,21 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
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) {
@@ -183,40 +218,55 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse deployment(Path path) {
- var deployment = deploymentFrom(path);
- var instance = controller.applications().requireInstance(deployment.applicationId());
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 */
@@ -276,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/restapi/routing/RoutingApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
index b368cb6dd5e..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
@@ -76,6 +76,47 @@ public class RoutingApiTest extends ControllerContainerTest {
}
@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/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)"
+ }
+ ]
+}