aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeandro Alves <ldalves@gmail.com>2021-09-13 22:08:52 +0200
committerGitHub <noreply@github.com>2021-09-13 22:08:52 +0200
commit51b1e5418c40c69e4f5c822aa75d3647a5993737 (patch)
treefdb7cc21d2020d1a219749229cb23037b3164b93
parent3edab06353b366a022b33803cd1ca676b20da89e (diff)
parent368188baa0301ccc2d049f37203bbf5e719887ce (diff)
Merge pull request #19061 from vespa-engine/ogronnesby/endpoint-status-routing-v1
Expose all endpoints for a given instance
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java113
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/endpoint/endpoints.json31
3 files changed, 131 insertions, 28 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 828e7e63483..f16f30b1a6c 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
@@ -21,6 +21,7 @@ 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.Endpoint;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
@@ -31,8 +32,10 @@ import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* This implements the /routing/v1 API, which provides operator with global routing control at both zone- and
@@ -85,11 +88,49 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
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/tenant/{tenant}/application/{application}/instance/{instance}/endpoint")) return endpoints(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 endpoints(Path path) {
+ var instanceId = instanceFrom(path);
+ var endpoints = controller.routing().endpointsOf(instanceId);
+
+ var deployments = endpoints.asList().stream()
+ .flatMap(e -> e.zones().stream())
+ .distinct()
+ .map(zoneId -> new DeploymentId(instanceId, zoneId))
+ .collect(Collectors.toList());
+
+ var deploymentsStatus = deployments.stream()
+ .collect(Collectors.toMap(
+ deploymentId -> deploymentId,
+ deploymentId -> Stream.concat(
+ directGlobalRoutingStatus(deploymentId).stream(),
+ sharedGlobalRoutingStatus(deploymentId).stream()
+ ).collect(Collectors.toList())
+ ));
+
+ var slime = new Slime();
+ var root = slime.setObject();
+ var endpointsRoot = root.setArray("endpoints");
+ endpoints.forEach(endpoint -> {
+ var endpointRoot = endpointsRoot.addObject();
+ endpointToSlime(endpointRoot, endpoint);
+ var zonesRoot = endpointRoot.setArray("zones");
+ endpoint.zones().forEach(zoneId -> {
+ var deploymentId = new DeploymentId(instanceId, zoneId);
+ deploymentsStatus.getOrDefault(deploymentId, List.of()).forEach(status -> {
+ deploymentStatusToSlime(zonesRoot.addObject(), deploymentId, status, endpoint.routingMethod());
+ });
+ });
+ });
+
+ return new SlimeJsonResponse(slime);
+ }
+
private HttpResponse environment(HttpRequest request) {
var zones = controller.zoneRegistry().zones().all().ids();
if (isRecursive(request)) {
@@ -241,42 +282,50 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
for (var zone : zones) {
var deploymentId = requireDeployment(new DeploymentId(instance.id(), zone), instance);
// Include status from rotation
- if (rotationCanRouteTo(zone)) {
- var rotationStatus = controller.routing().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);
- }
- }
+ sharedGlobalRoutingStatus(deploymentId).ifPresent(status -> {
+ deploymentStatusToSlime(deploymentsArray.addObject(), deploymentId, status, RoutingMethod.shared);
+ });
// Include status from routing policies
- var routingPolicies = controller.routing().policies().get(deploymentId);
- for (var policy : routingPolicies.values()) {
- if (policy.endpoints().isEmpty()) continue; // This policy does not apply to a global endpoint
- if (!controller.zoneRegistry().routingMethods(policy.id().zone()).contains(RoutingMethod.exclusive)) continue;
- deploymentStatusToSlime(deploymentsArray.addObject(), new DeploymentId(policy.id().owner(),
- policy.id().zone()),
- policy.status().globalRouting(), RoutingMethod.exclusive);
- }
+ directGlobalRoutingStatus(deploymentId).forEach(status -> {
+ deploymentStatusToSlime(deploymentsArray.addObject(), deploymentId, status, RoutingMethod.exclusive);
+ });
}
}
}
}
+ private Optional<GlobalRouting> sharedGlobalRoutingStatus(DeploymentId deploymentId) {
+ if (rotationCanRouteTo(deploymentId.zoneId())) {
+ var rotationStatus = controller.routing().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;
+ return Optional.of(new GlobalRouting(status, agent, changedAt));
+ }
+ }
+ return Optional.empty();
+ }
+
+ private List<GlobalRouting> directGlobalRoutingStatus(DeploymentId deploymentId) {
+ return controller.routing().policies().get(deploymentId).values().stream()
+ .filter(p -> ! p.endpoints().isEmpty()) // This policy does not apply to a global endpoint
+ .filter(p -> controller.zoneRegistry().routingMethods(p.id().zone()).contains(RoutingMethod.exclusive))
+ .map(p -> p.status().globalRouting())
+ .collect(Collectors.toList());
+ }
+
/** Returns whether a rotation can route traffic to given zone */
private boolean rotationCanRouteTo(ZoneId zone) {
// A system may support multiple routing methods, i.e. it has both exclusively routed zones and zones using
@@ -304,6 +353,14 @@ public class RoutingApiHandler extends AuditLoggingRequestHandler {
object.setLong("changedAt", globalRouting.changedAt().toEpochMilli());
}
+ private static void endpointToSlime(Cursor object, Endpoint endpoint) {
+ object.setString("name", endpoint.name());
+ object.setString("dnsName", endpoint.dnsName());
+ object.setString("routingMethod", endpoint.routingMethod().name());
+ object.setString("cluster", endpoint.cluster().value());
+ object.setString("scope", endpoint.scope().name());
+ }
+
private TenantName tenantFrom(Path path) {
return TenantName.from(path.get("tenant"));
}
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 b1bd7df059c..42c9b5bdf4f 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
@@ -362,4 +362,19 @@ public class RoutingApiTest extends ControllerContainerTest {
400);
}
+ @Test
+ public void endpoints_list() {
+ 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();
+
+ tester.assertResponse(operatorRequest("http://localhost:8080/routing/v1/status/tenant/t1/application/a1/instance/default/endpoint", "", Request.Method.GET),
+ new File("endpoint/endpoints.json"));
+ }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/endpoint/endpoints.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/endpoint/endpoints.json
new file mode 100644
index 00000000000..f78f913cb7e
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/responses/endpoint/endpoints.json
@@ -0,0 +1,31 @@
+{
+ "endpoints": [
+ {
+ "name": "default",
+ "dnsName": "a1--t1.global.vespa.oath.cloud",
+ "routingMethod": "shared",
+ "cluster": "default",
+ "scope": "global",
+ "zones": [
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-east-3",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": 1497618757000
+ },
+ {
+ "routingMethod": "shared",
+ "instance": "t1:a1:default",
+ "environment": "prod",
+ "region": "us-west-1",
+ "status": "in",
+ "agent": "unknown",
+ "changedAt": 1497618757000
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file