package com.hubspot.singularity.s3downloader.server; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.continuation.Continuation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.Timer.Context; import com.google.common.collect.ImmutableMap; import com.hubspot.deploy.S3Artifact; import com.hubspot.mesos.JavaUtils; import com.hubspot.singularity.runner.base.sentry.SingularityRunnerExceptionNotifier; import com.hubspot.singularity.s3.base.ArtifactDownloadRequest; import com.hubspot.singularity.s3.base.ArtifactManager; import com.hubspot.singularity.s3downloader.SingularityS3DownloaderMetrics; public class SingularityS3DownloaderAsyncHandler implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(SingularityS3DownloaderAsyncHandler.class); private final ArtifactDownloadRequest artifactDownloadRequest; private final Continuation continuation; private final ArtifactManager artifactManager; private final long start; private final SingularityS3DownloaderMetrics metrics; private final SingularityRunnerExceptionNotifier exceptionNotifier; private final DownloadListener downloadListener; public SingularityS3DownloaderAsyncHandler(ArtifactManager artifactManager, ArtifactDownloadRequest artifactDownloadRequest, Continuation continuation, SingularityS3DownloaderMetrics metrics, SingularityRunnerExceptionNotifier exceptionNotifier, DownloadListener downloadListener) { this.artifactManager = artifactManager; this.artifactDownloadRequest = artifactDownloadRequest; this.continuation = continuation; this.metrics = metrics; this.start = System.currentTimeMillis(); this.exceptionNotifier = exceptionNotifier; this.downloadListener = downloadListener; } public S3Artifact getS3Artifact() { return artifactDownloadRequest.getS3Artifact(); } private boolean download() throws Exception { LOG.info("Beginning download {} after {}", artifactDownloadRequest, JavaUtils.duration(start)); if (continuation.isExpired()) { LOG.info("Continuation expired for {}, aborting...", artifactDownloadRequest.getTargetDirectory()); return false; } final Path fetched = artifactManager.fetch(artifactDownloadRequest.getS3Artifact()); downloadListener.notifyDownloadFinished(this); final Path targetDirectory = Paths.get(artifactDownloadRequest.getTargetDirectory()); if (continuation.isExpired()) { LOG.info("Continuation expired for {} after download, aborting...", artifactDownloadRequest.getTargetDirectory()); return false; } if (Objects.toString(fetched.getFileName()).endsWith(".tar.gz")) { artifactManager.untar(fetched, targetDirectory); } else { artifactManager.copy(fetched, targetDirectory, artifactDownloadRequest.getS3Artifact().getFilename()); } LOG.info("Finishing request {} after {}", artifactDownloadRequest.getTargetDirectory(), JavaUtils.duration(start)); getResponse().getOutputStream().close(); return true; } private HttpServletResponse getResponse() { return (HttpServletResponse) continuation.getServletResponse(); } @Override public void run() { boolean success = false; try (final Context context = metrics.getDownloadTimer().time()) { success = download(); if (!success) { metrics.getServerErrorsMeter().mark(); getResponse().sendError(500, "Hit client timeout"); } } catch (Throwable t) { metrics.getServerErrorsMeter().mark(); LOG.error("While handling {}", artifactDownloadRequest.getTargetDirectory(), t); exceptionNotifier.notify(String.format("Error handling download (%s)", t.getMessage()), t, ImmutableMap.of("s3Bucket", artifactDownloadRequest.getS3Artifact().getS3Bucket(), "s3Key", artifactDownloadRequest.getS3Artifact().getS3ObjectKey(), "targetDirectory", artifactDownloadRequest.getTargetDirectory())); try { getResponse().sendError(500); } catch (Throwable t2) { LOG.error("While sending error for {}", artifactDownloadRequest.getTargetDirectory(), t2); } } finally { continuation.complete(); } } }