summaryrefslogtreecommitdiffstats
path: root/orchestrator/src/main/java
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2021-04-12 20:49:31 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2021-04-12 20:57:24 +0200
commit97e6a063049a93d7a78301f29c27148f72dcef67 (patch)
treeb515dcb09a5b6e1380e16ca4aaafe373f45a9d18 /orchestrator/src/main/java
parent8cf392f9573d4061bef71e426c4940426dd121a7 (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.java158
-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.java122
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);
- }
- }
-
-}