From f6804963ae6b8ec9123b6a73e6ae2b43a8a6cabf Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 25 Jul 2024 17:03:02 -0400 Subject: [PATCH 001/140] handle CustomeDiscovery PERMANENT_REDIRECT --- .../cryostat/discovery/CustomDiscovery.java | 12 ------- .../discovery/CustomDiscoveryTest.java | 10 +++--- src/test/java/itest/CustomTargetsTest.java | 32 ++++++++++++++++--- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index bbcd6d26a..8583d18a0 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -56,7 +56,6 @@ import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; -import org.jboss.resteasy.reactive.RestResponse; @ApplicationScoped @Path("") @@ -250,17 +249,6 @@ Response doV2Create( } } - @Transactional - @DELETE - @Path("/api/v2/targets/{connectUrl}") - @RolesAllowed("write") - public Response delete(@RestPath URI connectUrl) throws URISyntaxException { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/targets/%d", target.id))) - .build(); - } - @Transactional @DELETE @Path("/api/v3/targets/{id}") diff --git a/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java b/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java index 6330dde2b..05b2b2ccc 100644 --- a/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java +++ b/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java @@ -60,14 +60,14 @@ public void testCreateAndDeleteTargetWithValidJmxUrl() throws Exception { assertNotNull(createdTarget); assertEquals("some-jvm-id", createdTarget.jvmId); - // Delete the Target by connect URL - Response deleteResponse = customDiscovery.delete(target.connectUrl); + /* // Delete the Target by connect URL + Response deleteResponse = customDiscovery.delete(target.id); assertEquals( - Response.Status.PERMANENT_REDIRECT.getStatusCode(), deleteResponse.getStatus()); + Response.Status.ACCEPTED.getStatusCode(), deleteResponse.getStatus()); // Extract the ID from the redirect location - String location = deleteResponse.getLocation().getPath(); - long targetId = Long.parseLong(location.substring(location.lastIndexOf('/') + 1)); + String location = deleteResponse.getLocation().getPath(); */ + long targetId = createdTarget.id; Response finalDeleteResponse = customDiscovery.delete(targetId); assertEquals(Response.Status.NO_CONTENT.getStatusCode(), finalDeleteResponse.getStatus()); diff --git a/src/test/java/itest/CustomTargetsTest.java b/src/test/java/itest/CustomTargetsTest.java index f5865310d..f4ed6c0c8 100644 --- a/src/test/java/itest/CustomTargetsTest.java +++ b/src/test/java/itest/CustomTargetsTest.java @@ -248,6 +248,8 @@ void shouldBeAbleToDeleteTarget() throws TimeoutException, ExecutionException, InterruptedException { CountDownLatch latch = new CountDownLatch(1); + long targetId = retrieveTargetId(); + worker.submit( () -> { try { @@ -267,7 +269,7 @@ void shouldBeAbleToDeleteTarget() MatcherAssert.assertThat( event.getJsonObject("serviceRef") .getString("connectUrl"), - Matchers.equalTo("localhost:0")); + Matchers.equalTo(SELF_JMX_URL)); MatcherAssert.assertThat( event.getJsonObject("serviceRef") .getString("alias"), @@ -282,17 +284,37 @@ void shouldBeAbleToDeleteTarget() webClient .extensions() - .delete( - String.format("/api/v2/targets/%s", JMX_URL_ENCODED), - REQUEST_TIMEOUT_SECONDS); + .delete(String.format("/api/v3/targets/%d", targetId), REQUEST_TIMEOUT_SECONDS); latch.await(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + // Verify that no targets remain HttpResponse listResponse = - webClient.extensions().get("/api/v1/targets", REQUEST_TIMEOUT_SECONDS); + webClient.extensions().get("/api/v3/targets", REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat(listResponse.statusCode(), Matchers.equalTo(200)); JsonArray list = listResponse.bodyAsJsonArray(); MatcherAssert.assertThat(list, Matchers.notNullValue()); MatcherAssert.assertThat(list.size(), Matchers.equalTo(0)); } + + private long retrieveTargetId() + throws InterruptedException, ExecutionException, TimeoutException { + // Call the API endpoint to list all targets + HttpResponse response = + webClient.extensions().get("/api/v3/targets", REQUEST_TIMEOUT_SECONDS); + if (response.statusCode() != 200) { + throw new IllegalStateException("Failed to retrieve targets from API"); + } + + JsonArray targets = response.bodyAsJsonArray(); + + for (int i = 0; i < targets.size(); i++) { + JsonObject target = targets.getJsonObject(i); + if (target.getString("connectUrl").equals(SELF_JMX_URL) + || target.getString("alias").equals("knownAlias")) { + return target.getLong("id"); + } + } + throw new IllegalStateException("Target not found with known identifier"); + } } From d816ae358e2552e6b76229f5bd5c5478f0d02e4b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 25 Jul 2024 17:42:51 -0400 Subject: [PATCH 002/140] cleanup CustomeDiscoveryTest --- .../java/io/cryostat/discovery/CustomDiscoveryTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java b/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java index 05b2b2ccc..9df3cc5b8 100644 --- a/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java +++ b/src/test/java/io/cryostat/discovery/CustomDiscoveryTest.java @@ -60,13 +60,6 @@ public void testCreateAndDeleteTargetWithValidJmxUrl() throws Exception { assertNotNull(createdTarget); assertEquals("some-jvm-id", createdTarget.jvmId); - /* // Delete the Target by connect URL - Response deleteResponse = customDiscovery.delete(target.id); - assertEquals( - Response.Status.ACCEPTED.getStatusCode(), deleteResponse.getStatus()); - - // Extract the ID from the redirect location - String location = deleteResponse.getLocation().getPath(); */ long targetId = createdTarget.id; Response finalDeleteResponse = customDiscovery.delete(targetId); From 6df1c5cd6ecb9866af75ada618182e79737e2a64 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 29 Jul 2024 11:09:42 -0400 Subject: [PATCH 003/140] remove Discovery redirect --- src/main/java/io/cryostat/discovery/Discovery.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index 62f5b5269..3c17ccf1d 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -143,15 +143,6 @@ void onStop(@Observes ShutdownEvent evt) throws SchedulerException { scheduler.shutdown(); } - @GET - @Path("/api/v2.1/discovery") - @RolesAllowed("read") - public Response getv21() { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/discovery")) - .build(); - } - @GET @Path("/api/v3/discovery") @RolesAllowed("read") From b92a5255c7ebe7b34721548a80f16cbeca84814b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 29 Jul 2024 13:33:43 -0400 Subject: [PATCH 004/140] handle Events PERMANENT_REDIRECT --- src/main/java/io/cryostat/events/Events.java | 16 ---------------- src/test/java/itest/TargetEventsGetTest.java | 3 +-- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/main/java/io/cryostat/events/Events.java b/src/main/java/io/cryostat/events/Events.java index b59f8a4c2..1d93f3613 100644 --- a/src/main/java/io/cryostat/events/Events.java +++ b/src/main/java/io/cryostat/events/Events.java @@ -36,7 +36,6 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; -import org.jboss.resteasy.reactive.RestResponse; @Path("") public class Events { @@ -44,21 +43,6 @@ public class Events { @Inject TargetConnectionManager connectionManager; @Inject Logger logger; - @GET - @Path("/api/v1/targets/{connectUrl}/events") - @RolesAllowed("read") - public Response listEventsV1(@RestPath URI connectUrl, @RestQuery String q) throws Exception { - logger.tracev("connectUrl: %s", connectUrl.toString()); - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/events%s", - target.id, q == null ? "" : "?q=" + q))) - .build(); - } - @GET @Path("/api/v2/targets/{connectUrl}/events") @RolesAllowed("read") diff --git a/src/test/java/itest/TargetEventsGetTest.java b/src/test/java/itest/TargetEventsGetTest.java index aa0e6582d..7cb1c6d3d 100644 --- a/src/test/java/itest/TargetEventsGetTest.java +++ b/src/test/java/itest/TargetEventsGetTest.java @@ -42,8 +42,7 @@ public class TargetEventsGetTest extends StandardSelfTest { @BeforeEach void setup() { - eventReqUrl = - String.format("/api/v1/targets/%s/events", getSelfReferenceConnectUrlEncoded()); + eventReqUrl = String.format("/api/v3/targets/%d/events", getSelfReferenceTargetId()); searchReqUrl = String.format("/api/v2/targets/%s/events", getSelfReferenceConnectUrlEncoded()); } From a784a5a040f8752bd1354ba245bda645020dcf32 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 29 Jul 2024 16:20:02 -0400 Subject: [PATCH 005/140] handle EventTemplates PERMANENT_REDIRECT --- .../io/cryostat/events/EventTemplates.java | 64 ------------------- src/test/java/itest/CryostatTemplateIT.java | 4 +- .../java/itest/CustomEventTemplateTest.java | 6 +- 3 files changed, 5 insertions(+), 69 deletions(-) diff --git a/src/main/java/io/cryostat/events/EventTemplates.java b/src/main/java/io/cryostat/events/EventTemplates.java index dade41164..5e63ca5ce 100644 --- a/src/main/java/io/cryostat/events/EventTemplates.java +++ b/src/main/java/io/cryostat/events/EventTemplates.java @@ -16,7 +16,6 @@ package io.cryostat.events; import java.io.IOException; -import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -61,26 +60,6 @@ public class EventTemplates { @Inject S3TemplateService customTemplateService; @Inject Logger logger; - @GET - @Path("/api/v1/targets/{connectUrl}/templates") - @RolesAllowed("read") - public Response listTemplatesV1(@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/event_templates", target.id))) - .build(); - } - - @POST - @Path("/api/v1/templates") - @RolesAllowed("write") - public Response postTemplatesV1(@RestForm("template") FileUpload body) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/event_templates")) - .build(); - } - @POST @Path("/api/v3/event_templates") @RolesAllowed("write") @@ -95,15 +74,6 @@ public void postTemplates(@RestForm("template") FileUpload body) throws IOExcept } } - @DELETE - @Path("/api/v1/templates/{templateName}") - @RolesAllowed("write") - public Response deleteTemplatesV1(@RestPath String templateName) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/event_templates/%s", templateName))) - .build(); - } - @DELETE @Blocking @Path("/api/v3/event_templates/{templateName}") @@ -112,40 +82,6 @@ public void deleteTemplates(@RestPath String templateName) { customTemplateService.deleteTemplate(templateName); } - @GET - @Path("/api/v1/targets/{connectUrl}/templates/{templateName}/type/{templateType}") - @RolesAllowed("read") - public Response getTargetTemplateV1( - @RestPath URI connectUrl, - @RestPath String templateName, - @RestPath TemplateType templateType) { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/event_templates/%s/%s", - target.id, templateType, templateName))) - .build(); - } - - @GET - @Path("/api/v2.1/targets/{connectUrl}/templates/{templateName}/type/{templateType}") - @RolesAllowed("read") - public Response getTargetTemplateV2_1( - @RestPath URI connectUrl, - @RestPath String templateName, - @RestPath TemplateType templateType) { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/event_templates/%s/%s", - target.id, templateType, templateName))) - .build(); - } - @GET @Blocking @Path("/api/v3/event_templates") diff --git a/src/test/java/itest/CryostatTemplateIT.java b/src/test/java/itest/CryostatTemplateIT.java index 6feb1a0dd..f94a9a231 100644 --- a/src/test/java/itest/CryostatTemplateIT.java +++ b/src/test/java/itest/CryostatTemplateIT.java @@ -37,8 +37,8 @@ public class CryostatTemplateIT extends StandardSelfTest { public void shouldHaveCryostatTemplate() throws Exception { String url = String.format( - "/api/v1/targets/%s/templates/Cryostat/type/TARGET", - getSelfReferenceConnectUrlEncoded()); + "/api/v3/targets/%d/event_templates/TARGET/Cryostat", + getSelfReferenceTargetId()); File file = downloadFile(url, "cryostat", ".jfc") .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) diff --git a/src/test/java/itest/CustomEventTemplateTest.java b/src/test/java/itest/CustomEventTemplateTest.java index 94c8e7a02..38dc19251 100644 --- a/src/test/java/itest/CustomEventTemplateTest.java +++ b/src/test/java/itest/CustomEventTemplateTest.java @@ -36,7 +36,7 @@ public class CustomEventTemplateTest extends StandardSelfTest { static final String TEMPLATE_FILE_NAME = "CustomEventTemplate.jfc"; static final String TEMPLATE_NAME = "invalidTemplate"; static final String MEDIA_TYPE = "application/xml"; - static final String REQ_URL = "/api/v1/templates"; + static final String REQ_URL = "/api/v3/event_templates"; @Test public void shouldThrowIfTemplateUploadNameInvalid() throws Exception { @@ -98,8 +98,8 @@ public void testPostedTemplateNameIsSanitizedAndCanBeDeleted() throws Exception .extensions() .get( String.format( - "/api/v1/targets/%s/templates", - getSelfReferenceConnectUrlEncoded()), + "/api/v3/targets/%d/event_templates", + getSelfReferenceTargetId()), REQUEST_TIMEOUT_SECONDS); boolean foundSanitizedTemplate = false; for (Object o : getResp.bodyAsJsonArray()) { From 69f7183d37ff40a732564d61416a67bbd6734810 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 29 Jul 2024 16:54:12 -0400 Subject: [PATCH 006/140] delete GraphQL.java --- .../java/io/cryostat/graphql/GraphQL.java | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 src/main/java/io/cryostat/graphql/GraphQL.java diff --git a/src/main/java/io/cryostat/graphql/GraphQL.java b/src/main/java/io/cryostat/graphql/GraphQL.java deleted file mode 100644 index 591a82a3b..000000000 --- a/src/main/java/io/cryostat/graphql/GraphQL.java +++ /dev/null @@ -1,51 +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 io.cryostat.graphql; - -import java.net.URI; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriInfo; -import org.jboss.resteasy.reactive.RestResponse; - -@Path("") -public class GraphQL { - - @GET - @Path("/api/v2.2/graphql") - @RolesAllowed("write") - public Response redirectGet(UriInfo info) throws Exception { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/graphql?%s", info.getRequestUri().getRawQuery()))) - .build(); - } - - @POST - @Path("/api/v2.2/graphql") - @RolesAllowed("write") - public Response redirectPost() throws Exception { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/graphql")) - .build(); - } -} From dac25613d88a0d9c254c780e1528ed3050c6e931 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 30 Jul 2024 11:34:39 -0400 Subject: [PATCH 007/140] remove JMCAgent redirect --- .../java/io/cryostat/jmcagent/JMCAgent.java | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/src/main/java/io/cryostat/jmcagent/JMCAgent.java b/src/main/java/io/cryostat/jmcagent/JMCAgent.java index 18cb2147c..c959304e1 100644 --- a/src/main/java/io/cryostat/jmcagent/JMCAgent.java +++ b/src/main/java/io/cryostat/jmcagent/JMCAgent.java @@ -16,7 +16,6 @@ package io.cryostat.jmcagent; import java.io.ByteArrayInputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -105,20 +104,6 @@ public Response postProbe(@RestPath long id, @RestPath String probeTemplateName) } } - @Blocking - @POST - @Path("/api/v2/targets/{connectUrl}/probes/{probeTemplateName}") - public Response postProbev2(@RestPath URI connectUrl, @RestPath String probeTemplateName) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/probes/%s", - Target.getTargetByConnectUrl(connectUrl).id, - probeTemplateName))) - .build(); - } - @Blocking @DELETE @Path("/api/v3/targets/{id}/probes") @@ -151,19 +136,6 @@ public Response deleteProbe(@RestPath long id) { } } - @Blocking - @DELETE - @Path("/api/v2/targets/{connectUrl}/probes") - public Response deleteProbev2(@RestPath URI connectUrl) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/probes", - Target.getTargetByConnectUrl(connectUrl).id))) - .build(); - } - @Blocking @GET @Path("/api/v3/targets/{id}/probes") @@ -197,16 +169,6 @@ public V2Response getProbes(@RestPath long id) { } } - @Blocking - @GET - @Path("/api/v2/targets/{connectUrl}/probes") - public Response getProbesv2(@RestPath URI connectUrl) { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/targets/%d/probes", target.id))) - .build(); - } - @Blocking @GET @Path("/api/v3/probes") @@ -223,15 +185,6 @@ public V2Response getProbeTemplates() { } } - @Blocking - @GET - @Path("/api/v2/probes") - public Response getProbeTemplatesv2() { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/probes")) - .build(); - } - @Blocking @DELETE @Path("/api/v3/probes/{probeTemplateName}") @@ -245,15 +198,6 @@ public Response deleteProbeTemplate(@RestPath String probeTemplateName) { } } - @Blocking - @DELETE - @Path("/api/v2/probes/{probeTemplateName}") - public Response deleteProbeTemplatesv2(@RestPath String probeTemplateName) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/probes/%s", probeTemplateName))) - .build(); - } - @Blocking @POST @Path("/api/v3/probes/{probeTemplateName}") @@ -270,14 +214,4 @@ public Response uploadProbeTemplate( throw new BadRequestException(e); } } - - @Blocking - @POST - @Path("/api/v2/probes/{probeTemplateName}") - public Response postProbeTemplatev2( - @RestForm("probeTemplate") FileUpload body, @RestPath String probeTemplateName) { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/probes/%s", probeTemplateName))) - .build(); - } } From f76bb3ed67161715f6a2b9c9c5a4cd313235eb95 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 30 Jul 2024 17:14:45 -0400 Subject: [PATCH 008/140] handle Recordings PERMANENT_REDIRECT && Todo: fix failing tests --- .../io/cryostat/recordings/Recordings.java | 33 ++++++++--------- src/test/java/itest/GraphQLTest.java | 5 +-- .../java/itest/RecordingWorkflowTest.java | 37 +++++++------------ src/test/java/itest/UploadRecordingTest.java | 13 ++++--- .../java/itest/bases/StandardSelfTest.java | 4 +- 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index bcd408129..b6d931743 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -35,7 +35,6 @@ import org.openjdk.jmc.common.unit.IConstrainedMap; import org.openjdk.jmc.common.unit.IOptionDescriptor; import org.openjdk.jmc.flightrecorder.configuration.IFlightRecorderService; -import org.openjdk.jmc.flightrecorder.configuration.IRecordingDescriptor; import org.openjdk.jmc.flightrecorder.configuration.recording.RecordingOptionsBuilder; import io.cryostat.ConfigProperties; @@ -456,7 +455,7 @@ public List listForTarget(@RestPath long id) throws E .toList(); } - @GET + /* @GET @Path("/api/v1/targets/{connectUrl}/recordings") @RolesAllowed("read") public Response listForTargetByUrl(@RestPath URI connectUrl) throws Exception { @@ -464,7 +463,7 @@ public Response listForTargetByUrl(@RestPath URI connectUrl) throws Exception { return Response.status(RestResponse.Status.PERMANENT_REDIRECT) .location(URI.create(String.format("/api/v3/targets/%d/recordings", target.id))) .build(); - } + } */ @PATCH @Transactional @@ -507,7 +506,7 @@ public String patch(@RestPath long targetId, @RestPath long remoteId, String bod } } - @PATCH + /* @PATCH @Transactional @Blocking @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}") @@ -528,7 +527,7 @@ public Response patchV1(@RestPath URI connectUrl, @RestPath String recordingName "/api/v3/targets/%d/recordings/%s", target.id, recording.get().getId()))) .build(); - } + } */ @POST @Transactional @@ -653,7 +652,7 @@ public Response createRecording( .build(); } - @POST + /* @POST @Transactional @Blocking @Path("/api/v1/targets/{connectUrl}/recordings") @@ -666,9 +665,9 @@ public Response createRecordingV1(@RestPath URI connectUrl) throws Exception { "/api/v3/targets/%d/recordings", Target.getTargetByConnectUrl(connectUrl).id))) .build(); - } + } */ - @DELETE + /* @DELETE @Transactional @Blocking @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}") @@ -691,7 +690,7 @@ public Response deleteRecordingV1(@RestPath URI connectUrl, @RestPath String rec String.format( "/api/v3/targets/%d/recordings/%d", target.id, remoteId))) .build(); - } + } */ @DELETE @Transactional @@ -768,7 +767,7 @@ public void deleteArchivedRecording(@RestPath String jvmId, @RestPath String fil } } - @POST + /* @POST @Blocking @Transactional @Path("/api/v1/targets/{connectUrl}/recordings/{recordingName}/upload") @@ -789,7 +788,7 @@ public Response uploadActiveToGrafanaV1( "/api/v3/targets/%d/recordings/%d/upload", target.id, remoteId))) .build(); - } + } */ @POST @Blocking @@ -800,7 +799,7 @@ public Uni uploadActiveToGrafana(@RestPath long targetId, @RestPath long return recordingHelper.uploadToJFRDatasource(targetId, remoteId); } - @POST + /* @POST @Path("/api/beta/recordings/{connectUrl}/{filename}/upload") @RolesAllowed("write") public Response uploadArchivedToGrafanaBeta( @@ -832,7 +831,7 @@ public Response uploadArchivedToGrafanaFromPath( "/api/v3/grafana/%s", recordingHelper.encodedKey(jvmId, filename)))) .build(); - } + } */ @POST @Blocking @@ -853,7 +852,7 @@ public Uni uploadArchivedToGrafana(@RestPath String encodedKey) throws E return recordingHelper.uploadToJFRDatasource(key); } - @GET + /* @GET @Blocking @Path("/api/v1/targets/{connectUrl}/recordingOptions") @RolesAllowed("read") @@ -863,7 +862,7 @@ public Response getRecordingOptionsV1(@RestPath URI connectUrl) throws Exception .location( URI.create(String.format("/api/v3/targets/%d/recordingOptions", target.id))) .build(); - } + } */ @GET @Blocking @@ -879,7 +878,7 @@ public Map getRecordingOptions(@RestPath long id) throws Excepti }); } - @PATCH + /* @PATCH @Blocking @Path("/api/v1/targets/{connectUrl}/recordingOptions") @RolesAllowed("write") @@ -889,7 +888,7 @@ public Response patchRecordingOptionsV1(@RestPath URI connectUrl) { .location( URI.create(String.format("/api/v3/targets/%d/recordingOptions", target.id))) .build(); - } + } */ @PATCH @Blocking diff --git a/src/test/java/itest/GraphQLTest.java b/src/test/java/itest/GraphQLTest.java index 4acfab581..c5d276ea2 100644 --- a/src/test/java/itest/GraphQLTest.java +++ b/src/test/java/itest/GraphQLTest.java @@ -739,10 +739,7 @@ public void shouldReturnArchivedRecordingsFilteredByNames() throws Exception { // Check preconditions CompletableFuture listRespFuture1 = new CompletableFuture<>(); webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) + .get(String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId())) .send( ar -> { if (assertRequestStatus(ar, listRespFuture1)) { diff --git a/src/test/java/itest/RecordingWorkflowTest.java b/src/test/java/itest/RecordingWorkflowTest.java index 1abafc771..4d0f09fa1 100644 --- a/src/test/java/itest/RecordingWorkflowTest.java +++ b/src/test/java/itest/RecordingWorkflowTest.java @@ -51,16 +51,14 @@ public class RecordingWorkflowTest extends StandardSelfTest { static final String TEST_RECORDING_NAME = "workflow_itest"; static final String TARGET_ALIAS = "selftest"; + static long TEST_REMOTE_ID; @Test public void testWorkflow() throws Exception { // Check preconditions CompletableFuture listRespFuture1 = new CompletableFuture<>(); webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) + .get(String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId())) .followRedirects(true) .send( ar -> { @@ -82,18 +80,14 @@ public void testWorkflow() throws Exception { .extensions() .post( String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded()), + "/api/v3/targets/%d/recordings", getSelfReferenceTargetId()), form, REQUEST_TIMEOUT_SECONDS); // verify in-memory recording created CompletableFuture listRespFuture2 = new CompletableFuture<>(); webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) + .get(String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId())) .followRedirects(true) .send( ar -> { @@ -102,12 +96,16 @@ 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", listResp.size(), Matchers.equalTo(1)); 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")); @@ -122,9 +120,8 @@ public void testWorkflow() throws Exception { .extensions() .patch( String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), - TEST_RECORDING_NAME), + "/api/v3/targets/%d/recordings/%d", + getSelfReferenceTargetId(), TEST_REMOTE_ID), saveHeaders, Buffer.buffer("SAVE"), REQUEST_TIMEOUT_SECONDS) @@ -134,10 +131,7 @@ public void testWorkflow() throws Exception { // check that the in-memory recording list hasn't changed CompletableFuture listRespFuture3 = new CompletableFuture<>(); webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) + .get(String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId())) .followRedirects(true) .send( ar -> { @@ -185,10 +179,7 @@ public void testWorkflow() throws Exception { // verify the in-memory recording list has not changed, except recording is now stopped CompletableFuture listRespFuture5 = new CompletableFuture<>(); webClient - .get( - String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) + .get(String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId())) .followRedirects(true) .send( ar -> { @@ -255,8 +246,8 @@ public void testWorkflow() throws Exception { .extensions() .delete( String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME), + "/api/v3/targets/%d/recordings/%d", + getSelfReferenceTargetId(), TEST_REMOTE_ID), REQUEST_TIMEOUT_SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { throw new ITestCleanupFailedException( diff --git a/src/test/java/itest/UploadRecordingTest.java b/src/test/java/itest/UploadRecordingTest.java index 6c8d16b57..a3614a032 100644 --- a/src/test/java/itest/UploadRecordingTest.java +++ b/src/test/java/itest/UploadRecordingTest.java @@ -56,6 +56,7 @@ public class UploadRecordingTest extends StandardSelfTest { static String CREATE_RECORDING_URL; static String DELETE_RECORDING_URL; static String UPLOAD_RECORDING_URL; + static long RECORDING_REMOTE_ID; @BeforeAll public static void createRecording() throws Exception { @@ -65,9 +66,11 @@ public static void createRecording() throws Exception { form.add("events", "template=ALL"); CREATE_RECORDING_URL = - String.format("/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded()); + String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId()); HttpResponse resp = webClient.extensions().post(CREATE_RECORDING_URL, form, RECORDING_DURATION_SECONDS); + long id = resp.bodyAsJsonObject().getLong("remoteId"); + RECORDING_REMOTE_ID = id; MatcherAssert.assertThat(resp.statusCode(), Matchers.equalTo(201)); Thread.sleep( Long.valueOf( @@ -82,8 +85,8 @@ public static void deleteRecording() throws Exception { .extensions() .delete( String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), RECORDING_NAME), + "/api/v3/targets/%d/recordings/%d", + getSelfReferenceTargetId(), RECORDING_REMOTE_ID), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat(resp.statusCode(), Matchers.equalTo(204)); } catch (InterruptedException | ExecutionException | TimeoutException e) { @@ -102,8 +105,8 @@ public void shouldLoadRecordingToDatasource() throws Exception { .extensions() .post( String.format( - "/api/v1/targets/%s/recordings/%s/upload", - getSelfReferenceConnectUrlEncoded(), RECORDING_NAME), + "/api/v3/targets/%d/recordings/%d/upload", + getSelfReferenceTargetId(), RECORDING_REMOTE_ID), (Buffer) null, 0); diff --git a/src/test/java/itest/bases/StandardSelfTest.java b/src/test/java/itest/bases/StandardSelfTest.java index 19b9a613f..381143113 100644 --- a/src/test/java/itest/bases/StandardSelfTest.java +++ b/src/test/java/itest/bases/StandardSelfTest.java @@ -95,8 +95,8 @@ public static void assertNoRecordings() throws Exception { .extensions() .get( String.format( - "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded()), + "/api/v3/targets/%d/recordings", + getSelfReferenceTargetId()), REQUEST_TIMEOUT_SECONDS) .bodyAsJsonArray(); if (!listResp.isEmpty()) { From db05ecbf069e29c9a43e3ab84146a1d15db922f6 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 31 Jul 2024 15:52:08 -0400 Subject: [PATCH 009/140] debug SnapShotTest on CI --- .../io/cryostat/recordings/Recordings.java | 356 +++++----- src/test/java/itest/SnapshotTest.java | 620 +++++++++--------- .../java/itest/TargetRecordingPatchTest.java | 20 +- 3 files changed, 542 insertions(+), 454 deletions(-) diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index b6d931743..a3d436e14 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -210,7 +210,8 @@ public void agentPush( } logger.tracev("Removing {0}", toRemove); - // FIXME this notification should be emitted in the deletion operation stream so that there + // FIXME this notification should be emitted in the deletion operation stream so + // that there // is one notification per deleted object var target = Target.getTargetByJvmId(jvmId); var event = @@ -455,15 +456,21 @@ 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(); - } */ + /* + * @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 @@ -506,28 +513,35 @@ 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(); - } */ + /* + * @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 @@ -597,7 +611,8 @@ public Response createRecording( @RestForm String recordingName, @RestForm String events, @RestForm Optional replace, - // restart param is deprecated, only 'replace' should be used and takes priority if both + // restart param is deprecated, only 'replace' should be used and takes priority + // if both // are provided @Deprecated @RestForm Optional restart, @RestForm Optional duration, @@ -652,45 +667,60 @@ 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(); - } */ + /* + * @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 @@ -754,7 +784,7 @@ public void deleteArchivedRecording(@RestPath String jvmId, @RestPath String fil recordingHelper.downloadUrl(jvmId, filename), recordingHelper.reportUrl(jvmId, filename), metadata, - 0 /*filesize*/, + 0 /* filesize */, clock.getMonotonicTime()))); bus.publish(event.category().category(), event.payload().recording()); bus.publish( @@ -767,28 +797,35 @@ 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 + * + * @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 @@ -799,39 +836,47 @@ 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 + * + * @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 @@ -852,17 +897,23 @@ 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/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 @@ -878,17 +929,22 @@ 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/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 diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 7517cf5dc..3724dc543 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -15,299 +15,327 @@ */ package itest; -import java.net.URI; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -import io.quarkus.test.junit.QuarkusTest; -import io.vertx.core.MultiMap; -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.handler.HttpException; -import itest.bases.StandardSelfTest; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -@QuarkusTest -public class SnapshotTest extends StandardSelfTest { - - static final String TEST_RECORDING_NAME = "someRecording"; - static final Pattern SNAPSHOT_NAME_PATTERN = Pattern.compile("^snapshot-[0-9]+$"); - - String v1RequestUrl() { - return String.format("/api/v1/targets/%s", getSelfReferenceConnectUrlEncoded()); - } - - String v2RequestUrl() { - return String.format("/api/v2/targets/%s", getSelfReferenceConnectUrlEncoded()); - } - - @Test - void testPostV1ShouldHandleEmptySnapshot() throws Exception { - // precondition, there should be no recordings before we start - CompletableFuture preListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, preListRespFuture)) { - preListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - - CompletableFuture result = new CompletableFuture<>(); - // Create an empty snapshot recording (no active recordings present) - webClient - .post(String.format("%s/snapshot", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, result)) { - result.complete(ar.result().statusCode()); - } - }); - MatcherAssert.assertThat( - result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); - - // The empty snapshot should've been deleted (i.e. there should be no recordings - // present) - CompletableFuture postListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, postListRespFuture)) { - postListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); - } - - @Test - void testPostV2ShouldHandleEmptySnapshot() throws Exception { - // precondition, there should be no recordings before we start - CompletableFuture preListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, preListRespFuture)) { - preListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - - CompletableFuture result = new CompletableFuture<>(); - // Create an empty snapshot recording (no active recordings present) - webClient - .post(String.format("%s/snapshot", v2RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, result)) { - result.complete(ar.result().statusCode()); - } - }); - MatcherAssert.assertThat( - result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); - - // The empty snapshot should've been deleted (i.e. there should be no recordings - // present) - CompletableFuture postListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, postListRespFuture)) { - postListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); - } - - @Test - void testPostV1ShouldCreateSnapshot() throws Exception { - CompletableFuture snapshotName = 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 - .extensions() - .post( - String.format("%s/recordings", v1RequestUrl()), - form, - REQUEST_TIMEOUT_SECONDS); - - Thread.sleep(5_000l); - - // Create a snapshot recording of all events at that time - webClient - .post(String.format("%s/snapshot", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, snapshotName)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo("text/plain;charset=UTF-8")); - snapshotName.complete(ar.result().bodyAsString()); - } - }); - - MatcherAssert.assertThat( - snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); - - // Clean up recording and snapshot - webClient - .extensions() - .delete( - String.format("%s/recordings/%s", v1RequestUrl(), TEST_RECORDING_NAME), - REQUEST_TIMEOUT_SECONDS); - webClient - .extensions() - .delete( - String.format( - "%s/recordings/%s", - v1RequestUrl(), - snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), - REQUEST_TIMEOUT_SECONDS); - } - - @Test - void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { - CompletableFuture snapshotResponse = new CompletableFuture<>(); - webClient - .post("/api/v1/targets/notFound%2F9000/snapshot") - .send( - ar -> { - assertRequestStatus(ar, snapshotResponse); - }); - ExecutionException ex = - Assertions.assertThrows( - ExecutionException.class, - () -> snapshotResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - MatcherAssert.assertThat( - ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); - } - - @Test - void testPostV2ShouldCreateSnapshot() throws Exception { - CompletableFuture snapshotName = 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 - .extensions() - .post( - String.format("%s/recordings", v1RequestUrl()), - form, - REQUEST_TIMEOUT_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/%s", v1RequestUrl(), TEST_RECORDING_NAME), - REQUEST_TIMEOUT_SECONDS); - webClient - .extensions() - .delete( - String.format( - "%s/recordings/%s", - v1RequestUrl(), - snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), - REQUEST_TIMEOUT_SECONDS); - } - - @Test - void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { - CompletableFuture snapshotName = new CompletableFuture<>(); - webClient - .post("/api/v2/targets/notFound:9000/snapshot") - .send( - ar -> { - assertRequestStatus(ar, snapshotName); - }); - ExecutionException ex = - Assertions.assertThrows( - ExecutionException.class, - () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - MatcherAssert.assertThat( - ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); - } -} +/* + * @QuarkusTest + * public class SnapshotTest extends StandardSelfTest { + * + * static final String TEST_RECORDING_NAME = "someRecording"; + * static final Pattern SNAPSHOT_NAME_PATTERN = + * Pattern.compile("^snapshot-[0-9]+$"); + * + * String v1RequestUrl() { + * return String.format("/api/v1/targets/%s", + * getSelfReferenceConnectUrlEncoded()); + * } + * + * String recordingPostRequestUrl() { + * return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); + * } + * + * String recordingGetRequestUrl() { + * return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); + * } + * + * String v2RequestUrl() { + * return String.format("/api/v2/targets/%s", + * getSelfReferenceConnectUrlEncoded()); + * } + * + * String deleteRequestUrl() { + * return String.format("/api/v3/targets/%d/recordings", + * getSelfReferenceTargetId()); + * } + * + * @Test + * void testPostV1ShouldHandleEmptySnapshot() throws Exception { + * // precondition, there should be no recordings before we start + * CompletableFuture preListRespFuture = new CompletableFuture<>(); + * webClient + * .get(String.format("%s/recordings", recordingGetRequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, preListRespFuture)) { + * preListRespFuture.complete(ar.result().bodyAsJsonArray()); + * } + * }); + * JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, + * TimeUnit.SECONDS); + * MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); + * + * CompletableFuture result = new CompletableFuture<>(); + * // Create an empty snapshot recording (no active recordings present) + * webClient + * .post(String.format("%s/snapshot", v1RequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, result)) { + * result.complete(ar.result().statusCode()); + * } + * }); + * MatcherAssert.assertThat( + * result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), + * Matchers.equalTo(202)); + * + * // The empty snapshot should've been deleted (i.e. there should be no + * recordings + * // present) + * CompletableFuture postListRespFuture = new CompletableFuture<>(); + * webClient + * .get(String.format("%s/recordings", recordingGetRequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, postListRespFuture)) { + * postListRespFuture.complete(ar.result().bodyAsJsonArray()); + * } + * }); + * JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, + * TimeUnit.SECONDS); + * MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); + * } + * + * @Test + * void testPostV2ShouldHandleEmptySnapshot() throws Exception { + * // precondition, there should be no recordings before we start + * CompletableFuture preListRespFuture = new CompletableFuture<>(); + * webClient + * .get(String.format("%s/recordings", recordingGetRequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, preListRespFuture)) { + * preListRespFuture.complete(ar.result().bodyAsJsonArray()); + * } + * }); + * JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, + * TimeUnit.SECONDS); + * MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); + * + * CompletableFuture result = new CompletableFuture<>(); + * // Create an empty snapshot recording (no active recordings present) + * webClient + * .post(String.format("%s/snapshot", v2RequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, result)) { + * result.complete(ar.result().statusCode()); + * } + * }); + * MatcherAssert.assertThat( + * result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), + * Matchers.equalTo(202)); + * + * // The empty snapshot should've been deleted (i.e. there should be no + * recordings + * // present) + * CompletableFuture postListRespFuture = new CompletableFuture<>(); + * webClient + * .get(String.format("%s/recordings", recordingGetRequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, postListRespFuture)) { + * postListRespFuture.complete(ar.result().bodyAsJsonArray()); + * } + * }); + * JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, + * TimeUnit.SECONDS); + * MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); + * } + * + * @Test + * void testPostV1ShouldCreateSnapshot() throws Exception { + * CompletableFuture remoteIdFuture = new CompletableFuture<>(); + * CompletableFuture snapshotName = new CompletableFuture<>(); + * + * // Create a recording + * MultiMap form = MultiMap.caseInsensitiveMultiMap(); + * form.add("recordingName", TEST_RECORDING_NAME); + * form.add("duration", "5"); + * form.add("events", "template=ALL"); + * + * HttpResponse response = + * webClient + * .extensions() + * .post( + * String.format("%s/recordings", recordingPostRequestUrl()), + * form, + * REQUEST_TIMEOUT_SECONDS); + * + * if (response.statusCode() == 201) { + * System.out.println("++++" + response.bodyAsString()); + * Long remoteId = response.bodyAsJsonObject().getLong("remoteId"); + * remoteIdFuture.complete(remoteId); + * System.out.println("+++Created recording with remote ID: " + remoteId); + * } else { + * throw new AssertionError( + * "Recording creation failed with status: " + response.statusCode()); + * } + * + * Long remoteId = remoteIdFuture.get(); + * + * Thread.sleep(5_000l); // Wait for recording to gather data + * + * // Create a snapshot recording of all events at that time + * webClient + * .post(String.format("%s/snapshot", v1RequestUrl())) + * .send( + * ar -> { + * if (assertRequestStatus(ar, snapshotName)) { + * MatcherAssert.assertThat( + * ar.result().statusCode(), Matchers.equalTo(200)); + * MatcherAssert.assertThat( + * ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), + * Matchers.equalTo("text/plain;charset=UTF-8")); + * snapshotName.complete(ar.result().bodyAsString()); + * } + * }); + * + * MatcherAssert.assertThat( + * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), + * Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); + * + * // Clean up recording and snapshot + * webClient + * .extensions() + * .delete( + * String.format("%s/recordings/%d", deleteRequestUrl(), remoteId), + * REQUEST_TIMEOUT_SECONDS); + * webClient + * .extensions() + * .delete( + * String.format( + * "%s/recordings/%s", + * deleteRequestUrl(), + * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), + * REQUEST_TIMEOUT_SECONDS); + * } + * + * @Test + * void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { + * CompletableFuture snapshotResponse = new CompletableFuture<>(); + * webClient + * .post("/api/v1/targets/notFound%2F9000/snapshot") + * .send( + * ar -> { + * assertRequestStatus(ar, snapshotResponse); + * }); + * ExecutionException ex = + * Assertions.assertThrows( + * ExecutionException.class, + * () -> snapshotResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + * MatcherAssert.assertThat( + * ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + * MatcherAssert.assertThat(ex.getCause().getMessage(), + * Matchers.equalTo("Not Found")); + * } + * + * @Test + * void testPostV2ShouldCreateSnapshot() throws Exception { + * CompletableFuture snapshotName = 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 + * .extensions() + * .post( + * String.format("%s/recordings", recordingPostRequestUrl()), + * form, + * REQUEST_TIMEOUT_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", deleteRequestUrl(), result.getLong("remoteId")), + * REQUEST_TIMEOUT_SECONDS); + * webClient + * .extensions() + * .delete( + * String.format( + * "%s/recordings/%s", + * deleteRequestUrl(), + * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), + * REQUEST_TIMEOUT_SECONDS); + * } + * + * @Test + * void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { + * CompletableFuture snapshotName = new CompletableFuture<>(); + * webClient + * .post("/api/v2/targets/notFound:9000/snapshot") + * .send( + * ar -> { + * assertRequestStatus(ar, snapshotName); + * }); + * ExecutionException ex = + * Assertions.assertThrows( + * ExecutionException.class, + * () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + * MatcherAssert.assertThat( + * ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + * MatcherAssert.assertThat(ex.getCause().getMessage(), + * Matchers.equalTo("Not Found")); + * } + * } + */ diff --git a/src/test/java/itest/TargetRecordingPatchTest.java b/src/test/java/itest/TargetRecordingPatchTest.java index b6f7e6628..e77f5e91e 100644 --- a/src/test/java/itest/TargetRecordingPatchTest.java +++ b/src/test/java/itest/TargetRecordingPatchTest.java @@ -24,6 +24,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; import itest.bases.StandardSelfTest; import itest.util.ITestCleanupFailedException; @@ -38,7 +39,11 @@ public class TargetRecordingPatchTest extends StandardSelfTest { static final String TEST_RECORDING_NAME = "someRecording"; String recordingRequestUrl() { - return String.format("/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded()); + return String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId()); + } + + String deleteRecordingRequestUrl() { + return String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId()); } String archivesRequestUrl() { @@ -46,12 +51,12 @@ String archivesRequestUrl() { } String optionsRequestUrl() { - return String.format( - "/api/v1/targets/%s/recordingOptions", getSelfReferenceConnectUrlEncoded()); + return String.format("/api/v3/targets/%d/recordingOptions", getSelfReferenceTargetId()); } @Test void testSaveEmptyRecordingDoesNotArchiveRecordingFile() throws Exception { + long remoteId = -1; try { MultiMap optionsForm = MultiMap.caseInsensitiveMultiMap(); optionsForm.add("toDisk", "false"); @@ -68,14 +73,14 @@ void testSaveEmptyRecordingDoesNotArchiveRecordingFile() throws Exception { HttpResponse postResponse = webClient.extensions().post(recordingRequestUrl(), form, 5); MatcherAssert.assertThat(postResponse.statusCode(), Matchers.equalTo(201)); - + JsonObject postResult = postResponse.bodyAsJsonObject(); + remoteId = postResult.getLong("remoteId"); // Attempt to save the recording to archive HttpResponse saveResponse = webClient .extensions() .patch( - String.format( - "%s/%s", recordingRequestUrl(), TEST_RECORDING_NAME), + String.format("%s/%d", recordingRequestUrl(), remoteId), null, Buffer.buffer("SAVE"), 5); @@ -101,8 +106,7 @@ void testSaveEmptyRecordingDoesNotArchiveRecordingFile() throws Exception { webClient .extensions() .delete( - String.format( - "%s/%s", recordingRequestUrl(), TEST_RECORDING_NAME), + String.format("%s/%d", deleteRecordingRequestUrl(), remoteId), 5); if (!HttpStatusCodeIdentifier.isSuccessCode(deleteResponse.statusCode())) { throw new ITestCleanupFailedException(); From ac788dfcf88936b295f7efdca63d2b2071fa5b3e Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 31 Jul 2024 17:50:42 -0400 Subject: [PATCH 010/140] pushed to debug latest updates --- src/test/java/itest/SnapshotTest.java | 652 +++++++++++++------------- 1 file changed, 328 insertions(+), 324 deletions(-) diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 3724dc543..81527dacd 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -15,327 +15,331 @@ */ package itest; -/* - * @QuarkusTest - * public class SnapshotTest extends StandardSelfTest { - * - * static final String TEST_RECORDING_NAME = "someRecording"; - * static final Pattern SNAPSHOT_NAME_PATTERN = - * Pattern.compile("^snapshot-[0-9]+$"); - * - * String v1RequestUrl() { - * return String.format("/api/v1/targets/%s", - * getSelfReferenceConnectUrlEncoded()); - * } - * - * String recordingPostRequestUrl() { - * return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); - * } - * - * String recordingGetRequestUrl() { - * return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); - * } - * - * String v2RequestUrl() { - * return String.format("/api/v2/targets/%s", - * getSelfReferenceConnectUrlEncoded()); - * } - * - * String deleteRequestUrl() { - * return String.format("/api/v3/targets/%d/recordings", - * getSelfReferenceTargetId()); - * } - * - * @Test - * void testPostV1ShouldHandleEmptySnapshot() throws Exception { - * // precondition, there should be no recordings before we start - * CompletableFuture preListRespFuture = new CompletableFuture<>(); - * webClient - * .get(String.format("%s/recordings", recordingGetRequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, preListRespFuture)) { - * preListRespFuture.complete(ar.result().bodyAsJsonArray()); - * } - * }); - * JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - * - * CompletableFuture result = new CompletableFuture<>(); - * // Create an empty snapshot recording (no active recordings present) - * webClient - * .post(String.format("%s/snapshot", v1RequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, result)) { - * result.complete(ar.result().statusCode()); - * } - * }); - * MatcherAssert.assertThat( - * result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - * Matchers.equalTo(202)); - * - * // The empty snapshot should've been deleted (i.e. there should be no - * recordings - * // present) - * CompletableFuture postListRespFuture = new CompletableFuture<>(); - * webClient - * .get(String.format("%s/recordings", recordingGetRequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, postListRespFuture)) { - * postListRespFuture.complete(ar.result().bodyAsJsonArray()); - * } - * }); - * JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); - * } - * - * @Test - * void testPostV2ShouldHandleEmptySnapshot() throws Exception { - * // precondition, there should be no recordings before we start - * CompletableFuture preListRespFuture = new CompletableFuture<>(); - * webClient - * .get(String.format("%s/recordings", recordingGetRequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, preListRespFuture)) { - * preListRespFuture.complete(ar.result().bodyAsJsonArray()); - * } - * }); - * JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - * - * CompletableFuture result = new CompletableFuture<>(); - * // Create an empty snapshot recording (no active recordings present) - * webClient - * .post(String.format("%s/snapshot", v2RequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, result)) { - * result.complete(ar.result().statusCode()); - * } - * }); - * MatcherAssert.assertThat( - * result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - * Matchers.equalTo(202)); - * - * // The empty snapshot should've been deleted (i.e. there should be no - * recordings - * // present) - * CompletableFuture postListRespFuture = new CompletableFuture<>(); - * webClient - * .get(String.format("%s/recordings", recordingGetRequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, postListRespFuture)) { - * postListRespFuture.complete(ar.result().bodyAsJsonArray()); - * } - * }); - * JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, - * TimeUnit.SECONDS); - * MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); - * } - * - * @Test - * void testPostV1ShouldCreateSnapshot() throws Exception { - * CompletableFuture remoteIdFuture = new CompletableFuture<>(); - * CompletableFuture snapshotName = new CompletableFuture<>(); - * - * // Create a recording - * MultiMap form = MultiMap.caseInsensitiveMultiMap(); - * form.add("recordingName", TEST_RECORDING_NAME); - * form.add("duration", "5"); - * form.add("events", "template=ALL"); - * - * HttpResponse response = - * webClient - * .extensions() - * .post( - * String.format("%s/recordings", recordingPostRequestUrl()), - * form, - * REQUEST_TIMEOUT_SECONDS); - * - * if (response.statusCode() == 201) { - * System.out.println("++++" + response.bodyAsString()); - * Long remoteId = response.bodyAsJsonObject().getLong("remoteId"); - * remoteIdFuture.complete(remoteId); - * System.out.println("+++Created recording with remote ID: " + remoteId); - * } else { - * throw new AssertionError( - * "Recording creation failed with status: " + response.statusCode()); - * } - * - * Long remoteId = remoteIdFuture.get(); - * - * Thread.sleep(5_000l); // Wait for recording to gather data - * - * // Create a snapshot recording of all events at that time - * webClient - * .post(String.format("%s/snapshot", v1RequestUrl())) - * .send( - * ar -> { - * if (assertRequestStatus(ar, snapshotName)) { - * MatcherAssert.assertThat( - * ar.result().statusCode(), Matchers.equalTo(200)); - * MatcherAssert.assertThat( - * ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), - * Matchers.equalTo("text/plain;charset=UTF-8")); - * snapshotName.complete(ar.result().bodyAsString()); - * } - * }); - * - * MatcherAssert.assertThat( - * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - * Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); - * - * // Clean up recording and snapshot - * webClient - * .extensions() - * .delete( - * String.format("%s/recordings/%d", deleteRequestUrl(), remoteId), - * REQUEST_TIMEOUT_SECONDS); - * webClient - * .extensions() - * .delete( - * String.format( - * "%s/recordings/%s", - * deleteRequestUrl(), - * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), - * REQUEST_TIMEOUT_SECONDS); - * } - * - * @Test - * void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { - * CompletableFuture snapshotResponse = new CompletableFuture<>(); - * webClient - * .post("/api/v1/targets/notFound%2F9000/snapshot") - * .send( - * ar -> { - * assertRequestStatus(ar, snapshotResponse); - * }); - * ExecutionException ex = - * Assertions.assertThrows( - * ExecutionException.class, - * () -> snapshotResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - * MatcherAssert.assertThat( - * ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - * MatcherAssert.assertThat(ex.getCause().getMessage(), - * Matchers.equalTo("Not Found")); - * } - * - * @Test - * void testPostV2ShouldCreateSnapshot() throws Exception { - * CompletableFuture snapshotName = 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 - * .extensions() - * .post( - * String.format("%s/recordings", recordingPostRequestUrl()), - * form, - * REQUEST_TIMEOUT_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", deleteRequestUrl(), result.getLong("remoteId")), - * REQUEST_TIMEOUT_SECONDS); - * webClient - * .extensions() - * .delete( - * String.format( - * "%s/recordings/%s", - * deleteRequestUrl(), - * snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)), - * REQUEST_TIMEOUT_SECONDS); - * } - * - * @Test - * void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { - * CompletableFuture snapshotName = new CompletableFuture<>(); - * webClient - * .post("/api/v2/targets/notFound:9000/snapshot") - * .send( - * ar -> { - * assertRequestStatus(ar, snapshotName); - * }); - * ExecutionException ex = - * Assertions.assertThrows( - * ExecutionException.class, - * () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - * MatcherAssert.assertThat( - * ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - * MatcherAssert.assertThat(ex.getCause().getMessage(), - * Matchers.equalTo("Not Found")); - * } - * } - */ +import java.net.URI; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import io.quarkus.test.junit.QuarkusTest; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.HttpResponse; +import io.vertx.ext.web.handler.HttpException; +import itest.bases.StandardSelfTest; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class SnapshotTest extends StandardSelfTest { + + static final String TEST_RECORDING_NAME = "someRecording"; + static final Pattern SNAPSHOT_NAME_PATTERN = Pattern.compile("^snapshot-[0-9]+$"); + + String v1RequestUrl() { + return String.format("/api/v1/targets/%s", getSelfReferenceConnectUrlEncoded()); + } + + String v3RequestUrl() { + return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); + } + + String v2RequestUrl() { + return String.format("/api/v2/targets/%s", getSelfReferenceConnectUrlEncoded()); + } + + @Test + void testPostV1ShouldHandleEmptySnapshot() throws Exception { + // precondition, there should be no recordings before we start + CompletableFuture preListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, preListRespFuture)) { + preListRespFuture.complete(ar.result().bodyAsJsonArray()); + } + }); + JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); + + CompletableFuture result = new CompletableFuture<>(); + // Create an empty snapshot recording (no active recordings present) + webClient + .post(String.format("%s/snapshot", v1RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, result)) { + result.complete(ar.result().statusCode()); + } + }); + MatcherAssert.assertThat( + result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); + + // The empty snapshot should've been deleted (i.e. there should be no recordings + // present) + CompletableFuture postListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v1RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, postListRespFuture)) { + postListRespFuture.complete(ar.result().bodyAsJsonArray()); + } + }); + JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); + } + + @Test + void testPostV2ShouldHandleEmptySnapshot() throws Exception { + // precondition, there should be no recordings before we start + CompletableFuture preListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, preListRespFuture)) { + preListRespFuture.complete(ar.result().bodyAsJsonArray()); + } + }); + JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); + + CompletableFuture result = new CompletableFuture<>(); + // Create an empty snapshot recording (no active recordings present) + webClient + .post(String.format("%s/snapshot", v2RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, result)) { + result.complete(ar.result().statusCode()); + } + }); + MatcherAssert.assertThat( + result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); + + // The empty snapshot should've been deleted (i.e. there should be no recordings + // present) + CompletableFuture postListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, postListRespFuture)) { + postListRespFuture.complete(ar.result().bodyAsJsonArray()); + } + }); + JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); + } + + @Test + void testPostV1ShouldCreateSnapshot() 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 + webClient + .post(String.format("%s/snapshot", v1RequestUrl())) + .send( + ar -> { + if (assertRequestStatus(ar, snapshotName)) { + MatcherAssert.assertThat( + ar.result().statusCode(), Matchers.equalTo(200)); + MatcherAssert.assertThat( + ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("text/plain;charset=UTF-8")); + snapshotName.complete(ar.result().bodyAsString()); + } + }); + + MatcherAssert.assertThat( + snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), + Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); + + // Clean up recording and snapshot + webClient + .extensions() + .delete( + String.format("%s/recordings/%d", v3RequestUrl(), remoteId), + REQUEST_TIMEOUT_SECONDS); + webClient + .extensions() + .delete( + String.format("%s/recordings/%d", v3RequestUrl(), remoteId), + REQUEST_TIMEOUT_SECONDS); + } + + @Test + void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { + CompletableFuture snapshotResponse = new CompletableFuture<>(); + webClient + .post("/api/v1/targets/notFound%2F9000/snapshot") + .send( + ar -> { + assertRequestStatus(ar, snapshotResponse); + }); + ExecutionException ex = + Assertions.assertThrows( + ExecutionException.class, + () -> snapshotResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + MatcherAssert.assertThat( + ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); + } + + @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); + } + + @Test + void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { + CompletableFuture snapshotName = new CompletableFuture<>(); + webClient + .post("/api/v2/targets/notFound:9000/snapshot") + .send( + ar -> { + assertRequestStatus(ar, snapshotName); + }); + ExecutionException ex = + Assertions.assertThrows( + ExecutionException.class, + () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + MatcherAssert.assertThat( + ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); + } +} From 9ead30bee0eeb234f1db94f7126548c7391ea86d Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 1 Aug 2024 17:01:25 -0400 Subject: [PATCH 011/140] V2snapcreation fails --- .../io/cryostat/recordings/Recordings.java | 8 +- src/test/java/itest/SnapshotTest.java | 497 ++++++++++-------- 2 files changed, 291 insertions(+), 214 deletions(-) diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index a3d436e14..e309ac49c 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -554,7 +554,13 @@ public Uni createSnapshotV1(@RestPath URI connectUrl) throws Exception .onItem() .transform( recording -> - Response.status(Response.Status.OK).entity(recording.name).build()) + Response.status(Response.Status.OK) + .entity( + new JsonObject() + .put("name", recording.name) + .put("remoteId", recording.remoteId) + .encode()) + .build()) .onFailure(SnapshotCreationException.class) .recoverWithItem(Response.status(Response.Status.ACCEPTED).build()); } diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 81527dacd..83f192396 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -15,9 +15,10 @@ */ package itest; -import java.net.URI; -import java.time.Instant; -import java.util.Map; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -26,7 +27,6 @@ import io.quarkus.test.junit.QuarkusTest; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -34,7 +34,9 @@ import itest.bases.StandardSelfTest; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @QuarkusTest @@ -42,6 +44,41 @@ public class SnapshotTest extends StandardSelfTest { static final String TEST_RECORDING_NAME = "someRecording"; static final Pattern SNAPSHOT_NAME_PATTERN = Pattern.compile("^snapshot-[0-9]+$"); + static long REMOTE_ID; + static long SNAPSHOT_ID; + private List recordingsToDelete; + + @BeforeEach + void setup() throws Exception { + recordingsToDelete = new ArrayList<>(); + cleanupRecordings(); + } + + @AfterEach + void cleanup() throws Exception { + cleanupRecordings(); + } + + private void cleanupRecordings() throws Exception { + for (Long remoteId : recordingsToDelete) { + webClient + .delete(String.format("%s/recordings/%d", v3RequestUrl(), remoteId)) + .send( + ar -> { + if (ar.succeeded()) { + System.out.println( + "Deleted recording with remote ID: " + remoteId); + } else { + System.err.println( + "Failed to delete recording with remote ID: " + + remoteId + + ", cause: " + + ar.cause()); + } + }); + } + recordingsToDelete.clear(); + } String v1RequestUrl() { return String.format("/api/v1/targets/%s", getSelfReferenceConnectUrlEncoded()); @@ -57,154 +94,40 @@ String v2RequestUrl() { @Test void testPostV1ShouldHandleEmptySnapshot() throws Exception { - // precondition, there should be no recordings before we start - CompletableFuture preListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v3RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, preListRespFuture)) { - preListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonArray preListResp = fetchPreTestRecordings(); MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - CompletableFuture result = new CompletableFuture<>(); - // Create an empty snapshot recording (no active recordings present) - webClient - .post(String.format("%s/snapshot", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, result)) { - result.complete(ar.result().statusCode()); - } - }); - MatcherAssert.assertThat( - result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); + int statusCode = createEmptySnapshot(v1RequestUrl()); + MatcherAssert.assertThat(statusCode, Matchers.equalTo(202)); - // The empty snapshot should've been deleted (i.e. there should be no recordings - // present) - CompletableFuture postListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, postListRespFuture)) { - postListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonArray postListResp = fetchPostTestRecordings(); MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); } @Test void testPostV2ShouldHandleEmptySnapshot() throws Exception { - // precondition, there should be no recordings before we start - CompletableFuture preListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v3RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, preListRespFuture)) { - preListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray preListResp = preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonArray preListResp = fetchPreTestRecordings(); MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - CompletableFuture result = new CompletableFuture<>(); - // Create an empty snapshot recording (no active recordings present) - webClient - .post(String.format("%s/snapshot", v2RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, result)) { - result.complete(ar.result().statusCode()); - } - }); - MatcherAssert.assertThat( - result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(202)); + int statusCode = createEmptySnapshot(v2RequestUrl()); + MatcherAssert.assertThat(statusCode, Matchers.equalTo(202)); - // The empty snapshot should've been deleted (i.e. there should be no recordings - // present) - CompletableFuture postListRespFuture = new CompletableFuture<>(); - webClient - .get(String.format("%s/recordings", v3RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, postListRespFuture)) { - postListRespFuture.complete(ar.result().bodyAsJsonArray()); - } - }); - JsonArray postListResp = postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + JsonArray postListResp = fetchPostTestRecordings(); MatcherAssert.assertThat(postListResp, Matchers.equalTo(new JsonArray())); } @Test void testPostV1ShouldCreateSnapshot() throws Exception { CompletableFuture snapshotName = new CompletableFuture<>(); - CompletableFuture remoteIdFuture = new CompletableFuture<>(); + createRecording(snapshotName); - // 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()); - } - }); + Thread.sleep(5_000); - long remoteId = remoteIdFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - Thread.sleep(5_000l); - - // Create a snapshot recording of all events at that time - webClient - .post(String.format("%s/snapshot", v1RequestUrl())) - .send( - ar -> { - if (assertRequestStatus(ar, snapshotName)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result().getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo("text/plain;charset=UTF-8")); - snapshotName.complete(ar.result().bodyAsString()); - } - }); + createSnapshot(snapshotName); MatcherAssert.assertThat( snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.matchesPattern(SNAPSHOT_NAME_PATTERN)); - - // Clean up recording and snapshot - webClient - .extensions() - .delete( - String.format("%s/recordings/%d", v3RequestUrl(), remoteId), - REQUEST_TIMEOUT_SECONDS); - webClient - .extensions() - .delete( - String.format("%s/recordings/%d", v3RequestUrl(), remoteId), - REQUEST_TIMEOUT_SECONDS); } @Test @@ -226,11 +149,89 @@ void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { } @Test - void testPostV2ShouldCreateSnapshot() throws Exception { + void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { CompletableFuture snapshotName = new CompletableFuture<>(); - CompletableFuture remoteIdFuture = new CompletableFuture<>(); + webClient + .post("/api/v2/targets/notFound:9000/snapshot") + .send( + ar -> { + assertRequestStatus(ar, snapshotName); + }); + ExecutionException ex = + Assertions.assertThrows( + ExecutionException.class, + () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + MatcherAssert.assertThat( + ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + 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)); + * } + */ - // Create a recording + private JsonArray fetchPreTestRecordings() throws Exception { + CompletableFuture preListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (ar.succeeded()) { + preListRespFuture.complete(ar.result().bodyAsJsonArray()); + } else { + preListRespFuture.completeExceptionally( + new RuntimeException("Failed to fetch recordings")); + } + }); + return preListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + private JsonArray fetchPostTestRecordings() throws Exception { + CompletableFuture postListRespFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (ar.succeeded()) { + postListRespFuture.complete(ar.result().bodyAsJsonArray()); + } else { + postListRespFuture.completeExceptionally( + new RuntimeException("Failed to fetch recordings")); + } + }); + return postListRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + private int createEmptySnapshot(String requestUrl) throws Exception { + CompletableFuture result = new CompletableFuture<>(); + webClient + .post(requestUrl + "/snapshot") + .send( + ar -> { + if (ar.succeeded()) { + result.complete(ar.result().statusCode()); + } else { + result.completeExceptionally( + new RuntimeException("Failed to create snapshot")); + } + }); + return result.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + private void createRecording(CompletableFuture snapshotName) throws Exception { MultiMap form = MultiMap.caseInsensitiveMultiMap(); form.add("recordingName", TEST_RECORDING_NAME); form.add("duration", "5"); @@ -245,101 +246,171 @@ void testPostV2ShouldCreateSnapshot() throws Exception { 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")); + REMOTE_ID = remoteId; + recordingsToDelete.add(remoteId); // Store for later cleanup + System.out.println( + "Recording created with remote ID: " + remoteId); } - } 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<>(); + private void createSnapshot(CompletableFuture snapshotName) throws Exception { webClient - .post(String.format("%s/snapshot", v2RequestUrl())) + .post(String.format("%s/snapshot", v1RequestUrl())) .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()); + if (ar.succeeded()) { + HttpResponse response = ar.result(); + if (response.statusCode() == 200) { + JsonObject jsonResponse = response.bodyAsJsonObject(); + long snapshotRemoteId = jsonResponse.getLong("remoteId"); + SNAPSHOT_ID = snapshotRemoteId; + recordingsToDelete.add( + snapshotRemoteId); // Store for later cleanup + snapshotName.complete(jsonResponse.getString("name")); + } else { + snapshotName.completeExceptionally( + new RuntimeException("Failed to create snapshot")); + } + } else { + snapshotName.completeExceptionally(ar.cause()); } }); - - 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); } - @Test - void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { - CompletableFuture snapshotName = new CompletableFuture<>(); + private void createV2Snapshot(CompletableFuture snapshotName) throws Exception { webClient - .post("/api/v2/targets/notFound:9000/snapshot") + .post(String.format("%s/snapshot", v2RequestUrl())) .send( ar -> { - assertRequestStatus(ar, snapshotName); + if (ar.succeeded()) { + HttpResponse response = ar.result(); + if (response.statusCode() == 201) { + JsonObject jsonResponse = response.bodyAsJsonObject(); + String name = + jsonResponse.getJsonObject("data").getString("name"); + long snapshotRemoteId = + jsonResponse.getJsonObject("data").getLong("remoteId"); + SNAPSHOT_ID = snapshotRemoteId; + recordingsToDelete.add(snapshotRemoteId); // Add to cleanup list + snapshotName.complete(name); + } else { + snapshotName.completeExceptionally( + new RuntimeException( + "Failed to create snapshot, Status code: " + + response.statusCode())); + } + } else { + snapshotName.completeExceptionally(ar.cause()); + } }); - ExecutionException ex = - Assertions.assertThrows( - ExecutionException.class, - () -> snapshotName.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - MatcherAssert.assertThat( - ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); - MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); } } +/* + * @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); + * } + * + */ From 1be644920bbc2dc324d17cbfbe22ca28f8bc6696 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 2 Aug 2024 12:36:08 -0400 Subject: [PATCH 012/140] 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); - * } - * - */ From 3fb76479126b05e082197318a8d881826d325402 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 2 Aug 2024 17:36:37 -0400 Subject: [PATCH 013/140] handle Targets PERMANENT_REDIRECT --- .../java/io/cryostat/reports/Reports.java | 60 ------------------- .../java/io/cryostat/targets/Targets.java | 12 ---- src/test/java/itest/CustomTargetsTest.java | 2 +- 3 files changed, 1 insertion(+), 73 deletions(-) diff --git a/src/main/java/io/cryostat/reports/Reports.java b/src/main/java/io/cryostat/reports/Reports.java index bdf20909f..7bab7aa33 100644 --- a/src/main/java/io/cryostat/reports/Reports.java +++ b/src/main/java/io/cryostat/reports/Reports.java @@ -15,8 +15,6 @@ */ package io.cryostat.reports; -import java.net.URI; -import java.util.HashMap; import java.util.Map; import io.cryostat.ConfigProperties; @@ -31,18 +29,14 @@ import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.GET; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; -import org.jboss.resteasy.reactive.RestResponse; @Path("") public class Reports { @@ -66,37 +60,6 @@ void onStart(@Observes StartupEvent evt) { } } - @Blocking - @GET - @Path("/api/v1/reports/{recordingName}") - @Produces(MediaType.APPLICATION_JSON) - @RolesAllowed("read") - @Deprecated(since = "3.0", forRemoval = true) - public Response getV1(@RestPath String recordingName) { - var result = new HashMap(); - helper.listArchivedRecordingObjects() - .forEach( - item -> { - String objectName = item.key().strip(); - String jvmId = objectName.split("/")[0]; - String filename = objectName.split("/")[1]; - result.put(jvmId, filename); - }); - if (result.size() == 0) { - throw new NotFoundException(); - } - if (result.size() > 1) { - throw new ClientErrorException(Response.Status.CONFLICT); - } - var entry = result.entrySet().iterator().next(); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/reports/%s", entry.getKey(), entry.getValue()))) - .build(); - } - @GET @Blocking @Path("/api/v3/reports/{encodedKey}") @@ -108,29 +71,6 @@ public Uni> get(@RestPath String encodedKey) { return reportsService.reportFor(pair.getKey(), pair.getValue()); } - @GET - @Blocking - @Transactional - @Path("/api/v1/targets/{targetId}/reports/{recordingName}") - @Produces({MediaType.APPLICATION_JSON}) - @RolesAllowed("read") - @Deprecated(since = "3.0", forRemoval = true) - public Response getActiveV1(@RestPath String targetId, @RestPath String recordingName) { - var target = Target.getTargetByConnectUrl(URI.create(targetId)); - var recording = - helper.listActiveRecordings(target).stream() - .filter(r -> r.name.equals(recordingName)) - .findFirst() - .orElseThrow(); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create( - String.format( - "/api/v3/targets/%d/reports/%d", - target.id, recording.remoteId))) - .build(); - } - @GET @Blocking @Path("/api/v3/targets/{targetId}/reports/{recordingId}") diff --git a/src/main/java/io/cryostat/targets/Targets.java b/src/main/java/io/cryostat/targets/Targets.java index c50a248a5..a222409a6 100644 --- a/src/main/java/io/cryostat/targets/Targets.java +++ b/src/main/java/io/cryostat/targets/Targets.java @@ -15,7 +15,6 @@ */ package io.cryostat.targets; -import java.net.URI; import java.time.Duration; import java.util.List; @@ -26,11 +25,9 @@ import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; -import org.jboss.resteasy.reactive.RestResponse; @Path("") public class Targets { @@ -42,15 +39,6 @@ public class Targets { @ConfigProperty(name = ConfigProperties.CONNECTIONS_FAILED_TIMEOUT) Duration timeout; - @GET - @Path("/api/v1/targets") - @RolesAllowed("read") - public Response listV1() { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/targets")) - .build(); - } - @GET @Path("/api/v3/targets") @RolesAllowed("read") diff --git a/src/test/java/itest/CustomTargetsTest.java b/src/test/java/itest/CustomTargetsTest.java index f4ed6c0c8..ab95c6815 100644 --- a/src/test/java/itest/CustomTargetsTest.java +++ b/src/test/java/itest/CustomTargetsTest.java @@ -216,7 +216,7 @@ void shouldBeAbleToDefineTarget() Matchers.equalTo(alias)); HttpResponse listResponse = - webClient.extensions().get("/api/v1/targets", REQUEST_TIMEOUT_SECONDS); + webClient.extensions().get("/api/v3/targets", REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat(listResponse.statusCode(), Matchers.equalTo(200)); JsonArray list = listResponse.bodyAsJsonArray(); MatcherAssert.assertThat(list, Matchers.notNullValue()); From 3ae570ef8ca615d94e48c91728097aa8b31a7bbd Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 2 Aug 2024 17:57:43 -0400 Subject: [PATCH 014/140] update permissions --- .github/workflows/pr-ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-ci.yaml b/.github/workflows/pr-ci.yaml index 9eaccef1b..5856625c4 100644 --- a/.github/workflows/pr-ci.yaml +++ b/.github/workflows/pr-ci.yaml @@ -276,6 +276,9 @@ jobs: auto-commit-schemas: needs: [compare-graphql-schema, compare-openapi-schema, checkout-branch, update-schemas] runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write if: needs.update-schemas.outputs.OPENAPI_STATUS != '0' || needs.update-schemas.outputs.GRAPHQL_STATUS != '0' steps: - name: Check out PR branch From 8eb9ecd520e926a5807460fcdd1fe6b6074e0296 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 6 Aug 2024 11:15:33 -0400 Subject: [PATCH 015/140] revert permission changes --- .github/workflows/pr-ci.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/pr-ci.yaml b/.github/workflows/pr-ci.yaml index 5856625c4..9eaccef1b 100644 --- a/.github/workflows/pr-ci.yaml +++ b/.github/workflows/pr-ci.yaml @@ -276,9 +276,6 @@ jobs: auto-commit-schemas: needs: [compare-graphql-schema, compare-openapi-schema, checkout-branch, update-schemas] runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write if: needs.update-schemas.outputs.OPENAPI_STATUS != '0' || needs.update-schemas.outputs.GRAPHQL_STATUS != '0' steps: - name: Check out PR branch From a13d9bda8ab5e1ceaa08e5a89657392cfcc13d1a Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 13 Aug 2024 11:35:08 -0400 Subject: [PATCH 016/140] fix line breaks && V1 API --- src/main/java/io/cryostat/ApiResponse.java | 60 ++++++++++++ .../io/cryostat/recordings/Recordings.java | 14 +-- src/test/java/itest/SnapshotTest.java | 94 +++++++++---------- 3 files changed, 107 insertions(+), 61 deletions(-) create mode 100644 src/main/java/io/cryostat/ApiResponse.java diff --git a/src/main/java/io/cryostat/ApiResponse.java b/src/main/java/io/cryostat/ApiResponse.java new file mode 100644 index 000000000..9d577d80b --- /dev/null +++ b/src/main/java/io/cryostat/ApiResponse.java @@ -0,0 +1,60 @@ +/* + * 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; + +import java.util.Map; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +public class ApiResponse { + + public static Response success(Object payload) { + return Response.ok(payload).type(MediaType.APPLICATION_JSON).build(); + } + + public static Response error(Response.Status status, String message) { + ErrorResponse errorResponse = new ErrorResponse(message, status.getStatusCode()); + return Response.status(status) + .entity(errorResponse) + .type(MediaType.APPLICATION_JSON) + .build(); + } + + private static class ErrorResponse { + private String message; + private int statusCode; + + public ErrorResponse(String message, int statusCode) { + this.message = message; + this.statusCode = statusCode; + } + + public String getMessage() { + return message; + } + + public int getStatusCode() { + return statusCode; + } + } + + public static Object json(Status ok, Map of) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'json'"); + } +} diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index 2b2697cc0..42fe6d57d 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -210,8 +210,7 @@ public void agentPush( } logger.tracev("Removing {0}", toRemove); - // FIXME this notification should be emitted in the deletion operation stream so - // that there + // FIXME this notification should be emitted in the deletion operation stream so that there // is one notification per deleted object var target = Target.getTargetByJvmId(jvmId); var event = @@ -508,13 +507,7 @@ public Uni createSnapshotV1(@RestPath URI connectUrl) throws Exception .onItem() .transform( recording -> - Response.status(Response.Status.OK) - .entity( - new JsonObject() - .put("name", recording.name) - .put("remoteId", recording.remoteId) - .encode()) - .build()) + Response.status(Response.Status.OK).entity(recording.name).build()) .onFailure(SnapshotCreationException.class) .recoverWithItem(Response.status(Response.Status.ACCEPTED).build()); } @@ -568,8 +561,7 @@ public Response createRecording( @RestForm String recordingName, @RestForm String events, @RestForm Optional replace, - // restart param is deprecated, only 'replace' should be used and takes priority - // if both + // restart param is deprecated, only 'replace' should be used and takes priority if both // are provided @Deprecated @RestForm Optional restart, @RestForm Optional duration, diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 510498237..6977f9ab8 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -62,21 +62,47 @@ void cleanup() throws Exception { } private void cleanupRecordings() throws Exception { - for (Long remoteId : recordingsToDelete) { - webClient - .delete(String.format("%s/recordings/%d", v3RequestUrl(), remoteId)) - .send( - ar -> { - if (!ar.succeeded()) { - System.err.println( - "Failed to delete recording with remote ID: " - + remoteId - + ", cause: " - + ar.cause()); - } - }); + JsonArray recordings = fetchAllRecordings(); + for (Object obj : recordings) { + if (obj instanceof JsonObject) { + JsonObject recording = (JsonObject) obj; + Long remoteId = recording.getLong("remoteId"); + if (remoteId != null) { + deleteRecording(remoteId); + } + } } - recordingsToDelete.clear(); + } + + private JsonArray fetchAllRecordings() throws Exception { + CompletableFuture recordingsFuture = new CompletableFuture<>(); + webClient + .get(String.format("%s/recordings", v3RequestUrl())) + .send( + ar -> { + if (ar.succeeded()) { + recordingsFuture.complete(ar.result().bodyAsJsonArray()); + } else { + recordingsFuture.completeExceptionally( + new RuntimeException("Failed to fetch recordings")); + } + }); + return recordingsFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + private void deleteRecording(Long remoteId) { + webClient + .delete(String.format("%s/recordings/%d", v3RequestUrl(), remoteId)) + .send( + ar -> { + if (!ar.succeeded()) { + System.err.println( + "Failed to delete recording with remote ID: " + + remoteId + + ", cause: " + + ar.cause()); + } + }); } String v1RequestUrl() { @@ -104,7 +130,7 @@ void testPostV1ShouldHandleEmptySnapshot() throws Exception { } @Test - void testPostV2ShouldHandleEmptySnapshot() throws Exception { + void testPostV3ShouldHandleEmptySnapshot() throws Exception { JsonArray preListResp = fetchPreTestRecordings(); MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); @@ -291,7 +317,7 @@ private void createRecording(CompletableFuture snapshotName) throws Exce JsonObject jsonResponse = response.bodyAsJsonObject(); long remoteId = jsonResponse.getLong("remoteId"); REMOTE_ID = remoteId; - recordingsToDelete.add(remoteId); // Store for later cleanup + recordingsToDelete.add(remoteId); } } }); @@ -305,43 +331,11 @@ private void createSnapshot(CompletableFuture snapshotName) throws Excep if (ar.succeeded()) { HttpResponse response = ar.result(); if (response.statusCode() == 200) { - JsonObject jsonResponse = response.bodyAsJsonObject(); - long snapshotRemoteId = jsonResponse.getLong("remoteId"); - SNAPSHOT_ID = snapshotRemoteId; - recordingsToDelete.add( - snapshotRemoteId); // Store for later cleanup - snapshotName.complete(jsonResponse.getString("name")); - } else { - snapshotName.completeExceptionally( - new RuntimeException("Failed to create snapshot")); - } - } else { - snapshotName.completeExceptionally(ar.cause()); - } - }); - } - - private void createV2Snapshot(CompletableFuture snapshotName) throws Exception { - 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.getJsonObject("data").getString("name"); - long snapshotRemoteId = - jsonResponse.getJsonObject("data").getLong("remoteId"); - SNAPSHOT_ID = snapshotRemoteId; - recordingsToDelete.add(snapshotRemoteId); // Add to cleanup list + String name = response.bodyAsString(); snapshotName.complete(name); } else { snapshotName.completeExceptionally( - new RuntimeException( - "Failed to create snapshot, Status code: " - + response.statusCode())); + new RuntimeException("Failed to create snapshot")); } } else { snapshotName.completeExceptionally(ar.cause()); From 341afa3f8b34960144bf046e92422e36f4d88a07 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 13 Aug 2024 11:45:16 -0400 Subject: [PATCH 017/140] remove ApiResponse --- src/main/java/io/cryostat/ApiResponse.java | 60 ---------------------- 1 file changed, 60 deletions(-) delete mode 100644 src/main/java/io/cryostat/ApiResponse.java diff --git a/src/main/java/io/cryostat/ApiResponse.java b/src/main/java/io/cryostat/ApiResponse.java deleted file mode 100644 index 9d577d80b..000000000 --- a/src/main/java/io/cryostat/ApiResponse.java +++ /dev/null @@ -1,60 +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 io.cryostat; - -import java.util.Map; - -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.Status; - -public class ApiResponse { - - public static Response success(Object payload) { - return Response.ok(payload).type(MediaType.APPLICATION_JSON).build(); - } - - public static Response error(Response.Status status, String message) { - ErrorResponse errorResponse = new ErrorResponse(message, status.getStatusCode()); - return Response.status(status) - .entity(errorResponse) - .type(MediaType.APPLICATION_JSON) - .build(); - } - - private static class ErrorResponse { - private String message; - private int statusCode; - - public ErrorResponse(String message, int statusCode) { - this.message = message; - this.statusCode = statusCode; - } - - public String getMessage() { - return message; - } - - public int getStatusCode() { - return statusCode; - } - } - - public static Object json(Status ok, Map of) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'json'"); - } -} From 87a2be22343b0fa63df3815c1cd75e970bbdc106 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 13 Aug 2024 11:57:52 -0400 Subject: [PATCH 018/140] remove v2 endpoint and v2Response on Snapshot --- .../io/cryostat/recordings/Recordings.java | 22 ------------------- src/test/java/itest/SnapshotTest.java | 18 +++++---------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/main/java/io/cryostat/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index 42fe6d57d..644daeb3a 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -40,7 +40,6 @@ import io.cryostat.ConfigProperties; import io.cryostat.Producers; import io.cryostat.StorageBuckets; -import io.cryostat.V2Response; import io.cryostat.core.EventOptionsBuilder; import io.cryostat.core.RecordingOptionsCustomizer; import io.cryostat.libcryostat.sys.Clock; @@ -512,27 +511,6 @@ public Uni createSnapshotV1(@RestPath URI connectUrl) throws Exception .recoverWithItem(Response.status(Response.Status.ACCEPTED).build()); } - @POST - @Transactional - @Path("/api/v2/targets/{connectUrl}/snapshot") - @RolesAllowed("write") - public Uni createSnapshotV2(@RestPath URI connectUrl) throws Exception { - Target target = Target.getTargetByConnectUrl(connectUrl); - return recordingHelper - .createSnapshot(target) - .onItem() - .transform( - recording -> - Response.status(Response.Status.CREATED) - .entity(recordingHelper.toExternalForm(recording)) - .build()) - .onFailure(SnapshotCreationException.class) - .recoverWithItem( - Response.status(Response.Status.ACCEPTED) - .entity(V2Response.json(Response.Status.ACCEPTED, null)) - .build()); - } - @POST @Transactional @Path("/api/v3/targets/{id}/snapshot") diff --git a/src/test/java/itest/SnapshotTest.java b/src/test/java/itest/SnapshotTest.java index 6977f9ab8..efc7c98b3 100644 --- a/src/test/java/itest/SnapshotTest.java +++ b/src/test/java/itest/SnapshotTest.java @@ -15,8 +15,6 @@ */ package itest; -import static org.junit.jupiter.api.Assertions.*; - import java.net.URI; import java.time.Instant; import java.util.ArrayList; @@ -113,10 +111,6 @@ String v3RequestUrl() { return String.format("/api/v3/targets/%d", getSelfReferenceTargetId()); } - String v2RequestUrl() { - return String.format("/api/v2/targets/%s", getSelfReferenceConnectUrlEncoded()); - } - @Test void testPostV1ShouldHandleEmptySnapshot() throws Exception { JsonArray preListResp = fetchPreTestRecordings(); @@ -134,7 +128,7 @@ void testPostV3ShouldHandleEmptySnapshot() throws Exception { JsonArray preListResp = fetchPreTestRecordings(); MatcherAssert.assertThat(preListResp, Matchers.equalTo(new JsonArray())); - int statusCode = createEmptySnapshot(v2RequestUrl()); + int statusCode = createEmptySnapshot(v3RequestUrl()); MatcherAssert.assertThat(statusCode, Matchers.equalTo(202)); JsonArray postListResp = fetchPostTestRecordings(); @@ -174,7 +168,7 @@ void testPostV1SnapshotThrowsWithNonExistentTarget() throws Exception { } @Test - void testPostV2ShouldCreateSnapshot() throws Exception { + void testPostV3ShouldCreateSnapshot() throws Exception { CompletableFuture snapshotName = new CompletableFuture<>(); // Create a recording @@ -185,12 +179,12 @@ void testPostV2ShouldCreateSnapshot() throws Exception { // Create a snapshot recording of all events at that time CompletableFuture createResponse = new CompletableFuture<>(); webClient - .post(String.format("%s/snapshot", v2RequestUrl())) + .post(String.format("%s/snapshot", v3RequestUrl())) .send( ar -> { if (ar.succeeded()) { HttpResponse response = ar.result(); - if (response.statusCode() == 201) { + if (response.statusCode() == 200) { JsonObject jsonResponse = response.bodyAsJsonObject(); String name = jsonResponse.getString("name"); long snapshotRemoteId = jsonResponse.getLong("remoteId"); @@ -236,10 +230,10 @@ void testPostV2ShouldCreateSnapshot() throws Exception { } @Test - void testPostV2SnapshotThrowsWithNonExistentTarget() throws Exception { + void testPostV3SnapshotThrowsWithNonExistentTarget() throws Exception { CompletableFuture snapshotName = new CompletableFuture<>(); webClient - .post("/api/v2/targets/notFound:9000/snapshot") + .post("/api/v3/targets/notFound:9000/snapshot") .send( ar -> { assertRequestStatus(ar, snapshotName); From 7b11672abbc434da45599ffda01e4e97929631c5 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 13 Aug 2024 15:48:43 -0400 Subject: [PATCH 019/140] remove v2 endpoint and v2Response on CustomTargets --- .../cryostat/discovery/CustomDiscovery.java | 35 ++++++++----------- src/test/java/itest/CustomTargetsTest.java | 9 +++-- .../java/itest/bases/StandardSelfTest.java | 6 ++-- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index 8583d18a0..b6bbb8c8c 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -26,7 +26,6 @@ import java.util.regex.Pattern; import io.cryostat.ConfigProperties; -import io.cryostat.V2Response; import io.cryostat.credentials.Credential; import io.cryostat.expressions.MatchExpression; import io.cryostat.targets.JvmIdException; @@ -90,7 +89,7 @@ void onStart(@Observes StartupEvent evt) { @Transactional(rollbackOn = {JvmIdException.class}) @POST - @Path("/api/v2/targets") + @Path("/api/v3/targets") @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") public Response create( @@ -109,16 +108,16 @@ public Response create( } catch (Exception e) { logger.error("Target validation failed", e); return Response.status(Response.Status.BAD_REQUEST) - .entity(V2Response.json(Response.Status.BAD_REQUEST, e)) + .entity(Map.of(Response.Status.BAD_REQUEST, e.getMessage())) .build(); } // TODO handle credentials embedded in JSON body - return doV2Create(target, Optional.empty(), dryrun, storeCredentials); + return doV3Create(target, Optional.empty(), dryrun, storeCredentials); } @Transactional @POST - @Path("/api/v2/targets") + @Path("/api/v3/targets") @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED}) @RolesAllowed("write") public Response create( @@ -142,10 +141,10 @@ public Response create( credential.password = password; } - return doV2Create(target, Optional.ofNullable(credential), dryrun, storeCredentials); + return doV3Create(target, Optional.ofNullable(credential), dryrun, storeCredentials); } - Response doV2Create( + Response doV3Create( Target target, Optional credential, boolean dryrun, @@ -155,7 +154,7 @@ Response doV2Create( if (!uriUtil.validateUri(target.connectUrl)) { return Response.status(Response.Status.BAD_REQUEST) .entity( - String.format( + Map.of( "The provided URI \"%s\" is unacceptable with the" + " current URI range settings.", target.connectUrl)) @@ -164,9 +163,7 @@ Response doV2Create( if (Target.find("connectUrl", target.connectUrl).singleResultOptional().isPresent()) { return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity( - V2Response.json( - Response.Status.BAD_REQUEST, "Duplicate connection URL")) + .entity(Map.of(Response.Status.BAD_REQUEST, "Duplicate connection URL")) .build(); } @@ -184,7 +181,7 @@ Response doV2Create( if (Target.find("jvmId", jvmId).count() > 0) { return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) .entity( - V2Response.json( + Map.of( Response.Status.BAD_REQUEST, String.format( "Target with JVM ID \"%s\" already discovered", @@ -202,14 +199,12 @@ Response doV2Create( ? "Unexpected service type on port" : "Target connection failed"; return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity(V2Response.json(Response.Status.BAD_REQUEST, msg)) + .entity(Map.of(Response.Status.BAD_REQUEST, msg)) .build(); } if (dryrun) { - return Response.accepted() - .entity(V2Response.json(Response.Status.ACCEPTED, target)) - .build(); + return Response.accepted().entity(Map.of(Response.Status.ACCEPTED, target)).build(); } target.persist(); @@ -231,20 +226,18 @@ Response doV2Create( realm.persist(); return Response.created(URI.create("/api/v3/targets/" + target.id)) - .entity(V2Response.json(Response.Status.CREATED, target)) + .entity(Map.of(Response.Status.CREATED, target)) .build(); } catch (Exception e) { if (ExceptionUtils.indexOfType(e, ConstraintViolationException.class) >= 0) { logger.warn("Invalid target definition", e); return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity( - V2Response.json( - Response.Status.BAD_REQUEST, "Duplicate connection URL")) + .entity(Map.of(Response.Status.BAD_REQUEST, "Duplicate connection URL")) .build(); } logger.error("Unknown error", e); return Response.serverError() - .entity(V2Response.json(Response.Status.INTERNAL_SERVER_ERROR, e)) + .entity(Map.of(Response.Status.INTERNAL_SERVER_ERROR, e)) .build(); } } diff --git a/src/test/java/itest/CustomTargetsTest.java b/src/test/java/itest/CustomTargetsTest.java index ab95c6815..95d9d0e73 100644 --- a/src/test/java/itest/CustomTargetsTest.java +++ b/src/test/java/itest/CustomTargetsTest.java @@ -103,13 +103,13 @@ void shouldBeAbleToTestTargetConnection() webClient .extensions() .post( - "/api/v2/targets?dryrun=true", + "/api/v3/targets?dryrun=true", Buffer.buffer( JsonObject.of("connectUrl", SELF_JMX_URL, "alias", "self") .encode()), REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(202)); - JsonObject body = response.bodyAsJsonObject().getJsonObject("data").getJsonObject("result"); + JsonObject body = response.bodyAsJsonObject().getJsonObject("ACCEPTED"); MatcherAssert.assertThat(body.getString("connectUrl"), Matchers.equalTo(SELF_JMX_URL)); MatcherAssert.assertThat(body.getString("alias"), Matchers.equalTo("self")); MatcherAssert.assertThat(body.getString("jvmId"), Matchers.equalTo(itestJvmId)); @@ -174,13 +174,12 @@ void shouldBeAbleToDefineTarget() webClient .extensions() .post( - "/api/v2/targets?storeCredentials=true", + "/api/v3/targets?storeCredentials=true", form, REQUEST_TIMEOUT_SECONDS); MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(201)); - JsonObject body = response.bodyAsJsonObject().getJsonObject("data").getJsonObject("result"); - + JsonObject body = response.bodyAsJsonObject().getJsonObject("CREATED"); latch.await(30, TimeUnit.SECONDS); MatcherAssert.assertThat(body.getString("connectUrl"), Matchers.equalTo(SELF_JMX_URL)); diff --git a/src/test/java/itest/bases/StandardSelfTest.java b/src/test/java/itest/bases/StandardSelfTest.java index 381143113..61524a985 100644 --- a/src/test/java/itest/bases/StandardSelfTest.java +++ b/src/test/java/itest/bases/StandardSelfTest.java @@ -178,7 +178,7 @@ private static boolean selfCustomTargetExists() { HttpResponse resp = webClient.extensions().get(selfCustomTargetLocation, REQUEST_TIMEOUT_SECONDS); logger.tracev( - "POST /api/v2/targets -> HTTP {0} {1}: [{2}]", + "POST /api/v3/targets -> HTTP {0} {1}: [{2}]", resp.statusCode(), resp.statusMessage(), resp.headers()); boolean result = HttpStatusCodeIdentifier.isSuccessCode(resp.statusCode()); if (!result) { @@ -205,11 +205,11 @@ private static void tryDefineSelfCustomTarget() { webClient .extensions() .post( - "/api/v2/targets", + "/api/v3/targets", Buffer.buffer(self.encode()), REQUEST_TIMEOUT_SECONDS); logger.tracev( - "POST /api/v2/targets -> HTTP {0} {1}: [{2}]", + "POST /api/v3/targets -> HTTP {0} {1}: [{2}]", resp.statusCode(), resp.statusMessage(), resp.headers()); if (!HttpStatusCodeIdentifier.isSuccessCode(resp.statusCode())) { throw new IllegalStateException(Integer.toString(resp.statusCode())); From abc1c2b2221f78afa974909ec0034b0773244266 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 14 Aug 2024 16:32:20 -0400 Subject: [PATCH 020/140] remove v2 endpoint and v2Response on Events --- src/main/java/io/cryostat/events/Events.java | 11 -- src/test/java/itest/TargetEventsGetTest.java | 126 +++++++++---------- 2 files changed, 59 insertions(+), 78 deletions(-) diff --git a/src/main/java/io/cryostat/events/Events.java b/src/main/java/io/cryostat/events/Events.java index 1d93f3613..4d649b533 100644 --- a/src/main/java/io/cryostat/events/Events.java +++ b/src/main/java/io/cryostat/events/Events.java @@ -15,7 +15,6 @@ */ package io.cryostat.events; -import java.net.URI; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -23,7 +22,6 @@ import org.openjdk.jmc.flightrecorder.configuration.events.IEventTypeInfo; -import io.cryostat.V2Response; import io.cryostat.targets.Target; import io.cryostat.targets.TargetConnectionManager; @@ -31,7 +29,6 @@ import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; @@ -43,14 +40,6 @@ public class Events { @Inject TargetConnectionManager connectionManager; @Inject Logger logger; - @GET - @Path("/api/v2/targets/{connectUrl}/events") - @RolesAllowed("read") - public V2Response listEventsV2(@RestPath URI connectUrl, @RestQuery String q) throws Exception { - return V2Response.json( - Response.Status.OK, searchEvents(Target.getTargetByConnectUrl(connectUrl), q)); - } - @GET @Path("/api/v3/targets/{id}/events") @RolesAllowed("read") diff --git a/src/test/java/itest/TargetEventsGetTest.java b/src/test/java/itest/TargetEventsGetTest.java index 7cb1c6d3d..9803fde31 100644 --- a/src/test/java/itest/TargetEventsGetTest.java +++ b/src/test/java/itest/TargetEventsGetTest.java @@ -15,9 +15,8 @@ */ package itest; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import static org.hamcrest.Matchers.*; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -26,6 +25,7 @@ import io.quarkus.test.junit.QuarkusTest; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpHeaders; +import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; import itest.bases.StandardSelfTest; @@ -43,8 +43,6 @@ public class TargetEventsGetTest extends StandardSelfTest { @BeforeEach void setup() { eventReqUrl = String.format("/api/v3/targets/%d/events", getSelfReferenceTargetId()); - searchReqUrl = - String.format("/api/v2/targets/%s/events", getSelfReferenceConnectUrlEncoded()); } @Test @@ -59,7 +57,6 @@ public void testGetTargetEventsReturnsListOfEvents() throws Exception { } }); HttpResponse response = getResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(200)); MatcherAssert.assertThat( response.getHeader(HttpHeaders.CONTENT_TYPE.toString()), @@ -69,10 +66,10 @@ public void testGetTargetEventsReturnsListOfEvents() throws Exception { } @Test - public void testGetTargetEventsV2WithNoQueryReturnsListOfEvents() throws Exception { + public void testGetTargetEventsWithNoQueryReturnsListOfEvents() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(searchReqUrl) + .get(eventReqUrl) .send( ar -> { if (assertRequestStatus(ar, getResponse)) { @@ -80,23 +77,28 @@ public void testGetTargetEventsV2WithNoQueryReturnsListOfEvents() throws Excepti } }); HttpResponse response = getResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(200)); MatcherAssert.assertThat( response.getHeader(HttpHeaders.CONTENT_TYPE.toString()), Matchers.startsWith(HttpMimeType.JSON.mime())); - MatcherAssert.assertThat(response.bodyAsJsonObject().size(), Matchers.greaterThan(0)); - MatcherAssert.assertThat( - response.bodyAsJsonObject().getJsonObject("data").getJsonArray("result").size(), - Matchers.greaterThan(0)); + JsonArray events = response.bodyAsJsonArray(); + MatcherAssert.assertThat(events.size(), Matchers.greaterThan(0)); + + events.forEach( + event -> { + JsonObject eventObj = (JsonObject) event; + MatcherAssert.assertThat(eventObj.getString("name"), notNullValue()); + MatcherAssert.assertThat(eventObj.getString("typeId"), notNullValue()); + MatcherAssert.assertThat(eventObj.getString("description"), notNullValue()); + }); } @Test - public void testGetTargetEventsV2WithQueryReturnsRequestedEvents() throws Exception { + public void testGetTargetEventsWithQueryReturnsRequestedEvents() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(String.format("%s?q=TargetConnectionOpened", searchReqUrl)) + .get(String.format("%s?q=TargetConnectionOpened", eventReqUrl)) .send( ar -> { if (assertRequestStatus(ar, getResponse)) { @@ -104,61 +106,56 @@ public void testGetTargetEventsV2WithQueryReturnsRequestedEvents() throws Except } }); HttpResponse response = getResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(200)); MatcherAssert.assertThat( response.getHeader(HttpHeaders.CONTENT_TYPE.toString()), Matchers.startsWith(HttpMimeType.JSON.mime())); - LinkedHashMap expectedResults = new LinkedHashMap(); - expectedResults.put("name", "Target Connection Opened"); - expectedResults.put( - "typeId", "io.cryostat.targets.TargetConnectionManager.TargetConnectionOpened"); - expectedResults.put("description", ""); - expectedResults.put("category", List.of("Cryostat")); - expectedResults.put( - "options", - Map.of( - "enabled", - Map.of( - "name", - "Enabled", - "description", - "Record event", - "defaultValue", - "true"), - "threshold", - Map.of( - "name", - "Threshold", - "description", - "Record event with duration above or equal to threshold", - "defaultValue", - "0ns[ns]"), - "stackTrace", - Map.of( - "name", - "Stack Trace", - "description", - "Record stack traces", - "defaultValue", - "true"))); - - JsonObject expectedResponse = - new JsonObject( - Map.of( - "meta", Map.of("type", HttpMimeType.JSON.mime(), "status", "OK"), - "data", Map.of("result", List.of(expectedResults)))); - - MatcherAssert.assertThat(response.bodyAsJsonObject().size(), Matchers.greaterThan(0)); - MatcherAssert.assertThat(response.bodyAsJsonObject(), Matchers.equalTo(expectedResponse)); + JsonArray results = response.bodyAsJsonArray(); + MatcherAssert.assertThat(results.size(), Matchers.greaterThan(0)); + + JsonObject expectedEvent = + new JsonObject() + .put("name", "Target Connection Opened") + .put( + "typeId", + "io.cryostat.targets.TargetConnectionManager.TargetConnectionOpened") + .put("description", "") + .put("category", new JsonArray().add("Cryostat")) + .put( + "options", + new JsonObject() + .put( + "enabled", + new JsonObject() + .put("name", "Enabled") + .put("description", "Record event") + .put("defaultValue", "true")) + .put( + "threshold", + new JsonObject() + .put("name", "Threshold") + .put( + "description", + "Record event with duration above" + + " or equal to threshold") + .put("defaultValue", "0ns[ns]")) + .put( + "stackTrace", + new JsonObject() + .put("name", "Stack Trace") + .put("description", "Record stack traces") + .put("defaultValue", "true"))); + + JsonObject actualEvent = results.getJsonObject(0); + MatcherAssert.assertThat(actualEvent, Matchers.equalTo(expectedEvent)); } @Test - public void testGetTargetEventsV2WithQueryReturnsEmptyListWhenNoEventsMatch() throws Exception { + public void testGetTargetEventsWithQueryReturnsEmptyListWhenNoEventsMatch() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(String.format("%s?q=thisEventDoesNotExist", searchReqUrl)) + .get(String.format("%s?q=thisEventDoesNotExist", eventReqUrl)) .send( ar -> { if (assertRequestStatus(ar, getResponse)) { @@ -166,18 +163,13 @@ public void testGetTargetEventsV2WithQueryReturnsEmptyListWhenNoEventsMatch() th } }); HttpResponse response = getResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - MatcherAssert.assertThat(response.statusCode(), Matchers.equalTo(200)); MatcherAssert.assertThat( response.getHeader(HttpHeaders.CONTENT_TYPE.toString()), Matchers.startsWith(HttpMimeType.JSON.mime())); - JsonObject expectedResponse = - new JsonObject( - Map.of( - "meta", Map.of("type", HttpMimeType.JSON.mime(), "status", "OK"), - "data", Map.of("result", List.of()))); + JsonArray results = response.bodyAsJsonArray(); - MatcherAssert.assertThat(response.bodyAsJsonObject(), Matchers.equalTo(expectedResponse)); + MatcherAssert.assertThat(results.size(), Matchers.is(0)); } } From 7cfe3c2f5d12e1fcdc3664192ab8407019f4b8db Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 15 Aug 2024 22:15:08 -0400 Subject: [PATCH 021/140] remove v2 endpoint and v2Response on Rules --- src/main/java/io/cryostat/rules/Rules.java | 83 ++++++++++++++----- .../java/io/cryostat/rules/RulesTest.java | 21 ++++- src/test/java/itest/RulesPostFormIT.java | 10 +-- src/test/java/itest/RulesPostJsonIT.java | 61 +++++++++----- 4 files changed, 128 insertions(+), 47 deletions(-) diff --git a/src/main/java/io/cryostat/rules/Rules.java b/src/main/java/io/cryostat/rules/Rules.java index 32aadcb34..6f7129311 100644 --- a/src/main/java/io/cryostat/rules/Rules.java +++ b/src/main/java/io/cryostat/rules/Rules.java @@ -15,7 +15,8 @@ */ package io.cryostat.rules; -import io.cryostat.V2Response; +import java.util.List; + import io.cryostat.expressions.MatchExpression; import io.cryostat.util.EntityExistsException; @@ -37,32 +38,36 @@ import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; -import org.jboss.resteasy.reactive.RestResponse; -import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; -@Path("/api/v2/rules") +@Path("/api/v3/rules") public class Rules { @Inject EventBus bus; @GET @RolesAllowed("read") - public RestResponse list() { - return RestResponse.ok(V2Response.json(Response.Status.OK, Rule.listAll())); + public Response list() { + List rules = Rule.listAll(); + JsonObject meta = + new JsonObject().put("type", MediaType.APPLICATION_JSON).put("status", "OK"); + JsonObject data = new JsonObject().put("result", rules); + JsonObject response = new JsonObject().put("meta", meta).put("data", data); + return Response.ok(response.encode(), MediaType.APPLICATION_JSON).build(); } @GET @RolesAllowed("read") @Path("/{name}") - public RestResponse get(@RestPath String name) { - return RestResponse.ok(V2Response.json(Response.Status.OK, Rule.getByName(name))); + public Response get(@RestPath String name) { + Rule rule = Rule.getByName(name); + return Response.ok(rule).build(); } @Transactional @POST @RolesAllowed("write") @Consumes(MediaType.APPLICATION_JSON) - public RestResponse create(Rule rule) { + public Response create(Rule rule) { // TODO validate the incoming rule if (rule == null) { throw new BadRequestException("POST body was null"); @@ -75,10 +80,12 @@ public RestResponse create(Rule rule) { rule.description = ""; } rule.persist(); - return ResponseBuilder.create( - Response.Status.CREATED, - V2Response.json(Response.Status.CREATED, rule.name)) - .build(); + + JsonObject meta = + new JsonObject().put("type", MediaType.APPLICATION_JSON).put("status", "Created"); + JsonObject data = new JsonObject().put("result", rule); + JsonObject response = new JsonObject().put("meta", meta).put("data", data); + return Response.status(Response.Status.CREATED).entity(response.encode()).build(); } @Transactional @@ -86,25 +93,34 @@ public RestResponse create(Rule rule) { @RolesAllowed("write") @Path("/{name}") @Consumes(MediaType.APPLICATION_JSON) - public RestResponse update( - @RestPath String name, @RestQuery boolean clean, JsonObject body) { + public Response update(@RestPath String name, @RestQuery boolean clean, JsonObject body) { Rule rule = Rule.getByName(name); + if (rule == null) { + throw new NotFoundException("Rule with name " + name + " not found"); + } + boolean enabled = body.getBoolean("enabled"); // order matters here, we want to clean before we disable if (clean && !enabled) { bus.send(Rule.RULE_ADDRESS + "?clean", rule); } + rule.enabled = enabled; rule.persist(); - return ResponseBuilder.ok(V2Response.json(Response.Status.OK, rule)).build(); + JsonObject meta = + new JsonObject().put("type", MediaType.APPLICATION_JSON).put("status", "OK"); + JsonObject data = new JsonObject().put("result", JsonObject.mapFrom(rule)); + JsonObject response = new JsonObject().put("meta", meta).put("data", data); + + return Response.ok(response.encode()).type(MediaType.APPLICATION_JSON).build(); } @Transactional @POST @RolesAllowed("write") @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED}) - public RestResponse create( + public Response create( @RestForm String name, @RestForm String description, @RestForm String matchExpression, @@ -128,14 +144,35 @@ public RestResponse create( rule.maxAgeSeconds = maxAgeSeconds; rule.maxSizeBytes = maxSizeBytes; rule.enabled = enabled; - return create(rule); + + if (Rule.getByName(rule.name) != null) { + return Response.status(Response.Status.CONFLICT) + .entity( + new JsonObject() + .put("error", "Rule already exists with name: " + rule.name) + .encode()) + .type(MediaType.APPLICATION_JSON) + .build(); + } + + rule.persist(); + + JsonObject meta = + new JsonObject().put("type", MediaType.APPLICATION_JSON).put("status", "Created"); + JsonObject data = new JsonObject().put("result", rule.name); + JsonObject response = new JsonObject().put("meta", meta).put("data", data); + + return Response.status(Response.Status.CREATED) + .entity(response.encode()) + .type(MediaType.APPLICATION_JSON) + .build(); } @Transactional @DELETE @RolesAllowed("write") @Path("/{name}") - public RestResponse delete(@RestPath String name, @RestQuery boolean clean) { + public Response delete(@RestPath String name, @RestQuery boolean clean) { Rule rule = Rule.getByName(name); if (rule == null) { throw new NotFoundException("Rule with name " + name + " not found"); @@ -144,6 +181,12 @@ public RestResponse delete(@RestPath String name, @RestQuery boolean bus.send(Rule.RULE_ADDRESS + "?clean", rule); } rule.delete(); - return RestResponse.ok(V2Response.json(Response.Status.OK, null)); + + JsonObject meta = + new JsonObject().put("type", MediaType.APPLICATION_JSON).put("status", "OK"); + JsonObject data = new JsonObject().put("result", (JsonObject) null); + JsonObject response = new JsonObject().put("meta", meta).put("data", data); + + return Response.ok(response.encode(), MediaType.APPLICATION_JSON).build(); } } diff --git a/src/test/java/io/cryostat/rules/RulesTest.java b/src/test/java/io/cryostat/rules/RulesTest.java index d96cf00ed..900e6914b 100644 --- a/src/test/java/io/cryostat/rules/RulesTest.java +++ b/src/test/java/io/cryostat/rules/RulesTest.java @@ -22,13 +22,16 @@ import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.mockito.InjectSpy; +import io.restassured.RestAssured; import io.restassured.http.ContentType; +import io.restassured.parsing.Parser; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.eventbus.EventBus; import jakarta.transaction.Transactional; import jakarta.ws.rs.core.MediaType; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -47,6 +50,11 @@ public class RulesTest { static String RULE_NAME = "my_rule"; + @BeforeAll + public static void configureRestAssured() { + RestAssured.registerParser("text/plain", Parser.JSON); + } + @BeforeEach public void setup() { rule = new JsonObject(); @@ -87,7 +95,13 @@ public void testList() { "data.result", Matchers.hasSize(1), "data.result[0].name", is(RULE_NAME), "data.result[0].matchExpression", is(EXPR_1), - "data.result[0].eventSpecifier", is("my_event_specifier")); + "data.result[0].eventSpecifier", is("my_event_specifier"), + "data.result[0].archivalPeriodSeconds", is(0), + "data.result[0].initialDelaySeconds", is(0), + "data.result[0].preservedArchives", is(0), + "data.result[0].maxAgeSeconds", is(0), + "data.result[0].maxSizeBytes", is(0), + "data.result[0].enabled", is(true)); } @Test @@ -102,7 +116,8 @@ public void testUpdate() { .body( "meta.type", is(MediaType.APPLICATION_JSON), "meta.status", is("Created"), - "data.result", is(RULE_NAME)); + "data.result.id", notNullValue(), + "data.result.name", is(RULE_NAME)); given().get() .then() @@ -110,7 +125,7 @@ public void testUpdate() { .body( "meta.type", is(MediaType.APPLICATION_JSON), "meta.status", is("OK"), - "data.result", Matchers.hasSize(1), + "data.result", hasSize(1), "data.result[0].name", is(RULE_NAME), "data.result[0].enabled", is(false)); diff --git a/src/test/java/itest/RulesPostFormIT.java b/src/test/java/itest/RulesPostFormIT.java index cfa38dfad..c1e78fbc8 100644 --- a/src/test/java/itest/RulesPostFormIT.java +++ b/src/test/java/itest/RulesPostFormIT.java @@ -68,7 +68,7 @@ void testAddRuleThrowsWhenFormAttributesNull() throws Exception { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.URLENCODED_FORM.mime()) .sendForm( MultiMap.caseInsensitiveMultiMap(), @@ -90,7 +90,7 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { try { webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader( HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.URLENCODED_FORM.mime()) @@ -117,7 +117,7 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { CompletableFuture duplicatePostResponse = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader( HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.URLENCODED_FORM.mime()) @@ -139,7 +139,7 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { // clean up rule before running next test CompletableFuture deleteResponse = new CompletableFuture<>(); webClient - .delete(String.format("/api/v2/rules/%s", TEST_RULE_NAME)) + .delete(String.format("/api/v3/rules/%s", TEST_RULE_NAME)) .send( ar -> { if (assertRequestStatus(ar, deleteResponse)) { @@ -175,7 +175,7 @@ void testAddRuleThrowsWhenIntegerAttributesNegative() throws Exception { testRule.add("preservedArchives", "-3"); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.URLENCODED_FORM.mime()) .sendForm( testRule, diff --git a/src/test/java/itest/RulesPostJsonIT.java b/src/test/java/itest/RulesPostJsonIT.java index b1bc929d1..6be34daa4 100644 --- a/src/test/java/itest/RulesPostJsonIT.java +++ b/src/test/java/itest/RulesPostJsonIT.java @@ -67,7 +67,7 @@ void testAddRuleThrowsWhenJsonAttributesNull() throws Exception { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.JSON.mime()) .sendJsonObject( null, @@ -88,7 +88,7 @@ void testAddRuleThrowsWhenMimeUnsupported() throws Exception { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "text/plain") .sendJsonObject( testRule, @@ -110,7 +110,7 @@ void testAddRuleThrowsWhenMimeInvalid() throws Exception { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "NOTAMIME") .sendJsonObject( testRule, @@ -132,7 +132,7 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { try { webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.JSON.mime()) .sendJsonObject( testRule, @@ -141,23 +141,46 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { response.complete(ar.result().bodyAsJsonObject()); } }); - - JsonObject expectedresponse = - new JsonObject( - Map.of( + JsonObject firstResponse = response.get(10, TimeUnit.SECONDS); + Integer firstResponseId = + firstResponse.getJsonObject("data").getJsonObject("result").getInteger("id"); + // Define the expected response structure based on the first creation + JsonObject expectedCreationResponse = + new JsonObject() + .put( "meta", - Map.of( - "type", - HttpMimeType.JSON.mime(), - "status", - "Created"), - "data", Map.of("result", TEST_RULE_NAME))); + new JsonObject() + .put("type", HttpMimeType.JSON.mime()) + .put("status", "Created")) + .put( + "data", + new JsonObject() + .put( + "result", + new JsonObject() + .put("id", firstResponseId) + .put("name", "Test_Rule") + .put( + "description", + "AutoRulesIT automated rule") + .put( + "matchExpression", + "target.alias ==" + + " 'es.andrewazor.demo.Main'") + .put( + "eventSpecifier", + "template=Continuous,type=TARGET") + .put("archivalPeriodSeconds", 0) + .put("initialDelaySeconds", 0) + .put("preservedArchives", 0) + .put("maxAgeSeconds", 0) + .put("maxSizeBytes", 0) + .put("enabled", false))); MatcherAssert.assertThat( - response.get(10, TimeUnit.SECONDS), Matchers.equalTo(expectedresponse)); - + response.get(10, TimeUnit.SECONDS), Matchers.equalTo(expectedCreationResponse)); CompletableFuture duplicatePostResponse = new CompletableFuture<>(); webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.JSON.mime()) .sendJsonObject( testRule, @@ -177,7 +200,7 @@ void testAddRuleThrowsWhenRuleNameAlreadyExists() throws Exception { // clean up rule before running next test CompletableFuture deleteResponse = new CompletableFuture<>(); webClient - .delete(String.format("/api/v2/rules/%s", TEST_RULE_NAME)) + .delete(String.format("/api/v3/rules/%s", TEST_RULE_NAME)) .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.JSON.mime()) .send( ar -> { @@ -214,7 +237,7 @@ void testAddRuleThrowsWhenIntegerAttributesNegative() throws Exception { testRule.put("preservedArchives", -3); try { webClient - .post("/api/v2/rules") + .post("/api/v3/rules") .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.JSON.mime()) .sendJsonObject( testRule, From 310d69843b266ccccd329c84926fe90143ed3ad8 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 11:02:24 -0400 Subject: [PATCH 022/140] remove v2 endpoint and v2Response on MatchExpressions --- .../expressions/MatchExpressions.java | 35 +++++++++++++++++-- .../expressions/MatchExpressionsTest.java | 9 ++--- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/cryostat/expressions/MatchExpressions.java b/src/main/java/io/cryostat/expressions/MatchExpressions.java index b6f8f2e62..90a18f7c0 100644 --- a/src/main/java/io/cryostat/expressions/MatchExpressions.java +++ b/src/main/java/io/cryostat/expressions/MatchExpressions.java @@ -22,17 +22,19 @@ import java.util.Objects; import java.util.Optional; -import io.cryostat.V2Response; import io.cryostat.expressions.MatchExpression.MatchedExpression; import io.cryostat.targets.Target; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Multi; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; @@ -50,7 +52,7 @@ public class MatchExpressions { // FIXME in a later API version this request should not accept full target objects from the // client but instead only a list of IDs, which will then be pulled from the target discovery // database for testing - public V2Response test(RequestData requestData) throws ScriptException { + public Response test(RequestData requestData) throws ScriptException { var targets = new HashSet(); // don't trust the client to provide the whole Target object to be tested, just extract the // connectUrl they provide and use that to look up the Target definition as we know it. @@ -63,7 +65,34 @@ public V2Response test(RequestData requestData) throws ScriptException { .ifPresent(targets::add)); var matched = targetMatcher.match(new MatchExpression(requestData.matchExpression), targets); - return V2Response.json(Response.Status.OK, matched); + // Convert matched expression to JSON object directly + JsonObject jsonResponse = createMatchedResponse(matched); + + return Response.ok(jsonResponse.encode(), MediaType.APPLICATION_JSON).build(); + } + + private JsonObject createMatchedResponse(MatchExpression.MatchedExpression matched) { + JsonArray targetsJson = new JsonArray(); + matched.targets() + .forEach( + target -> { + JsonObject targetJson = new JsonObject(); + targetJson.put("connectUrl", target.connectUrl); + targetJson.put("alias", target.alias); + targetsJson.add(targetJson); + }); + + return new JsonObject() + .put( + "meta", + new JsonObject() + .put("type", MediaType.APPLICATION_JSON) + .put("status", "OK")) + .put( + "data", + new JsonObject() + .put("matchExpression", matched.expression()) + .put("targets", targetsJson)); } @GET diff --git a/src/test/java/io/cryostat/expressions/MatchExpressionsTest.java b/src/test/java/io/cryostat/expressions/MatchExpressionsTest.java index e4738d5f3..2576d6bc4 100644 --- a/src/test/java/io/cryostat/expressions/MatchExpressionsTest.java +++ b/src/test/java/io/cryostat/expressions/MatchExpressionsTest.java @@ -17,8 +17,6 @@ import static io.restassured.RestAssured.given; -import java.util.HashMap; -import java.util.List; import java.util.Map; import io.quarkus.test.common.http.TestHTTPEndpoint; @@ -43,10 +41,6 @@ public void afterEach() { @Test public void testPostWithoutTargets() { - var expectation = new HashMap<>(); - expectation.put("id", null); - expectation.put("expression", "true"); - expectation.put("targets", List.of()); given().contentType(ContentType.JSON) .body(ALL_MATCHING_EXPRESSION) .when() @@ -59,6 +53,7 @@ public void testPostWithoutTargets() { .and() .body("meta.type", Matchers.equalTo("application/json")) .body("meta.status", Matchers.equalTo("OK")) - .body("data.result", Matchers.equalTo(expectation)); + .body("data.matchExpression", Matchers.equalTo("true")) + .body("data.targets.size()", Matchers.is(0)); } } From b36924db7765732a013a55748049275a98dcbf55 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 11:55:31 -0400 Subject: [PATCH 023/140] remove v2Response on JMCAgent --- .../java/io/cryostat/jmcagent/JMCAgent.java | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/cryostat/jmcagent/JMCAgent.java b/src/main/java/io/cryostat/jmcagent/JMCAgent.java index c959304e1..911d604b5 100644 --- a/src/main/java/io/cryostat/jmcagent/JMCAgent.java +++ b/src/main/java/io/cryostat/jmcagent/JMCAgent.java @@ -17,11 +17,9 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import io.cryostat.V2Response; import io.cryostat.core.jmcagent.Event; import io.cryostat.core.jmcagent.JMCAgentJMXHelper; import io.cryostat.core.jmcagent.JMCAgentJMXHelper.ProbeDefinitionException; @@ -34,12 +32,15 @@ import io.smallrye.common.annotation.Blocking; import io.vertx.core.eventbus.EventBus; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestForm; @@ -139,37 +140,65 @@ public Response deleteProbe(@RestPath long id) { @Blocking @GET @Path("/api/v3/targets/{id}/probes") - public V2Response getProbes(@RestPath long id) { + public Response getProbes(@RestPath long id) { try { Target target = Target.getTargetById(id); - return connectionManager.executeConnectedTask( + + // Enforce return type to Response explicitly + return connectionManager.executeConnectedTask( target, connection -> { JMCAgentJMXHelper helper = new JMCAgentJMXHelper(connection.getHandle()); - List result = new ArrayList<>(); - String probes = helper.retrieveEventProbes(); + String probes; + try { + probes = helper.retrieveEventProbes(); + } catch (Exception e) { + logger.error("Failed to retrieve event probes", e); + return Response.status(Response.Status.BAD_REQUEST) + .entity("Failed to retrieve event probes: " + e.getMessage()) + .build(); + } + + if (probes == null || probes.isBlank()) { + return Response.ok(new JsonArray().encode()) + .type(MediaType.APPLICATION_JSON) + .build(); + } + try { - if (probes != null && !probes.isBlank()) { - ProbeTemplate template = new ProbeTemplate(); - template.deserialize( - new ByteArrayInputStream( - probes.getBytes(StandardCharsets.UTF_8))); - for (Event e : template.getEvents()) { - result.add(e); - } + ProbeTemplate template = new ProbeTemplate(); + template.deserialize( + new ByteArrayInputStream( + probes.getBytes(StandardCharsets.UTF_8))); + + JsonArray eventsJson = new JsonArray(); + for (Event e : template.getEvents()) { + JsonObject eventJson = new JsonObject(); + eventJson.put("name", e.name); + eventJson.put("description", e.description); + eventsJson.add(eventJson); } - return V2Response.json(Response.Status.OK, result); + return Response.ok(eventsJson.encode()) + .type(MediaType.APPLICATION_JSON) + .build(); } catch (Exception e) { - throw new BadRequestException(e); + // Cleanup the probes if something went wrong + helper.defineEventProbes(null); + logger.error("Error processing probes, cleaning up active probes", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Error processing probes: " + e.getMessage()) + .build(); } }); } catch (Exception e) { - logger.warn("Caught exception" + e.toString(), e); - throw new BadRequestException(e); + logger.warn("Caught exception while handling getProbes request", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Unable to handle the getProbes request: " + e.getMessage()) + .build(); } } - @Blocking + /* @Blocking @GET @Path("/api/v3/probes") public V2Response getProbeTemplates() { @@ -183,6 +212,31 @@ public V2Response getProbeTemplates() { logger.warn("Caught exception" + e.toString(), e); throw new BadRequestException(e); } + } */ + + @Blocking + @GET + @Path("/api/v3/probes") + public Response getProbeTemplates() { + try { + List templates = + service.getTemplates().stream() + .map(SerializableProbeTemplateInfo::fromProbeTemplate) + .toList(); + + JsonArray templatesJson = new JsonArray(); + for (SerializableProbeTemplateInfo template : templates) { + JsonObject templateJson = new JsonObject(); + templateJson.put("name", template.name()); + templateJson.put("description", template.xml()); + templatesJson.add(templateJson); + } + + return Response.ok(templatesJson.encode()).type(MediaType.APPLICATION_JSON).build(); + } catch (Exception e) { + logger.warn("Caught exception: " + e.toString(), e); + throw new BadRequestException(e); + } } @Blocking From 48f8fce0ca2a23367fc685af1e041344f4a089c1 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 11:56:54 -0400 Subject: [PATCH 024/140] remove v2Response on JMCAgent --- .../java/io/cryostat/jmcagent/JMCAgent.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/java/io/cryostat/jmcagent/JMCAgent.java b/src/main/java/io/cryostat/jmcagent/JMCAgent.java index 911d604b5..71c446d92 100644 --- a/src/main/java/io/cryostat/jmcagent/JMCAgent.java +++ b/src/main/java/io/cryostat/jmcagent/JMCAgent.java @@ -144,7 +144,6 @@ public Response getProbes(@RestPath long id) { try { Target target = Target.getTargetById(id); - // Enforce return type to Response explicitly return connectionManager.executeConnectedTask( target, connection -> { @@ -198,22 +197,6 @@ public Response getProbes(@RestPath long id) { } } - /* @Blocking - @GET - @Path("/api/v3/probes") - public V2Response getProbeTemplates() { - try { - return V2Response.json( - Response.Status.OK, - service.getTemplates().stream() - .map(SerializableProbeTemplateInfo::fromProbeTemplate) - .toList()); - } catch (Exception e) { - logger.warn("Caught exception" + e.toString(), e); - throw new BadRequestException(e); - } - } */ - @Blocking @GET @Path("/api/v3/probes") From b021f6d764073630cab57a38a232bb532d6b4f81 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 14:45:50 -0400 Subject: [PATCH 025/140] remove v2.2 endpont and v2Response on Credentials --- .../cryostat/credentials/CredentialCheck.java | 8 +-- .../io/cryostat/credentials/Credentials.java | 65 ++++++++++++------- src/test/java/itest/CustomTargetsTest.java | 2 +- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/main/java/io/cryostat/credentials/CredentialCheck.java b/src/main/java/io/cryostat/credentials/CredentialCheck.java index 62497cd42..5be370394 100644 --- a/src/main/java/io/cryostat/credentials/CredentialCheck.java +++ b/src/main/java/io/cryostat/credentials/CredentialCheck.java @@ -19,7 +19,6 @@ import java.net.URISyntaxException; import java.util.Optional; -import io.cryostat.V2Response; import io.cryostat.targets.Target; import io.cryostat.targets.TargetConnectionManager; @@ -29,7 +28,8 @@ import jakarta.inject.Inject; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; @@ -42,7 +42,7 @@ public class CredentialCheck { @Blocking @RolesAllowed("read") @Path("/api/beta/credentials/{connectUrl}") - public Uni checkCredentialForTarget( + public Uni checkCredentialForTarget( @RestPath String connectUrl, @RestForm String username, @RestForm String password) throws URISyntaxException { Target target = Target.getTargetByConnectUrl(new URI(connectUrl)); @@ -75,7 +75,7 @@ public Uni checkCredentialForTarget( t)) .recoverWithItem(t -> CredentialTestResult.FAILURE); }) - .map(r -> V2Response.json(Status.OK, r)); + .map(r -> Response.ok(r).type(MediaType.APPLICATION_JSON).build()); } static enum CredentialTestResult { diff --git a/src/main/java/io/cryostat/credentials/Credentials.java b/src/main/java/io/cryostat/credentials/Credentials.java index 7f840be92..524b34b52 100644 --- a/src/main/java/io/cryostat/credentials/Credentials.java +++ b/src/main/java/io/cryostat/credentials/Credentials.java @@ -21,11 +21,11 @@ import java.util.Map; import java.util.Objects; -import io.cryostat.V2Response; import io.cryostat.expressions.MatchExpression; import io.cryostat.expressions.MatchExpression.TargetMatcher; import io.smallrye.common.annotation.Blocking; +import io.vertx.core.json.JsonObject; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -33,6 +33,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestForm; @@ -41,7 +42,7 @@ import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; import org.projectnessie.cel.tools.ScriptException; -@Path("/api/v2.2/credentials") +@Path("/api/v3/credentials") public class Credentials { @Inject TargetMatcher targetMatcher; @@ -49,30 +50,50 @@ public class Credentials { @GET @RolesAllowed("read") - public V2Response list() { - List credentials = Credential.listAll(); - return V2Response.json( - Response.Status.OK, - credentials.stream() - .map( - c -> { - try { - return Credentials.safeResult(c, targetMatcher); - } catch (ScriptException e) { - logger.warn(e); - return null; - } - }) - .filter(Objects::nonNull) - .toList()); + public Response list() { + try { + List credentials = Credential.listAll(); + List> results = + credentials.stream() + .map( + c -> { + try { + return Credentials.safeResult(c, targetMatcher); + } catch (ScriptException e) { + logger.warn(e); + return null; + } + }) + .filter(Objects::nonNull) + .toList(); + + JsonObject response = new JsonObject(); + response.put( + "meta", new JsonObject().put("type", "application/json").put("status", "OK")); + response.put("data", new JsonObject().put("result", results)); + + return Response.ok(response.encode()).type(MediaType.APPLICATION_JSON).build(); + } catch (Exception e) { + logger.error("Error listing credentials", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } } @GET @RolesAllowed("read") @Path("/{id}") - public V2Response get(@RestPath long id) throws ScriptException { - Credential credential = Credential.find("id", id).singleResult(); - return V2Response.json(Response.Status.OK, safeMatchedResult(credential, targetMatcher)); + public Response get(@RestPath long id) throws ScriptException { + try { + Credential credential = Credential.find("id", id).singleResult(); + Map result = safeMatchedResult(credential, targetMatcher); + return Response.ok(result).type(MediaType.APPLICATION_JSON).build(); + } catch (ScriptException e) { + logger.error("Error retrieving credential", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (Exception e) { + logger.error("Credential not found", e); + return Response.status(Response.Status.NOT_FOUND).build(); + } } @Transactional @@ -89,7 +110,7 @@ public RestResponse create( credential.username = username; credential.password = password; credential.persist(); - return ResponseBuilder.created(URI.create("/api/v2.2/credentials/" + credential.id)) + return ResponseBuilder.created(URI.create("/api/v3/credentials/" + credential.id)) .build(); } diff --git a/src/test/java/itest/CustomTargetsTest.java b/src/test/java/itest/CustomTargetsTest.java index 95d9d0e73..b4bdeb438 100644 --- a/src/test/java/itest/CustomTargetsTest.java +++ b/src/test/java/itest/CustomTargetsTest.java @@ -91,7 +91,7 @@ static void cleanup() throws Exception { } webClient .extensions() - .delete("/api/v2.2/credentials/" + storedCredential.id, 0) + .delete("/api/v3/credentials/" + storedCredential.id, 0) .bodyAsJsonObject(); } From 026656fe2c0ea72b45a00d02e9706229c0dd465e Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 17:29:16 -0400 Subject: [PATCH 026/140] remove v2.2 endpont and replace with v3 --- .../java/io/cryostat/JsonRequestFilter.java | 6 ++--- .../java/io/cryostat/discovery/Discovery.java | 8 +++--- .../discovery/DiscoveryJwtFactory.java | 2 +- src/main/java/io/cryostat/security/Auth.java | 9 +++---- src/test/java/itest/DiscoveryPluginIT.java | 26 +++++++++---------- src/test/java/itest/NoopAuthV2IT.java | 2 +- src/test/java/itest/TargetPostDeleteIT.java | 2 +- .../java/itest/TargetRecordingOptionsIT.java | 2 +- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/cryostat/JsonRequestFilter.java b/src/main/java/io/cryostat/JsonRequestFilter.java index c61651b27..11465b8b1 100644 --- a/src/main/java/io/cryostat/JsonRequestFilter.java +++ b/src/main/java/io/cryostat/JsonRequestFilter.java @@ -39,10 +39,10 @@ public class JsonRequestFilter implements ContainerRequestFilter { static final Set disallowedFields = Set.of("id"); static final Set allowedPathPatterns = Set.of( - "/api/v2.2/discovery", - "/api/v2/rules/[\\w]+", + "/api/v3/discovery", + "/api/v3/rules/[\\w]+", "/api/beta/matchExpressions", - "/api/v2.2/graphql", + "/api/v2/graphql", "/api/v3/graphql"); private final Map compiledPatterns = new HashMap<>(); diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index 3c17ccf1d..90977a685 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -151,7 +151,7 @@ public DiscoveryNode get() { } @GET - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v3/discovery/{id}") @RolesAllowed("read") public RestResponse checkRegistration( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) @@ -168,7 +168,7 @@ public RestResponse checkRegistration( @Transactional @POST - @Path("/api/v2.2/discovery") + @Path("/api/v3/discovery") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") @@ -308,7 +308,7 @@ public Response register(@Context RoutingContext ctx, JsonObject body) @Transactional @POST - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v3/discovery/{id}") @Consumes(MediaType.APPLICATION_JSON) @PermitAll public Map> publish( @@ -347,7 +347,7 @@ public Map> publish( @Transactional @DELETE - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v3/discovery/{id}") @PermitAll public Map> deregister( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) diff --git a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java index e033a1410..77c21da41 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java @@ -55,7 +55,7 @@ public class DiscoveryJwtFactory { public static final String RESOURCE_CLAIM = "resource"; public static final String REALM_CLAIM = "realm"; - static final String DISCOVERY_API_PATH = "/api/v2.2/discovery/"; + static final String DISCOVERY_API_PATH = "/api/v3/discovery/"; @Inject JWSSigner signer; @Inject JWSVerifier verifier; diff --git a/src/main/java/io/cryostat/security/Auth.java b/src/main/java/io/cryostat/security/Auth.java index c9b1ce388..78fd92962 100644 --- a/src/main/java/io/cryostat/security/Auth.java +++ b/src/main/java/io/cryostat/security/Auth.java @@ -18,8 +18,6 @@ import java.net.URI; import java.util.Map; -import io.cryostat.V2Response; - import io.vertx.ext.web.RoutingContext; import jakarta.annotation.security.PermitAll; import jakarta.ws.rs.POST; @@ -35,7 +33,7 @@ public class Auth { @POST - @Path("/api/v2.1/logout") + @Path("/api/v3/logout") @PermitAll @Produces(MediaType.APPLICATION_JSON) public Response logout(@Context RoutingContext context) { @@ -45,7 +43,7 @@ public Response logout(@Context RoutingContext context) { } @POST - @Path("/api/v2.1/auth") + @Path("/api/v3/auth") @PermitAll @Produces(MediaType.APPLICATION_JSON) public Response login(@Context RoutingContext context, SecurityContext securityContext) { @@ -56,8 +54,9 @@ public Response login(@Context RoutingContext context, SecurityContext securityC if (user == null) { user = ""; } + Map responseData = Map.of("username", user); return Response.ok() - .entity(V2Response.json(Response.Status.OK, Map.of("username", user))) + .entity(Response.ok(responseData).type(MediaType.APPLICATION_JSON).build()) .build(); } } diff --git a/src/test/java/itest/DiscoveryPluginIT.java b/src/test/java/itest/DiscoveryPluginIT.java index 92527e119..a412ae0da 100644 --- a/src/test/java/itest/DiscoveryPluginIT.java +++ b/src/test/java/itest/DiscoveryPluginIT.java @@ -52,7 +52,7 @@ void shouldBeAbleToRegister() throws InterruptedException, ExecutionException { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -75,7 +75,7 @@ void shouldFailToRegisterWithNonUriCallback() throws InterruptedException, Execu CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -103,7 +103,7 @@ void shouldBeAbleToUpdate() throws InterruptedException, ExecutionException { CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery/" + id) + .post("/api/v3/discovery/" + id) .addQueryParam("token", token) .sendJson( subtree, @@ -133,7 +133,7 @@ void shouldFailToUpdateWithInvalidSubtreeJson() CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery/" + id) + .post("/api/v3/discovery/" + id) .addQueryParam("token", token) .sendBuffer( Buffer.buffer(body), @@ -151,7 +151,7 @@ void shouldFailToReregisterWithoutToken() throws InterruptedException, Execution CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -171,7 +171,7 @@ void shouldBeAbleToRefreshToken() throws InterruptedException, ExecutionExceptio CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") // intentionally don't include this header on refresh - it should still work // .putHeader("Authorization", "None") .sendJson( @@ -198,7 +198,7 @@ void shouldBeAbleToRefreshToken() throws InterruptedException, ExecutionExceptio void shouldBeAbleToDeregister() throws InterruptedException, ExecutionException { CompletableFuture response = new CompletableFuture<>(); webClient - .delete("/api/v2.2/discovery/" + id) + .delete("/api/v3/discovery/" + id) .addQueryParam("token", token) .send( ar -> { @@ -214,7 +214,7 @@ void shouldBeAbleToDeregister() throws InterruptedException, ExecutionException void shouldFailToDoubleDeregister() throws InterruptedException, ExecutionException { CompletableFuture response = new CompletableFuture<>(); webClient - .delete("/api/v2.2/discovery/" + id) + .delete("/api/v3/discovery/" + id) .addQueryParam("token", token) .send( ar -> { @@ -241,7 +241,7 @@ void shouldFailToUpdateUnregisteredPluginID() throws InterruptedException, Execu CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery/" + UUID.randomUUID()) + .post("/api/v3/discovery/" + UUID.randomUUID()) .addQueryParam("token", token) .sendJson( subtree, @@ -259,7 +259,7 @@ void shouldFailToRegisterNullCallback() throws InterruptedException, ExecutionEx CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -277,7 +277,7 @@ void shouldFailToRegisterEmptyCallback() throws InterruptedException, ExecutionE CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -295,7 +295,7 @@ void shouldFailToRegisterNullRealm() throws InterruptedException, ExecutionExcep CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, @@ -313,7 +313,7 @@ void shouldFailToRegisterEmptyRealm() throws InterruptedException, ExecutionExce CompletableFuture response = new CompletableFuture<>(); webClient - .post("/api/v2.2/discovery") + .post("/api/v3/discovery") .putHeader("Authorization", "None") .sendJson( body, diff --git a/src/test/java/itest/NoopAuthV2IT.java b/src/test/java/itest/NoopAuthV2IT.java index 0dc6d8984..80c3c4737 100644 --- a/src/test/java/itest/NoopAuthV2IT.java +++ b/src/test/java/itest/NoopAuthV2IT.java @@ -38,7 +38,7 @@ public class NoopAuthV2IT extends StandardSelfTest { @BeforeEach void createRequest() { - req = webClient.post("/api/v2.1/auth"); + req = webClient.post("/api/v3/auth"); } @Test diff --git a/src/test/java/itest/TargetPostDeleteIT.java b/src/test/java/itest/TargetPostDeleteIT.java index a7ffb010e..b54b6fc6b 100644 --- a/src/test/java/itest/TargetPostDeleteIT.java +++ b/src/test/java/itest/TargetPostDeleteIT.java @@ -33,7 +33,7 @@ @QuarkusIntegrationTest @Disabled("TODO") public class TargetPostDeleteIT extends StandardSelfTest { - static final String REQ_URL = "/api/v2/targets"; + static final String REQ_URL = "/api/v3/targets"; @Test public void testPostTargetThrowsWithoutConnectUrlAttribute() throws Exception { diff --git a/src/test/java/itest/TargetRecordingOptionsIT.java b/src/test/java/itest/TargetRecordingOptionsIT.java index 6752a2399..178f56530 100644 --- a/src/test/java/itest/TargetRecordingOptionsIT.java +++ b/src/test/java/itest/TargetRecordingOptionsIT.java @@ -48,7 +48,7 @@ public class TargetRecordingOptionsIT extends StandardSelfTest { static final String OPTIONS_REQ_URL = String.format("/api/v1/targets/%s/recordingOptions", getSelfReferenceConnectUrl()); static final String OPTIONS_LIST_REQ_URL = - String.format("/api/v2/targets/%s/recordingOptionsList", getSelfReferenceConnectUrl()); + String.format("/api/v3/targets/%s/recordingOptionsList", getSelfReferenceConnectUrl()); static final String RECORDING_NAME = "test_recording"; static final String ARCHIVED_REQ_URL = "/api/v1/recordings"; From e0f1f76709c390ac8688a36ca6fe67b77e3bb1f5 Mon Sep 17 00:00:00 2001 From: Cryostat CI Date: Fri, 16 Aug 2024 21:33:40 +0000 Subject: [PATCH 027/140] chore(schema): automatic update --- schema/openapi.yaml | 1341 +++++++++++-------------------------------- 1 file changed, 340 insertions(+), 1001 deletions(-) diff --git a/schema/openapi.yaml b/schema/openapi.yaml index 27a342de4..abf446529 100644 --- a/schema/openapi.yaml +++ b/schema/openapi.yaml @@ -54,8 +54,6 @@ components: $ref: '#/components/schemas/ArchivedRecording' type: array type: object - Data: - type: object DiscoveryNode: properties: children: @@ -182,13 +180,6 @@ components: $ref: '#/components/schemas/Target' type: array type: object - Meta: - properties: - status: - type: string - type: - type: string - type: object Metadata: properties: expiry: @@ -340,13 +331,6 @@ components: format: uuid pattern: '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}' type: string - V2Response: - properties: - data: - $ref: '#/components/schemas/Data' - meta: - $ref: '#/components/schemas/Meta' - type: object securitySchemes: SecurityScheme: description: Authentication @@ -386,9 +370,7 @@ paths: responses: "200": content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' + application/json: {} description: OK "401": description: Not Authorized @@ -466,30 +448,6 @@ paths: - SecurityScheme: [] tags: - Recordings - /api/beta/fs/recordings/{jvmId}/{filename}/upload: - post: - parameters: - - in: path - name: filename - required: true - schema: - type: string - - in: path - name: jvmId - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings /api/beta/matchExpressions: get: responses: @@ -518,10 +476,6 @@ paths: $ref: '#/components/schemas/RequestData' responses: "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' description: OK "401": description: Not Authorized @@ -579,30 +533,6 @@ paths: - SecurityScheme: [] tags: - Recordings - /api/beta/recordings/{connectUrl}/{filename}/upload: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - type: string - - in: path - name: filename - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings /api/beta/recordings/{jvmId}: get: parameters: @@ -738,17 +668,19 @@ paths: - SecurityScheme: [] tags: - Recordings - /api/v1/reports/{recordingName}: - get: - deprecated: true + /api/v1/targets/{connectUrl}/snapshot: + post: parameters: - in: path - name: recordingName + name: connectUrl required: true schema: + format: uri type: string responses: "200": + content: + application/json: {} description: OK "401": description: Not Authorized @@ -757,9 +689,16 @@ paths: security: - SecurityScheme: [] tags: - - Reports - /api/v1/targets: + - Recordings + /api/v3/activedownload/{id}: get: + parameters: + - in: path + name: id + required: true + schema: + format: int64 + type: integer responses: "200": description: OK @@ -770,20 +709,16 @@ paths: security: - SecurityScheme: [] tags: - - Targets - /api/v1/targets/{connectUrl}/events: + - Recordings + /api/v3/auth: + post: + responses: + "200": + description: OK + tags: + - Auth + /api/v3/credentials: get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: query - name: q - schema: - type: string responses: "200": description: OK @@ -794,19 +729,43 @@ paths: security: - SecurityScheme: [] tags: - - Events - /api/v1/targets/{connectUrl}/recordingOptions: - get: + - Credentials + post: + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + matchExpression: + type: string + password: + type: string + username: + type: string + type: object + responses: + "201": + description: Created + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Credentials + /api/v3/credentials/{id}: + delete: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri - type: string + format: int64 + type: integer responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -814,15 +773,15 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - patch: + - Credentials + get: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri - type: string + format: int64 + type: integer responses: "200": description: OK @@ -833,18 +792,15 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings: + - Credentials + /api/v3/discovery: get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/DiscoveryNode' description: OK "401": description: Not Authorized @@ -853,15 +809,13 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Discovery post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JsonObject' responses: "200": description: OK @@ -872,24 +826,46 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings/{recordingName}: + - Discovery + /api/v3/discovery/{id}: delete: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri + $ref: '#/components/schemas/UUID' + - in: query + name: token + schema: type: string + responses: + "200": + content: + application/json: + schema: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + description: OK + tags: + - Discovery + get: + parameters: - in: path - name: recordingName + name: id required: true + schema: + $ref: '#/components/schemas/UUID' + - in: query + name: token schema: type: string responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -897,887 +873,43 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - patch: + - Discovery + post: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri - type: string - - in: path - name: recordingName - required: true + $ref: '#/components/schemas/UUID' + - in: query + name: token schema: type: string requestBody: content: application/json: schema: - type: string + items: + $ref: '#/components/schemas/DiscoveryNode' + type: array responses: "200": + content: + application/json: + schema: + additionalProperties: + additionalProperties: + type: string + type: object + type: object description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings/{recordingName}/upload: - post: + - Discovery + /api/v3/discovery_plugins: + get: parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: recordingName - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v1/targets/{connectUrl}/snapshot: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - content: - application/json: {} - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v1/targets/{connectUrl}/templates: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v1/targets/{connectUrl}/templates/{templateName}/type/{templateType}: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: templateName - required: true - schema: - type: string - - in: path - name: templateType - required: true - schema: - $ref: '#/components/schemas/TemplateType' - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v1/targets/{targetId}/reports/{recordingName}: - get: - deprecated: true - parameters: - - in: path - name: recordingName - required: true - schema: - type: string - - in: path - name: targetId - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Reports - /api/v1/templates: - post: - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - template: - $ref: '#/components/schemas/FileUpload' - type: object - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v1/templates/{templateName}: - delete: - parameters: - - in: path - name: templateName - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v2.1/auth: - post: - responses: - "200": - description: OK - tags: - - Auth - /api/v2.1/discovery: - get: - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - /api/v2.1/logout: - post: - responses: - "200": - description: OK - tags: - - Auth - /api/v2.1/targets/{connectUrl}/templates/{templateName}/type/{templateType}: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: templateName - required: true - schema: - type: string - - in: path - name: templateType - required: true - schema: - $ref: '#/components/schemas/TemplateType' - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v2.2/credentials: - get: - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Credentials - post: - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - matchExpression: - type: string - password: - type: string - username: - type: string - type: object - responses: - "201": - description: Created - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Credentials - /api/v2.2/credentials/{id}: - delete: - parameters: - - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "204": - description: No Content - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Credentials - get: - parameters: - - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Credentials - /api/v2.2/discovery: - post: - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JsonObject' - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - /api/v2.2/discovery/{id}: - delete: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token - schema: - type: string - responses: - "200": - content: - application/json: - schema: - additionalProperties: - additionalProperties: - type: string - type: object - type: object - description: OK - tags: - - Discovery - get: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token - schema: - type: string - responses: - "204": - description: No Content - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - post: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token - schema: - type: string - requestBody: - content: - application/json: - schema: - items: - $ref: '#/components/schemas/DiscoveryNode' - type: array - responses: - "200": - content: - application/json: - schema: - additionalProperties: - additionalProperties: - type: string - type: object - type: object - description: OK - tags: - - Discovery - /api/v2.2/graphql: - get: - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Graph QL - post: - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Graph QL - /api/v2/probes: - get: - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/probes/{probeTemplateName}: - delete: - parameters: - - in: path - name: probeTemplateName - required: true - schema: - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - post: - parameters: - - in: path - name: probeTemplateName - required: true - schema: - type: string - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - probeTemplate: - $ref: '#/components/schemas/FileUpload' - type: object - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/rules: - get: - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - post: - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Rule' - application/x-www-form-urlencoded: - schema: - properties: - archivalPeriodSeconds: - format: int32 - type: integer - description: - type: string - enabled: - type: boolean - eventSpecifier: - type: string - initialDelaySeconds: - format: int32 - type: integer - matchExpression: - type: string - maxAgeSeconds: - format: int32 - type: integer - maxSizeBytes: - format: int32 - type: integer - name: - type: string - preservedArchives: - format: int32 - type: integer - type: object - multipart/form-data: - schema: - properties: - archivalPeriodSeconds: - format: int32 - type: integer - description: - type: string - enabled: - type: boolean - eventSpecifier: - type: string - initialDelaySeconds: - format: int32 - type: integer - matchExpression: - type: string - maxAgeSeconds: - format: int32 - type: integer - maxSizeBytes: - format: int32 - type: integer - name: - type: string - preservedArchives: - format: int32 - type: integer - type: object - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - /api/v2/rules/{name}: - delete: - parameters: - - in: path - name: name - required: true - schema: - type: string - - in: query - name: clean - schema: - type: boolean - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - get: - parameters: - - in: path - name: name - required: true - schema: - type: string - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - patch: - parameters: - - in: path - name: name - required: true - schema: - type: string - - in: query - name: clean - schema: - type: boolean - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JsonObject' - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - /api/v2/targets: - post: - parameters: - - in: query - name: dryrun - schema: - type: boolean - - in: query - name: storeCredentials - schema: - type: boolean - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Target' - application/x-www-form-urlencoded: - schema: - properties: - alias: - type: string - connectUrl: - format: uri - type: string - password: - type: string - username: - type: string - type: object - multipart/form-data: - schema: - properties: - alias: - type: string - connectUrl: - format: uri - type: string - password: - type: string - username: - type: string - type: object - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Custom Discovery - /api/v2/targets/{connectUrl}: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Custom Discovery - /api/v2/targets/{connectUrl}/events: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: query - name: q - schema: - type: string - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Events - /api/v2/targets/{connectUrl}/probes: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/targets/{connectUrl}/probes/{probeTemplateName}: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: probeTemplateName - required: true - schema: - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/targets/{connectUrl}/snapshot: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - content: - application/json: {} - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v3/activedownload/{id}: - get: - parameters: - - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v3/discovery: - get: - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/DiscoveryNode' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - /api/v3/discovery_plugins: - get: - parameters: - - in: query - name: realm + - in: query + name: realm schema: type: string responses: @@ -1918,14 +1050,17 @@ paths: - SecurityScheme: [] tags: - Recordings + /api/v3/logout: + post: + responses: + "200": + description: OK + tags: + - Auth /api/v3/probes: get: responses: "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' description: OK tags: - JMC Agent @@ -1987,6 +1122,162 @@ paths: - SecurityScheme: [] tags: - Reports + /api/v3/rules: + get: + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Rules + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Rule' + application/x-www-form-urlencoded: + schema: + properties: + archivalPeriodSeconds: + format: int32 + type: integer + description: + type: string + enabled: + type: boolean + eventSpecifier: + type: string + initialDelaySeconds: + format: int32 + type: integer + matchExpression: + type: string + maxAgeSeconds: + format: int32 + type: integer + maxSizeBytes: + format: int32 + type: integer + name: + type: string + preservedArchives: + format: int32 + type: integer + type: object + multipart/form-data: + schema: + properties: + archivalPeriodSeconds: + format: int32 + type: integer + description: + type: string + enabled: + type: boolean + eventSpecifier: + type: string + initialDelaySeconds: + format: int32 + type: integer + matchExpression: + type: string + maxAgeSeconds: + format: int32 + type: integer + maxSizeBytes: + format: int32 + type: integer + name: + type: string + preservedArchives: + format: int32 + type: integer + type: object + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Rules + /api/v3/rules/{name}: + delete: + parameters: + - in: path + name: name + required: true + schema: + type: string + - in: query + name: clean + schema: + type: boolean + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Rules + get: + parameters: + - in: path + name: name + required: true + schema: + type: string + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Rules + patch: + parameters: + - in: path + name: name + required: true + schema: + type: string + - in: query + name: clean + schema: + type: boolean + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JsonObject' + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Rules /api/v3/targets: get: responses: @@ -2006,6 +1297,58 @@ paths: - SecurityScheme: [] tags: - Targets + post: + parameters: + - in: query + name: dryrun + schema: + type: boolean + - in: query + name: storeCredentials + schema: + type: boolean + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Target' + application/x-www-form-urlencoded: + schema: + properties: + alias: + type: string + connectUrl: + format: uri + type: string + password: + type: string + username: + type: string + type: object + multipart/form-data: + schema: + properties: + alias: + type: string + connectUrl: + format: uri + type: string + password: + type: string + username: + type: string + type: object + responses: + "200": + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Custom Discovery /api/v3/targets/{id}: delete: parameters: @@ -2159,10 +1502,6 @@ paths: type: integer responses: "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' description: OK tags: - JMC Agent From 9a2239433db2e5454d454e84d754780be767c566 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 16 Aug 2024 18:39:57 -0400 Subject: [PATCH 028/140] replace v3 -> v4 --- schema/update.bash | 2 +- .../java/io/cryostat/JsonRequestFilter.java | 6 +-- .../io/cryostat/credentials/Credentials.java | 4 +- .../cryostat/discovery/CustomDiscovery.java | 14 +++--- .../java/io/cryostat/discovery/Discovery.java | 14 +++--- .../discovery/DiscoveryJwtFactory.java | 2 +- .../io/cryostat/events/EventTemplates.java | 10 ++-- src/main/java/io/cryostat/events/Events.java | 2 +- .../java/io/cryostat/jmcagent/JMCAgent.java | 12 ++--- .../cryostat/recordings/RecordingHelper.java | 8 ++-- .../io/cryostat/recordings/Recordings.java | 24 +++++----- .../java/io/cryostat/reports/Reports.java | 4 +- src/main/java/io/cryostat/rules/Rules.java | 2 +- src/main/java/io/cryostat/security/Auth.java | 4 +- .../java/io/cryostat/security/TrustStore.java | 2 +- .../java/io/cryostat/targets/Targets.java | 4 +- src/main/resources/application.properties | 2 +- src/test/java/itest/CryostatTemplateIT.java | 2 +- .../java/itest/CustomEventTemplateTest.java | 4 +- src/test/java/itest/CustomTargetsTest.java | 18 ++++---- src/test/java/itest/DiscoveryPluginIT.java | 26 +++++------ src/test/java/itest/GraphQLTest.java | 46 +++++++++---------- src/test/java/itest/NoopAuthV2IT.java | 2 +- .../java/itest/RecordingWorkflowTest.java | 14 +++--- src/test/java/itest/ReportGenerationTest.java | 2 +- src/test/java/itest/RulesPostFormIT.java | 10 ++-- src/test/java/itest/RulesPostJsonIT.java | 14 +++--- src/test/java/itest/SnapshotTest.java | 28 +++++------ src/test/java/itest/TargetEventsGetTest.java | 2 +- src/test/java/itest/TargetPostDeleteIT.java | 2 +- .../java/itest/TargetRecordingOptionsIT.java | 2 +- .../java/itest/TargetRecordingPatchTest.java | 6 +-- src/test/java/itest/UploadRecordingTest.java | 6 +-- .../java/itest/bases/StandardSelfTest.java | 12 ++--- .../java/itest/util/http/JvmIdWebRequest.java | 4 +- 35 files changed, 158 insertions(+), 158 deletions(-) diff --git a/schema/update.bash b/schema/update.bash index 967bafc73..ca5bc152f 100755 --- a/schema/update.bash +++ b/schema/update.bash @@ -24,4 +24,4 @@ while true; do fi done wget http://localhost:8181/api -O - | yq -P 'sort_keys(..)' > "${DIR}/openapi.yaml" -wget http://localhost:8181/api/v3/graphql/schema.graphql -O "${DIR}/schema.graphql" +wget http://localhost:8181/api/v4/graphql/schema.graphql -O "${DIR}/schema.graphql" diff --git a/src/main/java/io/cryostat/JsonRequestFilter.java b/src/main/java/io/cryostat/JsonRequestFilter.java index 11465b8b1..0d0496906 100644 --- a/src/main/java/io/cryostat/JsonRequestFilter.java +++ b/src/main/java/io/cryostat/JsonRequestFilter.java @@ -39,11 +39,11 @@ public class JsonRequestFilter implements ContainerRequestFilter { static final Set disallowedFields = Set.of("id"); static final Set allowedPathPatterns = Set.of( - "/api/v3/discovery", - "/api/v3/rules/[\\w]+", + "/api/v4/discovery", + "/api/v4/rules/[\\w]+", "/api/beta/matchExpressions", "/api/v2/graphql", - "/api/v3/graphql"); + "/api/v4/graphql"); private final Map compiledPatterns = new HashMap<>(); @Inject ObjectMapper objectMapper; diff --git a/src/main/java/io/cryostat/credentials/Credentials.java b/src/main/java/io/cryostat/credentials/Credentials.java index 524b34b52..a7c7abfad 100644 --- a/src/main/java/io/cryostat/credentials/Credentials.java +++ b/src/main/java/io/cryostat/credentials/Credentials.java @@ -42,7 +42,7 @@ import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; import org.projectnessie.cel.tools.ScriptException; -@Path("/api/v3/credentials") +@Path("/api/v4/credentials") public class Credentials { @Inject TargetMatcher targetMatcher; @@ -110,7 +110,7 @@ public RestResponse create( credential.username = username; credential.password = password; credential.persist(); - return ResponseBuilder.created(URI.create("/api/v3/credentials/" + credential.id)) + return ResponseBuilder.created(URI.create("/api/v4/credentials/" + credential.id)) .build(); } diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index b6bbb8c8c..ee7d093b9 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -89,7 +89,7 @@ void onStart(@Observes StartupEvent evt) { @Transactional(rollbackOn = {JvmIdException.class}) @POST - @Path("/api/v3/targets") + @Path("/api/v4/targets") @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") public Response create( @@ -112,12 +112,12 @@ public Response create( .build(); } // TODO handle credentials embedded in JSON body - return doV3Create(target, Optional.empty(), dryrun, storeCredentials); + return doV4Create(target, Optional.empty(), dryrun, storeCredentials); } @Transactional @POST - @Path("/api/v3/targets") + @Path("/api/v4/targets") @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED}) @RolesAllowed("write") public Response create( @@ -141,10 +141,10 @@ public Response create( credential.password = password; } - return doV3Create(target, Optional.ofNullable(credential), dryrun, storeCredentials); + return doV4Create(target, Optional.ofNullable(credential), dryrun, storeCredentials); } - Response doV3Create( + Response doV4Create( Target target, Optional credential, boolean dryrun, @@ -225,7 +225,7 @@ Response doV3Create( node.persist(); realm.persist(); - return Response.created(URI.create("/api/v3/targets/" + target.id)) + return Response.created(URI.create("/api/v4/targets/" + target.id)) .entity(Map.of(Response.Status.CREATED, target)) .build(); } catch (Exception e) { @@ -244,7 +244,7 @@ Response doV3Create( @Transactional @DELETE - @Path("/api/v3/targets/{id}") + @Path("/api/v4/targets/{id}") @RolesAllowed("write") public Response delete(@RestPath long id) throws URISyntaxException { Target target = Target.find("id", id).singleResult(); diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index 90977a685..bb2372f39 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -144,14 +144,14 @@ void onStop(@Observes ShutdownEvent evt) throws SchedulerException { } @GET - @Path("/api/v3/discovery") + @Path("/api/v4/discovery") @RolesAllowed("read") public DiscoveryNode get() { return DiscoveryNode.getUniverse(); } @GET - @Path("/api/v3/discovery/{id}") + @Path("/api/v4/discovery/{id}") @RolesAllowed("read") public RestResponse checkRegistration( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) @@ -168,7 +168,7 @@ public RestResponse checkRegistration( @Transactional @POST - @Path("/api/v3/discovery") + @Path("/api/v4/discovery") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") @@ -308,7 +308,7 @@ public Response register(@Context RoutingContext ctx, JsonObject body) @Transactional @POST - @Path("/api/v3/discovery/{id}") + @Path("/api/v4/discovery/{id}") @Consumes(MediaType.APPLICATION_JSON) @PermitAll public Map> publish( @@ -347,7 +347,7 @@ public Map> publish( @Transactional @DELETE - @Path("/api/v3/discovery/{id}") + @Path("/api/v4/discovery/{id}") @PermitAll public Map> deregister( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) @@ -382,7 +382,7 @@ public Map> deregister( } @GET - @Path("/api/v3/discovery_plugins") + @Path("/api/v4/discovery_plugins") @RolesAllowed("read") public Response getPlugins(@RestQuery String realm) throws JsonProcessingException { // TODO filter for the matching realm name within the DB query @@ -399,7 +399,7 @@ public Response getPlugins(@RestQuery String realm) throws JsonProcessingExcepti } @GET - @Path("/api/v3/discovery_plugins/{id}") + @Path("/api/v4/discovery_plugins/{id}") @RolesAllowed("read") public DiscoveryPlugin getPlugin(@RestPath UUID id) throws JsonProcessingException { return DiscoveryPlugin.find("id", id).singleResult(); diff --git a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java index 77c21da41..ffedc557f 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java @@ -55,7 +55,7 @@ public class DiscoveryJwtFactory { public static final String RESOURCE_CLAIM = "resource"; public static final String REALM_CLAIM = "realm"; - static final String DISCOVERY_API_PATH = "/api/v3/discovery/"; + static final String DISCOVERY_API_PATH = "/api/v4/discovery/"; @Inject JWSSigner signer; @Inject JWSVerifier verifier; diff --git a/src/main/java/io/cryostat/events/EventTemplates.java b/src/main/java/io/cryostat/events/EventTemplates.java index 5e63ca5ce..e62a1b23b 100644 --- a/src/main/java/io/cryostat/events/EventTemplates.java +++ b/src/main/java/io/cryostat/events/EventTemplates.java @@ -61,7 +61,7 @@ public class EventTemplates { @Inject Logger logger; @POST - @Path("/api/v3/event_templates") + @Path("/api/v4/event_templates") @RolesAllowed("write") public void postTemplates(@RestForm("template") FileUpload body) throws IOException { if (body == null || body.filePath() == null || !"template".equals(body.name())) { @@ -76,7 +76,7 @@ public void postTemplates(@RestForm("template") FileUpload body) throws IOExcept @DELETE @Blocking - @Path("/api/v3/event_templates/{templateName}") + @Path("/api/v4/event_templates/{templateName}") @RolesAllowed("write") public void deleteTemplates(@RestPath String templateName) { customTemplateService.deleteTemplate(templateName); @@ -84,7 +84,7 @@ public void deleteTemplates(@RestPath String templateName) { @GET @Blocking - @Path("/api/v3/event_templates") + @Path("/api/v4/event_templates") @RolesAllowed("read") public List