diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2021-04-12 20:49:31 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2021-04-12 20:57:24 +0200 |
commit | 97e6a063049a93d7a78301f29c27148f72dcef67 (patch) | |
tree | b515dcb09a5b6e1380e16ca4aaafe373f45a9d18 /orchestrator/src/main/java | |
parent | 8cf392f9573d4061bef71e426c4940426dd121a7 (diff) |
Convert remaining JAX-RS resources to request handlers
Diffstat (limited to 'orchestrator/src/main/java')
-rw-r--r-- | orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandler.java | 158 | ||||
-rw-r--r-- | orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandler.java (renamed from orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/host/HostResource.java) | 152 | ||||
-rw-r--r-- | orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandler.java (renamed from orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResource.java) | 117 | ||||
-rw-r--r-- | orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResource.java | 122 |
4 files changed, 301 insertions, 248 deletions
diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandler.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandler.java new file mode 100644 index 00000000000..4fecbefaffd --- /dev/null +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandler.java @@ -0,0 +1,158 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.orchestrator.resources; + +import com.google.inject.Inject; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.container.jdisc.EmptyResponse; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.jdisc.http.HttpResponse.Status; +import com.yahoo.restapi.RestApi; +import com.yahoo.restapi.RestApiException; +import com.yahoo.restapi.RestApiRequestHandler; +import com.yahoo.vespa.orchestrator.ApplicationIdNotFoundException; +import com.yahoo.vespa.orchestrator.ApplicationStateChangeDeniedException; +import com.yahoo.vespa.orchestrator.Orchestrator; +import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; + +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * @author smorgrav + * @author bjorncs + */ +public class ApplicationSuspensionRequestHandler extends RestApiRequestHandler<ApplicationSuspensionRequestHandler> { + + private static final Logger log = Logger.getLogger(ApplicationSuspensionRequestHandler.class.getName()); + + private final Orchestrator orchestrator; + + @Inject + public ApplicationSuspensionRequestHandler(LoggingRequestHandler.Context context, Orchestrator orchestrator) { + super(context, ApplicationSuspensionRequestHandler::createRestApiDefinition); + this.orchestrator = orchestrator; + } + + private static RestApi createRestApiDefinition(ApplicationSuspensionRequestHandler self) { + return RestApi.builder() + .addRoute(RestApi.route("/orchestrator/v1/suspensions/applications") + .get(self::getApplications) + .post(String.class, self::suspend)) + .addRoute(RestApi.route("/orchestrator/v1/suspensions/applications/{application}") + .get(self::getApplication) + .delete(self::resume)) + .registerJacksonResponseEntity(Set.class) + .build(); + } + + /** + * Lists all applications that is currently suspended. + * + * HTTP Behavior: + * Always 200 + * + * @return A list of application ids of suspended applications + */ + private Set<String> getApplications(RestApi.RequestContext context) { + Set<ApplicationId> refs = orchestrator.getAllSuspendedApplications(); + return refs.stream().map(ApplicationId::serializedForm).collect(Collectors.toSet()); + } + + /** + * Shows the Orchestrator status for an application instance + * + * HTTP Behavior: + * 204 if the application is suspended + * 400 if the applicationId is invalid + * 404 if the application is not suspended + */ + private HttpResponse getApplication(RestApi.RequestContext context) { + String applicationIdString = context.pathParameters().getStringOrThrow("application"); + ApplicationId appId = toApplicationId(applicationIdString); + ApplicationInstanceStatus status; + + try { + status = orchestrator.getApplicationInstanceStatus(appId); + } catch (ApplicationIdNotFoundException e) { + throw new RestApiException.NotFoundException("Application " + applicationIdString + " could not be found", e); + } + + if (status.equals(ApplicationInstanceStatus.NO_REMARKS)) { + throw new RestApiException.NotFoundException("Application " + applicationIdString + " is not suspended"); + } + return new EmptyResponse(Status.NO_CONTENT); + } + + /** + * Ask for permission to temporarily suspend all services for an application instance. + * + * On success all content nodes for this application instance have been set in maintenance mode. + * + * Once the application is ready to resume normal operations, it must finish with resume() (see below). + * + * If the application has already been granted permission to suspend all services, requesting + * suspension again is idempotent and will succeed. + * + * HTTP Behavior: + * 204 is the suspend operation was successful + * 400 if the applicationId is invalid + * 409 if the suspend was denied + */ + private HttpResponse suspend(RestApi.RequestContext context, String applicationIdString) { + ApplicationId applicationId = toApplicationId(applicationIdString); + try { + orchestrator.suspend(applicationId); + } catch (ApplicationIdNotFoundException e) { + log.log(Level.INFO, "ApplicationId " + applicationIdString + " not found.", e); + throw new RestApiException.NotFoundException(e); + } catch (ApplicationStateChangeDeniedException e) { + log.log(Level.INFO, "Suspend for " + applicationIdString + " failed.", e); + throw new RestApiException.Conflict(); + } catch (RuntimeException e) { + log.log(Level.INFO, "Suspend for " + applicationIdString + " failed from unknown reasons", e); + throw new RestApiException.InternalServerError(e); + } + return new EmptyResponse(Status.NO_CONTENT); + } + + /** + * Resume normal operations for all services for an application + * instance that has previously been allowed suspension. + * + * If the host is already registered as running normal operations, then resume() is idempotent + * and will succeed. + * + * HTTP Behavior: + * Returns 204 is the resume operation was successful (or the application was not suspended) + * Returns 400 if the applicationId is invalid + */ + private HttpResponse resume(RestApi.RequestContext context) { + String applicationIdString = context.pathParameters().getStringOrThrow("application"); + ApplicationId applicationId = toApplicationId(applicationIdString); + try { + orchestrator.resume(applicationId); + } catch (ApplicationIdNotFoundException e) { + log.log(Level.INFO, "ApplicationId " + applicationIdString + " not found.", e); + throw new RestApiException.NotFoundException(e); + } catch (ApplicationStateChangeDeniedException e) { + log.log(Level.INFO, "Suspend for " + applicationIdString + " failed.", e); + throw new RestApiException.Conflict(); + } catch (RuntimeException e) { + log.log(Level.INFO, "Suspend for " + applicationIdString + " failed from unknown reasons", e); + throw new RestApiException.InternalServerError(e); + } + return new EmptyResponse(Status.NO_CONTENT); + } + + private ApplicationId toApplicationId(String applicationIdString) { + try { + return ApplicationId.fromSerializedForm(applicationIdString); + } catch (IllegalArgumentException e) { + throw new RestApiException.BadRequest(e); + } + } + +} diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/host/HostResource.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandler.java index c55eeeef069..4dcd76cbb10 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/host/HostResource.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandler.java @@ -1,8 +1,14 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.orchestrator.resources.host; +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.orchestrator.resources; import com.google.common.util.concurrent.UncheckedTimeoutException; -import com.yahoo.container.jaxrs.annotation.Component; +import com.google.inject.Inject; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.jdisc.Response; +import com.yahoo.restapi.JacksonJsonResponse; +import com.yahoo.restapi.RestApi; +import com.yahoo.restapi.RestApiException; +import com.yahoo.restapi.RestApiRequestHandler; import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.orchestrator.Host; import com.yahoo.vespa.orchestrator.HostNameNotFoundException; @@ -10,8 +16,6 @@ import com.yahoo.vespa.orchestrator.OrchestrationException; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.orchestrator.policy.HostStateChangeDeniedException; import com.yahoo.vespa.orchestrator.policy.HostedVespaPolicy; -import com.yahoo.vespa.orchestrator.resources.instance.InstanceResource; -import com.yahoo.vespa.orchestrator.restapi.HostApi; import com.yahoo.vespa.orchestrator.restapi.wire.GetHostResponse; import com.yahoo.vespa.orchestrator.restapi.wire.HostService; import com.yahoo.vespa.orchestrator.restapi.wire.HostStateChangeDenialReason; @@ -20,16 +24,6 @@ import com.yahoo.vespa.orchestrator.restapi.wire.PatchHostResponse; import com.yahoo.vespa.orchestrator.restapi.wire.UpdateHostResponse; import com.yahoo.vespa.orchestrator.status.HostStatus; -import javax.inject.Inject; -import javax.ws.rs.BadRequestException; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.Path; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; import java.net.URI; import java.time.Instant; import java.util.List; @@ -39,30 +33,47 @@ import java.util.stream.Collectors; /** * @author oyving + * @author bjorncs */ -@Path("") -public class HostResource implements HostApi { - private static final Logger log = Logger.getLogger(HostResource.class.getName()); +public class HostRequestHandler extends RestApiRequestHandler<HostRequestHandler> { + + private static final Logger log = Logger.getLogger(HostRequestHandler.class.getName()); private final Orchestrator orchestrator; - private final UriInfo uriInfo; @Inject - public HostResource(@Component Orchestrator orchestrator, @Context UriInfo uriInfo) { + public HostRequestHandler(LoggingRequestHandler.Context context, Orchestrator orchestrator) { + super(context, HostRequestHandler::createRestApiDefinition); this.orchestrator = orchestrator; - this.uriInfo = uriInfo; } - @Override - public GetHostResponse getHost(String hostNameString) { + private static RestApi createRestApiDefinition(HostRequestHandler self) { + return RestApi.builder() + .addRoute(RestApi.route("/orchestrator/v1/hosts/{hostname}") + .get(self::getHost) + .patch(PatchHostRequest.class, self::patch)) + .addRoute(RestApi.route("/orchestrator/v1/hosts/{hostname}/suspended") + .put(self::suspend) + .delete(self::resume)) + .registerJacksonRequestEntity(PatchHostRequest.class) + .registerJacksonResponseEntity(GetHostResponse.class) + .registerJacksonResponseEntity(PatchHostResponse.class) + .registerJacksonResponseEntity(UpdateHostResponse.class) + .build(); + } + + /** + * Shows the Orchestrator state of a host. + */ + private GetHostResponse getHost(RestApi.RequestContext context) { + String hostNameString = context.pathParameters().getStringOrThrow("hostname"); HostName hostName = new HostName(hostNameString); try { Host host = orchestrator.getHost(hostName); - URI applicationUri = uriInfo.getBaseUriBuilder() - .path(InstanceResource.class) - .path(host.getApplicationInstanceReference().asString()) - .build(); + URI applicationUri = context.uriBuilder() + .withPath("/orchestrator/v1/instances/" + host.getApplicationInstanceReference().asString()) + .toURI(); List<HostService> hostServices = host.getServiceInstances().stream() .map(serviceInstance -> new HostService( @@ -80,15 +91,18 @@ public class HostResource implements HostApi { hostServices); } catch (UncheckedTimeoutException e) { log.log(Level.FINE, "Failed to get host " + hostName + ": " + e.getMessage()); - throw webExceptionFromTimeout("getHost", hostName, e); + throw restApiExceptionFromTimeout("getHost", hostName, e); } catch (HostNameNotFoundException e) { log.log(Level.FINE, "Host not found: " + hostName); - throw new NotFoundException(e); + throw new RestApiException.NotFoundException(e); } } - @Override - public PatchHostResponse patch(String hostNameString, PatchHostRequest request) { + /** + * Tweak internal Orchestrator state for host. + */ + private PatchHostResponse patch(RestApi.RequestContext context, PatchHostRequest request) { + String hostNameString = context.pathParameters().getStringOrThrow("hostname"); HostName hostName = new HostName(hostNameString); if (request.state != null) { @@ -96,21 +110,21 @@ public class HostResource implements HostApi { try { state = HostStatus.valueOf(request.state); } catch (IllegalArgumentException dummy) { - throw new BadRequestException("Bad state in request: '" + request.state + "'"); + throw new RestApiException.BadRequest("Bad state in request: '" + request.state + "'"); } try { orchestrator.setNodeStatus(hostName, state); } catch (HostNameNotFoundException e) { log.log(Level.FINE, "Host not found: " + hostName); - throw new NotFoundException(e); + throw new RestApiException.NotFoundException(e); } catch (UncheckedTimeoutException e) { log.log(Level.FINE, "Failed to patch " + hostName + ": " + e.getMessage()); - throw webExceptionFromTimeout("patch", hostName, e); + throw restApiExceptionFromTimeout("patch", hostName, e); } catch (OrchestrationException e) { String message = "Failed to set " + hostName + " to " + state + ": " + e.getMessage(); log.log(Level.FINE, message, e); - throw new InternalServerErrorException(message); + throw new RestApiException.InternalServerError(message); } } @@ -119,74 +133,82 @@ public class HostResource implements HostApi { return response; } - @Override - public UpdateHostResponse suspend(String hostNameString) { + /** + * Ask for permission to temporarily suspend all services on a host. + * + * On success, none, some, or all services on the host may already have been effectively suspended, + * e.g. as of Feb 2015, a content node would already be set in the maintenance state. + * + * Once the host is ready to resume normal operations, it must finish with resume() (see below). + * + * If the host has already been granted permission to suspend all services, requesting + * suspension again is idempotent and will succeed. + */ + private UpdateHostResponse suspend(RestApi.RequestContext context) { + String hostNameString = context.pathParameters().getStringOrThrow("hostname"); HostName hostName = new HostName(hostNameString); try { orchestrator.suspend(hostName); } catch (HostNameNotFoundException e) { log.log(Level.FINE, "Host not found: " + hostName); - throw new NotFoundException(e); + throw new RestApiException.NotFoundException(e); } catch (UncheckedTimeoutException e) { log.log(Level.FINE, "Failed to suspend " + hostName + ": " + e.getMessage()); - throw webExceptionFromTimeout("suspend", hostName, e); + throw restApiExceptionFromTimeout("suspend", hostName, e); } catch (HostStateChangeDeniedException e) { log.log(Level.FINE, "Failed to suspend " + hostName + ": " + e.getMessage()); - throw webExceptionWithDenialReason("suspend", hostName, e); + throw restApiExceptionWithDenialReason("suspend", hostName, e); } return new UpdateHostResponse(hostName.s(), null); } - - @Override - public UpdateHostResponse resume(final String hostNameString) { + /** + * Resume normal operations for all services on a host that has previously been allowed suspension. + * + * If the host is already registered as running normal operations, then resume() is idempotent + * and will succeed. + */ + private UpdateHostResponse resume(RestApi.RequestContext context) { + String hostNameString = context.pathParameters().getStringOrThrow("hostname"); HostName hostName = new HostName(hostNameString); try { orchestrator.resume(hostName); } catch (HostNameNotFoundException e) { log.log(Level.FINE, "Host not found: " + hostName); - throw new NotFoundException(e); + throw new RestApiException.NotFoundException(e); } catch (UncheckedTimeoutException e) { log.log(Level.FINE, "Failed to resume " + hostName + ": " + e.getMessage()); - throw webExceptionFromTimeout("resume", hostName, e); + throw restApiExceptionFromTimeout("resume", hostName, e); } catch (HostStateChangeDeniedException e) { log.log(Level.FINE, "Failed to resume " + hostName + ": " + e.getMessage()); - throw webExceptionWithDenialReason("resume", hostName, e); + throw restApiExceptionWithDenialReason("resume", hostName, e); } return new UpdateHostResponse(hostName.s(), null); } - private static WebApplicationException webExceptionFromTimeout(String operationDescription, - HostName hostName, - UncheckedTimeoutException e) { + private RestApiException restApiExceptionFromTimeout(String operationDescription, + HostName hostName, + UncheckedTimeoutException e) { // Return timeouts as 409 Conflict instead of 504 Gateway Timeout to reduce noise in 5xx graphs. - return createWebException(operationDescription, hostName, e, + return createRestApiException(operationDescription, hostName, e, HostedVespaPolicy.DEADLINE_CONSTRAINT, e.getMessage(), Response.Status.CONFLICT); } - private static WebApplicationException webExceptionWithDenialReason( + private RestApiException restApiExceptionWithDenialReason( String operationDescription, HostName hostName, HostStateChangeDeniedException e) { - return createWebException(operationDescription, hostName, e, e.getConstraintName(), e.getMessage(), + return createRestApiException(operationDescription, hostName, e, e.getConstraintName(), e.getMessage(), Response.Status.CONFLICT); } - private static WebApplicationException createWebException(String operationDescription, - HostName hostname, - Exception e, - String constraint, - String message, - Response.Status status) { + private RestApiException createRestApiException( + String operationDescription, HostName hostname, Exception e, String constraint, String message, int status) { HostStateChangeDenialReason hostStateChangeDenialReason = new HostStateChangeDenialReason( constraint, operationDescription + " failed: " + message); UpdateHostResponse response = new UpdateHostResponse(hostname.s(), hostStateChangeDenialReason); - return new WebApplicationException( + return new RestApiException( + new JacksonJsonResponse<>(status, response, restApi().jacksonJsonMapper(), true), hostStateChangeDenialReason.toString(), - e, - Response.status(status) - .entity(response) - .type(MediaType.APPLICATION_JSON_TYPE) - .build()); + e); } } - diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResource.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandler.java index 742f7d6bbd7..5f0c7caf931 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResource.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandler.java @@ -1,9 +1,17 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.orchestrator.resources.instance; - +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.orchestrator.resources; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.inject.Inject; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.container.jaxrs.annotation.Component; +import com.yahoo.container.jdisc.LoggingRequestHandler; import com.yahoo.jrt.slobrok.api.Mirror; +import com.yahoo.restapi.RestApi; +import com.yahoo.restapi.RestApiException; +import com.yahoo.restapi.RestApiRequestHandler; import com.yahoo.vespa.applicationmodel.ApplicationInstance; import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference; import com.yahoo.vespa.applicationmodel.ClusterId; @@ -12,7 +20,6 @@ import com.yahoo.vespa.applicationmodel.HostName; import com.yahoo.vespa.applicationmodel.ServiceStatusInfo; import com.yahoo.vespa.applicationmodel.ServiceType; import com.yahoo.vespa.orchestrator.OrchestratorUtil; -import com.yahoo.vespa.orchestrator.resources.InstanceStatusResponse; import com.yahoo.vespa.orchestrator.restapi.wire.SlobrokEntryResponse; import com.yahoo.vespa.orchestrator.restapi.wire.WireHostInfo; import com.yahoo.vespa.orchestrator.status.HostInfo; @@ -23,14 +30,7 @@ import com.yahoo.vespa.service.manager.UnionMonitorManager; import com.yahoo.vespa.service.monitor.ServiceMonitor; import com.yahoo.vespa.service.monitor.SlobrokApi; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.time.Instant; import java.util.List; @@ -45,10 +45,10 @@ import static com.yahoo.vespa.orchestrator.OrchestratorUtil.parseApplicationInst * This API can be unstable and is not meant to be used programmatically. * * @author andreer - * @author bakksjo + * @author Oyvind Bakksjo + * @author bjorncs */ -@Path("") -public class InstanceResource { +public class InstanceRequestHandler extends RestApiRequestHandler<InstanceRequestHandler> { public static final String DEFAULT_SLOBROK_PATTERN = "**"; @@ -58,26 +58,45 @@ public class InstanceResource { private final ServiceMonitor serviceMonitor; @Inject - public InstanceResource(@Component ServiceMonitor serviceMonitor, - @Component StatusService statusService, - @Component SlobrokApi slobrokApi, - @Component UnionMonitorManager rootManager) { - this.serviceMonitor = serviceMonitor; + public InstanceRequestHandler(LoggingRequestHandler.Context context, + ServiceMonitor serviceMonitor, + StatusService statusService, + SlobrokApi slobrokApi, + UnionMonitorManager rootManager) { + super(context, InstanceRequestHandler::createRestApiDefinition); this.statusService = statusService; this.slobrokApi = slobrokApi; this.rootManager = rootManager; + this.serviceMonitor = serviceMonitor; } - @GET - @Produces(MediaType.APPLICATION_JSON) - public List<ApplicationInstanceReference> getAllInstances() { + private static RestApi createRestApiDefinition(InstanceRequestHandler self) { + return RestApi.builder() + .addRoute(RestApi.route("/orchestrator/v1/instances") + .get(self::getAllInstances)) + .addRoute(RestApi.route("/orchestrator/v1/instances/{instanceId}") + .get(self::getInstance)) + .addRoute(RestApi.route("/orchestrator/v1/instances/{instanceId}/slobrok") + .get(self::getSlobrokEntries)) + .addRoute(RestApi.route("/orchestrator/v1/instances/{instanceId}/serviceStatusInfo") + .get(self::getServiceStatus)) + .registerJacksonResponseEntity(List.class) + .registerJacksonResponseEntity(InstanceStatusResponse.class) + .registerJacksonResponseEntity(ServiceStatusInfo.class) + // Overriding object mapper to change serialization of timestamps + .setObjectMapper(new ObjectMapper() + .registerModule(new JavaTimeModule()) + .registerModule(new Jdk8Module()) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)) + .build(); + } + + private List<ApplicationInstanceReference> getAllInstances(RestApi.RequestContext context) { return serviceMonitor.getAllApplicationInstanceReferences().stream().sorted().collect(Collectors.toList()); } - @GET - @Path("/{instanceId}") - @Produces(MediaType.APPLICATION_JSON) - public InstanceStatusResponse getInstance(@PathParam("instanceId") String instanceIdString) { + private InstanceStatusResponse getInstance(RestApi.RequestContext context) { + String instanceIdString = context.pathParameters().getStringOrThrow("instanceId"); ApplicationInstanceReference instanceId = parseInstanceId(instanceIdString); ApplicationInstance applicationInstance @@ -102,12 +121,9 @@ public class InstanceResource { return new WireHostInfo(hostStatusString, suspendedSinceUtcOrNull); } - @GET - @Path("/{instanceId}/slobrok") - @Produces(MediaType.APPLICATION_JSON) - public List<SlobrokEntryResponse> getSlobrokEntries( - @PathParam("instanceId") String instanceId, - @QueryParam("pattern") String pattern) { + private List<SlobrokEntryResponse> getSlobrokEntries(RestApi.RequestContext context) { + String instanceId = context.pathParameters().getStringOrThrow("instanceId"); + String pattern = context.queryParameters().getString("pattern").orElse(null); ApplicationInstanceReference reference = parseInstanceId(instanceId); ApplicationId applicationId = OrchestratorUtil.toApplicationId(reference); @@ -121,29 +137,14 @@ public class InstanceResource { .collect(Collectors.toList()); } - @GET - @Path("/{instanceId}/serviceStatusInfo") - @Produces(MediaType.APPLICATION_JSON) - public ServiceStatusInfo getServiceStatus( - @PathParam("instanceId") String instanceId, - @QueryParam("clusterId") String clusterIdString, - @QueryParam("serviceType") String serviceTypeString, - @QueryParam("configId") String configIdString) { + private ServiceStatusInfo getServiceStatus(RestApi.RequestContext context) { + String instanceId = context.pathParameters().getStringOrThrow("instanceId"); + String clusterIdString = context.queryParameters().getStringOrThrow("clusterId"); + String serviceTypeString = context.queryParameters().getStringOrThrow("serviceType"); + String configIdString = context.queryParameters().getStringOrThrow("configId"); ApplicationInstanceReference reference = parseInstanceId(instanceId); ApplicationId applicationId = OrchestratorUtil.toApplicationId(reference); - if (clusterIdString == null) { - throwBadRequest("Missing clusterId query parameter"); - } - - if (serviceTypeString == null) { - throwBadRequest("Missing serviceType query parameter"); - } - - if (configIdString == null) { - throwBadRequest("Missing configId query parameter"); - } - ClusterId clusterId = new ClusterId(clusterIdString); ServiceType serviceType = new ServiceType(serviceTypeString); ConfigId configId = new ConfigId(configIdString); @@ -151,18 +152,12 @@ public class InstanceResource { return rootManager.getStatus(applicationId, clusterId, serviceType, configId); } - static ApplicationInstanceReference parseInstanceId(String instanceIdString) { + private static ApplicationInstanceReference parseInstanceId(String instanceIdString) { try { return parseApplicationInstanceReference(instanceIdString); } catch (IllegalArgumentException e) { - throwBadRequest(e.getMessage()); - return null; // Necessary for compiler + throw new RestApiException.BadRequest(e.getMessage(), e); } } - static void throwBadRequest(String message) { - throw new WebApplicationException( - Response.status(Response.Status.BAD_REQUEST).entity(message).build()); - } - } diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResource.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResource.java deleted file mode 100644 index 361b1f5e361..00000000000 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResource.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.orchestrator.resources.appsuspension; - -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.container.jaxrs.annotation.Component; -import com.yahoo.vespa.orchestrator.ApplicationIdNotFoundException; -import com.yahoo.vespa.orchestrator.ApplicationStateChangeDeniedException; -import com.yahoo.vespa.orchestrator.OrchestratorImpl; -import com.yahoo.vespa.orchestrator.restapi.ApplicationSuspensionApi; -import com.yahoo.vespa.orchestrator.status.ApplicationInstanceStatus; - -import javax.inject.Inject; -import javax.ws.rs.BadRequestException; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.Path; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * @author smorgrav - */ -@Path("") -public class ApplicationSuspensionResource implements ApplicationSuspensionApi { - - private static final Logger log = Logger.getLogger(ApplicationSuspensionResource.class.getName()); - - private final OrchestratorImpl orchestrator; - - @Inject - public ApplicationSuspensionResource(@Component OrchestratorImpl orchestrator) { - this.orchestrator = orchestrator; - } - - @Override - public Set<String> getApplications() { - Set<ApplicationId> refs = orchestrator.getAllSuspendedApplications(); - return refs.stream().map(ApplicationId::serializedForm).collect(Collectors.toSet()); - } - - @Override - public void getApplication(String applicationIdString) { - ApplicationId appId = toApplicationId(applicationIdString); - ApplicationInstanceStatus status; - - try { - status = orchestrator.getApplicationInstanceStatus(appId); - } catch (ApplicationIdNotFoundException e) { - throw new NotFoundException("Application " + applicationIdString + " could not be found"); - } - - if (status.equals(ApplicationInstanceStatus.NO_REMARKS)) { - throw new NotFoundException("Application " + applicationIdString + " is not suspended"); - } - - // Return void as we have nothing to return except 204 No - // Content. Unfortunately, Jersey outputs a warning for this case: - // - // The following warnings have been detected: HINT: A HTTP GET - // method, public void com.yahoo.vespa.orchestrator.resources. - // ApplicationSuspensionResource.getApplication(java.lang.String), - // returns a void type. It can be intentional and perfectly fine, - // but it is a little uncommon that GET method returns always "204 - // No Content" - // - // We have whitelisted the warning for our systemtests. - // - // bakksjo has a pending jersey PR fix that avoids making the hint - // become a warning: - // https://github.com/jersey/jersey/pull/212 - // - // TODO: Remove whitelisting and this comment once jersey has been - // fixed. - } - - @Override - public void suspend(String applicationIdString) { - ApplicationId applicationId = toApplicationId(applicationIdString); - try { - orchestrator.suspend(applicationId); - } catch (ApplicationIdNotFoundException e) { - log.log(Level.INFO, "ApplicationId " + applicationIdString + " not found.", e); - throw new NotFoundException(e); - } catch (ApplicationStateChangeDeniedException e) { - log.log(Level.INFO, "Suspend for " + applicationIdString + " failed.", e); - throw new WebApplicationException(Response.Status.CONFLICT); - } catch (RuntimeException e) { - log.log(Level.INFO, "Suspend for " + applicationIdString + " failed from unknown reasons", e); - throw new InternalServerErrorException(e); - } - } - - @Override - public void resume(String applicationIdString) { - ApplicationId applicationId = toApplicationId(applicationIdString); - try { - orchestrator.resume(applicationId); - } catch (ApplicationIdNotFoundException e) { - log.log(Level.INFO, "ApplicationId " + applicationIdString + " not found.", e); - throw new NotFoundException(e); - } catch (ApplicationStateChangeDeniedException e) { - log.log(Level.INFO, "Suspend for " + applicationIdString + " failed.", e); - throw new WebApplicationException(Response.Status.CONFLICT); - } catch (RuntimeException e) { - log.log(Level.INFO, "Suspend for " + applicationIdString + " failed from unknown reasons", e); - throw new InternalServerErrorException(e); - } - } - - private ApplicationId toApplicationId(String applicationIdString) { - try { - return ApplicationId.fromSerializedForm(applicationIdString); - } catch (IllegalArgumentException e) { - throw new BadRequestException(e); - } - } - -} |