From f1ed1c2578dc596a27d877afc581ac9fdcd24a7b Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 14 Aug 2023 11:39:26 -0400 Subject: [PATCH] feat(agent): implement Agent HTTP dynamic JFR stop/delete (#1604) --- .../java/io/cryostat/net/AgentClient.java | 52 ++++++++++++++++--- .../java/io/cryostat/net/AgentJFRService.java | 20 +++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/cryostat/net/AgentClient.java b/src/main/java/io/cryostat/net/AgentClient.java index 4d0ff0c155..5552758a9c 100644 --- a/src/main/java/io/cryostat/net/AgentClient.java +++ b/src/main/java/io/cryostat/net/AgentClient.java @@ -123,7 +123,7 @@ Future ping() { Future mbeanMetrics() { Future> f = - invoke(HttpMethod.GET, "/mbean-metrics", BodyCodec.string()); + invoke(HttpMethod.GET, "/mbean-metrics/", BodyCodec.string()); return f.map(HttpResponse::body) // uses Gson rather than Vertx's Jackson because Gson is able to handle MBeanMetrics // with no additional fuss. Jackson complains about private final fields. @@ -134,7 +134,7 @@ Future startRecording(StartRecordingRequest req) { Future> f = invoke( HttpMethod.POST, - "/recordings", + "/recordings/", Buffer.buffer(gson.toJson(req)), BodyCodec.string()); return f.map( @@ -152,8 +152,48 @@ Future startRecording(StartRecordingRequest req) { }); } + Future stopRecording(long id) { + Future> f = + invoke( + HttpMethod.PATCH, + String.format("/recordings/%d", id), + Buffer.buffer(), + BodyCodec.none()); + return f.map( + resp -> { + int statusCode = resp.statusCode(); + if (HttpStatusCodeIdentifier.isSuccessCode(statusCode)) { + return null; + } else if (statusCode == 403) { + throw new UnsupportedOperationException(); + } else { + throw new RuntimeException("Unknown failure"); + } + }); + } + + Future deleteRecording(long id) { + Future> f = + invoke( + HttpMethod.DELETE, + String.format("/recordings/%d", id), + Buffer.buffer(), + BodyCodec.none()); + return f.map( + resp -> { + int statusCode = resp.statusCode(); + if (HttpStatusCodeIdentifier.isSuccessCode(statusCode)) { + return null; + } else if (statusCode == 403) { + throw new UnsupportedOperationException(); + } else { + throw new RuntimeException("Unknown failure"); + } + }); + } + Future> activeRecordings() { - Future> f = invoke(HttpMethod.GET, "/recordings", BodyCodec.string()); + Future> f = invoke(HttpMethod.GET, "/recordings/", BodyCodec.string()); return f.map(HttpResponse::body) .map( s -> @@ -168,14 +208,14 @@ Future> activeRecordings() { Future> eventTypes() { Future> f = - invoke(HttpMethod.GET, "/event-types", BodyCodec.jsonArray()); + invoke(HttpMethod.GET, "/event-types/", BodyCodec.jsonArray()); return f.map(HttpResponse::body) .map(arr -> arr.stream().map(o -> new AgentEventTypeInfo((JsonObject) o)).toList()); } Future> eventSettings() { Future> f = - invoke(HttpMethod.GET, "/event-settings", BodyCodec.jsonArray()); + invoke(HttpMethod.GET, "/event-settings/", BodyCodec.jsonArray()); return f.map(HttpResponse::body) .map( arr -> { @@ -229,7 +269,7 @@ Future> eventSettings() { Future> eventTemplates() { Future> f = - invoke(HttpMethod.GET, "/event-templates", BodyCodec.jsonArray()); + invoke(HttpMethod.GET, "/event-templates/", BodyCodec.jsonArray()); return f.map(HttpResponse::body).map(arr -> arr.stream().map(Object::toString).toList()); } diff --git a/src/main/java/io/cryostat/net/AgentJFRService.java b/src/main/java/io/cryostat/net/AgentJFRService.java index 7e80a179b2..df9b490a76 100644 --- a/src/main/java/io/cryostat/net/AgentJFRService.java +++ b/src/main/java/io/cryostat/net/AgentJFRService.java @@ -104,7 +104,14 @@ public String getVersion() { @Override public void close(IRecordingDescriptor descriptor) throws FlightRecorderException { - throw new UnimplementedException(); + try { + client.deleteRecording(descriptor.getId()) + .toCompletionStage() + .toCompletableFuture() + .get(); + } catch (InterruptedException | ExecutionException e) { + throw new FlightRecorderException("Failed to stop recording", e); + } } @Override @@ -227,8 +234,15 @@ public IRecordingDescriptor start( } @Override - public void stop(IRecordingDescriptor arg0) throws FlightRecorderException { - throw new UnimplementedException(); + public void stop(IRecordingDescriptor descriptor) throws FlightRecorderException { + try { + client.stopRecording(descriptor.getId()) + .toCompletionStage() + .toCompletableFuture() + .get(); + } catch (InterruptedException | ExecutionException e) { + throw new FlightRecorderException("Failed to stop recording", e); + } } @Override