Skip to content

Commit

Permalink
feat(reports): use S3 presigned URL for report recording transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores committed Jan 25, 2024
1 parent 97c91a1 commit 874f435
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 9 deletions.
3 changes: 3 additions & 0 deletions smoketest/compose/reports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ services:
environment:
JAVA_OPTS_APPEND: "-Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11224 -Dcom.sun.management.jmxremote.rmi.port=11224 -Djava.rmi.server.hostname=reports -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false"
QUARKUS_HTTP_PORT: 10001
CRYOSTAT_STORAGE_BASE_URI: http://auth:8080/storage
CRYOSTAT_STORAGE_AUTH_METHOD: Basic
CRYOSTAT_STORAGE_AUTH: dXNlcjpwYXNz # user:pass
61 changes: 52 additions & 9 deletions src/main/java/io/cryostat/reports/ReportsServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
Expand All @@ -44,22 +47,33 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

@ApplicationScoped
class ReportsServiceImpl implements ReportsService {

@ConfigProperty(name = ConfigProperties.REPORTS_SIDECAR_URL)
Optional<URI> sidecarUri;

@ConfigProperty(name = ConfigProperties.AWS_BUCKET_NAME_ARCHIVES)
String archiveBucket;

@ConfigProperty(name = ConfigProperties.STORAGE_EXT_URL)
Optional<String> externalStorageUrl;

@Inject ObjectMapper mapper;
@Inject RecordingHelper helper;
@Inject InterruptibleReportGenerator reportGenerator;
@Inject S3Presigner presigner;
@Inject Logger logger;

CloseableHttpClient http;
Expand All @@ -84,8 +98,7 @@ public Uni<Map<String, AnalysisResult>> reportFor(
"sidecar reportFor active recording {0} {1}",
recording.target.jvmId, recording.remoteId);
try {
return fireRequest(
uri, helper.getActiveInputStream(recording));
return fireRequest(uri, getPresignedUri(recording));
} catch (Exception e) {
throw new ReportGenerationException(e);
}
Expand Down Expand Up @@ -116,9 +129,11 @@ public Uni<Map<String, AnalysisResult>> reportFor(
logger.tracev(
"sidecar reportFor archived recording {0} {1}",
jvmId, filename);
return fireRequest(
uri,
helper.getArchivedRecordingStream(jvmId, filename));
try {
return fireRequest(uri, getPresignedPath(jvmId, filename));
} catch (Exception e) {
throw new ReportGenerationException(e);
}
})
.orElseGet(
() -> {
Expand All @@ -139,11 +154,39 @@ private Future<Map<String, AnalysisResult>> process(
new BufferedInputStream(stream), predicate);
}

private Future<Map<String, AnalysisResult>> fireRequest(URI uri, InputStream stream) {
private URI getPresignedUri(ActiveRecording recording) throws Exception {
// TODO refactor, this is copied out of Recordings.java
String savename = recording.name;
String filename = helper.saveRecording(recording, savename, Instant.now().plusSeconds(60));
return getPresignedPath(recording.target.jvmId, filename);
}

private URI getPresignedPath(String jvmId, String filename) throws URISyntaxException {
// TODO refactor, this is copied out of Recordings.java
logger.infov("Handling presigned download request for {0}/{1}", jvmId, filename);
GetObjectRequest getRequest =
GetObjectRequest.builder()
.bucket(archiveBucket)
.key(helper.archivedRecordingKey(Pair.of(jvmId, filename)))
.build();
GetObjectPresignRequest presignRequest =
GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(1))
.getObjectRequest(getRequest)
.build();
return URI.create(presigner.presignGetObject(presignRequest).url().toString()).normalize();
}

private Future<Map<String, AnalysisResult>> fireRequest(
URI sidecarUri, URI presignedRecordingUri) {
var cf = new CompletableFuture<Map<String, AnalysisResult>>();
try {
var post = new HttpPost(uri.resolve("report"));
var form = MultipartEntityBuilder.create().addBinaryBody("file", stream).build();
try {
var post = new HttpPost(sidecarUri.resolve("remote_report"));
var form =
MultipartEntityBuilder.create()
.addTextBody("path", presignedRecordingUri.getPath())
.addTextBody("query", presignedRecordingUri.getQuery())
.build();
post.setEntity(form);
http.execute(
post,
Expand Down

0 comments on commit 874f435

Please sign in to comment.