package com.hubspot.singularity.resources;
import static com.hubspot.singularity.WebExceptions.checkBadRequest;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.core.MediaType;
import com.google.common.base.Optional;
import com.google.inject.Inject;
import com.hubspot.singularity.ExtendedTaskState;
import com.hubspot.singularity.OrderDirection;
import com.hubspot.singularity.SingularityAuthorizationScope;
import com.hubspot.singularity.SingularityDeployHistory;
import com.hubspot.singularity.SingularityDeployKey;
import com.hubspot.singularity.SingularityPaginatedResponse;
import com.hubspot.singularity.SingularityRequestHistory;
import com.hubspot.singularity.SingularityService;
import com.hubspot.singularity.SingularityTask;
import com.hubspot.singularity.SingularityTaskHistory;
import com.hubspot.singularity.SingularityTaskHistoryQuery;
import com.hubspot.singularity.SingularityTaskId;
import com.hubspot.singularity.SingularityTaskIdHistory;
import com.hubspot.singularity.SingularityUser;
import com.hubspot.singularity.auth.SingularityAuthorizationHelper;
import com.hubspot.singularity.data.DeployManager;
import com.hubspot.singularity.data.TaskManager;
import com.hubspot.singularity.data.history.DeployHistoryHelper;
import com.hubspot.singularity.data.history.DeployTaskHistoryHelper;
import com.hubspot.singularity.data.history.HistoryManager;
import com.hubspot.singularity.data.history.RequestHistoryHelper;
import com.hubspot.singularity.data.history.TaskHistoryHelper;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
@Path(HistoryResource.PATH)
@Produces({ MediaType.APPLICATION_JSON })
@Api(description = "Manages historical data for tasks, requests, and deploys.", value = HistoryResource.PATH)
public class HistoryResource extends AbstractHistoryResource {
public static final String PATH = SingularityService.API_BASE_PATH + "/history";
public static final int DEFAULT_ARGS_HISTORY_COUNT = 5;
private final DeployHistoryHelper deployHistoryHelper;
private final TaskHistoryHelper taskHistoryHelper;
private final RequestHistoryHelper requestHistoryHelper;
private final DeployTaskHistoryHelper deployTaskHistoryHelper;
@Inject
public HistoryResource(HistoryManager historyManager, TaskManager taskManager, DeployManager deployManager, DeployHistoryHelper deployHistoryHelper, TaskHistoryHelper taskHistoryHelper,
RequestHistoryHelper requestHistoryHelper, SingularityAuthorizationHelper authorizationHelper, Optional<SingularityUser> user, DeployTaskHistoryHelper deployTaskHistoryHelper) {
super(historyManager, taskManager, deployManager, authorizationHelper, user);
this.requestHistoryHelper = requestHistoryHelper;
this.deployHistoryHelper = deployHistoryHelper;
this.taskHistoryHelper = taskHistoryHelper;
this.deployTaskHistoryHelper = deployTaskHistoryHelper;
}
@GET
@Path("/task/{taskId}")
@ApiOperation("Retrieve the history for a specific task.")
public SingularityTaskHistory getHistoryForTask(@ApiParam("Task ID to look up") @PathParam("taskId") String taskId) {
SingularityTaskId taskIdObj = getTaskIdObject(taskId);
return getTaskHistoryRequired(taskIdObj);
}
private Integer getLimitCount(Integer countParam) {
if (countParam == null) {
return 100;
}
checkBadRequest(countParam >= 0, "count param must be non-zero");
if (countParam > 1000) {
return 1000;
}
return countParam;
}
private Integer getLimitStart(Integer limitCount, Integer pageParam) {
if (pageParam == null) {
return 0;
}
checkBadRequest(pageParam >= 1, "page param must be 1 or greater");
return limitCount * (pageParam - 1);
}
private Optional<Integer> getPageCount(Optional<Integer> dataCount, Integer count) {
if (!dataCount.isPresent()) {
return Optional.absent();
}
return Optional.fromNullable((int) Math.ceil((double) dataCount.get() / count));
}
@GET
@Path("/request/{requestId}/tasks/active")
@ApiOperation("Retrieve the history for all active tasks of a specific request.")
public List<SingularityTaskIdHistory> getTaskHistoryForRequest(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
List<SingularityTaskId> activeTaskIds = taskManager.getActiveTaskIdsForRequest(requestId);
return taskHistoryHelper.getTaskHistoriesFor(taskManager, activeTaskIds);
}
@GET
@Path("/request/{requestId}/deploy/{deployId}")
@ApiOperation("Retrieve the history for a specific deploy.")
public SingularityDeployHistory getDeploy(@ApiParam("Request ID for deploy") @PathParam("requestId") String requestId,
@ApiParam("Deploy ID") @PathParam("deployId") String deployId) {
return getDeployHistory(requestId, deployId);
}
@GET
@Path("/request/{requestId}/deploy/{deployId}/tasks/active")
@ApiOperation("Retrieve the task history for a specific deploy.")
public List<SingularityTaskIdHistory> getActiveDeployTasks(
@ApiParam("Request ID for deploy") @PathParam("requestId") String requestId,
@ApiParam("Deploy ID") @PathParam("deployId") String deployId) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
List<SingularityTaskId> activeTaskIds = taskManager.getActiveTaskIdsForDeploy(requestId, deployId);
return taskHistoryHelper.getTaskHistoriesFor(taskManager, activeTaskIds);
}
@GET
@Path("/request/{requestId}/deploy/{deployId}/tasks/inactive")
@ApiOperation("Retrieve the task history for a specific deploy.")
public List<SingularityTaskIdHistory> getInactiveDeployTasks(
@ApiParam("Request ID for deploy") @PathParam("requestId") String requestId,
@ApiParam("Deploy ID") @PathParam("deployId") String deployId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
SingularityDeployKey key = new SingularityDeployKey(requestId, deployId);
return deployTaskHistoryHelper.getBlendedHistory(key, limitStart, limitCount);
}
@GET
@Path("/request/{requestId}/deploy/{deployId}/tasks/inactive/withmetadata")
@ApiOperation("Retrieve the task history for a specific deploy.")
public SingularityPaginatedResponse<SingularityTaskIdHistory> getInactiveDeployTasksWithMetadata(
@ApiParam("Request ID for deploy") @PathParam("requestId") String requestId,
@ApiParam("Deploy ID") @PathParam("deployId") String deployId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
SingularityDeployKey key = new SingularityDeployKey(requestId, deployId);
Optional<Integer> dataCount = deployTaskHistoryHelper.getBlendedHistoryCount(key);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
final List<SingularityTaskIdHistory> data = deployTaskHistoryHelper.getBlendedHistory(key, limitStart, limitCount);
Optional<Integer> pageCount = getPageCount(dataCount, limitCount);
return new SingularityPaginatedResponse<>(dataCount, pageCount, Optional.fromNullable(page), data);
}
@GET
@Path("/tasks")
@ApiOperation("Retrieve the history sorted by startedAt for all inactive tasks.")
public List<SingularityTaskIdHistory> getTaskHistory(
@ApiParam("Optional Request ID to match") @QueryParam("requestId") Optional<String> requestId,
@ApiParam("Optional deploy ID to match") @QueryParam("deployId") Optional<String> deployId,
@ApiParam("Optional runId to match") @QueryParam("runId") Optional<String> runId,
@ApiParam("Optional host to match") @QueryParam("host") Optional<String> host,
@ApiParam("Optional last task status to match") @QueryParam("lastTaskStatus") Optional<ExtendedTaskState> lastTaskStatus,
@ApiParam("Optionally match only tasks started before") @QueryParam("startedBefore") Optional<Long> startedBefore,
@ApiParam("Optionally match only tasks started after") @QueryParam("startedAfter") Optional<Long> startedAfter,
@ApiParam("Optionally match tasks last updated before") @QueryParam("updatedBefore") Optional<Long> updatedBefore,
@ApiParam("Optionally match tasks last updated after") @QueryParam("updatedAfter") Optional<Long> updatedAfter,
@ApiParam("Sort direction") @QueryParam("orderDirection") Optional<OrderDirection> orderDirection,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
if (requestId.isPresent()) {
authorizationHelper.checkForAuthorizationByRequestId(requestId.get(), user, SingularityAuthorizationScope.READ);
} else {
authorizationHelper.checkAdminAuthorization(user);
}
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
return taskHistoryHelper.getBlendedHistory(new SingularityTaskHistoryQuery(requestId, deployId, runId, host, lastTaskStatus, startedBefore, startedAfter,
updatedBefore, updatedAfter, orderDirection), limitStart, limitCount);
}
@GET
@Path("/tasks/withmetadata")
@ApiOperation("Retrieve the history sorted by startedAt for all inactive tasks.")
public SingularityPaginatedResponse<SingularityTaskIdHistory> getTaskHistoryWithMetadata(
@ApiParam("Optional Request ID to match") @QueryParam("requestId") Optional<String> requestId,
@ApiParam("Optional deploy ID to match") @QueryParam("deployId") Optional<String> deployId,
@ApiParam("Optional runId to match") @QueryParam("runId") Optional<String> runId,
@ApiParam("Optional host to match") @QueryParam("host") Optional<String> host,
@ApiParam("Optional last task status to match") @QueryParam("lastTaskStatus") Optional<ExtendedTaskState> lastTaskStatus,
@ApiParam("Optionally match only tasks started before") @QueryParam("startedBefore") Optional<Long> startedBefore,
@ApiParam("Optionally match only tasks started after") @QueryParam("startedAfter") Optional<Long> startedAfter,
@ApiParam("Optionally match tasks last updated before") @QueryParam("updatedBefore") Optional<Long> updatedBefore,
@ApiParam("Optionally match tasks last updated after") @QueryParam("updatedAfter") Optional<Long> updatedAfter,
@ApiParam("Sort direction") @QueryParam("orderDirection") Optional<OrderDirection> orderDirection,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
if (requestId.isPresent()) {
authorizationHelper.checkForAuthorizationByRequestId(requestId.get(), user, SingularityAuthorizationScope.READ);
} else {
authorizationHelper.checkAdminAuthorization(user);
}
final Optional<Integer> dataCount = taskHistoryHelper.getBlendedHistoryCount(new SingularityTaskHistoryQuery(requestId, deployId, runId, host, lastTaskStatus, startedBefore, startedAfter, updatedBefore, updatedAfter, orderDirection));
final int limitCount = getLimitCount(count);
final List<SingularityTaskIdHistory> data = this.getTaskHistory(requestId, deployId, runId, host, lastTaskStatus, startedBefore, startedAfter, updatedBefore, updatedAfter, orderDirection, count, page);
final Optional<Integer> pageCount = getPageCount(dataCount, limitCount);
return new SingularityPaginatedResponse<>(dataCount, pageCount, Optional.fromNullable(page), data);
}
@GET
@Path("/request/{requestId}/tasks")
@ApiOperation("Retrieve the history sorted by startedAt for all inactive tasks of a specific request.")
public List<SingularityTaskIdHistory> getTaskHistoryForRequest(
@ApiParam("Request ID to match") @PathParam("requestId") String requestId,
@ApiParam("Optional deploy ID to match") @QueryParam("deployId") Optional<String> deployId,
@ApiParam("Optional runId to match") @QueryParam("runId") Optional<String> runId,
@ApiParam("Optional host to match") @QueryParam("host") Optional<String> host,
@ApiParam("Optional last task status to match") @QueryParam("lastTaskStatus") Optional<ExtendedTaskState> lastTaskStatus,
@ApiParam("Optionally match only tasks started before") @QueryParam("startedBefore") Optional<Long> startedBefore,
@ApiParam("Optionally match only tasks started after") @QueryParam("startedAfter") Optional<Long> startedAfter,
@ApiParam("Optionally match tasks last updated before") @QueryParam("updatedBefore") Optional<Long> updatedBefore,
@ApiParam("Optionally match tasks last updated after") @QueryParam("updatedAfter") Optional<Long> updatedAfter,
@ApiParam("Sort direction") @QueryParam("orderDirection") Optional<OrderDirection> orderDirection,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
return taskHistoryHelper.getBlendedHistory(new SingularityTaskHistoryQuery(Optional.of(requestId), deployId, runId, host, lastTaskStatus, startedBefore, startedAfter,
updatedBefore, updatedAfter, orderDirection), limitStart, limitCount);
}
@GET
@Path("/request/{requestId}/tasks/withmetadata")
@ApiOperation("Retrieve the history count for all inactive tasks of a specific request.")
public SingularityPaginatedResponse<SingularityTaskIdHistory> getTaskHistoryForRequestWithMetadata(
@ApiParam("Request ID to match") @PathParam("requestId") String requestId,
@ApiParam("Optional deploy ID to match") @QueryParam("deployId") Optional<String> deployId,
@ApiParam("Optional runId to match") @QueryParam("runId") Optional<String> runId,
@ApiParam("Optional host to match") @QueryParam("host") Optional<String> host,
@ApiParam("Optional last task status to match") @QueryParam("lastTaskStatus") Optional<ExtendedTaskState> lastTaskStatus,
@ApiParam("Optionally match only tasks started before") @QueryParam("startedBefore") Optional<Long> startedBefore,
@ApiParam("Optionally match only tasks started after") @QueryParam("startedAfter") Optional<Long> startedAfter,
@ApiParam("Optionally match tasks last updated before") @QueryParam("updatedBefore") Optional<Long> updatedBefore,
@ApiParam("Optionally match tasks last updated after") @QueryParam("updatedAfter") Optional<Long> updatedAfter,
@ApiParam("Sort direction") @QueryParam("orderDirection") Optional<OrderDirection> orderDirection,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Optional<Integer> dataCount = taskHistoryHelper.getBlendedHistoryCount(new SingularityTaskHistoryQuery(Optional.of(requestId), deployId, runId, host, lastTaskStatus, startedBefore, startedAfter, updatedBefore, updatedAfter, orderDirection));
final int limitCount = getLimitCount(count);
final List<SingularityTaskIdHistory> data = this.getTaskHistoryForRequest(requestId, deployId, runId, host, lastTaskStatus, startedBefore, startedAfter, updatedBefore, updatedAfter, orderDirection, count, page);
final Optional<Integer> pageCount = getPageCount(dataCount, limitCount);
return new SingularityPaginatedResponse<>(dataCount, pageCount, Optional.fromNullable(page), data);
}
@GET
@Path("/request/{requestId}/run/{runId}")
@ApiOperation("Retrieve the history for a task by runId")
public Optional<SingularityTaskIdHistory> getTaskHistoryForRequestAndRunId(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("runId to look up") @PathParam("runId") String runId) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
return taskHistoryHelper.getByRunId(requestId, runId);
}
@GET
@Path("/request/{requestId}/deploys")
@ApiOperation("Get deploy history for a single request")
public List<SingularityDeployHistory> getDeploys(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
return deployHistoryHelper.getBlendedHistory(requestId, limitStart, limitCount);
}
@GET
@Path("/request/{requestId}/deploys/withmetadata")
@ApiOperation("Get deploy history with metadata for a single request")
public SingularityPaginatedResponse<SingularityDeployHistory> getDeploysWithMetadata(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Optional<Integer> dataCount = deployHistoryHelper.getBlendedHistoryCount(requestId);
final int limitCount = getLimitCount(count);
final List<SingularityDeployHistory> data = this.getDeploys(requestId, count, page);
final Optional<Integer> pageCount = getPageCount(dataCount, limitCount);
return new SingularityPaginatedResponse<>(dataCount, pageCount, Optional.fromNullable(page), data);
}
@GET
@Path("/request/{requestId}/requests")
@ApiOperation("Get request history for a single request")
public List<SingularityRequestHistory> getRequestHistoryForRequest(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
return requestHistoryHelper.getBlendedHistory(requestId, limitStart, limitCount);
}
@GET
@Path("/request/{requestId}/requests/withmetadata")
@ApiOperation("Get request history for a single request")
public SingularityPaginatedResponse<SingularityRequestHistory> getRequestHistoryForRequestWithMetadata(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final Optional<Integer> dataCount = requestHistoryHelper.getBlendedHistoryCount(requestId);
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
final List<SingularityRequestHistory> data = requestHistoryHelper.getBlendedHistory(requestId, limitStart, limitCount);
final Optional<Integer> pageCount = getPageCount(dataCount, limitCount);
return new SingularityPaginatedResponse<>(dataCount, pageCount, Optional.fromNullable(page), data);
}
@GET
@Path("/requests/search")
@ApiOperation("Search for requests.")
public Iterable<String> getRequestHistoryForRequestLike(
@ApiParam("Request ID prefix to search for") @QueryParam("requestIdLike") String requestIdLike,
@ApiParam("Maximum number of items to return") @QueryParam("count") Integer count,
@ApiParam("Which page of items to view") @QueryParam("page") Integer page,
@QueryParam("useWebCache") Boolean useWebCache) {
final Integer limitCount = getLimitCount(count);
final Integer limitStart = getLimitStart(limitCount, page);
List<String> requestIds = historyManager.getRequestHistoryLike(requestIdLike, limitStart, limitCount);
return authorizationHelper.filterAuthorizedRequestIds(user, requestIds, SingularityAuthorizationScope.READ, useWebCache != null && useWebCache); // TODO: will this screw up pagination? A: yes.
}
@GET
@Path("/request/{requestId}/command-line-args")
@ApiOperation("Get a list of recently used command line args for an on-demand or scheduled request")
public Set<List<String>> getRecentCommandLineArgs(
@ApiParam("Request ID to look up") @PathParam("requestId") String requestId,
@ApiParam("Max number of recent args to return") @QueryParam("count") Optional<Integer> count) {
authorizationHelper.checkForAuthorizationByRequestId(requestId, user, SingularityAuthorizationScope.READ);
final int argCount = count.or(DEFAULT_ARGS_HISTORY_COUNT);
List<SingularityTaskIdHistory> historiesToCheck = taskHistoryHelper.getBlendedHistory(new SingularityTaskHistoryQuery(
Optional.of(requestId), Optional.<String>absent(), Optional.<String>absent(), Optional.<String>absent(), Optional.<ExtendedTaskState>absent(), Optional.<Long>absent(), Optional.<Long>absent(),
Optional.<Long>absent(), Optional.<Long>absent(), Optional.<OrderDirection>absent()), 0, argCount);
Collections.sort(historiesToCheck);
Set<List<String>> args = new HashSet<>();
for (SingularityTaskIdHistory taskIdHistory : historiesToCheck) {
Optional<SingularityTask> maybeTask = taskHistoryHelper.getTask(taskIdHistory.getTaskId());
if (maybeTask.isPresent() && maybeTask.get().getTaskRequest().getPendingTask().getCmdLineArgsList().isPresent()) {
List<String> taskArgs = maybeTask.get().getTaskRequest().getPendingTask().getCmdLineArgsList().get();
if (!taskArgs.isEmpty()) {
args.add(maybeTask.get().getTaskRequest().getPendingTask().getCmdLineArgsList().get());
}
}
}
return args;
}
}