Skip to content

Commit

Permalink
[DROOLS-7636] ansible-rulebook : event structure suggestion (#125)
Browse files Browse the repository at this point in the history
- Handle event list
- accept array without bracket
  • Loading branch information
tkobayas committed Oct 31, 2024
1 parent 2a0deb2 commit 4d7c533
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ private List<String> splitEventPath(String eventPath) {
String[] nodes = eventPath.split("\\.");
List<String> pathNodeList = new ArrayList<>();
for (String node : nodes) {
node = node.trim();
// handle bracket notation
int leftBracket = node.indexOf('[');
if (leftBracket > 0) {
Expand Down Expand Up @@ -235,6 +236,8 @@ private void validateRulesSetEventStructure() {
try {
JsonNode jsonNode = OBJECT_MAPPER.readTree(firstEventJson);

jsonNode = takeOneChildIfEventList(jsonNode);

for (Map.Entry<EventPath, List<String>> eventPathEntry : eventPathMap.entrySet()) {
validateEventPathWithEventStructure(eventPathEntry, jsonNode);
}
Expand All @@ -247,6 +250,16 @@ private void validateRulesSetEventStructure() {
}
}

private JsonNode takeOneChildIfEventList(JsonNode jsonNode) {
if (jsonNode.isObject() && jsonNode.size() == 1) {
JsonNode events = jsonNode.get("events");
if (events != null && events.isArray() && !events.isEmpty()) {
return events.get(0);
}
}
return jsonNode;
}

private void validateEventPathWithEventStructure(Map.Entry<EventPath, List<String>> eventPathEntry, JsonNode rootJsonNode) {
List<String> eventPathNodeList = eventPathEntry.getKey().getEventPathNodeList();
List<String> ruleNames = eventPathEntry.getValue();
Expand Down Expand Up @@ -368,6 +381,10 @@ private Optional<String> suggestTypo(String input, Set<String> candidates) {
LevenshteinDistance levenshteinDistance = new LevenshteinDistance();

for (String candidate : candidates) {
if (isArrayWithoutBracket(input, candidate)) {
// array without bracket is an acceptable syntax: DROOLS-7639
return Optional.empty();
}
int distance = levenshteinDistance.apply(input, candidate);
if (LOG.isTraceEnabled()) {
LOG.trace("The Levenshtein distance between '{}' and '{}' is: {}", input, candidate, distance);
Expand All @@ -379,6 +396,10 @@ private Optional<String> suggestTypo(String input, Set<String> candidates) {
return Optional.empty();
}

private boolean isArrayWithoutBracket(String input, String candidate) {
return candidate.endsWith("[]") && input.equals(candidate.substring(0, candidate.length() - 2));
}

private String concatNodeList(List<String> eventPathNodeList) {
return String.join(".", eventPathNodeList);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public void githubEvent() throws IOException {
{
"EqualsExpression": {
"lhs": {
"Event": "commits.added[1]"
"Event": "commit[0].added[1]"
},
"rhs": {
"String": "extensions/eda/rulebooks/100_million_events.yml"
Expand Down Expand Up @@ -359,7 +359,7 @@ public void gitlabEvent() throws IOException {

assertNumberOfRulesSetEventStructureWarnLogs(2);
assertThat(stringPrintStream.getStringList())
.anyMatch(s -> s.contains("'commits' in the condition 'commits.added[]'" +
.anyMatch(s -> s.contains("'commit[]' in the condition 'commit[].added[]'" +
" in rule set 'ruleSet1' rule [r1]" +
" does not meet with the incoming event property" +
" [commits[], user_avatar, total_commits_count, user_email, before," +
Expand Down Expand Up @@ -645,4 +645,204 @@ public void nestedArray() {

rulesExecutor.dispose();
}

public static final String JSON_WHITESPACE =
"""
{
"name": "ruleSet1",
"rules": [
{
"Rule": {
"name": "r1",
"condition": {
"AllCondition": [
{
"EqualsExpression": {
"lhs": {
"Event": " payload .alerts[0] .lebel . job"
},
"rhs": {
"String": "kube-state-metrics"
}
}
}
]
},
"actions": [
{
"Action": {
"action": "debug",
"action_args": {}
}
}
],
"enabled": true
}
}
]
}
""";

@Test
public void whitespace() {
// whitespaces in the event path are trimmed, so it doesn't affect the validation
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(JSON_WHITESPACE);

List<Match> matchedRules = rulesExecutor.processEvents(EVENT).join();

assertNumberOfRulesSetEventStructureWarnLogs(1);
assertThat(stringPrintStream.getStringList())
.anyMatch(s -> s.contains("'lebel' in the condition 'payload.alerts[].lebel.job'" +
" in rule set 'ruleSet1' rule [r1]" +
" does not meet with the incoming event property [labels]." +
" Did you mean 'labels'?"));
assertEquals(0, matchedRules.size());

rulesExecutor.dispose();
}

public static final String EVENTS =
"""
{
"events": [
{
"payload":{
"alerts":[
{
"labels":{
"job":"kube-state-metrics"
}
}
]
}
},
{
"payload":{
"alerts":[
{
"labels":{
"job":"node-exporter"
}
}
]
}
}
]
}
""";

@Test
public void events() {

RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(JSON_TYPO);

// "events" is the root node of multiple events, so the first event is used for the validation
List<Match> matchedRules = rulesExecutor.processEvents(EVENTS).join();

assertNumberOfRulesSetEventStructureWarnLogs(1);
assertThat(stringPrintStream.getStringList())
.anyMatch(s -> s.contains("'lebel' in the condition 'payload.alerts[].lebel.job'" +
" in rule set 'ruleSet1' rule [r1]" +
" does not meet with the incoming event property [labels]." +
" Did you mean 'labels'?"));
assertEquals(0, matchedRules.size());

rulesExecutor.dispose();
}

@Test
public void arrayWithoutBracket() {
String JSON_ARRAY_IN_ARRAY =
"""
{
"rules":[
{
"Rule":{
"name":"r1",
"condition":{
"AllCondition":[
{
"SelectAttrExpression":{
"lhs":{
"Event":"incident.alerts.tags"
},
"rhs":{
"key":{
"String":"value"
},
"operator":{
"String":"=="
},
"value":{
"String":"XXXX"
}
}
}
}
]
},
"actions":[
{
"Action":{
"action":"debug",
"action_args":{
"msg":"Found a match with alerts"
}
}
}
],
"enabled":true
}
}
]
}
""";

RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(JSON_ARRAY_IN_ARRAY);

List<Match> matchedRules = rulesExecutor.processEvents( """
{
"incident":{
"id":"aaa",
"active":false,
"alerts":[
{
"id":"bbb",
"tags":[
{
"name":"alertname",
"value":"MariadbDown"
},
{
"name":"severity",
"value":"critical"
}
],
"status":"Ok"
},
{
"id":"ccc",
"tags":[
{
"name":"severity",
"value":"critical"
},
{
"name":"alertname",
"value":"DiskUsage"
}
],
"status":"Ok"
}
]
}
}
""" ).join();

// "alerts" and "tags" without brackets are accepted. No warning. See DROOLS-7639
assertNumberOfRulesSetEventStructureWarnLogs(0);
assertEquals(0, matchedRules.size());

rulesExecutor.dispose();
}
}

0 comments on commit 4d7c533

Please sign in to comment.