From afb9785d1f4ba3284e3995eff7d00afcbe2cfa31 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Aug 2023 15:01:14 -0400 Subject: [PATCH] feat(agent): implement Agent HTTP recording retrieval --- .../java/io/cryostat/net/AgentClient.java | 20 ++++++++++++++++++ .../java/io/cryostat/net/AgentJFRService.java | 21 +++++++++++++++---- .../java/io/cryostat/net/web/WebServer.java | 9 ++------ .../recordings/RecordingArchiveHelper.java | 2 +- src/main/java/io/cryostat/util/URIUtil.java | 14 +++++++++++++ 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/cryostat/net/AgentClient.java b/src/main/java/io/cryostat/net/AgentClient.java index 5552758a9c..61c3f8eb29 100644 --- a/src/main/java/io/cryostat/net/AgentClient.java +++ b/src/main/java/io/cryostat/net/AgentClient.java @@ -46,6 +46,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; +import java.io.InputStream; import javax.script.ScriptException; @@ -152,6 +153,25 @@ Future startRecording(StartRecordingRequest req) { }); } + Future openStream(long id) { + Future> f = + invoke( + HttpMethod.GET, + "/recordings/" + id, + BodyCodec.buffer()); + return f.map( + resp -> { + int statusCode = resp.statusCode(); + if (HttpStatusCodeIdentifier.isSuccessCode(statusCode)) { + return resp.body(); + } else if (statusCode == 403) { + throw new UnsupportedOperationException(); + } else { + throw new RuntimeException("Unknown failure"); + } + }); + } + Future stopRecording(long id) { Future> f = invoke( diff --git a/src/main/java/io/cryostat/net/AgentJFRService.java b/src/main/java/io/cryostat/net/AgentJFRService.java index df9b490a76..2fdc07c72b 100644 --- a/src/main/java/io/cryostat/net/AgentJFRService.java +++ b/src/main/java/io/cryostat/net/AgentJFRService.java @@ -37,6 +37,8 @@ */ package io.cryostat.net; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; @@ -72,6 +74,8 @@ import io.cryostat.core.templates.MergedTemplateService; import io.cryostat.core.templates.Template; import io.cryostat.core.templates.TemplateType; +import io.vertx.core.Future; +import io.vertx.core.buffer.Buffer; import org.jsoup.nodes.Document; @@ -208,20 +212,29 @@ public boolean isEnabled() { } @Override - public InputStream openStream(IRecordingDescriptor arg0, boolean arg1) + public InputStream openStream(IRecordingDescriptor descriptor, boolean removeOnClose) throws FlightRecorderException { - throw new UnimplementedException(); + Future f = client.openStream(descriptor.getId()); + try { + Buffer b = f.toCompletionStage().toCompletableFuture().get(); + return new BufferedInputStream( + new ByteArrayInputStream(b.getBytes()) + ); + } catch (ExecutionException | InterruptedException e) { + logger.warn(e); + throw new FlightRecorderException("Failed to open remote recording stream", e); + } } @Override - public InputStream openStream(IRecordingDescriptor arg0, IQuantity arg1, boolean arg2) + public InputStream openStream(IRecordingDescriptor descriptor, IQuantity lastPartDuration, boolean removeOnClose) throws FlightRecorderException { throw new UnimplementedException(); } @Override public InputStream openStream( - IRecordingDescriptor arg0, IQuantity arg1, IQuantity arg2, boolean arg3) + IRecordingDescriptor descriptor, IQuantity startTime, IQuantity endTime, boolean removeOnClose) throws FlightRecorderException { throw new UnimplementedException(); } diff --git a/src/main/java/io/cryostat/net/web/WebServer.java b/src/main/java/io/cryostat/net/web/WebServer.java index 8cafc3fa0a..27afd68b82 100644 --- a/src/main/java/io/cryostat/net/web/WebServer.java +++ b/src/main/java/io/cryostat/net/web/WebServer.java @@ -72,6 +72,7 @@ import io.cryostat.net.web.http.api.ApiVersion; import io.cryostat.net.web.http.api.v2.ApiException; import io.cryostat.util.HttpStatusCodeIdentifier; +import io.cryostat.util.URIUtil; import com.google.gson.Gson; import io.vertx.core.AbstractVerticle; @@ -318,13 +319,7 @@ public String getAssetDownloadURL(ApiVersion apiVersion, String... pathSegments) } private String getTargetId(JFRConnection conn) throws IOException { - // TODO this is a hack, the JFRConnection interface should be refactored to expose a more - // general connection URL / targetId method since the JMX implementation is now only one - // possible implementation - if (conn instanceof AgentConnection) { - return ((AgentConnection) conn).getUri().toString(); - } - return conn.getJMXURL().toString(); + return URIUtil.getConnectionUri(conn).toString(); } public FileUpload getTempFileUpload( diff --git a/src/main/java/io/cryostat/recordings/RecordingArchiveHelper.java b/src/main/java/io/cryostat/recordings/RecordingArchiveHelper.java index 1711cfe491..bcbd657aa9 100644 --- a/src/main/java/io/cryostat/recordings/RecordingArchiveHelper.java +++ b/src/main/java/io/cryostat/recordings/RecordingArchiveHelper.java @@ -938,7 +938,7 @@ private void validateRecordingPath( Path writeRecordingToDestination(JFRConnection connection, IRecordingDescriptor descriptor) throws IOException, URISyntaxException, FlightRecorderException, Exception { - URI serviceUri = URIUtil.convert(connection.getJMXURL()); + URI serviceUri = URIUtil.getConnectionUri(connection); String jvmId = jvmIdHelper.getJvmId(serviceUri.toString()); Path specificRecordingsPath = getRecordingSubdirectoryPath(jvmId); if (!fs.exists(specificRecordingsPath)) { diff --git a/src/main/java/io/cryostat/util/URIUtil.java b/src/main/java/io/cryostat/util/URIUtil.java index b81ae2fec6..9a113ebf70 100644 --- a/src/main/java/io/cryostat/util/URIUtil.java +++ b/src/main/java/io/cryostat/util/URIUtil.java @@ -37,11 +37,15 @@ */ package io.cryostat.util; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import javax.management.remote.JMXServiceURL; +import io.cryostat.core.net.JFRConnection; +import io.cryostat.net.AgentConnection; + public class URIUtil { private URIUtil() {} @@ -70,4 +74,14 @@ public static URI getRmiTarget(JMXServiceURL serviceUrl) throws URISyntaxExcepti } return new URI(pathPart.substring("/jndi/".length(), pathPart.length())); } + + public static URI getConnectionUri(JFRConnection connection) throws IOException { + // TODO this is a hack, the JFRConnection interface should be refactored to expose a more + // general connection URL / targetId method since the JMX implementation is now only one + // possible implementation + if (connection instanceof AgentConnection) { + return ((AgentConnection) connection).getUri(); + } + return URI.create(connection.getJMXURL().toString()); + } }