diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/Action.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/Action.java
index f8d9aa90ecc..debf8bf9ab6 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/Action.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/Action.java
@@ -27,6 +27,7 @@ public interface Action extends ToXContent {
FAILURE,
PARTIAL_FAILURE,
THROTTLED,
+ CONDITION_FAILED,
SIMULATED;
@Override
@@ -51,12 +52,17 @@ public interface Action extends ToXContent {
return status;
}
- public static class Failure extends Result {
+ /**
+ * {@code StoppedResult} is a {@link Result} with a {@link #reason()}.
+ *
+ * Any {@code StoppedResult} should provide a reason why it is stopped.
+ */
+ public static class StoppedResult extends Result {
private final String reason;
- public Failure(String type, String reason, Object... args) {
- super(type, Status.FAILURE);
+ protected StoppedResult(String type, Status status, String reason, Object... args) {
+ super(type, status);
this.reason = LoggerMessageFormat.format(reason, args);
}
@@ -68,25 +74,42 @@ public interface Action extends ToXContent {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
}
+
}
- public static class Throttled extends Result {
+ /**
+ * {@code Failure} is a {@link StoppedResult} with a status of {@link Status#FAILURE} for actiosn that have failed unexpectedly
+ * (e.g., an exception was thrown in a place that wouldn't expect one, like transformation or an HTTP request).
+ */
+ public static class Failure extends StoppedResult {
- private final String reason;
+ public Failure(String type, String reason, Object... args) {
+ super(type, Status.FAILURE, reason, args);
+ }
+
+ }
+
+ /**
+ * {@code Throttled} is a {@link StoppedResult} with a status of {@link Status#THROTTLED} for actions that have been throttled.
+ */
+ public static class Throttled extends StoppedResult {
public Throttled(String type, String reason) {
- super(type, Status.THROTTLED);
- this.reason = reason;
+ super(type, Status.THROTTLED, reason);
}
- public String reason() {
- return reason;
+ }
+
+ /**
+ * {@code ConditionFailed} is a {@link StoppedResult} with a status of {@link Status#FAILURE} for actions that have been skipped
+ * because the action's condition failed (either expected or unexpected).
+ */
+ public static class ConditionFailed extends StoppedResult {
+
+ public ConditionFailed(String type, String reason, Object... args) {
+ super(type, Status.CONDITION_FAILED, reason, args);
}
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- return builder.field(Field.REASON.getPreferredName(), reason);
- }
}
}
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionRegistry.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionRegistry.java
index 018420f4273..0579ac92ead 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionRegistry.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionRegistry.java
@@ -10,6 +10,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.support.clock.Clock;
+import org.elasticsearch.xpack.watcher.condition.ConditionRegistry;
import org.elasticsearch.xpack.watcher.support.validation.Validation;
import org.elasticsearch.xpack.watcher.transform.TransformRegistry;
@@ -23,14 +24,18 @@ import java.util.Map;
public class ActionRegistry {
private final Map parsers;
+ private final ConditionRegistry conditionRegistry;
private final TransformRegistry transformRegistry;
private final Clock clock;
private final XPackLicenseState licenseState;
@Inject
- public ActionRegistry(Map parsers, TransformRegistry transformRegistry, Clock clock,
+ public ActionRegistry(Map parsers,
+ ConditionRegistry conditionRegistry, TransformRegistry transformRegistry,
+ Clock clock,
XPackLicenseState licenseState) {
this.parsers = parsers;
+ this.conditionRegistry = conditionRegistry;
this.transformRegistry = transformRegistry;
this.clock = clock;
this.licenseState = licenseState;
@@ -57,8 +62,7 @@ public class ActionRegistry {
throw new ElasticsearchParseException("could not parse action [{}] for watch [{}]. {}", id, watchId, error);
}
} else if (token == XContentParser.Token.START_OBJECT && id != null) {
- ActionWrapper action = ActionWrapper.parse(watchId, id, parser, this, transformRegistry, clock, licenseState);
- actions.add(action);
+ actions.add(ActionWrapper.parse(watchId, id, parser, this, conditionRegistry, transformRegistry, clock, licenseState));
}
}
return new ExecutableActions(actions);
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionWrapper.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionWrapper.java
index d0a1811ec3a..c113b6df343 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionWrapper.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionWrapper.java
@@ -17,6 +17,9 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.watcher.actions.throttler.ActionThrottler;
import org.elasticsearch.xpack.watcher.actions.throttler.Throttler;
+import org.elasticsearch.xpack.watcher.condition.Condition;
+import org.elasticsearch.xpack.watcher.condition.ConditionRegistry;
+import org.elasticsearch.xpack.watcher.condition.ExecutableCondition;
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
import org.elasticsearch.xpack.watcher.support.WatcherDateTimeUtils;
import org.elasticsearch.xpack.support.clock.Clock;
@@ -24,6 +27,7 @@ import org.elasticsearch.xpack.watcher.transform.ExecutableTransform;
import org.elasticsearch.xpack.watcher.transform.Transform;
import org.elasticsearch.xpack.watcher.transform.TransformRegistry;
import org.elasticsearch.xpack.watcher.watch.Payload;
+import org.elasticsearch.xpack.watcher.watch.Watch;
import java.io.IOException;
@@ -33,16 +37,23 @@ import java.io.IOException;
public class ActionWrapper implements ToXContent {
private String id;
- @Nullable private final ExecutableTransform transform;
+ @Nullable
+ private final ExecutableCondition condition;
+ @Nullable
+ private final ExecutableTransform transform;
private final ActionThrottler throttler;
private final ExecutableAction action;
public ActionWrapper(String id, ExecutableAction action) {
- this(id, null, null, action);
+ this(id, null, null, null, action);
}
- public ActionWrapper(String id, ActionThrottler throttler, @Nullable ExecutableTransform transform, ExecutableAction action) {
+ public ActionWrapper(String id, ActionThrottler throttler,
+ @Nullable ExecutableCondition condition,
+ @Nullable ExecutableTransform transform,
+ ExecutableAction action) {
this.id = id;
+ this.condition = condition;
this.throttler = throttler;
this.transform = transform;
this.action = action;
@@ -52,6 +63,10 @@ public class ActionWrapper implements ToXContent {
return id;
}
+ public ExecutableCondition condition() {
+ return condition;
+ }
+
public ExecutableTransform transform() {
return transform;
}
@@ -64,7 +79,21 @@ public class ActionWrapper implements ToXContent {
return action;
}
- public ActionWrapper.Result execute(WatchExecutionContext ctx) throws IOException {
+ /**
+ * Execute the current {@link #action()}.
+ *
+ * This executes in the order of:
+ *
+ * - Throttling
+ * - Conditional Check
+ * - Transformation
+ * - Action
+ *
+ *
+ * @param ctx The current watch's context
+ * @return Never {@code null}
+ */
+ public ActionWrapper.Result execute(WatchExecutionContext ctx) {
ActionWrapper.Result result = ctx.actionsResults().get(id);
if (result != null) {
return result;
@@ -75,6 +104,20 @@ public class ActionWrapper implements ToXContent {
return new ActionWrapper.Result(id, new Action.Result.Throttled(action.type(), throttleResult.reason()));
}
}
+ Condition.Result conditionResult = null;
+ if (condition != null) {
+ try {
+ conditionResult = condition.execute(ctx);
+ if (conditionResult.met() == false) {
+ return new ActionWrapper.Result(id, conditionResult, null,
+ new Action.Result.ConditionFailed(action.type(), "condition not met. skipping"));
+ }
+ } catch (RuntimeException e) {
+ action.logger().error("failed to execute action [{}/{}]. failed to execute condition", e, ctx.watch().id(), id);
+ return new ActionWrapper.Result(id, new Action.Result.ConditionFailed(action.type(),
+ "condition failed. skipping: {}", e.getMessage()));
+ }
+ }
Payload payload = ctx.payload();
Transform.Result transformResult = null;
if (transform != null) {
@@ -84,18 +127,19 @@ public class ActionWrapper implements ToXContent {
action.logger().error("failed to execute action [{}/{}]. failed to transform payload. {}", ctx.watch().id(), id,
transformResult.reason());
String msg = "Failed to transform payload";
- return new ActionWrapper.Result(id, transformResult, new Action.Result.Failure(action.type(), msg));
+ return new ActionWrapper.Result(id, conditionResult, transformResult, new Action.Result.Failure(action.type(), msg));
}
payload = transformResult.payload();
} catch (Exception e) {
action.logger().error("failed to execute action [{}/{}]. failed to transform payload.", e, ctx.watch().id(), id);
- return new ActionWrapper.Result(id, new Action.Result.Failure(action.type(), "Failed to transform payload. error: " +
- ExceptionsHelper.detailedMessage(e)));
+ return new ActionWrapper.Result(id, conditionResult, null,
+ new Action.Result.Failure(action.type(), "Failed to transform payload. error: {}",
+ ExceptionsHelper.detailedMessage(e)));
}
}
try {
Action.Result actionResult = action.execute(id, ctx, payload);
- return new ActionWrapper.Result(id, transformResult, actionResult);
+ return new ActionWrapper.Result(id, conditionResult, transformResult, actionResult);
} catch (Exception e) {
action.logger().error("failed to execute action [{}/{}]", e, ctx.watch().id(), id);
return new ActionWrapper.Result(id, new Action.Result.Failure(action.type(), ExceptionsHelper.detailedMessage(e)));
@@ -110,6 +154,7 @@ public class ActionWrapper implements ToXContent {
ActionWrapper that = (ActionWrapper) o;
if (!id.equals(that.id)) return false;
+ if (condition != null ? !condition.equals(that.condition) : that.condition != null) return false;
if (transform != null ? !transform.equals(that.transform) : that.transform != null) return false;
return action.equals(that.action);
}
@@ -117,6 +162,7 @@ public class ActionWrapper implements ToXContent {
@Override
public int hashCode() {
int result = id.hashCode();
+ result = 31 * result + (condition != null ? condition.hashCode() : 0);
result = 31 * result + (transform != null ? transform.hashCode() : 0);
result = 31 * result + action.hashCode();
return result;
@@ -129,6 +175,11 @@ public class ActionWrapper implements ToXContent {
if (throttlePeriod != null) {
builder.field(Throttler.Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod);
}
+ if (condition != null) {
+ builder.startObject(Watch.Field.CONDITION.getPreferredName())
+ .field(condition.type(), condition, params)
+ .endObject();
+ }
if (transform != null) {
builder.startObject(Transform.Field.TRANSFORM.getPreferredName())
.field(transform.type(), transform, params)
@@ -139,11 +190,12 @@ public class ActionWrapper implements ToXContent {
}
static ActionWrapper parse(String watchId, String actionId, XContentParser parser,
- ActionRegistry actionRegistry, TransformRegistry transformRegistry,
+ ActionRegistry actionRegistry, ConditionRegistry conditionRegistry, TransformRegistry transformRegistry,
Clock clock, XPackLicenseState licenseState) throws IOException {
assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+ ExecutableCondition condition = null;
ExecutableTransform transform = null;
TimeValue throttlePeriod = null;
ExecutableAction action = null;
@@ -154,7 +206,9 @@ public class ActionWrapper implements ToXContent {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
- if (ParseFieldMatcher.STRICT.match(currentFieldName, Transform.Field.TRANSFORM)) {
+ if (ParseFieldMatcher.STRICT.match(currentFieldName, Watch.Field.CONDITION)) {
+ condition = conditionRegistry.parseExecutable(watchId, parser);
+ } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Transform.Field.TRANSFORM)) {
transform = transformRegistry.parse(watchId, parser);
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Throttler.Field.THROTTLE_PERIOD)) {
try {
@@ -179,21 +233,25 @@ public class ActionWrapper implements ToXContent {
}
ActionThrottler throttler = new ActionThrottler(clock, throttlePeriod, licenseState);
- return new ActionWrapper(actionId, throttler, transform, action);
+ return new ActionWrapper(actionId, throttler, condition, transform, action);
}
public static class Result implements ToXContent {
private final String id;
- @Nullable private final Transform.Result transform;
+ @Nullable
+ private final Condition.Result condition;
+ @Nullable
+ private final Transform.Result transform;
private final Action.Result action;
public Result(String id, Action.Result action) {
- this(id, null, action);
+ this(id, null, null, action);
}
- public Result(String id, @Nullable Transform.Result transform, Action.Result action) {
+ public Result(String id, @Nullable Condition.Result condition, @Nullable Transform.Result transform, Action.Result action) {
this.id = id;
+ this.condition = condition;
this.transform = transform;
this.action = action;
}
@@ -202,6 +260,10 @@ public class ActionWrapper implements ToXContent {
return id;
}
+ public Condition.Result condition() {
+ return condition;
+ }
+
public Transform.Result transform() {
return transform;
}
@@ -218,6 +280,7 @@ public class ActionWrapper implements ToXContent {
Result result = (Result) o;
if (!id.equals(result.id)) return false;
+ if (condition != null ? !condition.equals(result.condition) : result.condition != null) return false;
if (transform != null ? !transform.equals(result.transform) : result.transform != null) return false;
return action.equals(result.action);
}
@@ -225,6 +288,7 @@ public class ActionWrapper implements ToXContent {
@Override
public int hashCode() {
int result = id.hashCode();
+ result = 31 * result + (condition != null ? condition.hashCode() : 0);
result = 31 * result + (transform != null ? transform.hashCode() : 0);
result = 31 * result + action.hashCode();
return result;
@@ -235,7 +299,10 @@ public class ActionWrapper implements ToXContent {
builder.startObject();
builder.field(Field.ID.getPreferredName(), id);
builder.field(Field.TYPE.getPreferredName(), action.type());
- builder.field(Field.STATUS.getPreferredName(), action.status, params);
+ builder.field(Field.STATUS.getPreferredName(), action.status(), params);
+ if (condition != null) {
+ builder.field(Watch.Field.CONDITION.getPreferredName(), condition, params);
+ }
if (transform != null) {
builder.field(Transform.Field.TRANSFORM.getPreferredName(), transform, params);
}
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/client/WatchSourceBuilder.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/client/WatchSourceBuilder.java
index a8fb325e675..3bc6c56aec0 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/client/WatchSourceBuilder.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/client/WatchSourceBuilder.java
@@ -97,12 +97,26 @@ public class WatchSourceBuilder implements ToXContent {
return addAction(id, null, transform.build(), action.build());
}
+ public WatchSourceBuilder addAction(String id, Condition.Builder condition, Action.Builder action) {
+ return addAction(id, null, condition.build(), null, action.build());
+ }
+
public WatchSourceBuilder addAction(String id, TimeValue throttlePeriod, Transform.Builder transform, Action.Builder action) {
return addAction(id, throttlePeriod, transform.build(), action.build());
}
public WatchSourceBuilder addAction(String id, TimeValue throttlePeriod, Transform transform, Action action) {
- actions.put(id, new TransformedAction(id, action, throttlePeriod, transform));
+ actions.put(id, new TransformedAction(id, action, throttlePeriod, null, transform));
+ return this;
+ }
+
+ public WatchSourceBuilder addAction(String id, TimeValue throttlePeriod, Condition.Builder condition, Transform.Builder transform,
+ Action.Builder action) {
+ return addAction(id, throttlePeriod, condition.build(), transform.build(), action.build());
+ }
+
+ public WatchSourceBuilder addAction(String id, TimeValue throttlePeriod, Condition condition, Transform transform, Action action) {
+ actions.put(id, new TransformedAction(id, action, throttlePeriod, condition, transform));
return this;
}
@@ -173,11 +187,14 @@ public class WatchSourceBuilder implements ToXContent {
private final String id;
private final Action action;
@Nullable private final TimeValue throttlePeriod;
+ @Nullable private final Condition condition;
@Nullable private final Transform transform;
- public TransformedAction(String id, Action action, @Nullable TimeValue throttlePeriod, @Nullable Transform transform) {
+ public TransformedAction(String id, Action action, @Nullable TimeValue throttlePeriod,
+ @Nullable Condition condition, @Nullable Transform transform) {
this.id = id;
this.throttlePeriod = throttlePeriod;
+ this.condition = condition;
this.transform = transform;
this.action = action;
}
@@ -188,6 +205,11 @@ public class WatchSourceBuilder implements ToXContent {
if (throttlePeriod != null) {
builder.field(Throttler.Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod);
}
+ if (condition != null) {
+ builder.startObject(Watch.Field.CONDITION.getPreferredName())
+ .field(condition.type(), condition, params)
+ .endObject();
+ }
if (transform != null) {
builder.startObject(Transform.Field.TRANSFORM.getPreferredName())
.field(transform.type(), transform, params)
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/Condition.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/Condition.java
index 7134c65ee22..d930d911b01 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/Condition.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/Condition.java
@@ -31,6 +31,7 @@ public interface Condition extends ToXContent {
protected final boolean met;
public Result(String type, boolean met) {
+ // TODO: FAILURE status is never used, but a some code assumes that it is used
this.status = Status.SUCCESS;
this.type = type;
this.met = met;
@@ -46,7 +47,6 @@ public interface Condition extends ToXContent {
}
public boolean met() {
- assert status == Status.SUCCESS;
return met;
}
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java
index 1447e03a7b8..c3a9fe25e8b 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java
@@ -17,7 +17,6 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.xpack.support.clock.Clock;
import org.elasticsearch.xpack.watcher.Watcher;
-import org.elasticsearch.xpack.watcher.WatcherFeatureSet;
import org.elasticsearch.xpack.common.stats.Counters;
import org.elasticsearch.xpack.watcher.actions.ActionWrapper;
import org.elasticsearch.xpack.watcher.condition.Condition;
@@ -32,7 +31,6 @@ import org.elasticsearch.xpack.watcher.watch.WatchStore;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -349,7 +347,7 @@ public class ExecutionService extends AbstractComponent {
}
}
- WatchRecord executeInner(WatchExecutionContext ctx) throws IOException {
+ WatchRecord executeInner(WatchExecutionContext ctx) {
ctx.start();
Watch watch = ctx.watch();
diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java
index 161acb11ac4..0f4ec07a3ce 100644
--- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java
+++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java
@@ -14,11 +14,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
-import org.elasticsearch.indices.query.IndicesQueriesRegistry;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchRequestParsers;
-import org.elasticsearch.search.aggregations.AggregatorParsers;
-import org.elasticsearch.search.suggest.Suggesters;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy;
import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService;
diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java
index 1eea012f815..97846e9ba02 100644
--- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java
+++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java
@@ -6,6 +6,7 @@
package org.elasticsearch.xpack.watcher.execution;
import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.support.clock.Clock;
@@ -67,7 +68,6 @@ public class ExecutionServiceTests extends ESTestCase {
private Input.Result inputResult;
private WatchStore watchStore;
- private TriggeredWatchStore triggeredWatchStore;
private HistoryStore historyStore;
private WatchLockService watchLockService;
private ExecutionService executionService;
@@ -75,6 +75,8 @@ public class ExecutionServiceTests extends ESTestCase {
@Before
public void init() throws Exception {
+ TriggeredWatchStore triggeredWatchStore;
+
payload = mock(Payload.class);
input = mock(ExecutableInput.class);
inputResult = mock(Input.Result.class);
@@ -87,7 +89,7 @@ public class ExecutionServiceTests extends ESTestCase {
historyStore = mock(HistoryStore.class);
WatchExecutor executor = mock(WatchExecutor.class);
- when(executor.queue()).thenReturn(new ArrayBlockingQueue(1));
+ when(executor.queue()).thenReturn(new ArrayBlockingQueue<>(1));
watchLockService = mock(WatchLockService.class);
clock = new ClockMock();
@@ -95,7 +97,7 @@ public class ExecutionServiceTests extends ESTestCase {
watchLockService, clock);
ClusterState clusterState = mock(ClusterState.class);
- when(triggeredWatchStore.loadTriggeredWatches(clusterState)).thenReturn(new ArrayList());
+ when(triggeredWatchStore.loadTriggeredWatches(clusterState)).thenReturn(new ArrayList<>());
executionService.start(clusterState);
}
@@ -127,11 +129,27 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
+ // action level conditional
+ ExecutableCondition actionCondition = null;
+ Condition.Result actionConditionResult = null;
+
+ if (randomBoolean()) {
+ Tuple pair = whenCondition(context);
+
+ actionCondition = pair.v1();
+ actionConditionResult = pair.v2();
+ }
+
// action level transform
- Transform.Result actionTransformResult = mock(Transform.Result.class);
- when(actionTransformResult.payload()).thenReturn(payload);
- ExecutableTransform actionTransform = mock(ExecutableTransform.class);
- when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
+ ExecutableTransform actionTransform = null;
+ Transform.Result actionTransformResult = null;
+
+ if (randomBoolean()) {
+ Tuple pair = whenTransform(context);
+
+ actionTransform = pair.v1();
+ actionTransformResult = pair.v2();
+ }
// the action
Action.Result actionResult = mock(Action.Result.class);
@@ -141,7 +159,7 @@ public class ExecutionServiceTests extends ESTestCase {
when(action.type()).thenReturn("MY_AWESOME_TYPE");
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(clock.nowUTC())));
@@ -158,6 +176,7 @@ public class ExecutionServiceTests extends ESTestCase {
ActionWrapper.Result result = watchRecord.result().actionsResults().get("_action");
assertThat(result, notNullValue());
assertThat(result.id(), is("_action"));
+ assertThat(result.condition(), sameInstance(actionConditionResult));
assertThat(result.transform(), sameInstance(actionTransformResult));
assertThat(result.action(), sameInstance(actionResult));
@@ -208,11 +227,10 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
- // action level transform
- Transform.Result actionTransformResult = mock(Transform.Result.class);
- when(actionTransformResult.payload()).thenReturn(payload);
- ExecutableTransform actionTransform = mock(ExecutableTransform.class);
- when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
+ // action level condition (unused)
+ ExecutableCondition actionCondition = randomBoolean() ? mock(ExecutableCondition.class) : null;
+ // action level transform (unused)
+ ExecutableTransform actionTransform = randomBoolean() ? mock(ExecutableTransform.class) : null;
// the action
Action.Result actionResult = mock(Action.Result.class);
@@ -221,7 +239,7 @@ public class ExecutionServiceTests extends ESTestCase {
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(clock.nowUTC())));
@@ -276,11 +294,10 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
- // action level transform
- Transform.Result actionTransformResult = mock(Transform.Result.class);
- when(actionTransformResult.payload()).thenReturn(payload);
- ExecutableTransform actionTransform = mock(ExecutableTransform.class);
- when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
+ // action level condition (unused)
+ ExecutableCondition actionCondition = randomBoolean() ? mock(ExecutableCondition.class) : null;
+ // action level transform (unused)
+ ExecutableTransform actionTransform = randomBoolean() ? mock(ExecutableTransform.class) : null;
// the action
Action.Result actionResult = mock(Action.Result.class);
@@ -289,7 +306,7 @@ public class ExecutionServiceTests extends ESTestCase {
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(clock.nowUTC())));
@@ -343,11 +360,10 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
- // action level transform
- Transform.Result actionTransformResult = mock(Transform.Result.class);
- when(actionTransformResult.payload()).thenReturn(payload);
- ExecutableTransform actionTransform = mock(ExecutableTransform.class);
- when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
+ // action level condition (unused)
+ ExecutableCondition actionCondition = randomBoolean() ? mock(ExecutableCondition.class) : null;
+ // action level transform (unused)
+ ExecutableTransform actionTransform = randomBoolean() ? mock(ExecutableTransform.class) : null;
// the action
Action.Result actionResult = mock(Action.Result.class);
@@ -356,7 +372,7 @@ public class ExecutionServiceTests extends ESTestCase {
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(clock.nowUTC())));
@@ -410,6 +426,17 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
+ // action level condition
+ ExecutableCondition actionCondition = null;
+ Condition.Result actionConditionResult = null;
+
+ if (randomBoolean()) {
+ Tuple pair = whenCondition(context);
+
+ actionCondition = pair.v1();
+ actionConditionResult = pair.v2();
+ }
+
// action level transform
Transform.Result actionTransformResult = mock(Transform.Result.class);
when(actionTransformResult.status()).thenReturn(Transform.Result.Status.FAILURE);
@@ -425,7 +452,7 @@ public class ExecutionServiceTests extends ESTestCase {
when(action.logger()).thenReturn(logger);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(clock.nowUTC())));
@@ -442,6 +469,7 @@ public class ExecutionServiceTests extends ESTestCase {
assertThat(watchRecord.result().transformResult(), is(watchTransformResult));
assertThat(watchRecord.result().actionsResults(), notNullValue());
assertThat(watchRecord.result().actionsResults().count(), is(1));
+ assertThat(watchRecord.result().actionsResults().get("_action").condition(), is(actionConditionResult));
assertThat(watchRecord.result().actionsResults().get("_action").transform(), is(actionTransformResult));
assertThat(watchRecord.result().actionsResults().get("_action").action().status(), is(Action.Result.Status.FAILURE));
@@ -476,11 +504,27 @@ public class ExecutionServiceTests extends ESTestCase {
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
+ // action level conditional
+ ExecutableCondition actionCondition = null;
+ Condition.Result actionConditionResult = null;
+
+ if (randomBoolean()) {
+ Tuple pair = whenCondition(context);
+
+ actionCondition = pair.v1();
+ actionConditionResult = pair.v2();
+ }
+
// action level transform
- Transform.Result actionTransformResult = mock(Transform.Result.class);
- when(actionTransformResult.payload()).thenReturn(payload);
- ExecutableTransform actionTransform = mock(ExecutableTransform.class);
- when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
+ ExecutableTransform actionTransform = null;
+ Transform.Result actionTransformResult = null;
+
+ if (randomBoolean()) {
+ Tuple pair = whenTransform(context);
+
+ actionTransform = pair.v1();
+ actionTransformResult = pair.v2();
+ }
// the action
Action.Result actionResult = mock(Action.Result.class);
@@ -489,7 +533,7 @@ public class ExecutionServiceTests extends ESTestCase {
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(now)));
@@ -506,6 +550,7 @@ public class ExecutionServiceTests extends ESTestCase {
ActionWrapper.Result result = watchRecord.result().actionsResults().get("_action");
assertThat(result, notNullValue());
assertThat(result.id(), is("_action"));
+ assertThat(result.condition(), sameInstance(actionConditionResult));
assertThat(result.transform(), sameInstance(actionTransformResult));
assertThat(result.action(), sameInstance(actionResult));
@@ -524,17 +569,20 @@ public class ExecutionServiceTests extends ESTestCase {
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
+ // action throttler
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(true);
when(throttleResult.reason()).thenReturn("_throttle_reason");
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
- ExecutableTransform transform = mock(ExecutableTransform.class);
+ // unused with throttle
+ ExecutableCondition actionCondition = mock(ExecutableCondition.class);
+ ExecutableTransform actionTransform = mock(ExecutableTransform.class);
ExecutableAction action = mock(ExecutableAction.class);
when(action.type()).thenReturn("_type");
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, transform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(now)));
@@ -552,6 +600,7 @@ public class ExecutionServiceTests extends ESTestCase {
ActionWrapper.Result result = watchRecord.result().actionsResults().get("_action");
assertThat(result, notNullValue());
assertThat(result.id(), is("_action"));
+ assertThat(result.condition(), nullValue());
assertThat(result.transform(), nullValue());
assertThat(result.action(), instanceOf(Action.Result.Throttled.class));
Action.Result.Throttled throttled = (Action.Result.Throttled) result.action();
@@ -559,7 +608,8 @@ public class ExecutionServiceTests extends ESTestCase {
verify(condition, times(1)).execute(context);
verify(throttler, times(1)).throttle("_action", context);
- verify(transform, never()).execute(context, payload);
+ verify(actionCondition, never()).execute(context);
+ verify(actionTransform, never()).execute(context, payload);
}
public void testExecuteInnerConditionNotMet() throws Exception {
@@ -568,6 +618,126 @@ public class ExecutionServiceTests extends ESTestCase {
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event, timeValueSeconds(5));
+ Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
+ ExecutableCondition condition = mock(ExecutableCondition.class);
+ when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
+
+ // action throttler
+ Throttler.Result throttleResult = mock(Throttler.Result.class);
+ when(throttleResult.throttle()).thenReturn(false);
+ ActionThrottler throttler = mock(ActionThrottler.class);
+ when(throttler.throttle("_action", context)).thenReturn(throttleResult);
+
+ // action condition (always fails)
+ Condition.Result actionConditionResult = mock(Condition.Result.class);
+ // note: sometimes it can be met _with_ success
+ if (randomBoolean()) {
+ when(actionConditionResult.status()).thenReturn(Condition.Result.Status.SUCCESS);
+ } else {
+ when(actionConditionResult.status()).thenReturn(Condition.Result.Status.FAILURE);
+ }
+ when(actionConditionResult.met()).thenReturn(false);
+ ExecutableCondition actionCondition = mock(ExecutableCondition.class);
+ when(actionCondition.execute(context)).thenReturn(actionConditionResult);
+
+ // unused with failed condition
+ ExecutableTransform actionTransform = mock(ExecutableTransform.class);
+
+ ExecutableAction action = mock(ExecutableAction.class);
+ when(action.type()).thenReturn("_type");
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
+ ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
+
+ WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(now)));
+
+ when(watch.input()).thenReturn(input);
+ when(watch.condition()).thenReturn(condition);
+ when(watch.actions()).thenReturn(actions);
+ when(watch.status()).thenReturn(watchStatus);
+
+ WatchRecord watchRecord = executionService.executeInner(context);
+ assertThat(watchRecord.result().inputResult(), sameInstance(inputResult));
+ assertThat(watchRecord.result().conditionResult(), sameInstance(conditionResult));
+ assertThat(watchRecord.result().transformResult(), nullValue());
+ assertThat(watchRecord.result().actionsResults().count(), is(1));
+ ActionWrapper.Result result = watchRecord.result().actionsResults().get("_action");
+ assertThat(result, notNullValue());
+ assertThat(result.id(), is("_action"));
+ assertThat(result.condition(), sameInstance(actionConditionResult));
+ assertThat(result.transform(), nullValue());
+ assertThat(result.action(), instanceOf(Action.Result.ConditionFailed.class));
+ Action.Result.ConditionFailed conditionFailed = (Action.Result.ConditionFailed) result.action();
+ assertThat(conditionFailed.reason(), is("condition not met. skipping"));
+
+ verify(condition, times(1)).execute(context);
+ verify(throttler, times(1)).throttle("_action", context);
+ verify(actionCondition, times(1)).execute(context);
+ verify(actionTransform, never()).execute(context, payload);
+ }
+
+ public void testExecuteInnerConditionNotMetDueToException() throws Exception {
+ DateTime now = DateTime.now(DateTimeZone.UTC);
+ Watch watch = mock(Watch.class);
+ when(watch.id()).thenReturn(getTestName());
+ ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
+ WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event, timeValueSeconds(5));
+
+ Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
+ ExecutableCondition condition = mock(ExecutableCondition.class);
+ when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
+
+ // action throttler
+ Throttler.Result throttleResult = mock(Throttler.Result.class);
+ when(throttleResult.throttle()).thenReturn(false);
+ ActionThrottler throttler = mock(ActionThrottler.class);
+ when(throttler.throttle("_action", context)).thenReturn(throttleResult);
+
+ // action condition (always fails)
+ ExecutableCondition actionCondition = mock(ExecutableCondition.class);
+ when(actionCondition.execute(context)).thenThrow(new IllegalArgumentException("[expected] failed for test"));
+
+ // unused with failed condition
+ ExecutableTransform actionTransform = mock(ExecutableTransform.class);
+
+ ExecutableAction action = mock(ExecutableAction.class);
+ when(action.type()).thenReturn("_type");
+ when(action.logger()).thenReturn(logger);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
+ ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
+
+ WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(now)));
+
+ when(watch.input()).thenReturn(input);
+ when(watch.condition()).thenReturn(condition);
+ when(watch.actions()).thenReturn(actions);
+ when(watch.status()).thenReturn(watchStatus);
+
+ WatchRecord watchRecord = executionService.executeInner(context);
+ assertThat(watchRecord.result().inputResult(), sameInstance(inputResult));
+ assertThat(watchRecord.result().conditionResult(), sameInstance(conditionResult));
+ assertThat(watchRecord.result().transformResult(), nullValue());
+ assertThat(watchRecord.result().actionsResults().count(), is(1));
+ ActionWrapper.Result result = watchRecord.result().actionsResults().get("_action");
+ assertThat(result, notNullValue());
+ assertThat(result.id(), is("_action"));
+ assertThat(result.condition(), nullValue());
+ assertThat(result.transform(), nullValue());
+ assertThat(result.action(), instanceOf(Action.Result.ConditionFailed.class));
+ Action.Result.ConditionFailed conditionFailed = (Action.Result.ConditionFailed) result.action();
+ assertThat(conditionFailed.reason(), is("condition failed. skipping: [expected] failed for test"));
+
+ verify(condition, times(1)).execute(context);
+ verify(throttler, times(1)).throttle("_action", context);
+ verify(actionCondition, times(1)).execute(context);
+ verify(actionTransform, never()).execute(context, payload);
+ }
+
+ public void testExecuteConditionNotMet() throws Exception {
+ DateTime now = DateTime.now(DateTimeZone.UTC);
+ Watch watch = mock(Watch.class);
+ ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
+ WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event, timeValueSeconds(5));
+
Condition.Result conditionResult = NeverCondition.Result.INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
@@ -577,9 +747,10 @@ public class ExecutionServiceTests extends ESTestCase {
// action throttler
ActionThrottler throttler = mock(ActionThrottler.class);
+ ExecutableCondition actionCondition = mock(ExecutableCondition.class);
ExecutableTransform actionTransform = mock(ExecutableTransform.class);
ExecutableAction action = mock(ExecutableAction.class);
- ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
+ ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionCondition, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(clock.nowUTC(), singletonMap("_action", new ActionStatus(now)));
@@ -602,4 +773,23 @@ public class ExecutionServiceTests extends ESTestCase {
verify(actionTransform, never()).execute(context, payload);
verify(action, never()).execute("_action", context, payload);
}
+
+ private Tuple whenCondition(final WatchExecutionContext context) {
+ Condition.Result conditionResult = mock(Condition.Result.class);
+ when(conditionResult.met()).thenReturn(true);
+ ExecutableCondition condition = mock(ExecutableCondition.class);
+ when(condition.execute(context)).thenReturn(conditionResult);
+
+ return new Tuple<>(condition, conditionResult);
+ }
+
+ private Tuple whenTransform(final WatchExecutionContext context) {
+ Transform.Result transformResult = mock(Transform.Result.class);
+ when(transformResult.payload()).thenReturn(payload);
+ ExecutableTransform transform = mock(ExecutableTransform.class);
+ when(transform.execute(context, payload)).thenReturn(transformResult);
+
+ return new Tuple<>(transform, transformResult);
+ }
+
}
diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/history/HistoryActionConditionTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/history/HistoryActionConditionTests.java
new file mode 100644
index 00000000000..d2411263731
--- /dev/null
+++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/history/HistoryActionConditionTests.java
@@ -0,0 +1,275 @@
+/*
+ * 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.xpack.watcher.history;
+
+import com.google.common.collect.Lists;
+
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.script.MockScriptPlugin;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder;
+import org.elasticsearch.xpack.watcher.condition.Condition;
+import org.elasticsearch.xpack.watcher.condition.compare.CompareCondition;
+import org.elasticsearch.xpack.watcher.execution.ExecutionState;
+import org.elasticsearch.xpack.watcher.input.Input;
+import org.elasticsearch.xpack.watcher.support.WatcherScript;
+import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase;
+import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.loggingAction;
+import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder;
+import static org.elasticsearch.xpack.watcher.condition.ConditionBuilders.alwaysCondition;
+import static org.elasticsearch.xpack.watcher.condition.ConditionBuilders.compareCondition;
+import static org.elasticsearch.xpack.watcher.condition.ConditionBuilders.neverCondition;
+import static org.elasticsearch.xpack.watcher.condition.ConditionBuilders.scriptCondition;
+import static org.elasticsearch.xpack.watcher.input.InputBuilders.simpleInput;
+import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule;
+import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.interval;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * This test makes sure per-action conditions are honored.
+ */
+public class HistoryActionConditionTests extends AbstractWatcherIntegrationTestCase {
+
+ private final Input input = simpleInput("key", 15).build();
+
+ private final Condition.Builder scriptConditionPasses = mockScriptCondition("return true;");
+ private final Condition.Builder compareConditionPasses = compareCondition("ctx.payload.key", CompareCondition.Op.GTE, 15);
+ private final Condition.Builder conditionPasses = randomFrom(alwaysCondition(), scriptConditionPasses, compareConditionPasses);
+
+ private final Condition.Builder scriptConditionFails = mockScriptCondition("return false;");
+ private final Condition.Builder compareConditionFails = compareCondition("ctx.payload.key", CompareCondition.Op.LT, 15);
+ private final Condition.Builder conditionFails = randomFrom(neverCondition(), scriptConditionFails, compareConditionFails);
+
+ @Override
+ protected List> pluginTypes() {
+ List> types = super.pluginTypes();
+ types.add(CustomScriptPlugin.class);
+ return types;
+ }
+
+ public static class CustomScriptPlugin extends MockScriptPlugin {
+
+ @Override
+ protected Map, Object>> pluginScripts() {
+ Map, Object>> scripts = new HashMap<>();
+
+ scripts.put("return true;", vars -> true);
+ scripts.put("return false;", vars -> false);
+ scripts.put("throw new IllegalStateException('failed');", vars -> {
+ throw new IllegalStateException("[expected] failed hard");
+ });
+
+ return scripts;
+ }
+
+ }
+
+ @Override
+ protected boolean timeWarped() {
+ return true; // just to have better control over the triggers
+ }
+
+ @Override
+ protected boolean enableSecurity() {
+ return false; // remove security noise from this test
+ }
+
+ /**
+ * A hard failure is where an exception is thrown by the script condition.
+ */
+ @SuppressWarnings("unchecked")
+ public void testActionConditionWithHardFailures() throws Exception {
+ final String id = "testActionConditionWithHardFailures";
+
+ final Condition.Builder scriptConditionFailsHard = mockScriptCondition("throw new IllegalStateException('failed');");
+ final List actionConditionsWithFailure =
+ Lists.newArrayList(scriptConditionFailsHard, conditionPasses, alwaysCondition());
+
+ Collections.shuffle(actionConditionsWithFailure, random());
+
+ final int failedIndex = actionConditionsWithFailure.indexOf(scriptConditionFailsHard);
+
+ putAndTriggerWatch(id, input, actionConditionsWithFailure.toArray(new Condition.Builder[actionConditionsWithFailure.size()]));
+
+ flush();
+
+ assertWatchWithMinimumActionsCount(id, ExecutionState.EXECUTED, 1);
+
+ // only one action should have failed via condition
+ final SearchResponse response = searchHistory(SearchSourceBuilder.searchSource().query(termQuery("watch_id", id)));
+ assertThat(response.getHits().getTotalHits(), is(1L));
+
+ final SearchHit hit = response.getHits().getAt(0);
+ final List