From e1c3c816f9bdbf6d096ad09f5168c23aee7f10ae Mon Sep 17 00:00:00 2001 From: Jay Shaughnessy Date: Wed, 29 Jul 2015 10:55:21 -0400 Subject: [PATCH] HWKALERTS-73 Add context data to Triggers User can now optionally add a String Map of context data to a Trigger definition. The context data will automatically be applied to Alerts generated for the trigger. --- .../alerts/api/model/condition/Alert.java | 40 +++++++++++++++++-- .../alerts/api/model/trigger/Trigger.java | 15 +++++-- .../api/model/trigger/TriggerTemplate.java | 34 +++++++++++++++- .../hawkular/alerts/api/JsonJacksonTest.java | 15 +++++-- .../impl/CassDefinitionsServiceImpl.java | 5 ++- .../alerts/engine/impl/CassStatement.java | 8 ++-- .../main/resources/hawkular-alerts-schema.cql | 1 + .../alerts/engine/rules/ConditionMatch.drl | 1 + .../alerts/rest/LifecycleITest.groovy | 29 +++++++++----- 9 files changed, 124 insertions(+), 24 deletions(-) diff --git a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/condition/Alert.java b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/condition/Alert.java index 8c2df72f2..fd5f30cfe 100644 --- a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/condition/Alert.java +++ b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/condition/Alert.java @@ -20,7 +20,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.hawkular.alerts.api.model.Severity; @@ -92,8 +94,8 @@ public enum Status { private String resolvedNotes; /* - * This is the trigger defined when the alert was fired. - * A trigger definition can change during time, but an alert should be attached with a specific instance. + * If set this should be the trigger as defined when the alert was fired. A trigger definition can change + * over time, but an alert should be attached with the relevant instance. */ @JsonInclude(Include.NON_EMPTY) @Thin @@ -111,6 +113,13 @@ public enum Status { @Thin private List> resolvedEvalSets; + /* + * This should be initialized to the owning trigger's context. It is not set automatically so as to allow + * for flexibility. Note, this is not marked as Thin, whereas the trigger is Thin. + */ + @JsonInclude(Include.NON_EMPTY) + private Map context; + public Alert() { // for json assembly } @@ -120,6 +129,7 @@ public Alert(String tenantId, String triggerId, Severity severity, List getContext() { + return context; + } + + public void setContext(Map context) { + this.context = context; + } + + /** + * Add context information. + * @param name context key. + * @param value context value. + */ + public void addProperty(String name, String value) { + if (null == name || null == value) { + throw new IllegalArgumentException("Propety must have non-null name and value"); + } + if (null == context) { + context = new HashMap<>(); + } + context.put(name, value); + } + @Override public int hashCode() { final int prime = 31; @@ -282,7 +315,8 @@ public boolean equals(Object obj) { @Override public String toString() { return "Alert [alertId=" + alertId + ", status=" + status + ", ackTime=" + ackTime - + ", ackBy=" + ackBy + ", resolvedTime=" + resolvedTime + ", resolvedBy=" + resolvedBy + "]"; + + ", ackBy=" + ackBy + ", resolvedTime=" + resolvedTime + ", resolvedBy=" + resolvedBy + ", context=" + + context + "]"; } } diff --git a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/Trigger.java b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/Trigger.java index 0f7bcb52a..75a9b5fed 100644 --- a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/Trigger.java +++ b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/Trigger.java @@ -16,6 +16,7 @@ */ package org.hawkular.alerts.api.model.trigger; +import java.util.Map; import java.util.UUID; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -56,7 +57,11 @@ public Trigger() { } public Trigger(String name) { - this(generateId(), name); + this(generateId(), name, null); + } + + public Trigger(String name, Map context) { + this(generateId(), name, context); } public static String generateId() { @@ -64,7 +69,11 @@ public static String generateId() { } public Trigger(String id, String name) { - super(name); + this(id, name, null); + } + + public Trigger(String id, String name, Map context) { + super(name, context); if (id == null || id.isEmpty()) { throw new IllegalArgumentException("Trigger id must be non-empty"); @@ -157,7 +166,7 @@ public int hashCode() { public String toString() { return "Trigger [tenantId=" + tenantId + " id=" + id + ", enabled=" + enabled + ", mode=" + mode + ", getName()=" + getName() + ", isAutoDisable()=" + isAutoDisable() + ", isAutoEnable()=" - + isAutoEnable() + ", isAutoResolve()=" + isAutoResolve() + "]"; + + isAutoEnable() + ", isAutoResolve()=" + isAutoResolve() + ", context=" + context + "]"; } } diff --git a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/TriggerTemplate.java b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/TriggerTemplate.java index aa3352771..f4a703a6b 100644 --- a/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/TriggerTemplate.java +++ b/hawkular-alerts-api/src/main/java/org/hawkular/alerts/api/model/trigger/TriggerTemplate.java @@ -69,8 +69,16 @@ public enum Match { @JsonInclude private Match autoResolveMatch; + @JsonInclude(Include.NON_EMPTY) + protected Map context; + public TriggerTemplate(String name) { + this(name, null); + } + + public TriggerTemplate(String name, Map context) { this.name = name; + this.context = context; this.autoDisable = false; this.autoEnable = false; @@ -203,12 +211,36 @@ public void removeAction(String actionPlugin, String actionId) { } } + public Map getContext() { + return context; + } + + public void setContext(Map context) { + this.context = context; + } + + /** + * Add context information. + * @param name context key. + * @param value context value. + */ + public void addProperty(String name, String value) { + if (null == name || null == value) { + throw new IllegalArgumentException("Propety must have non-null name and value"); + } + if (null == context) { + context = new HashMap<>(); + } + context.put(name, value); + } + @Override public String toString() { return "TriggerTemplate [name=" + name + ", " + "description=" + description + ", " + "firingMatch=" + firingMatch + ", " + - "safetyMatch=" + autoResolveMatch + "]"; + "safetyMatch=" + autoResolveMatch + ", " + + "context=" + context + "]"; } } diff --git a/hawkular-alerts-api/src/test/java/org/hawkular/alerts/api/JsonJacksonTest.java b/hawkular-alerts-api/src/test/java/org/hawkular/alerts/api/JsonJacksonTest.java index 895b299ed..5eefdf2bd 100644 --- a/hawkular-alerts-api/src/test/java/org/hawkular/alerts/api/JsonJacksonTest.java +++ b/hawkular-alerts-api/src/test/java/org/hawkular/alerts/api/JsonJacksonTest.java @@ -173,8 +173,8 @@ public void jsonToAlertTest() throws Exception { "\"ackNotes\":null," + "\"resolvedTime\":0," + "\"resolvedBy\":null," + - "\"resolvedNotes\":null" + - "}"; + "\"resolvedNotes\":null," + + "\"context\":{\"n1\":\"v1\",\"n2\":\"v2\"}}"; ObjectMapper mapper = new ObjectMapper(); Alert alert = mapper.readValue(jsonAlert, Alert.class); @@ -182,6 +182,10 @@ public void jsonToAlertTest() throws Exception { assertNotNull(alert.getEvalSets()); assertEquals(1, alert.getEvalSets().size()); assertEquals(2, alert.getEvalSets().get(0).size()); + assertTrue(alert.getContext() != null); + assertTrue(alert.getContext().size() == 2); + assertTrue(alert.getContext().get("n1").equals("v1")); + assertTrue(alert.getContext().get("n2").equals("v2")); /* Testing thin deserializer @@ -883,7 +887,8 @@ public void jsonTriggerTest() throws Exception { "\"autoEnable\":true," + "\"autoResolve\":true," + "\"autoResolveAlerts\":true," + - "\"severity\":\"HIGH\"}"; + "\"severity\":\"HIGH\"," + + "\"context\":{\"n1\":\"v1\",\"n2\":\"v2\"}}"; Trigger trigger = objectMapper.readValue(str, Trigger.class); assertTrue(trigger.getName().equals("test-name")); @@ -899,6 +904,10 @@ public void jsonTriggerTest() throws Exception { assertTrue(trigger.isAutoResolve()); assertTrue(trigger.isAutoResolveAlerts()); assertTrue(trigger.getSeverity() == Severity.HIGH); + assertTrue(trigger.getContext() != null); + assertTrue(trigger.getContext().size() == 2); + assertTrue(trigger.getContext().get("n1").equals("v1")); + assertTrue(trigger.getContext().get("n2").equals("v2")); String output = objectMapper.writeValueAsString(trigger); diff --git a/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassDefinitionsServiceImpl.java b/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassDefinitionsServiceImpl.java index 0e15f96f6..4dde757ad 100644 --- a/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassDefinitionsServiceImpl.java +++ b/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassDefinitionsServiceImpl.java @@ -187,6 +187,7 @@ private void initTriggers(File fFolder) throws Exception { TriggerTemplate.Match autoResolveMatch = TriggerTemplate.Match .valueOf((String)t.get("autoResolveMatch")); List> actions = (List>)t.get("actions"); + Map context = (Map)t.get("context"); Trigger trigger = new Trigger(triggerId, name); trigger.setEnabled(enabled); @@ -202,6 +203,7 @@ private void initTriggers(File fFolder) throws Exception { for (Map action : actions) { trigger.addAction(action.get("actionPlugin"), action.get("actionId")); } + trigger.setContext(context); addTrigger(tenantId, trigger); log.debugf("Init file - Inserting [%s]", trigger); } @@ -446,7 +448,7 @@ public void addTrigger(String tenantId, Trigger trigger) throws Exception { trigger.isAutoDisable(), trigger.isAutoEnable(), trigger.isAutoResolve(), trigger.isAutoResolveAlerts(), trigger.getSeverity().name(), trigger.getFiringMatch().name(), trigger.getAutoResolveMatch().name(), - trigger.getId(), trigger.isEnabled(), trigger.getTenantId())); + trigger.getId(), trigger.isEnabled(), trigger.getTenantId(), trigger.getContext())); insertTriggerActions(trigger); } catch (Exception e) { @@ -766,6 +768,7 @@ private Trigger mapTrigger(Row row) { trigger.setId(row.getString("id")); trigger.setEnabled(row.getBool("enabled")); trigger.setTenantId(row.getString("tenantId")); + trigger.setContext(row.getMap("context", String.class, String.class)); return trigger; } diff --git a/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassStatement.java b/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassStatement.java index 89498e96d..1c918901b 100644 --- a/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassStatement.java +++ b/hawkular-alerts-engine/src/main/java/org/hawkular/alerts/engine/impl/CassStatement.java @@ -224,7 +224,7 @@ public class CassStatement { INSERT_TRIGGER = "INSERT INTO " + keyspace + ".triggers " + "(name, description, autoDisable, autoEnable, autoResolve, autoResolveAlerts, severity, firingMatch, " - + "autoResolveMatch, id, enabled, tenantId) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "; + + "autoResolveMatch, id, enabled, tenantId, context) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "; INSERT_TRIGGER_ACTIONS = "INSERT INTO " + keyspace + ".triggers_actions " + "(tenantId, triggerId, actionPlugin, actions) VALUES (?, ?, ?, ?) "; @@ -336,7 +336,7 @@ public class CassStatement { + "WHERE tenantId = ? AND name = ? "; SELECT_TRIGGER = "SELECT name, description, autoDisable, autoEnable, autoResolve, " - + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId " + + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId, context " + "FROM " + keyspace + ".triggers " + "WHERE tenantId = ? AND id = ? "; @@ -368,11 +368,11 @@ public class CassStatement { + "WHERE tenantId = ? AND triggerId = ? and triggerMode = ? "; SELECT_TRIGGERS_ALL = "SELECT name, description, autoDisable, autoEnable, autoResolve, " - + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId " + + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId, context " + "FROM " + keyspace + ".triggers "; SELECT_TRIGGERS_TENANT = "SELECT name, description, autoDisable, autoEnable, autoResolve, " - + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId " + + "autoResolveAlerts, severity, firingMatch, autoResolveMatch, id, enabled, tenantId, context " + "FROM " + keyspace + ".triggers WHERE tenantId = ? "; UPDATE_ACTION = "UPDATE " + keyspace + ".actions SET properties = ? " diff --git a/hawkular-alerts-engine/src/main/resources/hawkular-alerts-schema.cql b/hawkular-alerts-engine/src/main/resources/hawkular-alerts-schema.cql index a4b7807b0..903d86295 100644 --- a/hawkular-alerts-engine/src/main/resources/hawkular-alerts-schema.cql +++ b/hawkular-alerts-engine/src/main/resources/hawkular-alerts-schema.cql @@ -41,6 +41,7 @@ CREATE TABLE ${keyspace}.triggers ( autoResolveMatch text, id text, enabled boolean, + context map, PRIMARY KEY (tenantId, id) ); diff --git a/hawkular-alerts-engine/src/main/resources/org/hawkular/alerts/engine/rules/ConditionMatch.drl b/hawkular-alerts-engine/src/main/resources/org/hawkular/alerts/engine/rules/ConditionMatch.drl index 84475cd0d..174c623dd 100644 --- a/hawkular-alerts-engine/src/main/resources/org/hawkular/alerts/engine/rules/ConditionMatch.drl +++ b/hawkular-alerts-engine/src/main/resources/org/hawkular/alerts/engine/rules/ConditionMatch.drl @@ -425,6 +425,7 @@ rule AlertOnSatisfiedDampening Alert newAlert = new Alert( $t.getTenantId(), $tid, $t.getSeverity(), $d.getSatisfyingEvals() ); newAlert.setTrigger($t); newAlert.setDampening($d); + newAlert.setContext($t.getContext()); alerts.add(newAlert); if (actions != null) { for (String actionPlugin : $t.getActions().keySet()) { diff --git a/hawkular-alerts-rest-tests/src/test/groovy/org/hawkular/alerts/rest/LifecycleITest.groovy b/hawkular-alerts-rest-tests/src/test/groovy/org/hawkular/alerts/rest/LifecycleITest.groovy index e4282f058..4e0824675 100644 --- a/hawkular-alerts-rest-tests/src/test/groovy/org/hawkular/alerts/rest/LifecycleITest.groovy +++ b/hawkular-alerts-rest-tests/src/test/groovy/org/hawkular/alerts/rest/LifecycleITest.groovy @@ -57,7 +57,11 @@ class LifecycleITest extends AbstractITestBase { def resp = client.get(path: "") assertEquals(200, resp.status) - Trigger testTrigger = new Trigger("test-autodisable-trigger", "test-autodisable-trigger"); + // sub-test: add context and ensure it carries through to the alert + Map context = new HashMap<>(1); + context.put("contextName","contextValue"); + context.put("contextName2","contextValue2"); + Trigger testTrigger = new Trigger("test-autodisable-trigger", "test-autodisable-trigger", context); // remove if it exists resp = client.delete(path: "triggers/test-autodisable-trigger") @@ -92,6 +96,9 @@ class LifecycleITest extends AbstractITestBase { assertTrue(resp.data.autoDisable); assertFalse(resp.data.autoEnable); assertEquals("LOW", resp.data.severity); + assertNotNull(resp.data.context); + assertEquals("contextValue", resp.data.context.get("contextName")); + assertEquals("contextValue2", resp.data.context.get("contextName2")); // FETCH recent alerts for trigger, should not be any resp = client.get(path: "", query: [startTime:start,triggerIds:"test-autodisable-trigger"] ) @@ -108,7 +115,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 1 @@ -139,6 +146,10 @@ class LifecycleITest extends AbstractITestBase { assertEquals("testUser", resp.data[0].resolvedBy) assertEquals("testNotes", resp.data[0].resolvedNotes) assertNull(resp.data[0].resolvedEvalSets) + assertNotNull(resp.data[0].context); + Map alertContext = (Map)resp.data[0].context; + assertEquals("contextValue", alertContext.get("contextName")); + assertEquals("contextValue2", alertContext.get("contextName2")); // FETCH trigger and make sure it's still disabled, because autoEnable was set to false resp = client.get(path: "triggers/test-autodisable-trigger"); @@ -218,7 +229,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 1 @@ -262,7 +273,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 1 @@ -339,7 +350,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 20; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 5 @@ -589,7 +600,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(1000); // FETCH recent alerts for trigger, there should be 5 @@ -869,7 +880,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 1 because the trigger should have disabled after firing @@ -983,7 +994,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent alerts for trigger, there should be 1 @@ -1033,7 +1044,7 @@ class LifecycleITest extends AbstractITestBase { // The alert processing happens async, so give it a little time before failing... for ( int i=0; i < 10; ++i ) { - println "SLEEP!" ; + // println "SLEEP!" ; Thread.sleep(500); // FETCH recent OPEN alerts for trigger, there should be 1