Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(grafana): add grafana and jfr-datasource integration #43

Merged
merged 12 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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