Move acking/throttling to the action level

Until now, acking and throttling functionality was applied at the watch level. This has major drawbacks in different aspects:

- When multiple actions are defined on a watch, acking a watch effectively acks all the actions. This is conceptually wrong. Say you have two actions: `email` and `index`. It's very likely you'd like to ack the email action (to avoid receiving too many emails) but at the same time continue indexing the data in the `index` action. Right now it's not possible.

- Different actions types may require different throttling. An `email` action probably needs a longer throttle period compared to an `index` action. Also for different `webhook` actions, the throttling is ultimately determined by the 3rd party system that is called.

This commit changes how we do throttling & acking. Moving this functionality to the action level. Now, when acking, each action in the watch will be acked separately. During executiong, each action will determine whether it needs to be throttled or not. The throttler is not associated with the action, not with the watch.

The throttle period was enhanced. There is a default throttle period that is configured for watcher as a whole (using the `watcher.execution.default_throttle_period` setting. Next to that, each `watch` can define its own `throttle_period` that can serve as the default throttle period for the actions in the watch. Lastly, each action can have its own throttle period set.

Since the throttler is now an action "thing", the `throttle` package was renamed to `throttler` and moved under the `actions` package. Also, `WatchThrottler` was renamed to `ActionThrottler`.

With this change, the `Watch Execute API` changed as well. Now, when executing a watch, you can define an execution mode per action. The execution mode offers 4 types of execution:
- `execute`: executes the watch normally (actually executing the action and it may be throttled)
- `force_execute`: skips/ignores throttling and executes the watch
- `simulate`: simulates the watch execution yet it may be throttled
- `force_simulate`: skips/ignores throttling and simulates the watch execution

As part of this change, the structure of the watch status changed along with the xconent representing the `watch_record`. A new `ActionStatus` was introduced (as part of the `WatchStatus`) and is always set for every action in the watch. This status holds:
 - the current state of the action (`ackable`, `awaits_successful_execution`, `acked`)
 - the last execution state (success/failure + reason)
 - the last successful execution state
 - the last throttle state (timestamp + reason)

Original commit: elastic/x-pack-elasticsearch@32c2985ed8
This commit is contained in:
uboness 2015-04-27 23:13:50 +02:00
parent ea91c1e617
commit e0a70722e0
76 changed files with 2594 additions and 2007 deletions

View File

@ -42,7 +42,7 @@
watcher.ack_watch:
id: "my_watch"
- match: { "status.ack.state" : "awaits_execution" }
- match: { "status.actions.test_index.ack_status.state" : "awaits_successful_execution" }
- do:
watcher.delete_watch:

View File

@ -59,18 +59,19 @@
id: "my_exe_watch"
body: >
{
"ignore_condition" : true,
"ignore_throttle" : true,
"simulated_actions" : "_all",
"alternative_input" : {
"foo" : "bar"
},
"trigger_event" : {
"schedule" : {
"scheduled_time" : "2015-05-05T20:58:02.443Z",
"triggered_time" : "2015-05-05T20:58:02.443Z"
}
},
"alternative_input" : {
"foo" : "bar"
},
"ignore_condition" : true,
"action_modes" : {
"_all" : "force_simulate"
},
"record_execution" : true
}
- match: { "watch_id": "my_exe_watch" }
@ -79,5 +80,5 @@
- match: { "execution_result.condition.always": {} }
- match: { "execution_result.input.simple.payload.foo": "bar" }
- match: { "execution_result.actions.0.id" : "email_admin" }
- match: { "execution_result.actions.0.email.success" : true }
- match: { "execution_result.actions.0.email.simulated_email.subject" : "404 recently encountered" }
- match: { "execution_result.actions.0.email.status" : "simulated" }
- match: { "execution_result.actions.0.email.email.subject" : "404 recently encountered" }

View File

@ -1,5 +1,5 @@
---
"Test execute watch api with empty body":
"Test execute watch api with minimal body":
- do:
cluster.health:
wait_for_status: green
@ -47,5 +47,5 @@
- match: { "execution_result.condition.script.met": true }
- match: { "state": "executed" }
- match: { "execution_result.actions.0.id" : "logging" }
- match: { "execution_result.actions.0.logging.success" : true }
- match: { "execution_result.actions.0.logging.status" : "success" }
- match: { "execution_result.actions.0.logging.logged_text" : "foobar" }

View File

@ -17,16 +17,21 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.watcher.execution.ExecutionService;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.trigger.TriggerService;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchLockService;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.elasticsearch.watcher.watch.WatchStore;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
public class WatcherService extends AbstractComponent {
private final Clock clock;
private final TriggerService triggerService;
private final Watch.Parser watchParser;
private final WatchStore watchStore;
@ -35,9 +40,10 @@ public class WatcherService extends AbstractComponent {
private final AtomicReference<WatcherState> state = new AtomicReference<>(WatcherState.STOPPED);
@Inject
public WatcherService(Settings settings, TriggerService triggerService, WatchStore watchStore, Watch.Parser watchParser,
ExecutionService executionService, WatchLockService watchLockService) {
public WatcherService(Settings settings, Clock clock, TriggerService triggerService, WatchStore watchStore,
Watch.Parser watchParser, ExecutionService executionService, WatchLockService watchLockService) {
super(settings);
this.clock = clock;
this.triggerService = triggerService;
this.watchStore = watchStore;
this.watchParser = watchParser;
@ -136,7 +142,7 @@ public class WatcherService extends AbstractComponent {
/**
* Acks the watch if needed
*/
public Watch.Status ackWatch(String id, TimeValue timeout) {
public WatchStatus ackWatch(String id, TimeValue timeout) {
ensureStarted();
WatchLockService.Lock lock = watchLockService.tryAcquire(id, timeout);
if (lock == null) {
@ -147,7 +153,7 @@ public class WatcherService extends AbstractComponent {
if (watch == null) {
throw new WatcherException("watch [{}] does not exist", id);
}
if (watch.ack()) {
if (watch.ack(clock.now(UTC), "_all")) {
try {
watchStore.updateStatus(watch);
} catch (IOException ioe) {
@ -157,7 +163,7 @@ public class WatcherService extends AbstractComponent {
}
}
// we need to create a safe copy of the status
return new Watch.Status(watch.status());
return new WatchStatus(watch.status());
} finally {
lock.release();
}

View File

@ -6,10 +6,12 @@
package org.elasticsearch.watcher.actions;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Locale;
/**
*
@ -20,36 +22,70 @@ public interface Action extends ToXContent {
abstract class Result implements ToXContent {
protected final String type;
protected final boolean success;
public enum Status { SUCCESS, FAILURE, THROTTLED, SIMULATED }
protected Result(String type, boolean success) {
protected final String type;
protected final Status status;
protected Result(String type, Status status) {
this.type = type;
this.success = success;
this.status = status;
}
public String type() {
return type;
}
public boolean success() {
return success;
public Status status() {
return status;
}
@Override
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.SUCCESS.getPreferredName(), success);
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
xContentBody(builder, params);
return builder.endObject();
}
protected abstract XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException;
public interface Failure extends ToXContent {
public static class Failure extends Result {
String reason();
private final String reason;
public Failure(String type, String reason, Object... args) {
super(type, Status.FAILURE);
this.reason = LoggerMessageFormat.format(reason, args);
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
}
}
public static class Throttled extends Result {
private final String reason;
public Throttled(String type, String reason) {
super(type, Status.THROTTLED);
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
}
}
}
@ -59,7 +95,7 @@ public interface Action extends ToXContent {
}
interface Field {
ParseField SUCCESS = new ParseField("success");
ParseField STATUS = new ParseField("status");
ParseField REASON = new ParseField("reason");
}
}

View File

@ -14,7 +14,7 @@ import java.io.IOException;
/**
* Parses xcontent to a concrete action of the same type.
*/
public abstract class ActionFactory<A extends Action, R extends Action.Result, E extends ExecutableAction<A, R>> {
public abstract class ActionFactory<A extends Action, E extends ExecutableAction<A>> {
protected final ESLogger actionLogger;
@ -29,7 +29,7 @@ public abstract class ActionFactory<A extends Action, R extends Action.Result, E
public abstract A parseAction(String watchId, String actionId, XContentParser parser) throws IOException;
public abstract R parseResult(Wid wid, String actionId, XContentParser parser) throws IOException;
public abstract Action.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException;
public abstract E createExecutable(A action);

View File

@ -9,6 +9,8 @@ import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.transform.TransformRegistry;
import java.io.IOException;
@ -23,11 +25,15 @@ public class ActionRegistry {
private final ImmutableMap<String, ActionFactory> parsers;
private final TransformRegistry transformRegistry;
private final Clock clock;
private final LicenseService licenseService;
@Inject
public ActionRegistry(Map<String, ActionFactory> parsers, TransformRegistry transformRegistry) {
public ActionRegistry(Map<String, ActionFactory> parsers, TransformRegistry transformRegistry, Clock clock, LicenseService licenseService) {
this.parsers = ImmutableMap.copyOf(parsers);
this.transformRegistry = transformRegistry;
this.clock = clock;
this.licenseService = licenseService;
}
ActionFactory factory(String type) {
@ -35,6 +41,9 @@ public class ActionRegistry {
}
public ExecutableActions parseActions(String watchId, XContentParser parser) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
throw new ActionException("could not parse actions for watch [{}]. expected an object but found [{}] instead", watchId, parser.currentToken());
}
List<ActionWrapper> actions = new ArrayList<>();
String id = null;
@ -43,7 +52,7 @@ public class ActionRegistry {
if (token == XContentParser.Token.FIELD_NAME) {
id = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT && id != null) {
ActionWrapper action = ActionWrapper.parse(watchId, id, parser, this, transformRegistry);
ActionWrapper action = ActionWrapper.parse(watchId, id, parser, this, transformRegistry, clock, licenseService);
actions.add(action);
}
}

View File

@ -0,0 +1,520 @@
/*
* 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.watcher.actions;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException;
import java.io.IOException;
import java.util.Locale;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.watcher.support.WatcherDateUtils.dateTimeFormatter;
/**
*
*/
public class ActionStatus implements ToXContent {
private AckStatus ackStatus;
private @Nullable Execution lastExecution;
private @Nullable Execution lastSuccessfulExecution;
private @Nullable Throttle lastThrottle;
public ActionStatus(DateTime now) {
this(new AckStatus(now, AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION), null, null, null);
}
public ActionStatus(AckStatus ackStatus, @Nullable Execution lastExecution, @Nullable Execution lastSuccessfulExecution, @Nullable Throttle lastThrottle) {
this.ackStatus = ackStatus;
this.lastExecution = lastExecution;
this.lastSuccessfulExecution = lastSuccessfulExecution;
this.lastThrottle = lastThrottle;
}
public AckStatus ackStatus() {
return ackStatus;
}
public Execution lastExecution() {
return lastExecution;
}
public Execution lastSuccessfulExecution() {
return lastSuccessfulExecution;
}
public Throttle lastThrottle() {
return lastThrottle;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActionStatus that = (ActionStatus) o;
if (!ackStatus.equals(that.ackStatus)) return false;
if (lastExecution != null ? !lastExecution.equals(that.lastExecution) : that.lastExecution != null)
return false;
if (lastSuccessfulExecution != null ? !lastSuccessfulExecution.equals(that.lastSuccessfulExecution) : that.lastSuccessfulExecution != null)
return false;
return !(lastThrottle != null ? !lastThrottle.equals(that.lastThrottle) : that.lastThrottle != null);
}
@Override
public int hashCode() {
int result = ackStatus.hashCode();
result = 31 * result + (lastExecution != null ? lastExecution.hashCode() : 0);
result = 31 * result + (lastSuccessfulExecution != null ? lastSuccessfulExecution.hashCode() : 0);
result = 31 * result + (lastThrottle != null ? lastThrottle.hashCode() : 0);
return result;
}
public void update(DateTime timestamp, Action.Result result) {
switch (result.status()) {
case FAILURE:
String reason = result instanceof Action.Result.Failure ? ((Action.Result.Failure) result).reason() : "";
lastExecution = Execution.failure(timestamp, reason);
return;
case THROTTLED:
reason = result instanceof Action.Result.Throttled ? ((Action.Result.Throttled) result).reason() : "";
lastThrottle = new Throttle(timestamp, reason);
return;
case SUCCESS:
case SIMULATED:
lastExecution = Execution.successful(timestamp);
lastSuccessfulExecution = lastExecution;
if (ackStatus.state == AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION) {
ackStatus = new AckStatus(timestamp, AckStatus.State.ACKABLE);
}
}
}
public boolean onAck(DateTime timestamp) {
if (ackStatus.state == AckStatus.State.ACKABLE) {
ackStatus = new AckStatus(timestamp, AckStatus.State.ACKED);
return true;
}
return false;
}
public boolean resetAckStatus(DateTime timestamp) {
if (ackStatus.state != AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION) {
ackStatus = new AckStatus(timestamp, AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION);
return true;
}
return false;
}
public static void writeTo(ActionStatus status, StreamOutput out) throws IOException {
AckStatus.writeTo(status.ackStatus, out);
out.writeBoolean(status.lastExecution != null);
if (status.lastExecution != null) {
Execution.writeTo(status.lastExecution, out);
}
out.writeBoolean(status.lastSuccessfulExecution != null);
if (status.lastSuccessfulExecution != null) {
Execution.writeTo(status.lastSuccessfulExecution, out);
}
out.writeBoolean(status.lastThrottle != null);
if (status.lastThrottle != null) {
Throttle.writeTo(status.lastThrottle, out);
}
}
public static ActionStatus readFrom(StreamInput in) throws IOException {
AckStatus ackStatus = AckStatus.readFrom(in);
Execution lastExecution = in.readBoolean() ? Execution.readFrom(in) : null;
Execution lastSuccessfulExecution = in.readBoolean() ? Execution.readFrom(in) : null;
Throttle lastThrottle = in.readBoolean() ? Throttle.readFrom(in) : null;
return new ActionStatus(ackStatus, lastExecution, lastSuccessfulExecution, lastThrottle);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.ACK_STATUS.getPreferredName(), ackStatus, params);
if (lastExecution != null) {
builder.field(Field.LAST_EXECUTION.getPreferredName(), lastExecution, params);
}
if (lastSuccessfulExecution != null) {
builder.field(Field.LAST_SUCCESSFUL_EXECUTION.getPreferredName(), lastSuccessfulExecution, params);
}
if (lastThrottle != null) {
builder.field(Field.LAST_THROTTLE.getPreferredName(), lastThrottle, params);
}
return builder.endObject();
}
public static ActionStatus parse(String watchId, String actionId, XContentParser parser) throws IOException {
AckStatus ackStatus = null;
Execution lastExecution = null;
Execution lastSuccessfulExecution = null;
Throttle lastThrottle = 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 (Field.ACK_STATUS.match(currentFieldName)) {
ackStatus = AckStatus.parse(watchId, actionId, parser);
} else if (Field.LAST_EXECUTION.match(currentFieldName)) {
lastExecution = Execution.parse(watchId, actionId, parser);
} else if (Field.LAST_SUCCESSFUL_EXECUTION.match(currentFieldName)) {
lastSuccessfulExecution = Execution.parse(watchId, actionId, parser);
} else if (Field.LAST_THROTTLE.match(currentFieldName)) {
lastThrottle = Throttle.parse(watchId, actionId, parser);
} else {
throw new ParseException("could not parse action status for [{}/{}]. unexpected field [{}]", watchId, actionId, currentFieldName);
}
}
if (ackStatus == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}]", watchId, actionId, Field.ACK_STATUS.getPreferredName());
}
return new ActionStatus(ackStatus, lastExecution, lastSuccessfulExecution, lastThrottle);
}
public static class AckStatus implements ToXContent {
public enum State {
AWAITS_SUCCESSFUL_EXECUTION((byte) 1),
ACKABLE((byte) 2),
ACKED((byte) 3);
private byte value;
State(byte value) {
this.value = value;
}
static State resolve(byte value) {
switch (value) {
case 1 : return AWAITS_SUCCESSFUL_EXECUTION;
case 2 : return ACKABLE;
case 3 : return ACKED;
default:
throw new WatcherException("unknown action ack status state value [{}]", value);
}
}
}
private final DateTime timestamp;
private final State state;
public AckStatus(DateTime timestamp, State state) {
this.timestamp = timestamp.toDateTime(UTC);
this.state = state;
}
public DateTime timestamp() {
return timestamp;
}
public State state() {
return state;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AckStatus ackStatus = (AckStatus) o;
if (!timestamp.equals(ackStatus.timestamp)) return false;
return state == ackStatus.state;
}
@Override
public int hashCode() {
int result = timestamp.hashCode();
result = 31 * result + state.hashCode();
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(Field.TIMESTAMP.getPreferredName()).value(timestamp, dateTimeFormatter.printer())
.field(Field.ACK_STATUS_STATE.getPreferredName(), state.name().toLowerCase(Locale.ROOT))
.endObject();
}
public static AckStatus parse(String watchId, String actionId, XContentParser parser) throws IOException {
DateTime timestamp = null;
State state = 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 (Field.TIMESTAMP.match(currentFieldName)) {
timestamp = dateTimeFormatter.parser().parseDateTime(parser.text());
} else if (Field.ACK_STATUS_STATE.match(currentFieldName)) {
state = State.valueOf(parser.text().toUpperCase(Locale.ROOT));
} else {
throw new ParseException("could not parse action status for [{}/{}]. unexpected field [{}.{}]", watchId, actionId, Field.ACK_STATUS.getPreferredName(), currentFieldName);
}
}
if (timestamp == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.ACK_STATUS.getPreferredName(), Field.TIMESTAMP.getPreferredName());
}
if (state == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.ACK_STATUS.getPreferredName(), Field.ACK_STATUS_STATE.getPreferredName());
}
return new AckStatus(timestamp, state);
}
static void writeTo(AckStatus status, StreamOutput out) throws IOException {
out.writeLong(status.timestamp.getMillis());
out.writeByte(status.state.value);
}
static AckStatus readFrom(StreamInput in) throws IOException {
DateTime timestamp = new DateTime(in.readLong(), UTC);
State state = State.resolve(in.readByte());
return new AckStatus(timestamp, state);
}
}
public static class Execution implements ToXContent {
public static Execution successful(DateTime timestamp) {
return new Execution(timestamp, true, null);
}
public static Execution failure(DateTime timestamp, String reason) {
return new Execution(timestamp, false, reason);
}
private final DateTime timestamp;
private final boolean successful;
private final String reason;
private Execution(DateTime timestamp, boolean successful, String reason) {
this.timestamp = timestamp.toDateTime(UTC);
this.successful = successful;
this.reason = reason;
}
public DateTime timestamp() {
return timestamp;
}
public boolean successful() {
return successful;
}
public String reason() {
return reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Execution execution = (Execution) o;
if (successful != execution.successful) return false;
if (!timestamp.equals(execution.timestamp)) return false;
return !(reason != null ? !reason.equals(execution.reason) : execution.reason != null);
}
@Override
public int hashCode() {
int result = timestamp.hashCode();
result = 31 * result + (successful ? 1 : 0);
result = 31 * result + (reason != null ? reason.hashCode() : 0);
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.TIMESTAMP.getPreferredName()).value(timestamp, dateTimeFormatter.printer());
builder.field(Field.EXECUTION_SUCCESSFUL.getPreferredName(), successful);
if (reason != null) {
builder.field(Field.REASON.getPreferredName(), reason);
}
return builder.endObject();
}
public static Execution parse(String watchId, String actionId, XContentParser parser) throws IOException {
DateTime timestamp = null;
Boolean successful = null;
String reason = 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 (Field.TIMESTAMP.match(currentFieldName)) {
timestamp = dateTimeFormatter.parser().parseDateTime(parser.text());
} else if (Field.EXECUTION_SUCCESSFUL.match(currentFieldName)) {
successful = parser.booleanValue();
} else if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else {
throw new ParseException("could not parse action status for [{}/{}]. unexpected field [{}.{}]", watchId, actionId, Field.LAST_EXECUTION.getPreferredName(), currentFieldName);
}
}
if (timestamp == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.LAST_EXECUTION.getPreferredName(), Field.TIMESTAMP.getPreferredName());
}
if (successful == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.LAST_EXECUTION.getPreferredName(), Field.EXECUTION_SUCCESSFUL.getPreferredName());
}
if (successful) {
return successful(timestamp);
}
if (reason == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field for unsuccessful execution [{}.{}]", watchId, actionId, Field.LAST_EXECUTION.getPreferredName(), Field.REASON.getPreferredName());
}
return failure(timestamp, reason);
}
public static void writeTo(Execution execution, StreamOutput out) throws IOException {
out.writeLong(execution.timestamp.getMillis());
out.writeBoolean(execution.successful);
if (!execution.successful) {
out.writeString(execution.reason);
}
}
public static Execution readFrom(StreamInput in) throws IOException {
DateTime timestamp = new DateTime(in.readLong(), UTC);
boolean successful = in.readBoolean();
if (successful) {
return successful(timestamp);
}
return failure(timestamp, in.readSharedString());
}
}
public static class Throttle implements ToXContent {
private final DateTime timestamp;
private final String reason;
public Throttle(DateTime timestamp, String reason) {
this.timestamp = timestamp.toDateTime(UTC);
this.reason = reason;
}
public DateTime timestamp() {
return timestamp;
}
public String reason() {
return reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Throttle throttle = (Throttle) o;
if (!timestamp.equals(throttle.timestamp)) return false;
return reason.equals(throttle.reason);
}
@Override
public int hashCode() {
int result = timestamp.hashCode();
result = 31 * result + reason.hashCode();
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(Field.TIMESTAMP.getPreferredName()).value(timestamp, dateTimeFormatter.printer())
.field(Field.REASON.getPreferredName(), reason)
.endObject();
}
public static Throttle parse(String watchId, String actionId, XContentParser parser) throws IOException {
DateTime timestamp = null;
String reason = 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 (Field.TIMESTAMP.match(currentFieldName)) {
timestamp = dateTimeFormatter.parser().parseDateTime(parser.text());
} else if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else {
throw new ParseException("could not parse action status for [{}/{}]. unexpected field [{}.{}]", watchId, actionId, Field.LAST_THROTTLE.getPreferredName(), currentFieldName);
}
}
if (timestamp == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.LAST_THROTTLE.getPreferredName(), Field.TIMESTAMP.getPreferredName());
}
if (reason == null) {
throw new ParseException("could not parse action status for [{}/{}]. missing required field [{}.{}]", watchId, actionId, Field.LAST_THROTTLE.getPreferredName(), Field.REASON.getPreferredName());
}
return new Throttle(timestamp, reason);
}
static void writeTo(Throttle throttle, StreamOutput out) throws IOException {
out.writeLong(throttle.timestamp.getMillis());
out.writeString(throttle.reason);
}
static Throttle readFrom(StreamInput in) throws IOException {
DateTime timestamp = new DateTime(in.readLong(), UTC);
return new Throttle(timestamp, in.readString());
}
}
static class ParseException extends WatcherException {
public ParseException(String msg, Object... args) {
super(msg, args);
}
public ParseException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
interface Field {
ParseField ACK_STATUS = new ParseField("ack_status");
ParseField ACK_STATUS_STATE = new ParseField("state");
ParseField LAST_EXECUTION = new ParseField("last_execution");
ParseField LAST_SUCCESSFUL_EXECUTION = new ParseField("last_successful_execution");
ParseField EXECUTION_SUCCESSFUL = new ParseField("successful");
ParseField LAST_THROTTLE = new ParseField("last_throttle");
ParseField TIMESTAMP = new ParseField("timestamp");
ParseField REASON = new ParseField("reason");
}
}

View File

@ -5,9 +5,11 @@
*/
package org.elasticsearch.watcher.actions;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
@ -15,6 +17,10 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.actions.throttler.ActionThrottler;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
@ -31,14 +37,16 @@ public class ActionWrapper implements ToXContent {
private String id;
private final @Nullable ExecutableTransform transform;
private final ActionThrottler throttler;
private final ExecutableAction action;
public ActionWrapper(String id, ExecutableAction action) {
this(id, null, action);
this(id, null, null, action);
}
public ActionWrapper(String id, @Nullable ExecutableTransform transform, ExecutableAction action) {
public ActionWrapper(String id, ActionThrottler throttler, @Nullable ExecutableTransform transform, ExecutableAction action) {
this.id = id;
this.throttler = throttler;
this.transform = transform;
this.action = action;
}
@ -51,20 +59,43 @@ public class ActionWrapper implements ToXContent {
return transform;
}
public Throttler throttler() {
return throttler;
}
public ExecutableAction action() {
return action;
}
public ActionWrapper.Result execute(WatchExecutionContext ctx) throws IOException {
Payload payload = ctx.payload();
ActionWrapper.Result result = ctx.actionsResults().get(id);
if (result != null) {
return result;
}
if (!ctx.skipThrottling(id)) {
Throttler.Result throttleResult = throttler.throttle(id, ctx);
if (throttleResult.throttle()) {
return new ActionWrapper.Result(id, new Action.Result.Throttled(action.type(), throttleResult.reason()));
}
}
Payload payload = ctx.transformedPayload();
Transform.Result transformResult = null;
if (transform != null) {
try {
transformResult = transform.execute(ctx, payload);
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)));
}
}
try {
Action.Result actionResult = action.execute(id, ctx, payload);
return new ActionWrapper.Result(id, 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)));
}
}
@Override
@ -90,6 +121,10 @@ public class ActionWrapper implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
TimeValue throttlePeriod = throttler.throttlePeriod();
if (throttlePeriod != null) {
builder.field(Throttler.Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod.getMillis());
}
if (transform != null) {
builder.startObject(Transform.Field.TRANSFORM.getPreferredName())
.field(transform.type(), transform, params)
@ -99,10 +134,14 @@ public class ActionWrapper implements ToXContent {
return builder.endObject();
}
static ActionWrapper parse(String watchId, String actionId, XContentParser parser, ActionRegistry actionRegistry, TransformRegistry transformRegistry) throws IOException {
static ActionWrapper parse(String watchId, String actionId, XContentParser parser,
ActionRegistry actionRegistry, TransformRegistry transformRegistry,
Clock clock, LicenseService licenseService) throws IOException {
assert parser.currentToken() == XContentParser.Token.START_OBJECT;
ExecutableTransform transform = null;
TimeValue throttlePeriod = null;
ExecutableAction action = null;
String currentFieldName = null;
@ -113,6 +152,12 @@ public class ActionWrapper implements ToXContent {
} else {
if (Transform.Field.TRANSFORM.match(currentFieldName)) {
transform = transformRegistry.parse(watchId, parser);
} else if (Throttler.Field.THROTTLE_PERIOD.match(currentFieldName)) {
if (token == XContentParser.Token.VALUE_NUMBER) {
throttlePeriod = new TimeValue(parser.longValue());
} else {
throw new ActionException("could not parse action [{}/{}]. expected field [{}] to hold a numeric value, but instead found", watchId, actionId, currentFieldName, token);
}
} else {
// it's the type of the action
ActionFactory actionFactory = actionRegistry.factory(currentFieldName);
@ -126,7 +171,9 @@ public class ActionWrapper implements ToXContent {
if (action == null) {
throw new ActionException("could not parse watch action [{}/{}]. missing action type", watchId, actionId);
}
return new ActionWrapper(actionId, transform, action);
ActionThrottler throttler = new ActionThrottler(clock, throttlePeriod, licenseService);
return new ActionWrapper(actionId, throttler, transform, action);
}
public static class Result implements ToXContent {

View File

@ -6,7 +6,6 @@
package org.elasticsearch.watcher.actions;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
@ -16,7 +15,7 @@ import java.io.IOException;
/**
*/
public abstract class ExecutableAction<A extends Action, R extends Action.Result> implements ToXContent {
public abstract class ExecutableAction<A extends Action> implements ToXContent {
protected final A action;
protected final ESLogger logger;
@ -29,7 +28,7 @@ public abstract class ExecutableAction<A extends Action, R extends Action.Result
/**
* @return the type of this action
*/
public final String type() {
public String type() {
return action.type();
}
@ -37,25 +36,7 @@ public abstract class ExecutableAction<A extends Action, R extends Action.Result
return action;
}
public R execute(String actionId, WatchExecutionContext context, Payload payload) throws IOException {
try {
return doExecute(actionId, context, payload);
} catch (Exception e){
logger.error("failed to execute [{}] action [{}/{}]", e, type(), context.id().value(), actionId);
return failure(LoggerMessageFormat.format("failed to execute [{}] action [{}/{}]. error: {}", (Object) type(), context.id().value(), actionId, e.getMessage()));
}
}
/**
* Executes the action. The implementation need not to worry about handling exception/errors as they're handled
* here by default. Of course, if the implementation wants to do that, it can... nothing stops you.
*/
protected abstract R doExecute(String actionId, WatchExecutionContext context, Payload payload) throws Exception;
/**
* Returns an appropriate failure result that contains the given failure reason.
*/
protected abstract R failure(String reason);
public abstract Action.Result execute(String actionId, WatchExecutionContext context, Payload payload) throws Exception;
@Override
public boolean equals(Object o) {

View File

@ -80,6 +80,15 @@ public class ExecutableActions implements Iterable<ActionWrapper>, ToXContent {
return results.get(id);
}
public boolean throttled() {
for (ActionWrapper.Result result : results.values()) {
if (result.action().status() == Action.Result.Status.THROTTLED) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -165,8 +165,8 @@ public class EmailAction implements Action {
public static abstract class Result extends Action.Result {
protected Result(boolean success) {
super(TYPE, success);
protected Result(Status status) {
super(TYPE, status);
}
public static class Success extends Result {
@ -175,7 +175,7 @@ public class EmailAction implements Action {
private final Email email;
Success(String account, Email email) {
super(true);
super(Status.SUCCESS);
this.account = account;
this.email = email;
}
@ -195,25 +195,6 @@ public class EmailAction implements Action {
}
}
public static class Failure extends Result {
private final String reason;
public Failure(String reason) {
super(false);
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Action.Field.REASON.getPreferredName(), reason);
}
}
public static class Simulated extends Result {
private final Email email;
@ -223,20 +204,19 @@ public class EmailAction implements Action {
}
Simulated(Email email) {
super(true);
super(Status.SIMULATED);
this.email = email;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.SIMULATED_EMAIL.getPreferredName(), email, params);
return builder.field(Field.EMAIL.getPreferredName(), email, params);
}
}
public static Result parse(String watchId, String actionId, XContentParser parser) throws IOException {
Boolean success = null;
public static Action.Result parse(String watchId, String actionId, XContentParser parser) throws IOException {
Status status = null;
Email email = null;
Email simulatedEmail = null;
String account = null;
String reason = null;
@ -251,40 +231,31 @@ public class EmailAction implements Action {
} catch (Email.ParseException pe) {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (Field.SIMULATED_EMAIL.match(currentFieldName)) {
try {
simulatedEmail = Email.parse(parser);
} catch (Email.ParseException pe) {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.ACCOUNT.match(currentFieldName)) {
account = parser.text();
} else if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else if (Field.STATUS.match(currentFieldName)) {
try {
status = Status.valueOf(parser.text().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. unknown result status value [{}]", TYPE, watchId, actionId, parser.text());
}
} else {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Field.SUCCESS.match(currentFieldName)) {
success = parser.booleanValue();
} else {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. unexpected boolean field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
if (simulatedEmail != null) {
return new Simulated(simulatedEmail);
if (status == null) {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.STATUS.getPreferredName());
}
if (success == null) {
throw new EmailActionException("could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.SUCCESS.getPreferredName());
}
if (success) {
switch (status) {
case SUCCESS:
if (account == null) {
throw new EmailActionException("could not parse [{}] action successful result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.ACCOUNT.getPreferredName());
}
@ -292,11 +263,26 @@ public class EmailAction implements Action {
throw new EmailActionException("could not parse [{}] action successful result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.EMAIL.getPreferredName());
}
return new Success(account, email);
case SIMULATED:
if (email == null) {
throw new EmailActionException("could not parse [{}] action simulated result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.EMAIL.getPreferredName());
}
return new Simulated(email);
case THROTTLED:
if (reason == null) {
throw new EmailActionException("could not parse [{}] action failure result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
throw new EmailActionException("could not parse [{}] action throttled result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
}
return new Throttled(TYPE, reason);
default: // Failure
assert status == Status.FAILURE : "unhandled action result status";
if (reason == null) {
throw new EmailActionException("could not parse [{}] action throttled result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
}
return new Failure(TYPE, reason);
}
return new Failure(reason);
}
}
@ -350,6 +336,5 @@ public class EmailAction implements Action {
// result fields
ParseField EMAIL = new ParseField("email");
ParseField SIMULATED_EMAIL = new ParseField("simulated_email");
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.execution.Wid;
@ -19,7 +20,7 @@ import java.io.IOException;
/**
*
*/
public class EmailActionFactory extends ActionFactory<EmailAction, EmailAction.Result, ExecutableEmailAction> {
public class EmailActionFactory extends ActionFactory<EmailAction, ExecutableEmailAction> {
private final EmailService emailService;
private final TemplateEngine templateEngine;
@ -46,7 +47,7 @@ public class EmailActionFactory extends ActionFactory<EmailAction, EmailAction.R
}
@Override
public EmailAction.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
public Action.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return EmailAction.Result.parse(wid.watchId(), actionId, parser);
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.watcher.actions.email;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.actions.email.service.Attachment;
import org.elasticsearch.watcher.actions.email.service.Email;
@ -20,7 +21,7 @@ import java.util.Map;
/**
*/
public class ExecutableEmailAction extends ExecutableAction<EmailAction, EmailAction.Result> {
public class ExecutableEmailAction extends ExecutableAction<EmailAction> {
final EmailService emailService;
final TemplateEngine templateEngine;
@ -31,7 +32,7 @@ public class ExecutableEmailAction extends ExecutableAction<EmailAction, EmailAc
this.templateEngine = templateEngine;
}
protected EmailAction.Result doExecute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
Map<String, Attachment> attachments = new HashMap<>();
@ -51,9 +52,4 @@ public class ExecutableEmailAction extends ExecutableAction<EmailAction, EmailAc
EmailService.EmailSent sent = emailService.send(email.build(), action.getAuth(), action.getProfile(), action.getAccount());
return new EmailAction.Result.Success(sent.account(), sent.email());
}
@Override
protected EmailAction.Result failure(String reason) {
return new EmailAction.Result.Failure(reason);
}
}

View File

@ -10,16 +10,16 @@ import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ExecutableIndexAction extends ExecutableAction<IndexAction, IndexAction.Result> {
public class ExecutableIndexAction extends ExecutableAction<IndexAction> {
private final ClientProxy client;
@ -29,7 +29,7 @@ public class ExecutableIndexAction extends ExecutableAction<IndexAction, IndexAc
}
@Override
protected IndexAction.Result doExecute(String actionId, WatchExecutionContext ctx, Payload payload) throws IOException {
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
IndexRequest indexRequest = new IndexRequest();
indexRequest.index(action.index);
indexRequest.type(action.docType);
@ -52,12 +52,7 @@ public class ExecutableIndexAction extends ExecutableAction<IndexAction, IndexAc
data.put("version", response.getVersion());
data.put("type", response.getType());
data.put("index", response.getIndex());
return new IndexAction.Result.Executed(new Payload.Simple(data), response.isCreated());
}
@Override
protected IndexAction.Result failure(String reason) {
return new IndexAction.Result.Failure(reason);
return new IndexAction.Result.Success(new Payload.Simple(data));
}
}

View File

@ -9,9 +9,13 @@ import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.Action.Result.Failure;
import org.elasticsearch.watcher.actions.Action.Result.Status;
import org.elasticsearch.watcher.actions.Action.Result.Throttled;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
import java.util.Locale;
/**
*
@ -100,94 +104,8 @@ public class IndexAction implements Action {
return new IndexAction(index, docType);
}
public static Builder builder(String index, String docType) {
return new Builder(index, docType);
}
public abstract static class Result extends Action.Result {
protected Result(boolean success) {
super(TYPE, success);
}
static class Executed extends Result {
private final Payload response;
public Executed(Payload response, boolean isCreated) {
super(isCreated);
this.response = response;
}
public Payload response() {
return response;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
if (response != null) {
builder.field(Field.RESPONSE.getPreferredName(), response, params);
}
return builder;
}
}
static class Failure extends Result {
private final String reason;
public Failure(String reason) {
super(false);
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
}
}
public static class Simulated extends Result {
private final String index;
private final String docType;
private final Payload source;
protected Simulated(String index, String docType, Payload source) {
super(true);
this.index = index;
this.docType = docType;
this.source = source;
}
public String index() {
return index;
}
public String docType() {
return docType;
}
public Payload source() {
return source;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.startObject(Field.SIMULATED_REQUEST.getPreferredName())
.field(Field.INDEX.getPreferredName(), index)
.field(Field.DOC_TYPE.getPreferredName(), docType)
.field(Field.SOURCE.getPreferredName(), source, params)
.endObject();
}
}
public static Result parse(String watchId, String actionId, XContentParser parser) throws IOException {
Boolean success = null;
public static Action.Result parseResult(String watchId, String actionId, XContentParser parser) throws IOException {
Status status = null;
Payload response = null;
String reason = null;
Payload source = null;
@ -202,19 +120,19 @@ public class IndexAction implements Action {
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else if (Field.STATUS.match(currentFieldName)) {
try {
status = Status.valueOf(parser.text().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new IndexActionException("could not parse [{}] action result [{}/{}]. unknown result status value [{}]", TYPE, watchId, actionId, parser.text());
}
} else {
throw new IndexActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Field.SUCCESS.match(currentFieldName)) {
success = parser.booleanValue();
} else {
throw new IndexActionException("could not parse [{}] action result [{}/{}]. unexpected boolean field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Field.RESPONSE.match(currentFieldName)) {
response = new Payload.Simple(parser.map());
} else if (Field.SIMULATED_REQUEST.match(currentFieldName)) {
} else if (Field.REQUEST.match(currentFieldName)) {
String context = currentFieldName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -243,21 +161,29 @@ public class IndexAction implements Action {
}
}
if (index != null || docType != null || source != null) {
assertNotNull(index, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.SIMULATED_REQUEST.getPreferredName(), Field.INDEX.getPreferredName());
assertNotNull(docType, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.SIMULATED_REQUEST.getPreferredName(), Field.DOC_TYPE.getPreferredName());
assertNotNull(source, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.SIMULATED_REQUEST.getPreferredName(), Field.SOURCE.getPreferredName());
return new Simulated(index, docType, source);
}
assertNotNull(success, "could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.SUCCESS.getPreferredName());
if (reason != null) {
return new Failure(reason);
}
assertNotNull(status, "could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.STATUS.getPreferredName());
switch (status) {
case SUCCESS:
assertNotNull(response, "could not parse executed [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.RESPONSE.getPreferredName());
return new Executed(response, success);
return new Result.Success(response);
case SIMULATED:
assertNotNull(index, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.REQUEST.getPreferredName(), Field.INDEX.getPreferredName());
assertNotNull(docType, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.REQUEST.getPreferredName(), Field.DOC_TYPE.getPreferredName());
assertNotNull(source, "could not parse simulated [{}] action result [{}/{}]. missing required [{}.{}] field", TYPE, watchId, actionId, Field.REQUEST.getPreferredName(), Field.SOURCE.getPreferredName());
return new Result.Simulated(index, docType, source);
case THROTTLED:
assertNotNull(reason, "could not parse throttled [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
return new Throttled(TYPE, reason);
default: // FAILURE
assert status == Status.FAILURE : "unhandled action result status";
assertNotNull(reason, "could not parse failure [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
return new Failure(TYPE, reason);
}
}
private static void assertNotNull(Object value, String message, Object... args) {
@ -265,6 +191,69 @@ public class IndexAction implements Action {
throw new IndexActionException(message, args);
}
}
public static Builder builder(String index, String docType) {
return new Builder(index, docType);
}
public interface Result {
class Success extends Action.Result implements Result {
private final Payload response;
public Success(Payload response) {
super(TYPE, Status.SUCCESS);
this.response = response;
}
public Payload response() {
return response;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
if (response != null) {
builder.field(Field.RESPONSE.getPreferredName(), response, params);
}
return builder;
}
}
class Simulated extends Action.Result implements Result {
private final String index;
private final String docType;
private final Payload source;
protected Simulated(String index, String docType, Payload source) {
super(TYPE, Status.SIMULATED);
this.index = index;
this.docType = docType;
this.source = source;
}
public String index() {
return index;
}
public String docType() {
return docType;
}
public Payload source() {
return source;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.startObject(Field.REQUEST.getPreferredName())
.field(Field.INDEX.getPreferredName(), index)
.field(Field.DOC_TYPE.getPreferredName(), docType)
.field(Field.SOURCE.getPreferredName(), source, params)
.endObject();
}
}
}
public static class Builder implements Action.Builder<IndexAction> {
@ -288,6 +277,6 @@ public class IndexAction implements Action {
ParseField DOC_TYPE = new ParseField("doc_type");
ParseField SOURCE = new ParseField("source");
ParseField RESPONSE = new ParseField("response");
ParseField SIMULATED_REQUEST = new ParseField("simulated_request");
ParseField REQUEST = new ParseField("request");
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.email.ExecutableEmailAction;
import org.elasticsearch.watcher.execution.Wid;
@ -19,7 +20,7 @@ import java.io.IOException;
/**
*
*/
public class IndexActionFactory extends ActionFactory<IndexAction, IndexAction.Result, ExecutableIndexAction> {
public class IndexActionFactory extends ActionFactory<IndexAction, ExecutableIndexAction> {
private final ClientProxy client;
@ -40,8 +41,8 @@ public class IndexActionFactory extends ActionFactory<IndexAction, IndexAction.R
}
@Override
public IndexAction.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return IndexAction.Result.parse(wid.watchId(), actionId, parser);
public Action.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return IndexAction.parseResult(wid.watchId(), actionId, parser);
}
@Override

View File

@ -8,19 +8,19 @@ package org.elasticsearch.watcher.actions.logging;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
import java.util.Map;
/**
*
*/
public class ExecutableLoggingAction extends ExecutableAction<LoggingAction, LoggingAction.Result> {
public class ExecutableLoggingAction extends ExecutableAction<LoggingAction> {
private final ESLogger textLogger;
private final TemplateEngine templateEngine;
@ -43,7 +43,7 @@ public class ExecutableLoggingAction extends ExecutableAction<LoggingAction, Log
}
@Override
protected LoggingAction.Result doExecute(String actionId, WatchExecutionContext ctx, Payload payload) throws IOException {
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
String loggedText = templateEngine.render(action.text, model);
@ -54,9 +54,4 @@ public class ExecutableLoggingAction extends ExecutableAction<LoggingAction, Log
action.level.log(textLogger, loggedText);
return new LoggingAction.Result.Success(loggedText);
}
@Override
protected LoggingAction.Result failure(String reason) {
return new LoggingAction.Result.Failure(reason);
}
}

View File

@ -10,6 +10,8 @@ import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.Action.Result.Failure;
import org.elasticsearch.watcher.actions.Action.Result.Throttled;
import org.elasticsearch.watcher.support.template.Template;
import java.io.IOException;
@ -108,22 +110,76 @@ public class LoggingAction implements Action {
return new LoggingAction(text, level, category);
}
public static Action.Result parseResult(String watchId, String actionId, XContentParser parser) throws IOException {
Action.Result.Status status = null;
String loggedText = null;
String reason = null;
XContentParser.Token token;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.LOGGED_TEXT.match(currentFieldName)) {
loggedText = parser.text();
} else if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else if (Field.STATUS.match(currentFieldName)) {
try {
status = Action.Result.Status.valueOf(parser.text().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unknown result status value [{}]", TYPE, watchId, actionId, parser.text());
}
} else {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
assertNotNull(status, "could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.STATUS.getPreferredName());
switch (status) {
case SUCCESS:
assertNotNull(loggedText, "could not parse successful [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.LOGGED_TEXT.getPreferredName());
return new Result.Success(loggedText);
case SIMULATED:
assertNotNull(loggedText, "could not parse simulated [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.LOGGED_TEXT.getPreferredName());
return new Result.Simulated(loggedText);
case THROTTLED:
assertNotNull(reason, "could not parse throttled [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
return new Throttled(TYPE, reason);
default: // FAILURE
assert status == Action.Result.Status.FAILURE : "unhandled action result status";
assertNotNull(reason, "could not parse failure [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
return new Failure(TYPE, reason);
}
}
private static void assertNotNull(Object value, String message, Object... args) {
if (value == null) {
throw new LoggingActionException(message, args);
}
}
public static Builder builder(Template template) {
return new Builder(template);
}
public static abstract class Result extends Action.Result {
public interface Result {
protected Result(boolean success) {
super(TYPE, success);
}
public static class Success extends Result {
class Success extends Action.Result implements Result {
private final String loggedText;
public Success(String loggedText) {
super(true);
super(TYPE, Status.SUCCESS);
this.loggedText = loggedText;
}
@ -137,31 +193,12 @@ public class LoggingAction implements Action {
}
}
public static class Failure extends Result {
private final String reason;
public Failure(String reason) {
super(false);
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
}
}
public static class Simulated extends Result {
class Simulated extends Action.Result implements Result {
private final String loggedText;
protected Simulated(String loggedText) {
super(true);
super(TYPE, Status.SIMULATED);
this.loggedText = loggedText;
}
@ -171,62 +208,9 @@ public class LoggingAction implements Action {
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.SIMULATED_LOGGED_TEXT.getPreferredName(), loggedText);
return builder.field(Field.LOGGED_TEXT.getPreferredName(), loggedText);
}
}
public static Result parse(String watchId, String actionId, XContentParser parser) throws IOException {
Boolean success = null;
String loggedText = null;
String simulatedLoggedText = null;
String reason = null;
XContentParser.Token token;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.LOGGED_TEXT.match(currentFieldName)) {
loggedText = parser.text();
} else if (Field.SIMULATED_LOGGED_TEXT.match(currentFieldName)) {
simulatedLoggedText = parser.text();
} else if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Field.SUCCESS.match(currentFieldName)) {
success = parser.booleanValue();
} else {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unexpected boolean field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
if (success == null) {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.SUCCESS.getPreferredName());
}
if (simulatedLoggedText != null) {
return new LoggingAction.Result.Simulated(simulatedLoggedText);
}
if (success) {
if (loggedText == null) {
throw new LoggingActionException("could not parse successful [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.LOGGED_TEXT.getPreferredName());
}
return new LoggingAction.Result.Success(loggedText);
}
if (reason == null) {
throw new LoggingActionException("could not parse failed [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.REASON.getPreferredName());
}
return new LoggingAction.Result.Failure(reason);
}
}
public static class Builder implements Action.Builder<LoggingAction> {
@ -260,6 +244,5 @@ public class LoggingAction implements Action {
ParseField LEVEL = new ParseField("level");
ParseField TEXT = new ParseField("text");
ParseField LOGGED_TEXT = new ParseField("logged_text");
ParseField SIMULATED_LOGGED_TEXT = new ParseField("simulated_logged_text");
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.support.template.TemplateEngine;
@ -18,7 +19,7 @@ import java.io.IOException;
/**
*
*/
public class LoggingActionFactory extends ActionFactory<LoggingAction, LoggingAction.Result, ExecutableLoggingAction> {
public class LoggingActionFactory extends ActionFactory<LoggingAction, ExecutableLoggingAction> {
private final Settings settings;
private final TemplateEngine templateEngine;
@ -41,8 +42,8 @@ public class LoggingActionFactory extends ActionFactory<LoggingAction, LoggingAc
}
@Override
public LoggingAction.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return LoggingAction.Result.parse(wid.watchId(), actionId, parser);
public Action.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return LoggingAction.parseResult(wid.watchId(), actionId, parser);
}
@Override

View File

@ -0,0 +1,29 @@
/*
* 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.watcher.actions.throttler;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionStatus.AckStatus;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate;
/**
*
*/
public class AckThrottler implements Throttler {
@Override
public Result throttle(String actionId, WatchExecutionContext ctx) {
ActionStatus actionStatus = ctx.watch().status().actionStatus(actionId);
AckStatus ackStatus = actionStatus.ackStatus();
if (ackStatus.state() == AckStatus.State.ACKED) {
return Result.throttle("action [{}] was acked at [{}]", actionId, formatDate(ackStatus.timestamp()));
}
return Result.NO;
}
}

View File

@ -3,8 +3,11 @@
* 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.watcher.throttle;
package org.elasticsearch.watcher.actions.throttler;
import org.elasticsearch.watcher.actions.throttler.AckThrottler;
import org.elasticsearch.watcher.actions.throttler.PeriodThrottler;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.clock.Clock;
@ -14,7 +17,7 @@ import org.elasticsearch.common.unit.TimeValue;
/**
*
*/
public class WatchThrottler implements Throttler {
public class ActionThrottler implements Throttler {
private static final AckThrottler ACK_THROTTLER = new AckThrottler();
@ -22,27 +25,31 @@ public class WatchThrottler implements Throttler {
private final PeriodThrottler periodThrottler;
private final AckThrottler ackThrottler;
public WatchThrottler(Clock clock, @Nullable TimeValue throttlePeriod, LicenseService licenseService) {
this(throttlePeriod != null ? new PeriodThrottler(clock, throttlePeriod) : null, ACK_THROTTLER, licenseService);
public ActionThrottler(Clock clock, @Nullable TimeValue throttlePeriod, LicenseService licenseService) {
this(new PeriodThrottler(clock, throttlePeriod), ACK_THROTTLER, licenseService);
}
WatchThrottler(PeriodThrottler periodThrottler, AckThrottler ackThrottler, LicenseService licenseService) {
ActionThrottler(PeriodThrottler periodThrottler, AckThrottler ackThrottler, LicenseService licenseService) {
this.periodThrottler = periodThrottler;
this.ackThrottler = ackThrottler;
this.licenseService = licenseService;
}
public TimeValue throttlePeriod() {
return periodThrottler != null ? periodThrottler.period() : null;
}
@Override
public Result throttle(WatchExecutionContext ctx) {
public Result throttle(String actionId, WatchExecutionContext ctx) {
if (!licenseService.enabled()) {
return Result.throttle("watcher license expired");
}
if (periodThrottler != null) {
Result throttleResult = periodThrottler.throttle(ctx);
Result throttleResult = periodThrottler.throttle(actionId, ctx);
if (throttleResult.throttle()) {
return throttleResult;
}
}
return ackThrottler.throttle(ctx);
return ackThrottler.throttle(actionId, ctx);
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.watcher.actions.throttler;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.joda.time.PeriodType;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.clock.Clock;
/**
* This throttler throttles the action based on its last <b>successful</b> execution time. If the time passed since
* the last successful execution is lower than the given period, the aciton will be throttled.
*/
public class PeriodThrottler implements Throttler {
private final @Nullable TimeValue period;
private final PeriodType periodType;
private final Clock clock;
public PeriodThrottler(Clock clock, TimeValue period) {
this(clock, period, PeriodType.minutes());
}
public PeriodThrottler(Clock clock, TimeValue period, PeriodType periodType) {
this.period = period;
this.periodType = periodType;
this.clock = clock;
}
public TimeValue period() {
return period;
}
@Override
public Result throttle(String actionId, WatchExecutionContext ctx) {
TimeValue period = this.period;
if (period == null) {
// falling back on the throttle period of the watch
period = ctx.watch().throttlePeriod();
}
if (period == null) {
// falling back on the default throttle period of watcher
period = ctx.defaultThrottlePeriod();
}
ActionStatus status = ctx.watch().status().actionStatus(actionId);
if (status.lastSuccessfulExecution() == null) {
return Result.NO;
}
TimeValue timeElapsed = clock.timeElapsedSince(status.lastSuccessfulExecution().timestamp());
if (timeElapsed.getMillis() <= period.getMillis()) {
return Result.throttle("throttling interval is set to [{}] but time elapsed since last execution is [{}]",
period.format(periodType), timeElapsed.format(periodType));
}
return Result.NO;
}
}

View File

@ -3,8 +3,10 @@
* 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.watcher.throttle;
package org.elasticsearch.watcher.actions.throttler;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
/**
@ -12,16 +14,16 @@ import org.elasticsearch.watcher.execution.WatchExecutionContext;
*/
public interface Throttler {
public static final Throttler NO_THROTTLE = new Throttler() {
Throttler NO_THROTTLE = new Throttler() {
@Override
public Result throttle(WatchExecutionContext ctx) {
public Result throttle(String actionId, WatchExecutionContext ctx) {
return Result.NO;
}
};
Result throttle(WatchExecutionContext ctx);
Result throttle(String actionId, WatchExecutionContext ctx);
static class Result {
class Result {
public static final Result NO = new Result(false, null);
@ -33,8 +35,8 @@ public interface Throttler {
this.reason = reason;
}
public static Result throttle(String reason) {
return new Result(true, reason);
public static Result throttle(String reason, Object... args) {
return new Result(true, LoggerMessageFormat.format(reason, args));
}
public boolean throttle() {
@ -46,4 +48,8 @@ public interface Throttler {
}
}
interface Field {
ParseField THROTTLE_PERIOD = new ParseField("throttle_period");
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.watcher.actions.webhook;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
@ -15,12 +16,11 @@ import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
import java.util.Map;
/**
*/
public class ExecutableWebhookAction extends ExecutableAction<WebhookAction, WebhookAction.Result> {
public class ExecutableWebhookAction extends ExecutableAction<WebhookAction> {
private final HttpClient httpClient;
private final TemplateEngine templateEngine;
@ -32,7 +32,7 @@ public class ExecutableWebhookAction extends ExecutableAction<WebhookAction, Web
}
@Override
protected WebhookAction.Result doExecute(String actionId, WatchExecutionContext ctx, Payload payload) throws IOException {
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
HttpRequest request = action.requestTemplate.render(templateEngine, model);
@ -44,14 +44,10 @@ public class ExecutableWebhookAction extends ExecutableAction<WebhookAction, Web
HttpResponse response = httpClient.execute(request);
int status = response.status();
if (status >= 300) {
if (status >= 400) {
logger.warn("received http status [{}] when connecting to watch action [{}/{}/{}]", status, ctx.watch().id(), type(), actionId);
return new WebhookAction.Result.Failure(request, response);
}
return new WebhookAction.Result.Executed(request, response);
}
@Override
protected WebhookAction.Result failure(String reason) {
return new WebhookAction.Result.Failure(reason);
return new WebhookAction.Result.Success(request, response);
}
}

View File

@ -6,15 +6,18 @@
package org.elasticsearch.watcher.actions.webhook;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.Action.Result.Status;
import org.elasticsearch.watcher.actions.Action.Result.Throttled;
import org.elasticsearch.watcher.actions.logging.LoggingActionException;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpRequestTemplate;
import org.elasticsearch.watcher.support.http.HttpResponse;
import java.io.IOException;
import java.util.Locale;
/**
*
@ -67,23 +70,94 @@ public class WebhookAction implements Action {
}
}
public static Action.Result parseResult(String watchId, String actionId, XContentParser parser, HttpRequest.Parser requestParser) throws IOException {
Status status = null;
String reason = null;
HttpRequest request = null;
HttpResponse response = 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 (Field.REQUEST.match(currentFieldName)) {
try {
request = requestParser.parse(parser);
} catch (HttpRequest.Parser.ParseException pe) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (Field.RESPONSE.match(currentFieldName)) {
try {
response = HttpResponse.parse(parser);
} catch (HttpResponse.ParseException pe) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else if (Field.STATUS.match(currentFieldName)) {
try {
status = Status.valueOf(parser.text().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new LoggingActionException("could not parse [{}] action result [{}/{}]. unknown result status value [{}]", TYPE, watchId, actionId, parser.text());
}
} else {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
assertNotNull(status, "could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.STATUS.getPreferredName());
switch (status) {
case SUCCESS:
assertNotNull(request, "could not parse successful [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REQUEST.getPreferredName());
assertNotNull(response, "could not parse successful [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.RESPONSE.getPreferredName());
return new Result.Success(request, response);
case SIMULATED:
assertNotNull(request, "could not parse simulated [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REQUEST.getPreferredName());
return new Result.Simulated(request);
case THROTTLED:
assertNotNull(reason, "could not parse throttled [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REASON.getPreferredName());
return new Throttled(TYPE, reason);
default: // Failure
assert status == Status.FAILURE : "unhandled action result status";
assertNotNull(reason, "could not parse failure [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REASON.getPreferredName());
if (request != null || response != null) {
assertNotNull(request, "could not parse failure [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REQUEST.getPreferredName());
assertNotNull(response, "could not parse failure [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.RESPONSE.getPreferredName());
return new Result.Failure(request, response, reason);
}
return new Action.Result.Failure(TYPE, reason);
}
}
private static void assertNotNull(Object value, String message, Object... args) {
if (value == null) {
throw new WebhookActionException(message, args);
}
}
public static Builder builder(HttpRequestTemplate requestTemplate) {
return new Builder(requestTemplate);
}
public abstract static class Result extends Action.Result {
public interface Result {
public Result(boolean success) {
super(TYPE, success);
}
public static class Executed extends Result {
class Success extends Action.Result implements Result {
private final HttpRequest request;
private final HttpResponse response;
public Executed(HttpRequest request, HttpResponse response) {
super(response.status() < 400);
public Success(HttpRequest request, HttpResponse response) {
super(TYPE, Status.SUCCESS);
this.request = request;
this.response = response;
}
@ -103,31 +177,43 @@ public class WebhookAction implements Action {
}
}
static class Failure extends Result {
class Failure extends Action.Result.Failure implements Result {
private final String reason;
private final HttpRequest request;
private final HttpResponse response;
Failure(String reason, Object... args) {
super(false);
this.reason = LoggerMessageFormat.format(reason, args);
public Failure(HttpRequest request, HttpResponse response) {
this(request, response, "received [{}] status code", response.status());
}
public String reason() {
return reason;
private Failure(HttpRequest request, HttpResponse response, String reason, Object... args) {
super(TYPE, reason, args);
this.request = request;
this.response = response;
}
public HttpResponse response() {
return response;
}
public HttpRequest request() {
return request;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.REASON.getPreferredName(), reason);
super.xContentBody(builder, params);
return builder.field(Field.REQUEST.getPreferredName(), request, params)
.field(Field.RESPONSE.getPreferredName(), response, params);
}
}
static class Simulated extends Result {
class Simulated extends Action.Result implements Result {
private final HttpRequest request;
public Simulated(HttpRequest request) {
super(true);
super(TYPE, Status.SIMULATED);
this.request = request;
}
@ -137,79 +223,10 @@ public class WebhookAction implements Action {
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(Field.SIMULATED_REQUEST.getPreferredName(), request, params);
return builder.field(Field.REQUEST.getPreferredName(), request, params);
}
}
public static Result parse(String watchId, String actionId, XContentParser parser, HttpRequest.Parser requestParser) throws IOException {
Boolean success = null;
String reason = null;
HttpRequest request = null;
HttpRequest simulatedRequest = null;
HttpResponse response = 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 (Field.REQUEST.match(currentFieldName)) {
try {
request = requestParser.parse(parser);
} catch (HttpRequest.Parser.ParseException pe) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (Field.SIMULATED_REQUEST.match(currentFieldName)) {
try {
simulatedRequest = requestParser.parse(parser);
} catch (HttpRequest.Parser.ParseException pe) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (Field.RESPONSE.match(currentFieldName)) {
try {
response = HttpResponse.parse(parser);
} catch (HttpResponse.ParseException pe) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. failed parsing [{}] field", pe, TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.REASON.match(currentFieldName)) {
reason = parser.text();
} else {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. unexpected string field [{}]", TYPE, watchId, actionId, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Field.SUCCESS.match(currentFieldName)) {
success = parser.booleanValue();
} else {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. unexpected boolean field [{}]", TYPE, watchId, actionId, watchId, currentFieldName);
}
} else {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
if (success == null) {
throw new WebhookActionException("could not parse [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.SUCCESS.getPreferredName());
}
if (simulatedRequest != null) {
return new Simulated(simulatedRequest);
}
if (reason != null) {
return new Failure(reason);
}
if (request == null) {
throw new WebhookActionException("could not parse executed [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.REQUEST.getPreferredName());
}
if (response == null) {
throw new WebhookActionException("could not parse executed [{}] action result [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, currentFieldName, Field.RESPONSE.getPreferredName());
}
return new Executed(request, response);
}
}
public static class Builder implements Action.Builder<WebhookAction> {
@ -228,7 +245,6 @@ public class WebhookAction implements Action {
interface Field extends Action.Field {
ParseField REQUEST = new ParseField("request");
ParseField SIMULATED_REQUEST = new ParseField("simulated_request");
ParseField RESPONSE = new ParseField("response");
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.support.http.HttpClient;
@ -21,7 +22,7 @@ import java.io.IOException;
/**
*
*/
public class WebhookActionFactory extends ActionFactory<WebhookAction, WebhookAction.Result, ExecutableWebhookAction> {
public class WebhookActionFactory extends ActionFactory<WebhookAction, ExecutableWebhookAction> {
private final HttpClient httpClient;
private final HttpRequest.Parser requestParser;
@ -50,8 +51,8 @@ public class WebhookActionFactory extends ActionFactory<WebhookAction, WebhookAc
}
@Override
public WebhookAction.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return WebhookAction.Result.parse(wid.watchId(), actionId, parser, requestParser);
public Action.Result parseResult(Wid wid, String actionId, XContentParser parser) throws IOException {
return WebhookAction.parseResult(wid.watchId(), actionId, parser, requestParser);
}
@Override

View File

@ -20,6 +20,7 @@ import org.elasticsearch.watcher.condition.always.AlwaysCondition;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.input.none.NoneInput;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.trigger.Trigger;
import org.elasticsearch.watcher.watch.Watch;
@ -40,7 +41,7 @@ public class WatchSourceBuilder implements ToXContent {
private Condition condition = AlwaysCondition.INSTANCE;
private Transform transform = null;
private Map<String, TransformedAction> actions = new HashMap<>();
private TimeValue throttlePeriod = null;
private TimeValue defaultThrottlePeriod = null;
private Map<String, Object> metadata;
public WatchSourceBuilder trigger(Trigger.Builder trigger) {
@ -79,32 +80,29 @@ public class WatchSourceBuilder implements ToXContent {
return transform(transform.build());
}
public WatchSourceBuilder throttlePeriod(TimeValue throttlePeriod) {
this.throttlePeriod = throttlePeriod;
return this;
}
public WatchSourceBuilder addAction(String id, Transform.Builder transform, Action action) {
return addAction(id, transform.build(), action);
}
public WatchSourceBuilder addAction(String id, Action action) {
actions.put(id, new TransformedAction(id, action));
public WatchSourceBuilder defaultThrottlePeriod(TimeValue throttlePeriod) {
this.defaultThrottlePeriod = throttlePeriod;
return this;
}
public WatchSourceBuilder addAction(String id, Action.Builder action) {
return addAction(id, action.build());
return addAction(id, null, null, action.build());
}
public WatchSourceBuilder addAction(String id, TimeValue throttlePeriod, Action.Builder action) {
return addAction(id, throttlePeriod, null, action.build());
}
public WatchSourceBuilder addAction(String id, Transform.Builder transform, Action.Builder action) {
actions.put(id, new TransformedAction(id, action.build(), transform.build()));
return this;
return addAction(id, null, transform.build(), action.build());
}
public WatchSourceBuilder addAction(String id, Transform transform, Action action) {
actions.put(id, new TransformedAction(id, action, transform));
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));
return this;
}
@ -124,36 +122,36 @@ public class WatchSourceBuilder implements ToXContent {
if (trigger == null) {
throw new BuilderException("failed to build watch source. no trigger defined");
}
builder.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
builder.startObject(Watch.Field.TRIGGER.getPreferredName())
.field(trigger.type(), trigger, params)
.endObject();
builder.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
builder.startObject(Watch.Field.INPUT.getPreferredName())
.field(input.type(), input, params)
.endObject();
builder.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
builder.startObject(Watch.Field.CONDITION.getPreferredName())
.field(condition.type(), condition, params)
.endObject();
if (transform != null) {
builder.startObject(Watch.Parser.TRANSFORM_FIELD.getPreferredName())
builder.startObject(Watch.Field.TRANSFORM.getPreferredName())
.field(transform.type(), transform, params)
.endObject();
}
if (throttlePeriod != null) {
builder.field(Watch.Parser.THROTTLE_PERIOD_FIELD.getPreferredName(), throttlePeriod.getMillis());
if (defaultThrottlePeriod != null) {
builder.field(Watch.Field.THROTTLE_PERIOD.getPreferredName(), defaultThrottlePeriod.getMillis());
}
builder.startObject(Watch.Parser.ACTIONS_FIELD.getPreferredName());
builder.startObject(Watch.Field.ACTIONS.getPreferredName());
for (Map.Entry<String, TransformedAction> entry : actions.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params);
}
builder.endObject();
if (metadata != null) {
builder.field(Watch.Parser.META_FIELD.getPreferredName(), metadata);
builder.field(Watch.Field.METADATA.getPreferredName(), metadata);
}
return builder.endObject();
@ -173,14 +171,12 @@ public class WatchSourceBuilder implements ToXContent {
private final String id;
private final Action action;
private final @Nullable TimeValue throttlePeriod;
private final @Nullable Transform transform;
public TransformedAction(String id, Action action) {
this(id, action, null);
}
public TransformedAction(String id, Action action, @Nullable Transform transform) {
public TransformedAction(String id, Action action, @Nullable TimeValue throttlePeriod, @Nullable Transform transform) {
this.id = id;
this.throttlePeriod = throttlePeriod;
this.transform = transform;
this.action = action;
}
@ -188,6 +184,9 @@ public class WatchSourceBuilder implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (throttlePeriod != null) {
builder.field(Throttler.Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod.getMillis());
}
if (transform != null) {
builder.startObject(Transform.Field.TRANSFORM.getPreferredName())
.field(transform.type(), transform, params)

View File

@ -0,0 +1,76 @@
/*
* 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.watcher.execution;
import org.elasticsearch.watcher.WatcherException;
import java.util.Locale;
/**
*
*/
public enum ActionExecutionMode {
/**
* The action will be simulated (not actually executed) and it will be throttled if needed.
*/
SIMULATE((byte) 1),
/**
* The action will be simulated (not actually executed) and it will <b>not</b> be throttled.
*/
FORCE_SIMULATE((byte) 2),
/**
* The action will be executed and it will be throttled if needed.
*/
EXECUTE((byte) 3),
/**
* The action will be executed and it will <b>not</b> be throttled.
*/
FORCE_EXECUTE((byte) 4),
/**
* The action will be skipped (it won't be executed nor simulated) - effectively it will be forcefully throttled
*/
SKIP((byte) 5);
private final byte id;
ActionExecutionMode(byte id) {
this.id = id;
}
public final byte id() {
return id;
}
public static ActionExecutionMode resolve(byte id) {
switch (id) {
case 1: return SIMULATE;
case 2: return FORCE_SIMULATE;
case 3: return EXECUTE;
case 4: return FORCE_EXECUTE;
case 5: return SKIP;
}
throw new WatcherException("unknown action execution mode id [{}]", id);
}
public static ActionExecutionMode resolve(String key) {
if (key == null) {
return null;
}
switch (key.toLowerCase(Locale.ROOT)) {
case "simulate": return SIMULATE;
case "force_simulate": return FORCE_SIMULATE;
case "execute": return EXECUTE;
case "force_execute": return FORCE_EXECUTE;
case "skip": return SKIP;
}
throw new WatcherException("unknown action execution mode [{}]", key);
}
}

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
@ -23,9 +24,6 @@ import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.history.WatchRecord;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchLockService;
@ -47,6 +45,8 @@ public class ExecutionService extends AbstractComponent {
private final WatchStore watchStore;
private final WatchLockService watchLockService;
private final Clock clock;
private final TimeValue defaultThrottlePeriod;
private final ConcurrentMap<String, WatchExecution> currentExecutions = ConcurrentCollections.newConcurrentMapWithAggressiveConcurrency();
private final AtomicBoolean started = new AtomicBoolean(false);
@ -60,6 +60,8 @@ public class ExecutionService extends AbstractComponent {
this.watchStore = watchStore;
this.watchLockService = watchLockService;
this.clock = clock;
TimeValue throttlePeriod = componentSettings.getAsTime("default_throttle_period", TimeValue.timeValueSeconds(5));
this.defaultThrottlePeriod = throttlePeriod.millis() == 0 ? null : throttlePeriod;
}
public void start(ClusterState state) {
@ -98,6 +100,10 @@ public class ExecutionService extends AbstractComponent {
return started.get();
}
public TimeValue defaultThrottlePeriod() {
return defaultThrottlePeriod;
}
public long queueSize() {
return executor.queue().size();
}
@ -135,7 +141,7 @@ public class ExecutionService extends AbstractComponent {
logger.warn("unable to find watch [{}] in the watch store, perhaps it has been deleted", event.jobName());
continue;
}
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event);
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event, defaultThrottlePeriod);
contexts.add(ctx);
records.add(new WatchRecord(ctx.id(), watch, event));
}
@ -200,7 +206,7 @@ public class ExecutionService extends AbstractComponent {
logger.warn("unable to find watch [{}] in the watch store, perhaps it has been deleted", event.jobName());
continue;
}
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event);
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event, defaultThrottlePeriod);
contexts.add(ctx);
records.add(new WatchRecord(ctx.id(), watch, event));
}
@ -281,26 +287,12 @@ public class ExecutionService extends AbstractComponent {
}
if (conditionResult.met()) {
Throttler.Result throttleResult = ctx.throttleResult();
if (throttleResult == null) {
throttleResult = watch.throttler().throttle(ctx);
ctx.onThrottleResult(throttleResult);
}
if (!throttleResult.throttle()) {
ExecutableTransform transform = watch.transform();
if (transform != null) {
ctx.beforeWatchTransform();
Transform.Result result = watch.transform().execute(ctx, inputResult.payload());
ctx.onTransformResult(result);
}
ctx.beforeAction();
for (ActionWrapper action : watch.actions()) {
ActionWrapper.Result actionResult = action.execute(ctx);
ctx.onActionResult(actionResult);
}
}
}
return ctx.finish();
}
@ -314,7 +306,7 @@ public class ExecutionService extends AbstractComponent {
record.update(WatchRecord.State.DELETED_WHILE_QUEUED, message);
historyStore.update(record);
} else {
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, clock.now(UTC), record.triggerEvent());
TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, clock.now(UTC), record.triggerEvent(), defaultThrottlePeriod);
executeAsync(ctx, record);
counter++;
}

View File

@ -5,17 +5,17 @@
*/
package org.elasticsearch.watcher.execution;
import org.elasticsearch.common.base.Predicate;
import org.elasticsearch.common.base.Predicates;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.trigger.manual.ManualTriggerEvent;
import org.elasticsearch.watcher.watch.Watch;
import java.util.Set;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
@ -23,30 +23,58 @@ import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
*/
public class ManualExecutionContext extends WatchExecutionContext {
private final Predicate<String> simulateActionPredicate;
private final Map<String, ActionExecutionMode> actionModes;
private final boolean recordExecution;
ManualExecutionContext(Watch watch, DateTime executionTime, ManualTriggerEvent triggerEvent,
Input.Result inputResult, Condition.Result conditionResult,
Throttler.Result throttlerResult, Predicate<String> simulateActionPredicate,
boolean recordExecution) {
super(watch, executionTime, triggerEvent);
TimeValue defaultThrottlePeriod, Input.Result inputResult, Condition.Result conditionResult,
Map<String, ActionExecutionMode> actionModes, boolean recordExecution) {
super(watch, executionTime, triggerEvent, defaultThrottlePeriod);
this.actionModes = actionModes;
this.recordExecution = recordExecution;
if (inputResult != null) {
onInputResult(inputResult);
}
if (conditionResult != null) {
onConditionResult(conditionResult);
}
if (throttlerResult != null) {
onThrottleResult(throttlerResult);
ActionExecutionMode allMode = actionModes.get(Builder.ALL);
if (allMode == null || allMode == ActionExecutionMode.SKIP) {
boolean throttleAll = allMode == ActionExecutionMode.SKIP;
for (ActionWrapper action : watch.actions()) {
if (throttleAll) {
onActionResult(new ActionWrapper.Result(action.id(), new Action.Result.Throttled(action.action().type(), "manually skipped")));
} else {
ActionExecutionMode mode = actionModes.get(action.id());
if (mode == ActionExecutionMode.SKIP) {
onActionResult(new ActionWrapper.Result(action.id(), new Action.Result.Throttled(action.action().type(), "manually skipped")));
}
}
}
}
this.simulateActionPredicate = simulateActionPredicate;
this.recordExecution = recordExecution;
}
@Override
public final boolean simulateAction(String actionId) {
return simulateActionPredicate.apply(actionId);
ActionExecutionMode mode = actionModes.get(Builder.ALL);
if (mode == ActionExecutionMode.SIMULATE || mode == ActionExecutionMode.FORCE_SIMULATE) {
return true;
}
mode = actionModes.get(actionId);
return mode == ActionExecutionMode.SIMULATE || mode == ActionExecutionMode.FORCE_SIMULATE;
}
@Override
public boolean skipThrottling(String actionId) {
ActionExecutionMode mode = actionModes.get(Builder.ALL);
if (mode == ActionExecutionMode.FORCE_EXECUTE || mode == ActionExecutionMode.FORCE_SIMULATE) {
return true;
}
mode = actionModes.get(actionId);
return mode == ActionExecutionMode.FORCE_EXECUTE || mode == ActionExecutionMode.FORCE_SIMULATE;
}
@Override
@ -54,26 +82,28 @@ public class ManualExecutionContext extends WatchExecutionContext {
return recordExecution;
}
public static Builder builder(Watch watch, ManualTriggerEvent event) {
return new Builder(watch, event);
public static Builder builder(Watch watch, ManualTriggerEvent event, TimeValue defaultThrottlePeriod) {
return new Builder(watch, event, defaultThrottlePeriod);
}
public static class Builder {
static final String ALL = "_all";
private final Watch watch;
private final ManualTriggerEvent triggerEvent;
private final TimeValue defaultThrottlePeriod;
protected DateTime executionTime;
private boolean recordExecution = false;
private Predicate<String> simulateActionPredicate = Predicates.alwaysFalse();
private ImmutableMap.Builder<String, ActionExecutionMode> actionModes = ImmutableMap.builder();
private Input.Result inputResult;
private Condition.Result conditionResult;
private Throttler.Result throttlerResult;
private Builder(Watch watch, ManualTriggerEvent triggerEvent) {
private Builder(Watch watch, ManualTriggerEvent triggerEvent, TimeValue defaultThrottlePeriod) {
this.watch = watch;
assert triggerEvent != null;
this.triggerEvent = triggerEvent;
this.defaultThrottlePeriod = defaultThrottlePeriod;
}
public Builder executionTime(DateTime executionTime) {
@ -86,13 +116,15 @@ public class ManualExecutionContext extends WatchExecutionContext {
return this;
}
public Builder simulateAllActions() {
simulateActionPredicate = Predicates.alwaysTrue();
return this;
public Builder allActionsMode(ActionExecutionMode mode) {
return actionMode(ALL, mode);
}
public Builder simulateActions(String... ids) {
simulateActionPredicate = Predicates.or(simulateActionPredicate, new IdsPredicate(ids));
public Builder actionMode(String id, ActionExecutionMode mode) {
if (ALL.equals(id)) {
actionModes = ImmutableMap.builder();
}
actionModes.put(id, mode);
return this;
}
@ -106,34 +138,11 @@ public class ManualExecutionContext extends WatchExecutionContext {
return this;
}
public Builder withThrottle(Throttler.Result throttlerResult) {
this.throttlerResult = throttlerResult;
return this;
}
public ManualExecutionContext build() {
if (executionTime == null) {
executionTime = DateTime.now(UTC);
}
return new ManualExecutionContext(watch, executionTime, triggerEvent, inputResult, conditionResult, throttlerResult, simulateActionPredicate, recordExecution);
}
}
static class IdsPredicate implements Predicate<String> {
private final Set<String> ids;
private Set<String> ids() {
return ids;
}
IdsPredicate(String... ids) {
this.ids = ImmutableSet.copyOf(ids);
}
@Override
public boolean apply(String id) {
return ids.contains(id);
return new ManualExecutionContext(watch, executionTime, triggerEvent, defaultThrottlePeriod, inputResult, conditionResult, actionModes.build(), recordExecution);
}
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.watcher.execution;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.watch.Watch;
@ -13,17 +14,22 @@ import org.elasticsearch.watcher.watch.Watch;
*/
public class TriggeredExecutionContext extends WatchExecutionContext {
public TriggeredExecutionContext(Watch watch, DateTime executionTime, TriggerEvent triggerEvent) {
super(watch, executionTime, triggerEvent);
public TriggeredExecutionContext(Watch watch, DateTime executionTime, TriggerEvent triggerEvent, TimeValue defaultThrottlePeriod) {
super(watch, executionTime, triggerEvent, defaultThrottlePeriod);
}
@Override
final public boolean simulateAction(String actionId) {
public final boolean simulateAction(String actionId) {
return false;
}
@Override
final public boolean recordExecution() {
public final boolean skipThrottling(String actionId) {
return false;
}
@Override
public final boolean recordExecution() {
return true;
}
}

View File

@ -7,17 +7,19 @@ package org.elasticsearch.watcher.execution;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import java.util.concurrent.ConcurrentMap;
import java.io.IOException;
/**
*
@ -28,21 +30,24 @@ public abstract class WatchExecutionContext {
private final Watch watch;
private final DateTime executionTime;
private final TriggerEvent triggerEvent;
private final TimeValue defaultThrottlePeriod;
private Input.Result inputResult;
private Condition.Result conditionResult;
private Throttler.Result throttleResult;
private Transform.Result transformResult;
private ConcurrentMap<String, ActionWrapper.Result> actionsResults = ConcurrentCollections.newConcurrentMap();
private Payload payload;
private Payload transformedPayload;
private volatile ExecutionPhase executionPhase = ExecutionPhase.AWAITS_EXECUTION;
public WatchExecutionContext(Watch watch, DateTime executionTime, TriggerEvent triggerEvent) {
public WatchExecutionContext(Watch watch, DateTime executionTime, TriggerEvent triggerEvent, TimeValue defaultThrottlePeriod) {
this.id = new Wid(watch.id(), watch.nonce(), executionTime);
this.watch = watch;
this.executionTime = executionTime;
this.triggerEvent = triggerEvent;
this.id = new Wid(watch.id(), watch.nonce(), executionTime);
this.defaultThrottlePeriod = defaultThrottlePeriod;
}
/**
@ -50,6 +55,8 @@ public abstract class WatchExecutionContext {
*/
public abstract boolean simulateAction(String actionId);
public abstract boolean skipThrottling(String actionId);
/**
* @return true if this execution should be recorded in the .watch_history index
*/
@ -67,6 +74,13 @@ public abstract class WatchExecutionContext {
return executionTime;
}
/**
* @return The default throttle period in the system.
*/
public TimeValue defaultThrottlePeriod() {
return defaultThrottlePeriod;
}
public TriggerEvent triggerEvent() {
return triggerEvent;
}
@ -75,6 +89,27 @@ public abstract class WatchExecutionContext {
return payload;
}
/**
* If a transform is associated with the watch itself, this method will return the transformed payload,
* that is, the payload after the transform was applied to it. note, after calling this method, the payload
* will be the same as the transformed payload. Also note, that the transform is only applied once. So calling
* this method multiple times will always result in the same payload.
*/
public Payload transformedPayload() throws IOException {
if (transformedPayload != null) {
return transformedPayload;
}
ExecutableTransform transform = watch.transform();
if (transform == null) {
transformedPayload = payload;
return transformedPayload;
}
this.transformResult = watch.transform().execute(this, payload);
this.payload = transformResult.payload();
this.transformedPayload = this.payload;
return transformedPayload;
}
public ExecutionPhase executionPhase() {
return executionPhase;
}
@ -108,25 +143,10 @@ public abstract class WatchExecutionContext {
return conditionResult;
}
public void onThrottleResult(Throttler.Result throttleResult) {
this.throttleResult = throttleResult;
if (recordExecution()) {
if (throttleResult.throttle()) {
watch.status().onThrottle(executionTime, throttleResult.reason());
} else {
watch.status().onExecution(executionTime);
}
}
}
public void beforeWatchTransform() {
this.executionPhase = ExecutionPhase.WATCH_TRANSFORM;
}
public Throttler.Result throttleResult() {
return throttleResult;
}
public void onTransformResult(Transform.Result transformResult) {
this.transformResult = transformResult;
this.payload = transformResult.payload();
@ -142,6 +162,9 @@ public abstract class WatchExecutionContext {
public void onActionResult(ActionWrapper.Result result) {
actionsResults.put(result.id(), result);
if (recordExecution()) {
watch.status().onActionResult(result.id(), executionTime, result.action());
}
}
public ExecutableActions.Results actionsResults() {

View File

@ -19,7 +19,6 @@ import org.elasticsearch.watcher.condition.ConditionRegistry;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.input.InputRegistry;
import org.elasticsearch.watcher.support.WatcherDateUtils;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
@ -35,19 +34,17 @@ public class WatchExecutionResult implements ToXContent {
private final DateTime executionTime;
private final Input.Result inputResult;
private final Condition.Result conditionResult;
private final Throttler.Result throttleResult;
private final @Nullable Transform.Result transformResult;
private final ExecutableActions.Results actionsResults;
public WatchExecutionResult(WatchExecutionContext context) {
this(context.executionTime(), context.inputResult(), context.conditionResult(), context.throttleResult(), context.transformResult(), context.actionsResults());
this(context.executionTime(), context.inputResult(), context.conditionResult(), context.transformResult(), context.actionsResults());
}
WatchExecutionResult(DateTime executionTime, Input.Result inputResult, Condition.Result conditionResult, Throttler.Result throttleResult, @Nullable Transform.Result transformResult, ExecutableActions.Results actionsResults) {
WatchExecutionResult(DateTime executionTime, Input.Result inputResult, Condition.Result conditionResult, @Nullable Transform.Result transformResult, ExecutableActions.Results actionsResults) {
this.executionTime = executionTime;
this.inputResult = inputResult;
this.conditionResult = conditionResult;
this.throttleResult = throttleResult;
this.transformResult = transformResult;
this.actionsResults = actionsResults;
}
@ -64,10 +61,6 @@ public class WatchExecutionResult implements ToXContent {
return conditionResult;
}
public Throttler.Result throttleResult() {
return throttleResult;
}
public Transform.Result transformResult() {
return transformResult;
}
@ -89,12 +82,6 @@ public class WatchExecutionResult implements ToXContent {
builder.field(Field.CONDITION.getPreferredName());
ConditionRegistry.writeResult(conditionResult, builder, params);
}
if (throttleResult != null && throttleResult.throttle()) {
builder.field(Field.THROTTLED.getPreferredName(), throttleResult.throttle());
if (throttleResult.reason() != null) {
builder.field(Field.THROTTLE_REASON.getPreferredName(), throttleResult.reason());
}
}
if (transformResult != null) {
builder.startObject(Transform.Field.TRANSFORM.getPreferredName())
.field(transformResult.type(), transformResult, params)
@ -110,8 +97,6 @@ public class WatchExecutionResult implements ToXContent {
public static WatchExecutionResult parse(Wid wid, XContentParser parser, ConditionRegistry conditionRegistry, ActionRegistry actionRegistry,
InputRegistry inputRegistry, TransformRegistry transformRegistry) throws IOException {
DateTime executionTime = null;
boolean throttled = false;
String throttleReason = null;
ExecutableActions.Results actionResults = null;
Input.Result inputResult = null;
Condition.Result conditionResult = null;
@ -128,22 +113,9 @@ public class WatchExecutionResult implements ToXContent {
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch execution [{}]. failed to parse date field [{}]", pe, wid, currentFieldName);
}
} else if (token.isValue()) {
if (Field.THROTTLE_REASON.match(currentFieldName)) {
throttleReason = parser.text();
} else if (Field.THROTTLED.match(currentFieldName)) {
throttled = parser.booleanValue();
} else {
throw new WatcherException("could not parse watch execution [{}]. unexpected field [{}]", wid, currentFieldName);
}
} else if (token == XContentParser.Token.START_ARRAY) {
if (Field.ACTIONS.match(currentFieldName)) {
} else if (Field.ACTIONS.match(currentFieldName)) {
actionResults = actionRegistry.parseResults(wid, parser);
} else {
throw new WatcherException("could not parse watch execution [{}]. unexpected field [{}]", wid, currentFieldName);
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Field.INPUT.match(currentFieldName)) {
} else if (Field.INPUT.match(currentFieldName)) {
inputResult = inputRegistry.parseResult(wid.watchId(), parser);
} else if (Field.CONDITION.match(currentFieldName)) {
conditionResult = conditionRegistry.parseResult(wid.watchId(), parser);
@ -152,17 +124,11 @@ public class WatchExecutionResult implements ToXContent {
} else {
throw new WatcherException("could not parse watch execution [{}]. unexpected field [{}]", wid, currentFieldName);
}
} else {
throw new WatcherException("could not parse watch execution [{}]. unexpected token [{}]", wid, token);
}
}
if (executionTime == null) {
throw new WatcherException("could not parse watch execution [{}]. missing required date field [{}]", wid, Field.EXECUTION_TIME.getPreferredName());
}
Throttler.Result throttleResult = throttled ? Throttler.Result.throttle(throttleReason) : Throttler.Result.NO;
return new WatchExecutionResult(executionTime, inputResult, conditionResult, throttleResult, transformResult, actionResults);
return new WatchExecutionResult(executionTime, inputResult, conditionResult, transformResult, actionResults);
}
}
@ -171,7 +137,5 @@ public class WatchExecutionResult implements ToXContent {
ParseField INPUT = new ParseField("input");
ParseField CONDITION = new ParseField("condition");
ParseField ACTIONS = new ParseField("actions");
ParseField THROTTLED = new ParseField("throttled");
ParseField THROTTLE_REASON = new ParseField("throttle_reason");
}
}

View File

@ -120,7 +120,7 @@ public class WatchRecord implements ToXContent {
if (!execution.conditionResult().met()) {
state = State.EXECUTION_NOT_NEEDED;
} else {
if (execution.throttleResult().throttle()) {
if (execution.actionsResults().throttled()) {
state = State.THROTTLED;
} else {
state = State.EXECUTED;
@ -135,10 +135,10 @@ public class WatchRecord implements ToXContent {
builder.startObject(Field.TRIGGER_EVENT.getPreferredName())
.field(triggerEvent.type(), triggerEvent, params)
.endObject();
builder.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
builder.startObject(Watch.Field.INPUT.getPreferredName())
.field(input.type(), input, params)
.endObject();
builder.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
builder.startObject(Watch.Field.CONDITION.getPreferredName())
.field(condition.type(), condition, params)
.endObject();
builder.field(Field.STATE.getPreferredName(), state.id());
@ -246,9 +246,9 @@ public class WatchRecord implements ToXContent {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (Watch.Parser.INPUT_FIELD.match(currentFieldName)) {
if (Watch.Field.INPUT.match(currentFieldName)) {
record.input = inputRegistry.parse(id, parser);
} else if (Watch.Parser.CONDITION_FIELD.match(currentFieldName)) {
} else if (Watch.Field.CONDITION.match(currentFieldName)) {
record.condition = conditionRegistry.parseCondition(id, parser);
} else if (Field.METADATA.match(currentFieldName)) {
record.metadata = parser.map();

View File

@ -38,7 +38,7 @@ public class RestAckWatchAction extends WatcherRestHandler {
@Override
public RestResponse buildResponse(AckWatchResponse response, XContentBuilder builder) throws Exception {
return new BytesRestResponse(RestStatus.OK, builder.startObject()
.field(Watch.Parser.STATUS_FIELD.getPreferredName(), response.getStatus(), WatcherParams.HIDE_SECRETS)
.field(Watch.Field.STATUS.getPreferredName(), response.getStatus(), WatcherParams.HIDE_SECRETS)
.endObject());
}

View File

@ -16,6 +16,7 @@ import org.elasticsearch.rest.*;
import org.elasticsearch.rest.action.support.RestBuilderListener;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import org.elasticsearch.watcher.rest.WatcherRestHandler;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchRequest;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchRequestBuilder;
@ -29,13 +30,6 @@ import java.io.IOException;
*/
public class RestExecuteWatchAction extends WatcherRestHandler {
static ParseField RECORD_EXECUTION_FIELD = new ParseField("record_execution");
static ParseField SIMULATED_ACTIONS_FIELD = new ParseField("simulated_actions");
static ParseField ALTERNATIVE_INPUT_FIELD = new ParseField("alternative_input");
static ParseField IGNORE_CONDITION_FIELD = new ParseField("ignore_condition");
static ParseField IGNORE_THROTTLE_FIELD = new ParseField("ignore_throttle");
static ParseField TRIGGER_EVENT_FIELD = new ParseField("trigger_event");
final TriggerService triggerService;
@Inject
@ -62,82 +56,81 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
//This tightly binds the REST API to the java API
private ExecuteWatchRequest parseRequest(RestRequest request, WatcherClient client) throws IOException {
String watchId = request.param("id");
ExecuteWatchRequestBuilder executeWatchRequestBuilder = client.prepareExecuteWatch(watchId);
ExecuteWatchRequestBuilder builder = client.prepareExecuteWatch(watchId);
TriggerEvent triggerEvent = null;
if (request.content() != null && request.content().length() != 0) {
if (request.content() == null || request.content().length() == 0) {
throw new WatcherException("could not parse watch execution request for [{}]. missing required [{}] field.", watchId, Field.TRIGGER_EVENT.getPreferredName());
}
XContentParser parser = XContentHelper.createParser(request.content());
parser.nextToken();
String currentFieldName = null;
XContentParser.Token token = parser.nextToken();
for (; token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) {
switch (token) {
case FIELD_NAME:
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
break;
case VALUE_BOOLEAN:
if (IGNORE_CONDITION_FIELD.match(currentFieldName)) {
executeWatchRequestBuilder.setIgnoreCondition(parser.booleanValue());
} else if (IGNORE_THROTTLE_FIELD.match(currentFieldName)) {
executeWatchRequestBuilder.setIgnoreThrottle(parser.booleanValue());
} else if (RECORD_EXECUTION_FIELD.match(currentFieldName)) {
executeWatchRequestBuilder.setRecordExecution(parser.booleanValue());
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (Field.IGNORE_CONDITION.match(currentFieldName)) {
builder.setIgnoreCondition(parser.booleanValue());
} else if (Field.RECORD_EXECUTION.match(currentFieldName)) {
builder.setRecordExecution(parser.booleanValue());
} else {
throw new ParseException("invalid watch execution request, unexpected boolean value field [" + currentFieldName + "]");
throw new ParseException("could not parse watch execution request for [{}]. unexpected boolean field [{}]", watchId, currentFieldName);
}
break;
case START_OBJECT:
if (ALTERNATIVE_INPUT_FIELD.match(currentFieldName)) {
executeWatchRequestBuilder.setAlternativeInput(parser.map());
} else if (TRIGGER_EVENT_FIELD.match(currentFieldName)) {
} else if (token == XContentParser.Token.START_OBJECT) {
if (Field.ALTERNATIVE_INPUT.match(currentFieldName)) {
builder.setAlternativeInput(parser.map());
} else if (Field.TRIGGER_EVENT.match(currentFieldName)) {
triggerEvent = triggerService.parseTriggerEvent(watchId, watchId, parser);
} else {
throw new ParseException("invalid watch execution request, unexpected object value field [" + currentFieldName + "]");
builder.setTriggerEvent(triggerEvent);
} else if (Field.ACTION_MODES.match(currentFieldName)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.VALUE_STRING) {
try {
ActionExecutionMode mode = ActionExecutionMode.resolve(parser.textOrNull());
builder.setActionMode(currentFieldName, mode);
} catch (WatcherException we) {
throw new ParseException("could not parse watch execution request for [{}].", watchId, we);
}
break;
case START_ARRAY:
if (SIMULATED_ACTIONS_FIELD.match(currentFieldName)) {
for (XContentParser.Token arrayToken = parser.nextToken(); arrayToken != XContentParser.Token.END_ARRAY; arrayToken = parser.nextToken()) {
if (arrayToken == XContentParser.Token.VALUE_STRING) {
executeWatchRequestBuilder.addSimulatedActions(parser.text());
} else {
throw new ParseException("could not parse watch execution request for [{}]. unexpected array field [{}]", watchId, currentFieldName);
}
}
} else {
throw new ParseException("invalid watch execution request, unexpected array value field [" + currentFieldName + "]");
}
break;
case VALUE_STRING:
if (SIMULATED_ACTIONS_FIELD.match(currentFieldName)) {
if (parser.text().equals("_all")) {
executeWatchRequestBuilder.addSimulatedActions("_all");
} else {
throw new ParseException("invalid watch execution request, unexpected string value [" + parser.text() + "] for field [" + SIMULATED_ACTIONS_FIELD.getPreferredName() + "]");
throw new ParseException("could not parse watch execution request for [{}]. unexpected object field [{}]", watchId, currentFieldName);
}
} else {
throw new ParseException("invalid watch execution request, unexpected string value field [" + currentFieldName + "]");
}
break;
default:
throw new ParseException("invalid watch execution request, unexpected token field [" + token + "]");
}
throw new ParseException("could not parse watch execution request for [{}]. unexpected token [{}]", watchId, token);
}
}
if (triggerEvent == null) {
throw new WatcherException("[{}] is a required field.",TRIGGER_EVENT_FIELD.getPreferredName());
throw new WatcherException("could not parse watch execution request for [{}]. missing required [{}] field.", watchId, Field.TRIGGER_EVENT.getPreferredName());
}
executeWatchRequestBuilder.setTriggerEvent(triggerEvent);
return executeWatchRequestBuilder.request();
return builder.request();
}
public static class ParseException extends WatcherException {
public ParseException(String msg) {
super(msg);
public ParseException(String msg, Object... args) {
super(msg, args);
}
public ParseException(String msg, Throwable cause) {
super(msg, cause);
public ParseException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
interface Field {
ParseField RECORD_EXECUTION = new ParseField("record_execution");
ParseField ACTION_MODES = new ParseField("action_modes");
ParseField ALTERNATIVE_INPUT = new ParseField("alternative_input");
ParseField IGNORE_CONDITION = new ParseField("ignore_condition");
ParseField TRIGGER_EVENT = new ParseField("trigger_event");
}
}

View File

@ -89,6 +89,9 @@ public class WatcherDateUtils {
}
public static XContentBuilder writeDate(String fieldName, XContentBuilder builder, DateTime date) throws IOException {
if (date == null) {
return builder.nullField(fieldName);
}
return builder.field(fieldName, formatDate(date));
}

View File

@ -1,24 +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.watcher.throttle;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate;
/**
*
*/
public class AckThrottler implements Throttler {
@Override
public Result throttle(WatchExecutionContext ctx) {
if (ctx.watch().acked()) {
return Result.throttle("watch [" + ctx.watch().id() + "] was acked at [" + formatDate(ctx.watch().status().ackStatus().timestamp()) + "]");
}
return Result.NO;
}
}

View File

@ -1,49 +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.watcher.throttle;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.common.joda.time.PeriodType;
import org.elasticsearch.common.unit.TimeValue;
/**
*
*/
public class PeriodThrottler implements Throttler {
private final TimeValue period;
private final PeriodType periodType;
private final Clock clock;
public PeriodThrottler(Clock clock, TimeValue period) {
this(clock, period, PeriodType.minutes());
}
public PeriodThrottler(Clock clock, TimeValue period, PeriodType periodType) {
this.period = period;
this.periodType = periodType;
this.clock = clock;
}
public TimeValue interval() {
return period;
}
@Override
public Result throttle(WatchExecutionContext ctx) {
Watch.Status status = ctx.watch().status();
if (status.lastExecuted() != null) {
TimeValue timeElapsed = clock.timeElapsedSince(status.lastExecuted());
if (timeElapsed.getMillis() <= period.getMillis()) {
return Result.throttle("throttling interval is set to [" + period.format(periodType) +
"] but time elapsed since last execution is [" + timeElapsed.format(periodType) + "]");
}
}
return Result.NO;
}
}

View File

@ -6,10 +6,10 @@
package org.elasticsearch.watcher.transport.actions.ack;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.watcher.watch.WatchStatus;
import java.io.IOException;
@ -18,26 +18,26 @@ import java.io.IOException;
*/
public class AckWatchResponse extends ActionResponse {
private Watch.Status status;
private WatchStatus status;
public AckWatchResponse() {
}
public AckWatchResponse(@Nullable Watch.Status status) {
public AckWatchResponse(@Nullable WatchStatus status) {
this.status = status;
}
/**
* @return The ack state for the watch
*/
public Watch.Status getStatus() {
public WatchStatus getStatus() {
return status;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
status = in.readBoolean() ? Watch.Status.read(in) : null;
status = in.readBoolean() ? WatchStatus.read(in) : null;
}
@Override

View File

@ -15,11 +15,11 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import java.io.IOException;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* An execute watch request to execute a watch by id
@ -33,7 +33,7 @@ public class ExecuteWatchRequest extends MasterNodeOperationRequest<ExecuteWatch
private Map<String, Object> alternativeInput = null;
private BytesReference triggerSource = null;
private String triggerType = null;
private Set<String> simulatedActionIds = new HashSet<>();
private Map<String, ActionExecutionMode> actionModes = new HashMap<>();
ExecuteWatchRequest() {
}
@ -132,6 +132,7 @@ public class ExecuteWatchRequest extends MasterNodeOperationRequest<ExecuteWatch
}
/**
<<<<<<< HEAD
* @param triggerEvent the trigger event to use
* @throws IOException
*/
@ -149,25 +150,23 @@ public class ExecuteWatchRequest extends MasterNodeOperationRequest<ExecuteWatch
/**
* @return the trigger data to use
=======
* @return the execution modes for the actions. These modes determine the nature of the execution
* of the watch actions while the watch is executing.
>>>>>>> Action level throttling
*/
public Set<String> getSimulatedActionIds() {
return simulatedActionIds;
public Map<String, ActionExecutionMode> getActionModes() {
return actionModes;
}
/**
* @param simulatedActionIds a list of action ids to run in simulations for this execution
* Sets the action execution mode for the give action (identified by its id).
*
* @param actionId the action id.
* @param actionMode the execution mode of the action.
*/
public void addSimulatedActions(String ... simulatedActionIds) {
for (String simulatedActionId : simulatedActionIds) {
this.simulatedActionIds.add(simulatedActionId);
}
}
/**
* @return true if all actions should be simulated
*/
public boolean isSimulateAllActions() {
return this.simulatedActionIds.contains("_all");
public void setActionMode(String actionId, ActionExecutionMode actionMode) {
actionModes.put(actionId, actionMode);
}
@Override
@ -194,9 +193,10 @@ public class ExecuteWatchRequest extends MasterNodeOperationRequest<ExecuteWatch
}
triggerSource = in.readBytesReference();
triggerType = in.readString();
long simulatedIdCount = in.readLong();
for(long i = 0; i < simulatedIdCount; ++i) {
simulatedActionIds.add(in.readString());
long actionModesCount = in.readLong();
actionModes = new HashMap<>();
for (int i = 0; i < actionModesCount; i++) {
actionModes.put(in.readString(), ActionExecutionMode.resolve(in.readByte()));
}
}
@ -214,9 +214,10 @@ public class ExecuteWatchRequest extends MasterNodeOperationRequest<ExecuteWatch
}
out.writeBytesReference(triggerSource);
out.writeString(triggerType);
out.writeLong(simulatedActionIds.size());
for (String simulatedId : simulatedActionIds) {
out.writeString(simulatedId);
out.writeLong(actionModes.size());
for (Map.Entry<String, ActionExecutionMode> entry : actionModes.entrySet()) {
out.writeString(entry.getKey());
out.writeByte(entry.getValue().id());
}
}

View File

@ -11,6 +11,7 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import java.io.IOException;
import java.util.Map;
@ -85,10 +86,13 @@ public class ExecuteWatchRequestBuilder extends MasterNodeOperationRequestBuilde
}
/**
* @param simulatedActionIds a list of action ids to run in simulations for this execution
* Sets the mode in which the given action (identified by its id) will be handled.
*
* @param actionId The id of the action
* @param actionMode The mode in which the action will be handled in the execution
*/
public ExecuteWatchRequestBuilder addSimulatedActions(String ... simulatedActionIds) {
request.addSimulatedActions(simulatedActionIds);
public ExecuteWatchRequestBuilder setActionMode(String actionId, ActionExecutionMode actionMode) {
request.setActionMode(actionId, actionMode);
return this;
}

View File

@ -21,6 +21,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import org.elasticsearch.watcher.execution.ExecutionService;
import org.elasticsearch.watcher.execution.ManualExecutionContext;
import org.elasticsearch.watcher.history.WatchRecord;
@ -28,7 +29,6 @@ import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.support.xcontent.WatcherParams;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.transport.actions.WatcherTransportAction;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.trigger.TriggerService;
@ -37,6 +37,8 @@ import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStore;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
/**
* Performs the watch execution operation.
@ -83,14 +85,12 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
}
TriggerEvent triggerEvent = triggerService.parseTriggerEvent(watch.id(), watch.id() + "_manual_execution", request.getTriggerType(), request.getTriggerSource());
ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent));
ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent), executionService.defaultThrottlePeriod());
DateTime executionTime = clock.now(UTC);
ctxBuilder.executionTime(executionTime);
if (request.isSimulateAllActions()) {
ctxBuilder.simulateAllActions();
} else {
ctxBuilder.simulateActions(request.getSimulatedActionIds().toArray(new String[request.getSimulatedActionIds().size()]));
for (Map.Entry<String, ActionExecutionMode> entry : request.getActionModes().entrySet()) {
ctxBuilder.actionMode(entry.getKey(), entry.getValue());
}
if (request.getAlternativeInput() != null) {
ctxBuilder.withInput(new SimpleInput.Result(new Payload.Simple(request.getAlternativeInput())));
@ -98,9 +98,6 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
if (request.isIgnoreCondition()) {
ctxBuilder.withCondition(AlwaysCondition.Result.INSTANCE);
}
if (request.isIgnoreThrottle()) {
ctxBuilder.withThrottle(Throttler.Result.NO);
}
ctxBuilder.recordExecution(request.isRecordExecution());
WatchRecord record = executionService.execute(ctxBuilder.build());

View File

@ -9,12 +9,11 @@ import com.google.common.collect.ImmutableList;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.joda.time.PeriodType;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
@ -24,6 +23,7 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.actions.ActionRegistry;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.condition.ConditionRegistry;
@ -32,13 +32,9 @@ import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
import org.elasticsearch.watcher.input.ExecutableInput;
import org.elasticsearch.watcher.input.InputRegistry;
import org.elasticsearch.watcher.input.none.ExecutableNoneInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.WatcherDateUtils;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.support.secret.SensitiveXContentParser;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.throttle.WatchThrottler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.trigger.Trigger;
@ -46,51 +42,39 @@ import org.elasticsearch.watcher.trigger.TriggerEngine;
import org.elasticsearch.watcher.trigger.TriggerService;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.support.WatcherDateUtils.*;
public class Watch implements TriggerEngine.Job, ToXContent {
private final static TimeValue DEFAULT_THROTTLE_PERIOD = new TimeValue(5, TimeUnit.SECONDS);
private final static String DEFAULT_THROTTLE_PERIOD_SETTING = "watcher.throttle.period.default_period";
private final String id;
private final Trigger trigger;
private final ExecutableInput input;
private final ExecutableCondition condition;
private final @Nullable ExecutableTransform transform;
private final ExecutableActions actions;
private final Throttler throttler;
private final Status status;
private final TimeValue throttlePeriod;
private transient long version = Versions.NOT_SET;
@Nullable
private final Map<String, Object> metadata;
@Nullable
private final ExecutableTransform transform;
private final @Nullable TimeValue throttlePeriod;
private final @Nullable Map<String, Object> metadata;
private final WatchStatus status;
private final transient AtomicLong nonceCounter = new AtomicLong();
public Watch(String id, Clock clock, LicenseService licenseService, Trigger trigger, ExecutableInput input, ExecutableCondition condition, @Nullable ExecutableTransform transform,
ExecutableActions actions, @Nullable Map<String, Object> metadata, @Nullable TimeValue throttlePeriod, @Nullable Status status) {
private transient long version = Versions.NOT_SET;
public Watch(String id, Trigger trigger, ExecutableInput input, ExecutableCondition condition, @Nullable ExecutableTransform transform,
@Nullable TimeValue throttlePeriod, ExecutableActions actions, @Nullable Map<String, Object> metadata, WatchStatus status) {
this.id = id;
this.trigger = trigger;
this.input = input;
this.condition = condition;
this.transform = transform;
this.actions = actions;
this.status = status != null ? status : new Status();
this.throttlePeriod = throttlePeriod;
this.metadata = metadata;
this.transform = transform;
throttler = new WatchThrottler(clock, throttlePeriod, licenseService);
this.status = status;
}
public String id() {
@ -111,8 +95,8 @@ public class Watch implements TriggerEngine.Job, ToXContent {
return transform;
}
public Throttler throttler() {
return throttler;
public TimeValue throttlePeriod() {
return throttlePeriod;
}
public ExecutableActions actions() {
@ -123,11 +107,7 @@ public class Watch implements TriggerEngine.Job, ToXContent {
return metadata;
}
public TimeValue throttlePeriod() {
return throttlePeriod;
}
public Status status() {
public WatchStatus status() {
return status;
}
@ -143,12 +123,13 @@ public class Watch implements TriggerEngine.Job, ToXContent {
*
* @return {@code true} if the status of this watch changed, {@code false} otherwise.
*/
public boolean ack() {
return status.onAck(new DateTime(UTC));
public boolean ack(DateTime now, String... actions) {
return status.onAck(now, actions);
}
public boolean acked() {
return status.ackStatus.state == Status.AckStatus.State.ACKED;
public boolean acked(String actionId) {
ActionStatus actionStatus = status.actionStatus(actionId);
return actionStatus.ackStatus().state() == ActionStatus.AckStatus.State.ACKED;
}
public long nonce() {
@ -172,20 +153,24 @@ public class Watch implements TriggerEngine.Job, ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Parser.TRIGGER_FIELD.getPreferredName()).startObject().field(trigger.type(), trigger, params).endObject();
builder.field(Parser.INPUT_FIELD.getPreferredName()).startObject().field(input.type(), input, params).endObject();
builder.field(Parser.CONDITION_FIELD.getPreferredName()).startObject().field(condition.type(), condition, params).endObject();
builder.field(Field.TRIGGER.getPreferredName()).startObject().field(trigger.type(), trigger, params).endObject();
builder.field(Field.INPUT.getPreferredName()).startObject().field(input.type(), input, params).endObject();
builder.field(Field.CONDITION.getPreferredName()).startObject().field(condition.type(), condition, params).endObject();
if (transform != null) {
builder.field(Parser.TRANSFORM_FIELD.getPreferredName()).startObject().field(transform.type(), transform, params).endObject();
builder.field(Field.TRANSFORM.getPreferredName()).startObject().field(transform.type(), transform, params).endObject();
}
if (throttlePeriod != null) {
builder.field(Parser.THROTTLE_PERIOD_FIELD.getPreferredName(), throttlePeriod.getMillis());
if (builder.humanReadable()) {
builder.field(Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod.format(PeriodType.seconds()));
} else {
builder.field(Field.THROTTLE_PERIOD.getPreferredName(), throttlePeriod.getMillis());
}
builder.field(Parser.ACTIONS_FIELD.getPreferredName(), actions, params);
}
builder.field(Field.ACTIONS.getPreferredName(), actions, params);
if (metadata != null) {
builder.field(Parser.META_FIELD.getPreferredName(), metadata);
builder.field(Field.METADATA.getPreferredName(), metadata);
}
builder.field(Parser.STATUS_FIELD.getPreferredName(), status, params);
builder.field(Field.STATUS.getPreferredName(), status, params);
builder.endObject();
return builder;
}
@ -202,48 +187,33 @@ public class Watch implements TriggerEngine.Job, ToXContent {
public static class Parser extends AbstractComponent {
public static final ParseField TRIGGER_FIELD = new ParseField("trigger");
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");
public static final ParseField META_FIELD = new ParseField("metadata");
public static final ParseField STATUS_FIELD = new ParseField("status");
public static final ParseField THROTTLE_PERIOD_FIELD = new ParseField("throttle_period");
private final LicenseService licenseService;
private final ConditionRegistry conditionRegistry;
private final TriggerService triggerService;
private final TransformRegistry transformRegistry;
private final ActionRegistry actionRegistry;
private final InputRegistry inputRegistry;
private final Clock clock;
private final SecretService secretService;
private final ExecutableInput defaultInput;
private final ExecutableCondition defaultCondition;
private final ExecutableActions defaultActions;
private final TimeValue defaultThrottleTimePeriod;
private final Clock clock;
@Inject
public Parser(Settings settings, LicenseService licenseService, ConditionRegistry conditionRegistry, TriggerService triggerService,
public Parser(Settings settings, ConditionRegistry conditionRegistry, TriggerService triggerService,
TransformRegistry transformRegistry, ActionRegistry actionRegistry,
InputRegistry inputRegistry, Clock clock, SecretService secretService) {
InputRegistry inputRegistry, SecretService secretService, Clock clock) {
super(settings);
this.licenseService = licenseService;
this.conditionRegistry = conditionRegistry;
this.transformRegistry = transformRegistry;
this.triggerService = triggerService;
this.actionRegistry = actionRegistry;
this.inputRegistry = inputRegistry;
this.clock = clock;
this.secretService = secretService;
this.defaultInput = new ExecutableNoneInput(logger);
this.defaultCondition = new ExecutableAlwaysCondition(logger);
this.defaultActions = new ExecutableActions(ImmutableList.<ActionWrapper>of());
this.defaultThrottleTimePeriod = settings.getAsTime(DEFAULT_THROTTLE_PERIOD_SETTING, DEFAULT_THROTTLE_PERIOD);
this.clock = clock;
}
public Watch parse(String name, boolean includeStatus, BytesReference source) {
@ -294,451 +264,93 @@ public class Watch implements TriggerEngine.Job, ToXContent {
ExecutableCondition condition = defaultCondition;
ExecutableActions actions = defaultActions;
ExecutableTransform transform = null;
TimeValue throttlePeriod = null;
Map<String, Object> metatdata = null;
Status status = null;
TimeValue throttlePeriod = defaultThrottleTimePeriod;
WatchStatus status = null;
String currentFieldName = null;
XContentParser.Token token = parser.nextToken();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
if (token == null ) {
throw new ParseException("could not parse watch [{}]. null token", id);
} else if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == null || currentFieldName == null) {
throw new WatcherException("could not parse watch [{}], unexpected token [{}]", id, token);
} else if ((token.isValue() || token == XContentParser.Token.START_OBJECT) && currentFieldName !=null ) {
assert token != XContentParser.Token.START_ARRAY;
if (TRIGGER_FIELD.match(currentFieldName)) {
} else if (Field.TRIGGER.match(currentFieldName)) {
trigger = triggerService.parseTrigger(id, parser);
} else if (INPUT_FIELD.match(currentFieldName)) {
} else if (Field.INPUT.match(currentFieldName)) {
input = inputRegistry.parse(id, parser);
} else if (CONDITION_FIELD.match(currentFieldName)) {
} else if (Field.CONDITION.match(currentFieldName)) {
condition = conditionRegistry.parseExecutable(id, parser);
} else if (ACTIONS_FIELD.match(currentFieldName)) {
actions = actionRegistry.parseActions(id, parser);
} else if (TRANSFORM_FIELD.match(currentFieldName)) {
} else if (Field.TRANSFORM.match(currentFieldName)) {
transform = transformRegistry.parse(id, parser);
} else if (META_FIELD.match(currentFieldName)) {
metatdata = parser.map();
} else if (STATUS_FIELD.match(currentFieldName)) {
Status parsedStatus= Status.parse(id, parser);
if (includeStatus) {
status = parsedStatus;
}
} else if (THROTTLE_PERIOD_FIELD.match(currentFieldName)) {
} else if (Field.THROTTLE_PERIOD.match(currentFieldName)) {
if (token == XContentParser.Token.VALUE_STRING) {
throttlePeriod = TimeValue.parseTimeValue(parser.text(), null);
} else if (token == XContentParser.Token.VALUE_NUMBER) {
throttlePeriod = TimeValue.timeValueMillis(parser.longValue());
} else {
throw new WatcherException("could not parse watch [{}] throttle period. could not parse token [{}] as time value (must either be string or number)", id, token);
throw new ParseException("could not parse watch [{}]. expected field [{}] to either be string or numeric, but found [{}] instead", id, currentFieldName, token);
}
} else if (Field.ACTIONS.match(currentFieldName)) {
actions = actionRegistry.parseActions(id, parser);
} else if (Field.METADATA.match(currentFieldName)) {
metatdata = parser.map();
} else if (Field.STATUS.match(currentFieldName)) {
if (includeStatus) {
status = WatchStatus.parse(id, parser);
} else {
parser.skipChildren();
}
} else {
throw new WatcherException("could not parse watch [{}]. unexpected field [{}]", id, currentFieldName);
}
} else if (currentFieldName != null) {
throw new WatcherException("could not parse watch [{}]. unexpected token [{}] in field [{}]", id, token, currentFieldName);
throw new ParseException("could not parse watch [{}]. unexpected field [{}]", id, currentFieldName);
}
}
if (trigger == null) {
throw new WatcherException("could not parse watch [{}]. missing required field [{}]", id, TRIGGER_FIELD.getPreferredName());
}
return new Watch(id, clock, licenseService, trigger, input, condition, transform, actions, metatdata, throttlePeriod, status);
throw new WatcherException("could not parse watch [{}]. missing required field [{}]", id, Field.TRIGGER.getPreferredName());
}
if (status != null) {
// verify the status is valid (that every action indeed has a status)
for (ActionWrapper action : actions) {
if (status.actionStatus(action.id()) == null) {
throw new WatcherException("could not parse watch [{}]. watch status in invalid state. action [{}] status is missing", id, action.id());
}
public static class Status implements ToXContent, Streamable {
public static final ParseField LAST_CHECKED_FIELD = new ParseField("last_checked");
public static final ParseField LAST_MET_CONDITION_FIELD = new ParseField("last_met_condition");
public static final ParseField LAST_THROTTLED_FIELD = new ParseField("last_throttled");
public static final ParseField LAST_EXECUTED_FIELD = new ParseField("last_executed");
public static final ParseField ACK_FIELD = new ParseField("ack");
public static final ParseField STATE_FIELD = new ParseField("state");
public static final ParseField TIMESTAMP_FIELD = new ParseField("timestamp");
public static final ParseField REASON_FIELD = new ParseField("reason");
private transient long version;
private DateTime lastChecked;
private DateTime lastMetCondition;
private Throttle lastThrottle;
private DateTime lastExecuted;
private AckStatus ackStatus;
private volatile boolean dirty = false;
public Status() {
this(-1, null, null, null, null, new AckStatus());
}
public Status(Status other) {
this(other.version, other.lastChecked, other.lastMetCondition, other.lastExecuted, other.lastThrottle, other.ackStatus);
}
private Status(long version, DateTime lastChecked, DateTime lastMetCondition, DateTime lastExecuted, Throttle lastThrottle, AckStatus ackStatus) {
this.version = version;
this.lastChecked = lastChecked;
this.lastMetCondition = lastMetCondition;
this.lastExecuted = lastExecuted;
this.lastThrottle = lastThrottle;
this.ackStatus = ackStatus;
}
public long version() {
return version;
}
public void version(long version) {
this.version = version;
}
public boolean checked() {
return lastChecked != null;
}
public DateTime lastChecked() {
return lastChecked;
}
public boolean metCondition() {
return lastMetCondition != null;
}
public DateTime lastMetCondition() {
return lastMetCondition;
}
public boolean executed() {
return lastExecuted != null;
}
public DateTime lastExecuted() {
return lastExecuted;
}
public Throttle lastThrottle() {
return lastThrottle;
}
public AckStatus ackStatus() {
return ackStatus;
}
/**
* @param dirty if true this Watch.Status has been modified since it was read, if false we just wrote the updated watch
*/
public void dirty(boolean dirty) {
this.dirty = dirty;
}
/**
* @return does this Watch.Status needs to be persisted to the index
*/
public boolean dirty() {
return dirty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Status status = (Status) o;
if (version != status.version) return false;
if (!ackStatus.equals(status.ackStatus)) return false;
if (lastChecked != null ? !lastChecked.equals(status.lastChecked) : status.lastChecked != null)
return false;
if (lastExecuted != null ? !lastExecuted.equals(status.lastExecuted) : status.lastExecuted != null)
return false;
if (lastMetCondition != null ? !lastMetCondition.equals(status.lastMetCondition) : status.lastMetCondition != null)
return false;
if (lastThrottle != null ? !lastThrottle.equals(status.lastThrottle) : status.lastThrottle != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = (int) (version ^ (version >>> 32));
result = 31 * result + (lastChecked != null ? lastChecked.hashCode() : 0);
result = 31 * result + (lastMetCondition != null ? lastMetCondition.hashCode() : 0);
result = 31 * result + (lastThrottle != null ? lastThrottle.hashCode() : 0);
result = 31 * result + (lastExecuted != null ? lastExecuted.hashCode() : 0);
result = 31 * result + ackStatus.hashCode();
return result;
}
/**
* Called whenever an watch is checked, ie. the condition of the watch is evaluated to see if
* the watch should be executed.
*
* @param metCondition indicates whether the watch's condition was met.
*/
public void onCheck(boolean metCondition, DateTime timestamp) {
lastChecked = timestamp;
if (metCondition) {
lastMetCondition = timestamp;
dirty(true);
} else if (ackStatus.state == AckStatus.State.ACKED) {
// didn't meet condition now after it met it in the past - we need to reset the ack state
ackStatus = new AckStatus(AckStatus.State.AWAITS_EXECUTION, timestamp);
dirty(true);
}
}
/**
* Called whenever an watch run is throttled
*/
public void onThrottle(DateTime timestamp, String reason) {
lastThrottle = new Throttle(timestamp, reason);
dirty(true);
}
/**
* Notified this status that the watch was executed. If the current state is {@link Watch.Status.AckStatus.State#AWAITS_EXECUTION}, it will change to
* {@link Watch.Status.AckStatus.State#ACKABLE}.
* @return {@code true} if the state changed due to the execution {@code false} otherwise
*/
public boolean onExecution(DateTime timestamp) {
lastExecuted = timestamp;
if (ackStatus.state == AckStatus.State.AWAITS_EXECUTION) {
ackStatus = new AckStatus(AckStatus.State.ACKABLE, timestamp);
dirty(true);
return true;
}
return false;
}
/**
* Notifies this status that the watch was acked. If the current state is {@link Watch.Status.AckStatus.State#ACKABLE}, then we'll change it
* to {@link Watch.Status.AckStatus.State#ACKED} (when set to {@link Watch.Status.AckStatus.State#ACKED}, the {@link org.elasticsearch.watcher.throttle.AckThrottler} will lastThrottle the
* execution.
*
* @return {@code true} if the state of changed due to the ack, {@code false} otherwise.
*/
boolean onAck(DateTime timestamp) {
if (ackStatus.state == AckStatus.State.ACKABLE) {
ackStatus = new AckStatus(AckStatus.State.ACKED, timestamp);
dirty(true);
return true;
}
return false;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeLong(version);
writeOptionalDate(out, lastChecked);
writeOptionalDate(out, lastMetCondition);
writeOptionalDate(out, lastExecuted);
if (lastThrottle == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeDate(out, lastThrottle.timestamp);
out.writeString(lastThrottle.reason);
}
out.writeString(ackStatus.state.name());
writeDate(out, ackStatus.timestamp);
}
@Override
public void readFrom(StreamInput in) throws IOException {
version = in.readLong();
lastChecked = readOptionalDate(in, UTC);
lastMetCondition = readOptionalDate(in, UTC);
lastExecuted = readOptionalDate(in, UTC);
lastThrottle = in.readBoolean() ? new Throttle(readDate(in, UTC), in.readString()) : null;
ackStatus = new AckStatus(AckStatus.State.valueOf(in.readString()), readDate(in, UTC));
}
public static Status read(StreamInput in) throws IOException {
Watch.Status status = new Watch.Status();
status.readFrom(in);
return status;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (lastChecked != null) {
builder.field(LAST_CHECKED_FIELD.getPreferredName(), lastChecked);
}
if (lastMetCondition != null) {
builder.field(LAST_MET_CONDITION_FIELD.getPreferredName(), lastMetCondition);
}
if (lastExecuted != null) {
builder.field(LAST_EXECUTED_FIELD.getPreferredName(), lastExecuted);
}
builder.startObject(ACK_FIELD.getPreferredName())
.field(STATE_FIELD.getPreferredName(), ackStatus.state.name().toLowerCase(Locale.ROOT))
.field(TIMESTAMP_FIELD.getPreferredName(), ackStatus.timestamp)
.endObject();
if (lastThrottle != null) {
builder.startObject(LAST_THROTTLED_FIELD.getPreferredName())
.field(TIMESTAMP_FIELD.getPreferredName(), lastThrottle.timestamp)
.field(REASON_FIELD.getPreferredName(), lastThrottle.reason)
.endObject();
}
return builder.endObject();
}
public static Status parse(String id, XContentParser parser) throws IOException {
DateTime lastChecked = null;
DateTime lastMetCondition = null;
Throttle lastThrottle = null;
DateTime lastExecuted = null;
AckStatus ackStatus = null;
String currentFieldName = null;
XContentParser.Token token = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (LAST_CHECKED_FIELD.match(currentFieldName)) {
try {
lastChecked = WatcherDateUtils.parseDate(currentFieldName, parser, UTC);
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch [{}]. failed to parse date field [{}]", pe, id, currentFieldName);
}
} else if (LAST_MET_CONDITION_FIELD.match(currentFieldName)) {
try {
lastMetCondition = WatcherDateUtils.parseDate(currentFieldName, parser, UTC);
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch [{}]. failed to parse date field [{}]", pe, id, currentFieldName);
}
} else if (LAST_EXECUTED_FIELD.match(currentFieldName)) {
try {
lastExecuted = WatcherDateUtils.parseDate(currentFieldName, parser, UTC);
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch [{}]. failed to parse date field [{}]", pe, id, currentFieldName);
}
} else if (LAST_THROTTLED_FIELD.match(currentFieldName)) {
String context = currentFieldName;
if (token == XContentParser.Token.START_OBJECT) {
DateTime timestamp = null;
String reason = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (TIMESTAMP_FIELD.match(currentFieldName)) {
try {
timestamp = WatcherDateUtils.parseDate(currentFieldName, parser, UTC);
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch [{}]. failed to parse date field [{}.{}].", pe, id, context, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (REASON_FIELD.match(currentFieldName)) {
reason = parser.text();
} else {
throw new WatcherException("could not parse watch [{}]. unexpected string field [{}.{}]", id, context, currentFieldName);
}
} else {
throw new WatcherException("could not parse watch [{}]. unexpected token [{}] under [{}]", id, token, context);
// we need to create the initial statuses for the actions
ImmutableMap.Builder<String, ActionStatus> actionsStatuses = ImmutableMap.builder();
DateTime now = clock.now(UTC);
for (ActionWrapper action : actions) {
actionsStatuses.put(action.id(), new ActionStatus(now));
}
status = new WatchStatus(actionsStatuses.build());
}
lastThrottle = new Throttle(timestamp, reason);
} else {
throw new WatcherException("could not parse watch [{}]. unexpected token [{}] under [{}]", id, token, context);
}
} else if (ACK_FIELD.match(currentFieldName)) {
String context = currentFieldName;
if (token == XContentParser.Token.START_OBJECT) {
AckStatus.State state = null;
DateTime timestamp = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (TIMESTAMP_FIELD.match(currentFieldName)) {
try {
timestamp = WatcherDateUtils.parseDate(currentFieldName, parser, UTC);
} catch (WatcherDateUtils.ParseException pe) {
throw new WatcherException("could not parse watch [{}]. failed to parse date field [{}.{}]", pe, id, context, currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (STATE_FIELD.match(currentFieldName)) {
state = AckStatus.State.valueOf(parser.text().toUpperCase(Locale.ROOT));
} else {
throw new WatcherException("could not parse watch [{}]. unexpected string field [{}.{}]", id, context, currentFieldName);
}
}
}
ackStatus = new AckStatus(state, timestamp);
} else {
throw new WatcherException("could not parse watch [{}]. unexpected token [{}] under [{}] field", id, token, context);
}
return new Watch(id, trigger, input, condition, transform, throttlePeriod, actions, metatdata, status);
}
}
return new Status(-1, lastChecked, lastMetCondition, lastExecuted, lastThrottle, ackStatus);
public static class ParseException extends WatcherException {
public ParseException(String msg, Object... args) {
super(msg, args);
}
public static class AckStatus {
public enum State {
AWAITS_EXECUTION,
ACKABLE,
ACKED
}
private final State state;
private final DateTime timestamp;
public AckStatus() {
this(State.AWAITS_EXECUTION, new DateTime(UTC));
}
public AckStatus(State state, DateTime timestamp) {
this.state = state;
this.timestamp = timestamp;
}
public State state() {
return state;
}
public DateTime timestamp() {
return timestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AckStatus ackStatus = (AckStatus) o;
if (state != ackStatus.state) return false;
if (!timestamp.equals(ackStatus.timestamp)) return false;
return true;
}
@Override
public int hashCode() {
int result = state.hashCode();
result = 31 * result + timestamp.hashCode();
return result;
public ParseException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
public static class Throttle {
private final DateTime timestamp;
private final String reason;
public Throttle(DateTime timestamp, String reason) {
this.timestamp = timestamp;
this.reason = reason;
}
}
public interface Field {
ParseField TRIGGER = new ParseField("trigger");
ParseField INPUT = new ParseField("input");
ParseField CONDITION = new ParseField("condition");
ParseField ACTIONS = new ParseField("actions");
ParseField TRANSFORM = new ParseField("transform");
ParseField THROTTLE_PERIOD = new ParseField("throttle_period");
ParseField METADATA = new ParseField("metadata");
ParseField STATUS = new ParseField("status");
}
}

View File

@ -0,0 +1,282 @@
/*
* 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.watcher.watch;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.lang3.ArrayUtils;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.throttler.AckThrottler;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.watcher.support.WatcherDateUtils.*;
/**
*
*/
public class WatchStatus implements ToXContent, Streamable {
private transient long version;
private @Nullable DateTime lastChecked;
private @Nullable DateTime lastMetCondition;
private ImmutableMap<String, ActionStatus> actions;
private volatile boolean dirty = false;
// for serialization
private WatchStatus() {
}
public WatchStatus(ImmutableMap<String, ActionStatus> actions) {
this(-1, null, null, actions);
}
public WatchStatus(WatchStatus other) {
this(other.version, other.lastChecked, other.lastMetCondition, other.actions);
}
private WatchStatus(long version, DateTime lastChecked, DateTime lastMetCondition, ImmutableMap<String, ActionStatus> actions) {
this.version = version;
this.lastChecked = lastChecked;
this.lastMetCondition = lastMetCondition;
this.actions = actions;
}
public long version() {
return version;
}
public void version(long version) {
this.version = version;
}
public boolean checked() {
return lastChecked != null;
}
public DateTime lastChecked() {
return lastChecked;
}
public boolean metCondition() {
return lastMetCondition != null;
}
public DateTime lastMetCondition() {
return lastMetCondition;
}
public ActionStatus actionStatus(String actionId) {
return actions.get(actionId);
}
/**
* marks this status as non-dirty. this should only be done when the current state of the status is in sync with
* the persisted state.
*/
public void resetDirty() {
this.dirty = false;
}
/**
* @return does this Watch.Status needs to be persisted to the index
*/
public boolean dirty() {
return dirty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WatchStatus that = (WatchStatus) o;
if (version != that.version) return false;
if (lastChecked != null ? !lastChecked.equals(that.lastChecked) : that.lastChecked != null) return false;
if (lastMetCondition != null ? !lastMetCondition.equals(that.lastMetCondition) : that.lastMetCondition != null)
return false;
return !(actions != null ? !actions.equals(that.actions) : that.actions != null);
}
@Override
public int hashCode() {
int result = (int) (version ^ (version >>> 32));
result = 31 * result + (lastChecked != null ? lastChecked.hashCode() : 0);
result = 31 * result + (lastMetCondition != null ? lastMetCondition.hashCode() : 0);
result = 31 * result + (actions != null ? actions.hashCode() : 0);
return result;
}
/**
* Called whenever an watch is checked, ie. the condition of the watch is evaluated to see if
* the watch should be executed.
*
* @param metCondition indicates whether the watch's condition was met.
*/
public void onCheck(boolean metCondition, DateTime timestamp) {
lastChecked = timestamp;
if (metCondition) {
lastMetCondition = timestamp;
dirty = true;
} else {
for (ActionStatus status : actions.values()) {
status.resetAckStatus(timestamp);
}
}
}
public void onActionResult(String actionId, DateTime timestamp, Action.Result result) {
ActionStatus status = actions.get(actionId);
status.update(timestamp, result);
dirty = true;
}
/**
* Notifies this status that the givne actions were acked. If the current state of one of these actions is {@link org.elasticsearch.watcher.actions.ActionStatus.AckStatus.State#ACKABLE ACKABLE},
* then we'll it'll change to {@link org.elasticsearch.watcher.actions.ActionStatus.AckStatus.State#ACKED ACKED}
* (when set to {@link org.elasticsearch.watcher.actions.ActionStatus.AckStatus.State#ACKED ACKED}, the {@link AckThrottler}
* will throttle the execution of the action.
*
* @return {@code true} if the state of changed due to the ack, {@code false} otherwise.
*/
boolean onAck(DateTime timestamp, String... actionIds) {
boolean changed = false;
if (ArrayUtils.contains(actionIds, "_all")) {
for (ActionStatus status : actions.values()) {
changed |= status.onAck(timestamp);
}
dirty |= changed;
return changed;
}
for (String actionId : actionIds) {
ActionStatus status = actions.get(actionId);
if (status != null) {
changed |= status.onAck(timestamp);
}
}
dirty |= changed;
return changed;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeLong(version);
writeOptionalDate(out, lastChecked);
writeOptionalDate(out, lastMetCondition);
out.writeInt(actions.size());
for (Map.Entry<String, ActionStatus> entry : actions.entrySet()) {
out.writeString(entry.getKey());
ActionStatus.writeTo(entry.getValue(), out);
}
}
@Override
public void readFrom(StreamInput in) throws IOException {
version = in.readLong();
lastChecked = readOptionalDate(in, UTC);
lastMetCondition = readOptionalDate(in, UTC);
ImmutableMap.Builder<String, ActionStatus> builder = ImmutableMap.builder();
int count = in.readInt();
for (int i = 0; i < count; i++) {
builder.put(in.readString(), ActionStatus.readFrom(in));
}
actions = builder.build();
}
public static WatchStatus read(StreamInput in) throws IOException {
WatchStatus status = new WatchStatus();
status.readFrom(in);
return status;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (lastChecked != null) {
builder.field(Field.LAST_CHECKED.getPreferredName(), lastChecked);
}
if (lastMetCondition != null) {
builder.field(Field.LAST_MET_CONDITION.getPreferredName(), lastMetCondition);
}
if (actions != null) {
builder.startObject(Field.ACTIONS.getPreferredName());
for (Map.Entry<String, ActionStatus> entry : actions.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params);
}
builder.endObject();
}
return builder.endObject();
}
public static WatchStatus parse(String watchId, XContentParser parser) throws IOException {
DateTime lastChecked = null;
DateTime lastMetCondition = null;
ImmutableMap.Builder<String, ActionStatus> actions = 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 (Field.LAST_CHECKED.match(currentFieldName)) {
if (token.isValue()) {
lastChecked = parseDate(currentFieldName, parser, UTC);
} else {
throw new WatcherException("could not parse watch status for [{}]. expecting field [{}] to hold a date value, found [{}] instead", watchId, currentFieldName, token);
}
} else if (Field.LAST_MET_CONDITION.match(currentFieldName)) {
if (token.isValue()) {
lastMetCondition = parseDate(currentFieldName, parser, UTC);
} else {
throw new WatcherException("could not parse watch status for [{}]. expecting field [{}] to hold a date value, found [{}] instead", watchId, currentFieldName, token);
}
} else if (Field.ACTIONS.match(currentFieldName)) {
actions = ImmutableMap.builder();
if (token == XContentParser.Token.START_OBJECT) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
ActionStatus actionStatus = ActionStatus.parse(watchId, currentFieldName, parser);
actions.put(currentFieldName, actionStatus);
}
}
} else {
throw new WatcherException("could not parse watch status for [{}]. expecting field [{}] to be an object, found [{}] instead", watchId, currentFieldName, token);
}
}
}
return new WatchStatus(-1, lastChecked, lastMetCondition, actions.build());
}
interface Field {
ParseField LAST_CHECKED = new ParseField("last_checked");
ParseField LAST_MET_CONDITION = new ParseField("last_met_condition");
ParseField ACTIONS = new ParseField("actions");
}
}

View File

@ -156,7 +156,7 @@ public class WatchStore extends AbstractComponent {
IndexResponse response = client.index(createIndexRequest(watch.id(), source, watch.version()));
watch.status().version(response.getVersion());
watch.version(response.getVersion());
watch.status().dirty(false);
watch.status().resetDirty();
// Don't need to update the watches, since we are working on an instance from it.
}

View File

@ -33,7 +33,7 @@
"dynamic" : true
},
"throttle_period": {
"type": "string"
"type": "long"
},
"transform": {
"type" : "object",

View File

@ -1,136 +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.watcher.actions;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformFactory;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import java.io.IOException;
import java.util.Map;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
/**
*/
public class TransformMocks {
public static class ExecutableTransformMock extends ExecutableTransform {
private static final String TYPE = "mock";
public ExecutableTransformMock() {
super(new Transform() {
@Override
public String type() {
return TYPE;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
}, Loggers.getLogger(ExecutableTransformMock.class));
}
@Override
public Transform.Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
return new Result(TYPE, new Payload.Simple("_key", "_value"));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
public static class Result extends Transform.Result {
public Result(String type, Payload payload) {
super(type, payload);
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder;
}
}
}
public static class TransformRegistryMock extends TransformRegistry {
public TransformRegistryMock(final ExecutableTransform executable) {
super(ImmutableMap.<String, TransformFactory>of("_transform", new TransformFactory(Loggers.getLogger(TransformRegistryMock.class)) {
@Override
public String type() {
return executable.type();
}
@Override
public Transform parseTransform(String watchId, XContentParser parser) throws IOException {
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return null;
}
@Override
public Transform.Result parseResult(String watchId, XContentParser parser) throws IOException {
return null;
}
@Override
public ExecutableTransform createExecutable(Transform transform) {
return executable;
}
}));
}
public TransformRegistryMock(final Transform.Result result) {
super(ImmutableMap.<String, TransformFactory>of("_transform_type", new TransformFactory(Loggers.getLogger(TransformRegistryMock.class)) {
@Override
public String type() {
return result.type();
}
@Override
public Transform parseTransform(String watchId, XContentParser parser) throws IOException {
return null;
}
@Override
public ExecutableTransform createExecutable(Transform transform) {
return null;
}
@Override
public Transform.Result parseResult(String watchId, XContentParser parser) throws IOException {
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.FIELD_NAME));
assertThat(parser.currentName(), is("payload"));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
Map<String, Object> data = parser.map();
assertThat(data, equalTo(result.payload().data()));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return result;
}
}));
}
}
}

View File

@ -17,6 +17,8 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.email.service.*;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.execution.Wid;
@ -28,6 +30,7 @@ import org.elasticsearch.watcher.watch.Payload;
import org.junit.Test;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
@ -122,7 +125,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
when(engine.render(htmlBody, expectedModel)).thenReturn(htmlBody.getTemplate());
}
EmailAction.Result result = executable.execute("_id", ctx, payload);
Action.Result result = executable.execute("_id", ctx, payload);
assertThat(result, notNullValue());
assertThat(result, instanceOf(EmailAction.Result.Success.class));
@ -368,8 +371,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
Wid wid = new Wid(randomAsciiOfLength(3), randomLong(), DateTime.now(UTC));
String actionId = randomAsciiOfLength(5);
boolean success = randomBoolean();
boolean simulated = randomBoolean();
Action.Result.Status status = randomFrom(Action.Result.Status.values());
Email email = Email.builder().id("_id")
.from(new Email.Address("from@domain"))
.to(Email.AddressList.parse("to@domain"))
@ -379,38 +381,56 @@ public class EmailActionTests extends ElasticsearchTestCase {
.build();
XContentBuilder builder = jsonBuilder().startObject()
.field("success", success);
if (simulated) {
builder.field("simulated_email", email);
}
else if (success) {
.field("status", status.name().toLowerCase(Locale.ROOT));
switch (status) {
case SUCCESS:
builder.field("email", email);
builder.field("account", "_account");
} else {
builder.field("reason", "_reason");
break;
case FAILURE:
builder.field("reason", "failure_reason");
break;
case THROTTLED:
builder.field("reason", "throttle_reason");
break;
case SIMULATED:
builder.field("email", email);
break;
default:
throw new WatcherException("unsupported action result status [{}]", status.name().toLowerCase(Locale.ROOT));
}
builder.endObject();
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
EmailService service = mock(EmailService.class);
TemplateEngine engine = mock(TemplateEngine.class);
EmailAction.Result result = new EmailActionFactory(ImmutableSettings.EMPTY, service, engine)
Action.Result result = new EmailActionFactory(ImmutableSettings.EMPTY, service, engine)
.parseResult(wid, actionId, parser);
if (simulated) {
assertThat(result, instanceOf(EmailAction.Result.Simulated.class));
assertThat(((EmailAction.Result.Simulated) result).email(), equalTo(email));
} else {
assertThat(result.success(), is(success));
if (success) {
assertThat(result.status(), is(status));
switch (status) {
case SUCCESS:
assertThat(result, instanceOf(EmailAction.Result.Success.class));
assertThat(((EmailAction.Result.Success) result).email(), equalTo(email));
assertThat(((EmailAction.Result.Success) result).account(), is("_account"));
} else {
assertThat(result, instanceOf(EmailAction.Result.Failure.class));
assertThat(((EmailAction.Result.Failure) result).reason(), is("_reason"));
}
break;
case FAILURE:
assertThat(result, instanceOf(Action.Result.Failure.class));
assertThat(((Action.Result.Failure) result).reason(), equalTo("failure_reason"));
break;
case THROTTLED:
assertThat(result, instanceOf(Action.Result.Throttled.class));
assertThat(((Action.Result.Throttled) result).reason(), equalTo("throttle_reason"));
break;
case SIMULATED:
assertThat(result, instanceOf(EmailAction.Result.Simulated.class));
assertThat(((EmailAction.Result.Simulated) result).email(), equalTo(email));
break;
default:
throw new WatcherException("unsupported action result status [{}]", status.name().toLowerCase(Locale.ROOT));
}
}
@ -435,7 +455,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
EmailAction.Result result = new EmailActionFactory(ImmutableSettings.EMPTY, mock(EmailService.class), mock(TemplateEngine.class))
Action.Result result = new EmailActionFactory(ImmutableSettings.EMPTY, mock(EmailService.class), mock(TemplateEngine.class))
.parseResult(wid, actionId, parser);
assertThat(result, instanceOf(EmailAction.Result.Simulated.class));

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
@ -19,6 +20,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.Action.Result.Status;
import org.elasticsearch.watcher.actions.email.service.Authentication;
import org.elasticsearch.watcher.actions.email.service.Email;
import org.elasticsearch.watcher.actions.email.service.EmailService;
@ -37,6 +40,7 @@ import org.elasticsearch.watcher.watch.Watch;
import org.junit.Test;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
@ -72,14 +76,16 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
}
},
logger);
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), new ScheduleTriggerEvent(watch.id(), new DateTime(), new DateTime()));
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), new ScheduleTriggerEvent(watch.id(), new DateTime(), new DateTime()), TimeValue.timeValueSeconds(5));
Map<String, Object> payloadMap = new HashMap<>();
payloadMap.put("test", "foo");
IndexAction.Result.Executed result = (IndexAction.Result.Executed) executable.execute("_id", ctx, new Payload.Simple(payloadMap));
Action.Result result = executable.execute("_id", ctx, new Payload.Simple(payloadMap));
assertThat(result.success(), equalTo(true));
Map<String, Object> responseData = result.response().data();
assertThat(result.status(), equalTo(Status.SUCCESS));
assertThat(result, instanceOf(IndexAction.Result.Success.class));
IndexAction.Result.Success successResult = (IndexAction.Result.Success) result;
Map<String, Object> responseData = successResult.response().data();
assertThat(responseData.get("created"), equalTo((Object)Boolean.TRUE));
assertThat(responseData.get("version"), equalTo((Object) 1L));
assertThat(responseData.get("type").toString(), equalTo("test-type"));
@ -142,28 +148,30 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
@Test @Repeat(iterations = 30)
public void testParser_Result() throws Exception {
boolean success = randomBoolean();
boolean simulated = randomBoolean();
Status status = randomFrom(Status.SUCCESS, Status.SIMULATED, Status.FAILURE);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", success);
.field("status", status.name().toLowerCase(Locale.ROOT));
Payload.Simple source = null;
String index = randomAsciiOfLength(5);
String docType = randomAsciiOfLength(5);
if (simulated) {
switch (status) {
case SIMULATED:
source = new Payload.Simple(ImmutableMap.<String, Object>builder()
.put("data", new HashMap<String, Object>())
.put("timestamp", DateTime.now(UTC).toString())
.build());
builder.startObject(IndexAction.Field.SIMULATED_REQUEST.getPreferredName())
builder.startObject(IndexAction.Field.REQUEST.getPreferredName())
.field(IndexAction.Field.INDEX.getPreferredName(), index)
.field(IndexAction.Field.DOC_TYPE.getPreferredName(), docType)
.field(IndexAction.Field.SOURCE.getPreferredName(), source)
.endObject();
break;
} else if (success) {
case SUCCESS:
Map<String,Object> data = new HashMap<>();
data.put("created", true);
data.put("id", "0");
@ -171,8 +179,9 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
data.put("type", "test-type");
data.put("index", "test-index");
builder.field(IndexAction.Field.RESPONSE.getPreferredName(), data);
break;
} else {
case FAILURE:
builder.field("reason", "_reason");
}
@ -181,28 +190,34 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
IndexAction.Result result = new IndexActionFactory(ImmutableSettings.EMPTY, ClientProxy.of(client()))
Action.Result result = new IndexActionFactory(ImmutableSettings.EMPTY, ClientProxy.of(client()))
.parseResult(wid, actionId, parser);
if (simulated){
assertThat(result.status(), is(status));
switch (status) {
case SIMULATED:
assertThat(result, instanceOf(IndexAction.Result.Simulated.class));
assertThat(((IndexAction.Result.Simulated) result).source(), equalTo((Payload) source));
assertThat(((IndexAction.Result.Simulated) result).index(), equalTo(index));
assertThat(((IndexAction.Result.Simulated) result).docType(), equalTo(docType));
break;
} else {
assertThat(result.success(), is(success));
if (success) {
assertThat(result, instanceOf(IndexAction.Result.Executed.class));
Map<String, Object> responseData = ((IndexAction.Result.Executed) result).response().data();
case SUCCESS:
assertThat(result, instanceOf(IndexAction.Result.Success.class));
Map<String, Object> responseData = ((IndexAction.Result.Success) result).response().data();
assertThat(responseData.get("created"), equalTo((Object) Boolean.TRUE));
assertThat(responseData.get("version"), equalTo((Object) 1));
assertThat(responseData.get("type").toString(), equalTo("test-type"));
assertThat(responseData.get("index").toString(), equalTo("test-index"));
} else {
assertThat(result, instanceOf(IndexAction.Result.Failure.class));
assertThat(((IndexAction.Result.Failure) result).reason(), is("_reason"));
}
break;
default: // failure
assertThat(result.status(), is(Status.FAILURE));
assertThat(result, instanceOf(Action.Result.Failure.class));
assertThat(((Action.Result.Failure) result).reason(), is("_reason"));
}
}
@ -231,7 +246,7 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
Wid wid = new Wid(randomAsciiOfLength(4), randomLong(), DateTime.now());
String actionId = randomAsciiOfLength(5);
IndexAction.Result result = new IndexActionFactory(ImmutableSettings.EMPTY, ClientProxy.of(client()))
Action.Result result = new IndexActionFactory(ImmutableSettings.EMPTY, ClientProxy.of(client()))
.parseResult(wid, actionId, parser);
assertThat(result, instanceOf(IndexAction.Result.Simulated.class));

View File

@ -18,6 +18,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionException;
import org.elasticsearch.watcher.actions.email.service.Attachment;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
@ -31,6 +32,7 @@ import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
@ -84,11 +86,11 @@ public class LoggingActionTests extends ElasticsearchTestCase {
.time("_watch_id", now)
.buildMock();
LoggingAction.Result result = executable.execute("_id", ctx, new Payload.Simple());
Action.Result result = executable.execute("_id", ctx, new Payload.Simple());
verifyLogger(actionLogger, level, text);
assertThat(result, notNullValue());
assertThat(result.success(), is(true));
assertThat(result.status(), is(Action.Result.Status.SUCCESS));
assertThat(result, instanceOf(LoggingAction.Result.Success.class));
assertThat(((LoggingAction.Result.Success) result).loggedText(), is(text));
}
@ -157,15 +159,11 @@ public class LoggingActionTests extends ElasticsearchTestCase {
String text = randomAsciiOfLength(10);
Template template = Template.inline(text).build();
LoggingAction.Builder actionBuilder = loggingAction(template);
String category = null;
if (randomBoolean()) {
category = randomAsciiOfLength(10);
actionBuilder.setCategory(category);
actionBuilder.setCategory(randomAsciiOfLength(10));
}
LoggingLevel level = null;
if (randomBoolean()) {
level = randomFrom(LoggingLevel.values());
actionBuilder.setLevel(level);
actionBuilder.setLevel(randomFrom(LoggingLevel.values()));
}
LoggingAction action = actionBuilder.build();
@ -176,6 +174,8 @@ public class LoggingActionTests extends ElasticsearchTestCase {
ExecutableLoggingAction executable = parser.parseExecutable(randomAsciiOfLength(4), randomAsciiOfLength(5), xContentParser);
assertThat(executable, notNullValue());
assertThat(executable.action(), is(action));
assertThat(executable.action(), is(action));
assertThat(executable.action(), is(action));
}
@Test(expected = ActionException.class)
@ -203,7 +203,7 @@ public class LoggingActionTests extends ElasticsearchTestCase {
String text = randomAsciiOfLength(10);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", true)
.field("status", Action.Result.Status.SUCCESS.name().toLowerCase(Locale.ROOT))
.field("logged_text", text)
.endObject();
@ -211,9 +211,9 @@ public class LoggingActionTests extends ElasticsearchTestCase {
xContentParser.nextToken();
// will fail as there's no text
LoggingAction.Result result = parser.parseResult(wid, actionId, xContentParser);
Action.Result result = parser.parseResult(wid, actionId, xContentParser);
assertThat(result, Matchers.notNullValue());
assertThat(result.success(), is(true));
assertThat(result.status(), is(Action.Result.Status.SUCCESS));
assertThat(result, Matchers.instanceOf(LoggingAction.Result.Success.class));
assertThat(((LoggingAction.Result.Success) result).loggedText(), is(text));
}
@ -228,7 +228,7 @@ public class LoggingActionTests extends ElasticsearchTestCase {
String reason = randomAsciiOfLength(10);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", false)
.field("status", Action.Result.Status.FAILURE.name().toLowerCase(Locale.ROOT))
.field("reason", reason)
.endObject();
@ -236,11 +236,11 @@ public class LoggingActionTests extends ElasticsearchTestCase {
xContentParser.nextToken();
// will fail as there's no text
LoggingAction.Result result = parser.parseResult(wid, actionId, xContentParser);
Action.Result result = parser.parseResult(wid, actionId, xContentParser);
assertThat(result, Matchers.notNullValue());
assertThat(result.success(), is(false));
assertThat(result, Matchers.instanceOf(LoggingAction.Result.Failure.class));
assertThat(((LoggingAction.Result.Failure) result).reason(), is(reason));
assertThat(result.status(), is(Action.Result.Status.FAILURE));
assertThat(result, Matchers.instanceOf(Action.Result.Failure.class));
assertThat(((Action.Result.Failure) result).reason(), is(reason));
}
@Test @Repeat(iterations = 30)
@ -253,17 +253,17 @@ public class LoggingActionTests extends ElasticsearchTestCase {
String text = randomAsciiOfLength(10);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", true)
.field("simulated_logged_text", text)
.field("status", Action.Result.Status.SIMULATED.name().toLowerCase(Locale.ROOT))
.field("logged_text", text)
.endObject();
XContentParser xContentParser = JsonXContent.jsonXContent.createParser(builder.bytes());
xContentParser.nextToken();
// will fail as there's no text
LoggingAction.Result result = parser.parseResult(wid, actionId, xContentParser);
Action.Result result = parser.parseResult(wid, actionId, xContentParser);
assertThat(result, Matchers.notNullValue());
assertThat(result.success(), is(true));
assertThat(result.status(), is(Action.Result.Status.SIMULATED));
assertThat(result, Matchers.instanceOf(LoggingAction.Result.Simulated.class));
assertThat(((LoggingAction.Result.Simulated) result).loggedText(), is(text));
}
@ -290,15 +290,15 @@ public class LoggingActionTests extends ElasticsearchTestCase {
xContentParser.nextToken();
// will fail as there's no text
LoggingAction.Result result = actionParser.parseResult(wid, actionId, xContentParser);
Action.Result result = actionParser.parseResult(wid, actionId, xContentParser);
assertThat(result, Matchers.notNullValue());
assertThat(result.success(), is(true));
assertThat(result.status(), is(Action.Result.Status.SIMULATED));
assertThat(result, Matchers.instanceOf(LoggingAction.Result.Simulated.class));
assertThat(((LoggingAction.Result.Simulated) result).loggedText(), is(text));
}
@Test(expected = ActionException.class)
public void testParser_Result_MissingSuccessField() throws Exception {
public void testParser_Result_MissingStatusField() throws Exception {
Settings settings = ImmutableSettings.EMPTY;
LoggingActionFactory parser = new LoggingActionFactory(settings, engine);
@ -327,7 +327,7 @@ public class LoggingActionTests extends ElasticsearchTestCase {
String text = randomAsciiOfLength(10);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", false);
.field("status", Action.Result.Status.FAILURE);
if (randomBoolean()) {
builder.field("logged_text", text);
}

View File

@ -0,0 +1,76 @@
/*
* 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.watcher.actions.throttler;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.junit.Test;
import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate;
import static org.elasticsearch.watcher.test.WatcherTestUtils.EMPTY_PAYLOAD;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class AckThrottlerTests extends ElasticsearchTestCase {
@Test
public void testWhenAcked() throws Exception {
DateTime timestamp = new DateTime();
WatchExecutionContext ctx = mockExecutionContext("_watch", EMPTY_PAYLOAD);
Watch watch = ctx.watch();
ActionStatus actionStatus = mock(ActionStatus.class);
when(actionStatus.ackStatus()).thenReturn(new ActionStatus.AckStatus(timestamp, ActionStatus.AckStatus.State.ACKED));
WatchStatus watchStatus = mock(WatchStatus.class);
when(watchStatus.actionStatus("_action")).thenReturn(actionStatus);
when(watch.status()).thenReturn(watchStatus);
AckThrottler throttler = new AckThrottler();
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result.throttle(), is(true));
assertThat(result.reason(), is("action [_action] was acked at [" + formatDate(timestamp) + "]"));
}
@Test
public void testThrottle_When_AwaitsSuccessfulExecution() throws Exception {
DateTime timestamp = new DateTime();
WatchExecutionContext ctx = mockExecutionContext("_watch", EMPTY_PAYLOAD);
Watch watch = ctx.watch();
ActionStatus actionStatus = mock(ActionStatus.class);
when(actionStatus.ackStatus()).thenReturn(new ActionStatus.AckStatus(timestamp, ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
WatchStatus watchStatus = mock(WatchStatus.class);
when(watchStatus.actionStatus("_action")).thenReturn(actionStatus);
when(watch.status()).thenReturn(watchStatus);
AckThrottler throttler = new AckThrottler();
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result.throttle(), is(false));
assertThat(result.reason(), nullValue());
}
@Test
public void testThrottle_When_Ackable() throws Exception {
DateTime timestamp = new DateTime();
WatchExecutionContext ctx = mockExecutionContext("_watch", EMPTY_PAYLOAD);
Watch watch = ctx.watch();
ActionStatus actionStatus = mock(ActionStatus.class);
when(actionStatus.ackStatus()).thenReturn(new ActionStatus.AckStatus(timestamp, ActionStatus.AckStatus.State.ACKABLE));
WatchStatus watchStatus = mock(WatchStatus.class);
when(watchStatus.actionStatus("_action")).thenReturn(actionStatus);
when(watch.status()).thenReturn(watchStatus);
AckThrottler throttler = new AckThrottler();
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result.throttle(), is(false));
assertThat(result.reason(), nullValue());
}
}

View File

@ -3,16 +3,17 @@
* 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.watcher.throttle;
package org.elasticsearch.watcher.actions.throttler;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.joda.time.PeriodType;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.junit.Test;
import static org.elasticsearch.watcher.test.WatcherTestUtils.EMPTY_PAYLOAD;
@ -28,17 +29,19 @@ import static org.mockito.Mockito.when;
public class PeriodThrottlerTests extends ElasticsearchTestCase {
@Test @Repeat(iterations = 10)
public void testBelowPeriod() throws Exception {
public void testBelowPeriod_Successful() throws Exception {
PeriodType periodType = randomFrom(PeriodType.millis(), PeriodType.seconds(), PeriodType.minutes());
TimeValue period = TimeValue.timeValueSeconds(randomIntBetween(2, 5));
PeriodThrottler throttler = new PeriodThrottler(SystemClock.INSTANCE, period, periodType);
WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD);
Watch.Status status = mock(Watch.Status.class);
ActionStatus actionStatus = mock(ActionStatus.class);
when(actionStatus.lastSuccessfulExecution()).thenReturn(ActionStatus.Execution.successful(new DateTime().minusSeconds((int) period.seconds() - 1)));
WatchStatus status = mock(WatchStatus.class);
when(status.actionStatus("_action")).thenReturn(actionStatus);
when(ctx.watch().status()).thenReturn(status);
when(status.lastExecuted()).thenReturn(new DateTime().minusSeconds((int) period.seconds() - 1));
Throttler.Result result = throttler.throttle(ctx);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result.throttle(), is(true));
assertThat(result.reason(), notNullValue());
@ -52,11 +55,13 @@ public class PeriodThrottlerTests extends ElasticsearchTestCase {
PeriodThrottler throttler = new PeriodThrottler(SystemClock.INSTANCE, period, periodType);
WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD);
Watch.Status status = mock(Watch.Status.class);
ActionStatus actionStatus = mock(ActionStatus.class);
when(actionStatus.lastSuccessfulExecution()).thenReturn(ActionStatus.Execution.successful(new DateTime().minusSeconds((int) period.seconds() + 1)));
WatchStatus status = mock(WatchStatus.class);
when(status.actionStatus("_action")).thenReturn(actionStatus);
when(ctx.watch().status()).thenReturn(status);
when(status.lastExecuted()).thenReturn(new DateTime().minusSeconds((int) period.seconds() + 1));
Throttler.Result result = throttler.throttle(ctx);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result.throttle(), is(false));
assertThat(result.reason(), nullValue());

View File

@ -3,16 +3,14 @@
* 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.watcher.throttle;
package org.elasticsearch.watcher.actions.throttler;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.license.LicenseService;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -26,13 +24,13 @@ public class WatchThrottlerTests extends ElasticsearchTestCase {
PeriodThrottler periodThrottler = mock(PeriodThrottler.class);
AckThrottler ackThrottler = mock(AckThrottler.class);
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
when(periodThrottler.throttle(ctx)).thenReturn(Throttler.Result.NO);
when(periodThrottler.throttle("_action", ctx)).thenReturn(Throttler.Result.NO);
Throttler.Result expectedResult = Throttler.Result.throttle("_reason");
when(ackThrottler.throttle(ctx)).thenReturn(expectedResult);
when(ackThrottler.throttle("_action", ctx)).thenReturn(expectedResult);
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
WatchThrottler throttler = new WatchThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle(ctx);
ActionThrottler throttler = new ActionThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result, is(expectedResult));
}
@ -43,12 +41,12 @@ public class WatchThrottlerTests extends ElasticsearchTestCase {
AckThrottler ackThrottler = mock(AckThrottler.class);
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Throttler.Result expectedResult = Throttler.Result.throttle("_reason");
when(periodThrottler.throttle(ctx)).thenReturn(expectedResult);
when(ackThrottler.throttle(ctx)).thenReturn(Throttler.Result.NO);
when(periodThrottler.throttle("_action", ctx)).thenReturn(expectedResult);
when(ackThrottler.throttle("_action", ctx)).thenReturn(Throttler.Result.NO);
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
WatchThrottler throttler = new WatchThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle(ctx);
ActionThrottler throttler = new ActionThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result, is(expectedResult));
}
@ -59,13 +57,13 @@ public class WatchThrottlerTests extends ElasticsearchTestCase {
AckThrottler ackThrottler = mock(AckThrottler.class);
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Throttler.Result periodResult = Throttler.Result.throttle("_reason_period");
when(periodThrottler.throttle(ctx)).thenReturn(periodResult);
when(periodThrottler.throttle("_action", ctx)).thenReturn(periodResult);
Throttler.Result ackResult = Throttler.Result.throttle("_reason_ack");
when(ackThrottler.throttle(ctx)).thenReturn(ackResult);
when(ackThrottler.throttle("_action", ctx)).thenReturn(ackResult);
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
WatchThrottler throttler = new WatchThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle(ctx);
ActionThrottler throttler = new ActionThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
// we always check the period first... so the result will come for the period throttler
assertThat(result, is(periodResult));
@ -76,12 +74,12 @@ public class WatchThrottlerTests extends ElasticsearchTestCase {
PeriodThrottler periodThrottler = mock(PeriodThrottler.class);
AckThrottler ackThrottler = mock(AckThrottler.class);
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
when(periodThrottler.throttle(ctx)).thenReturn(Throttler.Result.NO);
when(ackThrottler.throttle(ctx)).thenReturn(Throttler.Result.NO);
when(periodThrottler.throttle("_action", ctx)).thenReturn(Throttler.Result.NO);
when(ackThrottler.throttle("_action", ctx)).thenReturn(Throttler.Result.NO);
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
WatchThrottler throttler = new WatchThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle(ctx);
ActionThrottler throttler = new ActionThrottler(periodThrottler, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result, is(Throttler.Result.NO));
}
@ -91,11 +89,11 @@ public class WatchThrottlerTests extends ElasticsearchTestCase {
AckThrottler ackThrottler = mock(AckThrottler.class);
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Throttler.Result ackResult = mock(Throttler.Result.class);
when(ackThrottler.throttle(ctx)).thenReturn(ackResult);
when(ackThrottler.throttle("_action", ctx)).thenReturn(ackResult);
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
WatchThrottler throttler = new WatchThrottler(null, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle(ctx);
ActionThrottler throttler = new ActionThrottler(null, ackThrottler, licenseService);
Throttler.Result result = throttler.throttle("_action", ctx);
assertThat(result, notNullValue());
assertThat(result, sameInstance(ackResult));
}

View File

@ -18,6 +18,10 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.Action.Result.Status;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.email.service.*;
import org.elasticsearch.watcher.execution.TriggeredExecutionContext;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
@ -44,17 +48,18 @@ import org.junit.Test;
import javax.mail.internet.AddressException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
@ -66,7 +71,6 @@ public class WebhookActionTests extends ElasticsearchTestCase {
private ThreadPool tp = null;
private ScriptServiceProxy scriptService;
private SecretService secretService;
private TemplateEngine templateEngine;
private HttpAuthRegistry authRegistry;
private Template testBody;
@ -82,7 +86,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
Settings settings = ImmutableSettings.EMPTY;
scriptService = WatcherTestUtils.getScriptServiceProxy(tp);
templateEngine = new XMustacheTemplateEngine(settings, scriptService);
secretService = mock(SecretService.class);
SecretService secretService = mock(SecretService.class);
testBody = Template.inline(TEST_BODY_STRING).build();
testPath = Template.inline(TEST_PATH_STRING).build();
authRegistry = new HttpAuthRegistry(ImmutableMap.of("basic", (HttpAuthFactory) new BasicAuthFactory(secretService)));
@ -96,7 +100,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
@Test @Repeat(iterations = 30)
public void testExecute() throws Exception {
ClientProxy client = mock(ClientProxy.class);
ExecuteScenario scenario = randomFrom(ExecuteScenario.values());
ExecuteScenario scenario = randomFrom(ExecuteScenario.Success, ExecuteScenario.ErrorCode);
HttpClient httpClient = scenario.client();
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD);
@ -109,10 +113,10 @@ public class WebhookActionTests extends ElasticsearchTestCase {
ExecutableWebhookAction executable = new ExecutableWebhookAction(action, logger, httpClient, templateEngine);
Watch watch = createWatch("test_watch", client, account);
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), new ScheduleTriggerEvent(watch.id(), new DateTime(), new DateTime()));
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), new ScheduleTriggerEvent(watch.id(), new DateTime(), new DateTime()), timeValueSeconds(5));
WebhookAction.Result actionResult = executable.execute("_id", ctx, new Payload.Simple());
scenario.assertResult(actionResult);
Action.Result actionResult = executable.execute("_id", ctx, Payload.EMPTY);
scenario.assertResult(httpClient, actionResult);
}
private HttpRequestTemplate getHttpRequestTemplate(HttpMethod method, String host, int port, Template path, Template body, Map<String, Template> params) {
@ -143,7 +147,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentBuilder builder = jsonBuilder();
request.toXContent(builder, Attachment.XContent.EMPTY_PARAMS);
WebhookActionFactory actionParser = getParser(ExecuteScenario.Success.client());
WebhookActionFactory actionParser = webhookFactory(ExecuteScenario.Success.client());
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
@ -170,7 +174,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentBuilder builder = jsonBuilder();
executable.toXContent(builder, ToXContent.EMPTY_PARAMS);
WebhookActionFactory actionParser = getParser(ExecuteScenario.Success.client());
WebhookActionFactory actionParser = webhookFactory(ExecuteScenario.Success.client());
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
@ -197,7 +201,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentBuilder builder = jsonBuilder();
action.toXContent(builder, ToXContent.EMPTY_PARAMS);
WebhookActionFactory actionParser = getParser(ExecuteScenario.Success.client());
WebhookActionFactory actionParser = webhookFactory(ExecuteScenario.Success.client());
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
@ -219,7 +223,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookActionFactory actionParser = getParser(ExecuteScenario.Success.client());
WebhookActionFactory actionParser = webhookFactory(ExecuteScenario.Success.client());
//This should fail since we are not supplying a url
actionParser.parseExecutable("_watch", randomAsciiOfLength(5), parser);
fail("expected a WebhookActionException since we only provided either a host or a port but not both");
@ -230,7 +234,6 @@ public class WebhookActionTests extends ElasticsearchTestCase {
String body = "_body";
String host = "test.host";
String path = "/_url";
String reason = "_reason";
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD);
Wid wid = new Wid("_watch", randomLong(), DateTime.now());
@ -244,42 +247,83 @@ public class WebhookActionTests extends ElasticsearchTestCase {
HttpResponse response = new HttpResponse(randomIntBetween(200, 599), randomAsciiOfLength(10).getBytes(UTF8));
boolean error = randomBoolean();
Status status = randomFrom(Status.values());
boolean responseError = status == Status.FAILURE && randomBoolean();
boolean success = !error && response.status() < 400;
HttpClient client = status == Status.SUCCESS ? ExecuteScenario.Success.client() :
responseError ? ExecuteScenario.ErrorCode.client() :
status == Status.FAILURE ? ExecuteScenario.Error.client() : ExecuteScenario.NoExecute.client();
HttpClient client = ExecuteScenario.Success.client();
WebhookActionFactory actionParser = getParser(client);
WebhookActionFactory actionParser = webhookFactory(client);
XContentBuilder builder = jsonBuilder()
.startObject()
.field(WebhookAction.Field.SUCCESS.getPreferredName(), success);
if (!error) {
.field(WebhookAction.Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
switch (status) {
case SUCCESS:
builder.field(WebhookAction.Field.REQUEST.getPreferredName(), request);
builder.field(WebhookAction.Field.RESPONSE.getPreferredName(), response);
break;
case SIMULATED:
builder.field(WebhookAction.Field.REQUEST.getPreferredName(), request);
break;
case FAILURE:
if (responseError) {
builder.field(WebhookAction.Field.REASON.getPreferredName(), "status_code_failure_reason");
builder.field(WebhookAction.Field.REQUEST.getPreferredName(), request);
builder.field(WebhookAction.Field.RESPONSE.getPreferredName(), response);
} else {
builder.field(WebhookAction.Field.REASON.getPreferredName(), reason);
builder.field(WebhookAction.Field.REASON.getPreferredName(), "failure_reason");
}
break;
case THROTTLED:
builder.field(WebhookAction.Field.REASON.getPreferredName(), "throttle_reason");
break;
default:
throw new WatcherException("unsupported action result status [{}]", status.name());
}
builder.endObject();
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookAction.Result result = actionParser.parseResult(wid, actionId, parser);
Action.Result result = actionParser.parseResult(wid, actionId, parser);
assertThat(result.success(), equalTo(success));
if (!error) {
assertThat(result, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedResult = (WebhookAction.Result.Executed) result;
assertThat(executedResult.request(), equalTo(request));
assertThat(executedResult.response(), equalTo(response));
assertThat(result.status(), is(status));
switch (status) {
case SUCCESS:
assertThat(result, instanceOf(WebhookAction.Result.Success.class));
WebhookAction.Result.Success success = (WebhookAction.Result.Success) result;
assertThat(success.request(), equalTo(request));
assertThat(success.response(), equalTo(response));
break;
case SIMULATED:
assertThat(result, instanceOf(WebhookAction.Result.Simulated.class));
WebhookAction.Result.Simulated simulated = (WebhookAction.Result.Simulated) result;
assertThat(simulated.request(), equalTo(request));
break;
case FAILURE:
if (responseError) {
assertThat(result, instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure responseFailure = (WebhookAction.Result.Failure) result;
assertThat(responseFailure.reason(), is("status_code_failure_reason"));
assertThat(responseFailure.request(), equalTo(request));
assertThat(responseFailure.response(), equalTo(response));
} else {
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure failedResult = (WebhookAction.Result.Failure) result;
assertThat(failedResult.reason(), equalTo(reason));
assertThat(result, instanceOf(Action.Result.Failure.class));
Action.Result.Failure failure = (Action.Result.Failure) result;
assertThat(failure.reason(), is("failure_reason"));
}
break;
case THROTTLED:
assertThat(result, instanceOf(Action.Result.Throttled.class));
Action.Result.Throttled throttled = (Action.Result.Throttled) result;
assertThat(throttled.reason(), is("throttle_reason"));
}
}
@Test @Repeat(iterations = 5)
@ -300,17 +344,17 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentBuilder builder = jsonBuilder()
.startObject()
.field(WebhookAction.Field.SUCCESS.getPreferredName(), true)
.field(WebhookAction.Field.SIMULATED_REQUEST.getPreferredName(), request)
.field(Action.Field.STATUS.getPreferredName(), Status.SIMULATED.name().toLowerCase(Locale.ROOT))
.field(WebhookAction.Field.REQUEST.getPreferredName(), request)
.endObject();
HttpClient client = ExecuteScenario.Success.client();
WebhookActionFactory actionParser = getParser(client);
WebhookActionFactory actionParser = webhookFactory(client);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookAction.Result result = actionParser.parseResult(wid, actionId, parser);
Action.Result result = actionParser.parseResult(wid, actionId, parser);
assertThat(result, instanceOf(WebhookAction.Result.Simulated.class));
assertThat(((WebhookAction.Result.Simulated) result).request(), equalTo(request));
}
@ -341,7 +385,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
WebhookAction.Result result = getParser(ExecuteScenario.Success.client())
Action.Result result = webhookFactory(ExecuteScenario.Success.client())
.parseResult(wid, actionId, parser);
assertThat(result, instanceOf(WebhookAction.Result.Simulated.class));
@ -349,7 +393,7 @@ public class WebhookActionTests extends ElasticsearchTestCase {
}
private WebhookActionFactory getParser(HttpClient client) {
private WebhookActionFactory webhookFactory(HttpClient client) {
return new WebhookActionFactory(ImmutableSettings.EMPTY, client, new HttpRequest.Parser(authRegistry),
new HttpRequestTemplate.Parser(authRegistry), templateEngine);
}
@ -379,14 +423,14 @@ public class WebhookActionTests extends ElasticsearchTestCase {
DateTime time = new DateTime(UTC);
Watch watch = createWatch(watchId, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, time, new ScheduleTriggerEvent(watchId, time, time));
WebhookAction.Result result = webhookAction.doExecute(actionId, ctx, Payload.EMPTY);
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, time, new ScheduleTriggerEvent(watchId, time, time), timeValueSeconds(5));
Action.Result result = webhookAction.execute(actionId, ctx, Payload.EMPTY);
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executed = (WebhookAction.Result.Executed) result;
assertThat(executed.request().body(), equalTo(watchId));
assertThat(executed.request().path(), equalTo(time.toString()));
assertThat(executed.request().params().get("foo"), equalTo(time.toString()));
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Success.class));
WebhookAction.Result.Success success = (WebhookAction.Result.Success) result;
assertThat(success.request().body(), equalTo(watchId));
assertThat(success.request().path(), equalTo(time.toString()));
assertThat(success.request().params().get("foo"), equalTo(time.toString()));
}
@ -395,18 +439,18 @@ public class WebhookActionTests extends ElasticsearchTestCase {
HttpClient httpClient = ExecuteScenario.Success.client();
HttpMethod method = HttpMethod.POST;
Template path = Template.inline("/test_{{ctx.watch_id}}").build();
Template path = Template.defaultType("/test_{{ctx.watch_id}}").build();
String host = "test.host";
HttpRequestTemplate requestTemplate = getHttpRequestTemplate(method, host, TEST_PORT, path, testBody, null);
WebhookAction action = new WebhookAction(requestTemplate);
ExecutableWebhookAction webhookAction = new ExecutableWebhookAction(action, logger, httpClient, templateEngine);
ExecutableWebhookAction executable = new ExecutableWebhookAction(action, logger, httpClient, templateEngine);
String watchId = "test_url_encode" + randomAsciiOfLength(10);
Watch watch = createWatch(watchId, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(UTC), new ScheduleTriggerEvent(watchId, new DateTime(UTC), new DateTime(UTC)));
WebhookAction.Result result = webhookAction.execute("_id", ctx, new Payload.Simple());
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Executed.class));
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(UTC), new ScheduleTriggerEvent(watchId, new DateTime(UTC), new DateTime(UTC)), timeValueSeconds(5));
Action.Result result = executable.execute("_id", ctx, new Payload.Simple());
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Success.class));
}
private Watch createWatch(String watchId, ClientProxy client, final String account) throws AddressException, IOException {
@ -439,10 +483,10 @@ public class WebhookActionTests extends ElasticsearchTestCase {
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult.success(), is(false));
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedActionResult = (WebhookAction.Result.Executed) actionResult;
public void assertResult(HttpClient client, Action.Result actionResult) throws Exception {
assertThat(actionResult.status(), is(Status.FAILURE));
assertThat(actionResult, instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure executedActionResult = (WebhookAction.Result.Failure) actionResult;
assertThat(executedActionResult.response().status(), greaterThanOrEqualTo(400));
assertThat(executedActionResult.response().status(), lessThanOrEqualTo(599));
assertThat(executedActionResult.request().body(), equalTo(TEST_BODY_STRING));
@ -460,10 +504,10 @@ public class WebhookActionTests extends ElasticsearchTestCase {
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
public void assertResult(HttpClient client, Action.Result actionResult) throws Exception {
assertThat(actionResult, instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure failResult = (WebhookAction.Result.Failure) actionResult;
assertThat(failResult.success(), is(false));
assertThat(failResult.status(), is(Status.FAILURE));
}
},
@ -477,20 +521,32 @@ public class WebhookActionTests extends ElasticsearchTestCase {
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedActionResult = (WebhookAction.Result.Executed) actionResult;
public void assertResult(HttpClient client, Action.Result actionResult) throws Exception {
assertThat(actionResult.status(), is(Status.SUCCESS));
assertThat(actionResult, instanceOf(WebhookAction.Result.Success.class));
WebhookAction.Result.Success executedActionResult = (WebhookAction.Result.Success) actionResult;
assertThat(executedActionResult.response().status(), greaterThanOrEqualTo(200));
assertThat(executedActionResult.response().status(), lessThanOrEqualTo(399));
assertThat(executedActionResult.request().body(), equalTo(TEST_BODY_STRING));
assertThat(executedActionResult.request().path(), equalTo(TEST_PATH_STRING));
}
},
NoExecute() {
@Override
public HttpClient client() throws IOException{
return mock(HttpClient.class);
}
@Override
public void assertResult(HttpClient client, Action.Result actionResult) throws Exception {
verify(client, never()).execute(any(HttpRequest.class));
}
};
public abstract HttpClient client() throws IOException;
public abstract void assertResult(WebhookAction.Result result);
public abstract void assertResult(HttpClient client, Action.Result result) throws Exception ;
}
}

View File

@ -11,13 +11,8 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.condition.ConditionException;
import org.elasticsearch.watcher.condition.ConditionFactory;
import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
import org.elasticsearch.watcher.condition.always.AlwaysConditionException;
import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory;
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;

View File

@ -14,12 +14,6 @@ import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.condition.ConditionFactory;
import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
import org.elasticsearch.watcher.condition.always.AlwaysConditionException;
import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory;
import org.elasticsearch.watcher.condition.never.ExecutableNeverCondition;
import org.elasticsearch.watcher.condition.never.NeverCondition;
import org.elasticsearch.watcher.condition.never.NeverConditionException;
import org.elasticsearch.watcher.condition.never.NeverConditionFactory;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;

View File

@ -5,12 +5,11 @@
*/
package org.elasticsearch.watcher.execution;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.actions.*;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
@ -20,7 +19,8 @@ import org.elasticsearch.watcher.input.ExecutableInput;
import org.elasticsearch.watcher.input.Input;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.actions.throttler.ActionThrottler;
import org.elasticsearch.watcher.actions.throttler.Throttler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
@ -31,9 +31,9 @@ import org.junit.Test;
import java.util.Arrays;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.*;
/**
@ -64,140 +64,155 @@ public class ExecutionServiceTests extends ElasticsearchTestCase {
@Test
public void testExecute() throws Exception {
Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
Throttler.Result throttleResult = Throttler.Result.NO;
Transform.Result transformResult = mock(Transform.Result.class);
when(transformResult.payload()).thenReturn(payload);
Action.Result actionResult = mock(Action.Result.class);
when(actionResult.type()).thenReturn("_action_type");
ActionWrapper.Result watchActionResult = new ActionWrapper.Result("_id", null, actionResult);
DateTime now = DateTime.now(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 = AlwaysCondition.Result.INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
Throttler throttler = mock(Throttler.class);
when(throttler.throttle(any(WatchExecutionContext.class))).thenReturn(throttleResult);
ExecutableTransform transform = mock(ExecutableTransform.class);
when(transform.execute(any(WatchExecutionContext.class), same(payload))).thenReturn(transformResult);
ActionWrapper action = mock(ActionWrapper.class);
when(action.execute(any(WatchExecutionContext.class))).thenReturn(watchActionResult);
ExecutableActions actions = new ExecutableActions(Arrays.asList(action));
Watch.Status watchStatus = new Watch.Status();
Watch watch = mock(Watch.class);
// watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.payload()).thenReturn(payload);
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
// 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 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);
// the action
Action.Result actionResult = mock(Action.Result.class);
when(actionResult.type()).thenReturn("_action_type");
when(actionResult.status()).thenReturn(Action.Result.Status.SUCCESS);
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(now)));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.throttler()).thenReturn(throttler);
when(watch.transform()).thenReturn(transform);
when(watch.transform()).thenReturn(watchTransform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
DateTime now = DateTime.now(UTC);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event);
WatchExecutionResult executionResult = executionService.executeInner(context);
assertThat(executionResult.conditionResult(), sameInstance(conditionResult));
assertThat(executionResult.transformResult(), sameInstance(transformResult));
assertThat(executionResult.throttleResult(), sameInstance(throttleResult));
assertThat(executionResult.actionsResults().get("_id"), sameInstance(watchActionResult));
assertThat(executionResult.transformResult(), sameInstance(watchTransformResult));
ActionWrapper.Result result = executionResult.actionsResults().get("_action");
assertThat(result, notNullValue());
assertThat(result.id(), is("_action"));
assertThat(result.transform(), sameInstance(actionTransformResult));
assertThat(result.action(), sameInstance(actionResult));
verify(condition, times(1)).execute(any(WatchExecutionContext.class));
verify(throttler, times(1)).throttle(any(WatchExecutionContext.class));
verify(transform, times(1)).execute(any(WatchExecutionContext.class), same(payload));
verify(action, times(1)).execute(any(WatchExecutionContext.class));
verify(condition, times(1)).execute(context);
verify(watchTransform, times(1)).execute(context, payload);
verify(action, times(1)).execute("_action", context, payload);
}
@Test
public void testExecute_throttled() throws Exception {
DateTime now = DateTime.now(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 = AlwaysCondition.Result.INSTANCE;
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(true);
Transform.Result transformResult = mock(Transform.Result.class);
when(transformResult.payload()).thenReturn(payload);
ActionWrapper.Result actionResult = mock(ActionWrapper.Result.class);
when(actionResult.id()).thenReturn("_id");
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
Throttler throttler = mock(Throttler.class);
when(throttler.throttle(any(WatchExecutionContext.class))).thenReturn(throttleResult);
ExecutableTransform transform = mock(ExecutableTransform.class);
when(transform.execute(any(WatchExecutionContext.class), same(payload))).thenReturn(transformResult);
ActionWrapper action = mock(ActionWrapper.class);
when(action.execute(any(WatchExecutionContext.class))).thenReturn(actionResult);
ExecutableActions actions = new ExecutableActions(Arrays.asList(action));
Watch.Status watchStatus = new Watch.Status();
Watch watch = mock(Watch.class);
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);
ExecutableAction action = mock(ExecutableAction.class);
when(action.type()).thenReturn("_type");
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, transform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(now)));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.throttler()).thenReturn(throttler);
when(watch.transform()).thenReturn(transform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
DateTime now = DateTime.now(UTC);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event);
WatchExecutionResult executionResult = executionService.executeInner(context);
assertThat(executionResult.inputResult(), sameInstance(inputResult));
assertThat(executionResult.conditionResult(), sameInstance(conditionResult));
assertThat(executionResult.throttleResult(), sameInstance(throttleResult));
assertThat(executionResult.actionsResults().count(), is(0));
assertThat(executionResult.transformResult(), nullValue());
assertThat(executionResult.actionsResults().count(), is(1));
ActionWrapper.Result result = executionResult.actionsResults().get("_action");
assertThat(result, notNullValue());
assertThat(result.id(), is("_action"));
assertThat(result.transform(), nullValue());
assertThat(result.action(), instanceOf(Action.Result.Throttled.class));
Action.Result.Throttled throttled = (Action.Result.Throttled) result.action();
assertThat(throttled.reason(), is("_throttle_reason"));
verify(condition, times(1)).execute(any(WatchExecutionContext.class));
verify(throttler, times(1)).throttle(any(WatchExecutionContext.class));
verify(transform, never()).execute(any(WatchExecutionContext.class), same(payload));
verify(action, never()).execute(any(WatchExecutionContext.class));
verify(condition, times(1)).execute(context);
verify(throttler, times(1)).throttle("_action", context);
verify(transform, never()).execute(context, payload);
}
@Test
public void testExecute_conditionNotMet() throws Exception {
DateTime now = DateTime.now(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;
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(true);
Transform.Result transformResult = mock(Transform.Result.class);
ActionWrapper.Result actionResult = mock(ActionWrapper.Result.class);
when(actionResult.id()).thenReturn("_id");
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
Throttler throttler = mock(Throttler.class);
when(throttler.throttle(any(WatchExecutionContext.class))).thenReturn(throttleResult);
ExecutableTransform transform = mock(ExecutableTransform.class);
when(transform.execute(any(WatchExecutionContext.class), same(payload))).thenReturn(transformResult);
ActionWrapper action = mock(ActionWrapper.class);
when(action.execute(any(WatchExecutionContext.class))).thenReturn(actionResult);
ExecutableActions actions = new ExecutableActions(Arrays.asList(action));
Watch.Status watchStatus = new Watch.Status();
Watch watch = mock(Watch.class);
// watch level transform
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
// action throttler
ActionThrottler throttler = mock(ActionThrottler.class);
ExecutableTransform actionTransform = mock(ExecutableTransform.class);
ExecutableAction action = mock(ExecutableAction.class);
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(now)));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.throttler()).thenReturn(throttler);
when(watch.transform()).thenReturn(transform);
when(watch.transform()).thenReturn(watchTransform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
DateTime now = DateTime.now(UTC);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
WatchExecutionContext context = new TriggeredExecutionContext(watch, now, event);
WatchExecutionResult executionResult = executionService.executeInner(context);
assertThat(executionResult.inputResult(), sameInstance(inputResult));
assertThat(executionResult.conditionResult(), sameInstance(conditionResult));
assertThat(executionResult.throttleResult(), nullValue());
assertThat(executionResult.transformResult(), nullValue());
assertThat(executionResult.actionsResults().count(), is(0));
verify(condition, times(1)).execute(any(WatchExecutionContext.class));
verify(throttler, never()).throttle(any(WatchExecutionContext.class));
verify(transform, never()).execute(any(WatchExecutionContext.class), same(payload));
verify(action, never()).execute(any(WatchExecutionContext.class));
verify(condition, times(1)).execute(context);
verify(watchTransform, never()).execute(context, payload);
verify(throttler, never()).throttle("_action", context);
verify(actionTransform, never()).execute(context, payload);
verify(action, never()).execute("_action", context, payload);
}
}

View File

@ -5,14 +5,15 @@
*/
package org.elasticsearch.watcher.execution;
import com.carrotsearch.randomizedtesting.annotations.Seed;
import org.apache.lucene.util.LuceneTestCase.Slow;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.WatcherService;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.logging.LoggingAction;
import org.elasticsearch.watcher.client.WatchSourceBuilder;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
@ -22,7 +23,6 @@ import org.elasticsearch.watcher.history.WatchRecord;
import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.transport.actions.delete.DeleteWatchResponse;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchResponse;
import org.elasticsearch.watcher.transport.actions.get.GetWatchRequest;
@ -43,6 +43,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
@ -53,6 +54,7 @@ import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.hamcrest.Matchers.*;
@Seed("8E31841F5876336B:1A23AE04C0480F1")
public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
@Override
@ -60,13 +62,13 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
return false;
}
@Test @Repeat(iterations = 10)
@Test //@Repeat(iterations = 10)
public void testExecuteWatch() throws Exception {
ensureWatcherStarted();
boolean ignoreCondition = randomBoolean();
boolean recordExecution = randomBoolean();
boolean conditionAlwaysTrue = randomBoolean();
String actionIdToSimulate = randomFrom("_all", "log", null);
String action = randomFrom("_all", "log");
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
@ -82,10 +84,11 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
assertThat(putWatchResponse.getVersion(), greaterThan(0L));
refresh();
assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true));
ctxBuilder = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent); //If we are persisting the state we need to use the exact watch that is in memory
//If we are persisting the state we need to use the exact watch that is in memory
ctxBuilder = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent, timeValueSeconds(5));
} else {
parsedWatch = watchParser().parse("_id", false, watchBuilder.buildAsBytes(XContentType.JSON));
ctxBuilder = ManualExecutionContext.builder(parsedWatch, triggerEvent);
ctxBuilder = ManualExecutionContext.builder(parsedWatch, triggerEvent, timeValueSeconds(5));
}
if (ignoreCondition) {
@ -94,18 +97,19 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
ctxBuilder.recordExecution(recordExecution);
if (actionIdToSimulate != null) {
if ("_all".equals(actionIdToSimulate)) {
ctxBuilder.simulateAllActions();
if ("_all".equals(action)) {
ctxBuilder.allActionsMode(ActionExecutionMode.SIMULATE);
} else {
ctxBuilder.simulateActions(actionIdToSimulate);
}
ctxBuilder.actionMode(action, ActionExecutionMode.SIMULATE);
}
ManualExecutionContext ctx = ctxBuilder.build();
refresh();
long oldRecordCount = docCount(HistoryStore.INDEX_PREFIX + "*", HistoryStore.DOC_TYPE, matchAllQuery());
WatchRecord watchRecord = executionService().execute(ctxBuilder.build());
WatchRecord watchRecord = executionService().execute(ctx);
refresh();
@ -120,12 +124,12 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
assertThat("The action should not have run", watchRecord.execution().actionsResults().count(), equalTo(0));
}
if ((ignoreCondition || conditionAlwaysTrue) && actionIdToSimulate == null) {
if ((ignoreCondition || conditionAlwaysTrue) && action == null) {
assertThat("The action should have run non simulated", watchRecord.execution().actionsResults().get("log").action(),
not(instanceOf(LoggingAction.Result.Simulated.class)) );
}
if ((ignoreCondition || conditionAlwaysTrue) && actionIdToSimulate != null ) {
if ((ignoreCondition || conditionAlwaysTrue) && action != null ) {
assertThat("The action should have run simulated", watchRecord.execution().actionsResults().get("log").action(), instanceOf(LoggingAction.Result.Simulated.class));
}
@ -134,13 +138,13 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
refresh();
Watch persistedWatch = watchParser().parse("_id", true, watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().getSource().getBytes());
if (ignoreCondition || conditionAlwaysTrue) {
assertThat(testWatch.status().ackStatus().state(), equalTo(Watch.Status.AckStatus.State.ACKABLE));
assertThat(persistedWatch.status().ackStatus().state(), equalTo(Watch.Status.AckStatus.State.ACKABLE));
assertThat(testWatch.status().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.ACKABLE));
assertThat(persistedWatch.status().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.ACKABLE));
} else {
assertThat(testWatch.status().ackStatus().state(), equalTo(Watch.Status.AckStatus.State.AWAITS_EXECUTION));
assertThat(testWatch.status().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
}
} else {
assertThat(parsedWatch.status().ackStatus().state(), equalTo(Watch.Status.AckStatus.State.AWAITS_EXECUTION));
assertThat(parsedWatch.status().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
}
}
@ -163,16 +167,17 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
map2.put("foo", map1);
ManualTriggerEvent triggerEvent = new ManualTriggerEvent("_id", new ScheduleTriggerEvent(new DateTime(UTC), new DateTime(UTC)));
ManualExecutionContext.Builder ctxBuilder1 = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent, timeValueSeconds(5));
ctxBuilder1.actionMode("_all", ActionExecutionMode.SIMULATE);
ManualExecutionContext.Builder ctxBuilder1 = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent);
ctxBuilder1.simulateActions("_all");
ctxBuilder1.withInput(new SimpleInput.Result(new Payload.Simple(map1)));
ctxBuilder1.recordExecution(true);
WatchRecord watchRecord1 = executionService().execute(ctxBuilder1.build());
ManualExecutionContext.Builder ctxBuilder2 = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent);
ctxBuilder2.simulateActions("_all");
ManualExecutionContext.Builder ctxBuilder2 = ManualExecutionContext.builder(watchService().getWatch("_id"), triggerEvent, timeValueSeconds(5));
ctxBuilder2.actionMode("_all", ActionExecutionMode.SIMULATE);
ctxBuilder2.withInput(new SimpleInput.Result(new Payload.Simple(map2)));
ctxBuilder2.recordExecution(true);
@ -191,7 +196,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(neverCondition())
.throttlePeriod(new TimeValue(1, TimeUnit.HOURS))
.defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS))
.addAction("log", loggingAction("foobar"));
watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet();
@ -214,7 +219,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(alwaysCondition())
.throttlePeriod(new TimeValue(1, TimeUnit.HOURS))
.defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS))
.addAction("log", loggingAction("foobar"));
watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet();
@ -242,7 +247,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(new ScriptCondition((new Script.Builder.Inline("sleep 10000; return true")).build()))
.throttlePeriod(new TimeValue(1, TimeUnit.HOURS))
.defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS))
.addAction("log", loggingAction("foobar"));
int numberOfThreads = scaledRandomIntBetween(1, 5);
@ -293,9 +298,9 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
this.watchId = watchId;
this.startLatch = startLatch;
ManualTriggerEvent triggerEvent = new ManualTriggerEvent(watchId, new ScheduleTriggerEvent(new DateTime(UTC), new DateTime(UTC)));
ctxBuilder = ManualExecutionContext.builder(watcherService.getWatch(watchId), triggerEvent);
ctxBuilder = ManualExecutionContext.builder(watcherService.getWatch(watchId), triggerEvent, timeValueSeconds(5));
ctxBuilder.recordExecution(true);
ctxBuilder.withThrottle(Throttler.Result.NO);
ctxBuilder.actionMode("_all", ActionExecutionMode.FORCE_EXECUTE);
}
@Override

View File

@ -12,7 +12,6 @@ import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.input.none.ExecutableNoneInput;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Watch;
@ -31,7 +30,7 @@ public class HistoryStoreLifeCycleTest extends AbstractWatcherIntegrationTests {
public void testPutLoadUpdate() throws Exception {
ExecutableCondition condition = new ExecutableAlwaysCondition(logger);
HistoryStore historyStore = getInstanceFromMaster(HistoryStore.class);
Watch watch = new Watch("_name", SystemClock.INSTANCE, licenseService(), null, new ExecutableNoneInput(logger), condition, null, null, null, null, null);
Watch watch = new Watch("_name", null, new ExecutableNoneInput(logger), condition, null, null, null, null, null);
// Put watch records and verify that these are stored
WatchRecord[] watchRecords = new WatchRecord[randomIntBetween(1, 50)];

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.email.EmailAction;
import org.elasticsearch.watcher.actions.webhook.WebhookAction;
@ -22,7 +23,6 @@ import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.throttle.Throttler;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
@ -30,6 +30,7 @@ import org.elasticsearch.watcher.execution.WatchExecutionResult;
import org.junit.Test;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.hamcrest.Matchers.equalTo;
/**
@ -58,16 +59,15 @@ public class WatchRecordTests extends AbstractWatcherIntegrationTests {
ScheduleTriggerEvent event = new ScheduleTriggerEvent(watch.id(), DateTime.now(UTC), DateTime.now(UTC));
Wid wid = new Wid("_record", randomLong(), DateTime.now(UTC));
WatchRecord watchRecord = new WatchRecord(wid, watch, event);
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), event);
ctx.onActionResult(new ActionWrapper.Result("_email", new EmailAction.Result.Failure("failed to send because blah")));
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(), event, timeValueSeconds(5));
ctx.onActionResult(new ActionWrapper.Result("_email", new Action.Result.Failure(EmailAction.TYPE, "failed to send because blah")));
HttpRequest request = HttpRequest.builder("localhost", 8000)
.path("/watchfoo")
.body("{'awesome' : 'us'}")
.build();
ctx.onActionResult(new ActionWrapper.Result("_webhook", new WebhookAction.Result.Executed(request, new HttpResponse(300))));
ctx.onActionResult(new ActionWrapper.Result("_webhook", new WebhookAction.Result.Success(request, new HttpResponse(300))));
SimpleInput.Result inputResult = new SimpleInput.Result(new Payload.Simple());
Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
ctx.onThrottleResult(Throttler.NO_THROTTLE.throttle(ctx));
ctx.onInputResult(inputResult);
ctx.onConditionResult(conditionResult);
watchRecord.seal(new WatchExecutionResult(ctx));
@ -86,17 +86,16 @@ public class WatchRecordTests extends AbstractWatcherIntegrationTests {
public void testParser_WithSealedWatchRecord_WithScriptSearchCondition() throws Exception {
Watch watch = WatcherTestUtils.createTestWatch("fired_test", scriptService(), watcherHttpClient(), noopEmailService(), logger);
ScheduleTriggerEvent event = new ScheduleTriggerEvent(watch.id(), DateTime.now(UTC), DateTime.now(UTC));
WatchExecutionContext ctx = new TriggeredExecutionContext( watch, new DateTime(), event);
WatchExecutionContext ctx = new TriggeredExecutionContext( watch, new DateTime(), event, timeValueSeconds(5));
WatchRecord watchRecord = new WatchRecord(ctx.id(), watch, event);
ctx.onActionResult(new ActionWrapper.Result("_email", new EmailAction.Result.Failure("failed to send because blah")));
ctx.onActionResult(new ActionWrapper.Result("_email", new Action.Result.Failure(EmailAction.TYPE, "failed to send because blah")));
HttpRequest request = HttpRequest.builder("localhost", 8000)
.path("/watchfoo")
.body("{'awesome' : 'us'}")
.build();
ctx.onActionResult(new ActionWrapper.Result("_webhook", new WebhookAction.Result.Executed(request, new HttpResponse(300))));
ctx.onActionResult(new ActionWrapper.Result("_webhook", new WebhookAction.Result.Success(request, new HttpResponse(300))));
SimpleInput.Result inputResult = new SimpleInput.Result(new Payload.Simple());
Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
ctx.onThrottleResult(Throttler.NO_THROTTLE.throttle(ctx));
ctx.onInputResult(inputResult);
ctx.onConditionResult(conditionResult);
watchRecord.seal(new WatchExecutionResult(ctx));

View File

@ -12,10 +12,12 @@ import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
@ -24,8 +26,6 @@ import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.input.InputBuilders;
import org.elasticsearch.watcher.input.simple.ExecutableSimpleInput;
import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.support.http.*;
import org.elasticsearch.watcher.support.http.auth.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthFactory;
@ -40,6 +40,7 @@ import org.elasticsearch.watcher.trigger.schedule.ScheduleTrigger;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.junit.Before;
import org.junit.Test;
@ -88,19 +89,18 @@ public class HttpInputTests extends ElasticsearchTestCase {
when(templateEngine.render(eq(Template.inline("_body").build()), any(Map.class))).thenReturn("_body");
Watch watch = new Watch("test-watch",
new ClockMock(),
mock(LicenseService.class),
new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple()), logger),
new ExecutableAlwaysCondition(logger),
null,
null,
new ExecutableActions(new ArrayList<ActionWrapper>()),
null,
null,
new Watch.Status());
new WatchStatus(ImmutableMap.<String, ActionStatus>of()));
WatchExecutionContext ctx = new TriggeredExecutionContext(watch,
new DateTime(0, UTC),
new ScheduleTriggerEvent(watch.id(), new DateTime(0, UTC), new DateTime(0, UTC)));
new ScheduleTriggerEvent(watch.id(), new DateTime(0, UTC), new DateTime(0, UTC)),
TimeValue.timeValueSeconds(5));
HttpInput.Result result = input.execute(ctx);
assertThat(result.type(), equalTo(HttpInput.TYPE));
assertThat(result.payload().data(), equalTo(MapBuilder.<String, Object>newMapBuilder().put("key", "value").map()));

View File

@ -9,6 +9,7 @@ import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
@ -19,6 +20,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
@ -26,9 +28,7 @@ import org.elasticsearch.watcher.execution.TriggeredExecutionContext;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.input.simple.ExecutableSimpleInput;
import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.trigger.schedule.IntervalSchedule;
@ -36,6 +36,7 @@ import org.elasticsearch.watcher.trigger.schedule.ScheduleTrigger;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.junit.Test;
import java.io.IOException;
@ -46,6 +47,7 @@ import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
@ -55,8 +57,8 @@ import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.SUITE;
import static org.elasticsearch.watcher.test.WatcherTestUtils.areJsonEquivalent;
import static org.elasticsearch.watcher.test.WatcherTestUtils.getRandomSupportedSearchType;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
*/
@ -88,18 +90,17 @@ public class SearchInputTests extends ElasticsearchIntegrationTest {
ExecutableSearchInput searchInput = new ExecutableSearchInput(new SearchInput(request, null), logger, ClientProxy.of(client()));
WatchExecutionContext ctx = new TriggeredExecutionContext(
new Watch("test-watch",
new ClockMock(),
mock(LicenseService.class),
new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple()), logger),
new ExecutableAlwaysCondition(logger),
null,
null,
new ExecutableActions(new ArrayList<ActionWrapper>()),
null,
null,
new Watch.Status()),
new WatchStatus(ImmutableMap.<String, ActionStatus>of())),
new DateTime(0, UTC),
new ScheduleTriggerEvent("test-watch", new DateTime(0, UTC), new DateTime(0, UTC)));
new ScheduleTriggerEvent("test-watch", new DateTime(0, UTC), new DateTime(0, UTC)),
timeValueSeconds(5));
SearchInput.Result result = searchInput.execute(ctx);
assertThat((Integer) XContentMapValues.extractValue("hits.total", result.payload().data()), equalTo(0));
@ -191,18 +192,17 @@ public class SearchInputTests extends ElasticsearchIntegrationTest {
ExecutableSearchInput searchInput = new ExecutableSearchInput(new SearchInput(request, null), logger, ClientProxy.of(client()));
WatchExecutionContext ctx = new TriggeredExecutionContext(
new Watch("test-watch",
new ClockMock(),
mock(LicenseService.class),
new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple()), logger),
new ExecutableAlwaysCondition(logger),
null,
null,
new ExecutableActions(new ArrayList<ActionWrapper>()),
null,
null,
new Watch.Status()),
new WatchStatus(ImmutableMap.<String, ActionStatus>of())),
new DateTime(0, UTC),
new ScheduleTriggerEvent("test-watch", new DateTime(0, UTC), new DateTime(0, UTC)));
new ScheduleTriggerEvent("test-watch", new DateTime(0, UTC), new DateTime(0, UTC)),
timeValueSeconds(5));
SearchInput.Result result = searchInput.execute(ctx);
assertThat((Integer) XContentMapValues.extractValue("hits.total", result.payload().data()), equalTo(0));
@ -311,18 +311,17 @@ public class SearchInputTests extends ElasticsearchIntegrationTest {
ExecutableSearchInput searchInput = new ExecutableSearchInput(si, logger, ClientProxy.of(client()));
WatchExecutionContext ctx = new TriggeredExecutionContext(
new Watch("test-watch",
new ClockMock(),
mock(LicenseService.class),
new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple()), logger),
new ExecutableAlwaysCondition(logger),
null,
null,
new ExecutableActions(new ArrayList<ActionWrapper>()),
null,
null,
new Watch.Status()),
new WatchStatus(ImmutableMap.<String, ActionStatus>of())),
new DateTime(60000, UTC),
new ScheduleTriggerEvent("test-watch", new DateTime(60000, UTC), new DateTime(60000, UTC)));
new ScheduleTriggerEvent("test-watch", new DateTime(60000, UTC), new DateTime(60000, UTC)),
timeValueSeconds(5));
return searchInput.execute(ctx);
}

View File

@ -19,12 +19,12 @@ import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.watcher.WatcherVersion;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.elasticsearch.watcher.transport.actions.service.WatcherServiceResponse;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.junit.Test;
import java.util.ArrayList;
@ -94,7 +94,7 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
assertWatchWithMinimumPerformedActionsCount(watchName, 1, false);
// ack watch API should work
assertThat(watcherClient().prepareAckWatch(watchName).get().getStatus().ackStatus().state(), is(Watch.Status.AckStatus.State.ACKED));
assertThat(watcherClient().prepareAckWatch(watchName).get().getStatus().actionStatus("_index").ackStatus().state(), is(ActionStatus.AckStatus.State.ACKED));
// get watch API should work
assertThat(watcherClient().prepareGetWatch(watchName).get().getId(), is(watchName));
@ -162,8 +162,8 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
// and last... lets verify that we have throttled watches due to license expiration
long throttledCount = docCount(HistoryStore.INDEX_PREFIX + "*", HistoryStore.DOC_TYPE, filteredQuery(
matchQuery("execution_result.throttle_reason", "watcher license expired"),
termFilter("execution_result.throttled", true)));
matchQuery("execution_result.actions.index.reason", "watcher license expired"),
termFilter("execution_result.actions.index.status", "throttled")));
assertThat(throttledCount, is(1L));
//=====
@ -184,7 +184,7 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
}
try {
assertThat(watcherClient().prepareAckWatch(watchName).get().getStatus().ackStatus().state(), is(Watch.Status.AckStatus.State.ACKED));
watcherClient().prepareAckWatch(watchName).get();
fail("ack watch APIshould NOT work when license is disabled");
} catch (LicenseExpiredException lee) {
assertThat(lee.feature(), is(LicenseService.FEATURE_NAME));
@ -192,7 +192,7 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
}
try {
assertThat(watcherClient().prepareGetWatch(watchName).get().getId(), is(watchName));
watcherClient().prepareGetWatch(watchName).get();
fail("get watch API should NOT work when license is disabled");
} catch (LicenseExpiredException lee) {
assertThat(lee.feature(), is(LicenseService.FEATURE_NAME));
@ -200,16 +200,16 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
}
try {
assertThat(watcherClient().prepareDeleteWatch(watchName).get().isFound(), is(true));
watcherClient().prepareDeleteWatch(watchName).get();
fail("delete watch API should NOT work when license is disabled");
} catch (LicenseExpiredException lee) {
assertThat(lee.feature(), is(LicenseService.FEATURE_NAME));
assertThat(lee.status(), is(RestStatus.UNAUTHORIZED));
}
// watcher stats should work
// watcher stats should not work
try {
assertThat(watcherClient().prepareWatcherStats().get().getVersion(), is(WatcherVersion.CURRENT));
watcherClient().prepareWatcherStats().get();
fail("watcher stats API should NOT work when license is disabled");
} catch (LicenseExpiredException lee) {
assertThat(lee.feature(), is(LicenseService.FEATURE_NAME));
@ -217,7 +217,7 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
}
try {
assertThat(watcherClient().prepareWatchService().restart().get().isAcknowledged(), is(true));
watcherClient().prepareWatchService().restart().get();
fail("watcher service API should NOT work when license is disabled");
} catch (LicenseExpiredException lee) {
assertThat(lee.feature(), is(LicenseService.FEATURE_NAME));
@ -246,12 +246,12 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTests {
@Override
public void run() {
XContentSource source = watcherClient().prepareGetWatch(watchName).get().getSource();
assertThat(source.getValue("status.ack.state"), is((Object) "ackable"));
assertThat(source.getValue("status.actions._index.ack_status.state"), is((Object) "ackable"));
}
});
// ack watch API should work
assertThat(watcherClient().prepareAckWatch(watchName).get().getStatus().ackStatus().state(), is(Watch.Status.AckStatus.State.ACKED));
assertThat(watcherClient().prepareAckWatch(watchName).get().getStatus().actionStatus("_index").ackStatus().state(), is(ActionStatus.AckStatus.State.ACKED));
// get watch API should work
assertThat(watcherClient().prepareGetWatch(watchName).get().getId(), is(watchName));

View File

@ -28,6 +28,7 @@ import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.actions.email.EmailAction;
@ -47,7 +48,6 @@ import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpMethod;
import org.elasticsearch.watcher.support.http.HttpRequestTemplate;
@ -65,12 +65,14 @@ import org.elasticsearch.watcher.trigger.schedule.CronSchedule;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTrigger;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStatus;
import javax.mail.internet.AddressException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.*;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.ElasticsearchTestCase.randomFrom;
@ -190,18 +192,20 @@ public final class WatcherTestUtils {
LicenseService licenseService = mock(LicenseService.class);
when(licenseService.enabled()).thenReturn(true);
DateTime now = DateTime.now(UTC);
return new Watch(
watchName,
SystemClock.INSTANCE,
licenseService,
new ScheduleTrigger(new CronSchedule("0/5 * * * * ? *")),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple(inputData)), logger),
new ExecutableScriptCondition(new ScriptCondition(Script.inline("return true").build()), logger, scriptService),
new ExecutableSearchTransform(new SearchTransform(transformRequest), logger, client),
new TimeValue(0),
new ExecutableActions(actions),
metadata,
new TimeValue(0),
new Watch.Status());
new WatchStatus(ImmutableMap.<String, ActionStatus>builder()
.put("_webhook", new ActionStatus(now))
.put("_email", new ActionStatus(now))
.build()));
}
public static ScriptServiceProxy getScriptServiceProxy(ThreadPool tp) throws Exception {

View File

@ -261,7 +261,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTests {
WatchSourceBuilder source = watchBuilder()
.trigger(schedule(interval("1s")))
.input(simpleInput("key", "value"))
.throttlePeriod(TimeValue.timeValueSeconds(0))
.defaultThrottlePeriod(TimeValue.timeValueSeconds(0))
.addAction("_id", loggingAction("hello {{ctx.watcher_id}}!"));
watcherClient().preparePutWatch("_name")
.setSource(source)
@ -272,7 +272,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTests {
source = watchBuilder()
.trigger(schedule(interval("100s")))
.throttlePeriod(TimeValue.timeValueSeconds(0))
.defaultThrottlePeriod(TimeValue.timeValueSeconds(0))
.input(simpleInput("key", "value"))
.addAction("_id", loggingAction("hello {{ctx.watcher_id}}!"));
watcherClient().preparePutWatch("_name")

View File

@ -65,25 +65,37 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
// valid watch
client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id0")
.setSource(jsonBuilder().startObject()
.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
.startObject(Watch.Field.TRIGGER.getPreferredName())
.startObject("schedule")
.field("interval", "1s")
.endObject()
.endObject()
.startObject(Watch.Parser.ACTIONS_FIELD.getPreferredName())
.startObject(Watch.Field.ACTIONS.getPreferredName())
.endObject()
.endObject())
.get();
// no actions field:
client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id1")
.setSource(jsonBuilder().startObject()
.startObject(Watch.Field.TRIGGER.getPreferredName())
.startObject("schedule")
.field("interval", "1s")
.endObject()
.endObject()
.endObject())
.get();
// invalid interval
client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id2")
.setSource(jsonBuilder().startObject()
.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
.startObject(Watch.Field.TRIGGER.getPreferredName())
.startObject("schedule")
.field("interval", true)
.endObject()
.endObject()
.startObject(Watch.Parser.ACTIONS_FIELD.getPreferredName())
.startObject(Watch.Field.ACTIONS.getPreferredName())
.endObject()
.endObject())
.get();
@ -91,13 +103,13 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
// illegal top level field
client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id3")
.setSource(jsonBuilder().startObject()
.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
.startObject(Watch.Field.TRIGGER.getPreferredName())
.startObject("schedule")
.field("interval", "1s")
.endObject()
.startObject("illegal_field").endObject()
.endObject()
.startObject(Watch.Parser.ACTIONS_FIELD.getPreferredName()).endObject()
.startObject(Watch.Field.ACTIONS.getPreferredName()).endObject()
.endObject())
.get();
@ -115,12 +127,12 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
public void testLoadMalformedWatchRecord() throws Exception {
client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id")
.setSource(jsonBuilder().startObject()
.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
.startObject(Watch.Field.TRIGGER.getPreferredName())
.startObject("schedule")
.field("cron", "0/5 * * * * ? 2050")
.endObject()
.endObject()
.startObject(Watch.Parser.ACTIONS_FIELD.getPreferredName())
.startObject(Watch.Field.ACTIONS.getPreferredName())
.endObject()
.endObject())
.get();
@ -137,10 +149,10 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.startObject(WatchRecord.Field.TRIGGER_EVENT.getPreferredName())
.field(event.type(), event)
.endObject()
.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
.startObject(Watch.Field.CONDITION.getPreferredName())
.field(condition.type(), condition)
.endObject()
.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
.startObject(Watch.Field.INPUT.getPreferredName())
.startObject("none").endObject()
.endObject()
.field(WatchRecord.Field.STATE.getPreferredName(), WatchRecord.State.AWAITS_EXECUTION)
@ -157,10 +169,10 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.startObject(WatchRecord.Field.TRIGGER_EVENT.getPreferredName())
.field(event.type(), event)
.endObject()
.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
.startObject(Watch.Field.CONDITION.getPreferredName())
.startObject("unknown").endObject()
.endObject()
.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
.startObject(Watch.Field.INPUT.getPreferredName())
.startObject("none").endObject()
.endObject()
.field(WatchRecord.Field.STATE.getPreferredName(), WatchRecord.State.AWAITS_EXECUTION)
@ -177,10 +189,10 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.startObject(WatchRecord.Field.TRIGGER_EVENT.getPreferredName())
.startObject("unknown").endObject()
.endObject()
.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
.startObject(Watch.Field.CONDITION.getPreferredName())
.field(condition.type(), condition)
.endObject()
.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
.startObject(Watch.Field.INPUT.getPreferredName())
.startObject("none").endObject()
.endObject()
.field(WatchRecord.Field.STATE.getPreferredName(), WatchRecord.State.AWAITS_EXECUTION)
@ -211,10 +223,10 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.startObject(WatchRecord.Field.TRIGGER_EVENT.getPreferredName())
.field(event.type(), event)
.endObject()
.startObject(Watch.Parser.CONDITION_FIELD.getPreferredName())
.startObject(Watch.Field.CONDITION.getPreferredName())
.field(condition.type(), condition)
.endObject()
.startObject(Watch.Parser.INPUT_FIELD.getPreferredName())
.startObject(Watch.Field.INPUT.getPreferredName())
.startObject("none").endObject()
.endObject()
.field(WatchRecord.Field.STATE.getPreferredName(), WatchRecord.State.AWAITS_EXECUTION)
@ -274,7 +286,7 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.input(searchInput(searchRequest))
.condition(alwaysCondition())
.addAction("_id", indexAction("output", "test"))
.throttlePeriod(TimeValue.timeValueMillis(0))
.defaultThrottlePeriod(TimeValue.timeValueMillis(0))
).get();
DateTime now = DateTime.now(UTC);
@ -327,7 +339,7 @@ public class BootStrapTests extends AbstractWatcherIntegrationTests {
.input(searchInput(searchRequest))
.condition(alwaysCondition())
.addAction("_id", indexAction("output", "test"))
.throttlePeriod(TimeValue.timeValueMillis(0))
.defaultThrottlePeriod(TimeValue.timeValueMillis(0))
).get();
DateTime now = DateTime.now(UTC);

View File

@ -15,10 +15,10 @@ import org.elasticsearch.watcher.actions.email.service.support.EmailServer;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.shield.ShieldSecretService;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchResponse;
import org.elasticsearch.watcher.transport.actions.get.GetWatchResponse;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.trigger.TriggerEvent;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.WatchStore;
@ -26,7 +26,6 @@ import org.junit.After;
import org.junit.Test;
import javax.mail.internet.MimeMessage;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -144,8 +143,9 @@ public class EmailSecretsIntegrationTests extends AbstractWatcherIntegrationTest
.get();
assertThat(executeResponse, notNullValue());
contentSource = executeResponse.getSource();
value = contentSource.getValue("execution_result.actions.0.email.success");
assertThat((Boolean) value, is(true));
value = contentSource.getValue("execution_result.actions.0.email.status");
assertThat((String) value, is("success"));
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waiting too long for the email to be sent");

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.watcher.actions.logging.LoggingAction;
import org.elasticsearch.watcher.actions.logging.LoggingLevel;
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.history.WatchRecord;
import org.elasticsearch.watcher.support.template.Template;
@ -29,6 +30,7 @@ import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.scriptCondition;
import static org.elasticsearch.watcher.input.InputBuilders.searchInput;
@ -80,7 +82,9 @@ public class WatchMetadataTests extends AbstractWatcherIntegrationTests {
metadata.put("foo", "bar");
metadata.put("logtext", "This is a test");
LoggingAction loggingAction = new LoggingAction(Template.inline("{{ctx.metadata.logtext}}").build(), LoggingLevel.DEBUG, "test");
LoggingAction.Builder loggingAction = loggingAction(Template.inline("{{ctx.metadata.logtext}}"))
.setLevel(LoggingLevel.DEBUG)
.setCategory("test");
watcherClient().preparePutWatch("_name")
.setSource(watchBuilder()
@ -93,7 +97,7 @@ public class WatchMetadataTests extends AbstractWatcherIntegrationTests {
WatchRecord.Parser parser = getInstanceFromMaster(WatchRecord.Parser.class);
TriggerEvent triggerEvent = new ScheduleTriggerEvent(new DateTime(UTC), new DateTime(UTC));
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_name").setTriggerEvent(triggerEvent).addSimulatedActions("_all").get();
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_name").setTriggerEvent(triggerEvent).setActionMode("_all", ActionExecutionMode.SIMULATE).get();
WatchRecord record = parser.parse("test_run", 1, executeWatchResponse.getSource().getBytes());
assertThat(record.metadata().get("foo").toString(), equalTo("bar"));

View File

@ -12,6 +12,7 @@ import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.history.WatchRecord;
@ -59,7 +60,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
.condition(scriptCondition("ctx.payload.hits.total > 0"))
.transform(searchTransform(matchAllRequest().indices("events")))
.addAction("_id", indexAction("actions", "action"))
.throttlePeriod(new TimeValue(0, TimeUnit.SECONDS)))
.defaultThrottlePeriod(new TimeValue(0, TimeUnit.SECONDS)))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
@ -70,7 +71,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
Thread.sleep(20000);
}
AckWatchResponse ackResponse = watcherClient.prepareAckWatch("_name").get();
assertThat(ackResponse.getStatus().ackStatus().state(), is(Watch.Status.AckStatus.State.ACKED));
assertThat(ackResponse.getStatus().actionStatus("_id").ackStatus().state(), is(ActionStatus.AckStatus.State.ACKED));
refresh();
long countAfterAck = docCount("actions", "action", matchAllQuery());
@ -102,7 +103,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
assertThat(getWatchResponse.isFound(), is(true));
Watch parsedWatch = watchParser().parse(getWatchResponse.getId(), true, getWatchResponse.getSource().getBytes());
assertThat(parsedWatch.status().ackStatus().state(), is(Watch.Status.AckStatus.State.AWAITS_EXECUTION));
assertThat(parsedWatch.status().actionStatus("_id").ackStatus().state(), is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
long throttledCount = docCount(HistoryStore.INDEX_PREFIX + "*", null,
matchQuery(WatchRecord.Field.STATE.getPreferredName(), WatchRecord.State.THROTTLED.id()));
@ -123,7 +124,8 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
}
@Test @Repeat(iterations = 10)
@Test
@Repeat(iterations = 10)
public void testTimeThrottle() throws Exception {
WatcherClient watcherClient = watcherClient();
indexTestDoc();
@ -136,7 +138,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
.condition(scriptCondition("ctx.payload.hits.total > 0"))
.transform(searchTransform(matchAllRequest().indices("events")))
.addAction("_id", indexAction("actions", "action"))
.throttlePeriod(TimeValue.timeValueSeconds(30)))
.defaultThrottlePeriod(TimeValue.timeValueSeconds(30)))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
@ -230,7 +232,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
}
AckWatchResponse ackResponse = watcherClient.prepareAckWatch("_name").get();
assertThat(ackResponse.getStatus().ackStatus().state(), is(Watch.Status.AckStatus.State.ACKED));
assertThat(ackResponse.getStatus().actionStatus("_id").ackStatus().state(), is(ActionStatus.AckStatus.State.ACKED));
refresh();
long countAfterAck = docCount("actions", "action", matchAllQuery());
@ -245,13 +247,12 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
GetWatchResponse watchResponse = watcherClient.getWatch(new GetWatchRequest("_name")).actionGet();
Watch watch = watchParser().parse("_name", true, watchResponse.getSource().getBytes());
assertThat(watch.status().ackStatus().state(), Matchers.equalTo(Watch.Status.AckStatus.State.ACKED));
assertThat(watch.status().actionStatus("_id").ackStatus().state(), Matchers.equalTo(ActionStatus.AckStatus.State.ACKED));
refresh();
GetResponse getResponse = client().get(new GetRequest(WatchStore.INDEX, WatchStore.DOC_TYPE, "_name")).actionGet();
Watch indexedWatch = watchParser().parse("_name", true, getResponse.getSourceAsBytesRef());
assertThat(watch.status().ackStatus().state(), Matchers.equalTo(indexedWatch.status().ackStatus().state()));
assertThat(watch.status().actionStatus("_id").ackStatus().state(), Matchers.equalTo(indexedWatch.status().actionStatus("_id").ackStatus().state()));
if (timeWarped()) {
timeWarp().scheduler().trigger("_name", 4, TimeValue.timeValueSeconds(5));
@ -265,6 +266,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
assertThat(countAfterPostAckFires, equalTo(countAfterAck));
}
@Test @Repeat(iterations = 10)
public void test_default_TimeThrottle() throws Exception {
WatcherClient watcherClient = watcherClient();
@ -287,7 +289,7 @@ public class WatchThrottleTests extends AbstractWatcherIntegrationTests {
timeWarp().scheduler().trigger("_name");
refresh();
// the first fire should work
// the first trigger should work
long actionsCount = docCount("actions", "action", matchAllQuery());
assertThat(actionsCount, is(1L));

View File

@ -1,58 +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.watcher.throttle;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate;
import static org.elasticsearch.watcher.test.WatcherTestUtils.EMPTY_PAYLOAD;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class AckThrottlerTests extends ElasticsearchTestCase {
@Test
public void testWhenAcked() throws Exception {
DateTime timestamp = new DateTime();
WatchExecutionContext ctx = mockExecutionContext("_watch", EMPTY_PAYLOAD);
Watch watch = ctx.watch();
Watch.Status status = mock(Watch.Status.class);
when(status.ackStatus()).thenReturn(new Watch.Status.AckStatus(Watch.Status.AckStatus.State.ACKED, timestamp));
when(watch.status()).thenReturn(status);
when(watch.id()).thenReturn("_watch");
when(watch.acked()).thenReturn(true);
AckThrottler throttler = new AckThrottler();
Throttler.Result result = throttler.throttle(ctx);
assertThat(result.throttle(), is(true));
assertThat(result.reason(), is("watch [_watch] was acked at [" + formatDate(timestamp) + "]"));
}
@Test
public void testWhenNotAcked() throws Exception {
DateTime timestamp = new DateTime();
WatchExecutionContext ctx = mockExecutionContext("_watch", EMPTY_PAYLOAD);
Watch watch = ctx.watch();
Watch.Status status = mock(Watch.Status.class);
Watch.Status.AckStatus.State state = randomFrom(Watch.Status.AckStatus.State.AWAITS_EXECUTION, Watch.Status.AckStatus.State.ACKABLE);
when(status.ackStatus()).thenReturn(new Watch.Status.AckStatus(state, timestamp));
when(watch.status()).thenReturn(status);
when(watch.acked()).thenReturn(false);
AckThrottler throttler = new AckThrottler();
Throttler.Result result = throttler.throttle(ctx);
assertThat(result.throttle(), is(false));
assertThat(result.reason(), nullValue());
}
}

View File

@ -11,6 +11,7 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
@ -21,6 +22,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
@ -28,9 +30,7 @@ import org.elasticsearch.watcher.execution.TriggeredExecutionContext;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.input.simple.ExecutableSimpleInput;
import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.transform.Transform;
@ -40,6 +40,7 @@ import org.elasticsearch.watcher.trigger.schedule.ScheduleTrigger;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchStatus;
import org.junit.Test;
import java.io.IOException;
@ -50,6 +51,7 @@ import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
@ -58,7 +60,6 @@ import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.SUITE;
import static org.elasticsearch.watcher.support.WatcherDateUtils.parseDate;
import static org.elasticsearch.watcher.test.WatcherTestUtils.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
/**
*
@ -400,18 +401,17 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
WatchExecutionContext ctx = new TriggeredExecutionContext(
new Watch("test-watch",
new ClockMock(),
mock(LicenseService.class),
new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))),
new ExecutableSimpleInput(new SimpleInput(new Payload.Simple()), logger),
new ExecutableAlwaysCondition(logger),
null,
null,
new ExecutableActions(new ArrayList<ActionWrapper>()),
null,
null,
new Watch.Status()),
new WatchStatus(ImmutableMap.<String, ActionStatus>of())),
new DateTime(60000, UTC),
new ScheduleTriggerEvent("test-watch", new DateTime(60000, UTC), new DateTime(60000, UTC)));
new ScheduleTriggerEvent("test-watch", new DateTime(60000, UTC), new DateTime(60000, UTC)),
timeValueSeconds(5));
return executableSearchTransform.execute(ctx, Payload.Simple.EMPTY);
}

View File

@ -9,13 +9,19 @@ import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.joda.time.DateTimeZone;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.WatcherService;
import org.elasticsearch.watcher.WatcherState;
import org.elasticsearch.watcher.actions.ActionStatus;
import org.elasticsearch.watcher.execution.ExecutionService;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.watcher.trigger.Trigger;
import org.elasticsearch.watcher.trigger.TriggerEngine;
import org.elasticsearch.watcher.trigger.TriggerService;
@ -25,6 +31,7 @@ import org.junit.Test;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Matchers.any;
@ -40,6 +47,7 @@ public class WatchServiceTests extends ElasticsearchTestCase {
private WatcherService watcherService;
private ExecutionService executionService;
private WatchLockService watchLockService;
private ClockMock clock;
@Before
public void init() throws Exception {
@ -48,7 +56,8 @@ public class WatchServiceTests extends ElasticsearchTestCase {
watchParser = mock(Watch.Parser.class);
executionService = mock(ExecutionService.class);
watchLockService = mock(WatchLockService.class);
watcherService = new WatcherService(ImmutableSettings.EMPTY, triggerService, watchStore, watchParser, executionService, watchLockService);
clock = new ClockMock();
watcherService = new WatcherService(ImmutableSettings.EMPTY, clock, triggerService, watchStore, watchParser, executionService, watchLockService);
Field field = WatcherService.class.getDeclaredField("state");
field.setAccessible(true);
AtomicReference<WatcherState> state = (AtomicReference<WatcherState>) field.get(watcherService);
@ -169,16 +178,18 @@ public class WatchServiceTests extends ElasticsearchTestCase {
@Test
public void testAckWatch() throws Exception {
DateTime now = new DateTime(UTC);
clock.setTime(now);
TimeValue timeout = TimeValue.timeValueSeconds(5);
WatchLockService.Lock lock = mock(WatchLockService.Lock.class);
when(watchLockService.tryAcquire("_id", timeout)).thenReturn(lock);
Watch watch = mock(Watch.class);
when(watch.ack()).thenReturn(true);
Watch.Status status = new Watch.Status();
when(watch.ack(now, "_all")).thenReturn(true);
WatchStatus status = new WatchStatus(ImmutableMap.<String, ActionStatus>of());
when(watch.status()).thenReturn(status);
when(watchStore.get("_id")).thenReturn(watch);
Watch.Status result = watcherService.ackWatch("_id", timeout);
WatchStatus result = watcherService.ackWatch("_id", timeout);
assertThat(result, not(sameInstance(status)));
verify(watchStore, times(1)).updateStatus(watch);
@ -193,16 +204,17 @@ public class WatchServiceTests extends ElasticsearchTestCase {
@Test
public void testAckWatch_NotAck() throws Exception {
DateTime now = SystemClock.INSTANCE.now();
TimeValue timeout = TimeValue.timeValueSeconds(5);
WatchLockService.Lock lock = mock(WatchLockService.Lock.class);
when(watchLockService.tryAcquire("_id", timeout)).thenReturn(lock);
Watch watch = mock(Watch.class);
when(watch.ack()).thenReturn(false);
Watch.Status status = new Watch.Status();
when(watch.ack(now)).thenReturn(false);
WatchStatus status = new WatchStatus(ImmutableMap.<String, ActionStatus>of());
when(watch.status()).thenReturn(status);
when(watchStore.get("_id")).thenReturn(watch);
Watch.Status result = watcherService.ackWatch("_id", timeout);
WatchStatus result = watcherService.ackWatch("_id", timeout);
assertThat(result, not(sameInstance(status)));
verify(watchStore, never()).updateStatus(watch);

View File

@ -250,7 +250,7 @@ public class WatchStoreTests extends ElasticsearchTestCase {
when(clientProxy.searchScroll(anyString(), any(TimeValue.class))).thenReturn(searchResponse2, searchResponse3);
Watch watch1 = mock(Watch.class);
Watch.Status status = new Watch.Status();
WatchStatus status = mock(WatchStatus.class);
when(watch1.status()).thenReturn(status);
Watch watch2 = mock(Watch.class);
when(watch2.status()).thenReturn(status);

View File

@ -9,6 +9,7 @@ import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.ImmutableSettings;
@ -18,10 +19,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.ActionRegistry;
import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.actions.*;
import org.elasticsearch.watcher.actions.email.DataAttachment;
import org.elasticsearch.watcher.actions.email.EmailAction;
import org.elasticsearch.watcher.actions.email.EmailActionFactory;
@ -63,6 +61,7 @@ import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.clock.Clock;
import org.elasticsearch.watcher.support.clock.ClockMock;
import org.elasticsearch.watcher.support.clock.SystemClock;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpMethod;
@ -77,6 +76,7 @@ import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.actions.throttler.ActionThrottler;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.transform.TransformFactory;
import org.elasticsearch.watcher.transform.TransformRegistry;
@ -100,6 +100,7 @@ import org.junit.Test;
import java.util.Collection;
import java.util.Map;
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
import static org.elasticsearch.watcher.input.InputBuilders.searchInput;
import static org.elasticsearch.watcher.test.WatcherTestUtils.matchAllRequest;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
@ -115,6 +116,7 @@ public class WatchTests extends ElasticsearchTestCase {
private TemplateEngine templateEngine;
private HttpAuthRegistry authRegistry;
private SecretService secretService;
private LicenseService licenseService;
private ESLogger logger;
private Settings settings = ImmutableSettings.EMPTY;
@ -126,19 +128,22 @@ public class WatchTests extends ElasticsearchTestCase {
emailService = mock(EmailService.class);
templateEngine = mock(TemplateEngine.class);
secretService = mock(SecretService.class);
licenseService = mock(LicenseService.class);
authRegistry = new HttpAuthRegistry(ImmutableMap.of("basic", (HttpAuthFactory) new BasicAuthFactory(secretService)));
logger = Loggers.getLogger(WatchTests.class);
}
@Test //@Repeat(iterations = 20)
public void testParser_SelfGenerated() throws Exception {
DateTime now = new DateTime(UTC);
ClockMock clock = new ClockMock();
clock.setTime(now);
TransformRegistry transformRegistry = transformRegistry();
boolean includeStatus = randomBoolean();
Schedule schedule = randomSchedule();
Trigger trigger = new ScheduleTrigger(schedule);
ScheduleRegistry scheduleRegistry = registry(schedule);
TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, SystemClock.INSTANCE);
TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, clock);
TriggerService triggerService = new TriggerService(ImmutableSettings.EMPTY, ImmutableSet.of(triggerEngine));
SecretService secretService = new SecretService.PlainText();
@ -155,20 +160,24 @@ public class WatchTests extends ElasticsearchTestCase {
Map<String, Object> metadata = ImmutableMap.<String, Object>of("_key", "_val");
Watch.Status status = new Watch.Status();
ImmutableMap.Builder<String, ActionStatus> actionsStatuses = ImmutableMap.builder();
for (ActionWrapper action : actions) {
actionsStatuses.put(action.id(), new ActionStatus(now));
}
WatchStatus watchStatus = new WatchStatus(actionsStatuses.build());
TimeValue throttlePeriod = randomBoolean() ? null : TimeValue.timeValueSeconds(randomIntBetween(5, 10));
Watch watch = new Watch("_name", SystemClock.INSTANCE, mock(LicenseService.class), trigger, input, condition, transform, actions, metadata, throttlePeriod, status);
Watch watch = new Watch("_name", trigger, input, condition, transform, throttlePeriod, actions, metadata, watchStatus);
BytesReference bytes = XContentFactory.jsonBuilder().value(watch).bytes();
logger.info(bytes.toUtf8());
Watch.Parser watchParser = new Watch.Parser(settings, mock(LicenseService.class), conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, SystemClock.INSTANCE, secretService);
Watch.Parser watchParser = new Watch.Parser(settings, conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, secretService, clock);
Watch parsedWatch = watchParser.parse("_name", includeStatus, bytes);
if (includeStatus) {
assertThat(parsedWatch.status(), equalTo(status));
assertThat(parsedWatch.status(), equalTo(watchStatus));
}
assertThat(parsedWatch.trigger(), equalTo(trigger));
assertThat(parsedWatch.input(), equalTo(input));
@ -182,8 +191,9 @@ public class WatchTests extends ElasticsearchTestCase {
@Test
public void testParser_BadActions() throws Exception {
ClockMock clock = new ClockMock();
ScheduleRegistry scheduleRegistry = registry(randomSchedule());
TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, SystemClock.INSTANCE);
TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, clock);
TriggerService triggerService = new TriggerService(ImmutableSettings.EMPTY, ImmutableSet.of(triggerEngine));
SecretService secretService = new SecretService.PlainText();
ExecutableCondition condition = randomCondition();
@ -197,18 +207,16 @@ public class WatchTests extends ElasticsearchTestCase {
ActionRegistry actionRegistry = registry(actions, transformRegistry);
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
jsonBuilder.startObject();
jsonBuilder.field("actions");
jsonBuilder.startArray();
jsonBuilder.endArray();
jsonBuilder.endObject();
Watch.Parser watchParser = new Watch.Parser(settings, mock(LicenseService.class), conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, SystemClock.INSTANCE, secretService);
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder()
.startObject()
.startArray("actions").endArray()
.endObject();
Watch.Parser watchParser = new Watch.Parser(settings, conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, secretService, clock);
try {
watchParser.parse("failure", false, jsonBuilder.bytes());
fail("This watch should fail to parse as actions is an array");
} catch (WatcherException we) {
assertThat(we.getMessage().contains("could not parse watch [failure]. unexpected token"), is(true));
assertThat(we.getMessage().contains("could not parse actions for watch [failure]"), is(true));
}
}
@ -228,11 +236,11 @@ public class WatchTests extends ElasticsearchTestCase {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
builder.startObject(Watch.Parser.TRIGGER_FIELD.getPreferredName())
builder.startObject(Watch.Field.TRIGGER.getPreferredName())
.field(ScheduleTrigger.TYPE, schedule(schedule).build())
.endObject();
builder.endObject();
Watch.Parser watchParser = new Watch.Parser(settings, mock(LicenseService.class), conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, SystemClock.INSTANCE, secretService);
Watch.Parser watchParser = new Watch.Parser(settings, conditionRegistry, triggerService, transformRegistry, actionRegistry, inputRegistry, secretService, SystemClock.INSTANCE);
Watch watch = watchParser.parse("failure", false, builder.bytes());
assertThat(watch, notNullValue());
assertThat(watch.trigger(), instanceOf(ScheduleTrigger.class));
@ -376,11 +384,11 @@ public class WatchTests extends ElasticsearchTestCase {
if (randomBoolean()) {
ExecutableTransform transform = randomTransform();
EmailAction action = new EmailAction(EmailTemplate.builder().build(), null, null, Profile.STANDARD, randomFrom(DataAttachment.JSON, DataAttachment.YAML, null));
list.add(new ActionWrapper("_email_" + randomAsciiOfLength(8), transform, new ExecutableEmailAction(action, logger, emailService, templateEngine)));
list.add(new ActionWrapper("_email_" + randomAsciiOfLength(8), randomThrottler(), transform, new ExecutableEmailAction(action, logger, emailService, templateEngine)));
}
if (randomBoolean()) {
IndexAction action = new IndexAction("_index", "_type");
list.add(new ActionWrapper("_index_" + randomAsciiOfLength(8), randomTransform(), new ExecutableIndexAction(action, logger, client)));
IndexAction aciton = new IndexAction("_index", "_type");
list.add(new ActionWrapper("_index_" + randomAsciiOfLength(8), randomThrottler(), randomTransform(), new ExecutableIndexAction(aciton, logger, client)));
}
if (randomBoolean()) {
HttpRequestTemplate httpRequest = HttpRequestTemplate.builder("test.host", randomIntBetween(8000, 9000))
@ -388,7 +396,7 @@ public class WatchTests extends ElasticsearchTestCase {
.path(Template.inline("_url").build())
.build();
WebhookAction action = new WebhookAction(httpRequest);
list.add(new ActionWrapper("_webhook_" + randomAsciiOfLength(8), randomTransform(), new ExecutableWebhookAction(action, logger, httpClient, templateEngine)));
list.add(new ActionWrapper("_webhook_" + randomAsciiOfLength(8), randomThrottler(), randomTransform(), new ExecutableWebhookAction(action, logger, httpClient, templateEngine)));
}
return new ExecutableActions(list.build());
}
@ -409,9 +417,12 @@ public class WatchTests extends ElasticsearchTestCase {
break;
}
}
return new ActionRegistry(parsers.build(), transformRegistry);
return new ActionRegistry(parsers.build(), transformRegistry, SystemClock.INSTANCE, licenseService);
}
private ActionThrottler randomThrottler() {
return new ActionThrottler(SystemClock.INSTANCE, randomBoolean() ? null : TimeValue.timeValueMinutes(randomIntBetween(3, 5)), licenseService);
}
static class ParseOnlyScheduleTriggerEngine extends ScheduleTriggerEngine {