Now input is separate from condition and condition just contains the decision logic.

```
    "input": {
        "search": {
            "request": {
                "body": {
                    "query": {
                        "match_all": {}
                    }
                }
            }
        }
    },
    "condition": {
        "script": {
            "script": "return true"
        }
    },
```
The result of this in the `alert_execution` looks like :
```
        "input_result": {
            "search": {
                "payload": {
                    "hits": {
                        "total": 1,
                        "hits": [
                            {
                                "_type": "my-type",
                                "_source": {
                                    "field": "value"
                                },
                                "_id": "AUujS61M4FTW2U3Ztz5U",
                                "_index": "my-index",
                                "_score": 0.30685282
                            }
                        ],
                        "max_score": 0.30685282
                    },
                    "_shards": {
                        "total": 5,
                        "failed": 0,
                        "successful": 5
                    },
                    "timed_out": false,
                    "took": 1823
                },
                "request": {
                    "body": {
                        "query": {
                            "match_all": {}
                        }
                   }
                }
            }
      }
      "condition_result": {
        "script": {
            "met": true
        }
      }
```
There are two Inputs currently the `SearchInput` as shown above and a `SimpleInput` that just contains a payload that will be returned in the result.
There are three conditions, the `ScriptCondition` as shown above and an `AlwaysTrueCondition` and AlwaysFalseCondition` condition.

Original commit: elastic/x-pack-elasticsearch@0d8ac24c5a
This commit is contained in:
Brian Murphy 2015-02-19 14:50:06 -05:00
parent fa02c150b4
commit 46cefe261a
32 changed files with 1528 additions and 456 deletions

View File

@ -8,14 +8,16 @@ package org.elasticsearch.alerts;
import org.elasticsearch.alerts.actions.ActionRegistry;
import org.elasticsearch.alerts.actions.Actions;
import org.elasticsearch.alerts.scheduler.Scheduler;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionRegistry;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputRegistry;
import org.elasticsearch.alerts.scheduler.schedule.Schedule;
import org.elasticsearch.alerts.scheduler.schedule.ScheduleRegistry;
import org.elasticsearch.alerts.throttle.AlertThrottler;
import org.elasticsearch.alerts.throttle.Throttler;
import org.elasticsearch.alerts.transform.Transform;
import org.elasticsearch.alerts.transform.TransformRegistry;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionRegistry;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesReference;
@ -42,6 +44,7 @@ public class Alert implements Scheduler.Job, ToXContent {
private final String name;
private final Schedule schedule;
private final Input input;
private final Condition condition;
private final Actions actions;
private final Throttler throttler;
@ -54,9 +57,10 @@ public class Alert implements Scheduler.Job, ToXContent {
@Nullable
private final Transform transform;
public Alert(String name, Schedule schedule, Condition condition, Transform transform, TimeValue throttlePeriod, Actions actions, Map<String, Object> metadata, Status status) {
public Alert(String name, Schedule schedule, Input input, Condition condition, Transform transform, Actions actions, Map<String, Object> metadata, Status status, TimeValue throttlePeriod) {
this.name = name;
this.schedule = schedule;
this.input = input;
this.condition = condition;
this.actions = actions;
this.status = status != null ? status : new Status();
@ -75,6 +79,8 @@ public class Alert implements Scheduler.Job, ToXContent {
return schedule;
}
public Input input() { return input;}
public Condition condition() {
return condition;
}
@ -134,6 +140,7 @@ public class Alert implements Scheduler.Job, ToXContent {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Parser.SCHEDULE_FIELD.getPreferredName()).startObject().field(schedule.type(), schedule).endObject();
builder.field(Parser.INPUT_FIELD.getPreferredName()).startObject().field(input.type(), input).endObject();
builder.field(Parser.CONDITION_FIELD.getPreferredName()).startObject().field(condition.type(), condition).endObject();
if (transform != Transform.NOOP) {
builder.field(Parser.TRANSFORM_FIELD.getPreferredName()).startObject().field(transform.type(), transform).endObject();
@ -153,6 +160,7 @@ public class Alert implements Scheduler.Job, ToXContent {
public static class Parser extends AbstractComponent {
public static final ParseField SCHEDULE_FIELD = new ParseField("schedule");
public static final ParseField INPUT_FIELD = new ParseField("input");
public static final ParseField CONDITION_FIELD = new ParseField("condition");
public static final ParseField ACTIONS_FIELD = new ParseField("actions");
public static final ParseField TRANSFORM_FIELD = new ParseField("transform");
@ -164,16 +172,19 @@ public class Alert implements Scheduler.Job, ToXContent {
private final ScheduleRegistry scheduleRegistry;
private final TransformRegistry transformRegistry;
private final ActionRegistry actionRegistry;
private final InputRegistry inputRegistry;
@Inject
public Parser(Settings settings, ConditionRegistry conditionRegistry, ScheduleRegistry scheduleRegistry,
TransformRegistry transformRegistry, ActionRegistry actionRegistry) {
TransformRegistry transformRegistry, ActionRegistry actionRegistry,
InputRegistry inputRegistry) {
super(settings);
this.conditionRegistry = conditionRegistry;
this.scheduleRegistry = scheduleRegistry;
this.transformRegistry = transformRegistry;
this.actionRegistry = actionRegistry;
this.inputRegistry = inputRegistry;
}
public Alert parse(String name, boolean includeStatus, BytesReference source) {
@ -189,6 +200,7 @@ public class Alert implements Scheduler.Job, ToXContent {
public Alert parse(String name, boolean includeStatus, XContentParser parser) throws IOException {
Schedule schedule = null;
Input input = null;
Condition condition = null;
Actions actions = null;
Transform transform = null;
@ -206,6 +218,8 @@ public class Alert implements Scheduler.Job, ToXContent {
} else if ((token.isValue() || token == XContentParser.Token.START_OBJECT || token == XContentParser.Token.START_ARRAY) && currentFieldName !=null ) {
if (SCHEDULE_FIELD.match(currentFieldName)) {
schedule = scheduleRegistry.parse(parser);
} else if (INPUT_FIELD.match(currentFieldName)) {
input = inputRegistry.parse(parser);
} else if (CONDITION_FIELD.match(currentFieldName)) {
condition = conditionRegistry.parse(parser);
} else if (ACTIONS_FIELD.match(currentFieldName)) {
@ -230,6 +244,9 @@ public class Alert implements Scheduler.Job, ToXContent {
if (schedule == null) {
throw new AlertsSettingsException("could not parse alert [" + name + "]. missing alert schedule");
}
if (input == null) {
throw new AlertsSettingsException("could not parse alert [" + name + "]. missing alert input");
}
if (condition == null) {
throw new AlertsSettingsException("could not parse alert [" + name + "]. missing alert condition");
}
@ -237,7 +254,7 @@ public class Alert implements Scheduler.Job, ToXContent {
throw new AlertsSettingsException("could not parse alert [" + name + "]. missing alert actions");
}
return new Alert(name, schedule, condition, transform, throttlePeriod, actions, metatdata, status);
return new Alert(name, schedule, input, condition, transform, actions, metatdata, status, throttlePeriod);
}
}

View File

@ -9,6 +9,8 @@ import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.ActionRegistry;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionRegistry;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputRegistry;
import org.elasticsearch.alerts.throttle.Throttler;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent;
@ -24,16 +26,18 @@ import java.util.Map;
*/
public class AlertExecution implements ToXContent {
private final Input.Result inputResult;
private final Condition.Result conditionResult;
private final Throttler.Result throttleResult;
private final Map<String, Action.Result> actionsResults;
private final Payload payload;
public AlertExecution(ExecutionContext context) {
this(context.conditionResult(), context.throttleResult(), context.actionsResults(), context.payload());
this(context.inputResult(), context.conditionResult(), context.throttleResult(), context.actionsResults(), context.payload());
}
AlertExecution(Condition.Result conditionResult, Throttler.Result throttleResult, Map<String, Action.Result> actionsResults, Payload payload) {
AlertExecution(Input.Result inputResult, Condition.Result conditionResult, Throttler.Result throttleResult, Map<String, Action.Result> actionsResults, Payload payload) {
this.inputResult = inputResult;
this.conditionResult = conditionResult;
this.throttleResult = throttleResult;
this.actionsResults = actionsResults;
@ -59,6 +63,9 @@ public class AlertExecution implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (inputResult != null) {
builder.startObject(Parser.INPUT_RESULT.getPreferredName()).field(inputResult.type(), inputResult).endObject();
}
if (conditionResult != null) {
builder.startObject(Parser.CONDITION_RESULT.getPreferredName()).field(conditionResult.type(), conditionResult).endObject();
}
@ -82,16 +89,19 @@ public class AlertExecution implements ToXContent {
public static class Parser {
public static final ParseField INPUT_RESULT = new ParseField("input_result");
public static final ParseField CONDITION_RESULT = new ParseField("condition_result");
public static final ParseField PAYLOAD = new ParseField("payload");
public static final ParseField ACTIONS_RESULTS = new ParseField("actions_results");
public static final ParseField THROTTLED = new ParseField("throttled");
public static final ParseField THROTTLE_REASON = new ParseField("throttle_reason");
public static AlertExecution parse(XContentParser parser, ConditionRegistry conditionRegistry, ActionRegistry actionRegistry) throws IOException {
public static AlertExecution parse(XContentParser parser, ConditionRegistry conditionRegistry, ActionRegistry actionRegistry,
InputRegistry inputRegistry) throws IOException {
boolean throttled = false;
String throttleReason = null;
Map<String, Action.Result> actionResults = new HashMap<>();
Input.Result inputResult = null;
Condition.Result conditionResult = null;
Payload payload = null;
@ -109,7 +119,9 @@ public class AlertExecution implements ToXContent {
throw new AlertsException("unable to parse alert run. unexpected field [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (CONDITION_RESULT.match(currentFieldName)) {
if (INPUT_RESULT.match(currentFieldName)) {
inputResult = inputRegistry.parseResult(parser);
} else if (CONDITION_RESULT.match(currentFieldName)) {
conditionResult = conditionRegistry.parseResult(parser);
} else if (PAYLOAD.match(currentFieldName)) {
payload = new Payload.XContent(parser);
@ -128,7 +140,7 @@ public class AlertExecution implements ToXContent {
}
Throttler.Result throttleResult = throttled ? Throttler.Result.throttle(throttleReason) : Throttler.Result.NO;
return new AlertExecution(conditionResult, throttleResult, actionResults, payload );
return new AlertExecution(inputResult, conditionResult, throttleResult, actionResults, payload );
}

View File

@ -8,7 +8,9 @@ package org.elasticsearch.alerts;
import org.elasticsearch.alerts.actions.ActionModule;
import org.elasticsearch.alerts.client.AlertsClientModule;
import org.elasticsearch.alerts.condition.ConditionModule;
import org.elasticsearch.alerts.history.HistoryModule;
import org.elasticsearch.alerts.input.InputModule;
import org.elasticsearch.alerts.rest.AlertsRestModule;
import org.elasticsearch.alerts.scheduler.SchedulerModule;
import org.elasticsearch.alerts.support.TemplateUtils;
@ -16,7 +18,6 @@ import org.elasticsearch.alerts.support.init.InitializingModule;
import org.elasticsearch.alerts.support.template.TemplateModule;
import org.elasticsearch.alerts.transform.TransformModule;
import org.elasticsearch.alerts.transport.AlertsTransportModule;
import org.elasticsearch.alerts.condition.ConditionModule;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Module;
@ -36,6 +37,7 @@ public class AlertsModule extends AbstractModule implements SpawnModules {
new SchedulerModule(),
new AlertsTransportModule(),
new ConditionModule(),
new InputModule(),
new ActionModule(),
new HistoryModule());
}

View File

@ -6,9 +6,10 @@
package org.elasticsearch.alerts;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.throttle.Throttler;
import org.elasticsearch.alerts.transform.Transform;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.common.joda.time.DateTime;
import java.util.HashMap;
@ -24,6 +25,7 @@ public class ExecutionContext {
private final DateTime fireTime;
private final DateTime scheduledTime;
private Input.Result inputResult;
private Condition.Result conditionResult;
private Throttler.Result throttleResult;
private Transform.Result transformResult;
@ -58,10 +60,18 @@ public class ExecutionContext {
return payload;
}
public void onInputResult(Input.Result inputResult) {
this.inputResult = inputResult;
this.payload = inputResult.payload();
}
public Input.Result inputResult() {
return inputResult;
}
public void onConditionResult(Condition.Result conditionResult) {
this.conditionResult = conditionResult;
this.payload = conditionResult.payload();
alert.status().onCheck(conditionResult.met(), fireTime);
this.conditionResult = conditionResult;
}
public Condition.Result conditionResult() {

View File

@ -6,11 +6,9 @@
package org.elasticsearch.alerts.condition;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
@ -20,6 +18,8 @@ import java.io.IOException;
*/
public abstract class Condition<R extends Condition.Result> implements ToXContent {
protected static final ParseField MET_FIELD = new ParseField("met");
protected final ESLogger logger;
protected Condition(ESLogger logger) {
@ -60,39 +60,19 @@ public abstract class Condition<R extends Condition.Result> implements ToXConten
public abstract static class Result implements ToXContent {
public static final ParseField MET_FIELD = new ParseField("met");
public static final ParseField PAYLOAD_FIELD = new ParseField("payload");
private final String type;
private final boolean met;
private final Payload payload;
public Result(String type, boolean met, Payload payload) {
public Result(String type, boolean met) {
this.type = type;
this.met = met;
this.payload = payload;
}
public String type() {
return type;
}
public boolean met() {
return met;
}
public boolean met() { return met; }
public Payload payload() {
return payload;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
.field(MET_FIELD.getPreferredName(), met)
.field(PAYLOAD_FIELD.getPreferredName(), payload);
return toXContentBody(builder, params).endObject();
}
protected abstract XContentBuilder toXContentBody(XContentBuilder builder, Params params) throws IOException;
}
}

View File

@ -5,8 +5,9 @@
*/
package org.elasticsearch.alerts.condition;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.simple.SimpleCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.condition.simple.AlwaysFalseCondition;
import org.elasticsearch.alerts.condition.simple.AlwaysTrueCondition;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.multibindings.MapBinder;
@ -28,10 +29,12 @@ public class ConditionModule extends AbstractModule {
protected void configure() {
MapBinder<String, Condition.Parser> parsersBinder = MapBinder.newMapBinder(binder(), String.class, Condition.Parser.class);
bind(ScriptSearchCondition.Parser.class).asEagerSingleton();
parsersBinder.addBinding(ScriptSearchCondition.TYPE).to(ScriptSearchCondition.Parser.class);
bind(SimpleCondition.Parser.class).asEagerSingleton();
parsersBinder.addBinding(SimpleCondition.TYPE).to(SimpleCondition.Parser.class);
bind(ScriptCondition.Parser.class).asEagerSingleton();
parsersBinder.addBinding(ScriptCondition.TYPE).to(ScriptCondition.Parser.class);
bind(AlwaysFalseCondition.Parser.class).asEagerSingleton();
parsersBinder.addBinding(AlwaysFalseCondition.TYPE).to(AlwaysFalseCondition.Parser.class);
bind(AlwaysTrueCondition.Parser.class).asEagerSingleton();
parsersBinder.addBinding(AlwaysTrueCondition.TYPE).to(AlwaysTrueCondition.Parser.class);
for (Map.Entry<String, Class<? extends Condition.Parser>> entry : parsers.entrySet()) {
bind(entry.getValue()).asEagerSingleton();

View File

@ -49,6 +49,13 @@ public class ConditionRegistry {
return condition;
}
/**
* Reads the contents of parser to create the correct Condition.Result
*
* @param parser The parser containing the condition result definition
* @return A new condition result instance from the parser
* @throws IOException
*/
public Condition.Result parseResult(XContentParser parser) throws IOException {
String type = null;
XContentParser.Token token;

View File

@ -3,33 +3,30 @@
* 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.alerts.condition.search;
package org.elasticsearch.alerts.condition.script;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService;
import java.io.IOException;
import java.util.Locale;
/**
*
* This class executes a script against the ctx payload and returns a boolean
*/
public class ScriptSearchCondition extends SearchCondition {
public class ScriptCondition extends Condition<ScriptCondition.Result> {
public static final String TYPE = "script";
@ -37,54 +34,13 @@ public class ScriptSearchCondition extends SearchCondition {
private final ScriptService.ScriptType scriptType;
private final String scriptLang;
public ScriptSearchCondition(ESLogger logger, ScriptServiceProxy scriptService, ClientProxy client,
SearchRequest request, String script, ScriptService.ScriptType scriptType,
String scriptLang) {
super(logger, scriptService, client, request);
private final ScriptServiceProxy scriptService;
public ScriptCondition(ESLogger logger, ScriptServiceProxy scriptService, String script, ScriptService.ScriptType scriptType, String scriptLang) {
super(logger);
this.script = script;
this.scriptType = scriptType;
this.scriptLang = scriptLang;
}
@Override
public String type() {
return TYPE;
}
@Override
protected Result processSearchResponse(SearchResponse response) {
Payload payload = new Payload.ActionResponse(response);
ExecutableScript executable = scriptService.executable(scriptLang, script, scriptType, payload.data());
Object value = executable.run();
if (value instanceof Boolean) {
return new Result(TYPE, (Boolean) value, request, payload);
}
throw new ConditionException("condition script [" + script + "] did not return a boolean value");
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request, builder, params);
builder.field(ScriptService.SCRIPT_INLINE.getPreferredName(), script);
builder.field(Parser.SCRIPT_TYPE_FIELD.getPreferredName(), scriptType);
builder.field(ScriptService.SCRIPT_LANG.getPreferredName(), scriptLang);
return builder.endObject();
}
public static class Parser extends AbstractComponent implements SearchCondition.Parser<Result, ScriptSearchCondition> {
public static ParseField REQUEST_FIELD = new ParseField("request");
public static ParseField SCRIPT_TYPE_FIELD = new ParseField("script_type");
private final ClientProxy client;
private final ScriptServiceProxy scriptService;
@Inject
public Parser(Settings settings, ClientProxy client, ScriptServiceProxy scriptService) {
super(settings);
this.client = client;
this.scriptService = scriptService;
}
@ -94,9 +50,47 @@ public class ScriptSearchCondition extends SearchCondition {
}
@Override
public ScriptSearchCondition parse(XContentParser parser) throws IOException {
public Result execute(ExecutionContext ctx) throws IOException {
return processPayload(ctx.payload());
}
SearchRequest request = null;
protected Result processPayload(Payload payload) {
ExecutableScript executable = scriptService.executable(scriptLang, script, scriptType, payload.data());
Object value = executable.run();
if (value instanceof Boolean) {
return (Boolean) value ? Result.MET : Result.UNMET;
}
throw new ConditionException("condition script [" + script + "] did not return a boolean value");
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(ScriptService.SCRIPT_INLINE.getPreferredName(), script);
builder.field(Parser.SCRIPT_TYPE_FIELD.getPreferredName(), scriptType);
builder.field(ScriptService.SCRIPT_LANG.getPreferredName(), scriptLang);
return builder.endObject();
}
public static class Parser extends AbstractComponent implements Condition.Parser<Result, ScriptCondition> {
public static ParseField SCRIPT_TYPE_FIELD = new ParseField("script_type");
private final ScriptServiceProxy scriptService;
@Inject
public Parser(Settings settings, ScriptServiceProxy service) {
super(settings);
scriptService = service;
}
@Override
public String type() {
return TYPE;
}
@Override
public ScriptCondition parse(XContentParser parser) throws IOException {
String scriptLang = null;
String script = null;
ScriptService.ScriptType scriptType = ScriptService.ScriptType.INLINE;
@ -107,67 +101,77 @@ public class ScriptSearchCondition extends SearchCondition {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if ((token.isValue() || token == XContentParser.Token.START_OBJECT) && currentFieldName != null) {
if (REQUEST_FIELD.match(currentFieldName)) {
request = AlertUtils.readSearchRequest(parser, DEFAULT_SEARCH_TYPE);
} else if (ScriptService.SCRIPT_ID.match(currentFieldName)) {
if (ScriptService.SCRIPT_ID.match(currentFieldName)) {
script = parser.text();
scriptType = ScriptService.ScriptType.INDEXED;
} else if (ScriptService.SCRIPT_INLINE.match(currentFieldName)) {
script = parser.text();
} else if (SCRIPT_TYPE_FIELD.match(currentFieldName)) {
scriptType = ScriptService.ScriptType.valueOf(parser.text());
String value = parser.text();
try {
scriptType = ScriptService.ScriptType.valueOf(value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new ConditionException("could not parse [script] condition. unknown script type [" + value + "]");
}
} else if (ScriptService.SCRIPT_LANG.match(currentFieldName)) {
scriptLang = parser.text();
} else {
throw new ConditionException("could not parse script condition. unexpected field [" + currentFieldName + "]");
throw new ConditionException("could not parse [script] condition. unexpected field [" + currentFieldName + "]");
}
}
}
if (request == null) {
throw new ConditionException("could not parse script condition. missing required search request");
}
if (script == null) {
throw new ConditionException("could not parse script condition. either [script] or [script_id] must be provided");
throw new ConditionException("could not parse [script] condition. either [script] or [script_id] must be provided");
}
return new ScriptSearchCondition(logger, scriptService, client, request, script, scriptType, scriptLang);
return new ScriptCondition(logger, scriptService, script, scriptType, scriptLang);
}
@Override
public ScriptSearchCondition.Result parseResult(XContentParser parser) throws IOException {
public Result parseResult(XContentParser parser) throws IOException {
Boolean met = null;
String currentFieldName = null;
XContentParser.Token token;
boolean met = false;
Payload payload = null;
SearchRequest request = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Condition.Result.MET_FIELD.match(currentFieldName)) {
if (Condition.MET_FIELD.match(currentFieldName)) {
met = parser.booleanValue();
} else {
throw new ConditionException("unable to parse condition result. unexpected field [" + currentFieldName + "]");
throw new ConditionException("unable to parse [script] condition result. expected a boolean, got [" + parser.text() + "]");
}
} else {
throw new ConditionException("unable to parse condition result. unexpected field [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Condition.Result.PAYLOAD_FIELD.match(currentFieldName)) {
payload = new Payload.Simple(parser.map()); ///@TODO FIXME
} else if (REQUEST_FIELD.match(currentFieldName)) {
request = AlertUtils.readSearchRequest(parser, DEFAULT_SEARCH_TYPE);
} else {
throw new ConditionException("unable to parse condition result. unexpected field [" + currentFieldName + "]");
}
} else {
throw new ConditionException("unable to parse condition result. unexpected token [" + token + "]");
throw new ConditionException("unable to parse [script] condition result. unexpected field [" + currentFieldName + "]");
}
}
return new Result(TYPE, met, request, payload);
}
if (met == null) {
throw new ConditionException("could not parse [script] condition result. [met] is a required field");
}
return met ? Result.MET : Result.UNMET;
}
}
public static class Result extends Condition.Result {
static final Result MET = new Result(true);
static final Result UNMET = new Result(false);
private Result(boolean met) {
super(TYPE, met);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(MET_FIELD.getPreferredName(), met())
.endObject();
}
}

View File

@ -1,128 +0,0 @@
/*
* 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.alerts.condition.search;
import org.elasticsearch.ElasticsearchIllegalStateException;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.Variables;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchHit;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.alerts.support.AlertsDateUtils.formatDate;
public abstract class SearchCondition extends Condition<SearchCondition.Result> {
public static final SearchType DEFAULT_SEARCH_TYPE = SearchType.COUNT;
protected final ScriptServiceProxy scriptService;
protected final ClientProxy client;
protected final SearchRequest request;
public SearchCondition(ESLogger logger, ScriptServiceProxy scriptService, ClientProxy client, SearchRequest request) {
super(logger);
this.scriptService = scriptService;
this.client = client;
this.request = request;
}
@Override
public Result execute(ExecutionContext ctx) throws IOException {
SearchRequest request = createSearchRequestWithTimes(this.request, ctx.scheduledTime(), ctx.fireTime(), scriptService);
if (logger.isTraceEnabled()) {
logger.trace("running query for [{}]", ctx.alert().name(), XContentHelper.convertToJson(request.source(), false, true));
}
// actionGet deals properly with InterruptedException
SearchResponse response = client.search(request).actionGet();
if (logger.isDebugEnabled()) {
logger.debug("got [{}] hits", ctx.alert().name(), response.getHits().getTotalHits());
for (SearchHit hit : response.getHits()) {
logger.debug("hit [{}]", XContentHelper.toString(hit));
}
}
return processSearchResponse(response);
}
/**
* Processes the search response and returns the appropriate condition result
*/
protected abstract Result processSearchResponse(SearchResponse response);
/**
* Creates a new search request applying the scheduledFireTime and fireTime to the original request
*/
public static SearchRequest createSearchRequestWithTimes(SearchRequest requestPrototype, DateTime scheduledFireTime, DateTime fireTime, ScriptServiceProxy scriptService) throws IOException {
SearchRequest request = new SearchRequest(requestPrototype)
.indicesOptions(requestPrototype.indicesOptions())
.indices(requestPrototype.indices());
if (Strings.hasLength(requestPrototype.source())) {
Map<String, String> templateParams = new HashMap<>();
templateParams.put(Variables.SCHEDULED_FIRE_TIME, formatDate(scheduledFireTime));
templateParams.put(Variables.FIRE_TIME, formatDate(fireTime));
String requestSource = XContentHelper.convertToJson(requestPrototype.source(), false);
ExecutableScript script = scriptService.executable("mustache", requestSource, ScriptService.ScriptType.INLINE, templateParams);
request.source((BytesReference) script.unwrap(script.run()), false);
} else if (requestPrototype.templateName() != null) {
MapBuilder<String, String> templateParams = MapBuilder.newMapBuilder(requestPrototype.templateParams())
.put(Variables.SCHEDULED_FIRE_TIME, formatDate(scheduledFireTime))
.put(Variables.FIRE_TIME, formatDate(fireTime));
request.templateParams(templateParams.map());
request.templateName(requestPrototype.templateName());
request.templateType(requestPrototype.templateType());
} else {
throw new ElasticsearchIllegalStateException("Search requests needs either source or template name");
}
return request;
}
public static class Result extends Condition.Result {
public static final ParseField REQUEST_FIELD = new ParseField("request");
private final SearchRequest request;
public Result(String type, boolean met, SearchRequest request, Payload payload) {
super(type, met, payload);
this.request = request;
}
public SearchRequest request() {
return request;
}
@Override
public XContentBuilder toXContentBody(XContentBuilder builder, Params params) throws IOException {
builder.field(REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request(), builder, params);
return builder;
}
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.alerts.condition.simple;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
*/
public class AlwaysFalseCondition extends Condition<Condition.Result> {
public static final String TYPE = "always_false";
public static final Result RESULT = new Result(TYPE, false) {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
};
public AlwaysFalseCondition(ESLogger logger) {
super(logger);
}
@Override
public String type() {
return TYPE;
}
@Override
public Result execute(ExecutionContext ctx) throws IOException {
return RESULT;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
public static class Parser extends AbstractComponent implements Condition.Parser<Result, AlwaysFalseCondition> {
@Inject
public Parser(Settings settings) {
super(settings);
}
@Override
public String type() {
return TYPE;
}
@Override
public AlwaysFalseCondition parse(XContentParser parser) throws IOException {
return new AlwaysFalseCondition(logger);
}
@Override
public Result parseResult(XContentParser parser) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_OBJECT){
throw new ConditionException("unable to parse [" + TYPE + "] condition result. expected a start object token, found [" + parser.currentToken() + "]");
}
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) {
throw new ConditionException("unable to parse [" + TYPE + "] condition result. expected an empty object, but found an object with [" + token + "]");
}
return RESULT;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.alerts.condition.simple;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
*/
public class AlwaysTrueCondition extends Condition<Condition.Result> {
public static final String TYPE = "always_true";
public static final Result RESULT = new Result(TYPE, true) {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
};
public AlwaysTrueCondition(ESLogger logger) {
super(logger);
}
@Override
public String type() {
return TYPE;
}
@Override
public Result execute(ExecutionContext ctx) throws IOException {
return RESULT;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
public static class Parser extends AbstractComponent implements Condition.Parser<Result, AlwaysTrueCondition> {
@Inject
public Parser(Settings settings) {
super(settings);
}
@Override
public String type() {
return TYPE;
}
@Override
public AlwaysTrueCondition parse(XContentParser parser) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_OBJECT){
throw new ConditionException("unable to parse [" + TYPE + "] condition. expected a start object token, found [" + parser.currentToken() + "]");
}
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) {
throw new ConditionException("unable to parse [" + TYPE + "] condition. expected an empty object, but found an object with [" + token + "]");
}
return new AlwaysTrueCondition(logger);
}
@Override
public Result parseResult(XContentParser parser) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_OBJECT){
throw new ConditionException("unable to parse [" + TYPE + "] condition result. expected a start object token, found [" + parser.currentToken() + "]");
}
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) {
throw new ConditionException("unable to parse [" + TYPE + "] condition. expected an empty object, but found an object with [" + token + "]");
}
return RESULT;
}
}
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.alerts.history;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.AlertExecution;
import org.elasticsearch.alerts.AlertsException;
@ -14,6 +13,8 @@ import org.elasticsearch.alerts.AlertsSettingsException;
import org.elasticsearch.alerts.actions.ActionRegistry;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionRegistry;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputRegistry;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesReference;
@ -37,6 +38,7 @@ public class FiredAlert implements ToXContent {
private String name;
private DateTime fireTime;
private DateTime scheduledTime;
private Input input;
private Condition condition;
private State state;
private AlertExecution execution;
@ -58,6 +60,7 @@ public class FiredAlert implements ToXContent {
this.fireTime = fireTime;
this.scheduledTime = scheduledTime;
this.condition = alert.condition();
this.input = alert.input();
this.state = State.AWAITS_EXECUTION;
this.metadata = alert.metadata();
this.version = 1;
@ -79,6 +82,8 @@ public class FiredAlert implements ToXContent {
return fireTime;
}
public Input input() { return input; }
public Condition condition() {
return condition;
}
@ -206,12 +211,15 @@ public class FiredAlert implements ToXContent {
private final ConditionRegistry conditionRegistry;
private final ActionRegistry actionRegistry;
private final InputRegistry inputRegistry;
@Inject
public Parser(Settings settings, ConditionRegistry conditionRegistry, ActionRegistry actionRegistry) {
public Parser(Settings settings, ConditionRegistry conditionRegistry, ActionRegistry actionRegistry,
InputRegistry inputRegistry) {
super(settings);
this.conditionRegistry = conditionRegistry;
this.actionRegistry = actionRegistry;
this.inputRegistry = inputRegistry;
}
public FiredAlert parse(BytesReference source, String historyId, long version) {
@ -234,12 +242,15 @@ public class FiredAlert implements ToXContent {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (Alert.Parser.CONDITION_FIELD.match(currentFieldName)) {
if (Alert.Parser.INPUT_FIELD.match(currentFieldName)) {
alert.input = inputRegistry.parse(parser);
} else if (Alert.Parser.CONDITION_FIELD.match(currentFieldName)) {
alert.condition = conditionRegistry.parse(parser);
} else if (METADATA_FIELD.match(currentFieldName)) {
alert.metadata = parser.map();
} else if (ALERT_EXECUTION_FIELD.match(currentFieldName)) {
alert.execution = AlertExecution.Parser.parse(parser, conditionRegistry, actionRegistry);
alert.execution = AlertExecution.Parser.parse(parser, conditionRegistry, actionRegistry,
inputRegistry);
} else {
throw new AlertsException("unable to parse fired alert. unexpected field [" + currentFieldName + "]");
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.ElasticsearchIllegalStateException;
import org.elasticsearch.alerts.*;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.scheduler.Scheduler;
import org.elasticsearch.alerts.support.Callback;
import org.elasticsearch.alerts.throttle.Throttler;
@ -252,6 +253,9 @@ public class HistoryService extends AbstractComponent {
*/
AlertExecution execute(ExecutionContext ctx) throws IOException {
Input.Result inputResult = alert.input().execute(ctx);
ctx.onInputResult(inputResult);
Condition.Result conditionResult = alert.condition().execute(ctx);
ctx.onConditionResult(conditionResult);
@ -260,7 +264,7 @@ public class HistoryService extends AbstractComponent {
ctx.onThrottleResult(throttleResult);
if (!throttleResult.throttle()) {
Transform.Result result = alert.transform().apply(ctx, conditionResult.payload());
Transform.Result result = alert.transform().apply(ctx, inputResult.payload());
ctx.onTransformResult(result);
for (Action action : alert.actions()) {
Action.Result actionResult = action.execute(ctx, result.payload());

View File

@ -0,0 +1,91 @@
/*
* 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.alerts.input;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
*
*/
public abstract class Input<R extends Input.Result> implements ToXContent {
protected final ESLogger logger;
protected Input(ESLogger logger) {
this.logger = logger;
}
/**
* @return the type of this input
*/
public abstract String type();
/**
* Executes this input
*/
public abstract R execute(ExecutionContext ctx) throws IOException;
/**
* Parses xcontent to a concrete input of the same type.
*/
public static interface Parser<R extends Input.Result, I extends Input<R>> {
/**
* @return The type of the input
*/
String type();
/**
* Parses the given xcontent and creates a concrete input
*/
I parse(XContentParser parser) throws IOException;
/**
* Parses the given xContent and creates a concrete result
*/
R parseResult(XContentParser parser) throws IOException;
}
public abstract static class Result implements ToXContent {
public static final ParseField PAYLOAD_FIELD = new ParseField("payload");
private final String type;
private final Payload payload;
public Result(String type, Payload payload) {
this.type = type;
this.payload = payload;
}
public String type() {
return type;
}
public Payload payload() {
return payload;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
.field(PAYLOAD_FIELD.getPreferredName(), payload);
return toXContentBody(builder, params).endObject();
}
protected abstract XContentBuilder toXContentBody(XContentBuilder builder, Params params) throws IOException;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.alerts.input;
import org.elasticsearch.alerts.AlertsException;
/**
*
*/
public class InputException extends AlertsException {
public InputException(String msg) {
super(msg);
}
public InputException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.alerts.input;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.input.simple.SimpleInput;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.multibindings.MapBinder;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class InputModule extends AbstractModule {
private final Map<String, Class<? extends Input.Parser>> parsers = new HashMap<>();
public void registerInput(String type, Class<? extends Input.Parser> parserType) {
parsers.put(type, parserType);
}
@Override
protected void configure() {
MapBinder<String, Input.Parser> parsersBinder = MapBinder.newMapBinder(binder(), String.class, Input.Parser.class);
bind(SearchInput.Parser.class).asEagerSingleton();
parsersBinder.addBinding(SearchInput.TYPE).to(SearchInput.Parser.class);
bind(SimpleInput.Parser.class).asEagerSingleton();
parsersBinder.addBinding(SimpleInput.TYPE).to(SimpleInput.Parser.class);
for (Map.Entry<String, Class<? extends Input.Parser>> entry : parsers.entrySet()) {
bind(entry.getValue()).asEagerSingleton();
parsersBinder.addBinding(entry.getKey()).to(entry.getValue());
}
bind(InputRegistry.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.alerts.input;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Map;
/**
*
*/
public class InputRegistry {
private final ImmutableMap<String, Input.Parser> parsers;
@Inject
public InputRegistry(Map<String, Input.Parser> parsers) {
this.parsers = ImmutableMap.copyOf(parsers);
}
/**
* Reads the contents of parser to create the correct Input
*
* @param parser The parser containing the input definition
* @return A new input instance from the parser
* @throws java.io.IOException
*/
public Input parse(XContentParser parser) throws IOException {
String type = null;
XContentParser.Token token;
Input input = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
type = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && type != null) {
Input.Parser inputParser = parsers.get(type);
if (inputParser == null) {
throw new InputException("unknown input type [" + type + "]");
}
input = inputParser.parse(parser);
}
}
return input;
}
public Input.Result parseResult(XContentParser parser) throws IOException {
String type = null;
XContentParser.Token token;
Input.Result inputResult = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
type = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && type != null) {
Input.Parser inputParser = parsers.get(type);
if (inputParser == null) {
throw new InputException("unknown input type [" + type + "]");
}
inputResult = inputParser.parseResult(parser);
}
}
return inputResult;
}
}

View File

@ -0,0 +1,225 @@
/*
* 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.alerts.input.search;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputException;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.Variables;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchHit;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.alerts.support.AlertsDateUtils.formatDate;
/**
* This class just defines an input that runs a search
*/
public class SearchInput extends Input<SearchInput.Result> {
public static final String TYPE = "search";
public static final SearchType DEFAULT_SEARCH_TYPE = SearchType.COUNT;
private final SearchRequest searchRequest;
private final ScriptServiceProxy scriptService;
private final ClientProxy client;
public SearchInput(ESLogger logger, ScriptServiceProxy scriptService, ClientProxy client, SearchRequest searchRequest) {
super(logger);
this.searchRequest = searchRequest;
this.scriptService = scriptService;
this.client = client;
}
@Override
public String type() {
return TYPE;
}
@Override
public Result execute(ExecutionContext ctx) throws IOException {
SearchRequest request = createSearchRequestWithTimes(this.searchRequest, ctx.scheduledTime(), ctx.fireTime(), scriptService);
if (logger.isTraceEnabled()) {
logger.trace("[{}] running query for [{}] [{}]", ctx.id(), ctx.alert().name(), XContentHelper.convertToJson(request.source(), false, true));
}
// actionGet deals properly with InterruptedException
SearchResponse response = client.search(request).actionGet();
if (logger.isDebugEnabled()) {
logger.debug("[{}] found [{}] hits", ctx.id(), ctx.alert().name(), response.getHits().getTotalHits());
for (SearchHit hit : response.getHits()) {
logger.debug("[{}] hit [{}]", ctx.id(), XContentHelper.toString(hit));
}
}
return new Result(TYPE, new Payload.ActionResponse(response), request);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(searchRequest, builder, params);
return builder.endObject();
}
/**
* Creates a new search request applying the scheduledFireTime and fireTime to the original request
*/
public static SearchRequest createSearchRequestWithTimes(SearchRequest requestPrototype, DateTime scheduledFireTime, DateTime fireTime, ScriptServiceProxy scriptService) throws IOException {
SearchRequest request = new SearchRequest(requestPrototype)
.indicesOptions(requestPrototype.indicesOptions())
.searchType(requestPrototype.searchType())
.indices(requestPrototype.indices());
if (Strings.hasLength(requestPrototype.source())) {
Map<String, String> templateParams = new HashMap<>();
templateParams.put(Variables.SCHEDULED_FIRE_TIME, formatDate(scheduledFireTime));
templateParams.put(Variables.FIRE_TIME, formatDate(fireTime));
String requestSource = XContentHelper.convertToJson(requestPrototype.source(), false);
ExecutableScript script = scriptService.executable("mustache", requestSource, ScriptService.ScriptType.INLINE, templateParams);
request.source((BytesReference) script.unwrap(script.run()), false);
} else if (requestPrototype.templateName() != null) {
MapBuilder<String, String> templateParams = MapBuilder.newMapBuilder(requestPrototype.templateParams())
.put(Variables.SCHEDULED_FIRE_TIME, formatDate(scheduledFireTime))
.put(Variables.FIRE_TIME, formatDate(fireTime));
request.templateParams(templateParams.map());
request.templateName(requestPrototype.templateName());
request.templateType(requestPrototype.templateType());
} else {
throw new InputException("search requests needs either source or template name");
}
return request;
}
public static class Result extends Input.Result {
private final SearchRequest request;
public Result(String type, Payload payload, SearchRequest request) {
super(type, payload);
this.request = request;
}
public SearchRequest request() {
return request;
}
@Override
protected XContentBuilder toXContentBody(XContentBuilder builder, Params params) throws IOException {
if (request != null) {
builder.field(Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request, builder, params);
}
return builder;
}
}
public static class Parser extends AbstractComponent implements Input.Parser<Result,SearchInput> {
public static ParseField REQUEST_FIELD = new ParseField("request");
private final ScriptServiceProxy scriptService;
private final ClientProxy client;
@Inject
public Parser(Settings settings, ScriptServiceProxy scriptService, ClientProxy client) {
super(settings);
this.scriptService = scriptService;
this.client = client;
}
@Override
public String type() {
return TYPE;
}
@Override
public SearchInput parse(XContentParser parser) throws IOException {
SearchRequest request = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && currentFieldName != null) {
if (REQUEST_FIELD.match(currentFieldName)) {
request = AlertUtils.readSearchRequest(parser, DEFAULT_SEARCH_TYPE);
} else {
throw new InputException("unable to parse [" + TYPE + "] input. unexpected field [" + currentFieldName + "]");
}
}
}
if (request == null) {
throw new InputException("search request is missing or null for [" + TYPE + "] input");
}
return new SearchInput(logger, scriptService, client, request);
}
@Override
public Result parseResult(XContentParser parser) throws IOException {
Payload payload = null;
SearchRequest request = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && currentFieldName != null) {
if (Input.Result.PAYLOAD_FIELD.match(currentFieldName)) {
payload = new Payload.XContent(parser);
} else if (REQUEST_FIELD.match(currentFieldName)) {
request = AlertUtils.readSearchRequest(parser, DEFAULT_SEARCH_TYPE);
} else {
throw new InputException("unable to parse [" + TYPE + "] input result. unexpected field [" + currentFieldName + "]");
}
}
}
if (payload == null) {
throw new InputException("unable to parse [" + TYPE + "] input result ["
+ Input.Result.PAYLOAD_FIELD.getPreferredName() + "] is required");
}
if (request == null) {
throw new InputException("unable to parse [" + TYPE + "] input result, ["
+ REQUEST_FIELD.getPreferredName() + "] is required");
}
return new Result(TYPE, payload, request);
}
}
}

View File

@ -3,12 +3,12 @@
* 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.alerts.condition.simple;
package org.elasticsearch.alerts.input.simple;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputException;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
@ -19,15 +19,14 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
* A condition that is always met and returns a static/fixed payload
* This class just defines a simple xcontent map as an input
*/
public class SimpleCondition extends Condition<SimpleCondition.Result> {
public class SimpleInput extends Input<SimpleInput.Result> {
public static final String TYPE = "simple";
private final Payload payload;
public SimpleCondition(ESLogger logger, Payload payload) {
public SimpleInput(ESLogger logger, Payload payload) {
super(logger);
this.payload = payload;
}
@ -39,18 +38,20 @@ public class SimpleCondition extends Condition<SimpleCondition.Result> {
@Override
public Result execute(ExecutionContext ctx) throws IOException {
return new Result(payload);
return new Result(TYPE, payload);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return payload.toXContent(builder, params);
builder.startObject();
builder.field(Input.Result.PAYLOAD_FIELD.getPreferredName(), payload);
return builder.endObject();
}
public static class Result extends Condition.Result {
public static class Result extends Input.Result {
public Result(Payload payload) {
super(TYPE, true, payload);
public Result(String type, Payload payload) {
super(type, payload);
}
@Override
@ -59,7 +60,7 @@ public class SimpleCondition extends Condition<SimpleCondition.Result> {
}
}
public static class Parser extends AbstractComponent implements Condition.Parser<Result, SimpleCondition> {
public static class Parser extends AbstractComponent implements Input.Parser<Result,SimpleInput> {
@Inject
public Parser(Settings settings) {
@ -72,45 +73,53 @@ public class SimpleCondition extends Condition<SimpleCondition.Result> {
}
@Override
public SimpleCondition parse(XContentParser parser) throws IOException {
return new SimpleCondition(logger, new Payload.XContent(parser));
public SimpleInput parse(XContentParser parser) throws IOException {
Payload payload = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && currentFieldName != null) {
if (Input.Result.PAYLOAD_FIELD.match(currentFieldName)) {
payload = new Payload.XContent(parser);
} else {
throw new InputException("unable to parse [" + TYPE + "] input. unexpected field [" + currentFieldName + "]");
}
}
}
if (payload == null) {
throw new InputException("unable to parse [" + TYPE + "] input [payload] is a required field");
}
return new SimpleInput(logger, payload);
}
@Override
public Result parseResult(XContentParser parser) throws IOException {
Payload payload = null;
String currentFieldName = null;
XContentParser.Token token;
Payload payload = null;
boolean met = false;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Condition.Result.MET_FIELD.match(currentFieldName)) {
met = parser.booleanValue();
} else {
throw new ConditionException("unable to parse simple condition result. unexpected field [" + currentFieldName + "]");
}
} else {
throw new ConditionException("unable to parse simple condition result. unexpected field [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Condition.Result.PAYLOAD_FIELD.match(currentFieldName)) {
} else if (token == XContentParser.Token.START_OBJECT && currentFieldName != null) {
if (Input.Result.PAYLOAD_FIELD.match(currentFieldName)) {
payload = new Payload.XContent(parser);
} else {
throw new ConditionException("unable to parse simple condition result. unexpected field [" + currentFieldName + "]");
throw new InputException("unable to parse [" + TYPE + "] input result. unexpected field [" + currentFieldName + "]");
}
} else {
throw new ConditionException("unable to parse simple condition result. unexpected token [" + token + "]");
}
}
if (!met) {
throw new ConditionException("unable to parse simple condition result. simple condition always matches, yet [met] field is either missing or set to [false]");
if (payload == null) {
throw new InputException("unable to parse [" + TYPE + "] input result [payload] is a required field");
}
return new Result(payload);
return new Result(TYPE, payload);
}
}
}

View File

@ -16,6 +16,16 @@
"enabled" : false,
"dynamic" : true
},
"input": {
"type": "object",
"enabled" : false,
"dynamic" : true
},
"condition": {
"type": "object",
"enabled" : false,
"dynamic" : true
},
"status": {
"type": "object",
"enabled" : false,
@ -24,7 +34,7 @@
"throttle_period": {
"type": "string"
},
"condition": {
"transform": {
"type" : "object",
"enabled" : false,
"dynamic" : true
@ -34,11 +44,6 @@
"enabled" : false,
"dynamic" : true
},
"transform": {
"type" : "object",
"enabled" : false,
"dynamic" : true
},
"meta" : {
"type" : "object",
"dynamic": true

View File

@ -18,10 +18,10 @@ import org.elasticsearch.alerts.actions.email.service.Profile;
import org.elasticsearch.alerts.actions.webhook.HttpClient;
import org.elasticsearch.alerts.actions.webhook.WebhookAction;
import org.elasticsearch.alerts.client.AlertsClient;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.search.SearchCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
@ -117,7 +117,7 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
protected BytesReference createAlertSource(String cron, SearchRequest conditionRequest, String conditionScript, Map<String,Object> metadata) throws IOException {
XContentBuilder builder = jsonBuilder();
builder.startObject();
{
builder.startObject("schedule")
.field("cron", cron)
.endObject();
@ -126,23 +126,37 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
builder.field("meta", metadata);
}
builder.startObject("condition");
builder.startObject("script");
builder.startObject("input");
{
builder.startObject("search");
builder.field("request");
AlertUtils.writeSearchRequest(conditionRequest, builder, ToXContent.EMPTY_PARAMS);
builder.field("script", conditionScript);
builder.endObject();
}
builder.endObject();
builder.startObject("condition");
{
builder.startObject("script");
builder.field("script", conditionScript);
builder.endObject();
}
builder.endObject();
builder.startArray("actions");
{
builder.startObject();
{
builder.startObject("index");
builder.field("index", "my-index");
builder.field("type", "trail");
builder.endObject();
}
builder.endObject();
}
builder.endArray();
}
builder.endObject();
return builder.bytes();
@ -151,7 +165,7 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
public static SearchRequest createConditionSearchRequest(String... indices) {
SearchRequest request = new SearchRequest(indices);
request.indicesOptions(AlertUtils.DEFAULT_INDICES_OPTIONS);
request.searchType(SearchCondition.DEFAULT_SEARCH_TYPE);
request.searchType(SearchInput.DEFAULT_SEARCH_TYPE);
return request;
}
@ -159,7 +173,7 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
SearchRequest conditionRequest = createConditionSearchRequest("my-condition-index").source(searchSource().query(matchAllQuery()));
SearchRequest transformRequest = createConditionSearchRequest("my-payload-index").source(searchSource().query(matchAllQuery()));
transformRequest.searchType(SearchTransform.DEFAULT_SEARCH_TYPE);
conditionRequest.searchType(SearchCondition.DEFAULT_SEARCH_TYPE);
conditionRequest.searchType(SearchInput.DEFAULT_SEARCH_TYPE);
List<Action> actions = new ArrayList<>();
@ -190,13 +204,10 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
return new Alert(
alertName,
new CronSchedule("0/5 * * * * ? *"),
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
new TimeValue(0),
new Actions(actions),
metadata,
new Alert.Status()
new SearchInput(logger, scriptService(), ClientProxy.of(client()),
conditionRequest),
new ScriptCondition(logger, scriptService(), "return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest), new Actions(actions), metadata, new Alert.Status(), new TimeValue(0)
);
}
@ -266,7 +277,7 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
.get();
assertThat(searchResponse.getHits().getTotalHits(), greaterThanOrEqualTo(minimumExpectedAlertActionsWithActionPerformed));
if (assertConditionMet) {
assertThat((Integer) XContentMapValues.extractValue("alert_execution.condition_result.script.payload.hits.total", searchResponse.getHits().getAt(0).sourceAsMap()), greaterThanOrEqualTo(1));
assertThat((Integer) XContentMapValues.extractValue("alert_execution.input_result.search.payload.hits.total", searchResponse.getHits().getAt(0).sourceAsMap()), greaterThanOrEqualTo(1));
}
}
});

View File

@ -15,9 +15,10 @@ import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.Actions;
import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.client.AlertsClient;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
@ -64,15 +65,12 @@ public class AlertThrottleTests extends AbstractAlertingTests {
actions.add(new IndexAction(logger, ClientProxy.of(client()), "action-index", "action-type"));
Alert alert = new Alert(
"test-serialization",
"test-ack-throttle",
new CronSchedule("0/5 * * * * ? *"),
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
new TimeValue(0),
new Actions(actions),
null,
new Alert.Status()
new SearchInput(logger, scriptService(), ClientProxy.of(client()),
request),
new ScriptCondition(logger, scriptService(), "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request), new Actions(actions), null, new Alert.Status(), new TimeValue(0)
);
@ -149,13 +147,10 @@ public class AlertThrottleTests extends AbstractAlertingTests {
Alert alert = new Alert(
"test-time-throttle",
new CronSchedule("0/5 * * * * ? *"),
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
new SearchInput(logger, scriptService(), ClientProxy.of(client()), request),
new ScriptCondition(logger, scriptService(), "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
new TimeValue(10, TimeUnit.SECONDS),
new Actions(actions),
null,
new Alert.Status()
new Actions(actions), null, new Alert.Status(), new TimeValue(10, TimeUnit.SECONDS)
);
@ -198,7 +193,6 @@ public class AlertThrottleTests extends AbstractAlertingTests {
assertThat(countResponse.getCount(), lessThanOrEqualTo(4L));
CountResponse countOfThrottledActions = client()
.prepareCount(HistoryStore.ALERT_HISTORY_INDEX_PREFIX + "*")
.setQuery(QueryBuilders.matchQuery(FiredAlert.Parser.STATE_FIELD.getPreferredName(), FiredAlert.State.THROTTLED.id()))

View File

@ -11,9 +11,10 @@ import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.Actions;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
@ -25,7 +26,6 @@ import org.elasticsearch.common.joda.time.DateTimeZone;
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.index.query.QueryBuilders;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.junit.annotations.TestLogging;
@ -78,14 +78,12 @@ public class BootStrapTest extends AbstractAlertingTests {
SearchRequest searchRequest = createConditionSearchRequest("my-index").source(searchSource().query(termQuery("field", "value")));
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? 2035"),
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
new CronSchedule("0/5 * * * * ? 2035"), //Set this into the future so we don't get any extra runs
new SearchInput(logger, scriptService(), ClientProxy.of(client()), searchRequest),
new ScriptCondition(logger, scriptService(), "return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
new TimeValue(0),
new Actions(new ArrayList<Action>()),
null,
new Alert.Status()
new Actions(new ArrayList<Action>()), null, new Alert.Status(), new TimeValue(0)
);
XContentBuilder builder = jsonBuilder().value(alert);
@ -139,13 +137,14 @@ public class BootStrapTest extends AbstractAlertingTests {
Alert alert = new Alert(
"action-test-"+ i + " " + j,
new CronSchedule("0/5 * * * * ? 2035"), //Set a cron schedule far into the future so this alert is never scheduled
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchInput(logger, scriptService(), ClientProxy.of(client()),
searchRequest),
new ScriptCondition(logger, scriptService(), "return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
new TimeValue(0),
new Actions(new ArrayList<Action>()),
null,
new Alert.Status()
new Alert.Status(),
new TimeValue(0)
);
XContentBuilder jsonBuilder = jsonBuilder();
alert.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
@ -154,9 +153,13 @@ public class BootStrapTest extends AbstractAlertingTests {
assertTrue(putAlertResponse.indexResponse().isCreated());
FiredAlert firedAlert = new FiredAlert(alert, historyIndexDate, historyIndexDate);
XContentBuilder jsonBuilder2 = jsonBuilder();
firedAlert.toXContent(jsonBuilder2, ToXContent.EMPTY_PARAMS);
IndexResponse indexResponse = client().prepareIndex(actionHistoryIndex, HistoryStore.ALERT_HISTORY_TYPE, firedAlert.id())
.setConsistencyLevel(WriteConsistencyLevel.ALL)
.setSource(XContentFactory.jsonBuilder().value(firedAlert))
.setSource(jsonBuilder2.bytes())
.get();
assertTrue(indexResponse.isCreated());
}

View File

@ -10,7 +10,8 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.Actions;
import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
@ -58,13 +59,11 @@ public class TransformSearchTest extends AbstractAlertingTests {
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? *"),
new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchInput(logger, scriptService(), ClientProxy.of(client()),
conditionRequest),
new ScriptCondition(logger, scriptService(), "return true", ScriptService.ScriptType.INLINE, "groovy"),
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
new TimeValue(0),
new Actions(actions),
metadata,
new Alert.Status()
new Actions(actions), metadata, new Alert.Status(), new TimeValue(0)
);
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();

View File

@ -10,7 +10,9 @@ import org.elasticsearch.alerts.AbstractAlertingTests;
import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.script.ScriptCondition;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.search.SearchInput;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
@ -56,19 +58,21 @@ public class ActionsTest extends AbstractAlertingTests {
final List<Action> actionList = new ArrayList<>();
actionList.add(alertAction);
Condition alertCondition = new ScriptSearchCondition(logger, scriptService(),
ClientProxy.of(client()), createConditionSearchRequest(), "return true", ScriptService.ScriptType.INLINE, "groovy");
Input alertInput = new SearchInput(logger, scriptService(), ClientProxy.of(client()),
createConditionSearchRequest());
Condition alertCondition = new ScriptCondition(logger, scriptService(), "return true", ScriptService.ScriptType.INLINE, "groovy");
Alert alert = new Alert(
"my-first-alert",
new CronSchedule("0/5 * * * * ? *"),
alertInput,
alertCondition,
new SearchTransform(logger, scriptService(), ClientProxy.of(client()), createConditionSearchRequest()),
new TimeValue(0),
new Actions(actionList),
null,
new Alert.Status());
new Actions(actionList), null, new Alert.Status(), new TimeValue(0)
);
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
alert.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);

View File

@ -0,0 +1,160 @@
/*
* 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.alerts.condition.script;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.env.Environment;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
*/
public class ScriptConditionTests extends ElasticsearchTestCase {
ThreadPool tp = null;
@Before
public void init() {
tp = new ThreadPool(ThreadPool.Names.SAME);
}
@After
public void cleanup() {
tp.shutdownNow();
}
@Test
public void testExecute() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ScriptCondition condition = new ScriptCondition(logger, scriptService, "hits.total > 1", ScriptService.ScriptType.INLINE, "groovy");
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
assertFalse(condition.processPayload(new Payload.ActionResponse(response)).met());
}
@Test
public void testParser_Valid() throws Exception {
ScriptCondition.Parser conditionParser = new ScriptCondition.Parser(ImmutableSettings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = createConditionContent("hits.total > 1", null, null);
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
ScriptCondition condition = conditionParser.parse(parser);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
assertFalse(condition.processPayload(new Payload.ActionResponse(response)).met());
builder = createConditionContent("return true", null, null);
parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
condition = conditionParser.parse(parser);
assertTrue(condition.processPayload(new Payload.ActionResponse(response)).met());
}
@Test(expected = ConditionException.class)
public void testParser_InValid() throws Exception {
ScriptCondition.Parser conditionParser = new ScriptCondition.Parser(ImmutableSettings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject().endObject();
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
try {
conditionParser.parse(parser);
} catch (Throwable t) {
throw t;
}
fail("expected a condition exception trying to parse an invalid condition XContent");
}
@Test
public void testScriptResultParser_Valid() throws Exception {
ScriptCondition.Parser conditionParser = new ScriptCondition.Parser(ImmutableSettings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.field("met", true );
builder.endObject();
ScriptCondition.Result scriptResult = conditionParser.parseResult(XContentFactory.xContent(builder.bytes()).createParser(builder.bytes()));
assertTrue(scriptResult.met());
builder = jsonBuilder();
builder.startObject();
builder.field("met", false );
builder.endObject();
scriptResult = conditionParser.parseResult(XContentFactory.xContent(builder.bytes()).createParser(builder.bytes()));
assertFalse(scriptResult.met());
}
@Test(expected = ConditionException.class)
public void testScriptResultParser_Invalid() throws Exception {
ScriptCondition.Parser conditionParser = new ScriptCondition.Parser(ImmutableSettings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = jsonBuilder();
builder.startObject().endObject();
try {
conditionParser.parseResult(XContentFactory.xContent(builder.bytes()).createParser(builder.bytes()));
} catch (Throwable t) {
throw t;
}
fail("expected a condition exception trying to parse an invalid condition XContent");
}
private static ScriptServiceProxy getScriptServiceProxy(ThreadPool tp) {
Settings settings = ImmutableSettings.settingsBuilder().build();
GroovyScriptEngineService groovyScriptEngineService = new GroovyScriptEngineService(settings);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(groovyScriptEngineService);
return ScriptServiceProxy.of(new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, tp)));
}
private static XContentBuilder createConditionContent(String script, String scriptLang, ScriptService.ScriptType scriptType) throws IOException {
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field("script");
jsonBuilder.startObject();
jsonBuilder.field("script", script);
if (scriptLang != null) {
jsonBuilder.field("script_lang", scriptLang);
}
if (scriptType != null) {
jsonBuilder.field("script_type", scriptType.toString());
}
jsonBuilder.endObject();
jsonBuilder.endObject();
return jsonBuilder;
}
}

View File

@ -1,96 +0,0 @@
/*
* 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.alerts.condition.search;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.alerts.AbstractAlertingTests;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.settings.ImmutableSettings;
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.XContentParser;
import org.elasticsearch.env.Environment;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
public class SearchConditionTests extends ElasticsearchTestCase {
private XContentBuilder createConditionContent(String script, String scriptLang, ScriptService.ScriptType scriptType, SearchRequest request) throws IOException {
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field("script");
jsonBuilder.startObject();
jsonBuilder.field("script", script);
if (scriptLang != null) {
jsonBuilder.field("script_lang", scriptLang);
}
if (scriptType != null) {
jsonBuilder.field("script_type", scriptType.toString());
}
jsonBuilder.field(ScriptSearchCondition.Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request, jsonBuilder, ToXContent.EMPTY_PARAMS);
jsonBuilder.endObject();
jsonBuilder.endObject();
return jsonBuilder;
}
public void testInlineScriptConditions() throws Exception {
Settings settings = ImmutableSettings.settingsBuilder().build();
GroovyScriptEngineService groovyScriptEngineService = new GroovyScriptEngineService(settings);
ThreadPool tp = new ThreadPool(ThreadPool.Names.SAME);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(groovyScriptEngineService);
ScriptService scriptService = new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, tp));
ScriptSearchCondition.Parser conditionParser = new ScriptSearchCondition.Parser(settings, null, ScriptServiceProxy.of(scriptService));
try {
XContentBuilder builder = createConditionContent("hits.total > 1", null, null, AbstractAlertingTests.createConditionSearchRequest());
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
ScriptSearchCondition condition = conditionParser.parse(parser);
SearchRequest request = new SearchRequest();
request.indices("my-index");
request.types("my-type");
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
XContentBuilder responseBuilder = jsonBuilder().startObject().value(response).endObject();
assertFalse(condition.processSearchResponse(response).met());
builder = createConditionContent("return true", null, null, AbstractAlertingTests.createConditionSearchRequest());
parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
condition = conditionParser.parse(parser);
assertTrue(condition.processSearchResponse(response).met());
tp.shutdownNow();
} catch (IOException ioe) {
throw new ElasticsearchException("Failed to construct the condition", ioe);
}
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.alerts.condition.simple;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
*/
public class TestAlwaysFalseCondition extends ElasticsearchTestCase {
@Test
public void testExecute() throws Exception {
Condition alwaysTrue = new AlwaysTrueCondition(logger);
assertTrue(alwaysTrue.execute(null).met());
}
@Test
public void testParser_Valid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
Condition alwaysTrue = p.parse(xp);
assertTrue(alwaysTrue.execute(null).met());
}
@Test(expected = ConditionException.class)
public void testParser_Invalid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.field("foo", "bar");
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
p.parse(xp);
fail("expected a condition exception trying to parse an invalid condition XContent, ["
+ AlwaysTrueCondition.TYPE + "] condition should not parse with a body");
}
@Test
public void testResultParser_Valid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
Condition.Result alwaysTrueResult = p.parseResult(xp);
assertTrue(alwaysTrueResult.met());
}
@Test(expected = ConditionException.class)
public void testResultParser_Invalid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.field("met", false );
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
p.parseResult(xp);
fail("expected a condition exception trying to parse an invalid condition result XContent, ["
+ AlwaysTrueCondition.TYPE + "] condition result should not parse with a [met] field");
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.alerts.condition.simple;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.ConditionException;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
*/
public class TestAlwaysTrueCondition extends ElasticsearchTestCase {
@Test
public void testExecute() throws Exception {
Condition alwaysTrue = new AlwaysTrueCondition(logger);
assertTrue(alwaysTrue.execute(null).met());
}
@Test
public void testParser_Valid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
Condition alwaysTrue = p.parse(xp);
assertTrue(alwaysTrue.execute(null).met());
}
@Test(expected = ConditionException.class)
public void testParser_Invalid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.field("foo", "bar");
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
p.parse(xp);
fail("expected a condition exception trying to parse an invalid condition XContent, ["
+ AlwaysTrueCondition.TYPE + "] condition should not parse with a body");
}
@Test
public void testResultParser_Valid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
Condition.Result alwaysTrueResult = p.parseResult(xp);
assertTrue(alwaysTrueResult.met());
}
@Test(expected = ConditionException.class)
public void testResultParser_Invalid() throws Exception {
Condition.Parser p = new AlwaysTrueCondition.Parser(ImmutableSettings.settingsBuilder().build());
XContentBuilder builder = jsonBuilder();
builder.startObject();
builder.field("met", false );
builder.endObject();
XContentParser xp = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
xp.nextToken();
p.parseResult(xp);
fail("expected a condition exception trying to parse an invalid condition result XContent, ["
+ AlwaysTrueCondition.TYPE + "] condition result should not parse with a [met] field");
}
}

View File

@ -8,11 +8,12 @@ package org.elasticsearch.alerts.history;
import org.elasticsearch.alerts.*;
import org.elasticsearch.alerts.actions.email.EmailAction;
import org.elasticsearch.alerts.actions.webhook.WebhookAction;
import org.elasticsearch.alerts.throttle.Throttler;
import org.elasticsearch.alerts.condition.Condition;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.condition.search.SearchCondition;
import org.elasticsearch.alerts.condition.simple.SimpleCondition;
import org.elasticsearch.alerts.condition.simple.AlwaysFalseCondition;
import org.elasticsearch.alerts.condition.simple.AlwaysTrueCondition;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.simple.SimpleInput;
import org.elasticsearch.alerts.throttle.Throttler;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -47,8 +48,10 @@ public class FiredAlertTest extends AbstractAlertingTests {
ExecutionContext ctx = new ExecutionContext(firedAlert.id(), alert, new DateTime(), new DateTime());
ctx.onActionResult(new EmailAction.Result.Failure("failed to send because blah"));
ctx.onActionResult(new WebhookAction.Result.Executed(300, "http://localhost:8000/alertfoo", "{'awesome' : 'us'}"));
Condition.Result conditionResult = new SimpleCondition.Result(new Payload.Simple());
Input.Result inputResult = new SimpleInput.Result(SimpleInput.TYPE, new Payload.Simple());
Condition.Result conditionResult = AlwaysTrueCondition.RESULT;
ctx.onThrottleResult(Throttler.NO_THROTTLE.throttle(ctx));
ctx.onInputResult(inputResult);
ctx.onConditionResult(conditionResult);
firedAlert.update(new AlertExecution(ctx));
@ -67,8 +70,10 @@ public class FiredAlertTest extends AbstractAlertingTests {
ExecutionContext ctx = new ExecutionContext(firedAlert.id(), alert, new DateTime(), new DateTime());
ctx.onActionResult(new EmailAction.Result.Failure("failed to send because blah"));
ctx.onActionResult(new WebhookAction.Result.Executed(300, "http://localhost:8000/alertfoo", "{'awesome' : 'us'}"));
Condition.Result conditionResult = new SearchCondition.Result(ScriptSearchCondition.TYPE, true, createConditionSearchRequest(), new Payload.Simple());
Input.Result inputResult = new SimpleInput.Result(SimpleInput.TYPE, new Payload.Simple());
Condition.Result conditionResult = AlwaysFalseCondition.RESULT;
ctx.onThrottleResult(Throttler.NO_THROTTLE.throttle(ctx));
ctx.onInputResult(inputResult);
ctx.onConditionResult(conditionResult);
firedAlert.update(new AlertExecution(ctx));

View File

@ -0,0 +1,153 @@
/*
* 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.alerts.input.search;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputException;
import org.elasticsearch.alerts.support.AlertUtils;
import org.elasticsearch.alerts.support.Variables;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.joda.time.DateTimeZone;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.hamcrest.Matchers.equalTo;
/**
*/
public class SearchInputTests extends ElasticsearchIntegrationTest {
@Test
public void testExecute() throws Exception {
SearchSourceBuilder searchSourceBuilder = searchSource().query(
filteredQuery(matchQuery("event_type", "a"), rangeFilter("_timestamp").from("{{" + Variables.SCHEDULED_FIRE_TIME + "}}||-30s").to("{{" + Variables.SCHEDULED_FIRE_TIME + "}}"))
);
SearchRequest request = client()
.prepareSearch()
.setSearchType(SearchInput.DEFAULT_SEARCH_TYPE)
.request()
.source(searchSourceBuilder);
SearchInput searchInput = new SearchInput(logger,
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
ClientProxy.of(client()), request);
ExecutionContext ctx = new ExecutionContext("test-alert", null,
new DateTime(0, DateTimeZone.UTC), new DateTime(0, DateTimeZone.UTC));
SearchInput.Result result = searchInput.execute(ctx);
assertThat((Integer) XContentMapValues.extractValue("hits.total", result.payload().data()), equalTo(0));
assertNotNull(result.request());
assertEquals(result.request().searchType(),request.searchType());
assertArrayEquals(result.request().indices(), request.indices());
assertEquals(result.request().indicesOptions(), request.indicesOptions());
}
@Test
public void testParser_Valid() throws Exception {
SearchSourceBuilder searchSourceBuilder = searchSource().query(
filteredQuery(matchQuery("event_type", "a"), rangeFilter("_timestamp").from("{{" + Variables.SCHEDULED_FIRE_TIME + "}}||-30s").to("{{" + Variables.SCHEDULED_FIRE_TIME + "}}"))
);
SearchRequest request = client()
.prepareSearch()
.setSearchType(SearchInput.DEFAULT_SEARCH_TYPE)
.request()
.source(searchSourceBuilder);
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field(SearchInput.Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request, jsonBuilder, ToXContent.EMPTY_PARAMS);
jsonBuilder.endObject();
Input.Parser searchInputParser = new SearchInput.Parser(ImmutableSettings.settingsBuilder().build(),
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
ClientProxy.of(client()));
Input searchInput = searchInputParser.parse(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
assertEquals(SearchInput.TYPE, searchInput.type());
}
@Test(expected = InputException.class)
public void testParser_Invalid() throws Exception {
Input.Parser searchInputParser = new SearchInput.Parser(ImmutableSettings.settingsBuilder().build(),
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
ClientProxy.of(client()));
Map<String, Object> data = new HashMap<>();
data.put("foo", "bar");
data.put("baz", new ArrayList<String>() );
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field(Input.Result.PAYLOAD_FIELD.getPreferredName(), data);
jsonBuilder.endObject();
searchInputParser.parseResult(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
fail("result parsing should fail if payload is provided but request is missing");
}
@Test
public void testResultParser() throws Exception {
Map<String, Object> data = new HashMap<>();
data.put("foo", "bar");
data.put("baz", new ArrayList<String>() );
SearchSourceBuilder searchSourceBuilder = searchSource().query(
filteredQuery(matchQuery("event_type", "a"), rangeFilter("_timestamp").from("{{" + Variables.SCHEDULED_FIRE_TIME + "}}||-30s").to("{{" + Variables.SCHEDULED_FIRE_TIME + "}}"))
);
SearchRequest request = client()
.prepareSearch()
.setSearchType(SearchInput.DEFAULT_SEARCH_TYPE)
.request()
.source(searchSourceBuilder);
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field(Input.Result.PAYLOAD_FIELD.getPreferredName(), data);
jsonBuilder.field(SearchInput.Parser.REQUEST_FIELD.getPreferredName());
AlertUtils.writeSearchRequest(request, jsonBuilder, ToXContent.EMPTY_PARAMS);
jsonBuilder.endObject();
Input.Parser searchInputParser = new SearchInput.Parser(ImmutableSettings.settingsBuilder().build(),
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
ClientProxy.of(client()));
Input.Result result = searchInputParser.parseResult(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
assertEquals(SearchInput.TYPE, result.type());
assertEquals(result.payload().data().get("foo"), "bar");
List baz = (List)result.payload().data().get("baz");
assertTrue(baz.isEmpty());
assertTrue(result instanceof SearchInput.Result);
SearchInput.Result searchInputResult = (SearchInput.Result) result;
assertNotNull(searchInputResult.request());
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.alerts.input.simple;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.input.Input;
import org.elasticsearch.alerts.input.InputException;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
*/
public class SimpleInputTests extends ElasticsearchTestCase {
@Test
public void textExecute() throws Exception {
Map<String, Object> data = new HashMap<>();
data.put("foo", "bar");
data.put("baz", new ArrayList<String>() );
Input staticInput = new SimpleInput(logger, new Payload.Simple(data));
Input.Result staticResult = staticInput.execute(null);
assertEquals(staticResult.payload().data().get("foo"), "bar");
List baz = (List)staticResult.payload().data().get("baz");
assertTrue(baz.isEmpty());
}
@Test
public void testParser_Valid() throws Exception {
Map<String, Object> data = new HashMap<>();
data.put("foo", "bar");
data.put("baz", new ArrayList<String>() );
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field(Input.Result.PAYLOAD_FIELD.getPreferredName(), data);
jsonBuilder.endObject();
Input.Parser parser = new SimpleInput.Parser(ImmutableSettings.builder().build());
Input input = parser.parse(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
assertEquals(input.type(), SimpleInput.TYPE);
Input.Result staticResult = input.execute(null);
assertEquals(staticResult.payload().data().get("foo"), "bar");
List baz = (List)staticResult.payload().data().get("baz");
assertTrue(baz.isEmpty());
}
@Test(expected = InputException.class)
public void testParser_Invalid() throws Exception {
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.endObject();
Input.Parser parser = new SimpleInput.Parser(ImmutableSettings.builder().build());
parser.parse(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
fail("[simple] input parse should fail with an InputException for an empty json object");
}
@Test
public void testResultParser_Valid() throws Exception {
Map<String, Object> data = new HashMap<>();
data.put("foo", "bar");
data.put("baz", new ArrayList<String>() );
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field(Input.Result.PAYLOAD_FIELD.getPreferredName(), data);
jsonBuilder.endObject();
Input.Parser parser = new SimpleInput.Parser(ImmutableSettings.builder().build());
Input.Result staticResult = parser.parseResult(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
assertEquals(staticResult.type(), SimpleInput.TYPE);
assertEquals(staticResult.payload().data().get("foo"), "bar");
List baz = (List)staticResult.payload().data().get("baz");
assertTrue(baz.isEmpty());
}
@Test(expected = InputException.class)
public void testResultParser_Invalid() throws Exception {
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.endObject();
Input.Parser parser = new SimpleInput.Parser(ImmutableSettings.builder().build());
parser.parseResult(XContentFactory.xContent(jsonBuilder.bytes()).createParser(jsonBuilder.bytes()));
fail("[simple] input result parse should fail with an InputException for an empty json object");
}
}