aboutsummaryrefslogtreecommitdiffstats
path: root/orchestrator
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
parent8cf392f9573d4061bef71e426c4940426dd121a7 (diff)
Convert remaining JAX-RS resources to request handlers
Diffstat (limited to 'orchestrator')
-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
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandlerTest.java157
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandlerTest.java (renamed from orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/host/HostResourceTest.java)185
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostSuspensionHandlerTest.java9
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandlerTest.java132
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResourceTest.java168
-rw-r--r--orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResourceTest.java92
10 files changed, 693 insertions, 599 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);
- }
- }
-
-}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandlerTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandlerTest.java
new file mode 100644
index 00000000000..176a95d1c04
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/ApplicationSuspensionRequestHandlerTest.java
@@ -0,0 +1,157 @@
+package com.yahoo.vespa.orchestrator.resources;// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.yahoo.cloud.config.ConfigserverConfig;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.jdisc.core.SystemTimer;
+import com.yahoo.jdisc.test.MockMetric;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.flags.InMemoryFlagSource;
+import com.yahoo.vespa.orchestrator.DummyServiceMonitor;
+import com.yahoo.vespa.orchestrator.Orchestrator;
+import com.yahoo.vespa.orchestrator.OrchestratorImpl;
+import com.yahoo.vespa.orchestrator.config.OrchestratorConfig;
+import com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactoryMock;
+import com.yahoo.vespa.orchestrator.status.ZkStatusService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.concurrent.Executors;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests the implementation of the orchestrator Application API.
+ *
+ * @author smorgrav
+ * @author bjorncs
+ */
+class ApplicationSuspensionRequestHandlerTest {
+ private static final String RESOURCE_1 = "mediasearch:imagesearch:default";
+ private static final String RESOURCE_2 = "test-tenant-id:application:instance";
+ private static final String INVALID_RESOURCE_NAME = "something_without_colons";
+
+ ApplicationSuspensionRequestHandler handler;
+
+ @BeforeEach
+ void createHandler() {
+ DummyServiceMonitor serviceMonitor = new DummyServiceMonitor();
+ Orchestrator orchestrator = new OrchestratorImpl(
+ new ClusterControllerClientFactoryMock(),
+ new ZkStatusService(new MockCurator(), new MockMetric(), new SystemTimer(), serviceMonitor),
+ new OrchestratorConfig(new OrchestratorConfig.Builder()),
+ serviceMonitor,
+ new ConfigserverConfig(new ConfigserverConfig.Builder()),
+ new InMemoryFlagSource());
+ var handlerContext = new LoggingRequestHandler.Context(Executors.newSingleThreadExecutor(), new MockMetric());
+ this.handler = new ApplicationSuspensionRequestHandler(handlerContext, orchestrator);
+ }
+
+
+ @Test
+ void get_all_suspended_applications_return_empty_list_initially() throws IOException {
+ HttpResponse httpResponse = executeRequest(Method.GET, "", null);
+ assertEquals(200, httpResponse.getStatus());
+ Set<String> set = parseResponseContent(httpResponse, new TypeReference<>() {});
+ assertEquals(0, set.size());
+ }
+
+ @Test
+ void invalid_application_id_throws_http_400() throws IOException {
+ HttpResponse httpResponse = executeRequest(Method.POST, "", INVALID_RESOURCE_NAME);
+ assertEquals(400, httpResponse.getStatus());
+ }
+
+ @Test
+ void get_application_status_returns_404_for_not_suspended_and_204_for_suspended() throws IOException {
+ // Get on application that is not suspended
+ HttpResponse httpResponse = executeRequest(Method.GET, "/"+RESOURCE_1, null);
+ assertEquals(404, httpResponse.getStatus());
+
+ // Post application
+ httpResponse = executeRequest(Method.POST, "", RESOURCE_1);
+ assertEquals(204, httpResponse.getStatus());
+
+ // Get on the application that now should be in suspended
+ httpResponse = executeRequest(Method.GET, "/"+RESOURCE_1, null);
+ assertEquals(204, httpResponse.getStatus());
+ }
+
+ @Test
+ void delete_works_on_suspended_and_not_suspended_applications() throws IOException {
+ // Delete an application that is not suspended
+ HttpResponse httpResponse = executeRequest(Method.DELETE, "/"+RESOURCE_1, null);
+ assertEquals(204, httpResponse.getStatus());
+
+ // Put application in suspend
+ httpResponse = executeRequest(Method.POST, "", RESOURCE_1);
+ assertEquals(204, httpResponse.getStatus());
+
+ // Check that it is in suspend
+ httpResponse = executeRequest(Method.GET, "/"+RESOURCE_1, null);
+ assertEquals(204, httpResponse.getStatus());
+
+ // Delete it
+ httpResponse = executeRequest(Method.DELETE, "/"+RESOURCE_1, null);
+ assertEquals(204, httpResponse.getStatus());
+
+ // Check that it is not in suspend anymore
+ httpResponse = executeRequest(Method.GET, "/"+RESOURCE_1, null);
+ assertEquals(404, httpResponse.getStatus());
+ }
+
+ @Test
+ void list_applications_returns_the_correct_list_of_suspended_applications() throws IOException {
+ // Test that initially we have the empty set
+ HttpResponse httpResponse = executeRequest(Method.GET, "", null);
+ assertEquals(200, httpResponse.getStatus());
+ Set<String> set = parseResponseContent(httpResponse, new TypeReference<>() {});
+ assertEquals(0, set.size());
+
+ // Add a couple of applications to maintenance
+ executeRequest(Method.POST, "", RESOURCE_1);
+ executeRequest(Method.POST, "", RESOURCE_2);
+
+ // Test that we get them back
+ httpResponse = executeRequest(Method.GET, "", null);
+ assertEquals(200, httpResponse.getStatus());
+ set = parseResponseContent(httpResponse, new TypeReference<>() {});
+ assertEquals(2, set.size());
+
+ // Remove suspend for the first resource
+ executeRequest(Method.DELETE, "/"+RESOURCE_1, null);
+
+ // Test that we are back to the start with the empty set
+ httpResponse = executeRequest(Method.GET, "", null);
+ assertEquals(200, httpResponse.getStatus());
+ set = parseResponseContent(httpResponse, new TypeReference<>() {});
+ assertEquals(1, set.size());
+ assertEquals(RESOURCE_2, set.iterator().next());
+ }
+
+ private HttpResponse executeRequest(Method method, String path, String applicationId) throws IOException {
+ String uri = "http://localhost/orchestrator/v1/suspensions/applications" + path;
+ com.yahoo.container.jdisc.HttpRequest request;
+ if (applicationId != null) {
+ ByteArrayInputStream requestData = new ByteArrayInputStream(applicationId.getBytes(StandardCharsets.UTF_8));
+ request = com.yahoo.container.jdisc.HttpRequest.createTestRequest(uri, method, requestData);
+ } else {
+ request = com.yahoo.container.jdisc.HttpRequest.createTestRequest(uri, method);
+ }
+ return handler.handle(request);
+ }
+
+ private <T> T parseResponseContent(HttpResponse response, TypeReference<T> responseEntityType) throws IOException {
+ assertEquals(200, response.getStatus());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ response.render(out);
+ return handler.restApi().jacksonJsonMapper().readValue(out.toByteArray(), responseEntityType);
+ }
+} \ No newline at end of file
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/host/HostResourceTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandlerTest.java
index d056c3730fd..c34775c1910 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/host/HostResourceTest.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostRequestHandlerTest.java
@@ -1,9 +1,15 @@
-// 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.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.jdisc.test.MockMetric;
import com.yahoo.jdisc.test.TestTimer;
+import com.yahoo.test.json.JsonTestHelper;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ApplicationInstanceId;
import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference;
@@ -41,24 +47,21 @@ import com.yahoo.vespa.orchestrator.status.StatusService;
import com.yahoo.vespa.orchestrator.status.ZkStatusService;
import com.yahoo.vespa.service.monitor.ServiceModel;
import com.yahoo.vespa.service.monitor.ServiceMonitor;
-import org.junit.Before;
-import org.junit.Test;
-
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.InternalServerErrorException;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
-import java.net.URI;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.time.Clock;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executors;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -68,8 +71,10 @@ import static org.mockito.Mockito.when;
/**
* @author hakonhall
+ * @author bjorncs
*/
-public class HostResourceTest {
+class HostRequestHandlerTest {
+
private static final Clock clock = mock(Clock.class);
private static final int SERVICE_MONITOR_CONVERGENCE_LATENCY_SECONDS = 0;
private static final TenantId TENANT_ID = new TenantId("tenantId");
@@ -121,14 +126,14 @@ public class HostResourceTest {
private final OrchestratorImpl alwaysAllowOrchestrator = createAlwaysAllowOrchestrator(clock);
private final OrchestratorImpl hostNotFoundOrchestrator = createHostNotFoundOrchestrator(clock);
- private final UriInfo uriInfo = mock(UriInfo.class);
+ private final OrchestratorImpl alwaysRejectOrchestrator = createAlwaysRejectResolver(clock);
- @Before
- public void setUp() {
+ @BeforeEach
+ void setUp() {
when(clock.instant()).thenReturn(Instant.now());
}
- public static OrchestratorImpl createAlwaysAllowOrchestrator(Clock clock) {
+ static OrchestratorImpl createAlwaysAllowOrchestrator(Clock clock) {
return new OrchestratorImpl(
new AlwaysAllowPolicy(),
new ClusterControllerClientFactoryMock(),
@@ -140,7 +145,7 @@ public class HostResourceTest {
new InMemoryFlagSource());
}
- public static OrchestratorImpl createHostNotFoundOrchestrator(Clock clock) {
+ static OrchestratorImpl createHostNotFoundOrchestrator(Clock clock) {
return new OrchestratorImpl(
new AlwaysAllowPolicy(),
new ClusterControllerClientFactoryMock(),
@@ -152,9 +157,9 @@ public class HostResourceTest {
new InMemoryFlagSource());
}
- public static OrchestratorImpl createAlwaysRejectResolver(Clock clock) {
+ static OrchestratorImpl createAlwaysRejectResolver(Clock clock) {
return new OrchestratorImpl(
- new HostResourceTest.AlwaysFailPolicy(),
+ new AlwaysFailPolicy(),
new ClusterControllerClientFactoryMock(),
EVERY_HOST_IS_UP_HOST_STATUS_SERVICE,
serviceMonitor,
@@ -165,27 +170,20 @@ public class HostResourceTest {
}
@Test
- public void returns_200_on_success() {
- HostResource hostResource =
- new HostResource(alwaysAllowOrchestrator, uriInfo);
-
- final String hostName = "hostname";
+ void returns_200_on_success() throws IOException {
+ HostRequestHandler handler = createHandler(alwaysAllowOrchestrator);
- UpdateHostResponse response = hostResource.suspend(hostName);
-
- assertEquals(hostName, response.hostname());
+ HttpResponse response = executeRequest(handler, Method.PUT, "/orchestrator/v1/hosts/hostname/suspended", null);
+ UpdateHostResponse updateHostResponse = parseResponseContent(handler, response, UpdateHostResponse.class);
+ assertEquals("hostname", updateHostResponse.hostname());
}
@Test
- public void throws_404_when_host_unknown() {
- try {
- HostResource hostResource =
- new HostResource(hostNotFoundOrchestrator, uriInfo);
- hostResource.suspend("hostname");
- fail();
- } catch (WebApplicationException w) {
- assertEquals(404, w.getResponse().getStatus());
- }
+ void throws_404_when_host_unknown() throws IOException {
+ HostRequestHandler handler = createHandler(hostNotFoundOrchestrator);
+
+ HttpResponse response = executeRequest(handler, Method.PUT, "/orchestrator/v1/hosts/hostname/suspended", null);
+ assertEquals(404, response.getStatus());
}
private static class AlwaysFailPolicy implements Policy {
@@ -221,79 +219,61 @@ public class HostResourceTest {
}
@Test
- public void throws_409_when_request_rejected_by_policies() {
- final OrchestratorImpl alwaysRejectResolver = new OrchestratorImpl(
- new AlwaysFailPolicy(),
- new ClusterControllerClientFactoryMock(),
- EVERY_HOST_IS_UP_HOST_STATUS_SERVICE,
- serviceMonitor,
- SERVICE_MONITOR_CONVERGENCE_LATENCY_SECONDS,
- clock,
- applicationApiFactory,
- new InMemoryFlagSource());
+ void throws_409_when_request_rejected_by_policies() throws IOException {
+ HostRequestHandler handler = createHandler(alwaysRejectOrchestrator);
- try {
- HostResource hostResource = new HostResource(alwaysRejectResolver, uriInfo);
- hostResource.suspend("hostname");
- fail();
- } catch (WebApplicationException w) {
- assertEquals(409, w.getResponse().getStatus());
- }
+ HttpResponse response = executeRequest(handler, Method.PUT, "/orchestrator/v1/hosts/hostname/suspended", null);
+ assertEquals(409, response.getStatus());
}
- @Test(expected = BadRequestException.class)
- public void patch_state_may_throw_bad_request() {
+ @Test
+ void patch_state_may_throw_bad_request() throws IOException {
Orchestrator orchestrator = mock(Orchestrator.class);
- HostResource hostResource = new HostResource(orchestrator, uriInfo);
+ HostRequestHandler handler = createHandler(orchestrator);
- String hostNameString = "hostname";
PatchHostRequest request = new PatchHostRequest();
request.state = "bad state";
- hostResource.patch(hostNameString, request);
+ HttpResponse response = executeRequest(handler, Method.PATCH, "/orchestrator/v1/hosts/hostname", request);
+ assertEquals(400, response.getStatus());
}
@Test
- public void patch_works() throws OrchestrationException {
+ void patch_works() throws OrchestrationException, IOException {
Orchestrator orchestrator = mock(Orchestrator.class);
- HostResource hostResource = new HostResource(orchestrator, uriInfo);
+ HostRequestHandler handler = createHandler(orchestrator);
String hostNameString = "hostname";
PatchHostRequest request = new PatchHostRequest();
request.state = "NO_REMARKS";
- PatchHostResponse response = hostResource.patch(hostNameString, request);
+ HttpResponse httpResponse = executeRequest(handler, Method.PATCH, "/orchestrator/v1/hosts/hostname", request);
+ PatchHostResponse response = parseResponseContent(handler, httpResponse, PatchHostResponse.class);
assertEquals(response.description, "ok");
verify(orchestrator, times(1)).setNodeStatus(new HostName(hostNameString), HostStatus.NO_REMARKS);
}
- @Test(expected = InternalServerErrorException.class)
- public void patch_handles_exception_in_orchestrator() throws OrchestrationException {
+ @Test
+ void patch_handles_exception_in_orchestrator() throws OrchestrationException, IOException {
Orchestrator orchestrator = mock(Orchestrator.class);
- HostResource hostResource = new HostResource(orchestrator, uriInfo);
+ HostRequestHandler handler = createHandler(orchestrator);
String hostNameString = "hostname";
PatchHostRequest request = new PatchHostRequest();
request.state = "NO_REMARKS";
doThrow(new OrchestrationException("error")).when(orchestrator).setNodeStatus(new HostName(hostNameString), HostStatus.NO_REMARKS);
- hostResource.patch(hostNameString, request);
+ HttpResponse httpResponse = executeRequest(handler, Method.PATCH, "/orchestrator/v1/hosts/hostname", request);
+ assertEquals(500, httpResponse.getStatus());
}
@Test
- public void getHost_works() throws Exception {
+ void getHost_works() throws Exception {
Orchestrator orchestrator = mock(Orchestrator.class);
- HostResource hostResource = new HostResource(orchestrator, uriInfo);
+ HostRequestHandler handler = createHandler(orchestrator);
HostName hostName = new HostName("hostname");
- UriBuilder baseUriBuilder = mock(UriBuilder.class);
- when(uriInfo.getBaseUriBuilder()).thenReturn(baseUriBuilder);
- when(baseUriBuilder.path(any(String.class))).thenReturn(baseUriBuilder);
- when(baseUriBuilder.path(any(Class.class))).thenReturn(baseUriBuilder);
- URI uri = new URI("https://foo.com/bar");
- when(baseUriBuilder.build()).thenReturn(uri);
-
ServiceInstance serviceInstance = new ServiceInstance(
new ConfigId("configId"),
hostName,
@@ -312,8 +292,11 @@ public class HostResourceTest {
new ApplicationInstanceId("applicationId")),
Collections.singletonList(serviceInstance));
when(orchestrator.getHost(hostName)).thenReturn(host);
- GetHostResponse response = hostResource.getHost(hostName.s());
- assertEquals("https://foo.com/bar", response.applicationUrl());
+
+ HttpResponse httpResponse = executeRequest(handler, Method.GET, "/orchestrator/v1/hosts/hostname", null);
+ GetHostResponse response = parseResponseContent(handler, httpResponse, GetHostResponse.class);
+
+ assertEquals("http://localhost/orchestrator/v1/instances/tenantId:applicationId", response.applicationUrl());
assertEquals("hostname", response.hostname());
assertEquals("ALLOWED_TO_BE_DOWN", response.state());
assertEquals("1970-01-01T00:00:00Z", response.suspendedSince());
@@ -325,18 +308,48 @@ public class HostResourceTest {
}
@Test
- public void throws_409_on_timeout() throws HostNameNotFoundException, HostStateChangeDeniedException {
+ void throws_409_on_timeout() throws HostNameNotFoundException, HostStateChangeDeniedException, IOException {
Orchestrator orchestrator = mock(Orchestrator.class);
doThrow(new UncheckedTimeoutException("Timeout Message")).when(orchestrator).resume(any(HostName.class));
- try {
- HostResource hostResource = new HostResource(orchestrator, uriInfo);
- hostResource.resume("hostname");
- fail();
- } catch (WebApplicationException w) {
- assertEquals(409, w.getResponse().getStatus());
- assertEquals("resume failed: Timeout Message [deadline]", w.getMessage());
+ HostRequestHandler handler = createHandler(orchestrator);
+ HttpResponse httpResponse = executeRequest(handler, Method.DELETE, "/orchestrator/v1/hosts/hostname/suspended", null);
+ assertEquals(409, httpResponse.getStatus());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ httpResponse.render(out);
+ JsonTestHelper.assertJsonEquals("{\n" +
+ " \"hostname\" : \"hostname\",\n" +
+ " \"reason\" : {\n" +
+ " \"constraint\" : \"deadline\",\n" +
+ " \"message\" : \"resume failed: Timeout Message\"\n" +
+ " }\n" +
+ "}",
+ out.toString());
+ }
+
+ private HostRequestHandler createHandler(Orchestrator orchestrator) {
+ var handlerContext = new LoggingRequestHandler.Context(Executors.newSingleThreadExecutor(), new MockMetric());
+ return new HostRequestHandler(handlerContext, orchestrator);
+ }
+
+ private HttpResponse executeRequest(HostRequestHandler handler, Method method, String path, Object requestEntity) throws IOException {
+ String uri = "http://localhost" + path;
+ HttpRequest request;
+ if (requestEntity != null) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ handler.restApi().jacksonJsonMapper().writeValue(out, requestEntity);
+ request = HttpRequest.createTestRequest(uri, method, new ByteArrayInputStream(out.toByteArray()));
+ } else {
+ request = HttpRequest.createTestRequest(uri, method);
}
+ return handler.handle(request);
+ }
+
+ private <T> T parseResponseContent(HostRequestHandler handler, HttpResponse response, Class<T> responseEntityType) throws IOException {
+ assertEquals(200, response.getStatus());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ response.render(out);
+ return handler.restApi().jacksonJsonMapper().readValue(out.toByteArray(), responseEntityType);
}
}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostSuspensionHandlerTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostSuspensionHandlerTest.java
index 9d413526037..be3cb047967 100644
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostSuspensionHandlerTest.java
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/HostSuspensionHandlerTest.java
@@ -12,7 +12,6 @@ import com.yahoo.vespa.orchestrator.BatchInternalErrorException;
import com.yahoo.vespa.orchestrator.Orchestrator;
import com.yahoo.vespa.orchestrator.OrchestratorImpl;
import com.yahoo.vespa.orchestrator.policy.BatchHostStateChangeDeniedException;
-import com.yahoo.vespa.orchestrator.resources.host.HostResourceTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -46,14 +45,14 @@ class HostSuspensionHandlerTest {
@Test
void returns_200_on_success_batch() throws IOException {
- HostSuspensionHandler handler = createHandler(HostResourceTest.createAlwaysAllowOrchestrator(clock));
+ HostSuspensionHandler handler = createHandler(HostRequestHandlerTest.createAlwaysAllowOrchestrator(clock));
HttpResponse response = executeSuspendAllRequest(handler, "parentHostname", List.of("hostname1", "hostname2"));
assertSuccess(response);
}
@Test
void returns_200_empty_batch() throws IOException {
- HostSuspensionHandler handler = createHandler(HostResourceTest.createAlwaysAllowOrchestrator(clock));
+ HostSuspensionHandler handler = createHandler(HostRequestHandlerTest.createAlwaysAllowOrchestrator(clock));
HttpResponse response = executeSuspendAllRequest(handler, "parentHostname", List.of());
assertSuccess(response);
}
@@ -63,14 +62,14 @@ class HostSuspensionHandlerTest {
// hostnames are part of the request body for multi-host.
@Test
void returns_400_when_host_unknown_for_batch() {
- HostSuspensionHandler handler = createHandler(HostResourceTest.createHostNotFoundOrchestrator(clock));
+ HostSuspensionHandler handler = createHandler(HostRequestHandlerTest.createHostNotFoundOrchestrator(clock));
HttpResponse response = executeSuspendAllRequest(handler, "parentHostname", List.of("hostname1", "hostname2"));
assertEquals(400, response.getStatus());
}
@Test
void returns_409_when_request_rejected_by_policies_for_batch() {
- OrchestratorImpl alwaysRejectResolver = HostResourceTest.createAlwaysRejectResolver(clock);
+ OrchestratorImpl alwaysRejectResolver = HostRequestHandlerTest.createAlwaysRejectResolver(clock);
HostSuspensionHandler handler = createHandler(alwaysRejectResolver);
HttpResponse response = executeSuspendAllRequest(handler, "parentHostname", List.of("hostname1", "hostname2"));
assertEquals(409, response.getStatus());
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandlerTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandlerTest.java
new file mode 100644
index 00000000000..bee19a6d6f5
--- /dev/null
+++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/InstanceRequestHandlerTest.java
@@ -0,0 +1,132 @@
+// 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.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.jdisc.test.MockMetric;
+import com.yahoo.jrt.slobrok.api.Mirror;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.applicationmodel.ConfigId;
+import com.yahoo.vespa.applicationmodel.ServiceStatus;
+import com.yahoo.vespa.applicationmodel.ServiceStatusInfo;
+import com.yahoo.vespa.applicationmodel.ServiceType;
+import com.yahoo.vespa.orchestrator.restapi.wire.SlobrokEntryResponse;
+import com.yahoo.vespa.service.manager.UnionMonitorManager;
+import com.yahoo.vespa.service.monitor.SlobrokApi;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+
+import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author bjorncs
+ */
+class InstanceRequestHandlerTest {
+
+ private static final String APPLICATION_INSTANCE_REFERENCE = "tenant:app:prod:us-west-1:instance";
+ private static final ApplicationId APPLICATION_ID = ApplicationId.from(
+ "tenant", "app", "instance");
+ private static final List<Mirror.Entry> ENTRIES = Arrays.asList(
+ new Mirror.Entry("name1", "tcp/spec:1"),
+ new Mirror.Entry("name2", "tcp/spec:2"));
+ private static final ClusterId CLUSTER_ID = new ClusterId("cluster-id");
+ private static final ObjectMapper jsonMapper = new ObjectMapper()
+ .registerModule(new JavaTimeModule())
+ .registerModule(new Jdk8Module());
+
+ private final SlobrokApi slobrokApi = mock(SlobrokApi.class);
+ private final UnionMonitorManager rootManager = mock(UnionMonitorManager.class);
+ private final InstanceRequestHandler handler = new InstanceRequestHandler(
+ new LoggingRequestHandler.Context(Executors.newSingleThreadExecutor(), new MockMetric()),
+ null,
+ null,
+ slobrokApi,
+ rootManager);
+
+
+ @Test
+ void testGetSlobrokEntries() throws Exception {
+ testGetSlobrokEntriesWith("foo", "foo");
+ }
+
+ @Test
+ void testGetSlobrokEntriesWithoutPattern() throws Exception {
+ testGetSlobrokEntriesWith(null, InstanceRequestHandler.DEFAULT_SLOBROK_PATTERN);
+ }
+
+ @Test
+ void testGetServiceStatusInfo() throws IOException {
+ ServiceType serviceType = new ServiceType("serviceType");
+ ConfigId configId = new ConfigId("configId");
+ ServiceStatus serviceStatus = ServiceStatus.UP;
+ when(rootManager.getStatus(APPLICATION_ID, CLUSTER_ID, serviceType, configId))
+ .thenReturn(new ServiceStatusInfo(serviceStatus));
+
+
+ String uriPath = String.format(
+ "/orchestrator/v1/instances/%s/serviceStatusInfo?clusterId=%s&serviceType=%s&configId=%s",
+ APPLICATION_INSTANCE_REFERENCE,
+ CLUSTER_ID.s(),
+ serviceType.s(),
+ configId.s());
+ ServiceStatusInfo serviceStatusInfo = executeRequest(uriPath, new TypeReference<>(){});
+
+ ServiceStatus actualServiceStatus = serviceStatusInfo.serviceStatus();
+ verify(rootManager).getStatus(APPLICATION_ID, CLUSTER_ID, serviceType, configId);
+ assertEquals(serviceStatus, actualServiceStatus);
+ }
+
+ @Test
+ void testBadRequest() {
+ String uriPath = String.format(
+ "/orchestrator/v1/instances/%s/serviceStatusInfo?clusterId=%s",
+ APPLICATION_INSTANCE_REFERENCE,
+ CLUSTER_ID.s());
+ HttpRequest request = HttpRequest.createTestRequest("http://localhost" + uriPath, GET);
+ HttpResponse response = handler.handle(request);
+ assertEquals(400, response.getStatus());
+ }
+
+ private void testGetSlobrokEntriesWith(String pattern, String expectedLookupPattern)
+ throws Exception{
+ when(slobrokApi.lookup(APPLICATION_ID, expectedLookupPattern))
+ .thenReturn(ENTRIES);
+
+ String uriPath = String.format("/orchestrator/v1/instances/%s/slobrok", APPLICATION_INSTANCE_REFERENCE);
+ if (pattern != null) {
+ uriPath += "?pattern=" + pattern;
+ }
+ List<SlobrokEntryResponse> response = executeRequest(uriPath, new TypeReference<>() {});
+
+ verify(slobrokApi).lookup(APPLICATION_ID, expectedLookupPattern);
+
+ String actualJson = jsonMapper.writeValueAsString(response);
+ assertEquals(
+ "[{\"name\":\"name1\",\"spec\":\"tcp/spec:1\"},{\"name\":\"name2\",\"spec\":\"tcp/spec:2\"}]",
+ actualJson);
+ }
+
+ private <T> T executeRequest(String path, TypeReference<T> responseEntityType) throws IOException {
+ HttpRequest request = HttpRequest.createTestRequest("http://localhost" + path, GET);
+ HttpResponse response = handler.handle(request);
+ assertEquals(200, response.getStatus());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ response.render(out);
+ return jsonMapper.readValue(out.toByteArray(), responseEntityType);
+ }
+}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResourceTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResourceTest.java
deleted file mode 100644
index a7514de5acd..00000000000
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/appsuspension/ApplicationSuspensionResourceTest.java
+++ /dev/null
@@ -1,168 +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.application.Application;
-import com.yahoo.application.Networking;
-import com.yahoo.container.Container;
-import com.yahoo.jdisc.http.server.jetty.JettyHttpServer;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.GenericType;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.net.URI;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests the implementation of the orchestrator Application API.
- *
- * @author smorgrav
- */
-public class ApplicationSuspensionResourceTest {
-
- private static final String BASE_PATH = "/orchestrator/v1/suspensions/applications";
- private static final String RESOURCE_1 = "mediasearch:imagesearch:default";
- private static final String RESOURCE_2 = "test-tenant-id:application:instance";
- private static final String INVALID_RESOURCE_NAME = "something_without_colons";
-
- private Application jdiscApplication;
- private WebTarget webTarget;
-
- @Before
- public void setup() throws Exception {
- jdiscApplication = Application.fromServicesXml(servicesXml(), Networking.enable);
- Client client = ClientBuilder.newClient();
-
- JettyHttpServer serverProvider = (JettyHttpServer) Container.get().getServerProviderRegistry().allComponents().get(0);
- String url = "http://localhost:" + serverProvider.getListenPort() + BASE_PATH;
- webTarget = client.target(new URI(url));
- }
-
- @After
- public void teardown() {
- jdiscApplication.close();
- webTarget = null;
- }
-
- @Ignore
- @Test
- public void run_application_locally_for_manual_browser_testing() throws Exception {
- System.out.println(webTarget.getUri());
- Thread.sleep(3600 * 1000);
- }
-
- @Test
- public void get_all_suspended_applications_return_empty_list_initially() {
- Response reply = webTarget.request().get();
- assertEquals(200, reply.getStatus());
- assertEquals("[]", reply.readEntity(String.class));
- }
-
- @Test
- public void invalid_application_id_throws_http_400() {
- Response reply = webTarget.request().post(Entity.entity(INVALID_RESOURCE_NAME, MediaType.APPLICATION_JSON_TYPE));
- assertEquals(400, reply.getStatus());
- }
-
- @Test
- public void get_application_status_returns_404_for_not_suspended_and_204_for_suspended() {
- // Get on application that is not suspended
- Response reply = webTarget.path(RESOURCE_1).request().get();
- assertEquals(404, reply.getStatus());
-
- // Post application
- reply = webTarget.request().post(Entity.entity(RESOURCE_1, MediaType.APPLICATION_JSON_TYPE));
- assertEquals(204, reply.getStatus());
-
- // Get on the application that now should be in suspended
- reply = webTarget.path(RESOURCE_1).request().get();
- assertEquals(204, reply.getStatus());
- }
-
- @Test
- public void delete_works_on_suspended_and_not_suspended_applications() {
- // Delete an application that is not suspended
- Response reply = webTarget.path(RESOURCE_1).request().delete();
- assertEquals(204, reply.getStatus());
-
- // Put application in suspend
- reply = webTarget.request().post(Entity.entity(RESOURCE_1, MediaType.APPLICATION_JSON_TYPE));
- assertEquals(204, reply.getStatus());
-
- // Check that it is in suspend
- reply = webTarget.path(RESOURCE_1).request(MediaType.APPLICATION_JSON).get();
- assertEquals(204, reply.getStatus());
-
- // Delete it
- reply = webTarget.path(RESOURCE_1).request().delete();
- assertEquals(204, reply.getStatus());
-
- // Check that it is not in suspend anymore
- reply = webTarget.path(RESOURCE_1).request(MediaType.APPLICATION_JSON).get();
- assertEquals(404, reply.getStatus());
- }
-
- @Test
- public void list_applications_returns_the_correct_list_of_suspended_applications() {
- // Test that initially we have the empty set
- Response reply = webTarget.request(MediaType.APPLICATION_JSON).get();
- assertEquals(200, reply.getStatus());
- assertEquals("[]", reply.readEntity(String.class));
-
- // Add a couple of applications to maintenance
- webTarget.request().post(Entity.entity(RESOURCE_1, MediaType.APPLICATION_JSON_TYPE));
- webTarget.request().post(Entity.entity(RESOURCE_2, MediaType.APPLICATION_JSON_TYPE));
- assertEquals(200, reply.getStatus());
-
- // Test that we get them back
- Set<String> responses = webTarget.request(MediaType.APPLICATION_JSON_TYPE)
- .get(new GenericType<Set<String>>() {});
- assertEquals(2, responses.size());
-
- // Remove suspend for the first resource
- webTarget.path(RESOURCE_1).request().delete();
-
- // Test that we are back to the start with the empty set
- responses = webTarget.request(MediaType.APPLICATION_JSON_TYPE)
- .get(new GenericType<Set<String>>() {});
- assertEquals(1, responses.size());
- assertEquals(RESOURCE_2, responses.iterator().next());
- }
-
- private String servicesXml() {
- return "<services>\n" +
- " <container version=\"1.0\" jetty=\"true\">\n" +
- " <accesslog type=\"disabled\"/>\n" +
- " <config name=\"container.handler.threadpool\">\n" +
- " <maxthreads>10</maxthreads>\n" +
- " </config>\n" +
- " <component id=\"com.yahoo.vespa.flags.InMemoryFlagSource\" bundle=\"flags\" />\n" +
- " <component id=\"com.yahoo.vespa.curator.mock.MockCurator\" bundle=\"zkfacade\" />\n" +
- " <component id=\"com.yahoo.vespa.orchestrator.status.ZkStatusService\" bundle=\"orchestrator\" />\n" +
- " <component id=\"com.yahoo.vespa.orchestrator.DummyServiceMonitor\" bundle=\"orchestrator\" />\n" +
- " <component id=\"com.yahoo.vespa.orchestrator.OrchestratorImpl\" bundle=\"orchestrator\" />\n" +
- " <component id=\"com.yahoo.vespa.orchestrator.controller.ClusterControllerClientFactoryMock\" bundle=\"orchestrator\" />\n" +
- "\n" +
- " <rest-api path=\"orchestrator/v1/suspensions/applications\" jersey2=\"true\">\n" +
- " <components bundle=\"orchestrator\">\n" +
- " <package>com.yahoo.vespa.orchestrator.resources.appsuspension</package>\n" +
- " </components>\n" +
- " </rest-api>\n" +
- "\n" +
- " <http>\n" +
- " <server id=\"foo\" port=\"0\"/>\n" +
- " </http>\n" +
- " </container>\n" +
- "</services>\n";
- }
-
-}
diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResourceTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResourceTest.java
deleted file mode 100644
index 8e2eeb7410d..00000000000
--- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/resources/instance/InstanceResourceTest.java
+++ /dev/null
@@ -1,92 +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.instance;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.jrt.slobrok.api.Mirror;
-import com.yahoo.vespa.applicationmodel.ClusterId;
-import com.yahoo.vespa.applicationmodel.ConfigId;
-import com.yahoo.vespa.applicationmodel.ServiceStatus;
-import com.yahoo.vespa.applicationmodel.ServiceStatusInfo;
-import com.yahoo.vespa.applicationmodel.ServiceType;
-import com.yahoo.vespa.orchestrator.resources.instance.InstanceResource;
-import com.yahoo.vespa.orchestrator.restapi.wire.SlobrokEntryResponse;
-import com.yahoo.vespa.service.manager.UnionMonitorManager;
-import com.yahoo.vespa.service.monitor.SlobrokApi;
-import org.junit.Test;
-
-import javax.ws.rs.WebApplicationException;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class InstanceResourceTest {
- private static final String APPLICATION_INSTANCE_REFERENCE = "tenant:app:prod:us-west-1:instance";
- private static final ApplicationId APPLICATION_ID = ApplicationId.from(
- "tenant", "app", "instance");
- private static final List<Mirror.Entry> ENTRIES = Arrays.asList(
- new Mirror.Entry("name1", "tcp/spec:1"),
- new Mirror.Entry("name2", "tcp/spec:2"));
- private static final ClusterId CLUSTER_ID = new ClusterId("cluster-id");
-
- private final SlobrokApi slobrokApi = mock(SlobrokApi.class);
- private final UnionMonitorManager rootManager = mock(UnionMonitorManager.class);
- private final InstanceResource resource = new InstanceResource(
- null,
- null,
- slobrokApi,
- rootManager);
-
- @Test
- public void testGetSlobrokEntries() throws Exception {
- testGetSlobrokEntriesWith("foo", "foo");
- }
-
- @Test
- public void testGetSlobrokEntriesWithoutPattern() throws Exception {
- testGetSlobrokEntriesWith(null, InstanceResource.DEFAULT_SLOBROK_PATTERN);
- }
-
- @Test
- public void testGetServiceStatusInfo() {
- ServiceType serviceType = new ServiceType("serviceType");
- ConfigId configId = new ConfigId("configId");
- ServiceStatus serviceStatus = ServiceStatus.UP;
- when(rootManager.getStatus(APPLICATION_ID, CLUSTER_ID, serviceType, configId))
- .thenReturn(new ServiceStatusInfo(serviceStatus));
- ServiceStatus actualServiceStatus = resource.getServiceStatus(
- APPLICATION_INSTANCE_REFERENCE,
- CLUSTER_ID.s(),
- serviceType.s(),
- configId.s()).serviceStatus();
- verify(rootManager).getStatus(APPLICATION_ID, CLUSTER_ID, serviceType, configId);
- assertEquals(serviceStatus, actualServiceStatus);
- }
-
- @Test(expected = WebApplicationException.class)
- public void testBadRequest() {
- resource.getServiceStatus(APPLICATION_INSTANCE_REFERENCE, CLUSTER_ID.s(), null, null);
- }
-
- private void testGetSlobrokEntriesWith(String pattern, String expectedLookupPattern)
- throws Exception{
- when(slobrokApi.lookup(APPLICATION_ID, expectedLookupPattern))
- .thenReturn(ENTRIES);
-
- List<SlobrokEntryResponse> response = resource.getSlobrokEntries(
- APPLICATION_INSTANCE_REFERENCE,
- pattern);
-
- verify(slobrokApi).lookup(APPLICATION_ID, expectedLookupPattern);
-
- ObjectMapper mapper = new ObjectMapper();
- String actualJson = mapper.writeValueAsString(response);
- assertEquals(
- "[{\"name\":\"name1\",\"spec\":\"tcp/spec:1\"},{\"name\":\"name2\",\"spec\":\"tcp/spec:2\"}]",
- actualJson);
- }
-} \ No newline at end of file