From f78bc8dcb2284ac0c3adb51150988062a0fb7211 Mon Sep 17 00:00:00 2001 From: Brian Murphy Date: Thu, 7 May 2015 09:57:30 -0400 Subject: [PATCH] Add DateMath support to ScheduledTriggerEvent This change changes how the ScheduledTriggerEvent is parsed to parse using DateMath instead of just as a date. This will allow the execute API to use such constructs as ``` POST _watcher/watch/test_watch/_execute { "ignore_throttle" : true, "trigger_event" : { "schedule" : { "triggered_time": "now-5h", "scheduled_time": "now" } } } ``` Fixes: elastic/elasticsearch#374 Original commit: elastic/x-pack-elasticsearch@fa286b217e65aaf9d620a7f312508bce8f60016e --- .../rest/action/RestExecuteWatchAction.java | 3 -- .../watcher/support/WatcherDateUtils.java | 53 ++++++++++++++++++- .../schedule/ScheduleTriggerEngine.java | 7 ++- .../schedule/ScheduleTriggerEvent.java | 8 +-- .../SchedulerScheduleTriggerEngine.java | 4 +- .../engine/TickerScheduleTriggerEngine.java | 5 +- .../trigger/ScheduleTriggerEngineMock.java | 7 ++- .../schedule/ScheduleTriggerEventTests.java | 42 +++++++++++++++ .../watcher/watch/WatchTests.java | 9 ++-- 9 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 src/test/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEventTests.java diff --git a/src/main/java/org/elasticsearch/watcher/rest/action/RestExecuteWatchAction.java b/src/main/java/org/elasticsearch/watcher/rest/action/RestExecuteWatchAction.java index fef5868456a..0a3ccfd543a 100644 --- a/src/main/java/org/elasticsearch/watcher/rest/action/RestExecuteWatchAction.java +++ b/src/main/java/org/elasticsearch/watcher/rest/action/RestExecuteWatchAction.java @@ -66,9 +66,6 @@ public class RestExecuteWatchAction extends WatcherRestHandler { ExecuteWatchRequestBuilder executeWatchRequestBuilder = client.prepareExecuteWatch(watchId); TriggerEvent triggerEvent = null; - - - if (request.content() != null && request.content().length() != 0) { XContentParser parser = XContentHelper.createParser(request.content()); parser.nextToken(); diff --git a/src/main/java/org/elasticsearch/watcher/support/WatcherDateUtils.java b/src/main/java/org/elasticsearch/watcher/support/WatcherDateUtils.java index 030f21ae3d3..36a8acc5e36 100644 --- a/src/main/java/org/elasticsearch/watcher/support/WatcherDateUtils.java +++ b/src/main/java/org/elasticsearch/watcher/support/WatcherDateUtils.java @@ -5,8 +5,10 @@ */ package org.elasticsearch.watcher.support; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.time.DateTime; import org.elasticsearch.common.joda.time.DateTimeZone; @@ -14,15 +16,18 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.core.DateFieldMapper; import org.elasticsearch.watcher.WatcherException; +import org.elasticsearch.watcher.support.clock.Clock; import java.io.IOException; - +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; /** * */ public class WatcherDateUtils { public static final FormatDateTimeFormatter dateTimeFormatter = DateFieldMapper.Defaults.DATE_TIME_FORMATTER; + public static final DateMathParser dateMathParser = new DateMathParser(dateTimeFormatter, TimeUnit.SECONDS); private WatcherDateUtils() { } @@ -40,6 +45,35 @@ public class WatcherDateUtils { return dateTimeFormatter.printer().print(date); } + public static DateTime parseDateMath(String fieldName, XContentParser parser, DateTimeZone timeZone, Clock clock) throws IOException { + if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { + throw new ParseException("could not parse date/time expected date field [{}] to not be null but was null", fieldName); + } + return parseDateMathOrNull(fieldName, parser, timeZone, clock); + } + + public static DateTime parseDateMathOrNull(String fieldName, XContentParser parser, DateTimeZone timeZone, Clock clock) throws IOException { + XContentParser.Token token = parser.currentToken(); + if (token == XContentParser.Token.VALUE_NUMBER) { + return new DateTime(parser.longValue(), timeZone); + } + if (token == XContentParser.Token.VALUE_STRING) { + try { + return parseDateMath(parser.text(), timeZone, clock); + } catch (ElasticsearchParseException epe) { + throw new ParseException("could not parse date/time. expected date field [{}] to be either a number or a DateMath string but found [{}] instead", epe, fieldName, parser.text()); + } + } + if (token == XContentParser.Token.VALUE_NULL) { + return null; + } + throw new ParseException("could not parse date/time. expected date field [{}] to be either a number or a string but found [{}] instead", fieldName, token); + } + + public static DateTime parseDateMath(String valueString, DateTimeZone timeZone, final Clock clock) { + return new DateTime(dateMathParser.parse(valueString, new ClockNowCallable(clock)), timeZone); + } + public static DateTime parseDate(String fieldName, XContentParser parser, DateTimeZone timeZone) throws IOException { XContentParser.Token token = parser.currentToken(); if (token == XContentParser.Token.VALUE_NUMBER) { @@ -80,8 +114,25 @@ public class WatcherDateUtils { } public static class ParseException extends WatcherException { + public ParseException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + public ParseException(String msg, Object... args) { super(msg, args); } } + + private static class ClockNowCallable implements Callable { + private final Clock clock; + + ClockNowCallable(Clock clock){ + this.clock = clock; + } + + @Override + public Long call() throws Exception { + return clock.millis(); + } + } } diff --git a/src/main/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEngine.java b/src/main/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEngine.java index 037efb2e274..40fb94eb0ae 100644 --- a/src/main/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEngine.java +++ b/src/main/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEngine.java @@ -7,6 +7,7 @@ package org.elasticsearch.watcher.trigger.schedule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.watcher.support.clock.Clock; import org.elasticsearch.watcher.trigger.AbstractTriggerEngine; import org.elasticsearch.watcher.trigger.TriggerService; @@ -20,10 +21,12 @@ public abstract class ScheduleTriggerEngine extends AbstractTriggerEngine schedules; private Ticker ticker; @Inject public TickerScheduleTriggerEngine(Settings settings, ScheduleRegistry scheduleRegistry, Clock clock) { - super(settings, scheduleRegistry); + super(settings, scheduleRegistry, clock); this.tickInterval = settings.getAsTime("watcher.trigger.schedule.ticker.tick_interval", TimeValue.timeValueMillis(500)); this.schedules = new ConcurrentHashMap<>(); - this.clock = clock; } @Override diff --git a/src/test/java/org/elasticsearch/watcher/trigger/ScheduleTriggerEngineMock.java b/src/test/java/org/elasticsearch/watcher/trigger/ScheduleTriggerEngineMock.java index ffe0ab2f038..71d3e82f2cc 100644 --- a/src/test/java/org/elasticsearch/watcher/trigger/ScheduleTriggerEngineMock.java +++ b/src/test/java/org/elasticsearch/watcher/trigger/ScheduleTriggerEngineMock.java @@ -34,13 +34,12 @@ public class ScheduleTriggerEngineMock extends ScheduleTriggerEngine { private final ESLogger logger; private final ConcurrentMap jobs = new ConcurrentHashMap<>(); - private final Clock clock; @Inject public ScheduleTriggerEngineMock(Settings settings, ScheduleRegistry scheduleRegistry, Clock clock) { - super(settings, scheduleRegistry); + super(settings, scheduleRegistry, clock); this.logger = Loggers.getLogger(ScheduleTriggerEngineMock.class, settings); - this.clock = clock; + } @Override @@ -50,7 +49,7 @@ public class ScheduleTriggerEngineMock extends ScheduleTriggerEngine { @Override public ScheduleTriggerEvent parseTriggerEvent(TriggerService service, String watchId, String context, XContentParser parser) throws IOException { - return ScheduleTriggerEvent.parse(watchId, context, parser); + return ScheduleTriggerEvent.parse(parser, watchId, context, clock); } @Override diff --git a/src/test/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEventTests.java b/src/test/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEventTests.java new file mode 100644 index 00000000000..e8133e36f7c --- /dev/null +++ b/src/test/java/org/elasticsearch/watcher/trigger/schedule/ScheduleTriggerEventTests.java @@ -0,0 +1,42 @@ +/* + * 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.trigger.schedule; + +import com.carrotsearch.randomizedtesting.annotations.Repeat; +import org.elasticsearch.common.xcontent.XContentBuilder; +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.support.clock.SystemClock; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; + +/** + */ +public class ScheduleTriggerEventTests extends ElasticsearchTestCase { + + @Test + @Repeat(iterations = 10) + public void testParser_RandomDateMath() throws Exception { + String triggeredTime = randomFrom("now", "now+5m", "2015-05-07T22:24:41.254Z", "2015-05-07T22:24:41.254Z||-5m"); + String scheduledTime = randomFrom("now", "now-5m", "2015-05-07T22:24:41.254Z", "2015-05-07T22:24:41.254Z||+5h"); + XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); + jsonBuilder.startObject(); + jsonBuilder.field(ScheduleTriggerEvent.Field.SCHEDULED_TIME.getPreferredName(), scheduledTime); + jsonBuilder.field(ScheduleTriggerEvent.Field.TRIGGERED_TIME.getPreferredName(), triggeredTime); + jsonBuilder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes()); + parser.nextToken(); + + ScheduleTriggerEvent scheduleTriggerEvent = ScheduleTriggerEvent.parse(parser, "_id", "_context", SystemClock.INSTANCE); + assertThat(scheduleTriggerEvent.scheduledTime().isAfter(0), is(true)); + assertThat(scheduleTriggerEvent.triggeredTime().isAfter(0), is(true)); + } + +} diff --git a/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java b/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java index 926f4fcf8ac..9789b277e47 100644 --- a/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java +++ b/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java @@ -57,6 +57,7 @@ import org.elasticsearch.watcher.input.simple.SimpleInputFactory; 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.SystemClock; import org.elasticsearch.watcher.support.http.HttpClient; import org.elasticsearch.watcher.support.http.HttpMethod; @@ -132,7 +133,7 @@ public class WatchTests extends ElasticsearchTestCase { Schedule schedule = randomSchedule(); Trigger trigger = new ScheduleTrigger(schedule); ScheduleRegistry scheduleRegistry = registry(schedule); - TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry); + TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, SystemClock.INSTANCE); TriggerService triggerService = new TriggerService(ImmutableSettings.EMPTY, ImmutableSet.of(triggerEngine)); SecretService secretService = new SecretService.PlainText(); @@ -178,7 +179,7 @@ public class WatchTests extends ElasticsearchTestCase { @Test public void testParser_BadActions() throws Exception { ScheduleRegistry scheduleRegistry = registry(randomSchedule()); - TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry); + TriggerEngine triggerEngine = new ParseOnlyScheduleTriggerEngine(ImmutableSettings.EMPTY, scheduleRegistry, SystemClock.INSTANCE); TriggerService triggerService = new TriggerService(ImmutableSettings.EMPTY, ImmutableSet.of(triggerEngine)); SecretService secretService = new SecretService.PlainText(); ExecutableCondition condition = randomCondition(); @@ -374,8 +375,8 @@ public class WatchTests extends ElasticsearchTestCase { static class ParseOnlyScheduleTriggerEngine extends ScheduleTriggerEngine { - public ParseOnlyScheduleTriggerEngine(Settings settings, ScheduleRegistry registry) { - super(settings, registry); + public ParseOnlyScheduleTriggerEngine(Settings settings, ScheduleRegistry registry, Clock clock) { + super(settings, registry, clock); } @Override