Skip to content

Commit

Permalink
feat(grafana): add grafana and jfr-datasource integration (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxcao13 authored Aug 8, 2023
1 parent 88f34da commit 95fbc44
Show file tree
Hide file tree
Showing 29 changed files with 688 additions and 94 deletions.
19 changes: 19 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<org.apache.commons.lang3.version>3.12.0</org.apache.commons.lang3.version>
<org.apache.commons.validator.version>1.7</org.apache.commons.validator.version>
<org.projectnessie.cel.bom.version>0.3.21</org.projectnessie.cel.bom.version>
<org.testcontainers.bom.version>1.18.3</org.testcontainers.bom.version>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.1.1.Final</quarkus.platform.version>
Expand Down Expand Up @@ -72,6 +73,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${org.testcontainers.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -215,6 +223,17 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
<build>
<plugins>
Expand Down
7 changes: 7 additions & 0 deletions smoketest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ cleanup() {
docker-compose \
-f ./smoketest/compose/db.yml \
-f ./smoketest/compose/s3-minio.yml \
-f ./smoketest/compose/cryostat-grafana.yml \
-f ./smoketest/compose/jfr-datasource.yml \
-f ./smoketest/compose/sample-apps.yml \
-f ./smoketest/compose/cryostat.yml \
down --volumes --remove-orphans
Expand Down Expand Up @@ -35,6 +37,8 @@ setupUserHosts() {
echo "localhost db" >> ~/.hosts
echo "localhost db-viewer" >> ~/.hosts
echo "localhost cryostat" >> ~/.hosts
echo "localhost jfr-datasource" >> ~/.hosts
echo "localhost grafana" >> ~/.hosts
echo "localhost vertx-fib-demo-1" >> ~/.hosts
echo "localhost quarkus-test-agent" >> ~/.hosts
}
Expand All @@ -44,6 +48,9 @@ setupUserHosts
docker-compose \
-f ./smoketest/compose/db.yml \
-f ./smoketest/compose/s3-minio.yml \
-f ./smoketest/compose/cryostat-grafana.yml \
-f ./smoketest/compose/jfr-datasource.yml \
-f ./smoketest/compose/sample-apps.yml \
-f ./smoketest/compose/cryostat.yml \
up

18 changes: 18 additions & 0 deletions smoketest/compose/cryostat-grafana.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3"
services:
cryostat:
environment:
- GRAFANA_DASHBOARD_EXT_URL=http://localhost:3000
- GRAFANA_DASHBOARD_URL=http://grafana:3000
grafana:
image: quay.io/cryostat/cryostat-grafana-dashboard:latest
hostname: grafana
restart: unless-stopped
environment:
- GF_INSTALL_PLUGINS=grafana-simple-json-datasource
- GF_AUTH_ANONYMOUS_ENABLED=true
- JFR_DATASOURCE_URL=http://jfr-datasource:8080
ports:
- "3000:3000"
expose:
- "3000"
12 changes: 12 additions & 0 deletions smoketest/compose/jfr-datasource.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3"
services:
cryostat:
environment:
- GRAFANA_DATASOURCE_URL=http://jfr-datasource:8080
jfr-datasource:
image: quay.io/cryostat/jfr-datasource:latest
restart: unless-stopped
ports:
- "8080:8080"
expose:
- "8080"
4 changes: 4 additions & 0 deletions smoketest/containers/smoketest_pod.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ cleanup() {
podman-compose --in-pod=1 \
-f ./smoketest/compose/db.yml \
-f ./smoketest/compose/s3-minio.yml \
-f ./smoketest/compose/cryostat-grafana.yml \
-f ./smoketest/compose/jfr-datasource.yml \
-f ./smoketest/compose/sample-apps.yml \
-f ./smoketest/compose/cryostat.yml \
down --volumes --remove-orphans
Expand Down Expand Up @@ -44,6 +46,8 @@ setupUserHosts
podman-compose --in-pod=1 \
-f ./smoketest/compose/db.yml \
-f ./smoketest/compose/s3-minio.yml \
-f ./smoketest/compose/cryostat-grafana.yml \
-f ./smoketest/compose/jfr-datasource.yml \
-f ./smoketest/compose/sample-apps.yml \
-f ./smoketest/compose/cryostat.yml \
up
4 changes: 4 additions & 0 deletions src/main/java/io/cryostat/ConfigProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@

public class ConfigProperties {
public static final String AWS_BUCKET_NAME_ARCHIVES = "storage.buckets.archives.name";

public static final String GRAFANA_DASHBOARD_URL = "grafana-dashboard.url";
public static final String GRAFANA_DASHBOARD_EXT_URL = "grafana-dashboard-ext.url";
public static final String GRAFANA_DATASOURCE_URL = "grafana-datasource.url";
}
184 changes: 143 additions & 41 deletions src/main/java/io/cryostat/Health.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@
*/
package io.cryostat;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import io.cryostat.util.HttpStatusCodeIdentifier;

import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;
import io.vertx.mutiny.ext.web.client.WebClient;
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

Expand All @@ -47,36 +60,47 @@ class Health {
@ConfigProperty(name = "quarkus.http.ssl.certificate.key-store-password")
Optional<String> sslPass;

@ConfigProperty(name = ConfigProperties.GRAFANA_DASHBOARD_URL)
Optional<String> dashboardURL;

@ConfigProperty(name = ConfigProperties.GRAFANA_DASHBOARD_EXT_URL)
Optional<String> dashboardExternalURL;

@ConfigProperty(name = ConfigProperties.GRAFANA_DATASOURCE_URL)
Optional<String> datasourceURL;

@Inject Logger logger;
@Inject WebClient webClient;

@GET
@Path("/health")
@PermitAll
public Response health() {
return Response.ok(
Map.of(
"cryostatVersion",
String.format("v%s", version),
"dashboardConfigured",
false,
"dashboardAvailable",
false,
"datasourceConfigured",
false,
"datasourceAvailable",
false,
"reportsConfigured",
false,
"reportsAvailable",
false))
.header("Access-Control-Allow-Origin", "http://localhost:9000")
.header(
"Access-Control-Allow-Headers",
"accept, origin, authorization, content-type,"
+ " x-requested-with, x-jmx-authorization")
.header("Access-Control-Expose-Headers", "x-www-authenticate, x-jmx-authenticate")
.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
.header("Access-Control-Allow-Credentials", "true")
CompletableFuture<Boolean> datasourceAvailable = new CompletableFuture<>();
CompletableFuture<Boolean> dashboardAvailable = new CompletableFuture<>();
CompletableFuture<Boolean> reportsAvailable = new CompletableFuture<>();

checkUri(dashboardURL, "/api/health", dashboardAvailable);
checkUri(datasourceURL, "/", datasourceAvailable);
reportsAvailable.complete(false);

return new PermittedResponseBuilder(
Response.ok(
Map.of(
"cryostatVersion",
String.format("v%s", version),
"dashboardConfigured",
dashboardURL.isPresent(),
"dashboardAvailable",
dashboardAvailable.join(),
"datasourceConfigured",
datasourceURL.isPresent(),
"datasourceAvailable",
datasourceAvailable.join(),
"reportsConfigured",
false,
"reportsAvailable",
false)))
.build();
}

Expand All @@ -89,24 +113,102 @@ public void liveness() {}
@Path("/api/v1/notifications_url")
@PermitAll
public Response notificationsUrl() {
// TODO @PermitAll annotation seems to skip the CORS filter, so these headers don't get
// added. We shouldn't need to add them manually like this and they should not be added in
// prod builds.
boolean ssl = sslPass.isPresent();
return Response.ok(
Map.of(
"notificationsUrl",
String.format(
"%s://%s:%d/api/v1/notifications",
ssl ? "wss" : "ws", host, ssl ? sslPort : port)))
.header("Access-Control-Allow-Origin", "http://localhost:9000")
.header(
"Access-Control-Allow-Headers",
"accept, origin, authorization, content-type,"
+ " x-requested-with, x-jmx-authorization")
.header("Access-Control-Expose-Headers", "x-www-authenticate, x-jmx-authenticate")
.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
.header("Access-Control-Allow-Credentials", "true")
return new PermittedResponseBuilder(
Response.ok(
Map.of(
"notificationsUrl",
String.format(
"%s://%s:%d/api/v1/notifications",
ssl ? "wss" : "ws", host, ssl ? sslPort : port))))
.build();
}

@GET
@Path("/api/v1/grafana_dashboard_url")
@PermitAll
@Produces({MediaType.APPLICATION_JSON})
public Response grafanaDashboardUrl() {
String url =
dashboardExternalURL.orElseGet(
() -> dashboardURL.orElseThrow(() -> new BadRequestException()));

return new PermittedResponseBuilder(Response.ok(Map.of("grafanaDashboardUrl", url)))
.build();
}

@GET
@Path("/api/v1/grafana_datasource_url")
@PermitAll
@Produces({MediaType.APPLICATION_JSON})
public Response grafanaDatasourceUrl() {
return new PermittedResponseBuilder(
Response.ok(Map.of("grafanaDatasourceUrl", datasourceURL)))
.corsSkippedHeaders()
.build();
}

private void checkUri(
Optional<String> configProperty, String path, CompletableFuture<Boolean> future) {
if (configProperty.isPresent()) {
URI uri;
try {
uri = new URI(configProperty.get());
} catch (URISyntaxException e) {
logger.error(e);
future.complete(false);
return;
}
logger.debugv("Testing health of {1}={2} {3}", configProperty, uri.toString(), path);
HttpRequest<Buffer> req = webClient.get(uri.getHost(), path);
if (uri.getPort() != -1) {
req = req.port(uri.getPort());
}
req.ssl("https".equals(uri.getScheme()))
.timeout(5000)
.send()
.subscribe()
.with(
item -> {
future.complete(
HttpStatusCodeIdentifier.isSuccessCode(item.statusCode()));
},
failure -> {
logger.warn(new IOException(failure));
future.complete(false);
});
} else {
future.complete(false);
}
}

static class PermittedResponseBuilder {
private ResponseBuilder builder;

public PermittedResponseBuilder(ResponseBuilder builder) {
this.builder = builder;
}

public ResponseBuilder corsSkippedHeaders() {
// TODO @PermitAll annotation seems to skip the CORS filter, so these headers don't get
// added. We shouldn't need to add them manually like this and they should not be added
// in
// prod builds.
return this.builder
.header("Access-Control-Allow-Origin", "http://localhost:9000")
.header(
"Access-Control-Allow-Headers",
"accept, origin, authorization, content-type,"
+ " x-requested-with, x-jmx-authorization")
.header(
"Access-Control-Expose-Headers",
"x-www-authenticate, x-jmx-authenticate")
.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
.header("Access-Control-Allow-Credentials", "true");
}

public Response build() {
return builder.build();
}
}
}
12 changes: 10 additions & 2 deletions src/main/java/io/cryostat/Producers.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
import java.util.concurrent.ScheduledExecutorService;

import io.cryostat.core.sys.Clock;
import io.cryostat.core.sys.FileSystem;

import io.quarkus.arc.DefaultBean;
import io.vertx.core.Vertx;
import io.vertx.ext.web.client.WebClient;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.ext.web.client.WebClient;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import org.eclipse.microprofile.config.inject.ConfigProperty;
Expand All @@ -43,6 +44,13 @@ public static Clock produceClock() {
return new Clock();
}

@Produces
@ApplicationScoped
@DefaultBean
public static FileSystem produceFileSystem() {
return new FileSystem();
}

@Produces
@ApplicationScoped
@DefaultBean
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/cryostat/credentials/Credential.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import io.cryostat.ws.Notification;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.vertx.core.eventbus.EventBus;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.Column;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/cryostat/discovery/CustomDiscovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import io.cryostat.targets.TargetConnectionManager;

import io.quarkus.runtime.StartupEvent;
import io.vertx.core.eventbus.EventBus;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
Expand Down
Loading

0 comments on commit 95fbc44

Please sign in to comment.