diff options
author | Martin Polden <mpolden@mpolden.no> | 2023-07-10 20:49:14 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-10 20:49:14 +0200 |
commit | 3008439ac5f2f2dfd0c5acccc1a6b8cf202f9933 (patch) | |
tree | 3bd216336060a5e6c13e6b8609e8051076ecde13 | |
parent | c4911b3f2924544e4700a1832df34ebbc03b53fc (diff) | |
parent | 53744f54af44b6024d4002ba69cf34feaac1b134 (diff) |
Merge pull request #27697 from vespa-engine/mpolden/endpoint-lookupv8.192.20
Add API for searching deployments by endpoint
8 files changed, 141 insertions, 6 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 1a8f4103659..6c603a1da7b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -38,7 +38,8 @@ enum PathGroup { "/provision/v2/{*}", "/zone/v2/{*}", "/state/v1/{*}", - "/changemanagement/v1/{*}"), + "/changemanagement/v1/{*}", + "/application/v4/search/{*}"), /** Paths used for creating and reading user resources. */ user("/application/v4/user", diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java index 65f4c851e3c..a635d512054 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java @@ -200,8 +200,8 @@ public class RoutingController { return EndpointList.copyOf(endpoints); } - /** Read test runner endpoints for given deployments, grouped by their zone */ - public Map<ZoneId, List<Endpoint>> readTestRunnerEndpointsOf(Collection<DeploymentId> deployments) { + /** Read endpoints for use in deployment steps, for given deployments, grouped by their zone */ + public Map<ZoneId, List<Endpoint>> readStepRunnerEndpointsOf(Collection<DeploymentId> deployments) { TreeMap<ZoneId, List<Endpoint>> endpoints = new TreeMap<>(Comparator.comparing(ZoneId::value)); for (var deployment : deployments) { EndpointList zoneEndpoints = readEndpointsOf(deployment).scope(Endpoint.Scope.zone) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java index e554bb2361a..dcc3e229f92 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java @@ -38,6 +38,11 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList> endpoint.name().equals(id.id())); } + /** Returns the endpoint which has given DNS name, if any */ + public Optional<Endpoint> dnsName(String dnsName) { + return matching(endpoint -> endpoint.dnsName().equals(dnsName)).first(); + } + /** Returns the subset of endpoints pointing to given cluster */ public EndpointList cluster(ClusterSpec.Id cluster) { return matching(endpoint -> endpoint.cluster().equals(cluster)); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java index e19915fbd24..07695c17042 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java @@ -502,7 +502,7 @@ public class InternalStepRunner implements StepRunner { private Availability endpointsAvailable(ApplicationId id, ZoneId zone, Deployment deployment, DualLogger logger) { DeploymentId deploymentId = new DeploymentId(id, zone); - Map<ZoneId, List<Endpoint>> endpoints = controller.routing().readTestRunnerEndpointsOf(Set.of(deploymentId)); + Map<ZoneId, List<Endpoint>> endpoints = controller.routing().readStepRunnerEndpointsOf(Set.of(deploymentId)); logEndpoints(endpoints, logger); DeploymentRoutingContext context = controller.routing().of(deploymentId); boolean resolveEndpoints = context.routingMethod() == RoutingMethod.exclusive; @@ -594,7 +594,7 @@ public class InternalStepRunner implements StepRunner { deployments.add(new DeploymentId(id.application(), zoneId)); logger.log("Attempting to find endpoints ..."); - var endpoints = controller.routing().readTestRunnerEndpointsOf(deployments); + var endpoints = controller.routing().readStepRunnerEndpointsOf(deployments); if ( ! endpoints.containsKey(zoneId)) { logger.log(WARNING, "Endpoints for the deployment to test vanished again, while it was still active!"); return Optional.of(error); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 7c7f0fe9876..daa64ddb07f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -150,6 +150,7 @@ import java.time.DayOfWeek; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collection; @@ -250,6 +251,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private HttpResponse handleGET(Path path, HttpRequest request) { if (path.matches("/application/v4/")) return root(request); + if (path.matches("/application/v4/search/{*}")) return search(path, request); if (path.matches("/application/v4/notifications")) return notifications(request, Optional.ofNullable(request.getProperty("tenant")), true); if (path.matches("/application/v4/tenant")) return tenants(request); if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request); @@ -310,6 +312,57 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { return ErrorResponse.notFoundError("Nothing at " + path); } + private HttpResponse search(Path path, HttpRequest request) { + if (path.matches("/application/v4/search/deployment")) return searchDeploymentsByEndpoint(request); + return ErrorResponse.notFoundError("Nothing at " + path); + } + + private HttpResponse searchDeploymentsByEndpoint(HttpRequest request) { + String endpoint = request.getProperty("endpoint"); + if (endpoint == null) { + throw new IllegalArgumentException("Missing 'endpoint' query parameter"); + } + endpoint = endpoint.trim(); + if (endpoint.startsWith("https://") || endpoint.startsWith("http://")) { + // Trim scheme and port + endpoint = URI.create(endpoint).getHost(); + } + List<Application> applications = controller.applications().asList(); + record EndpointTarget(DeploymentId deployment, ClusterSpec.Id cluster) {} + List<EndpointTarget> targets = new ArrayList<>(); + out: + for (var app : applications) { + Optional<Endpoint> declaredEndpoint = controller.routing().declaredEndpointsOf(app).dnsName(endpoint); + if (declaredEndpoint.isPresent()) { + for (var target : declaredEndpoint.get().targets()) { + targets.add(new EndpointTarget(target.deployment(), declaredEndpoint.get().cluster())); + } + break; + } else { + for (var instance : app.instances().values()) { + for (var deployment : instance.deployments().values()) { + DeploymentId id = new DeploymentId(instance.id(), deployment.zone()); + Optional<Endpoint> matchingEndpoint = controller.routing().readEndpointsOf(id).dnsName(endpoint); + if (matchingEndpoint.isPresent()) { + for (var target : matchingEndpoint.get().targets()) { + targets.add(new EndpointTarget(target.deployment(), matchingEndpoint.get().cluster())); + } + break out; + } + } + } + } + } + Slime slime = new Slime(); + Cursor root = slime.setObject(); + Cursor deploymentArray = root.setArray("deployments"); + for (var target : targets) { + toSlime(target.deployment, target.cluster, deploymentArray.addObject(), request); + } + return new SlimeJsonResponse(slime); + } + + private HttpResponse handlePUT(Path path, HttpRequest request) { if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/access/request/operator")) return requestSshAccess(path.get("tenant"), request); @@ -2640,7 +2693,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { deployment.version(), deployment.revision(), deployment.at(), - controller.routing().readTestRunnerEndpointsOf(deployments), + controller.routing().readStepRunnerEndpointsOf(deployments), controller.applications().reachableContentClustersByZone(deployments))); } @@ -3029,6 +3082,22 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { request.getUri()).toString()); } + private void toSlime(DeploymentId id, ClusterSpec.Id cluster, Cursor object, HttpRequest request) { + object.setString("tenant", id.applicationId().tenant().value()); + object.setString("application", id.applicationId().application().value()); + object.setString("instance", id.applicationId().instance().value()); + object.setString("environment", id.zoneId().environment().value()); + object.setString("region", id.zoneId().region().value()); + object.setString("cluster", cluster.value()); + object.setString("url", withPath("/application/v4" + + "/tenant/" + id.applicationId().tenant().value() + + "/application/" + id.applicationId().application().value() + + "/instance/" + id.applicationId().instance().value() + + "/environment/" + id.zoneId().environment().value() + + "/region/" + id.zoneId().region().value(), + request.getUri()).toString()); + } + private void stringsToSlime(List<String> strings, Cursor array) { for (String string : strings) array.addString(string); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index ac16aa727d5..14c279c9ef8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -787,6 +787,22 @@ public class ApplicationApiTest extends ControllerContainerTest { }, 200); + // GET searches deployments by endpoints + tester.assertResponse(request("/application/v4/search/deployment", GET).userIdentity(HOSTED_VESPA_OPERATOR), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Missing 'endpoint' query parameter\"}", 400); + tester.assertResponse(request("/application/v4/search/deployment", GET).properties(Map.of("endpoint", "https://instance1.application1.tenant1.global.vespa.oath.cloud:4443")) + .userIdentity(HOSTED_VESPA_OPERATOR), + new File("search-deployments-multi.json"), 200); + tester.assertResponse(request("/application/v4/search/deployment", GET).properties(Map.of("endpoint", "instance1.application1.tenant1.global.vespa.oath.cloud")) + .userIdentity(HOSTED_VESPA_OPERATOR), + new File("search-deployments-multi.json"), 200); + tester.assertResponse(request("/application/v4/search/deployment", GET).properties(Map.of("endpoint", "instance1.application1.tenant1.us-central-1.vespa.oath.cloud")) + .userIdentity(HOSTED_VESPA_OPERATOR), + new File("search-deployments-single.json"), 200); + tester.assertResponse(request("/application/v4/search/deployment", GET).properties(Map.of("endpoint", "non-existent")) + .userIdentity(HOSTED_VESPA_OPERATOR), + "{\"deployments\":[]}", 200); + // DELETE application with active deployments fails tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", DELETE) .userIdentity(USER_ID) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-multi.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-multi.json new file mode 100644 index 00000000000..8eded55ad64 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-multi.json @@ -0,0 +1,31 @@ +{ + "deployments": [ + { + "tenant": "tenant1", + "application": "application1", + "instance": "instance1", + "environment": "prod", + "region": "us-central-1", + "cluster": "foo", + "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1" + }, + { + "tenant": "tenant1", + "application": "application1", + "instance": "instance1", + "environment": "prod", + "region": "us-east-3", + "cluster": "foo", + "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-east-3" + }, + { + "tenant": "tenant1", + "application": "application1", + "instance": "instance1", + "environment": "prod", + "region": "us-west-1", + "cluster": "foo", + "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1" + } + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-single.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-single.json new file mode 100644 index 00000000000..ef524c300dc --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/search-deployments-single.json @@ -0,0 +1,13 @@ +{ + "deployments": [ + { + "tenant": "tenant1", + "application": "application1", + "instance": "instance1", + "environment": "prod", + "region": "us-central-1", + "cluster": "default", + "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1" + } + ] +} |