Alerting : Add scripted triggers and alert disabling.

This commit adds support for disabling alerts.
This commit adds preliminary support for scripted triggers.

Original commit: elastic/x-pack-elasticsearch@e14a56dbeb
This commit is contained in:
Brian Murphy 2014-08-18 12:13:39 +01:00
parent 4c1c502f80
commit 418b9f1a31
6 changed files with 164 additions and 26 deletions

View File

@ -15,9 +15,6 @@ import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.List;
/**
* Created by brian on 8/12/14.
*/
public class Alert implements ToXContent{
private final String alertName;
private String queryName;
@ -28,6 +25,16 @@ public class Alert implements ToXContent{
private DateTime lastRan;
private long version;
private DateTime running;
private boolean enabled;
public boolean enabled() {
return enabled;
}
public void enabled(boolean enabled) {
this.enabled = enabled;
}
public DateTime running() {
return running;
@ -109,7 +116,7 @@ public class Alert implements ToXContent{
public Alert(String alertName, String queryName, AlertTrigger trigger,
TimeValue timePeriod, List<AlertAction> actions, String schedule, DateTime lastRan,
List<String> indices, DateTime running, long version){
List<String> indices, DateTime running, long version, boolean enabled){
this.alertName = alertName;
this.queryName = queryName;
this.trigger = trigger;
@ -120,6 +127,7 @@ public class Alert implements ToXContent{
this.indices = indices;
this.version = version;
this.running = running;
this.enabled = enabled;
}
@Override
@ -132,6 +140,8 @@ public class Alert implements ToXContent{
builder.field(AlertManager.TIMEPERIOD_FIELD.getPreferredName(), timePeriod);
builder.field(AlertManager.LASTRAN_FIELD.getPreferredName(), lastRan);
builder.field(AlertManager.CURRENTLY_RUNNING.getPreferredName(), running);
builder.field(AlertManager.ENABLED.getPreferredName(), enabled);
builder.field(AlertManager.TRIGGER_FIELD.getPreferredName());
trigger.toXContent(builder, params);
builder.field(AlertManager.ACTION_FIELD.getPreferredName());

View File

@ -28,17 +28,11 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.search.SearchHit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
@ -57,6 +51,7 @@ public class AlertManager extends AbstractLifecycleComponent {
public static final ParseField LASTRAN_FIELD = new ParseField("lastRan");
public static final ParseField INDICES = new ParseField("indices");
public static final ParseField CURRENTLY_RUNNING = new ParseField("running");
public static final ParseField ENABLED = new ParseField("enabled");
private final Client client;
private AlertScheduler scheduler;
@ -429,7 +424,26 @@ public class AlertManager extends AbstractLifecycleComponent {
logger.warn("Indices : " + fields.get(INDICES.getPreferredName()) + " class " + fields.get(INDICES.getPreferredName()).getClass() );
}
return new Alert(alertId, query, trigger, timePeriod, actions, schedule, lastRan, indices, running, version);
boolean enabled = true;
if (fields.get(ENABLED.getPreferredName()) != null ) {
Object enabledObj = fields.get(ENABLED.getPreferredName());
if (enabledObj instanceof Boolean){
enabled = (Boolean)enabledObj;
} else {
if (enabledObj.toString().toLowerCase(Locale.ROOT).equals("true") ||
enabledObj.toString().toLowerCase(Locale.ROOT).equals("1")) {
enabled = true;
} else if ( enabledObj.toString().toLowerCase(Locale.ROOT).equals("false") ||
enabledObj.toString().toLowerCase(Locale.ROOT).equals("0")) {
enabled = false;
} else {
throw new ElasticsearchIllegalArgumentException("Unable to parse [" + enabledObj + "] as a boolean");
}
}
}
return new Alert(alertId, query, trigger, timePeriod, actions, schedule, lastRan, indices, running, version, enabled);
}
public Map<String,Alert> getSafeAlertMap() {

View File

@ -67,6 +67,9 @@ public class AlertScheduler extends AbstractLifecycleComponent {
logger.warn("Running [{}]",alertName);
Alert alert = alertManager.getAlertForName(alertName);
DateTime scheduledTime = new DateTime(jobExecutionContext.getScheduledFireTime());
if (!alert.enabled()) {
logger.warn("Alert [{}] is not enabled", alertName);
}
try {
if (!alertManager.claimAlertRun(alertName, scheduledTime) ){
logger.warn("Another process has already run this alert.");

View File

@ -8,8 +8,6 @@ package org.elasticsearch.alerting;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
@ -19,6 +17,16 @@ public class AlertTrigger implements ToXContent {
private TriggerType triggerType;
private int value;
public ScriptedAlertTrigger scriptedTrigger() {
return scriptedTrigger;
}
public void scriptedTrigger(ScriptedAlertTrigger scriptedTrigger) {
this.scriptedTrigger = scriptedTrigger;
}
private ScriptedAlertTrigger scriptedTrigger;
public SimpleTrigger trigger() {
return trigger;
}
@ -49,8 +57,17 @@ public class AlertTrigger implements ToXContent {
this.value = value;
}
public AlertTrigger(ScriptedAlertTrigger scriptedTrigger){
this.scriptedTrigger = scriptedTrigger;
this.triggerType = TriggerType.SCRIPT;
}
public String toString(){
return triggerType + " " + trigger + " " + value;
if(triggerType != TriggerType.SCRIPT) {
return triggerType + " " + trigger + " " + value;
} else {
return scriptedTrigger.toString();
}
}
public static enum SimpleTrigger {
@ -107,19 +124,26 @@ public class AlertTrigger implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(triggerType.toString(), trigger.toString() + value);
builder.endObject();
return builder;
if (triggerType != TriggerType.SCRIPT) {
builder.startObject();
builder.field(triggerType.toString(), trigger.toString() + value);
builder.endObject();
return builder;
} else {
return scriptedTrigger.toXContent(builder, params);
}
}
public static enum TriggerType {
NUMBER_OF_EVENTS;
NUMBER_OF_EVENTS,
SCRIPT;
public static TriggerType fromString(final String sTriggerType) {
switch (sTriggerType) {
case "numberOfEvents":
return NUMBER_OF_EVENTS;
case "script":
return SCRIPT;
default:
throw new ElasticsearchIllegalArgumentException("Unknown AlertTrigger:TriggerType [" + sTriggerType + "]");
}
@ -129,6 +153,8 @@ public class AlertTrigger implements ToXContent {
switch (triggerType) {
case NUMBER_OF_EVENTS:
return "numberOfEvents";
case SCRIPT:
return "script";
default:
return "unknown";
}

View File

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.alerting;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.script.ScriptService;
import java.io.IOException;
public class ScriptedAlertTrigger implements ToXContent {
public String script;
public ScriptService.ScriptType scriptType;
public String scriptLang;
public ScriptedAlertTrigger(String script, ScriptService.ScriptType scriptType, String scriptLang) {
this.script = script;
this.scriptType = scriptType;
this.scriptLang = scriptLang;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("script",script);
builder.field("script_type", scriptType);
builder.field("script_lang", scriptLang);
builder.endObject();
return builder;
}
}

View File

@ -10,29 +10,76 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService;
import java.util.HashMap;
import java.util.Map;
/*
* TODO : The trigger classes need cleanup and refactoring to be similar to the AlertActions and be pluggable
*/
public class TriggerManager extends AbstractComponent {
private final AlertManager alertManager;
private final ScriptService scriptService;
public static AlertTrigger parseTriggerFromMap(Map<String, Object> triggerMap) {
//For now just trigger on number of events greater than 1
for (Map.Entry<String,Object> entry : triggerMap.entrySet()){
AlertTrigger.TriggerType type = AlertTrigger.TriggerType.fromString(entry.getKey());
AlertTrigger.SimpleTrigger simpleTrigger = AlertTrigger.SimpleTrigger.fromString(entry.getValue().toString().substring(0, 1));
int value = Integer.valueOf(entry.getValue().toString().substring(1));
return new AlertTrigger(simpleTrigger, type, value);
if (type == AlertTrigger.TriggerType.SCRIPT) {
ScriptedAlertTrigger scriptedTrigger = parseScriptedTrigger(entry.getValue());
return new AlertTrigger(scriptedTrigger);
} else {
AlertTrigger.SimpleTrigger simpleTrigger = AlertTrigger.SimpleTrigger.fromString(entry.getValue().toString().substring(0, 1));
int value = Integer.valueOf(entry.getValue().toString().substring(1));
return new AlertTrigger(simpleTrigger, type, value);
}
}
throw new ElasticsearchIllegalArgumentException();
}
private static ScriptedAlertTrigger parseScriptedTrigger(Object value) {
if (value instanceof Map) {
Map<String,Object> valueMap = (Map<String,Object>)value;
try {
return new ScriptedAlertTrigger(valueMap.get("script").toString(),
ScriptService.ScriptType.valueOf(valueMap.get("script_type").toString()),
valueMap.get("script_lang").toString());
} catch (Exception e){
throw new ElasticsearchIllegalArgumentException("Unable to parse " + value + " as a ScriptedAlertTrigger");
}
} else {
throw new ElasticsearchIllegalArgumentException("Unable to parse " + value + " as a ScriptedAlertTrigger");
}
}
@Inject
public TriggerManager(Settings settings, AlertManager alertManager) {
public TriggerManager(Settings settings, AlertManager alertManager, ScriptService scriptService) {
super(settings);
this.alertManager = alertManager;
this.scriptService = scriptService;
}
public boolean doScriptTrigger(ScriptedAlertTrigger scriptTrigger, SearchResponse response) {
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder = response.toXContent(builder, ToXContent.EMPTY_PARAMS);
Map<String, Object> responseMap = XContentHelper.convertToMap(builder.bytes(), false).v2();
ExecutableScript executable = scriptService.executable(scriptTrigger.scriptLang, scriptTrigger.script,
scriptTrigger.scriptType, responseMap);
Object returnValue = executable.run();
logger.warn("Returned [{}] from script", returnValue);
} catch (Exception e ){
logger.error("Failed to execute script trigger", e);
}
return false;
}
public boolean isTriggered(String alertName, SearchResponse response) {
@ -46,6 +93,8 @@ public class TriggerManager extends AbstractComponent {
case NUMBER_OF_EVENTS:
testValue = response.getHits().getTotalHits();
break;
case SCRIPT:
return doScriptTrigger(alert.trigger().scriptedTrigger(), response);
default:
throw new ElasticsearchIllegalArgumentException("Bad value for trigger.triggerType [" + alert.trigger().triggerType() + "]");
}