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.S3LogResource Maven / Gradle / Ivy
package com.hubspot.singularity.resources;
import static com.hubspot.singularity.WebExceptions.checkBadRequest;
import static com.hubspot.singularity.WebExceptions.checkNotFound;
import static com.hubspot.singularity.WebExceptions.timeout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
import com.amazonaws.services.s3.model.ListObjectsV2Request;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import com.amazonaws.services.s3.model.ResponseHeaderOverrides;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.hubspot.mesos.JavaUtils;
import com.hubspot.singularity.SingularityAuthorizationScope;
import com.hubspot.singularity.SingularityDeployHistory;
import com.hubspot.singularity.SingularityRequest;
import com.hubspot.singularity.SingularityRequestHistory;
import com.hubspot.singularity.SingularityRequestHistory.RequestHistoryType;
import com.hubspot.singularity.SingularityRequestWithState;
import com.hubspot.singularity.SingularityS3FormatHelper;
import com.hubspot.singularity.SingularityS3Log;
import com.hubspot.singularity.SingularityS3LogMetadata;
import com.hubspot.singularity.SingularityS3UploaderFile;
import com.hubspot.singularity.SingularityTaskHistory;
import com.hubspot.singularity.SingularityTaskHistoryUpdate;
import com.hubspot.singularity.SingularityTaskHistoryUpdate.SimplifiedTaskState;
import com.hubspot.singularity.SingularityTaskId;
import com.hubspot.singularity.SingularityUser;
import com.hubspot.singularity.api.ContinuationToken;
import com.hubspot.singularity.api.SingularityS3SearchRequest;
import com.hubspot.singularity.api.SingularityS3SearchResult;
import com.hubspot.singularity.auth.SingularityAuthorizationHelper;
import com.hubspot.singularity.config.ApiPaths;
import com.hubspot.singularity.config.S3Configuration;
import com.hubspot.singularity.data.DeployManager;
import com.hubspot.singularity.data.RequestManager;
import com.hubspot.singularity.data.TaskManager;
import com.hubspot.singularity.data.history.HistoryManager;
import com.hubspot.singularity.data.history.RequestHistoryHelper;
import com.hubspot.singularity.helpers.S3ObjectSummaryHolder;
import com.hubspot.singularity.helpers.SingularityS3Service;
import com.hubspot.singularity.helpers.SingularityS3Services;
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.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
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;
@Path(ApiPaths.S3_LOG_RESOURCE_PATH)
@Produces({ MediaType.APPLICATION_JSON })
@Schema(title = "Manage Singularity task logs stored in S3")
@Tags({@Tag(name = "S3 Logs")})
public class S3LogResource extends AbstractHistoryResource {
private static final Logger LOG = LoggerFactory.getLogger(S3LogResource.class);
private static final String CONTENT_DISPOSITION_DOWNLOAD_HEADER = "attachment";
private static final String CONTENT_ENCODING_DOWNLOAD_HEADER = "identity";
private static final String CONTINUATION_TOKEN_KEY_FORMAT = "%s-%s-%s";
private static final int DEFAULT_TARGET_MAX_RESULTS = 10;
private final SingularityS3Services s3Services;
private final Optional configuration;
private final RequestHistoryHelper requestHistoryHelper;
private final RequestManager requestManager;
private static final Comparator LOG_COMPARATOR = new Comparator() {
@Override
public int compare(SingularityS3LogMetadata o1, SingularityS3LogMetadata o2) {
return Longs.compare(o2.getLastModified(), o1.getLastModified());
}
};
@Inject
public S3LogResource(RequestManager requestManager, HistoryManager historyManager, RequestHistoryHelper requestHistoryHelper, TaskManager taskManager, DeployManager deployManager,
Optional configuration, SingularityAuthorizationHelper authorizationHelper, SingularityS3Services s3Services) {
super(historyManager, taskManager, deployManager, authorizationHelper);
this.requestManager = requestManager;
this.configuration = configuration;
this.requestHistoryHelper = requestHistoryHelper;
this.s3Services = s3Services;
}
// Generation of prefixes
private Collection getS3PrefixesForTask(S3Configuration s3Configuration, SingularityTaskId taskId, Optional startArg, Optional endArg, String group, SingularityUser user) {
Optional history = getTaskHistory(taskId, user);
long start = taskId.getStartedAt();
if (startArg.isPresent()) {
start = Math.max(startArg.get(), start);
}
long end = start + s3Configuration.getMissingTaskDefaultS3SearchPeriodMillis();
if (history.isPresent()) {
SimplifiedTaskState taskState = SingularityTaskHistoryUpdate.getCurrentState(history.get().getTaskUpdates());
if (taskState == SimplifiedTaskState.DONE) {
end = Iterables.getLast(history.get().getTaskUpdates()).getTimestamp();
} else {
end = System.currentTimeMillis();
}
}
if (endArg.isPresent()) {
end = Math.min(endArg.get(), end);
}
Optional tag = Optional.absent();
if (history.isPresent() && history.get().getTask().getTaskRequest().getDeploy().getExecutorData().isPresent()) {
tag = history.get().getTask().getTaskRequest().getDeploy().getExecutorData().get().getLoggingTag();
}
Collection prefixes = SingularityS3FormatHelper.getS3KeyPrefixes(s3Configuration.getS3KeyFormat(), taskId, tag, start, end, group);
for (SingularityS3UploaderFile additionalFile : s3Configuration.getS3UploaderAdditionalFiles()) {
if (additionalFile.getS3UploaderKeyPattern().isPresent() && !additionalFile.getS3UploaderKeyPattern().get().equals(s3Configuration.getS3KeyFormat())) {
prefixes.addAll(SingularityS3FormatHelper.getS3KeyPrefixes(additionalFile.getS3UploaderKeyPattern().get(), taskId, tag, start, end, group));
}
}
LOG.trace("Task {} got S3 prefixes {} for start {}, end {}, tag {}", taskId, prefixes, start, end, tag);
return prefixes;
}
private boolean isCurrentDeploy(String requestId, String deployId) {
return deployId.equals(deployManager.getInUseDeployId(requestId).orNull());
}
private Collection getS3PrefixesForRequest(S3Configuration s3Configuration, String requestId, Optional startArg, Optional endArg, String group) {
Optional firstHistory = requestHistoryHelper.getFirstHistory(requestId);
checkNotFound(firstHistory.isPresent(), "No request history found for %s", requestId);
long start = firstHistory.get().getCreatedAt();
if (startArg.isPresent()) {
start = Math.max(startArg.get(), start);
}
Optional lastHistory = requestHistoryHelper.getLastHistory(requestId);
long end = System.currentTimeMillis();
if (lastHistory.isPresent() && (lastHistory.get().getEventType() == RequestHistoryType.DELETED || lastHistory.get().getEventType() == RequestHistoryType.PAUSED)) {
end = lastHistory.get().getCreatedAt() + TimeUnit.DAYS.toMillis(1);
}
if (endArg.isPresent()) {
end = Math.min(endArg.get(), end);
}
Collection prefixes = SingularityS3FormatHelper.getS3KeyPrefixes(s3Configuration.getS3KeyFormat(), requestId, start, end, group);
for (SingularityS3UploaderFile additionalFile : s3Configuration.getS3UploaderAdditionalFiles()) {
if (additionalFile.getS3UploaderKeyPattern().isPresent() && !additionalFile.getS3UploaderKeyPattern().get().equals(s3Configuration.getS3KeyFormat())) {
prefixes.addAll(SingularityS3FormatHelper.getS3KeyPrefixes(additionalFile.getS3UploaderKeyPattern().get(), requestId, start, end, group));
}
}
LOG.trace("Request {} got S3 prefixes {} for start {}, end {}", requestId, prefixes, start, end);
return prefixes;
}
private Collection getS3PrefixesForDeploy(S3Configuration s3Configuration, String requestId, String deployId, Optional startArg, Optional endArg, String group, SingularityUser user) {
SingularityDeployHistory deployHistory = getDeployHistory(requestId, deployId, user);
long start = deployHistory.getDeployMarker().getTimestamp();
if (startArg.isPresent()) {
start = Math.max(startArg.get(), start);
}
long end = System.currentTimeMillis();
if (!isCurrentDeploy(requestId, deployId) && deployHistory.getDeployStatistics().isPresent() && deployHistory.getDeployStatistics().get().getLastFinishAt().isPresent()) {
end = deployHistory.getDeployStatistics().get().getLastFinishAt().get() + TimeUnit.DAYS.toMillis(1);
}
if (endArg.isPresent()) {
end = Math.min(endArg.get(), end);
}
Optional tag = Optional.absent();
if (deployHistory.getDeploy().isPresent() && deployHistory.getDeploy().get().getExecutorData().isPresent()) {
tag = deployHistory.getDeploy().get().getExecutorData().get().getLoggingTag();
}
Collection prefixes = SingularityS3FormatHelper.getS3KeyPrefixes(s3Configuration.getS3KeyFormat(), requestId, deployId, tag, start, end, group);
for (SingularityS3UploaderFile additionalFile : s3Configuration.getS3UploaderAdditionalFiles()) {
if (additionalFile.getS3UploaderKeyPattern().isPresent() && !additionalFile.getS3UploaderKeyPattern().get().equals(s3Configuration.getS3KeyFormat())) {
prefixes.addAll(SingularityS3FormatHelper.getS3KeyPrefixes(additionalFile.getS3UploaderKeyPattern().get(), requestId, deployId, tag, start, end, group));
}
}
LOG.trace("Request {}, deploy {} got S3 prefixes {} for start {}, end {}, tag {}", requestId, deployId, prefixes, start, end, tag);
return prefixes;
}
private Map> getServiceToPrefixes(SingularityS3SearchRequest search, SingularityUser user) {
Map> servicesToPrefixes = new HashMap<>();
if (!search.getTaskIds().isEmpty()) {
for (String taskId : search.getTaskIds()) {
SingularityTaskId taskIdObject = getTaskIdObject(taskId);
String group = getRequestGroupForTask(taskIdObject, user).or(SingularityS3FormatHelper.DEFAULT_GROUP_NAME);
Set s3Buckets = getBuckets(group);
Collection prefixes = getS3PrefixesForTask(configuration.get(), taskIdObject, search.getStart(), search.getEnd(), group, user);
for (String s3Bucket : s3Buckets) {
SingularityS3Service s3Service = s3Services.getServiceByGroupAndBucketOrDefault(group, s3Bucket);
if (!servicesToPrefixes.containsKey(s3Service)) {
servicesToPrefixes.put(s3Service, new HashSet());
}
servicesToPrefixes.get(s3Service).addAll(prefixes);
}
}
}
if (!search.getRequestsAndDeploys().isEmpty()) {
for (Map.Entry> entry : search.getRequestsAndDeploys().entrySet()) {
String group = getRequestGroup(entry.getKey(), user).or(SingularityS3FormatHelper.DEFAULT_GROUP_NAME);
Set s3Buckets = getBuckets(group);
List prefixes = new ArrayList<>();
if (!entry.getValue().isEmpty()) {
for (String deployId : entry.getValue()) {
prefixes.addAll(getS3PrefixesForDeploy(configuration.get(), entry.getKey(), deployId, search.getStart(), search.getEnd(), group, user));
}
} else {
prefixes.addAll(getS3PrefixesForRequest(configuration.get(), entry.getKey(), search.getStart(), search.getEnd(), group));
}
for (String s3Bucket : s3Buckets) {
SingularityS3Service s3Service = s3Services.getServiceByGroupAndBucketOrDefault(group, s3Bucket);
if (!servicesToPrefixes.containsKey(s3Service)) {
servicesToPrefixes.put(s3Service, new HashSet());
}
servicesToPrefixes.get(s3Service).addAll(prefixes);
}
}
}
// Trim prefixes to search. Less specific prefixes will contain all results of matching + more specific ones
for (Map.Entry> entry : servicesToPrefixes.entrySet()) {
Set results = new HashSet<>();
boolean contains = false;
for (String prefix : entry.getValue()) {
for (String unique : results) {
if (prefix.startsWith(unique) && prefix.length() > unique.length()) {
contains = true;
break;
} else if (unique.startsWith(prefix) && unique.length() > prefix.length()) {
results.remove(unique);
results.add(prefix);
contains = true;
break;
}
}
if (!contains) {
results.add(prefix);
}
}
entry.getValue().retainAll(results);
}
return servicesToPrefixes;
}
private Set getBuckets(String group) {
Set s3Buckets = new HashSet<>();
s3Buckets.add(configuration.get().getGroupOverrides().containsKey(group) ? configuration.get().getGroupOverrides().get(group).getS3Bucket() : configuration.get().getS3Bucket());
s3Buckets.add(configuration.get().getGroupS3SearchConfigs().containsKey(group) ? configuration.get().getGroupS3SearchConfigs().get(group).getS3Bucket() : configuration.get().getS3Bucket());
for (SingularityS3UploaderFile uploaderFile : configuration.get().getS3UploaderAdditionalFiles()) {
if (uploaderFile.getS3UploaderBucket().isPresent() && !s3Buckets.contains(uploaderFile.getS3UploaderBucket().get())) {
s3Buckets.add(uploaderFile.getS3UploaderBucket().get());
}
}
return s3Buckets;
}
// Fetching logs
private List getS3LogsWithExecutorService(S3Configuration s3Configuration, ListeningExecutorService executorService, Map> servicesToPrefixes, int totalPrefixCount, final SingularityS3SearchRequest search, final ConcurrentHashMap continuationTokens, final boolean paginated) throws InterruptedException, ExecutionException, TimeoutException {
List>> futures = Lists.newArrayListWithCapacity(totalPrefixCount);
final AtomicInteger resultCount = new AtomicInteger();
for (final Map.Entry> entry : servicesToPrefixes.entrySet()) {
final String s3Bucket = entry.getKey().getBucket();
final String group = entry.getKey().getGroup();
final AmazonS3 s3Client = entry.getKey().getS3Client();
for (final String s3Prefix : entry.getValue()) {
final String key = String.format(CONTINUATION_TOKEN_KEY_FORMAT, group, s3Bucket, s3Prefix);
if (search.getContinuationTokens().containsKey(key) && search.getContinuationTokens().get(key).isLastPage()) {
LOG.trace("No further content for prefix {} in bucket {}, skipping", s3Prefix, s3Bucket);
continuationTokens.putIfAbsent(key, search.getContinuationTokens().get(key));
continue;
}
futures.add(executorService.submit(new Callable>() {
@Override
public List call() throws Exception {
ListObjectsV2Request request = new ListObjectsV2Request().withBucketName(s3Bucket).withPrefix(s3Prefix);
if (paginated) {
Optional token = Optional.absent();
if (search.getContinuationTokens().containsKey(key) && !Strings.isNullOrEmpty(search.getContinuationTokens().get(key).getValue())) {
request.setContinuationToken(search.getContinuationTokens().get(key).getValue());
token = Optional.of(search.getContinuationTokens().get(key));
}
int targetResultCount = search.getMaxPerPage().or(DEFAULT_TARGET_MAX_RESULTS);
request.setMaxKeys(targetResultCount);
if (resultCount.get() < targetResultCount) {
ListObjectsV2Result result = s3Client.listObjectsV2(request);
if (result.getObjectSummaries().isEmpty()) {
continuationTokens.putIfAbsent(key, new ContinuationToken(result.getNextContinuationToken(), true));
return Collections.emptyList();
} else {
boolean addToList = incrementIfLessThan(resultCount, result.getObjectSummaries().size(), targetResultCount);
if (addToList) {
continuationTokens.putIfAbsent(key, new ContinuationToken(result.getNextContinuationToken(), !result.isTruncated()));
List objectSummaryHolders = new ArrayList<>();
for (S3ObjectSummary objectSummary : result.getObjectSummaries()) {
objectSummaryHolders.add(new S3ObjectSummaryHolder(group, objectSummary));
}
return objectSummaryHolders;
} else {
continuationTokens.putIfAbsent(key, token.or(new ContinuationToken(null, false)));
return Collections.emptyList();
}
}
} else {
continuationTokens.putIfAbsent(key, token.or(new ContinuationToken(null, false)));
return Collections.emptyList();
}
} else {
ListObjectsV2Result result = s3Client.listObjectsV2(request);
List objectSummaryHolders = new ArrayList<>();
for (S3ObjectSummary objectSummary : result.getObjectSummaries()) {
objectSummaryHolders.add(new S3ObjectSummaryHolder(group, objectSummary));
}
return objectSummaryHolders;
}
}
}));
}
}
final long start = System.currentTimeMillis();
List> results = Futures.allAsList(futures).get(s3Configuration.getWaitForS3ListSeconds(), TimeUnit.SECONDS);
List objects = Lists.newArrayListWithExpectedSize(results.size() * 2);
for (List s3ObjectSummaryHolders : results) {
for (final S3ObjectSummaryHolder s3ObjectHolder : s3ObjectSummaryHolders) {
objects.add(s3ObjectHolder);
}
}
LOG.trace("Got {} objects from S3 after {}", objects.size(), JavaUtils.duration(start));
List> logFutures = Lists.newArrayListWithCapacity(objects.size());
final Date expireAt = new Date(System.currentTimeMillis() + s3Configuration.getExpireS3LinksAfterMillis());
for (final S3ObjectSummaryHolder s3ObjectHolder : objects) {
final S3ObjectSummary s3Object = s3ObjectHolder.getObjectSummary();
final AmazonS3 s3Client = s3Services.getServiceByGroupAndBucketOrDefault(s3ObjectHolder.getGroup(), s3Object.getBucketName()).getS3Client();
logFutures.add(executorService.submit(new Callable() {
@Override
public SingularityS3LogMetadata call() throws Exception {
Optional maybeStartTime = Optional.absent();
Optional maybeEndTime = Optional.absent();
if (!search.isExcludeMetadata()) {
GetObjectMetadataRequest metadataRequest = new GetObjectMetadataRequest(s3Object.getBucketName(), s3Object.getKey());
Map objectMetadata = s3Client.getObjectMetadata(metadataRequest).getUserMetadata();
maybeStartTime = getMetadataAsLong(objectMetadata, SingularityS3Log.LOG_START_S3_ATTR);
maybeEndTime = getMetadataAsLong(objectMetadata, SingularityS3Log.LOG_END_S3_ATTR);
}
if (search.isListOnly()) {
return new SingularityS3LogMetadata(s3Object.getKey(), s3Object.getLastModified().getTime(), s3Object.getSize(), maybeStartTime, maybeEndTime);
} else {
GeneratePresignedUrlRequest getUrlRequest = new GeneratePresignedUrlRequest(s3Object.getBucketName(), s3Object.getKey())
.withMethod(HttpMethod.GET)
.withExpiration(expireAt);
String getUrl = s3Client.generatePresignedUrl(getUrlRequest).toString();
ResponseHeaderOverrides downloadHeaders = new ResponseHeaderOverrides();
downloadHeaders.setContentDisposition(CONTENT_DISPOSITION_DOWNLOAD_HEADER);
downloadHeaders.setContentEncoding(CONTENT_ENCODING_DOWNLOAD_HEADER);
GeneratePresignedUrlRequest downloadUrlRequest = new GeneratePresignedUrlRequest(s3Object.getBucketName(), s3Object.getKey())
.withMethod(HttpMethod.GET)
.withExpiration(expireAt)
.withResponseHeaders(downloadHeaders);
String downloadUrl = s3Client.generatePresignedUrl(downloadUrlRequest).toString();
return new SingularityS3Log(getUrl, s3Object.getKey(), s3Object.getLastModified().getTime(), s3Object.getSize(), downloadUrl, maybeStartTime, maybeEndTime);
}
}
}));
}
return Futures.allAsList(logFutures).get(s3Configuration.getWaitForS3LinksSeconds(), TimeUnit.SECONDS);
}
private boolean incrementIfLessThan(AtomicInteger count, int add, int threshold) {
while (true) {
int current = count.get();
if (current >= threshold) {
return false;
}
if (count.compareAndSet(current, current + add)) {
return true;
}
}
}
private Optional getMetadataAsLong(Map objectMetadata, String keyName) {
try {
if (objectMetadata.containsKey(keyName)) {
Object maybeLong = objectMetadata.get(keyName);
return Optional.of(Long.parseLong((String) maybeLong));
} else {
return Optional.absent();
}
} catch (Exception e) {
return Optional.absent();
}
}
private SingularityS3SearchResult getS3Logs(S3Configuration s3Configuration, Map> servicesToPrefixes, final SingularityS3SearchRequest search, final boolean paginated) throws InterruptedException, ExecutionException, TimeoutException {
int totalPrefixCount = 0;
for (Map.Entry> entry : servicesToPrefixes.entrySet()) {
totalPrefixCount += entry.getValue().size();
}
if (totalPrefixCount == 0) {
return SingularityS3SearchResult.empty();
}
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(Math.min(totalPrefixCount, s3Configuration.getMaxS3Threads()),
new ThreadFactoryBuilder().setNameFormat("S3LogFetcher-%d").build()));
try {
final ConcurrentHashMap continuationTokens = new ConcurrentHashMap<>();
List logs = Lists.newArrayList(getS3LogsWithExecutorService(s3Configuration, executorService, servicesToPrefixes, totalPrefixCount, search, continuationTokens, paginated));
Collections.sort(logs, LOG_COMPARATOR);
return new SingularityS3SearchResult(continuationTokens, isFinalPageForAllPrefixes(continuationTokens.values()), logs);
} finally {
executorService.shutdownNow();
}
}
private boolean isFinalPageForAllPrefixes(Collection continuationTokens) {
for (ContinuationToken token : continuationTokens) {
if (!token.isLastPage()) {
return false;
}
}
return true;
}
// Finding request group
private Optional getRequestGroupForTask(final SingularityTaskId taskId, SingularityUser user) {
Optional maybeTaskHistory = getTaskHistory(taskId, user);
if (maybeTaskHistory.isPresent()) {
SingularityRequest request = maybeTaskHistory.get().getTask().getTaskRequest().getRequest();
authorizationHelper.checkForAuthorization(request, user, SingularityAuthorizationScope.READ);
return request.getGroup();
} else {
return getRequestGroup(taskId.getRequestId(), user);
}
}
private Optional getRequestGroup(final String requestId, SingularityUser user) {
final Optional maybeRequest = requestManager.getRequest(requestId);
if (maybeRequest.isPresent()) {
authorizationHelper.checkForAuthorization(maybeRequest.get().getRequest(), user, SingularityAuthorizationScope.READ);
return maybeRequest.get().getRequest().getGroup();
} else {
Optional maybeRequestHistory = requestHistoryHelper.getLastHistory(requestId);
if (maybeRequestHistory.isPresent()) {
authorizationHelper.checkForAuthorization(maybeRequestHistory.get().getRequest(), user, SingularityAuthorizationScope.READ);
return maybeRequestHistory.get().getRequest().getGroup();
} else {
// Deleted requests with no history data are searchable, but only by admins since we have no auth information about them
authorizationHelper.checkAdminAuthorization(user);
return Optional.absent();
}
}
}
private void checkS3() {
checkNotFound(s3Services.isS3ConfigPresent(), "S3 configuration was absent");
checkNotFound(configuration.isPresent(), "S3 configuration was absent");
}
@GET
@Path("/task/{taskId}")
@Operation(
summary = "Retrieve the list of logs stored in S3 for a specific task",
responses = {
@ApiResponse(
responseCode = "200",
description = "Returns a list of metadata about log files for the specified task",
content = {
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3LogMetadata.class))),
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3Log.class)))
}
),
@ApiResponse(responseCode = "404", description = "S3 configuration is not present")
}
)
public List getS3LogsForTask(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The task ID to search for") @PathParam("taskId") String taskId,
@Parameter(description = "Start timestamp (millis, 13 digit)") @QueryParam("start") Optional start,
@Parameter(description = "End timestamp (mills, 13 digit)") @QueryParam("end") Optional end,
@Parameter(description = "Exclude custom object metadata") @QueryParam("excludeMetadata") @DefaultValue ("false") boolean excludeMetadata,
@Parameter(description = "Do not generate download/get urls, only list the files and metadata") @QueryParam("list") @DefaultValue ("false") boolean listOnly) throws Exception {
checkS3();
final SingularityS3SearchRequest search = new SingularityS3SearchRequest(
Collections.emptyMap(),
Collections.singletonList(taskId),
start,
end,
excludeMetadata,
listOnly,
Optional.absent(),
Collections.emptyMap());
try {
return getS3Logs(configuration.get(), getServiceToPrefixes(search, user), search, false).getResults();
} catch (TimeoutException te) {
throw timeout("Timed out waiting for response from S3 for %s", taskId);
} catch (Throwable t) {
throw Throwables.propagate(t);
}
}
@GET
@Path("/request/{requestId}")
@Operation(
summary = "Retrieve the list of logs stored in S3 for a specific request",
responses = {
@ApiResponse(
responseCode = "200",
description = "Returns a list of metadata about log files for the specified task",
content = {
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3LogMetadata.class))),
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3Log.class)))
}
),
@ApiResponse(responseCode = "404", description = "S3 configuration is not present")
}
)
public List getS3LogsForRequest(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to search for") @PathParam("requestId") String requestId,
@Parameter(description = "Start timestamp (millis, 13 digit)") @QueryParam("start") Optional start,
@Parameter(description = "End timestamp (mills, 13 digit)") @QueryParam("end") Optional end,
@Parameter(description = "Exclude custom object metadata") @QueryParam("excludeMetadata") @DefaultValue ("false") boolean excludeMetadata,
@Parameter(description = "Do not generate download/get urls, only list the files and metadata") @QueryParam("list") @DefaultValue ("false") boolean listOnly,
@Parameter(description = "Max number of results to return per bucket searched") @QueryParam("maxPerPage") Optional maxPerPage) throws Exception {
checkS3();
try {
final SingularityS3SearchRequest search = new SingularityS3SearchRequest(
ImmutableMap.of(requestId, Collections.emptyList()),
Collections.emptyList(),
start,
end,
excludeMetadata,
listOnly,
Optional.absent(),
Collections.emptyMap());
return getS3Logs(configuration.get(), getServiceToPrefixes(search, user), search, false).getResults();
} catch (TimeoutException te) {
throw timeout("Timed out waiting for response from S3 for %s", requestId);
} catch (Throwable t) {
throw Throwables.propagate(t);
}
}
@GET
@Path("/request/{requestId}/deploy/{deployId}")
@Operation(
summary = "Retrieve the list of logs stored in S3 for a specific deploy",
responses = {
@ApiResponse(
responseCode = "200",
description = "Returns a list of metadata about log files for the specified task",
content = {
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3LogMetadata.class))),
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3Log.class)))
}
),
@ApiResponse(responseCode = "404", description = "S3 configuration is not present")
}
)
public List getS3LogsForDeploy(
@Parameter(hidden = true) @Auth SingularityUser user,
@Parameter(required = true, description = "The request ID to search for") @PathParam("requestId") String requestId,
@Parameter(required = true, description = "The deploy ID to search for") @PathParam("deployId") String deployId,
@Parameter(description = "Start timestamp (millis, 13 digit)") @QueryParam("start") Optional start,
@Parameter(description = "End timestamp (mills, 13 digit)") @QueryParam("end") Optional end,
@Parameter(description = "Exclude custom object metadata") @QueryParam("excludeMetadata") @DefaultValue ("false") boolean excludeMetadata,
@Parameter(description = "Do not generate download/get urls, only list the files and metadata") @QueryParam("list") @DefaultValue ("false") boolean listOnly,
@Parameter(description = "Max number of results to return per bucket searched") @QueryParam("maxPerPage") Optional maxPerPage) throws Exception {
checkS3();
try {
final SingularityS3SearchRequest search = new SingularityS3SearchRequest(
ImmutableMap.of(requestId, Collections.singletonList(deployId)),
Collections.emptyList(),
start,
end,
excludeMetadata,
listOnly,
Optional.absent(),
Collections.emptyMap());
return getS3Logs(configuration.get(), getServiceToPrefixes(search, user), search, false).getResults();
} catch (TimeoutException te) {
throw timeout("Timed out waiting for response from S3 for %s-%s", requestId, deployId);
} catch (Throwable t) {
throw Throwables.propagate(t);
}
}
@POST
@Path("/search")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(
summary = "Retrieve a paginated list of logs stored in S3",
responses = {
@ApiResponse(
responseCode = "200",
description = "Returns a list of metadata about log files for the specified task",
content = {
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3LogMetadata.class))),
@Content(array = @ArraySchema(schema = @Schema(implementation = SingularityS3Log.class)))
}
),
@ApiResponse(responseCode = "404", description = "S3 configuration is not present"),
@ApiResponse(responseCode = "400", description = "Missing required data for search")
}
)
public SingularityS3SearchResult getPaginatedS3Logs(
@Parameter(hidden = true) @Auth SingularityUser user,
@RequestBody(required = true) SingularityS3SearchRequest search) throws Exception {
checkS3();
checkBadRequest(!search.getRequestsAndDeploys().isEmpty() || !search.getTaskIds().isEmpty(), "Must specify at least one request or task to search");
try {
return getS3Logs(configuration.get(), getServiceToPrefixes(search, user), search, true);
} catch (TimeoutException te) {
throw timeout("Timed out waiting for response from S3 for %s", search);
} catch (Throwable t) {
throw Throwables.propagate(t);
}
}
}