From e29d0d14d06b6430bfdf939c35518dd130bf5cdb Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 15 Apr 2024 10:13:50 -0400 Subject: [PATCH 1/5] Revert "build(deps): upgrade quarkus to 3.2.9 (#352)" This reverts commit bbbbf38f7be5c9ca93e448d8f2465e6a2548173b. --- .github/workflows/pr-ci.yaml | 3 +-- .github/workflows/push-ci.yaml | 3 +-- pom.xml | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-ci.yaml b/.github/workflows/pr-ci.yaml index 09e81cfd3..794997f0d 100644 --- a/.github/workflows/pr-ci.yaml +++ b/.github/workflows/pr-ci.yaml @@ -97,8 +97,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - # java: [ '17', '21' ] - java: ['17'] + java: [ '17', '21' ] env: cache-name: cache-yarn name: Build and test Java ${{ matrix.java }} diff --git a/.github/workflows/push-ci.yaml b/.github/workflows/push-ci.yaml index 8ddc5f0ce..060c3787a 100644 --- a/.github/workflows/push-ci.yaml +++ b/.github/workflows/push-ci.yaml @@ -41,8 +41,7 @@ jobs: needs: [get-pom-properties] strategy: matrix: - # java: [ '17', '21' ] - java: ['17'] + java: [ '17', '21' ] env: IMAGE_VERSION: ${{ needs.get-pom-properties.outputs.image-version }} cache-name: cache-yarn diff --git a/pom.xml b/pom.xml index 1e5fdcb9d..587f3b4bb 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.19.7 quarkus-bom io.quarkus.platform - 3.2.9.Final + 3.2.6.Final 2.3.6 4.1.101.Final 3.5.0 From ba17a62ebedb7c18423622e0eecc2319fa34dfcc Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 15 Apr 2024 12:25:38 -0400 Subject: [PATCH 2/5] partially fix up old report generation test --- src/test/java/itest/ReportGenerationTest.java | 367 ++++++++++++++++++ src/test/java/itest/ReportIT.java | 289 -------------- .../java/itest/bases/StandardSelfTest.java | 18 + 3 files changed, 385 insertions(+), 289 deletions(-) create mode 100644 src/test/java/itest/ReportGenerationTest.java delete mode 100644 src/test/java/itest/ReportIT.java diff --git a/src/test/java/itest/ReportGenerationTest.java b/src/test/java/itest/ReportGenerationTest.java new file mode 100644 index 000000000..9970445fc --- /dev/null +++ b/src/test/java/itest/ReportGenerationTest.java @@ -0,0 +1,367 @@ +/* + * 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 java.util.concurrent.TimeUnit; + +import io.cryostat.resources.LocalStackResource; +import io.cryostat.util.HttpMimeType; + +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.JsonObject; +import io.vertx.ext.web.handler.HttpException; +import itest.bases.StandardSelfTest; +import itest.util.ITestCleanupFailedException; +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; + +@QuarkusTest +@QuarkusTestResource(LocalStackResource.class) +public class ReportGenerationTest extends StandardSelfTest { + + static final String TEST_RECORDING_NAME = "someRecording"; + + private String archivedReportRequestURL() { + return String.format("/api/beta/reports/%s", getSelfReferenceConnectUrlEncoded()); + } + + private String recordingRequestURL() { + return String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId()); + } + + private String archiveListRequestURL() { + return String.format("/api/beta/recordings/%s", getSelfReferenceConnectUrlEncoded()); + } + + @Test + void testGetActiveReport() throws Exception { + JsonObject activeRecording = null; + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Get a report for the above recording + CompletableFuture reportResponse = new CompletableFuture<>(); + webClient + .get(activeRecording.getString("reportUrl")) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, reportResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + reportResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = reportResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat( + jsonResponse.getMap(), + Matchers.is(Matchers.aMapWithSize(Matchers.greaterThan(0)))); + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + @Test + @Disabled + void testGetArchivedReport() throws Exception { + JsonObject activeRecording = null; + CompletableFuture saveRecordingResp = new CompletableFuture<>(); + String savedRecordingName = null; + + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Save the recording to archive + webClient + .patch( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "text/plain;charset=UTF-8") + .sendBuffer( + Buffer.buffer("SAVE"), + ar -> { + if (assertRequestStatus(ar, saveRecordingResp)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("text/plain;charset=UTF-8")); + saveRecordingResp.complete(ar.result().bodyAsString()); + } + }); + + savedRecordingName = saveRecordingResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Get a report for the archived recording + CompletableFuture archiveResponse = new CompletableFuture<>(); + webClient + .get(String.format("%s/%s", "/api/v1/reports", savedRecordingName)) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, archiveResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + archiveResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = archiveResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat( + jsonResponse.getMap(), + Matchers.is(Matchers.aMapWithSize(Matchers.greaterThan(0)))); + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + CompletableFuture deleteArchivedRecResp = new CompletableFuture<>(); + webClient + .delete(String.format("%s/%s", archiveListRequestURL(), savedRecordingName)) + .send( + ar -> { + if (assertRequestStatus(ar, deleteArchivedRecResp)) { + MatcherAssert.assertThat( + ar.result().statusCode(), Matchers.equalTo(200)); + deleteArchivedRecResp.complete(ar.result().bodyAsJsonObject()); + } + }); + try { + deleteArchivedRecResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + logger.error( + new ITestCleanupFailedException( + String.format( + "Failed to delete archived recording %s", + savedRecordingName), + e)); + } + } + } + + @Test + @Disabled("TODO query parameter filter is not implemented") + void testGetFilteredActiveReport() throws Exception { + JsonObject activeRecording = null; + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Get a report for the above recording + CompletableFuture reportResponse = new CompletableFuture<>(); + webClient + .get(activeRecording.getString("reportUrl")) + .addQueryParam("filter", "heap") + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, reportResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + reportResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = reportResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat(jsonResponse.getMap(), Matchers.is(Matchers.aMapWithSize(8))); + Assertions.assertTrue(jsonResponse.containsKey("HeapContent")); + Assertions.assertTrue(jsonResponse.containsKey("StringDeduplication")); + Assertions.assertTrue(jsonResponse.containsKey("PrimitiveToObjectConversion")); + Assertions.assertTrue(jsonResponse.containsKey("GcFreedRatio")); + Assertions.assertTrue(jsonResponse.containsKey("HighGc")); + Assertions.assertTrue(jsonResponse.containsKey("HeapDump")); + Assertions.assertTrue(jsonResponse.containsKey("Allocations.class")); + Assertions.assertTrue(jsonResponse.containsKey("LowOnPhysicalMemory")); + for (var obj : jsonResponse.getMap().entrySet()) { + var value = JsonObject.mapFrom(obj.getValue()); + Assertions.assertTrue(value.containsKey("score")); + MatcherAssert.assertThat( + value.getDouble("score"), + Matchers.anyOf( + Matchers.equalTo(-1d), + Matchers.equalTo(-2d), + Matchers.equalTo(-3d), + Matchers.both(Matchers.lessThanOrEqualTo(100d)) + .and(Matchers.greaterThanOrEqualTo(0d)))); + Assertions.assertTrue(value.containsKey("name")); + MatcherAssert.assertThat( + value.getString("name"), Matchers.not(Matchers.emptyOrNullString())); + Assertions.assertTrue(value.containsKey("topic")); + MatcherAssert.assertThat( + value.getString("topic"), Matchers.not(Matchers.emptyOrNullString())); + Assertions.assertTrue(value.containsKey("description")); + } + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("id"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + @Test + void testGetReportThrowsWithNonExistentRecordingName() throws Exception { + CompletableFuture response = new CompletableFuture<>(); + webClient + .get(String.format("%s/%s", archivedReportRequestURL(), TEST_RECORDING_NAME)) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.HTML.mime()) + .send( + ar -> { + assertRequestStatus(ar, response); + }); + ExecutionException ex = + Assertions.assertThrows( + ExecutionException.class, + () -> response.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/ReportIT.java b/src/test/java/itest/ReportIT.java deleted file mode 100644 index a17401ad5..000000000 --- a/src/test/java/itest/ReportIT.java +++ /dev/null @@ -1,289 +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.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import io.cryostat.util.HttpMimeType; - -import io.quarkus.test.junit.QuarkusIntegrationTest; -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.handler.HttpException; -import itest.bases.StandardSelfTest; -import itest.util.ITestCleanupFailedException; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -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 ReportIT extends StandardSelfTest { - - static final String TEST_RECORDING_NAME = "someRecording"; - static final String REPORT_REQ_URL = - String.format("/api/beta/reports/%s", getSelfReferenceConnectUrl()); - static final String RECORDING_REQ_URL = - String.format("/api/v1/targets/%s/recordings", getSelfReferenceConnectUrl()); - static final String ARCHIVE_REQ_URL = - String.format("/api/beta/recordings/%s", getSelfReferenceConnectUrl()); - static final String TEMP_REPORT = "src/test/resources/reportTest.html"; - - @Test - void testGetReportShouldSendFile() throws Exception { - - CompletableFuture saveRecordingResp = new CompletableFuture<>(); - String savedRecordingName = null; - File file = new File(TEMP_REPORT); - - try { - // Create a recording - CompletableFuture postResponse = new CompletableFuture<>(); - MultiMap form = MultiMap.caseInsensitiveMultiMap(); - form.add("recordingName", TEST_RECORDING_NAME); - form.add("duration", "1"); - form.add("events", "template=ALL"); - - webClient - .post(RECORDING_REQ_URL) - .sendForm( - form, - ar -> { - if (assertRequestStatus(ar, postResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(201)); - postResponse.complete(null); - } - }); - - postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Make a webserver request to generate some recording data - CompletableFuture targetGetResponse = new CompletableFuture<>(); - webClient - .get("/api/v1/targets") - .send( - ar -> { - if (assertRequestStatus(ar, targetGetResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.JSON.mime())); - targetGetResponse.complete(ar.result().bodyAsJsonArray()); - } - }); - - targetGetResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Save the recording to archive - webClient - .patch(String.format("%s/%s", RECORDING_REQ_URL, TEST_RECORDING_NAME)) - .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.PLAINTEXT.mime()) - .sendBuffer( - Buffer.buffer("SAVE"), - ar -> { - if (assertRequestStatus(ar, saveRecordingResp)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.PLAINTEXT.mime())); - saveRecordingResp.complete(ar.result().bodyAsString()); - } - }); - - savedRecordingName = saveRecordingResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Get a report for the above recording - CompletableFuture getResponse = new CompletableFuture<>(); - webClient - .get(String.format("%s/%s", REPORT_REQ_URL, savedRecordingName)) - .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.HTML.mime()) - .send( - ar -> { - if (assertRequestStatus(ar, getResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.HTML.mime())); - getResponse.complete(ar.result().bodyAsBuffer()); - } - }); - - try (FileOutputStream fos = new FileOutputStream(file); - BufferedOutputStream bos = new BufferedOutputStream(fos)) { - - byte[] bytes = getResponse.get().getBytes(); - bos.write(bytes); - } - - Document doc = Jsoup.parse(file, "UTF-8"); - - MatcherAssert.assertThat(file.length(), Matchers.greaterThan(0L)); - - 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)); - MatcherAssert.assertThat( - "Expected at least one