Skip to content

Commit 3c81e89

Browse files
authored
feat(rules): implement declarative Automated Rules (#749)
1 parent 255bed3 commit 3c81e89

File tree

7 files changed

+148
-2
lines changed

7 files changed

+148
-2
lines changed

src/main/docker/Dockerfile.jvm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ ENTRYPOINT [ "/deployments/app/entrypoint.bash", "/opt/jboss/container/java/run/
9393
# We make distinct layers so if there are application changes the library layers can be re-used
9494
COPY --chown=185 src/main/docker/include/cryostat.jfc /usr/lib/jvm/jre/lib/jfr/
9595
COPY --chown=185 src/main/docker/include/template_presets/* /opt/cryostat.d/presets.d/
96+
COPY --chown=185 src/main/docker/include/rule_presets/* /opt/cryostat.d/rules.d/
9697
COPY --chown=185 src/main/docker/include/genpass.bash /deployments/app/
9798
COPY --chown=185 src/main/docker/include/entrypoint.bash /deployments/app/
9899
COPY --chown=185 src/main/docker/include/truststore-setup.bash /deployments/app/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "quarkus",
3+
"description": "Preset Automated Rule for enabling Quarkus framework-specific events when available",
4+
"eventSpecifier": "template=Quarkus,type=PRESET",
5+
"matchExpression": "jfrEventTypeIds(target).exists(x, x.startsWith(\"quarkus\"))",
6+
"enabled": false
7+
}

src/main/java/io/cryostat/ConfigProperties.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class ConfigProperties {
5757
public static final String CUSTOM_TEMPLATES_DIR = "templates-dir";
5858
public static final String PRESET_TEMPLATES_DIR = "preset-templates-dir";
5959
public static final String SSL_TRUSTSTORE_DIR = "ssl.truststore.dir";
60+
public static final String RULES_DIR = "rules-dir";
6061

6162
public static final String URI_RANGE = "cryostat.target.uri-range";
6263

src/main/java/io/cryostat/rules/Rules.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@
1515
*/
1616
package io.cryostat.rules;
1717

18+
import java.io.BufferedInputStream;
19+
import java.io.IOException;
20+
import java.nio.file.Files;
1821
import java.util.List;
1922

23+
import io.cryostat.ConfigProperties;
2024
import io.cryostat.expressions.MatchExpression;
2125
import io.cryostat.util.EntityExistsException;
2226

27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import io.quarkus.runtime.StartupEvent;
2329
import io.vertx.core.json.JsonObject;
2430
import io.vertx.mutiny.core.eventbus.EventBus;
2531
import jakarta.annotation.security.RolesAllowed;
32+
import jakarta.enterprise.event.Observes;
2633
import jakarta.inject.Inject;
2734
import jakarta.transaction.Transactional;
2835
import jakarta.ws.rs.BadRequestException;
@@ -36,6 +43,8 @@
3643
import jakarta.ws.rs.core.Context;
3744
import jakarta.ws.rs.core.MediaType;
3845
import jakarta.ws.rs.core.UriInfo;
46+
import org.eclipse.microprofile.config.inject.ConfigProperty;
47+
import org.jboss.logging.Logger;
3948
import org.jboss.resteasy.reactive.RestForm;
4049
import org.jboss.resteasy.reactive.RestPath;
4150
import org.jboss.resteasy.reactive.RestQuery;
@@ -45,7 +54,58 @@
4554
@Path("/api/v4/rules")
4655
public class Rules {
4756

57+
@ConfigProperty(name = ConfigProperties.RULES_DIR)
58+
java.nio.file.Path dir;
59+
60+
@Inject Logger logger;
4861
@Inject EventBus bus;
62+
@Inject ObjectMapper mapper;
63+
64+
@Transactional
65+
void onStart(@Observes StartupEvent evt) {
66+
if (!checkDir()) {
67+
return;
68+
}
69+
try {
70+
Files.walk(dir)
71+
.filter(Files::isRegularFile)
72+
.filter(Files::isReadable)
73+
.forEach(this::processDeclarativeRule);
74+
} catch (IOException e) {
75+
logger.error(e);
76+
}
77+
}
78+
79+
private void processDeclarativeRule(java.nio.file.Path path) {
80+
try (var is = new BufferedInputStream(Files.newInputStream(path))) {
81+
var declarativeRule = mapper.readValue(is, Rule.class);
82+
logger.tracev(
83+
"Processing declarative Automated Rule with name \"{}\" at {}",
84+
declarativeRule.name,
85+
path);
86+
var exists = Rule.find("name", declarativeRule.name).count() != 0;
87+
if (exists) {
88+
logger.tracev(
89+
"Rule with name \"{}\" already exists in database. Skipping declarative"
90+
+ " rule at {}",
91+
declarativeRule.name,
92+
path);
93+
return;
94+
}
95+
declarativeRule.persist();
96+
} catch (IOException ioe) {
97+
logger.warn(ioe);
98+
} catch (Exception e) {
99+
logger.error(e);
100+
}
101+
}
102+
103+
private boolean checkDir() {
104+
return Files.exists(dir)
105+
&& Files.isReadable(dir)
106+
&& Files.isExecutable(dir)
107+
&& Files.isDirectory(dir);
108+
}
49109

50110
@GET
51111
@RolesAllowed("read")

src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ cryostat.target.uri-range=PUBLIC
4949
cryostat.agent.tls.required=true
5050

5151
conf-dir=/opt/cryostat.d
52+
rules-dir=${conf-dir}/rules.d
5253
templates-dir=${conf-dir}/templates.d
5354
preset-templates-dir=${conf-dir}/presets.d
5455
ssl.truststore=${conf-dir}/truststore.p12
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright The Cryostat Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package itest;
17+
18+
import java.io.File;
19+
import java.util.concurrent.CompletableFuture;
20+
import java.util.concurrent.TimeUnit;
21+
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import io.quarkus.test.junit.QuarkusIntegrationTest;
25+
import io.vertx.core.buffer.Buffer;
26+
import io.vertx.core.json.JsonArray;
27+
import io.vertx.ext.web.client.HttpRequest;
28+
import itest.bases.StandardSelfTest;
29+
import org.hamcrest.MatcherAssert;
30+
import org.hamcrest.Matchers;
31+
import org.junit.jupiter.api.Test;
32+
33+
@QuarkusIntegrationTest
34+
public class PresetRulesIT extends StandardSelfTest {
35+
36+
@Test
37+
public void shouldListPresetRules() throws Exception {
38+
CompletableFuture<JsonArray> future = new CompletableFuture<>();
39+
HttpRequest<Buffer> req = webClient.get("/api/v4/rules");
40+
req.send(
41+
ar -> {
42+
if (ar.failed()) {
43+
future.completeExceptionally(ar.cause());
44+
return;
45+
}
46+
future.complete(ar.result().bodyAsJsonArray());
47+
});
48+
JsonArray response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
49+
MatcherAssert.assertThat(response.size(), Matchers.equalTo(1));
50+
}
51+
52+
@Test
53+
public void shouldHavePresetQuarkusRule() throws Exception {
54+
String url = "/api/v4/rules/quarkus";
55+
File file =
56+
downloadFile(url, "quarkus", ".json")
57+
.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
58+
.toFile();
59+
60+
ObjectMapper mapper = new ObjectMapper();
61+
JsonNode json = mapper.readTree(file);
62+
63+
MatcherAssert.assertThat(json.get("name").asText(), Matchers.equalTo("quarkus"));
64+
MatcherAssert.assertThat(
65+
json.get("description").asText(),
66+
Matchers.equalTo(
67+
"Preset Automated Rule for enabling Quarkus framework-specific events when"
68+
+ " available"));
69+
MatcherAssert.assertThat(
70+
json.get("eventSpecifier").asText(),
71+
Matchers.equalTo("template=Quarkus,type=PRESET"));
72+
MatcherAssert.assertThat(
73+
json.get("matchExpression").asText(),
74+
Matchers.equalTo("jfrEventTypeIds(target).exists(x, x.startsWith(\"quarkus\"))"));
75+
MatcherAssert.assertThat(json.get("enabled").asBoolean(), Matchers.is(false));
76+
}
77+
}

src/test/java/itest/PresetTemplatesIT.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ public void shouldListPresetTemplates() throws Exception {
5555

5656
@Test
5757
public void shouldHavePresetQuarkusTemplate() throws Exception {
58-
String url =
59-
String.format("/api/v4/event_templates/PRESET/Quarkus", getSelfReferenceTargetId());
58+
String url = "/api/v4/event_templates/PRESET/Quarkus";
6059
File file =
6160
downloadFile(url, "quarkus", ".jfc")
6261
.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)

0 commit comments

Comments
 (0)