From 8740c84a9f7fb5ef1bc036463cba2a0bc9250867 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 2 Aug 2024 12:36:08 -0400 Subject: [PATCH] handle Recordings PERMANENT_REDIRECT --- .../io/cryostat/recordings/Recordings.java | 213 +----------------- src/test/java/itest/NonExistentTargetIT.java | 55 ----- .../java/itest/RecordingWorkflowTest.java | 2 - src/test/java/itest/SnapshotTest.java | 195 ++++++---------- 4 files changed, 66 insertions(+), 399 deletions(-) delete mode 100644 src/test/java/itest/NonExistentTargetIT.java diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index e309ac49c..2b2697cc0 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -456,22 +456,6 @@ public List listForTarget(@RestPath long id) throws E .toList(); } - /* - * @GET - * - * @Path("/api/v1/targets/{connectUrl}/recordings") - * - * @RolesAllowed("read") - * public Response listForTargetByUrl(@RestPath URI connectUrl) throws Exception - * { - * Target target = Target.getTargetByConnectUrl(connectUrl); - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location(URI.create(String.format("/api/v3/targets/%d/recordings", - * target.id))) - * .build(); - * } - */ - @PATCH @Transactional @Blocking @@ -513,36 +497,6 @@ public String patch(@RestPath long targetId, @RestPath long remoteId, String bod } } - /* - * @PATCH - * - * @Transactional - * - * @Blocking - * - * @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}") - * - * @RolesAllowed("write") - * public Response patchV1(@RestPath URI connectUrl, @RestPath String - * recordingName, String body) - * throws Exception { - * Target target = Target.getTargetByConnectUrl(connectUrl); - * Optional recording = - * connectionManager.executeConnectedTask( - * target, conn -> recordingHelper.getDescriptorByName(conn, recordingName)); - * if (recording.isEmpty()) { - * throw new NotFoundException(); - * } - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/targets/%d/recordings/%s", - * target.id, recording.get().getId()))) - * .build(); - * } - */ - @POST @Transactional @Path("/api/v1/targets/{connectUrl}/snapshot") @@ -577,10 +531,7 @@ public Uni createSnapshotV2(@RestPath URI connectUrl) throws Exception .transform( recording -> Response.status(Response.Status.CREATED) - .entity( - V2Response.json( - Response.Status.CREATED, - recordingHelper.toExternalForm(recording))) + .entity(recordingHelper.toExternalForm(recording)) .build()) .onFailure(SnapshotCreationException.class) .recoverWithItem( @@ -673,61 +624,6 @@ public Response createRecording( .build(); } - /* - * @POST - * - * @Transactional - * - * @Blocking - * - * @Path("/api/v1/targets/{connectUrl}/recordings") - * - * @RolesAllowed("write") - * public Response createRecordingV1(@RestPath URI connectUrl) throws Exception - * { - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/targets/%d/recordings", - * Target.getTargetByConnectUrl(connectUrl).id))) - * .build(); - * } - */ - - /* - * @DELETE - * - * @Transactional - * - * @Blocking - * - * @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}") - * - * @RolesAllowed("write") - * public Response deleteRecordingV1(@RestPath URI connectUrl, @RestPath String - * recordingName) - * throws Exception { - * if (StringUtils.isBlank(recordingName)) { - * throw new - * BadRequestException("\"recordingName\" form parameter must be provided"); - * } - * Target target = Target.getTargetByConnectUrl(connectUrl); - * long remoteId = - * recordingHelper.listActiveRecordings(target).stream() - * .filter(r -> Objects.equals(r.name, recordingName)) - * .findFirst() - * .map(r -> r.remoteId) - * .orElseThrow(() -> new NotFoundException()); - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/targets/%d/recordings/%d", target.id, remoteId))) - * .build(); - * } - */ - @DELETE @Transactional @Blocking @@ -803,36 +699,6 @@ public void deleteArchivedRecording(@RestPath String jvmId, @RestPath String fil } } - /* - * @POST - * - * @Blocking - * - * @Transactional - * - * @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}/upload") - * - * @RolesAllowed("write") - * public Response uploadActiveToGrafanaV1( - * - * @RestPath URI connectUrl, @RestPath String recordingName) { - * Target target = Target.getTargetByConnectUrl(connectUrl); - * long remoteId = - * recordingHelper.listActiveRecordings(target).stream() - * .filter(r -> Objects.equals(r.name, recordingName)) - * .findFirst() - * .map(r -> r.remoteId) - * .orElseThrow(() -> new NotFoundException()); - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/targets/%d/recordings/%d/upload", - * target.id, remoteId))) - * .build(); - * } - */ - @POST @Blocking @Path("/api/v3/targets/{targetId}/recordings/{remoteId}/upload") @@ -842,48 +708,6 @@ public Uni uploadActiveToGrafana(@RestPath long targetId, @RestPath long return recordingHelper.uploadToJFRDatasource(targetId, remoteId); } - /* - * @POST - * - * @Path("/api/beta/recordings/{connectUrl}/{filename}/upload") - * - * @RolesAllowed("write") - * public Response uploadArchivedToGrafanaBeta( - * - * @RestPath String connectUrl, @RestPath String filename) throws Exception { - * String jvmId; - * if ("uploads".equals(connectUrl)) { - * jvmId = "uploads"; - * } else { - * jvmId = Target.getTargetByConnectUrl(URI.create(connectUrl)).jvmId; - * } - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/grafana/%s", - * recordingHelper.encodedKey(jvmId, filename)))) - * .build(); - * } - * - * @POST - * - * @Path("/api/beta/fs/recordings/{jvmId}/{filename}/upload") - * - * @RolesAllowed("write") - * public Response uploadArchivedToGrafanaFromPath( - * - * @RestPath String jvmId, @RestPath String filename) throws Exception { - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create( - * String.format( - * "/api/v3/grafana/%s", - * recordingHelper.encodedKey(jvmId, filename)))) - * .build(); - * } - */ - @POST @Blocking @Path("/api/v3/grafana/{encodedKey}") @@ -903,24 +727,6 @@ public Uni uploadArchivedToGrafana(@RestPath String encodedKey) throws E return recordingHelper.uploadToJFRDatasource(key); } - /* - * @GET - * - * @Blocking - * - * @Path("/api/v1/targets/{connectUrl}/recordingOptions") - * - * @RolesAllowed("read") - * public Response getRecordingOptionsV1(@RestPath URI connectUrl) throws - * Exception { - * Target target = Target.getTargetByConnectUrl(connectUrl); - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create(String.format("/api/v3/targets/%d/recordingOptions", target.id))) - * .build(); - * } - */ - @GET @Blocking @Path("/api/v3/targets/{id}/recordingOptions") @@ -935,23 +741,6 @@ public Map getRecordingOptions(@RestPath long id) throws Excepti }); } - /* - * @PATCH - * - * @Blocking - * - * @Path("/api/v1/targets/{connectUrl}/recordingOptions") - * - * @RolesAllowed("write") - * public Response patchRecordingOptionsV1(@RestPath URI connectUrl) { - * Target target = Target.getTargetByConnectUrl(connectUrl); - * return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - * .location( - * URI.create(String.format("/api/v3/targets/%d/recordingOptions", target.id))) - * .build(); - * } - */ - @PATCH @Blocking @Path("/api/v3/targets/{id}/recordingOptions") diff --git a/src/test/java/itest/NonExistentTargetIT.java b/src/test/java/itest/NonExistentTargetIT.java deleted file mode 100644 index aac11db69..000000000 --- a/src/test/java/itest/NonExistentTargetIT.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 itest; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import io.quarkus.test.junit.QuarkusIntegrationTest; -import io.vertx.core.json.JsonArray; -import io.vertx.ext.web.handler.HttpException; -import itest.bases.StandardSelfTest; -import org.apache.http.client.utils.URLEncodedUtils; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -@QuarkusIntegrationTest -public class NonExistentTargetIT extends StandardSelfTest { - - static final String BAD_TARGET_CONNECT_URL = - "service:jmx:rmi:///jndi/rmi://nosuchhost:9091/jmxrmi"; - static final String BAD_TARGET_CONNECT_URL_ENCODED = - URLEncodedUtils.formatSegments(BAD_TARGET_CONNECT_URL); - - @Test - public void testConnectionFailsAsExpected() throws Exception { - CompletableFuture response = new CompletableFuture<>(); - webClient - .get(String.format("/api/v1/targets/%s/recordings", BAD_TARGET_CONNECT_URL_ENCODED)) - .send( - ar -> { - if (assertRequestStatus(ar, response)) { - response.complete(ar.result().bodyAsJsonArray()); - } - }); - ExecutionException ex = - Assertions.assertThrows(ExecutionException.class, () -> response.get()); - MatcherAssert.assertThat( - ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - } -} diff --git a/src/test/java/itest/RecordingWorkflowTest.java b/src/test/java/itest/RecordingWorkflowTest.java index 4d0f09fa1..9defe7185 100644 --- a/src/test/java/itest/RecordingWorkflowTest.java +++ b/src/test/java/itest/RecordingWorkflowTest.java @@ -96,7 +96,6 @@ public void testWorkflow() throws Exception { } }); listResp = listRespFuture2.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - System.out.println("+++List response: " + listResp.encode()); MatcherAssert.assertThat( "list should have size 1 after recording creation", @@ -105,7 +104,6 @@ public void testWorkflow() throws Exception { JsonObject recordingInfo = listResp.getJsonObject(0); long remoteId = recordingInfo.getLong("remoteId"); TEST_REMOTE_ID = remoteId; - System.out.println("+++TEST_REMORE_ID: " + TEST_REMOTE_ID); MatcherAssert.assertThat( recordingInfo.getString("name"), Matchers.equalTo(TEST_RECORDING_NAME)); MatcherAssert.assertThat(recordingInfo.getString("state"), Matchers.equalTo("RUNNING")); diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 83f192396..510498237 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -17,6 +17,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.net.URI; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -65,10 +67,7 @@ private void cleanupRecordings() throws Exception { .delete(String.format("%s/recordings/%d", v3RequestUrl(), remoteId)) .send( ar -> { - if (ar.succeeded()) { - System.out.println( - "Deleted recording with remote ID: " + remoteId); - } else { + if (!ar.succeeded()) { System.err.println( "Failed to delete recording with remote ID: " + remoteId @@ -148,6 +147,68 @@ void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); } + @Test + void testPostV2ShouldCreateSnapshot() throws Exception { + CompletableFuture snapshotName = new CompletableFuture<>(); + + // Create a recording + createRecording(snapshotName); + + Thread.sleep(5_000l); + + // Create a snapshot recording of all events at that time + CompletableFuture createResponse = new CompletableFuture<>(); + webClient + .post(String.format("%s/snapshot", v2RequestUrl())) + .send( + ar -> { + if (ar.succeeded()) { + HttpResponse response = ar.result(); + if (response.statusCode() == 201) { + JsonObject jsonResponse = response.bodyAsJsonObject(); + String name = jsonResponse.getString("name"); + long snapshotRemoteId = jsonResponse.getLong("remoteId"); + snapshotName.complete(name); + createResponse.complete(jsonResponse); + recordingsToDelete.add(snapshotRemoteId); + } else { + System.err.println( + "Failed to create snapshot, status code: " + + response.statusCode()); + createResponse.completeExceptionally( + new RuntimeException( + "Failed to create snapshot, Status code: " + + response.statusCode())); + } + } else { + System.err.println("Request failed: " + ar.cause()); + createResponse.completeExceptionally(ar.cause()); + } + }); + + JsonObject json = createResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + String resolvedName = snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Validate the JSON response + MatcherAssert.assertThat(json.getString("name"), Matchers.equalTo(resolvedName)); + MatcherAssert.assertThat(json.getLong("remoteId"), Matchers.greaterThan(0L)); + MatcherAssert.assertThat(json.getString("state"), Matchers.equalTo("STOPPED")); + MatcherAssert.assertThat( + json.getLong("startTime"), + Matchers.lessThanOrEqualTo(Instant.now().toEpochMilli())); + MatcherAssert.assertThat( + json.getString("downloadUrl"), Matchers.startsWith("/api/v3/activedownload/")); + MatcherAssert.assertThat( + json.getString("reportUrl"), + Matchers.equalTo( + URI.create( + String.format( + "%s/reports/%d", + selfCustomTargetLocation, json.getLong("remoteId"))) + .getPath())); + MatcherAssert.assertThat(json.containsKey("expiry"), Matchers.is(false)); + } + @Test void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { CompletableFuture snapshotName = new CompletableFuture<>(); @@ -166,23 +227,6 @@ void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); } - /* - * @Test - * void testPostV2ShouldCreateSnapshot() throws Exception { - * CompletableFuture snapshotName2 = new CompletableFuture<>(); - * - * // Create a recording - * createRecording(snapshotName2); - * - * Thread.sleep(5_000); - * createV2Snapshot(snapshotName2); - * - * MatcherAssert.assertThat( - * snapshotName2.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - * Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); - * } - */ - private JsonArray fetchPreTestRecordings() throws Exception { CompletableFuture preListRespFuture = new CompletableFuture<>(); webClient @@ -248,8 +292,6 @@ private void createRecording(CompletableFuture snapshotName) throws Exce long remoteId = jsonResponse.getLong("remoteId"); REMOTE_ID = remoteId; recordingsToDelete.add(remoteId); // Store for later cleanup - System.out.println( - "Recording created with remote ID: " + remoteId); } } }); @@ -307,110 +349,3 @@ private void createV2Snapshot(CompletableFuture snapshotName) throws Exc }); } } -/* - * @Test - * void testPostV2ShouldCreateSnapshot() throws Exception { - * CompletableFuture snapshotName = new CompletableFuture<>(); - * CompletableFuture remoteIdFuture = new CompletableFuture<>(); - * - * // Create a recording - * MultiMap form = MultiMap.caseInsensitiveMultiMap(); - * form.add("recordingName", TEST_RECORDING_NAME); - * form.add("duration", "5"); - * form.add("events", "template=ALL"); - * webClient - * .post(String.format("%s/recordings", v3RequestUrl())) - * .sendForm( - * form, - * ar -> { - * if (ar.succeeded()) { - * HttpResponse response = ar.result(); - * if (response.statusCode() == 201) { - * JsonObject jsonResponse = response.bodyAsJsonObject(); - * long remoteId = jsonResponse.getLong("remoteId"); - * remoteIdFuture.complete(remoteId); - * } else { - * remoteIdFuture.completeExceptionally( - * new RuntimeException("Failed to create recording")); - * } - * } else { - * remoteIdFuture.completeExceptionally(ar.cause()); - * } - * }); - * - * long remoteId = remoteIdFuture.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * - * Thread.sleep(5_000l); - * - * // Create a snapshot recording of all events at that time - * CompletableFuture createResponse = new CompletableFuture<>(); - * webClient - * .post(String.format("%s/snapshot", v2RequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, createResponse)) { - * MatcherAssert.assertThat( - * ar.result().statusCode(), Matchers.equalTo(201)); - * MatcherAssert.assertThat( - * ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), - * Matchers.equalTo("application/json;charset=UTF-8")); - * createResponse.complete(ar.result().bodyAsJsonObject()); - * } - * }); - * - * snapshotName.complete( - * createResponse - * .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - * .getJsonObject("data") - * .getJsonObject("result") - * .getString("name")); - * - * JsonObject json = createResponse.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * - * MatcherAssert.assertThat( - * json.getJsonObject("meta"), - * Matchers.equalTo( - * new JsonObject(Map.of("type", "application/json", "status", "Created")))); - * MatcherAssert.assertThat(json.getMap(), Matchers.hasKey("data")); - * MatcherAssert.assertThat(json.getJsonObject("data").getMap(), - * Matchers.hasKey("result")); - * JsonObject result = json.getJsonObject("data").getJsonObject("result"); - * MatcherAssert.assertThat(result.getString("state"), - * Matchers.equalTo("STOPPED")); - * MatcherAssert.assertThat( - * result.getLong("startTime"), - * Matchers.lessThanOrEqualTo(Instant.now().toEpochMilli())); - * MatcherAssert.assertThat( - * result.getString("name"), - * Matchers.equalTo(snapshotName.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS))); - * MatcherAssert.assertThat(result.getLong("id"), Matchers.greaterThan(0L)); - * MatcherAssert.assertThat( - * result.getString("downloadUrl"), - * Matchers.equalTo("/api/v3/activedownload/" + result.getLong("id"))); - * MatcherAssert.assertThat( - * result.getString("reportUrl"), - * Matchers.equalTo( - * URI.create( - * String.format( - * "%s/reports/%d", - * selfCustomTargetLocation, - * result.getLong("remoteId"))) - * .getPath())); - * MatcherAssert.assertThat(result.getLong("expiry"), Matchers.nullValue()); - * - * webClient - * .extensions() - * .delete( - * String.format("%s/recordings/%d", v3RequestUrl(), remoteId), - * REQUEST_TIMEOUT_SECONDS); - * webClient - * .extensions() - * .delete( - * String.format("%s/recordings/%d", v1RequestUrl(), remoteId), - * REQUEST_TIMEOUT_SECONDS); - * } - * - */