diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index 5f1c44417..59f51b86f 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -76,7 +76,7 @@ void onStart(@Observes StartupEvent evt) { @Transactional(rollbackOn = {JvmIdException.class}) @POST - @Path("v2/targets") + @Path("/api/v2/targets") @Consumes("application/json") @RolesAllowed("write") public Response create(Target target, @RestQuery boolean dryrun) { @@ -114,7 +114,7 @@ public Response create(Target target, @RestQuery boolean dryrun) { node.persist(); realm.persist(); - return Response.created(URI.create("v3/targets/" + target.id)).build(); + return Response.created(URI.create("/api/v3/targets/" + target.id)).build(); } catch (Exception e) { if (ExceptionUtils.indexOfType(e, ConstraintViolationException.class) >= 0) { logger.warn("Invalid target definition", e); @@ -127,7 +127,7 @@ public Response create(Target target, @RestQuery boolean dryrun) { @Transactional @POST - @Path("v2/targets") + @Path("/api/v2/targets") @Consumes("multipart/form-data") @RolesAllowed("write") public Response create( @@ -141,7 +141,7 @@ public Response create( @Transactional @DELETE - @Path("v2/targets/{connectUrl}") + @Path("/api/v2/targets/{connectUrl}") @RolesAllowed("write") public Response delete(@RestPath URI connectUrl) throws URISyntaxException { Target target = Target.getTargetByConnectUrl(connectUrl); @@ -152,7 +152,7 @@ public Response delete(@RestPath URI connectUrl) throws URISyntaxException { @Transactional @DELETE - @Path("v3/targets/{id}") + @Path("/api/v3/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/recordings/Recordings.java b/src/main/java/io/cryostat/recordings/Recordings.java index 62453e052..feae4cf60 100644 --- a/src/main/java/io/cryostat/recordings/Recordings.java +++ b/src/main/java/io/cryostat/recordings/Recordings.java @@ -682,7 +682,7 @@ public void deleteArchivedRecording(@RestPath String encodedJvmId, @RestPath Str .map(c -> c.toString()) .orElseGet(() -> metadata.labels.computeIfAbsent("connectUrl", k -> jvmId)); logger.infov( - "Archived recording from connectUrl {1} has metadata: {1}", connectUrl, metadata); + "Archived recording from connectUrl {0} has metadata: {1}", connectUrl, metadata); logger.infov( "Sending S3 deletion request for {0} {1}", archiveBucket, recordingHelper.archivedRecordingKey(jvmId, filename)); diff --git a/src/main/java/io/cryostat/rules/RuleService.java b/src/main/java/io/cryostat/rules/RuleService.java index 1601d5998..2de95134c 100644 --- a/src/main/java/io/cryostat/rules/RuleService.java +++ b/src/main/java/io/cryostat/rules/RuleService.java @@ -250,8 +250,8 @@ private void scheduleArchival(Rule rule, Target target, ActiveRecording recordin quartz.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { logger.infov( - "Failed to schedule archival job for rule {} in target {}" + rule.name, - target.alias); + "Failed to schedule archival job for rule {0} in target {1}", + rule.name, target.alias); logger.error(e); } jobs.add(jobDetail.getKey()); diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index ac558246d..e98288880 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -5,9 +5,6 @@ cryostat.discovery.podman.enabled=true cryostat.discovery.docker.enabled=true cryostat.auth.disabled=true -grafana-dashboard.url=http://grafana:3000 -grafana-datasource.url=http://jfr-datasource:8080 - quarkus.test.env.JAVA_OPTS_APPEND=-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false quarkus.datasource.devservices.enabled=true diff --git a/src/test/java/io/cryostat/resources/GrafanaResource.java b/src/test/java/io/cryostat/resources/GrafanaResource.java index 410369e36..1c376c25c 100644 --- a/src/test/java/io/cryostat/resources/GrafanaResource.java +++ b/src/test/java/io/cryostat/resources/GrafanaResource.java @@ -31,7 +31,7 @@ public class GrafanaResource private static Map envMap = Map.of( "GF_INSTALL_PLUGINS", "grafana-simple-json-datasource", - "GF_AUTH_ANONYMOUS_ENABLED", "tru", + "GF_AUTH_ANONYMOUS_ENABLED", "true", "JFR_DATASOURCE_URL", "http://jfr-datasource:8080"); private Optional containerNetworkId; diff --git a/src/test/java/itest/CryostatTemplateIT.java b/src/test/java/itest/CryostatTemplateIT.java index a4b8e7c5d..75f5ceec4 100644 --- a/src/test/java/itest/CryostatTemplateIT.java +++ b/src/test/java/itest/CryostatTemplateIT.java @@ -28,16 +28,16 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest public class CryostatTemplateIT extends StandardSelfTest { - static File file; + File file; - @BeforeAll - static void setup() throws Exception { + @BeforeEach + void setup() throws Exception { String url = String.format( "/api/v1/targets/%s/templates/Cryostat/type/TARGET", diff --git a/src/test/java/itest/CustomTargetsIT.java b/src/test/java/itest/CustomTargetsIT.java index b6ec3d390..92d3d0484 100644 --- a/src/test/java/itest/CustomTargetsIT.java +++ b/src/test/java/itest/CustomTargetsIT.java @@ -21,7 +21,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -51,7 +51,7 @@ @TestMethodOrder(OrderAnnotation.class) public class CustomTargetsIT extends StandardSelfTest { - private final ExecutorService worker = ForkJoinPool.commonPool(); + private final ExecutorService worker = Executors.newCachedThreadPool(); static final Map NULL_RESULT = new HashMap<>(); private String itestJvmId; private static StoredCredential storedCredential; diff --git a/src/test/java/itest/HealthIT.java b/src/test/java/itest/HealthIT.java index faaa3ff96..9f55c0e0c 100644 --- a/src/test/java/itest/HealthIT.java +++ b/src/test/java/itest/HealthIT.java @@ -18,6 +18,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import io.cryostat.resources.GrafanaResource; +import io.cryostat.resources.JFRDatasourceResource; + +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -27,21 +31,20 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest +@QuarkusTestResource(GrafanaResource.class) +@QuarkusTestResource(JFRDatasourceResource.class) public class HealthIT extends StandardSelfTest { - HttpRequest req; + JsonObject response; @BeforeEach - void createRequest() { - req = webClient.get("/health"); - } - - @Test - void shouldIncludeApplicationVersion() throws Exception { + void createRequest() throws Exception { CompletableFuture future = new CompletableFuture<>(); + HttpRequest req = webClient.get("/health"); req.send( ar -> { if (ar.failed()) { @@ -50,12 +53,40 @@ void shouldIncludeApplicationVersion() throws Exception { } future.complete(ar.result().bodyAsJsonObject()); }); - JsonObject response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + @Test + void shouldIncludeApplicationVersion() { Assertions.assertTrue(response.containsKey("cryostatVersion")); MatcherAssert.assertThat( response.getString("cryostatVersion"), Matchers.not(Matchers.emptyOrNullString())); MatcherAssert.assertThat( response.getString("cryostatVersion"), Matchers.not(Matchers.equalTo("unknown"))); + MatcherAssert.assertThat( + response.getString("cryostatVersion"), + Matchers.matchesRegex("^v[\\d]\\.[\\d]\\.[\\d](?:-SNAPSHOT)?")); + } + + @Disabled("TODO") + @Test + void shouldHaveAvailableDatasource() { + Assertions.assertTrue(response.containsKey("datasourceConfigured")); + MatcherAssert.assertThat( + response.getString("datasourceConfigured"), Matchers.equalTo("true")); + Assertions.assertTrue(response.containsKey("datasourceAvailable")); + MatcherAssert.assertThat( + response.getString("datasourceAvailable"), Matchers.equalTo("true")); + } + + @Disabled("TODO") + @Test + void shouldHaveAvailableDashboard() { + Assertions.assertTrue(response.containsKey("dashboardConfigured")); + MatcherAssert.assertThat( + response.getString("dashboardConfigured"), Matchers.equalTo("true")); + Assertions.assertTrue(response.containsKey("dashboardAvailable")); + MatcherAssert.assertThat( + response.getString("dashboardAvailable"), Matchers.equalTo("true")); } } diff --git a/src/test/java/itest/TargetEventsGetIT.java b/src/test/java/itest/TargetEventsGetIT.java index fd3b166ca..31cc9a41b 100644 --- a/src/test/java/itest/TargetEventsGetIT.java +++ b/src/test/java/itest/TargetEventsGetIT.java @@ -31,21 +31,28 @@ import itest.bases.StandardSelfTest; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @QuarkusIntegrationTest public class TargetEventsGetIT extends StandardSelfTest { - static final String EVENT_REQ_URL = - String.format("/api/v1/targets/%s/events", getSelfReferenceConnectUrlEncoded()); - static final String SEARCH_REQ_URL = - String.format("/api/v2/targets/%s/events", getSelfReferenceConnectUrlEncoded()); + String eventReqUrl; + String searchReqUrl; + + @BeforeEach + void setup() { + eventReqUrl = + String.format("/api/v1/targets/%s/events", getSelfReferenceConnectUrlEncoded()); + searchReqUrl = + String.format("/api/v2/targets/%s/events", getSelfReferenceConnectUrlEncoded()); + } @Test public void testGetTargetEventsReturnsListOfEvents() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(EVENT_REQ_URL) + .get(eventReqUrl) .basicAuthentication("user", "pass") .send( ar -> { @@ -67,7 +74,7 @@ public void testGetTargetEventsReturnsListOfEvents() throws Exception { public void testGetTargetEventsV2WithNoQueryReturnsListOfEvents() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(SEARCH_REQ_URL) + .get(searchReqUrl) .basicAuthentication("user", "pass") .send( ar -> { @@ -92,7 +99,7 @@ public void testGetTargetEventsV2WithNoQueryReturnsListOfEvents() throws Excepti public void testGetTargetEventsV2WithQueryReturnsRequestedEvents() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(String.format("%s?q=TargetConnectionOpened", SEARCH_REQ_URL)) + .get(String.format("%s?q=TargetConnectionOpened", searchReqUrl)) .basicAuthentication("user", "pass") .send( ar -> { @@ -155,7 +162,7 @@ public void testGetTargetEventsV2WithQueryReturnsRequestedEvents() throws Except public void testGetTargetEventsV2WithQueryReturnsEmptyListWhenNoEventsMatch() throws Exception { CompletableFuture> getResponse = new CompletableFuture<>(); webClient - .get(String.format("%s?q=thisEventDoesNotExist", SEARCH_REQ_URL)) + .get(String.format("%s?q=thisEventDoesNotExist", searchReqUrl)) .basicAuthentication("user", "pass") .send( ar -> { diff --git a/src/test/java/itest/UploadRecordingIT.java b/src/test/java/itest/UploadRecordingTest.java similarity index 91% rename from src/test/java/itest/UploadRecordingIT.java rename to src/test/java/itest/UploadRecordingTest.java index 0995a2fa0..1bd127d56 100644 --- a/src/test/java/itest/UploadRecordingIT.java +++ b/src/test/java/itest/UploadRecordingTest.java @@ -23,9 +23,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import io.cryostat.resources.GrafanaResource; +import io.cryostat.resources.JFRDatasourceResource; 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.http.HttpHeaders; import io.vertx.core.json.JsonArray; @@ -39,9 +42,11 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -@QuarkusIntegrationTest @Disabled("TODO") -public class UploadRecordingIT extends StandardSelfTest { +@QuarkusTest +@QuarkusTestResource(GrafanaResource.class) +@QuarkusTestResource(JFRDatasourceResource.class) +public class UploadRecordingTest extends StandardSelfTest { // TODO this should be a constant somewhere in the server sources public static final String DATASOURCE_FILENAME = "cryostat-analysis.jfr"; @@ -56,7 +61,12 @@ public static void createRecording() throws Exception { form.add("duration", String.valueOf(RECORDING_DURATION_SECONDS)); form.add("events", "template=ALL"); webClient - .post(String.format("/api/v1/targets/%s/recordings", getSelfReferenceConnectUrl())) + .post( + String.format( + "/api/v1/targets/%s/recordings", + getSelfReferenceConnectUrlEncoded())) + .basicAuthentication("user", "pass") + .followRedirects(true) .sendForm( form, ar -> { @@ -79,7 +89,9 @@ public static void deleteRecording() throws Exception { .delete( String.format( "/api/v1/targets/%s/recordings/%s", - getSelfReferenceConnectUrl(), RECORDING_NAME)) + getSelfReferenceConnectUrlEncoded(), RECORDING_NAME)) + .basicAuthentication("user", "pass") + .followRedirects(true) .send( ar -> { if (assertRequestStatus(ar, deleteRespFuture)) { @@ -103,7 +115,9 @@ public void shouldLoadRecordingToDatasource() throws Exception { .post( String.format( "/api/v1/targets/%s/recordings/%s/upload", - getSelfReferenceConnectUrl(), RECORDING_NAME)) + getSelfReferenceConnectUrlEncoded(), RECORDING_NAME)) + .basicAuthentication("user", "pass") + .followRedirects(true) .send( ar -> { if (assertRequestStatus(ar, uploadRespFuture)) { diff --git a/src/test/java/itest/bases/StandardSelfTest.java b/src/test/java/itest/bases/StandardSelfTest.java index 01fcf89fe..580abd650 100644 --- a/src/test/java/itest/bases/StandardSelfTest.java +++ b/src/test/java/itest/bases/StandardSelfTest.java @@ -18,26 +18,24 @@ import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import io.cryostat.util.HttpStatusCodeIdentifier; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.security.auth.module.UnixSystem; import io.vertx.core.AsyncResult; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.file.FileSystem; -import io.vertx.core.http.HttpMethod; import io.vertx.core.http.WebSocket; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -48,6 +46,8 @@ import io.vertx.ext.web.codec.BodyCodec; import io.vertx.ext.web.handler.HttpException; import itest.util.Utils; +import jakarta.ws.rs.core.HttpHeaders; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.http.client.utils.URLEncodedUtils; import org.jboss.logging.Logger; @@ -55,125 +55,190 @@ public abstract class StandardSelfTest { - public final Logger logger = Logger.getLogger(StandardSelfTest.class); + private static final String SELF_JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"; + private static final String SELFTEST_ALIAS = "selftest"; + private static final ExecutorService WORKER = Executors.newCachedThreadPool(); + public static final Logger logger = Logger.getLogger(StandardSelfTest.class); public static final ObjectMapper mapper = new ObjectMapper(); public static final int REQUEST_TIMEOUT_SECONDS = 30; public static final WebClient webClient = Utils.getWebClient(); + public static volatile String selfCustomTargetLocation; @BeforeAll - public static void waitForJdp() { - Logger logger = Logger.getLogger(StandardSelfTest.class); + public static void waitForDiscovery() { + waitForDiscovery(0); + } + + // @AfterAll + public static void deleteSelfCustomTarget() { + if (StringUtils.isBlank(selfCustomTargetLocation)) { + return; + } + logger.infov("Deleting self custom target at {0}", selfCustomTargetLocation); + String path = URI.create(selfCustomTargetLocation).getPath(); + WORKER.submit( + () -> { + webClient + .delete(path) + .basicAuthentication("user", "pass") + .timeout(2000) + .send( + ar -> { + if (ar.failed()) { + logger.error(ar.cause()); + return; + } + HttpResponse resp = ar.result(); + logger.infov( + "DELETE {0} -> HTTP {1} {2}: [{3}]", + path, + resp.statusCode(), + resp.statusMessage(), + resp.headers()); + selfCustomTargetLocation = null; + }); + }); + } + + public static void waitForDiscovery(int otherTargetsCount) { + final int totalTargets = otherTargetsCount + 1; boolean found = false; - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30); - String selfURL = getSelfReferenceConnectUrl(); + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(REQUEST_TIMEOUT_SECONDS); while (!found && System.nanoTime() < deadline) { - logger.infov("Waiting for self-discovery at {0} via JDP...", selfURL); + logger.infov("Waiting for discovery to see at least {0} target(s)...", totalTargets); CompletableFuture queryFound = new CompletableFuture<>(); - ForkJoinPool.commonPool() - .submit( - () -> { - webClient - .get("/api/v3/targets") - .basicAuthentication("user", "pass") - .as(BodyCodec.jsonArray()) - .timeout(500) - .send( - ar -> { - if (ar.failed()) { - logger.error(ar.cause()); - return; - } - JsonArray arr = ar.result().body(); - queryFound.complete( - arr.size() == 1 - && Objects.equals( - arr.getJsonObject(0) - .getString( - "connectUrl"), - selfURL)); - }); - }); + WORKER.submit( + () -> { + webClient + .get("/api/v3/targets") + .basicAuthentication("user", "pass") + .as(BodyCodec.jsonArray()) + .timeout(2000) + .send( + ar -> { + if (ar.failed()) { + logger.error(ar.cause()); + return; + } + HttpResponse resp = ar.result(); + JsonArray arr = resp.body(); + logger.infov( + "GET /api/v3/targets -> HTTP {1} {2}: [{3}] ->" + + " {4}", + selfCustomTargetLocation, + resp.statusCode(), + resp.statusMessage(), + resp.headers(), + arr); + logger.infov( + "Discovered {0} targets: {1}", arr.size(), arr); + queryFound.complete(arr.size() >= totalTargets); + }); + }); try { - found |= queryFound.get(1000, TimeUnit.MILLISECONDS); - Thread.sleep(1000); - } catch (InterruptedException | ExecutionException | TimeoutException e) { + found |= queryFound.get(2000, TimeUnit.MILLISECONDS); + if (!found) { + tryDefineSelfCustomTarget(); + Thread.sleep(3000); + } + } catch (Exception e) { logger.warn(e); } } if (!found) { - throw new RuntimeException(); + throw new RuntimeException("Timed out waiting for discovery"); } } - public static String getSelfReferenceConnectUrl() { - URI listPath = URI.create("http://d/v3.0.0/libpod/containers/json"); - String query = ""; - try { - - query = - mapper.writeValueAsString( - Map.of("label", List.of("io.cryostat.component=cryostat3"))); - } catch (JsonProcessingException jpe) { - throw new RuntimeException(jpe); + private static void tryDefineSelfCustomTarget() { + if (StringUtils.isNotBlank(selfCustomTargetLocation)) { + return; } - final String filter = query; + logger.info("Trying to define self-referential custom target..."); + CompletableFuture future = new CompletableFuture<>(); try { - CompletableFuture hostnameFuture = new CompletableFuture<>(); - ForkJoinPool.commonPool() - .submit( - () -> { - webClient - .request( - HttpMethod.GET, - getSocket(), - 80, - "localhost", - listPath.toString()) - .addQueryParam("filters", filter) - .timeout(500) - .as(BodyCodec.jsonArray()) - .send() - .onSuccess( - ar -> { - JsonArray response = ar.body(); - JsonObject obj = response.getJsonObject(0); - // containerFuture.complete(obj); - String id = obj.getString("Id"); - URI inspectPath = - URI.create( - String.format( - "http://d/v3.0.0/libpod/containers/%s/json", - id)); - webClient - .request( - HttpMethod.GET, - getSocket(), - 80, - "localhost", - inspectPath.toString()) - .timeout(500) - .as(BodyCodec.jsonObject()) - .send() - .onSuccess( - ar2 -> { - JsonObject json = - ar2.body(); - JsonObject config = - json.getJsonObject( - "Config"); - String hostname = - config.getString( - "Hostname"); - hostnameFuture.complete( - hostname); - }); - }); - }); - String hostname = hostnameFuture.get(5, TimeUnit.SECONDS); + JsonObject self = + new JsonObject(Map.of("connectUrl", SELF_JMX_URL, "alias", SELFTEST_ALIAS)); + WORKER.submit( + () -> { + webClient + .post("/api/v2/targets") + .basicAuthentication("user", "pass") + .timeout(5000) + .sendJson( + self, + ar -> { + if (ar.failed()) { + logger.error(ar.cause()); + future.completeExceptionally(ar.cause()); + return; + } + HttpResponse resp = ar.result(); + logger.infov( + "POST /api/v2/targets -> HTTP {0} {1}: [{2}]", + resp.statusCode(), + resp.statusMessage(), + resp.headers()); + if (HttpStatusCodeIdentifier.isSuccessCode( + resp.statusCode())) { + future.complete( + resp.headers().get(HttpHeaders.LOCATION)); + } else { + future.completeExceptionally( + new IllegalStateException( + Integer.toString( + resp.statusCode()))); + } + }); + }); + selfCustomTargetLocation = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception e) { + logger.warn(e); + } + } - return String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", hostname, 9091); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); + public static String getSelfReferenceConnectUrl() { + tryDefineSelfCustomTarget(); + CompletableFuture future = new CompletableFuture<>(); + WORKER.submit( + () -> { + String path = URI.create(selfCustomTargetLocation).getPath(); + webClient + .get(path) + .basicAuthentication("user", "pass") + .as(BodyCodec.jsonObject()) + .timeout(5000) + .send( + ar -> { + if (ar.failed()) { + logger.error(ar.cause()); + future.completeExceptionally(ar.cause()); + return; + } + HttpResponse resp = ar.result(); + JsonObject body = resp.body(); + logger.infov( + "GET {0} -> HTTP {1} {2}: [{3}] = {4}", + path, + resp.statusCode(), + resp.statusMessage(), + resp.headers(), + body); + if (!HttpStatusCodeIdentifier.isSuccessCode( + resp.statusCode())) { + future.completeExceptionally( + new IllegalStateException( + Integer.toString(resp.statusCode()))); + return; + } + future.complete(body); + }); + }); + try { + JsonObject obj = future.get(5000, TimeUnit.MILLISECONDS); + return obj.getString("connectUrl"); + } catch (Exception e) { + throw new RuntimeException("Could not determine own connectUrl", e); } } @@ -208,6 +273,10 @@ public static CompletableFuture expectNotification( ws.close(); } }) + // FIXME in the cryostat3 itests we DO use auth. The message below is + // copy-pasted from the old codebase, however cryostat3 does not yet + // perform authentication when websocket clients connect. + // just to initialize the connection - Cryostat expects // clients to send a message after the connection opens // to authenticate themselves, but in itests we don't @@ -239,19 +308,28 @@ public static boolean assertRequestStatus( private static Future getNotificationsUrl() { CompletableFuture future = new CompletableFuture<>(); - webClient - .get("/api/v1/notifications_url") - .send( - ar -> { - if (ar.succeeded()) { - future.complete( - ar.result() - .bodyAsJsonObject() - .getString("notificationsUrl")); - } else { - future.completeExceptionally(ar.cause()); - } - }); + WORKER.submit( + () -> { + webClient + .get("/api/v1/notifications_url") + .send( + ar -> { + if (ar.succeeded()) { + HttpResponse resp = ar.result(); + logger.infov( + "GET /api/v1/notifications_url -> HTTP {0} {1}:" + + " [{2}]", + resp.statusCode(), + resp.statusMessage(), + resp.headers()); + future.complete( + resp.bodyAsJsonObject() + .getString("notificationsUrl")); + } else { + future.completeExceptionally(ar.cause()); + } + }); + }); return future; } @@ -273,26 +351,39 @@ public static CompletableFuture downloadFileAbs( private static CompletableFuture fireDownloadRequest( HttpRequest request, String filename, String fileSuffix, MultiMap headers) { CompletableFuture future = new CompletableFuture<>(); - request.putHeaders(headers) - .basicAuthentication("user", "pass") - .followRedirects(true) - .send( - ar -> { - if (ar.failed()) { - future.completeExceptionally(ar.cause()); - return; - } - HttpResponse resp = ar.result(); - if (resp.statusCode() != 200) { - future.completeExceptionally( - new Exception(String.format("HTTP %d", resp.statusCode()))); - return; - } - FileSystem fs = Utils.getFileSystem(); - String file = fs.createTempFileBlocking(filename, fileSuffix); - fs.writeFileBlocking(file, ar.result().body()); - future.complete(Paths.get(file)); - }); + WORKER.submit( + () -> { + request.putHeaders(headers) + .basicAuthentication("user", "pass") + .followRedirects(true) + .send( + ar -> { + if (ar.failed()) { + future.completeExceptionally(ar.cause()); + return; + } + HttpResponse resp = ar.result(); + logger.infov( + "GET {0} -> HTTP {1} {2}: [{3}]", + request.uri(), + resp.statusCode(), + resp.statusMessage(), + resp.headers()); + if (!(HttpStatusCodeIdentifier.isSuccessCode( + resp.statusCode()))) { + future.completeExceptionally( + new Exception( + String.format( + "HTTP %d", resp.statusCode()))); + return; + } + FileSystem fs = Utils.getFileSystem(); + String file = + fs.createTempFileBlocking(filename, fileSuffix); + fs.writeFileBlocking(file, ar.result().body()); + future.complete(Paths.get(file)); + }); + }); return future; }