diff --git a/smoketest/compose/reports.yml b/smoketest/compose/reports.yml index ea26ac4ee..85eff2850 100644 --- a/smoketest/compose/reports.yml +++ b/smoketest/compose/reports.yml @@ -22,6 +22,9 @@ 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 healthcheck: test: curl --fail http://localhost:10001/ || exit 1 retries: 3 diff --git a/src/main/java/io/cryostat/reports/ReportsServiceImpl.java b/src/main/java/io/cryostat/reports/ReportsServiceImpl.java index b12b28749..1fb22aad8 100644 --- a/src/main/java/io/cryostat/reports/ReportsServiceImpl.java +++ b/src/main/java/io/cryostat/reports/ReportsServiceImpl.java @@ -16,8 +16,12 @@ package io.cryostat.reports; import java.io.BufferedInputStream; +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; @@ -35,16 +39,24 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; import io.vertx.ext.web.handler.HttpException; 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 { @@ -52,11 +64,28 @@ class ReportsServiceImpl implements ReportsService { @ConfigProperty(name = ConfigProperties.REPORTS_SIDECAR_URL) Optional sidecarUri; + @ConfigProperty(name = ConfigProperties.AWS_BUCKET_NAME_ARCHIVES) + String archiveBucket; + + @ConfigProperty(name = ConfigProperties.STORAGE_EXT_URL) + Optional externalStorageUrl; + @Inject ObjectMapper mapper; @Inject RecordingHelper helper; @Inject InterruptibleReportGenerator reportGenerator; + @Inject S3Presigner presigner; @Inject Logger logger; + CloseableHttpClient http; + + void onStart(@Observes StartupEvent evt) { + this.http = HttpClients.createSystem(); + } + + void onStop(@Observes ShutdownEvent evt) throws IOException { + this.http.close(); + } + @Blocking @Override public Uni> reportFor( @@ -69,8 +98,7 @@ public Uni> 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); } @@ -101,9 +129,11 @@ public Uni> 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( () -> { @@ -124,12 +154,39 @@ private Future> process( new BufferedInputStream(stream), predicate); } - private Future> 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> fireRequest( + URI sidecarUri, URI presignedRecordingUri) { var cf = new CompletableFuture>(); - try (var http = HttpClients.createDefault(); - stream) { - 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,