Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hubspot.singularity.resources.RequestResource Maven / Gradle / Ivy
package com.hubspot.singularity.resources;
import static com.hubspot.singularity.WebExceptions.checkBadRequest;
import static com.hubspot.singularity.WebExceptions.checkConflict;
import static com.hubspot.singularity.WebExceptions.checkNotNullBadRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.hubspot.jackson.jaxrs.PropertyFiltering;
import com.hubspot.singularity.AgentPlacement;
import com.hubspot.singularity.CrashLoopInfo;
import com.hubspot.singularity.MachineState;
import com.hubspot.singularity.RequestCleanupType;
import com.hubspot.singularity.RequestState;
import com.hubspot.singularity.RequestType;
import com.hubspot.singularity.Singularity;
import com.hubspot.singularity.SingularityAction;
import com.hubspot.singularity.SingularityAuthorizationScope;
import com.hubspot.singularity.SingularityCreateResult;
import com.hubspot.singularity.SingularityDeleteResult;
import com.hubspot.singularity.SingularityDeploy;
import com.hubspot.singularity.SingularityPendingDeploy;
import com.hubspot.singularity.SingularityPendingRequest;
import com.hubspot.singularity.SingularityPendingRequest.PendingType;
import com.hubspot.singularity.SingularityPendingRequestParent;
import com.hubspot.singularity.SingularityRequest;
import com.hubspot.singularity.SingularityRequestBatch;
import com.hubspot.singularity.SingularityRequestBuilder;
import com.hubspot.singularity.SingularityRequestCleanup;
import com.hubspot.singularity.SingularityRequestDeployState;
import com.hubspot.singularity.SingularityRequestHistory.RequestHistoryType;
import com.hubspot.singularity.SingularityRequestParent;
import com.hubspot.singularity.SingularityRequestWithState;
import com.hubspot.singularity.SingularityShellCommand;
import com.hubspot.singularity.SingularityTaskCleanup;
import com.hubspot.singularity.SingularityTaskHealthcheckResult;
import com.hubspot.singularity.SingularityTaskId;
import com.hubspot.singularity.SingularityTransformHelpers;
import com.hubspot.singularity.SingularityUser;
import com.hubspot.singularity.TaskCleanupType;
import com.hubspot.singularity.WebExceptions;
import com.hubspot.singularity.api.SingularityBounceRequest;
import com.hubspot.singularity.api.SingularityDeleteRequestRequest;
import com.hubspot.singularity.api.SingularityExitCooldownRequest;
import com.hubspot.singularity.api.SingularityPauseRequest;
import com.hubspot.singularity.api.SingularityRunNowRequest;
import com.hubspot.singularity.api.SingularityScaleRequest;
import com.hubspot.singularity.api.SingularitySkipHealthchecksRequest;
import com.hubspot.singularity.api.SingularityUnpauseRequest;
import com.hubspot.singularity.api.SingularityUpdateGroupsRequest;
import com.hubspot.singularity.auth.SingularityAuthorizer;
import com.hubspot.singularity.config.ApiPaths;
import com.hubspot.singularity.config.SingularityConfiguration;
import com.hubspot.singularity.data.AgentManager;
import com.hubspot.singularity.data.DeployManager;
import com.hubspot.singularity.data.RequestManager;
import com.hubspot.singularity.data.SingularityValidator;
import com.hubspot.singularity.data.TaskManager;
import com.hubspot.singularity.expiring.SingularityExpiringBounce;
import com.hubspot.singularity.expiring.SingularityExpiringPause;
import com.hubspot.singularity.expiring.SingularityExpiringRequestActionParent;
import com.hubspot.singularity.expiring.SingularityExpiringScale;
import com.hubspot.singularity.expiring.SingularityExpiringSkipHealthchecks;
import com.hubspot.singularity.helpers.RebalancingHelper;
import com.hubspot.singularity.helpers.RequestHelper;
import com.hubspot.singularity.mesos.SingularityAgentAndRackManager;
import com.hubspot.singularity.sentry.SingularityExceptionNotifier;
import com.hubspot.singularity.smtp.SingularityMailer;
import com.ning.http.client.AsyncHttpClient;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.dropwizard.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Path(ApiPaths.REQUEST_RESOURCE_PATH)
@Produces({ MediaType.APPLICATION_JSON })
@Schema(title = "Manages Singularity Requests, the parent object for any deployed task")
@Tags({ @Tag(name = "Requests") })
public class RequestResource extends AbstractRequestResource {
private static final Logger LOG = LoggerFactory.getLogger(RequestResource.class);
private final SingularityMailer mailer;
private final TaskManager taskManager;
private final RebalancingHelper rebalancingHelper;
private final RequestHelper requestHelper;
private final AgentManager agentManager;
private final SingularityConfiguration configuration;
private final SingularityExceptionNotifier exceptionNotifier;
private final SingularityAgentAndRackManager agentAndRackManager;
@Inject
public RequestResource(
SingularityValidator validator,
DeployManager deployManager,
TaskManager taskManager,
RebalancingHelper rebalancingHelper,
RequestManager requestManager,
SingularityMailer mailer,
SingularityAuthorizer authorizationHelper,
RequestHelper requestHelper,
LeaderLatch leaderLatch,
AgentManager agentManager,
AsyncHttpClient httpClient,
@Singularity ObjectMapper objectMapper,
SingularityConfiguration configuration,
SingularityExceptionNotifier exceptionNotifier,
SingularityAgentAndRackManager agentAndRackManager
) {
super(
requestManager,
deployManager,
validator,
authorizationHelper,
httpClient,
leaderLatch,
objectMapper,
requestHelper
);
this.mailer = mailer;
this.taskManager = taskManager;
this.rebalancingHelper = rebalancingHelper;
this.requestHelper = requestHelper;
this.agentManager = agentManager;
this.configuration = configuration;
this.exceptionNotifier = exceptionNotifier;
this.agentAndRackManager = agentAndRackManager;
}
private void submitRequest(
SingularityRequest request,
Optional oldRequestWithState,
Optional historyType,
Optional skipHealthchecks,
Optional message,
Optional maybeBounceRequest,
SingularityUser user
) {
checkNotNullBadRequest(request.getId(), "Request must have an id");
checkConflict(
!requestManager.cleanupRequestExists(request.getId()),
"Request %s is currently cleaning. Try again after a few moments",
request.getId()
);
Optional maybePendingDeploy = deployManager.getPendingDeploy(
request.getId()
);
checkConflict(
!(
maybePendingDeploy.isPresent() &&
maybePendingDeploy.get().getUpdatedRequest().isPresent()
),
"Request %s has a pending deploy that may change the request data. Try again when the deploy has finished",
request.getId()
);
Optional oldRequest = oldRequestWithState.isPresent()
? Optional.of(oldRequestWithState.get().getRequest())
: Optional.empty();
if (oldRequest.isPresent()) {
authorizationHelper.checkForAuthorization(
oldRequest.get(),
user,
SingularityAuthorizationScope.WRITE
);
authorizationHelper.checkForAuthorizedChanges(request, oldRequest.get(), user);
validator.checkActionEnabled(SingularityAction.UPDATE_REQUEST);
} else {
validator.checkActionEnabled(SingularityAction.CREATE_REQUEST);
}
if (
request.getAgentPlacement().isPresent() &&
(
request.getAgentPlacement().get() == AgentPlacement.SPREAD_ALL_SLAVES ||
request.getAgentPlacement().get() == AgentPlacement.SPREAD_ALL_AGENTS
)
) {
checkBadRequest(
validator.isSpreadAllAgentsEnabled(),
"You must enabled spread to all agents in order to use the SPREAD_ALL_AGENTS request type"
);
int currentActiveAgentCount = agentManager.getNumObjectsAtState(
MachineState.ACTIVE
);
request =
request.toBuilder().setInstances(Optional.of(currentActiveAgentCount)).build();
}
if (
!oldRequest.isPresent() ||
!(oldRequest.get().getInstancesSafe() == request.getInstancesSafe())
) {
validator.checkScale(request, Optional.empty());
}
authorizationHelper.checkForAuthorization(
request,
user,
SingularityAuthorizationScope.WRITE
);
RequestState requestState = RequestState.ACTIVE;
if (oldRequestWithState.isPresent()) {
requestState = oldRequestWithState.get().getState();
}
if (
oldRequest.isPresent() &&
request.getInstancesSafe() < oldRequest.get().getInstancesSafe()
) {
// Trigger cleanups for scale down
int newInstances = request.getInstancesSafe();
Optional maybeDeployState = deployManager.getRequestDeployState(
request.getId()
);
if (
maybeDeployState.isPresent() &&
maybeDeployState.get().getActiveDeploy().isPresent()
) {
List remainingActiveTasks = new ArrayList<>();
taskManager
.getActiveTaskIdsForDeploy(
request.getId(),
maybeDeployState.get().getActiveDeploy().get().getDeployId()
)
.forEach(
taskId -> {
if (taskId.getInstanceNo() > newInstances) {
taskManager.createTaskCleanup(
new SingularityTaskCleanup(
Optional.of(user.getId()),
TaskCleanupType.SCALING_DOWN,
System.currentTimeMillis(),
taskId,
message,
Optional.of(UUID.randomUUID().toString()),
Optional.empty()
)
);
} else {
remainingActiveTasks.add(taskId);
}
}
);
int activeRacksWithCapacityCount = agentAndRackManager.getActiveRacksWithCapacityCount();
if (oldRequest.get().getInstancesSafe() > activeRacksWithCapacityCount) {
if (request.isRackSensitive() && configuration.isRebalanceRacksOnScaleDown()) {
rebalancingHelper.rebalanceRacks(
request,
remainingActiveTasks,
user.getEmail()
);
}
}
if (request.getAgentAttributeMinimums().isPresent()) {
Set cleanedTasks = rebalancingHelper.rebalanceAttributeDistribution(
request,
user.getEmail(),
remainingActiveTasks
);
remainingActiveTasks.removeAll(cleanedTasks);
}
}
}
if (
oldRequest.isPresent() &&
!oldRequest.get().getSkipHealthchecks().orElse(false) &&
request.getSkipHealthchecks().orElse(false)
) {
LOG.info(
"Marking pending tasks as healthy for skipHealthchecks on {}",
request.getId()
);
taskManager
.getActiveTaskIdsForRequest(request.getId())
.forEach(
t -> {
// Will only be saved if async healthchecks have not already finished
taskManager.saveHealthcheckResult(
new SingularityTaskHealthcheckResult(
Optional.of(200),
Optional.empty(),
System.currentTimeMillis(),
Optional.of(String.format("Healthchecks skipped by %s", user.getId())),
Optional.empty(),
t,
Optional.empty()
)
);
}
);
}
requestHelper.updateRequest(
request,
oldRequest,
requestState,
historyType,
user.getEmail(),
skipHealthchecks,
message,
maybeBounceRequest
);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Create or update a Singularity Request",
responses = {
@ApiResponse(responseCode = "400", description = "Request object is invalid"),
@ApiResponse(
responseCode = "409",
description = "Request object is being cleaned. Try again shortly"
)
}
)
public SingularityRequestParent postRequest(
@Parameter(hidden = true) @Auth SingularityUser user,
@Context HttpServletRequest requestContext,
@RequestBody(
required = true,
description = "The Singularity request to create or update"
) SingularityRequest request
) {
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
request,
() -> postRequest(request, user)
);
}
public SingularityRequestParent postRequest(
SingularityRequest request,
SingularityUser user
) {
submitRequest(
request,
requestManager.getRequest(request.getId()),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
user
);
return fillEntireRequest(fetchRequestWithState(request.getId(), user));
}
private String getAndCheckDeployId(String requestId) {
Optional maybeDeployId = deployManager.getInUseDeployId(requestId);
checkConflict(
maybeDeployId.isPresent(),
"Can not schedule/bounce a request (%s) with no deploy",
requestId
);
return maybeDeployId.get();
}
@POST
@Path("/request/{requestId}/groups")
@Operation(
summary = "Update the group, readOnlyGroups, and readWriteGroups for a SingularityRequest",
responses = {
@ApiResponse(responseCode = "400", description = "Request object is invalid"),
@ApiResponse(
responseCode = "401",
description = "User is not authorized to make these updates"
)
}
)
public SingularityRequestParent updateAuthorizedGroups(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
required = true,
description = "The id of the request to update"
) @PathParam("requestId") String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
required = true,
description = "Updated group settings"
) SingularityUpdateGroupsRequest updateGroupsRequest
) {
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
updateGroupsRequest,
() -> updateAuthorizedGroups(user, requestId, updateGroupsRequest)
);
}
private SingularityRequestParent updateAuthorizedGroups(
SingularityUser user,
String requestId,
SingularityUpdateGroupsRequest updateGroupsRequest
) {
SingularityRequestWithState oldRequestWithState = fetchRequestWithState(
requestId,
user
);
authorizationHelper.checkForAuthorization(
oldRequestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
SingularityRequest newRequest = oldRequestWithState
.getRequest()
.toBuilder()
.setGroup(updateGroupsRequest.getGroup())
.setReadWriteGroups(Optional.of(updateGroupsRequest.getReadWriteGroups()))
.setReadOnlyGroups(Optional.of(updateGroupsRequest.getReadOnlyGroups()))
.build();
submitRequest(
newRequest,
Optional.of(oldRequestWithState),
Optional.of(RequestHistoryType.UPDATED),
Optional.empty(),
updateGroupsRequest.getMessage(),
Optional.empty(),
user
);
return fillEntireRequest(fetchRequestWithState(requestId, user));
}
@POST
@Path("/request/{requestId}/groups/auth-check")
@Operation(
summary = "Check authorization for updating the group, readOnlyGroups, and readWriteGroups for a SingularityRequest, without committing the change",
responses = {
@ApiResponse(responseCode = "400", description = "Request object is invalid"),
@ApiResponse(
responseCode = "401",
description = "User is not authorized to make these updates"
)
}
)
public Response checkAuthForGroupsUpdate(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
required = true,
description = "The id of the request to update"
) @PathParam("requestId") String requestId,
@RequestBody(
required = true,
description = "Updated group settings"
) SingularityUpdateGroupsRequest updateGroupsRequest
) {
Optional maybeOldRequestWithState = requestManager.getRequest(
requestId,
false
);
if (!maybeOldRequestWithState.isPresent()) {
// check against dummy request with same groups if none present in zk
authorizationHelper.checkForAuthorization(
new SingularityRequestBuilder(requestId, RequestType.WORKER)
.setGroup(updateGroupsRequest.getGroup())
.setReadWriteGroups(Optional.of(updateGroupsRequest.getReadWriteGroups()))
.setReadOnlyGroups(Optional.of(updateGroupsRequest.getReadOnlyGroups()))
.build(),
user,
SingularityAuthorizationScope.WRITE
);
return Response.ok().build();
}
SingularityRequestWithState oldRequestWithState = maybeOldRequestWithState.get();
authorizationHelper.checkForAuthorization(
oldRequestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
SingularityRequest newRequest = oldRequestWithState
.getRequest()
.toBuilder()
.setGroup(updateGroupsRequest.getGroup())
.setReadWriteGroups(Optional.of(updateGroupsRequest.getReadWriteGroups()))
.setReadOnlyGroups(Optional.of(updateGroupsRequest.getReadOnlyGroups()))
.build();
authorizationHelper.checkForAuthorizedChanges(
newRequest,
oldRequestWithState.getRequest(),
user
);
return Response.ok().build();
}
@POST
@Path("/request/{requestId}/bounce")
@Operation(summary = "Trigger a bounce for a request")
@SuppressFBWarnings("NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS")
public SingularityRequestParent bounce(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request to bounce") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext
) {
return bounce(user, requestId, requestContext, null);
}
@POST
@Path("/request/{requestId}/bounce")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Bounce a specific Singularity request. A bounce launches replacement task(s), and then kills the original task(s) if the replacement(s) are healthy"
)
public SingularityRequestParent bounce(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to bounce") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "Bounce request options"
) SingularityBounceRequest bounceRequest
) {
final Optional maybeBounceRequest = Optional.ofNullable(
bounceRequest
);
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
maybeBounceRequest.orElse(null),
() -> bounce(requestId, maybeBounceRequest, user)
);
}
public SingularityRequestParent bounce(
String requestId,
Optional bounceRequest,
SingularityUser user
) {
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
validator.checkActionEnabled(SingularityAction.BOUNCE_REQUEST);
checkBadRequest(
requestWithState.getRequest().isLongRunning(),
"Can not bounce a %s request (%s)",
requestWithState.getRequest().getRequestType(),
requestWithState
);
checkConflict(
requestWithState.getState() != RequestState.PAUSED,
"Request %s is paused. Unable to bounce (it must be manually unpaused first)",
requestWithState.getRequest().getId()
);
final boolean isIncrementalBounce =
bounceRequest.isPresent() && bounceRequest.get().getIncremental().orElse(false);
validator.checkResourcesForBounce(requestWithState.getRequest(), isIncrementalBounce);
validator.checkRequestForPriorityFreeze(requestWithState.getRequest());
final Optional skipHealthchecks = bounceRequest.isPresent()
? bounceRequest.get().getSkipHealthchecks()
: Optional.empty();
Optional message = Optional.empty();
Optional actionId = Optional.empty();
Optional runBeforeKill = Optional.empty();
if (bounceRequest.isPresent()) {
actionId = bounceRequest.get().getActionId();
message = bounceRequest.get().getMessage();
if (bounceRequest.get().getRunShellCommandBeforeKill().isPresent()) {
validator.checkValidShellCommand(
bounceRequest.get().getRunShellCommandBeforeKill().get()
);
runBeforeKill = bounceRequest.get().getRunShellCommandBeforeKill();
}
}
if (!actionId.isPresent()) {
actionId = Optional.of(UUID.randomUUID().toString());
}
final String deployId = getAndCheckDeployId(requestId);
checkConflict(
!(requestManager.markAsBouncing(requestId) == SingularityCreateResult.EXISTED),
"%s is already bouncing",
requestId
);
requestManager.createCleanupRequest(
new SingularityRequestCleanup(
user.getEmail(),
isIncrementalBounce
? RequestCleanupType.INCREMENTAL_BOUNCE
: RequestCleanupType.BOUNCE,
System.currentTimeMillis(),
Optional.empty(),
Optional.empty(),
requestId,
Optional.of(deployId),
skipHealthchecks,
message,
actionId,
runBeforeKill
)
);
requestManager.bounce(
requestWithState.getRequest(),
System.currentTimeMillis(),
Optional.of(user.getId()),
message
);
final SingularityBounceRequest validatedBounceRequest = validator.checkBounceRequest(
bounceRequest.orElse(SingularityBounceRequest.defaultRequest())
);
requestManager.saveExpiringObject(
new SingularityExpiringBounce(
requestId,
deployId,
Optional.of(user.getId()),
System.currentTimeMillis(),
validatedBounceRequest,
actionId.get()
)
);
return fillEntireRequest(requestWithState);
}
@POST
@Path("/request/{requestId}/run")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Schedule a one-off or scheduled Singularity request for immediate or delayed execution",
responses = {
@ApiResponse(
responseCode = "400",
description = "Singularity Request is not scheduled or one-off"
)
}
)
public SingularityPendingRequestParent scheduleImmediately(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to run") @PathParam(
"requestId"
) String requestId,
@Parameter(hidden = true) @Context HttpServletRequest requestContext,
@QueryParam("minimal") Boolean minimalReturn,
@RequestBody(
description = "Settings specific to this run of the request"
) SingularityRunNowRequest runNowRequest
) {
if (runNowRequest != null) {
runNowRequest
.getEnvOverrides()
.forEach(
(k, v) -> {
checkBadRequest(
!k.equals("STARTED_BY_USER") && !v.contains("STARTED_BY_USER"),
"Cannot override STARTED_BY_USER in env"
);
}
);
checkBadRequest(
!runNowRequest.getCommandLineArgs().isPresent() ||
runNowRequest
.getCommandLineArgs()
.get()
.stream()
.noneMatch(arg -> arg.contains("STARTED_BY_USER")),
"Cannot override STARTED_BY_USER"
);
if (runNowRequest.getResources().isPresent()) {
Optional maybeDeployId = deployManager.getActiveDeployId(requestId);
if (maybeDeployId.isPresent()) {
Optional maybeDeploy = deployManager.getDeploy(
requestId,
maybeDeployId.get()
);
if (maybeDeploy.isPresent() && maybeDeploy.get().getResources().isPresent()) {
int deployPorts = maybeDeploy.get().getResources().get().getNumPorts();
int runNowPorts = runNowRequest.getResources().get().getNumPorts();
checkBadRequest(
deployPorts <= runNowPorts,
"Number of ports in resource overrides must be >= the amount specified in the Singularity deploy (deploy: %d, runNowRequest: %d)",
deployPorts,
runNowPorts
);
}
}
}
}
long start = System.currentTimeMillis();
SingularityPendingRequestParent response;
if (configuration.isProxyRunNowToLeader()) {
response =
maybeProxyToLeader(
requestContext,
SingularityPendingRequestParent.class,
runNowRequest,
() ->
scheduleImmediately(
user,
requestId,
runNowRequest,
Optional.ofNullable(minimalReturn).orElse(false)
)
);
} else {
response =
scheduleImmediately(
user,
requestId,
runNowRequest,
Optional.ofNullable(minimalReturn).orElse(false)
);
}
long duration = System.currentTimeMillis() - start;
LOG.trace("Enqueue for {} took {}ms", requestId, duration);
if (duration > 15000) {
exceptionNotifier.notify(
String.format("Slow enqueue for %s", requestId),
ImmutableMap.of(
"leader",
Boolean.toString(isLeader()),
"duration",
Long.toString(duration)
)
);
}
return response;
}
public SingularityPendingRequestParent scheduleImmediately(
SingularityUser user,
String requestId,
SingularityRunNowRequest runNowRequest
) {
return scheduleImmediately(user, requestId, runNowRequest, false);
}
public SingularityPendingRequestParent scheduleImmediately(
SingularityUser user,
String requestId,
SingularityRunNowRequest runNowRequest,
boolean minimalReturn
) {
final Optional maybeRunNowRequest = Optional.ofNullable(
runNowRequest
);
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
checkConflict(
requestWithState.getState() != RequestState.PAUSED,
"Request %s is paused. Unable to run now (it must be manually unpaused first)",
requestWithState.getRequest().getId()
);
// Check these to avoid unnecessary calls to taskManager
int activeTasks = 0;
int pendingTasks = 0;
boolean isOneoffWithInstances =
requestWithState.getRequest().isOneOff() &&
requestWithState.getRequest().getInstances().isPresent();
if (requestWithState.getRequest().isScheduled() || isOneoffWithInstances) {
activeTasks = taskManager.getActiveTaskIdsForRequest(requestId).size();
}
if (isOneoffWithInstances) {
pendingTasks = taskManager.getPendingTaskIdsForRequest(requestId).size();
}
final SingularityPendingRequest pendingRequest = validator.checkRunNowRequest(
getAndCheckDeployId(requestId),
user.getEmail(),
requestWithState.getRequest(),
maybeRunNowRequest,
activeTasks,
pendingTasks
);
SingularityCreateResult result = requestManager.addToPendingQueue(pendingRequest);
checkConflict(
result != SingularityCreateResult.EXISTED,
"%s is already pending, please try again soon",
requestId
);
if (minimalReturn) {
return SingularityPendingRequestParent.minimalFromRequestWithState(
requestWithState,
pendingRequest
);
} else {
return SingularityPendingRequestParent.fromSingularityRequestParent(
fillEntireRequest(requestWithState),
pendingRequest
);
}
}
@GET
@Path("/request/{requestId}/run/{runId}")
@Operation(
summary = "Retrieve an active task by runId",
responses = {
@ApiResponse(
responseCode = "404",
description = "A task with the specified runID was not found"
)
}
)
public Optional getTaskByRunId(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "Id of the request") @PathParam(
"requestId"
) String requestId,
@Parameter(required = true, description = "Run id to search for") @PathParam(
"runId"
) String runId
) {
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.READ
);
return taskManager.getTaskByRunId(requestId, runId);
}
@POST
@Path("/request/{requestId}/pause")
@Operation(
summary = "Pause a Singularity request, future tasks will not run until it is manually unpaused. API can optionally choose to kill existing tasks",
responses = {
@ApiResponse(
responseCode = "409",
description = "Request is already paused or being cleaned"
)
}
)
@SuppressFBWarnings("NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS")
public SingularityRequestParent pause(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to pause") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext
) {
return pause(user, requestId, requestContext, null);
}
@POST
@Path("/request/{requestId}/pause")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Pause a Singularity request, future tasks will not run until it is manually unpaused. API can optionally choose to kill existing tasks",
responses = {
@ApiResponse(
responseCode = "409",
description = "Request is already paused or being cleaned"
)
}
)
public SingularityRequestParent pause(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to pause") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "Pause Request Options"
) SingularityPauseRequest pauseRequest
) {
final Optional maybePauseRequest = Optional.ofNullable(
pauseRequest
);
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
maybePauseRequest.orElse(null),
() -> pause(requestId, maybePauseRequest, user)
);
}
public SingularityRequestParent pause(
String requestId,
Optional pauseRequest,
SingularityUser user
) {
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
checkConflict(
requestWithState.getState() != RequestState.PAUSED,
"Request %s is paused. Unable to pause (it must be manually unpaused first)",
requestWithState.getRequest().getId()
);
Optional killTasks = Optional.empty();
Optional message = Optional.empty();
Optional actionId = Optional.empty();
Optional runBeforeKill = Optional.empty();
if (pauseRequest.isPresent()) {
killTasks = pauseRequest.get().getKillTasks();
message = pauseRequest.get().getMessage();
if (pauseRequest.get().getRunShellCommandBeforeKill().isPresent()) {
validator.checkValidShellCommand(
pauseRequest.get().getRunShellCommandBeforeKill().get()
);
runBeforeKill = pauseRequest.get().getRunShellCommandBeforeKill();
}
if (pauseRequest.get().getDurationMillis().isPresent() && !actionId.isPresent()) {
actionId = Optional.of(UUID.randomUUID().toString());
}
}
final long now = System.currentTimeMillis();
Optional removeFromLoadBalancer = Optional.empty();
SingularityCreateResult result = requestManager.createCleanupRequest(
new SingularityRequestCleanup(
user.getEmail(),
RequestCleanupType.PAUSING,
now,
killTasks,
removeFromLoadBalancer,
requestId,
Optional.empty(),
Optional.empty(),
message,
actionId,
runBeforeKill
)
);
checkConflict(
result == SingularityCreateResult.CREATED,
"%s is already pausing - try again soon",
requestId,
result
);
mailer.sendRequestPausedMail(
requestWithState.getRequest(),
pauseRequest,
user.getEmail()
);
requestManager.pause(requestWithState.getRequest(), now, user.getEmail(), message);
if (pauseRequest.isPresent() && pauseRequest.get().getDurationMillis().isPresent()) {
requestManager.saveExpiringObject(
new SingularityExpiringPause(
requestId,
user.getEmail(),
System.currentTimeMillis(),
pauseRequest.get(),
actionId.get()
)
);
}
return fillEntireRequest(
new SingularityRequestWithState(
requestWithState.getRequest(),
RequestState.PAUSED,
now
)
);
}
@POST
@Path("/request/{requestId}/unpause")
@SuppressFBWarnings("NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS")
public SingularityRequestParent unpauseNoBody(
@Parameter(hidden = true) @Auth SingularityUser user,
@PathParam("requestId") String requestId,
@Context HttpServletRequest requestContext
) {
return unpause(user, requestId, requestContext, null);
}
@POST
@Path("/request/{requestId}/unpause")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Unpause a Singularity Request, scheduling new tasks immediately",
responses = {
@ApiResponse(responseCode = "409", description = "Request is not paused")
}
)
public SingularityRequestParent unpause(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to unpause") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "Settings for how the unpause should behave"
) SingularityUnpauseRequest unpauseRequest
) {
final Optional maybeUnpauseRequest = Optional.ofNullable(
unpauseRequest
);
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
maybeUnpauseRequest.orElse(null),
() -> unpause(requestId, maybeUnpauseRequest, user)
);
}
public SingularityRequestParent unpause(
String requestId,
Optional unpauseRequest,
SingularityUser user
) {
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
checkConflict(
requestWithState.getState() == RequestState.PAUSED,
"Request %s is not in PAUSED state, it is in %s",
requestId,
requestWithState.getState()
);
Optional message = Optional.empty();
Optional skipHealthchecks = Optional.empty();
if (unpauseRequest.isPresent()) {
message = unpauseRequest.get().getMessage();
skipHealthchecks = unpauseRequest.get().getSkipHealthchecks();
}
requestManager.deleteExpiringObject(SingularityExpiringPause.class, requestId);
final long now = requestHelper.unpause(
requestWithState.getRequest(),
user.getEmail(),
message,
skipHealthchecks
);
return fillEntireRequest(
new SingularityRequestWithState(
requestWithState.getRequest(),
RequestState.ACTIVE,
now
)
);
}
@POST
@Path("/request/{requestId}/exit-cooldown")
@Operation(summary = "Immediately exits cooldown, scheduling new tasks immediately")
@SuppressFBWarnings("NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS")
public SingularityRequestParent exitCooldown(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request to operate on") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext
) {
return exitCooldown(user, requestId, requestContext, null);
}
@POST
@Path("/request/{requestId}/exit-cooldown")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Immediately exits cooldown, scheduling new tasks immediately",
responses = {
@ApiResponse(responseCode = "409", description = "Request is not in cooldown")
}
)
public SingularityRequestParent exitCooldown(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request to operate on") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "Settings related to how an exit cooldown should behave"
) SingularityExitCooldownRequest exitCooldownRequest
) {
final Optional maybeExitCooldownRequest = Optional.ofNullable(
exitCooldownRequest
);
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
maybeExitCooldownRequest.orElse(null),
() -> exitCooldown(requestId, maybeExitCooldownRequest, user)
);
}
public SingularityRequestParent exitCooldown(
String requestId,
Optional exitCooldownRequest,
SingularityUser user
) {
final SingularityRequestWithState requestWithState = fetchRequestWithState(
requestId,
user
);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.WRITE
);
checkConflict(
requestWithState.getState() == RequestState.SYSTEM_COOLDOWN,
"Request %s is not in SYSTEM_COOLDOWN state, it is in %s",
requestId,
requestWithState.getState()
);
final Optional maybeDeployId = deployManager.getInUseDeployId(requestId);
final long now = System.currentTimeMillis();
Optional message = Optional.empty();
Optional skipHealthchecks = Optional.empty();
if (exitCooldownRequest.isPresent()) {
message = exitCooldownRequest.get().getMessage();
skipHealthchecks = exitCooldownRequest.get().getSkipHealthchecks();
}
requestManager.exitCooldown(
requestWithState.getRequest(),
now,
Optional.of(user.getId()),
message
);
if (maybeDeployId.isPresent() && !requestWithState.getRequest().isOneOff()) {
requestManager.addToPendingQueue(
new SingularityPendingRequest(
requestId,
maybeDeployId.get(),
now,
Optional.of(user.getId()),
PendingType.IMMEDIATE,
skipHealthchecks,
message
)
);
}
return fillEntireRequest(requestWithState);
}
@GET
@Path("/batch")
@Operation(summary = "Retrieve a specific batch of requests")
public SingularityRequestBatch getRequestsBatch(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(description = "List of request ids to fetch") @QueryParam(
"id"
) List ids
) {
List found = filterAutorized(
Lists.newArrayList(requestManager.getRequests(ids)),
SingularityAuthorizationScope.READ,
user
)
.stream()
.map(this::fillEntireRequest)
.collect(Collectors.toList());
Set notFound = new HashSet<>(ids);
found.forEach(r -> notFound.remove(r.getRequest().getId()));
return new SingularityRequestBatch(found, notFound);
}
@GET
@PropertyFiltering
@Path("/active")
@Operation(summary = "Retrieve the list of active requests")
public List getActiveRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@Parameter(
description = "Only include requests that the user has operated on or is in a group for"
) @QueryParam("filterRelevantForUser") Boolean filterRelevantForUser,
@Parameter(
description = "Return full data, including deploy data and active task ids"
) @QueryParam("includeFullRequestData") Boolean includeFullRequestData,
@Parameter(description = "The maximum number of results to return") @QueryParam(
"limit"
) Integer limit,
@Parameter(description = "Only return requests of these types") @QueryParam(
"requestType"
) List requestTypes
) {
return requestHelper.fillDataForRequestsAndFilter(
filterAutorized(
Lists.newArrayList(requestManager.getActiveRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
),
user,
valueOrFalse(filterRelevantForUser),
valueOrFalse(includeFullRequestData),
Optional.ofNullable(limit),
requestTypes
);
}
@GET
@Path("/ids")
@Operation(summary = "Retrieve the list of all request ids")
public List getAllRequestIds(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@Parameter(
description = "Filter to request ids that match this string (case insensitive)"
) @QueryParam("requestIdLike") String requestIdLike,
@Parameter(description = "Filter by request state") @QueryParam(
"state"
) Set states
) {
List allIds = filterAutorized(
Lists.newArrayList(requestManager.getRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
)
.stream()
.filter(r -> states == null || states.isEmpty() || states.contains(r.getState()))
.map(r -> r.getRequest().getId())
.collect(Collectors.toList());
if (requestIdLike == null) {
return allIds;
} else {
String lowerCase = requestIdLike.toLowerCase();
return allIds
.stream()
.filter(id -> id.toLowerCase().startsWith(lowerCase))
.collect(Collectors.toList());
}
}
@GET
@Path("/ids/active")
@Operation(summary = "Retrieve the list of active request ids")
public List getActiveRequestIds(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache
) {
return filterAutorized(
Lists.newArrayList(requestManager.getActiveRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
)
.stream()
.map(r -> r.getRequest().getId())
.collect(Collectors.toList());
}
@GET
@PropertyFiltering
@Path("/paused")
@Operation(summary = "Retrieve the list of paused requests")
public List getPausedRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@Parameter(
description = "Only include requests that the user has operated on or is in a group for"
) @QueryParam("filterRelevantForUser") Boolean filterRelevantForUser,
@Parameter(
description = "Return full data, including deploy data and active task ids"
) @QueryParam("includeFullRequestData") Boolean includeFullRequestData,
@Parameter(description = "The maximum number of results to return") @QueryParam(
"limit"
) Integer limit,
@Parameter(description = "Only return requests of these types") @QueryParam(
"requestType"
) List requestTypes
) {
return requestHelper.fillDataForRequestsAndFilter(
filterAutorized(
Lists.newArrayList(requestManager.getPausedRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
),
user,
valueOrFalse(filterRelevantForUser),
valueOrFalse(includeFullRequestData),
Optional.ofNullable(limit),
requestTypes
);
}
@GET
@PropertyFiltering
@Path("/cooldown")
@Operation(summary = "Retrieve the list of requests in system cooldown")
public List getCooldownRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@Parameter(
description = "Only include requests that the user has operated on or is in a group for"
) @QueryParam("filterRelevantForUser") Boolean filterRelevantForUser,
@Parameter(
description = "Return full data, including deploy data and active task ids"
) @QueryParam("includeFullRequestData") Boolean includeFullRequestData,
@Parameter(description = "The maximum number of results to return") @QueryParam(
"limit"
) Integer limit,
@Parameter(description = "Only return requests of these types") @QueryParam(
"requestType"
) List requestTypes
) {
return requestHelper.fillDataForRequestsAndFilter(
filterAutorized(
Lists.newArrayList(requestManager.getCooldownRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
),
user,
valueOrFalse(filterRelevantForUser),
valueOrFalse(includeFullRequestData),
Optional.ofNullable(limit),
requestTypes
);
}
@GET
@PropertyFiltering
@Path("/finished")
@Operation(
summary = "Retreive the list of finished requests (Scheduled requests which have exhausted their schedules)"
)
public List getFinishedRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@QueryParam("filterRelevantForUser") Boolean filterRelevantForUser,
@QueryParam("includeFullRequestData") Boolean includeFullRequestData,
@QueryParam("limit") Integer limit,
@QueryParam("requestType") List requestTypes
) {
return requestHelper.fillDataForRequestsAndFilter(
filterAutorized(
Lists.newArrayList(requestManager.getFinishedRequests(useWebCache(useWebCache))),
SingularityAuthorizationScope.READ,
user
),
user,
valueOrFalse(filterRelevantForUser),
valueOrFalse(includeFullRequestData),
Optional.ofNullable(limit),
requestTypes
);
}
@GET
@PropertyFiltering
@Operation(summary = "Retrieve the list of all requests")
public List getRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache,
@Parameter(
description = "Only include requests that the user has operated on or is in a group for"
) @QueryParam("filterRelevantForUser") Boolean filterRelevantForUser,
@Parameter(
description = "Return full data, including deploy data and active task ids"
) @QueryParam("includeFullRequestData") Boolean includeFullRequestData,
@Parameter(description = "The maximum number of results to return") @QueryParam(
"limit"
) Integer limit,
@Parameter(description = "Only return requests of these types") @QueryParam(
"requestType"
) List requestTypes
) {
return requestHelper.fillDataForRequestsAndFilter(
filterAutorized(
requestManager.getRequests(useWebCache(useWebCache)),
SingularityAuthorizationScope.READ,
user
),
user,
valueOrFalse(filterRelevantForUser),
valueOrFalse(includeFullRequestData),
Optional.ofNullable(limit),
requestTypes
);
}
private boolean valueOrFalse(Boolean input) {
return input == null ? false : input;
}
private List filterAutorized(
List requests,
final SingularityAuthorizationScope scope,
SingularityUser user
) {
if (!authorizationHelper.hasAdminAuthorization(user)) {
return requests
.stream()
.filter(
parent ->
authorizationHelper.isAuthorizedForRequest(parent.getRequest(), user, scope)
)
.collect(Collectors.toList());
}
return requests;
}
@GET
@PropertyFiltering
@Path("/queued/pending")
@Operation(summary = "Retrieve the list of pending requests")
public List getPendingRequests(
@Parameter(hidden = true) @Auth SingularityUser user
) {
return authorizationHelper.filterByAuthorizedRequests(
user,
requestManager.getPendingRequests(),
SingularityTransformHelpers.PENDING_REQUEST_TO_REQUEST_ID::apply,
SingularityAuthorizationScope.READ
);
}
@GET
@PropertyFiltering
@Path("/queued/cleanup")
@Operation(summary = "Retrieve the list of requests being cleaned up")
public List getCleanupRequests(
@Parameter(hidden = true) @Auth SingularityUser user
) {
return authorizationHelper.filterByAuthorizedRequests(
user,
requestManager.getCleanupRequests(),
SingularityTransformHelpers.REQUEST_CLEANUP_TO_REQUEST_ID::apply,
SingularityAuthorizationScope.READ
);
}
@GET
@Path("/request/{requestId}")
@Operation(
summary = "Retrieve a specific Request by ID",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequestParent getRequest(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "Request ID") @PathParam(
"requestId"
) String requestId,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache
) {
return fillEntireRequest(
fetchRequestWithState(requestId, useWebCache(useWebCache), user)
);
}
public SingularityRequestParent getRequest(String requestId, SingularityUser user) {
return fillEntireRequest(fetchRequestWithState(requestId, false, user));
}
@GET
@Path("/request/{requestId}/simple")
@Operation(
summary = "Retrieve a specific Request by ID without additional deploy/task information",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequestWithState getRequestSimple(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "Request ID") @PathParam(
"requestId"
) String requestId,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache
) {
return fetchRequestWithState(requestId, useWebCache(useWebCache), user);
}
@DELETE
@Path("/request/{requestId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Delete a specific Request by ID and return the deleted Request",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequest deleteRequest(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to delete") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "Delete options"
) SingularityDeleteRequestRequest deleteRequest
) {
final Optional maybeDeleteRequest = Optional.ofNullable(
deleteRequest
);
return maybeProxyToLeader(
requestContext,
SingularityRequest.class,
maybeDeleteRequest.orElse(null),
() -> deleteRequest(requestId, maybeDeleteRequest, user)
);
}
public SingularityRequest deleteRequest(
String requestId,
Optional deleteRequest,
SingularityUser user
) {
SingularityRequest request = fetchRequestWithState(requestId, user).getRequest();
authorizationHelper.checkForAuthorization(
request,
user,
SingularityAuthorizationScope.WRITE
);
validator.checkActionEnabled(SingularityAction.REMOVE_REQUEST);
Optional message = Optional.empty();
Optional actionId = Optional.empty();
Optional deleteFromLoadBalancer = Optional.empty();
if (deleteRequest.isPresent()) {
actionId = deleteRequest.get().getActionId();
message = deleteRequest.get().getMessage();
deleteFromLoadBalancer = deleteRequest.get().getDeleteFromLoadBalancer();
}
requestManager.startDeletingRequest(
request,
deleteFromLoadBalancer,
user.getEmail(),
actionId,
message
);
mailer.sendRequestRemovedMail(request, user.getEmail(), message);
return request;
}
@PUT
@Path("/request/{requestId}/scale")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Scale the number of instances up or down for a specific Request",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequestParent scale(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID to scale") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
required = true,
description = "Object to hold number of instances to request"
) SingularityScaleRequest scaleRequest
) {
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
scaleRequest,
() -> scale(requestId, scaleRequest, user)
);
}
public SingularityRequestParent scale(
String requestId,
SingularityScaleRequest scaleRequest,
SingularityUser user
) {
SingularityRequestWithState oldRequestWithState = fetchRequestWithState(
requestId,
user
);
SingularityRequest oldRequest = oldRequestWithState.getRequest();
authorizationHelper.checkForAuthorization(
oldRequest,
user,
SingularityAuthorizationScope.WRITE
);
validator.checkActionEnabled(SingularityAction.SCALE_REQUEST);
SingularityRequest newRequest = oldRequest
.toBuilder()
.setInstances(scaleRequest.getInstances())
.build();
validator.checkScale(newRequest, Optional.empty());
checkBadRequest(
oldRequest.getInstancesSafe() != newRequest.getInstancesSafe(),
"Scale request has no affect on the # of instances (%s)",
newRequest.getInstancesSafe()
);
String scaleMessage = String.format(
"Scaling from %d -> %d",
oldRequest.getInstancesSafe(),
newRequest.getInstancesSafe()
);
if (scaleRequest.getMessage().isPresent()) {
scaleMessage =
String.format("%s -- %s", scaleRequest.getMessage().get(), scaleMessage);
} else {
scaleMessage = String.format("%s", scaleMessage);
}
if (scaleRequest.getBounce().orElse(newRequest.getBounceAfterScale().orElse(false))) {
validator.checkActionEnabled(SingularityAction.BOUNCE_REQUEST);
checkBadRequest(
newRequest.isLongRunning(),
"Can not bounce a %s request (%s)",
newRequest.getRequestType(),
newRequest
);
checkConflict(
oldRequestWithState.getState() != RequestState.PAUSED,
"Request %s is paused. Unable to bounce (it must be manually unpaused first)",
newRequest.getId()
);
checkConflict(
!requestManager.cleanupRequestExists(
newRequest.getId(),
RequestCleanupType.BOUNCE
),
"Request %s is already bouncing cannot bounce again",
newRequest.getId()
);
final boolean isIncrementalBounce = scaleRequest.getIncremental().orElse(true);
validator.checkResourcesForBounce(newRequest, isIncrementalBounce);
validator.checkRequestForPriorityFreeze(newRequest);
SingularityBounceRequest bounceRequest = new SingularityBounceRequest(
Optional.of(isIncrementalBounce),
scaleRequest.getSkipHealthchecks(),
Optional.empty(),
Optional.of(UUID.randomUUID().toString()),
Optional.empty(),
Optional.empty()
);
submitRequest(
newRequest,
Optional.of(oldRequestWithState),
Optional.of(RequestHistoryType.SCALED),
scaleRequest.getSkipHealthchecks(),
Optional.of(scaleMessage),
Optional.of(bounceRequest),
user
);
} else {
submitRequest(
newRequest,
Optional.of(oldRequestWithState),
Optional.of(RequestHistoryType.SCALED),
scaleRequest.getSkipHealthchecks(),
Optional.of(scaleMessage),
Optional.empty(),
user
);
}
if (scaleRequest.getDurationMillis().isPresent()) {
requestManager.saveExpiringObject(
new SingularityExpiringScale(
requestId,
user.getEmail(),
System.currentTimeMillis(),
scaleRequest,
oldRequest.getInstances(),
scaleRequest.getActionId().orElse(UUID.randomUUID().toString()),
scaleRequest.getBounce()
)
);
} else {
requestManager.deleteExpiringObject(SingularityExpiringScale.class, requestId);
}
if (
!scaleRequest.getSkipEmailNotification().isPresent() ||
!scaleRequest.getSkipEmailNotification().get()
) {
mailer.sendRequestScaledMail(
newRequest,
Optional.of(scaleRequest),
oldRequest.getInstances(),
user.getEmail()
);
}
return fillEntireRequest(fetchRequestWithState(requestId, user));
}
private > SingularityRequestParent deleteExpiringObject(
Class clazz,
String requestId,
SingularityUser user
) {
SingularityRequestWithState requestWithState = fetchRequestWithState(requestId, user);
SingularityDeleteResult deleteResult = requestManager.deleteExpiringObject(
clazz,
requestId
);
WebExceptions.checkNotFound(
deleteResult == SingularityDeleteResult.DELETED,
"%s didn't have an expiring %s request",
clazz.getSimpleName(),
requestId
);
return fillEntireRequest(requestWithState);
}
@DELETE
@Path("/request/{requestId}/scale")
@Operation(
summary = "Delete/cancel the expiring scale. This makes the scale request permanent",
responses = {
@ApiResponse(
responseCode = "404",
description = "No Request or expiring scale request for that ID"
)
}
)
public SingularityRequestParent deleteExpiringScale(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID") @PathParam(
"requestId"
) String requestId
) {
return deleteExpiringObject(SingularityExpiringScale.class, requestId, user);
}
@Deprecated
@DELETE
@Path("/request/{requestId}/skipHealthchecks")
@Operation(
summary = "Delete/cancel the expiring skipHealthchecks. This makes the skipHealthchecks request permanent",
responses = {
@ApiResponse(
responseCode = "404",
description = "No Request or expiring skipHealthchecks request for that ID"
)
}
)
public SingularityRequestParent deleteExpiringSkipHealthchecksDeprecated(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID") @PathParam(
"requestId"
) String requestId
) {
return deleteExpiringSkipHealthchecks(user, requestId);
}
@DELETE
@Path("/request/{requestId}/skip-healthchecks")
@Operation(
summary = "Delete/cancel the expiring skipHealthchecks. This makes the skipHealthchecks request permanent",
responses = {
@ApiResponse(
responseCode = "404",
description = "No Request or expiring skipHealthchecks request for that ID"
)
}
)
public SingularityRequestParent deleteExpiringSkipHealthchecks(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID") @PathParam(
"requestId"
) String requestId
) {
return deleteExpiringObject(
SingularityExpiringSkipHealthchecks.class,
requestId,
user
);
}
@DELETE
@Path("/request/{requestId}/pause")
@Operation(
summary = "Delete/cancel the expiring pause. This makes the pause request permanent",
responses = {
@ApiResponse(
responseCode = "404",
description = "No Request or expiring pause request for that ID"
)
}
)
public SingularityRequestParent deleteExpiringPause(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID") @PathParam(
"requestId"
) String requestId
) {
return deleteExpiringObject(SingularityExpiringPause.class, requestId, user);
}
@DELETE
@Path("/request/{requestId}/bounce")
@Operation(
summary = "Delete/cancel the expiring bounce. This makes the bounce request permanent",
responses = {
@ApiResponse(
responseCode = "404",
description = "No Request or expiring bounce request for that ID"
)
}
)
public SingularityRequestParent deleteExpiringBounce(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID") @PathParam(
"requestId"
) String requestId
) {
return deleteExpiringObject(SingularityExpiringBounce.class, requestId, user);
}
@Deprecated
@PUT
@Path("/request/{requestId}/skipHealthchecks")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Update the skipHealthchecks field for the request, possibly temporarily",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequestParent skipHealthchecksDeprecated(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The Request ID to scale") @PathParam(
"requestId"
) String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "SkipHealtchecks options"
) SingularitySkipHealthchecksRequest skipHealthchecksRequest
) {
return skipHealthchecks(user, requestId, requestContext, skipHealthchecksRequest);
}
@PUT
@Path("/request/{requestId}/skip-healthchecks")
@Consumes({ MediaType.APPLICATION_JSON })
@Operation(
summary = "Update the skipHealthchecks field for the request, possibly temporarily",
responses = {
@ApiResponse(responseCode = "404", description = "No Request with that ID")
}
)
public SingularityRequestParent skipHealthchecks(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
required = true,
description = "The Request ID to skip healthchecks for"
) @PathParam("requestId") String requestId,
@Context HttpServletRequest requestContext,
@RequestBody(
description = "SkipHealtchecks options"
) SingularitySkipHealthchecksRequest skipHealthchecksRequest
) {
return maybeProxyToLeader(
requestContext,
SingularityRequestParent.class,
skipHealthchecksRequest,
() -> skipHealthchecks(requestId, skipHealthchecksRequest, user)
);
}
public SingularityRequestParent skipHealthchecks(
String requestId,
SingularitySkipHealthchecksRequest skipHealthchecksRequest,
SingularityUser user
) {
SingularityRequestWithState oldRequestWithState = fetchRequestWithState(
requestId,
user
);
SingularityRequest oldRequest = oldRequestWithState.getRequest();
SingularityRequest newRequest = oldRequest
.toBuilder()
.setSkipHealthchecks(skipHealthchecksRequest.getSkipHealthchecks())
.build();
submitRequest(
newRequest,
Optional.of(oldRequestWithState),
Optional.empty(),
Optional.empty(),
skipHealthchecksRequest.getMessage(),
Optional.empty(),
user
);
if (skipHealthchecksRequest.getDurationMillis().isPresent()) {
requestManager.saveExpiringObject(
new SingularityExpiringSkipHealthchecks(
requestId,
user.getEmail(),
System.currentTimeMillis(),
skipHealthchecksRequest,
oldRequest.getSkipHealthchecks(),
skipHealthchecksRequest.getActionId().orElse(UUID.randomUUID().toString())
)
);
}
return fillEntireRequest(fetchRequestWithState(requestId, user));
}
@GET
@PropertyFiltering
@Path("/lbcleanup")
@Operation(summary = "Retrieve the list of tasks being cleaned from load balancers.")
public Iterable getLbCleanupRequests(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
description = "Fetched a cached version of this data to limit expensive operations"
) @QueryParam("useWebCache") Boolean useWebCache
) {
return authorizationHelper.filterAuthorizedRequestIds(
user,
requestManager.getLbCleanupRequestIds(),
SingularityAuthorizationScope.READ,
useWebCache(useWebCache)
);
}
@GET
@Path("/crashloops")
@Operation(summary = "Retrieve a map of all open crash loop details for all requests")
public Map> getAllCrashLoops(
@Parameter(hidden = true) @Auth SingularityUser user
) {
return authorizationHelper
.filterByAuthorizedRequests(
user,
requestManager.getAllCrashLoops(),
CrashLoopInfo::getRequestId,
SingularityAuthorizationScope.READ
)
.stream()
.collect(Collectors.groupingBy(CrashLoopInfo::getRequestId));
}
@GET
@Path("/request/{requestId}/crashloops")
@Operation(summary = "Retrieve a map of all open crash loop details for all requests")
public List getCrashLoopsForRequest(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(
required = true,
description = "The Request ID to fetch crash loops for"
) @PathParam("requestId") String requestId
) {
final SingularityRequestWithState requestWithState = fetchRequestWithState(
requestId,
user
);
authorizationHelper.checkForAuthorization(
requestWithState.getRequest(),
user,
SingularityAuthorizationScope.READ
);
return requestManager.getCrashLoopsForRequest(requestId);
}
}