diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2021-05-02 19:27:57 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-02 19:27:57 +0200 |
commit | edd0efdce13c9e4e264062a5c8e8163975164dc5 (patch) | |
tree | 05eb70684174321cf6ad3fda1578d0fce6be55c2 | |
parent | 2daa854096154742885d8ef2d7ca5d0745abb9d2 (diff) | |
parent | b947b3750ce77bbb28addf1a86b7cfaf22d8243d (diff) |
Merge pull request #17697 from vespa-engine/jonmv/reapply-rest-apis-without-trailing-slash-requirement
Jonmv/reapply rest apis without trailing slash requirement
11 files changed, 116 insertions, 171 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java index 3961fe12357..bd4231972e7 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/HttpErrorResponse.java @@ -110,7 +110,7 @@ public class HttpErrorResponse extends HttpResponse { new JsonFormat(true).encode(stream, slime); } - //@Override + @Override public String getContentType() { return HttpConfigResponse.JSON_CONTENT_TYPE; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java index 6aff3b8a361..bb94f8d442a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java @@ -3,112 +3,77 @@ package com.yahoo.vespa.config.server.http.v2; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; - import com.yahoo.config.provision.TenantName; -import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.application.BindingMatch; +import com.yahoo.restapi.ErrorResponse; +import com.yahoo.restapi.RestApi; +import com.yahoo.restapi.RestApiException; +import com.yahoo.restapi.RestApiRequestHandler; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.yolean.Exceptions; -import com.yahoo.vespa.config.server.http.BadRequestException; -import com.yahoo.vespa.config.server.http.HttpHandler; -import com.yahoo.vespa.config.server.http.InternalServerException; -import com.yahoo.vespa.config.server.http.Utils; /** * Handler to create, get and delete a tenant, and listing of tenants. * - * @author Vegard Havdal + * @author jonmv */ -public class TenantHandler extends HttpHandler { +public class TenantHandler extends RestApiRequestHandler<TenantHandler> { private static final String TENANT_NAME_REGEXP = "[\\w-]+"; private final TenantRepository tenantRepository; private final ApplicationRepository applicationRepository; - // instantiated by dependency injection @Inject public TenantHandler(Context ctx, ApplicationRepository applicationRepository) { - super(ctx); + super(ctx, TenantHandler::defineApi); this.tenantRepository = applicationRepository.tenantRepository(); this.applicationRepository = applicationRepository; } - @Override - protected HttpResponse handlePUT(HttpRequest request) { - TenantName tenantName = getAndValidateTenantFromRequest(request); - try { - tenantRepository.addTenant(tenantName); - } catch (Exception e) { - throw new InternalServerException(Exceptions.toMessageString(e)); - } - return new TenantCreateResponse(tenantName); - } - - @Override - protected HttpResponse handleGET(HttpRequest request) { - if (isGetTenantRequest(request)) { - final TenantName tenantName = getTenantNameFromRequest(request); - Utils.checkThatTenantExists(tenantRepository, tenantName); - return new TenantGetResponse(tenantName); - } else if (isListTenantsRequest(request)) { - return new ListTenantsResponse(ImmutableSet.copyOf(tenantRepository.getAllTenantNames())); - } else { - throw new BadRequestException(request.getUri().toString()); - } - } - - @Override - protected HttpResponse handleDELETE(HttpRequest request) { - final TenantName tenantName = getTenantNameFromRequest(request); - Utils.checkThatTenantExists(tenantRepository, tenantName); - applicationRepository.deleteTenant(tenantName); - return new TenantDeleteResponse(tenantName); + private RestApi defineApi() { + return RestApi.builder() + .addRoute(RestApi.route("/application/v2/tenant") + .get(this::getTenants)) + .addRoute(RestApi.route("/application/v2/tenant/{tenant}") + .get(this::getTenant) + .put(this::putTenant) + .delete(this::deleteTenant)) + .addExceptionMapper(IllegalArgumentException.class, (c, e) -> ErrorResponse.badRequest(Exceptions.toMessageString(e))) + .addExceptionMapper(RuntimeException.class, (c, e) -> ErrorResponse.internalServerError(Exceptions.toMessageString(e))) + .build(); } - /** - * Gets the tenant name from the request, throws if it exists already and validates its name - * - * @param request an {@link com.yahoo.container.jdisc.HttpRequest} - * @return tenant name - */ - private TenantName getAndValidateTenantFromRequest(HttpRequest request) { - final TenantName tenantName = getTenantNameFromRequest(request); - checkThatTenantDoesNotExist(tenantName); - validateTenantName(tenantName); - return tenantName; + private HttpResponse getTenants(RestApi.RequestContext context) { + return new ListTenantsResponse(ImmutableSet.copyOf(tenantRepository.getAllTenantNames())); } - private void validateTenantName(TenantName tenant) { - if (!tenant.value().matches(TENANT_NAME_REGEXP)) { - throw new BadRequestException("Illegal tenant name: " + tenant); - } - } + private HttpResponse getTenant(RestApi.RequestContext context) { + TenantName name = TenantName.from(context.pathParameters().getStringOrThrow("tenant")); + if ( ! tenantRepository.checkThatTenantExists(name)) + throw new RestApiException.NotFound("Tenant '" + name + "' was not found."); - private void checkThatTenantDoesNotExist(TenantName tenantName) { - if (tenantRepository.checkThatTenantExists(tenantName)) - throw new BadRequestException("There already exists a tenant '" + tenantName + "'"); + return new TenantGetResponse(name); } - private static BindingMatch<?> getBindingMatch(HttpRequest request) { - return HttpConfigRequests.getBindingMatch(request, - "http://*/application/v2/tenant/", - "http://*/application/v2/tenant/*"); - } + private HttpResponse putTenant(RestApi.RequestContext context) { + TenantName name = TenantName.from(context.pathParameters().getStringOrThrow("tenant")); + if (tenantRepository.checkThatTenantExists(name)) + throw new RestApiException.BadRequest("There already exists a tenant '" + name + "'"); + if ( ! name.value().matches(TENANT_NAME_REGEXP)) + throw new RestApiException.BadRequest("Illegal tenant name: " + name); - private static boolean isGetTenantRequest(HttpRequest request) { - return getBindingMatch(request).groupCount() == 3; + tenantRepository.addTenant(name); + return new TenantCreateResponse(name); } - private static boolean isListTenantsRequest(HttpRequest request) { - return getBindingMatch(request).groupCount() == 2 && - request.getUri().getPath().endsWith("/tenant/"); - } + private HttpResponse deleteTenant(RestApi.RequestContext context) { + TenantName name = TenantName.from(context.pathParameters().getStringOrThrow("tenant")); + if ( ! tenantRepository.checkThatTenantExists(name)) + throw new RestApiException.NotFound("Tenant '" + name + "' was not found."); - private static TenantName getTenantNameFromRequest(HttpRequest request) { - BindingMatch<?> bm = getBindingMatch(request); - return TenantName.from(bm.group(2)); + applicationRepository.deleteTenant(name); + return new TenantDeleteResponse(name); } } diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index fd8bda8f305..73aa5db98e4 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -102,8 +102,7 @@ <binding>http://*/status</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.TenantHandler' bundle='configserver'> - <binding>http://*/application/v2/tenant/</binding> - <binding>http://*/application/v2/tenant/*</binding> + <binding>http://*/application/v2/tenant*</binding> </handler> <handler id='com.yahoo.vespa.config.server.http.v2.SessionCreateHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/*/session</binding> diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java index b0b01ea24b4..a6cf6d24c88 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java @@ -6,16 +6,13 @@ 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.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.HttpRequestBuilder; import com.yahoo.jdisc.http.HttpRequest.Method; +import com.yahoo.restapi.RestApiTestDriver; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.MockProvisioner; import com.yahoo.vespa.config.server.application.OrchestratorMock; -import com.yahoo.vespa.config.server.http.BadRequestException; -import com.yahoo.vespa.config.server.http.NotFoundException; import com.yahoo.vespa.config.server.session.PrepareParams; -import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.TenantRepository; import com.yahoo.vespa.config.server.tenant.TestTenantRepository; import org.junit.After; @@ -29,11 +26,11 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import static org.hamcrest.CoreMatchers.is; +import static com.yahoo.jdisc.http.HttpRequest.Method.DELETE; +import static com.yahoo.jdisc.http.HttpRequest.Method.GET; +import static com.yahoo.jdisc.http.HttpRequest.Method.PUT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; public class TenantHandlerTest { @@ -44,6 +41,8 @@ public class TenantHandlerTest { private TenantHandler handler; private final TenantName a = TenantName.from("a"); + private RestApiTestDriver testDriver; + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -63,7 +62,8 @@ public class TenantHandlerTest { .withOrchestrator(new OrchestratorMock()) .withConfigserverConfig(configserverConfig) .build(); - handler = new TenantHandler(TenantHandler.testOnlyContext(), applicationRepository); + handler = new TenantHandler(RestApiTestDriver.createHandlerTestContext(), applicationRepository); + testDriver = RestApiTestDriver.newBuilder(handler).build(); } @After @@ -74,91 +74,81 @@ public class TenantHandlerTest { @Test public void testTenantCreate() throws Exception { assertNull(tenantRepository.getTenant(a)); - TenantCreateResponse response = putSync( - HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT)); - assertResponseEquals(response, "{\"message\":\"Tenant a created.\"}"); + assertResponse(PUT, "/application/v2/tenant/a", + "{\"message\":\"Tenant a created.\"}"); + assertEquals(a, tenantRepository.getTenant(a).getName()); } @Test public void testTenantCreateWithAllPossibleCharactersInName() throws Exception { TenantName tenantName = TenantName.from("aB-9999_foo"); assertNull(tenantRepository.getTenant(tenantName)); - TenantCreateResponse response = putSync( - HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + tenantName, Method.PUT)); - assertResponseEquals(response, "{\"message\":\"Tenant " + tenantName + " created.\"}"); + assertResponse(PUT, "/application/v2/tenant/aB-9999_foo", + "{\"message\":\"Tenant " + tenantName + " created.\"}"); } - @Test(expected=NotFoundException.class) - public void testGetNonExisting() { - handler.handleGET(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/x", Method.GET)); + @Test + public void testGetNonExisting() throws IOException { + assertResponse(GET, "/application/v2/tenant/x", + "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'x' was not found.\"}"); } - + @Test public void testGetAndList() throws Exception { tenantRepository.addTenant(a); - assertResponseEquals((TenantGetResponse) handler.handleGET( - HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.GET)), - "{\"message\":\"Tenant 'a' exists.\"}"); - assertResponseEquals((ListTenantsResponse) handler.handleGET( - HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/", Method.GET)), - "{\"tenants\":[\"default\",\"a\"]}"); + assertResponse(GET, "/application/v2/tenant/a", + "{\"message\":\"Tenant 'a' exists.\"}"); + assertResponse(GET, "/application/v2/tenant/", + "{\"tenants\":[\"default\",\"a\"]}"); + assertResponse(GET, "/application/v2/tenant", + "{\"tenants\":[\"default\",\"a\"]}"); } - @Test(expected=BadRequestException.class) + @Test public void testCreateExisting() throws Exception { assertNull(tenantRepository.getTenant(a)); - TenantCreateResponse response = putSync(HttpRequest.createTestRequest( - "http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT)); - assertResponseEquals(response, "{\"message\":\"Tenant a created.\"}"); + assertResponse(PUT, "/application/v2/tenant/a", + "{\"message\":\"Tenant a created.\"}"); assertEquals(tenantRepository.getTenant(a).getName(), a); - handler.handlePUT(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT)); + assertResponse(PUT, "/application/v2/tenant/a", + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"There already exists a tenant 'a'\"}"); } @Test public void testDelete() throws IOException { - putSync(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.PUT)); - assertEquals(tenantRepository.getTenant(a).getName(), a); - TenantDeleteResponse delResp = (TenantDeleteResponse) handler.handleDELETE( - HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.DELETE)); - assertResponseEquals(delResp, "{\"message\":\"Tenant a deleted.\"}"); + tenantRepository.addTenant(a); + assertResponse(DELETE, "/application/v2/tenant/a", + "{\"message\":\"Tenant a deleted.\"}"); assertNull(tenantRepository.getTenant(a)); } @Test - public void testDeleteTenantWithActiveApplications() { - putSync(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.PUT)); - Tenant tenant = tenantRepository.getTenant(a); - assertEquals(a, tenant.getName()); - + public void testDeleteTenantWithActiveApplications() throws IOException { + tenantRepository.addTenant(a); ApplicationId applicationId = ApplicationId.from(a, ApplicationName.from("foo"), InstanceName.defaultName()); applicationRepository.deploy(testApp, new PrepareParams.Builder().applicationId(applicationId).build()); - try { - handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.DELETE)); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), is("Cannot delete tenant 'a', it has active applications: [a.foo]")); - } + assertResponse(DELETE, "/application/v2/tenant/a", + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Cannot delete tenant 'a', it has active applications: [a.foo]\"}"); } - @Test(expected=NotFoundException.class) - public void testDeleteNonExisting() { - handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/x", Method.DELETE)); - } - - @Test(expected=BadRequestException.class) - public void testIllegalNameSlashes() { - putSync(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a/b", Method.PUT)); + @Test + public void testDeleteNonExisting() throws IOException { + assertResponse(DELETE, "/application/v2/tenant/a", + "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'a' was not found.\"}"); } - private TenantCreateResponse putSync(HttpRequest testRequest) { - return (TenantCreateResponse) handler.handlePUT(testRequest); + @Test + public void testIllegalNameSlashes() throws IOException { + assertResponse(PUT, "/application/v2/tenant/a/b", + "{\"error-code\":\"NOT_FOUND\",\"message\":\"Nothing at '/application/v2/tenant/a/b'\"}"); } - private void assertResponseEquals(HttpResponse response, String payload) throws IOException { + private void assertResponse(Method method, String path, String payload) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - response.render(baos); - assertEquals(baos.toString(StandardCharsets.UTF_8), payload); + testDriver.executeRequest(HttpRequestBuilder.create(method, path).build()) + .render(baos); + assertEquals(payload, baos.toString(StandardCharsets.UTF_8)); } } diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java index d9da320499f..68e46a3a9b8 100644 --- a/container-core/src/main/java/com/yahoo/restapi/RestApiException.java +++ b/container-core/src/main/java/com/yahoo/restapi/RestApiException.java @@ -42,6 +42,7 @@ public class RestApiException extends RuntimeException { public static class NotFound extends RestApiException { public NotFound() { this(null, null); } + public NotFound(HttpRequest request) { this("Nothing at '" + request.getUri().getRawPath() + "'", null); } public NotFound(Throwable cause) { this(cause.getMessage(), cause); } public NotFound(String message) { this(message, null); } public NotFound(String message, Throwable cause) { super(ErrorResponse::notFoundError, message, cause); } @@ -50,7 +51,8 @@ public class RestApiException extends RuntimeException { public static class MethodNotAllowed extends RestApiException { public MethodNotAllowed() { super(ErrorResponse::methodNotAllowed, "Method not allowed", null); } public MethodNotAllowed(HttpRequest request) { - super(ErrorResponse::methodNotAllowed, "Method '" + request.getMethod().name() + "' is not allowed", null); + super(ErrorResponse::methodNotAllowed, "Method '" + request.getMethod().name() + "' is not allowed at '" + + request.getUri().getRawPath() + "'", null); } } diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java index 8ba94f9aca9..d63add5ed1d 100644 --- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java +++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.ListIterator; @@ -144,7 +145,7 @@ class RestApiImpl implements RestApi { private static Route createDefaultRoute() { RouteBuilder routeBuilder = new RouteBuilderImpl("{*}") .defaultHandler(context -> { - throw new RestApiException.NotFound(); + throw new RestApiException.NotFound(context.request()); }); return ((RouteBuilderImpl)routeBuilder).build(); } @@ -155,6 +156,8 @@ class RestApiImpl implements RestApi { if (!disableDefaultMappers){ exceptionMappers.add(new ExceptionMapperHolder<>(RestApiException.class, (context, exception) -> exception.response())); } + // Topologically sort children before superclasses, so most the specific match is found by iterating through mappers in order. + exceptionMappers.sort((a, b) -> (a.type.isAssignableFrom(b.type) ? 1 : 0) + (b.type.isAssignableFrom(a.type) ? -1 : 0)); return exceptionMappers; } diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java index 028d0d69df6..64bcaf15a69 100644 --- a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java +++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java @@ -249,7 +249,7 @@ public class BindingSetTestCase { handlers.put(new UriPattern("http://*/config/v1/*/"), foo1); handlers.put(new UriPattern("http://*/config/v1/*/*"), foo2); handlers.put(new UriPattern("http://*/config/v1/*/*/"), foo3); - handlers.put(new UriPattern("http://*/application/v2/tenant/"), foo4); + handlers.put(new UriPattern("http://*/application/v2/tenant*"), foo4); handlers.put(new UriPattern("http://*/application/v2/tenant/*"), foo5); handlers.put(new UriPattern("http://*/application/v2/tenant/*/session"), foo6); handlers.put(new UriPattern("http://*/application/v2/tenant/*/session/*/prepared"), foo7); @@ -276,7 +276,7 @@ public class BindingSetTestCase { assertSame(foo3, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" + "/config/v1/cloud.config.log.logd/admin/"))); assertSame(foo4, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" + - "/application/v2/tenant/"))); + "/application/v2/tenant"))); assertSame(foo5, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" + "/application/v2/tenant/b"))); assertSame(foo6, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" + diff --git a/node-repository/src/main/config/node-repository.xml b/node-repository/src/main/config/node-repository.xml index a12e2a8b11c..8a6e466fdf3 100644 --- a/node-repository/src/main/config/node-repository.xml +++ b/node-repository/src/main/config/node-repository.xml @@ -14,6 +14,7 @@ </handler> <handler id="com.yahoo.vespa.hosted.provision.restapi.LoadBalancersV1ApiHandler" bundle="node-repository"> + <binding>http://*/loadbalancers/v1</binding> <binding>http://*/loadbalancers/v1/*</binding> </handler> diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiHandler.java index f81e3240397..e73f97304c1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiHandler.java @@ -1,53 +1,37 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.restapi; -import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; -import com.yahoo.restapi.ErrorResponse; -import com.yahoo.vespa.hosted.provision.NoSuchNodeException; +import com.yahoo.restapi.RestApi; +import com.yahoo.restapi.RestApiRequestHandler; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.yolean.Exceptions; import javax.inject.Inject; -import java.util.logging.Level; /** * @author mpolden + * @author jonmv */ -public class LoadBalancersV1ApiHandler extends LoggingRequestHandler { +public class LoadBalancersV1ApiHandler extends RestApiRequestHandler<LoadBalancersV1ApiHandler> { private final NodeRepository nodeRepository; @Inject public LoadBalancersV1ApiHandler(LoggingRequestHandler.Context parentCtx, NodeRepository nodeRepository) { - super(parentCtx); + super(parentCtx, LoadBalancersV1ApiHandler::createRestApiDefinition); this.nodeRepository = nodeRepository; } - @Override - public HttpResponse handle(HttpRequest request) { - try { - switch (request.getMethod()) { - case GET: return handleGET(request); - default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - } - } - catch (NotFoundException | NoSuchNodeException e) { - return ErrorResponse.notFoundError(Exceptions.toMessageString(e)); - } - catch (IllegalArgumentException e) { - return ErrorResponse.badRequest(Exceptions.toMessageString(e)); - } - catch (RuntimeException e) { - log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); - return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); - } + private static RestApi createRestApiDefinition(LoadBalancersV1ApiHandler self) { + return RestApi.builder() + .addRoute(RestApi.route("/loadbalancers/v1") + .get(self::getLoadBalancers)) + .build(); } - private HttpResponse handleGET(HttpRequest request) { - String path = request.getUri().getPath(); - if (path.equals("/loadbalancers/v1/")) return new LoadBalancersResponse(request, nodeRepository); - throw new NotFoundException("Nothing at path '" + path + "'"); + private HttpResponse getLoadBalancers(RestApi.RequestContext context) { + return new LoadBalancersResponse(context.request(), nodeRepository); } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java index 5e40c0bd9ff..ebaf4d47887 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java @@ -33,10 +33,10 @@ public class ContainerConfig { " <component id='com.yahoo.vespa.flags.InMemoryFlagSource'/>\n" + " <component id='com.yahoo.config.provision.Zone'/>\n" + " <handler id='com.yahoo.vespa.hosted.provision.restapi.NodesV2ApiHandler'>\n" + - " <binding>http://*/nodes/v2/*</binding>\n" + + " <binding>http://*/nodes/v2*</binding>\n" + " </handler>\n" + " <handler id='com.yahoo.vespa.hosted.provision.restapi.LoadBalancersV1ApiHandler'>\n" + - " <binding>http://*/loadbalancers/v1/*</binding>\n" + + " <binding>http://*/loadbalancers/v1*</binding>\n" + " </handler>\n" + " <http>\n" + " <server id='myServer' port='" + port + "'/>\n" + diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiTest.java index e9811985b7d..1ca552bca94 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersV1ApiTest.java @@ -22,6 +22,7 @@ public class LoadBalancersV1ApiTest { @Test public void test_load_balancers() throws Exception { + tester.assertFile(new Request("http://localhost:8080/loadbalancers/v1"), "load-balancers.json"); tester.assertFile(new Request("http://localhost:8080/loadbalancers/v1/"), "load-balancers.json"); tester.assertFile(new Request("http://localhost:8080/loadbalancers/v1/?application=tenant4.application4.instance4"), "load-balancers-single.json"); tester.assertResponse(new Request("http://localhost:8080/loadbalancers/v1/?application=tenant.nonexistent.default"), "{\"loadBalancers\":[]}"); |