From f551ae170dec42c84a0dafcea5731037fe63c1df Mon Sep 17 00:00:00 2001 From: lkonno Date: Tue, 14 Nov 2023 12:51:57 -0300 Subject: [PATCH 01/16] updated the tests and created a localstack resource --- pom.xml | 5 + .../java/io/cryostat/reports/Reports.java | 2 +- .../resources/LocalStackResource.java | 69 +++++++++++ src/test/java/itest/NonExistentTargetIT.java | 2 - src/test/java/itest/NoopAuthV2IT.java | 40 ++++--- src/test/java/itest/NotificationsUrlIT.java | 74 ++++++------ ...flowIT.java => RecordingWorkflowTest.java} | 110 ++++++++---------- .../java/itest/bases/StandardSelfTest.java | 5 + src/test/java/itest/util/Utils.java | 34 ++++++ 9 files changed, 222 insertions(+), 119 deletions(-) create mode 100644 src/test/java/io/cryostat/resources/LocalStackResource.java rename src/test/java/itest/{RecordingWorkflowIT.java => RecordingWorkflowTest.java} (79%) diff --git a/pom.xml b/pom.xml index d6202c5c8..798a8bf4a 100644 --- a/pom.xml +++ b/pom.xml @@ -251,6 +251,11 @@ junit-jupiter test + + org.testcontainers + localstack + test + diff --git a/src/main/java/io/cryostat/reports/Reports.java b/src/main/java/io/cryostat/reports/Reports.java index 49eaab0b5..b8c0ad264 100644 --- a/src/main/java/io/cryostat/reports/Reports.java +++ b/src/main/java/io/cryostat/reports/Reports.java @@ -146,7 +146,7 @@ public Response getActiveV1(@RestPath String targetId, @RestPath String recordin @CacheResult(cacheName = ACTIVE_CACHE) @GET @Path("/api/v3/targets/{targetId}/reports/{recordingId}") - @Produces(MediaType.APPLICATION_JSON) + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN}) @RolesAllowed("read") @Deprecated(since = "3.0", forRemoval = true) public Uni> getActive( diff --git a/src/test/java/io/cryostat/resources/LocalStackResource.java b/src/test/java/io/cryostat/resources/LocalStackResource.java new file mode 100644 index 000000000..3bfd7a821 --- /dev/null +++ b/src/test/java/io/cryostat/resources/LocalStackResource.java @@ -0,0 +1,69 @@ +/* + * 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.resources; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import io.quarkus.test.common.DevServicesContext; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.containers.localstack.LocalStackContainer.Service; +import org.testcontainers.utility.DockerImageName; + +public class LocalStackResource + implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { + + private static String IMAGE_NAME = "localstack/localstack:2.1.0"; + private static final LocalStackContainer LOCAL_STACK_CONTAINER; + private Optional containerNetworkId; + + static { + DockerImageName localstackImage = DockerImageName.parse(IMAGE_NAME); + LOCAL_STACK_CONTAINER = new LocalStackContainer(localstackImage).withServices(Service.S3); + } + + @Override + public Map start() { + LOCAL_STACK_CONTAINER.start(); + System.setProperty("aws.accessKeyId", LOCAL_STACK_CONTAINER.getAccessKey()); + System.setProperty("aws.secretAccessKey", LOCAL_STACK_CONTAINER.getSecretKey()); + Map properties = new HashMap(); + properties.put("quarkus.ses.aws.region", "us-east-1"); + properties.put( + "s3.url.override", + LOCAL_STACK_CONTAINER.getEndpointOverride(Service.S3).toString()); + properties.put("quarkus.ses.aws.credentials.type", "static"); + properties.put("aws.accessKeyId", LOCAL_STACK_CONTAINER.getAccessKey()); + properties.put("aws.secretAccessKey", LOCAL_STACK_CONTAINER.getSecretKey()); + + containerNetworkId.ifPresent(LOCAL_STACK_CONTAINER::withNetworkMode); + + return properties; + } + + @Override + public void stop() { + LOCAL_STACK_CONTAINER.stop(); + LOCAL_STACK_CONTAINER.close(); + } + + @Override + public void setIntegrationTestContext(DevServicesContext context) { + containerNetworkId = context.containerNetworkId(); + } +} diff --git a/src/test/java/itest/NonExistentTargetIT.java b/src/test/java/itest/NonExistentTargetIT.java index 05f34f140..cf43d91c2 100644 --- a/src/test/java/itest/NonExistentTargetIT.java +++ b/src/test/java/itest/NonExistentTargetIT.java @@ -26,11 +26,9 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest -@Disabled("TODO") public class NonExistentTargetIT extends StandardSelfTest { static final String BAD_TARGET_CONNECT_URL = diff --git a/src/test/java/itest/NoopAuthV2IT.java b/src/test/java/itest/NoopAuthV2IT.java index 770d8830a..228b9812b 100644 --- a/src/test/java/itest/NoopAuthV2IT.java +++ b/src/test/java/itest/NoopAuthV2IT.java @@ -27,11 +27,9 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest -@Disabled("TODO") public class NoopAuthV2IT extends StandardSelfTest { HttpRequest req; @@ -44,23 +42,27 @@ void createRequest() { @Test public void shouldRespond200() throws Exception { CompletableFuture future = new CompletableFuture<>(); - req.send( - ar -> { - if (ar.succeeded()) { - future.complete(ar.result().bodyAsJsonObject()); - } else { - future.completeExceptionally(ar.cause()); - } - }); - JsonObject expected = - new JsonObject( - Map.of( - "meta", - Map.of( - "status", "OK", - "type", "application/json"), - "data", Map.of("result", Map.of("username", "")))); + req.basicAuthentication("user", "pass") + .send( + ar -> { + if (ar.succeeded()) { + future.complete(ar.result().bodyAsJsonObject()); + } else { + future.completeExceptionally(ar.cause()); + } + }); + + JsonObject response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + MatcherAssert.assertThat(response.getJsonObject("meta"), Matchers.notNullValue()); + MatcherAssert.assertThat( + response.getJsonObject("meta").getString("status"), Matchers.equalTo("OK")); + MatcherAssert.assertThat( + response.getJsonObject("meta").getString("type"), + Matchers.equalTo("application/json")); + MatcherAssert.assertThat(response.getJsonObject("data"), Matchers.notNullValue()); MatcherAssert.assertThat( - future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(expected)); + response.getJsonObject("data").getString("result"), + Matchers.equalTo(Map.of("username", "user").toString())); } } diff --git a/src/test/java/itest/NotificationsUrlIT.java b/src/test/java/itest/NotificationsUrlIT.java index c9a60889c..ad41e2cb8 100644 --- a/src/test/java/itest/NotificationsUrlIT.java +++ b/src/test/java/itest/NotificationsUrlIT.java @@ -26,11 +26,9 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest -@Disabled("TODO") public class NotificationsUrlIT extends StandardSelfTest { HttpRequest req; @@ -43,14 +41,15 @@ void createRequest() { @Test public void shouldSucceed() throws Exception { CompletableFuture future = new CompletableFuture<>(); - req.send( - ar -> { - if (ar.succeeded()) { - future.complete(ar.result().statusCode()); - } else { - future.completeExceptionally(ar.cause()); - } - }); + req.basicAuthentication("user", "pass") + .send( + ar -> { + if (ar.succeeded()) { + future.complete(ar.result().statusCode()); + } else { + future.completeExceptionally(ar.cause()); + } + }); MatcherAssert.assertThat( future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo(200)); } @@ -58,14 +57,15 @@ public void shouldSucceed() throws Exception { @Test public void shouldReturnOK() throws Exception { CompletableFuture future = new CompletableFuture<>(); - req.send( - ar -> { - if (ar.succeeded()) { - future.complete(ar.result().statusMessage()); - } else { - future.completeExceptionally(ar.cause()); - } - }); + req.basicAuthentication("user", "pass") + .send( + ar -> { + if (ar.succeeded()) { + future.complete(ar.result().statusMessage()); + } else { + future.completeExceptionally(ar.cause()); + } + }); MatcherAssert.assertThat( future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo("OK")); } @@ -73,35 +73,37 @@ public void shouldReturnOK() throws Exception { @Test public void shouldReturnContentTypeJson() throws Exception { CompletableFuture future = new CompletableFuture<>(); - req.send( - ar -> { - if (ar.succeeded()) { - future.complete(ar.result().getHeader("Content-Type")); - } else { - future.completeExceptionally(ar.cause()); - } - }); + req.basicAuthentication("user", "pass") + .send( + ar -> { + if (ar.succeeded()) { + future.complete(ar.result().getHeader("Content-Type")); + } else { + future.completeExceptionally(ar.cause()); + } + }); MatcherAssert.assertThat( future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), - Matchers.equalTo("application/json")); + Matchers.equalTo("application/json;charset=UTF-8")); } @Test public void shouldReturnJsonMessage() throws Exception { CompletableFuture future = new CompletableFuture<>(); - req.send( - ar -> { - if (ar.succeeded()) { - future.complete(ar.result().bodyAsString()); - } else { - future.completeExceptionally(ar.cause()); - } - }); + req.basicAuthentication("user", "pass") + .send( + ar -> { + if (ar.succeeded()) { + future.complete(ar.result().bodyAsString()); + } else { + future.completeExceptionally(ar.cause()); + } + }); MatcherAssert.assertThat( future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS), Matchers.equalTo( String.format( "{\"notificationsUrl\":\"ws://%s:%d/api/v1/notifications\"}", - Utils.WEB_HOST, Utils.WEB_PORT))); + "0.0.0.0", Utils.WEB_PORT))); } } diff --git a/src/test/java/itest/RecordingWorkflowIT.java b/src/test/java/itest/RecordingWorkflowTest.java similarity index 79% rename from src/test/java/itest/RecordingWorkflowIT.java rename to src/test/java/itest/RecordingWorkflowTest.java index 53804775e..3feb0a114 100644 --- a/src/test/java/itest/RecordingWorkflowIT.java +++ b/src/test/java/itest/RecordingWorkflowTest.java @@ -23,11 +23,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import io.cryostat.resources.LocalStackResource; import io.cryostat.util.HttpMimeType; -import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.quarkus.test.common.QuarkusTestResource; +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; @@ -37,19 +38,21 @@ import jdk.jfr.consumer.RecordingFile; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.jboss.logging.Logger; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -@QuarkusIntegrationTest -@Disabled("TODO") -public class RecordingWorkflowIT extends StandardSelfTest { +@QuarkusTest +@QuarkusTestResource(LocalStackResource.class) +public class RecordingWorkflowTest extends StandardSelfTest { static final String TEST_RECORDING_NAME = "workflow_itest"; - static final String TARGET_ALIAS = "io-cryostat-Cryostat"; + static final String TARGET_ALIAS = "selftest"; + + public static final Logger logger = Logger.getLogger(RecordingWorkflowTest.class); @Test public void testWorkflow() throws Exception { @@ -61,6 +64,7 @@ public void testWorkflow() throws Exception { "/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded())) .basicAuthentication("user", "pass") + .followRedirects(true) .send( ar -> { if (assertRequestStatus(ar, listRespFuture1)) { @@ -72,25 +76,19 @@ public void testWorkflow() throws Exception { try { // create an in-memory recording - CompletableFuture dumpRespFuture = new CompletableFuture<>(); MultiMap form = MultiMap.caseInsensitiveMultiMap(); form.add("recordingName", TEST_RECORDING_NAME); form.add("duration", "5"); form.add("events", "template=ALL"); webClient + .extensions() .post( String.format( "/api/v1/targets/%s/recordings", - getSelfReferenceConnectUrlEncoded())) - .basicAuthentication("user", "pass") - .sendForm( + getSelfReferenceConnectUrlEncoded()), + true, form, - ar -> { - if (assertRequestStatus(ar, dumpRespFuture)) { - dumpRespFuture.complete(null); - } - }); - dumpRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + REQUEST_TIMEOUT_SECONDS); // verify in-memory recording created CompletableFuture listRespFuture2 = new CompletableFuture<>(); @@ -99,6 +97,7 @@ public void testWorkflow() throws Exception { String.format( "/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded())) + .followRedirects(true) .basicAuthentication("user", "pass") .send( ar -> { @@ -120,22 +119,18 @@ public void testWorkflow() throws Exception { Thread.sleep(2_000L); // wait some time to save a portion of the recording // save a copy of the partial recording dump - CompletableFuture saveRespFuture = new CompletableFuture<>(); + MultiMap saveHeaders = MultiMap.caseInsensitiveMultiMap(); + saveHeaders.add(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.PLAINTEXT.mime()); webClient + .extensions() .patch( String.format( "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME)) - .basicAuthentication("user", "pass") - .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.PLAINTEXT.mime()) - .sendBuffer( - Buffer.buffer("SAVE"), - ar -> { - if (assertRequestStatus(ar, saveRespFuture)) { - saveRespFuture.complete(null); - } - }); - saveRespFuture.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME), + true, + saveHeaders, + "SAVE", + REQUEST_TIMEOUT_SECONDS); // check that the in-memory recording list hasn't changed CompletableFuture listRespFuture3 = new CompletableFuture<>(); @@ -144,6 +139,7 @@ public void testWorkflow() throws Exception { String.format( "/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded())) + .followRedirects(true) .basicAuthentication("user", "pass") .send( ar -> { @@ -167,6 +163,7 @@ public void testWorkflow() throws Exception { webClient .get("/api/v1/recordings") .basicAuthentication("user", "pass") + .followRedirects(true) .send( ar -> { if (assertRequestStatus(ar, listRespFuture4)) { @@ -195,6 +192,7 @@ public void testWorkflow() throws Exception { String.format( "/api/v1/targets/%s/recordings", getSelfReferenceConnectUrlEncoded())) + .followRedirects(true) .basicAuthentication("user", "pass") .send( ar -> { @@ -218,10 +216,11 @@ public void testWorkflow() throws Exception { // the fully completed in-memory recording is larger than the saved partial copy String inMemoryDownloadUrl = recordingInfo.getString("downloadUrl"); Path inMemoryDownloadPath = - downloadFileAbs(inMemoryDownloadUrl, TEST_RECORDING_NAME, ".jfr") + downloadFile(inMemoryDownloadUrl, TEST_RECORDING_NAME, ".jfr") .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + Path savedDownloadPath = - downloadFileAbs(savedDownloadUrl, TEST_RECORDING_NAME + "_saved", ".jfr") + downloadFile(savedDownloadUrl, TEST_RECORDING_NAME + "_saved", ".jfr") .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); MatcherAssert.assertThat( inMemoryDownloadPath.toFile().length(), Matchers.greaterThan(0L)); @@ -236,8 +235,9 @@ public void testWorkflow() throws Exception { String reportUrl = recordingInfo.getString("reportUrl"); MultiMap headers = MultiMap.caseInsensitiveMultiMap(); headers.add(HttpHeaders.ACCEPT.toString(), HttpMimeType.HTML.mime()); + Path reportPath = - downloadFileAbs(reportUrl, TEST_RECORDING_NAME + "_report", ".html", headers) + downloadFile(reportUrl, TEST_RECORDING_NAME + "_report", ".html", headers) .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); File reportFile = reportPath.toFile(); MatcherAssert.assertThat(reportFile.length(), Matchers.greaterThan(0L)); @@ -258,22 +258,16 @@ public void testWorkflow() throws Exception { } finally { // Clean up what we created - CompletableFuture deleteRespFuture1 = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME)) - .basicAuthentication("user", "pass") - .send( - ar -> { - if (assertRequestStatus(ar, deleteRespFuture1)) { - deleteRespFuture1.complete(null); - } - }); - try { - deleteRespFuture1.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + webClient + .extensions() + .delete( + String.format( + "/api/v1/targets/%s/recordings/%s", + getSelfReferenceConnectUrlEncoded(), TEST_RECORDING_NAME), + true, + REQUEST_TIMEOUT_SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { logger.error( new ITestCleanupFailedException( @@ -308,21 +302,15 @@ public void testWorkflow() throws Exception { String recordingName = ((JsonObject) savedRecording).getString("name"); if (recordingName.matches( TARGET_ALIAS + "_" + TEST_RECORDING_NAME + "_[\\d]{8}T[\\d]{6}Z.jfr")) { - CompletableFuture deleteRespFuture2 = new CompletableFuture<>(); - webClient - .delete( - String.format( - "/api/beta/recordings/%s/%s", - getSelfReferenceConnectUrlEncoded(), recordingName)) - .basicAuthentication("user", "pass") - .send( - ar -> { - if (assertRequestStatus(ar, deleteRespFuture2)) { - deleteRespFuture2.complete(null); - } - }); try { - deleteRespFuture2.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + webClient + .extensions() + .delete( + String.format( + "/api/beta/recordings/%s/%s", + getSelfReferenceConnectUrlEncoded(), recordingName), + true, + REQUEST_TIMEOUT_SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { logger.error( new ITestCleanupFailedException( diff --git a/src/test/java/itest/bases/StandardSelfTest.java b/src/test/java/itest/bases/StandardSelfTest.java index 54e95f6fa..e39e6976d 100644 --- a/src/test/java/itest/bases/StandardSelfTest.java +++ b/src/test/java/itest/bases/StandardSelfTest.java @@ -386,6 +386,11 @@ public static CompletableFuture downloadFile(String url, String name, Stri webClient.get(url), name, suffix, MultiMap.caseInsensitiveMultiMap()); } + public static CompletableFuture downloadFile( + String url, String name, String suffix, MultiMap headers) { + return fireDownloadRequest(webClient.get(url), name, suffix, headers); + } + public static CompletableFuture downloadFileAbs(String url, String name, String suffix) { return fireDownloadRequest( webClient.getAbs(url), name, suffix, MultiMap.caseInsensitiveMultiMap()); diff --git a/src/test/java/itest/util/Utils.java b/src/test/java/itest/util/Utils.java index 2e120df1b..2f473945c 100644 --- a/src/test/java/itest/util/Utils.java +++ b/src/test/java/itest/util/Utils.java @@ -88,6 +88,10 @@ HttpResponse post(String url, boolean authentication, MultiMap form, int HttpResponse delete(String url, boolean authentication, int timeout) throws InterruptedException, ExecutionException, TimeoutException; + + HttpResponse patch( + String url, boolean authentication, MultiMap headers, String action, int timeout) + throws InterruptedException, ExecutionException, TimeoutException; } public static class TestWebClient extends WebClientBase { @@ -160,6 +164,36 @@ public HttpResponse delete(String url, boolean authentication, int timeo } return future.get(timeout, TimeUnit.SECONDS); } + + public HttpResponse patch( + String url, + boolean authentication, + MultiMap headers, + String action, + int timeout) + throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture> future = new CompletableFuture<>(); + RequestOptions options = new RequestOptions().setURI(url); + HttpRequest req = TestWebClient.this.request(HttpMethod.PATCH, options); + if (authentication) { + req.basicAuthentication("user", "pass"); + } + req.putHeaders(headers) + .sendBuffer( + Buffer.buffer(action), + ar -> { + if (ar.succeeded()) { + future.complete(ar.result()); + } else { + future.completeExceptionally(ar.cause()); + } + }); + if (future.get().statusCode() == 308) { + return patch( + future.get().getHeader("Location"), true, headers, action, timeout); + } + return future.get(timeout, TimeUnit.SECONDS); + } } } } From d2004374f3e1166e0de74d7589076d496154b454 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 14 Nov 2023 12:35:53 -0500 Subject: [PATCH 02/16] refactor localstack test resource to replace devservice --- pom.xml | 7 +-- .../resources/application-test.properties | 13 +--- .../cryostat/resources/GrafanaResource.java | 6 +- .../resources/JFRDatasourceResource.java | 6 +- .../resources/LocalStackResource.java | 59 +++++++++++-------- 5 files changed, 45 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index 798a8bf4a..7075f169d 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ 1.18.1 4.3 3.2.2 - 5 + 3 ${surefire-plugin.version} ${surefire.rerunFailingTestsCount} 2.10.1 @@ -251,11 +251,6 @@ junit-jupiter test - - org.testcontainers - localstack - test - diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index e98288880..9753b8dcb 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -18,15 +18,6 @@ quarkus.datasource.devservices.container-env.POSTGRESQL_DATABASE=quarkus quarkus.datasource.devservices.username=quarkus quarkus.datasource.devservices.password=quarkus quarkus.datasource.devservices.db-name=quarkus -# !!! -quarkus.s3.devservices.enabled=true -quarkus.s3.devservices.buckets=archivedrecordings -# FIXME the following overrides should not be required, but currently seem to help with testcontainers reliability -quarkus.aws.devservices.localstack.image-name=localstack/localstack:2.1.0 -quarkus.aws.devservices.localstack.container-properties.START_WEB=0 -quarkus.aws.devservices.localstack.container-properties.SERVICES=s3 -quarkus.aws.devservices.localstack.container-properties.EAGER_SERVICE_LOADING=1 -quarkus.aws.devservices.localstack.container-properties.SKIP_SSL_CERT_DOWNLOAD=1 -quarkus.aws.devservices.localstack.container-properties.SKIP_INFRA_DOWNLOADS=1 -quarkus.aws.devservices.localstack.container-properties.DISABLE_EVENTS=1 +quarkus.s3.devservices.enabled=false +# !!! diff --git a/src/test/java/io/cryostat/resources/GrafanaResource.java b/src/test/java/io/cryostat/resources/GrafanaResource.java index 8cd5a5bc3..5dcdd0c16 100644 --- a/src/test/java/io/cryostat/resources/GrafanaResource.java +++ b/src/test/java/io/cryostat/resources/GrafanaResource.java @@ -27,9 +27,9 @@ public class GrafanaResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static int GRAFANA_PORT = 3000; - private static String IMAGE_NAME = "quay.io/cryostat/cryostat-grafana-dashboard:latest"; - private static Map envMap = + private static final int GRAFANA_PORT = 3000; + private static final String IMAGE_NAME = "quay.io/cryostat/cryostat-grafana-dashboard:latest"; + private static final Map envMap = Map.of( "GF_INSTALL_PLUGINS", "grafana-simple-json-datasource", "GF_AUTH_ANONYMOUS_ENABLED", "true", diff --git a/src/test/java/io/cryostat/resources/JFRDatasourceResource.java b/src/test/java/io/cryostat/resources/JFRDatasourceResource.java index 8f3e9a59f..94b7089cb 100644 --- a/src/test/java/io/cryostat/resources/JFRDatasourceResource.java +++ b/src/test/java/io/cryostat/resources/JFRDatasourceResource.java @@ -27,9 +27,9 @@ public class JFRDatasourceResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static int JFR_DATASOURCE_PORT = 8080; - private static String IMAGE_NAME = "quay.io/cryostat/jfr-datasource:latest"; - private static Map envMap = Map.of(); + private static final int JFR_DATASOURCE_PORT = 8080; + private static final String IMAGE_NAME = "quay.io/cryostat/jfr-datasource:latest"; + private static final Map envMap = Map.of(); private Optional containerNetworkId; private GenericContainer container; diff --git a/src/test/java/io/cryostat/resources/LocalStackResource.java b/src/test/java/io/cryostat/resources/LocalStackResource.java index 3bfd7a821..4665e0139 100644 --- a/src/test/java/io/cryostat/resources/LocalStackResource.java +++ b/src/test/java/io/cryostat/resources/LocalStackResource.java @@ -21,45 +21,58 @@ import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; -import org.testcontainers.containers.localstack.LocalStackContainer; -import org.testcontainers.containers.localstack.LocalStackContainer.Service; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; public class LocalStackResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static String IMAGE_NAME = "localstack/localstack:2.1.0"; - private static final LocalStackContainer LOCAL_STACK_CONTAINER; + private static int S3_PORT = 4566; + private static final String IMAGE_NAME = "docker.io/localstack/localstack:latest"; + private static final Map envMap = + Map.of( + "START_WEB", "0", + "SERVICES", "s3", + "EAGER_SERVICE_LOADING", "1", + "SKIP_SSL_CERT_DOWNLOAD", "1", + "SKIP_INFRA_DOWNLOADS", "1", + "DISABLE_EVENTS", "1"); private Optional containerNetworkId; - - static { - DockerImageName localstackImage = DockerImageName.parse(IMAGE_NAME); - LOCAL_STACK_CONTAINER = new LocalStackContainer(localstackImage).withServices(Service.S3); - } + private GenericContainer container; @Override public Map start() { - LOCAL_STACK_CONTAINER.start(); - System.setProperty("aws.accessKeyId", LOCAL_STACK_CONTAINER.getAccessKey()); - System.setProperty("aws.secretAccessKey", LOCAL_STACK_CONTAINER.getSecretKey()); - Map properties = new HashMap(); - properties.put("quarkus.ses.aws.region", "us-east-1"); - properties.put( - "s3.url.override", - LOCAL_STACK_CONTAINER.getEndpointOverride(Service.S3).toString()); - properties.put("quarkus.ses.aws.credentials.type", "static"); - properties.put("aws.accessKeyId", LOCAL_STACK_CONTAINER.getAccessKey()); - properties.put("aws.secretAccessKey", LOCAL_STACK_CONTAINER.getSecretKey()); + container = + new GenericContainer<>(DockerImageName.parse(IMAGE_NAME)) + .withExposedPorts(S3_PORT) + .withEnv(envMap) + .waitingFor(Wait.forHealthcheck()); + containerNetworkId.ifPresent(container::withNetworkMode); - containerNetworkId.ifPresent(LOCAL_STACK_CONTAINER::withNetworkMode); + container.start(); + + String networkHostPort = + "http://" + container.getHost() + ":" + container.getMappedPort(S3_PORT); + + Map properties = new HashMap(); + properties.put("quarkus.s3.aws.region", "us-east-1"); + properties.put("s3.url.override", networkHostPort); + properties.put("quarkus.s3.endpoint-override", properties.get("s3.url.override")); + properties.put("quarkus.s3.aws.region", "us-east-1"); + properties.put("quarkus.s3.aws.credentials.type", "static"); + properties.put("quarkus.s3.aws.credentials.static-provider.access-key-id", "unused"); + properties.put("quarkus.s3.aws.credentials.static-provider.secret-access-key", "unused"); + properties.put("aws.access-key-id", "unused"); + properties.put("aws.secret-access-key", "unused"); return properties; } @Override public void stop() { - LOCAL_STACK_CONTAINER.stop(); - LOCAL_STACK_CONTAINER.close(); + container.stop(); + container.close(); } @Override From 2145566c3c6ba3c0ed3aa7062c5e60127234926f Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 14 Nov 2023 12:51:58 -0500 Subject: [PATCH 03/16] null safety --- src/test/java/io/cryostat/resources/GrafanaResource.java | 6 ++++-- .../java/io/cryostat/resources/JFRDatasourceResource.java | 6 ++++-- src/test/java/io/cryostat/resources/LocalStackResource.java | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/cryostat/resources/GrafanaResource.java b/src/test/java/io/cryostat/resources/GrafanaResource.java index 5dcdd0c16..fb8bf2d44 100644 --- a/src/test/java/io/cryostat/resources/GrafanaResource.java +++ b/src/test/java/io/cryostat/resources/GrafanaResource.java @@ -59,8 +59,10 @@ public Map start() { @Override public void stop() { - container.stop(); - container.close(); + if (container != null) { + container.stop(); + container.close(); + } } @Override diff --git a/src/test/java/io/cryostat/resources/JFRDatasourceResource.java b/src/test/java/io/cryostat/resources/JFRDatasourceResource.java index 94b7089cb..0b33e9bd0 100644 --- a/src/test/java/io/cryostat/resources/JFRDatasourceResource.java +++ b/src/test/java/io/cryostat/resources/JFRDatasourceResource.java @@ -56,8 +56,10 @@ public Map start() { @Override public void stop() { - container.stop(); - container.close(); + if (container != null) { + container.stop(); + container.close(); + } } @Override diff --git a/src/test/java/io/cryostat/resources/LocalStackResource.java b/src/test/java/io/cryostat/resources/LocalStackResource.java index 4665e0139..5850acdce 100644 --- a/src/test/java/io/cryostat/resources/LocalStackResource.java +++ b/src/test/java/io/cryostat/resources/LocalStackResource.java @@ -71,8 +71,10 @@ public Map start() { @Override public void stop() { - container.stop(); - container.close(); + if (container != null) { + container.stop(); + container.close(); + } } @Override From 1bc1fd708de1051e7615368dad7352dda0aed7a1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 14 Nov 2023 13:14:54 -0500 Subject: [PATCH 04/16] remove deprecated env var --- src/test/java/io/cryostat/resources/LocalStackResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/cryostat/resources/LocalStackResource.java b/src/test/java/io/cryostat/resources/LocalStackResource.java index 5850acdce..777e4d793 100644 --- a/src/test/java/io/cryostat/resources/LocalStackResource.java +++ b/src/test/java/io/cryostat/resources/LocalStackResource.java @@ -36,7 +36,6 @@ public class LocalStackResource "SERVICES", "s3", "EAGER_SERVICE_LOADING", "1", "SKIP_SSL_CERT_DOWNLOAD", "1", - "SKIP_INFRA_DOWNLOADS", "1", "DISABLE_EVENTS", "1"); private Optional containerNetworkId; private GenericContainer container; From 21bedb19b4b97676b550fda640e9af226eacd1af Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 14 Nov 2023 13:15:24 -0500 Subject: [PATCH 05/16] use path style access since test setup does not support dns lookup of s3 buckets as subdomains --- src/test/java/io/cryostat/resources/LocalStackResource.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/io/cryostat/resources/LocalStackResource.java b/src/test/java/io/cryostat/resources/LocalStackResource.java index 777e4d793..d64c69922 100644 --- a/src/test/java/io/cryostat/resources/LocalStackResource.java +++ b/src/test/java/io/cryostat/resources/LocalStackResource.java @@ -58,6 +58,7 @@ public Map start() { properties.put("quarkus.s3.aws.region", "us-east-1"); properties.put("s3.url.override", networkHostPort); properties.put("quarkus.s3.endpoint-override", properties.get("s3.url.override")); + properties.put("quarkus.s3.path-style-access", "true"); properties.put("quarkus.s3.aws.region", "us-east-1"); properties.put("quarkus.s3.aws.credentials.type", "static"); properties.put("quarkus.s3.aws.credentials.static-provider.access-key-id", "unused"); From 82e1e4e7dd6692feafc3f5daef6f16824e59c45c Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 14 Nov 2023 14:50:23 -0500 Subject: [PATCH 06/16] update test definition for report expectations to match latest in original repo --- .../java/itest/RecordingWorkflowTest.java | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/test/java/itest/RecordingWorkflowTest.java b/src/test/java/itest/RecordingWorkflowTest.java index 3feb0a114..51c8b40a7 100644 --- a/src/test/java/itest/RecordingWorkflowTest.java +++ b/src/test/java/itest/RecordingWorkflowTest.java @@ -18,6 +18,7 @@ import java.io.File; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -26,6 +27,7 @@ import io.cryostat.resources.LocalStackResource; import io.cryostat.util.HttpMimeType; +import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.vertx.core.MultiMap; @@ -39,9 +41,6 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.jboss.logging.Logger; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -241,21 +240,12 @@ public void testWorkflow() throws Exception { .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); File reportFile = reportPath.toFile(); MatcherAssert.assertThat(reportFile.length(), Matchers.greaterThan(0L)); - Document doc = Jsoup.parse(reportFile, "UTF-8"); - Elements head = doc.getElementsByTag("head"); - Elements titles = head.first().getElementsByTag("title"); - Elements body = doc.getElementsByTag("body"); - Elements script = head.first().getElementsByTag("script"); - - MatcherAssert.assertThat("Expected one ", head.size(), Matchers.equalTo(1)); - MatcherAssert.assertThat(titles.size(), Matchers.equalTo(1)); - MatcherAssert.assertThat("Expected one ", body.size(), Matchers.equalTo(1)); + ObjectMapper mapper = new ObjectMapper(); + Map response = mapper.readValue(reportFile, Map.class); + MatcherAssert.assertThat(response, Matchers.notNullValue()); MatcherAssert.assertThat( - "Expected at least one