diff --git a/src/main/java/io/cryostat/reports/PresignedFormData.java b/src/main/java/io/cryostat/reports/PresignedFormData.java new file mode 100644 index 0000000..4378a1c --- /dev/null +++ b/src/main/java/io/cryostat/reports/PresignedFormData.java @@ -0,0 +1,34 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cryostat.reports; + +import jakarta.ws.rs.core.MediaType; +import org.jboss.resteasy.reactive.PartType; +import org.jboss.resteasy.reactive.RestForm; + +public class PresignedFormData { + @RestForm + @PartType(MediaType.TEXT_PLAIN) + public String path; + + @RestForm + @PartType(MediaType.TEXT_PLAIN) + public String query; + + @RestForm + @PartType(MediaType.TEXT_PLAIN) + public String filter; +} diff --git a/src/main/java/io/cryostat/reports/ReportResource.java b/src/main/java/io/cryostat/reports/ReportResource.java index 46c1e8f..ef98c08 100644 --- a/src/main/java/io/cryostat/reports/ReportResource.java +++ b/src/main/java/io/cryostat/reports/ReportResource.java @@ -16,9 +16,13 @@ package io.cryostat.reports; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -47,6 +51,7 @@ import jakarta.ws.rs.ServerErrorException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; @@ -66,9 +71,19 @@ public class ReportResource { @ConfigProperty(name = "io.cryostat.reports.timeout", defaultValue = "29000") String timeoutMs; - @Inject Logger logger; + @ConfigProperty(name = "cryostat.storage.base-uri") + Optional storageBase; + + @ConfigProperty(name = "cryostat.storage.auth-method") + Optional storageAuthMethod; + + @ConfigProperty(name = "cryostat.storage.auth") + Optional storageAuth; + @Inject InterruptibleReportGenerator generator; @Inject FileSystem fs; + @Inject ObjectMapper mapper; + @Inject Logger logger; RuleFilterParser rfp = new RuleFilterParser(); @@ -87,12 +102,55 @@ void onStart(@Observes StartupEvent ev) { @Produces(MediaType.TEXT_PLAIN) public void healthCheck() {} + @Blocking + @Path("remote_report") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @POST + public String getReportFromPresigned(RoutingContext ctx, @BeanParam PresignedFormData form) + throws IOException, URISyntaxException { + long timeout = TimeUnit.MILLISECONDS.toNanos(Long.parseLong(timeoutMs)); + long start = System.nanoTime(); + + UriBuilder uriBuilder = + UriBuilder.newInstance() + .uri(new URI(storageBase.get())) + .path(form.path) + .replaceQuery(form.query); + URI downloadUri = uriBuilder.build(); + logger.infov("Attempting to download presigned recording from {0}", downloadUri); + HttpURLConnection httpConn = (HttpURLConnection) downloadUri.toURL().openConnection(); + httpConn.setRequestMethod("GET"); + if (storageAuth.isPresent() && storageAuth.isPresent()) { + httpConn.setRequestProperty( + "Authorization", + String.format("%s %s", storageAuthMethod.get(), storageAuth.get())); + } + try (var stream = httpConn.getInputStream()) { + + Predicate predicate = rfp.parse(form.filter); + Future> evalMapFuture = null; + + evalMapFuture = generator.generateEvalMapInterruptibly(stream, predicate); + long elapsed = System.nanoTime() - start; + ctxHelper(ctx, evalMapFuture); + return mapper.writeValueAsString( + evalMapFuture.get(timeout - elapsed, TimeUnit.NANOSECONDS)); + } catch (ExecutionException | InterruptedException e) { + throw new InternalServerErrorException(e); + } catch (TimeoutException e) { + throw new ServerErrorException(Response.Status.GATEWAY_TIMEOUT, e); + } finally { + httpConn.disconnect(); + } + } + @Blocking @Path("report") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) @POST - public String getEval(RoutingContext ctx, @BeanParam RecordingFormData form) + public String getReport(RoutingContext ctx, @BeanParam RecordingFormData form) throws IOException { FileUpload upload = form.file; @@ -105,11 +163,10 @@ public String getEval(RoutingContext ctx, @BeanParam RecordingFormData form) Predicate predicate = rfp.parse(form.filter); Future> evalMapFuture = null; - ObjectMapper oMapper = new ObjectMapper(); try (var stream = fs.newInputStream(file)) { evalMapFuture = generator.generateEvalMapInterruptibly(stream, predicate); ctxHelper(ctx, evalMapFuture); - return oMapper.writeValueAsString( + return mapper.writeValueAsString( evalMapFuture.get(timeout - elapsed, TimeUnit.NANOSECONDS)); } catch (ExecutionException | InterruptedException e) { throw new InternalServerErrorException(e); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cc1a906..6879f79 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,3 +13,7 @@ quarkus.http.body.delete-uploaded-files-on-end=true quarkus.native.additional-build-args =\ -H:ResourceConfigurationFiles=resource-config.json,\ -H:ReflectionConfigurationFiles=reflect-config.json + +cryostat.storage.base-uri= +cryostat.storage.auth-method= +cryostat.storage.auth=