diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java index 349ad06991d..82f4722daa5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java @@ -142,7 +142,6 @@ import org.elasticsearch.xpack.watcher.trigger.schedule.engine.SchedulerSchedule import org.elasticsearch.xpack.watcher.trigger.schedule.engine.TickerScheduleTriggerEngine; import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchLockService; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -255,13 +254,10 @@ public class Watcher implements ActionPlugin, ScriptPlugin { final InputRegistry inputRegistry = new InputRegistry(settings, inputFactories); inputFactories.put(ChainInput.TYPE, new ChainInputFactory(settings, inputRegistry)); - // TODO replace internal client where needed, so we can remove ctors final WatcherClientProxy watcherClientProxy = new WatcherClientProxy(settings, internalClient); - final WatcherClient watcherClient = new WatcherClient(internalClient); final HistoryStore historyStore = new HistoryStore(settings, watcherClientProxy); - final Set scheduleParsers = new HashSet<>(); scheduleParsers.add(new CronSchedule.Parser()); scheduleParsers.add(new DailySchedule.Parser()); @@ -288,10 +284,9 @@ public class Watcher implements ActionPlugin, ScriptPlugin { final WatchLockService watchLockService = new WatchLockService(settings); final WatchExecutor watchExecutor = getWatchExecutor(threadPool); final Watch.Parser watchParser = new Watch.Parser(settings, triggerService, registry, inputRegistry, cryptoService, clock); - final WatchStore watchStore = new WatchStore(settings, watcherClientProxy, watchParser); final ExecutionService executionService = new ExecutionService(settings, historyStore, triggeredWatchStore, watchExecutor, - watchStore, watchLockService, clock, threadPool); + watchLockService, clock, threadPool, watchParser, watcherClientProxy); final TriggerEngine.Listener triggerEngineListener = getTriggerEngineListener(executionService); triggerService.register(triggerEngineListener); @@ -299,15 +294,15 @@ public class Watcher implements ActionPlugin, ScriptPlugin { final WatcherIndexTemplateRegistry watcherIndexTemplateRegistry = new WatcherIndexTemplateRegistry(settings, clusterService.getClusterSettings(), clusterService, threadPool, internalClient); - final WatcherService watcherService = new WatcherService(settings, clock, triggerService, watchStore, - watchParser, executionService, watchLockService, watcherIndexTemplateRegistry); + final WatcherService watcherService = new WatcherService(settings, triggerService, executionService, watchLockService, + watcherIndexTemplateRegistry, watchParser, watcherClientProxy); final WatcherLifeCycleService watcherLifeCycleService = new WatcherLifeCycleService(settings, threadPool, clusterService, watcherService); return Arrays.asList(registry, watcherClient, inputRegistry, historyStore, triggerService, triggeredWatchParser, - watcherLifeCycleService, executionService, watchStore, triggerEngineListener, watcherService, watchParser, - configuredTriggerEngine, triggeredWatchStore, watcherSearchTemplateService); + watcherLifeCycleService, executionService, triggerEngineListener, watcherService, watchParser, + configuredTriggerEngine, triggeredWatchStore, watcherSearchTemplateService, watcherClientProxy); } protected TriggerEngine getTriggerEngine(Clock clock, ScheduleRegistry scheduleRegistry) { @@ -441,7 +436,7 @@ public class Watcher implements ActionPlugin, ScriptPlugin { String errorMessage = LoggerMessageFormat.format("the [action.auto_create_index] setting value [{}] is too" + " restrictive. disable [action.auto_create_index] or set it to " + - "[{}, {}, {}*]", (Object) value, WatchStore.INDEX, TriggeredWatchStore.INDEX_NAME, HistoryStore.INDEX_PREFIX); + "[{}, {}, {}*]", (Object) value, Watch.INDEX, TriggeredWatchStore.INDEX_NAME, HistoryStore.INDEX_PREFIX); if (Booleans.isExplicitFalse(value)) { throw new IllegalArgumentException(errorMessage); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleService.java index 0447f612448..3315b490d27 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleService.java @@ -22,7 +22,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import java.util.concurrent.CountDownLatch; @@ -130,15 +130,15 @@ public class WatcherLifeCycleService extends AbstractComponent implements Cluste threadPool.executor(ThreadPool.Names.GENERIC).execute(() -> start(state, false)); } else { boolean isWatchIndexDeleted = event.indicesDeleted().stream() - .filter(index -> WatchStore.INDEX.equals(index.getName())) + .filter(index -> Watch.INDEX.equals(index.getName())) .findAny() .isPresent(); - boolean isWatchIndexOpenInPreviousClusterState = event.previousState().metaData().hasIndex(WatchStore.INDEX) && - event.previousState().metaData().index(WatchStore.INDEX).getState() == IndexMetaData.State.OPEN; - boolean isWatchIndexClosedInCurrentClusterState = event.state().metaData().hasIndex(WatchStore.INDEX) && - event.state().metaData().index(WatchStore.INDEX).getState() == IndexMetaData.State.CLOSE; - boolean hasWatcherIndexBeenClosed = isWatchIndexOpenInPreviousClusterState && isWatchIndexClosedInCurrentClusterState; + final boolean isWatchIndexOpenInPreviousClusterState = event.previousState().metaData().hasIndex(Watch.INDEX) && + event.previousState().metaData().index(Watch.INDEX).getState() == IndexMetaData.State.OPEN; + final boolean isWatchIndexClosedInCurrentClusterState = event.state().metaData().hasIndex(Watch.INDEX) && + event.state().metaData().index(Watch.INDEX).getState() == IndexMetaData.State.CLOSE; + final boolean hasWatcherIndexBeenClosed = isWatchIndexOpenInPreviousClusterState && isWatchIndexClosedInCurrentClusterState; if (isWatchIndexDeleted || hasWatcherIndexBeenClosed) { threadPool.executor(ThreadPool.Names.GENERIC).execute(() -> watcherService.watchIndexDeletedOrClosed()); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherService.java index fcf528ef55f..9c3eff4c556 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/WatcherService.java @@ -6,57 +6,67 @@ package org.elasticsearch.xpack.watcher; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.util.Supplier; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchTimeoutException; -import org.elasticsearch.action.DocWriteResponse; -import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.engine.VersionConflictEngineException; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.xpack.watcher.execution.ExecutionService; import org.elasticsearch.xpack.watcher.support.WatcherIndexTemplateRegistry; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.trigger.TriggerService; import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchLockService; -import org.elasticsearch.xpack.watcher.watch.WatchStatus; -import org.elasticsearch.xpack.watcher.watch.WatchStore; -import org.joda.time.DateTime; +import org.elasticsearch.xpack.watcher.watch.WatchStoreUtils; -import java.io.IOException; -import java.time.Clock; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalArgument; import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalState; -import static org.elasticsearch.xpack.watcher.support.Exceptions.ioException; -import static org.joda.time.DateTimeZone.UTC; +import static org.elasticsearch.xpack.watcher.watch.Watch.DOC_TYPE; +import static org.elasticsearch.xpack.watcher.watch.Watch.INDEX; public class WatcherService extends AbstractComponent { - private final Clock clock; private final TriggerService triggerService; - private final Watch.Parser watchParser; - private final WatchStore watchStore; private final WatchLockService watchLockService; private final ExecutionService executionService; private final WatcherIndexTemplateRegistry watcherIndexTemplateRegistry; // package-private for testing final AtomicReference state = new AtomicReference<>(WatcherState.STOPPED); + private final TimeValue scrollTimeout; + private final int scrollSize; + private final Watch.Parser parser; + private final WatcherClientProxy client; - public WatcherService(Settings settings, Clock clock, TriggerService triggerService, WatchStore watchStore, - Watch.Parser watchParser, ExecutionService executionService, WatchLockService watchLockService, - WatcherIndexTemplateRegistry watcherIndexTemplateRegistry) { + public WatcherService(Settings settings, TriggerService triggerService, + ExecutionService executionService, WatchLockService watchLockService, + WatcherIndexTemplateRegistry watcherIndexTemplateRegistry, Watch.Parser parser, WatcherClientProxy client) { super(settings); - this.clock = clock; this.triggerService = triggerService; - this.watchStore = watchStore; - this.watchParser = watchParser; this.watchLockService = watchLockService; this.executionService = executionService; this.watcherIndexTemplateRegistry = watcherIndexTemplateRegistry; + this.scrollTimeout = settings.getAsTime("xpack.watcher.watch.scroll.timeout", TimeValue.timeValueSeconds(30)); + this.scrollSize = settings.getAsInt("xpack.watcher.watch.scroll.size", 100); + this.parser = parser; + this.client = client; } public void start(ClusterState clusterState) throws Exception { @@ -65,12 +75,9 @@ public class WatcherService extends AbstractComponent { logger.debug("starting watch service..."); watcherIndexTemplateRegistry.addTemplatesIfMissing(); watchLockService.start(); - - // Try to load watch store before the execution service, b/c action depends on watch store - watchStore.start(clusterState); executionService.start(clusterState); + triggerService.start(loadWatches(clusterState)); - triggerService.start(watchStore.activeWatches()); state.set(WatcherState.STARTED); logger.debug("watch service has started"); } catch (Exception e) { @@ -83,7 +90,7 @@ public class WatcherService extends AbstractComponent { } public boolean validate(ClusterState state) { - return watchStore.validate(state) && executionService.validate(state); + return executionService.validate(state); } public void stop() { @@ -96,7 +103,6 @@ public class WatcherService extends AbstractComponent { } catch (ElasticsearchTimeoutException te) { logger.warn("error stopping WatchLockService", te); } - watchStore.stop(); state.set(WatcherState.STOPPED); logger.debug("watch service has stopped"); } else { @@ -104,151 +110,74 @@ public class WatcherService extends AbstractComponent { } } - public WatchStore.WatchDelete deleteWatch(String id) { - ensureStarted(); - WatchStore.WatchDelete delete = watchStore.delete(id); - if (delete.deleteResponse().getResult() == DocWriteResponse.Result.DELETED) { - triggerService.remove(id); - } - return delete; - } - - public IndexResponse putWatch(String id, BytesReference watchSource, boolean active) throws IOException { - ensureStarted(); - DateTime now = new DateTime(clock.millis(), UTC); - Watch watch = watchParser.parseWithSecrets(id, false, watchSource, now); - watch.setState(active, now); - WatchStore.WatchPut result = watchStore.put(watch); - - if (result.previous() == null) { - // this is a newly created watch, so we only need to schedule it if it's active - if (result.current().status().state().isActive()) { - triggerService.add(result.current()); - } - - } else if (result.current().status().state().isActive()) { - - if (!result.previous().status().state().isActive()) { - // the replaced watch was inactive, which means it wasn't scheduled. The new watch is active - // so we need to schedule it - triggerService.add(result.current()); - - } else if (!result.previous().trigger().equals(result.current().trigger())) { - // the previous watch was active and its schedule is different than the schedule of the - // new watch, so we need to - triggerService.add(result.current()); - } - } else { - // if the current is inactive, we'll just remove it from the trigger service - // just to be safe - triggerService.remove(result.current().id()); - } - return result.indexResponse(); - } - /** - * TODO: add version, fields, etc support that the core get api has as well. + * This reads all watches from the .watches index/alias and puts them into memory for a short period of time, + * before they are fed into the trigger service. + * + * This is only invoked when a node becomes master, so either on start up or when a master node switches - while watcher is started up */ - public Watch getWatch(String name) { - return watchStore.get(name); + private Collection loadWatches(ClusterState clusterState) { + IndexMetaData indexMetaData = WatchStoreUtils.getConcreteIndex(INDEX, clusterState.metaData()); + + // no index exists, all good, we can start + if (indexMetaData == null) { + return Collections.emptyList(); + } + + RefreshResponse refreshResponse = client.refresh(new RefreshRequest(INDEX)); + if (refreshResponse.getSuccessfulShards() < indexMetaData.getNumberOfShards()) { + throw illegalState("not all required shards have been refreshed"); + } + + List watches = new ArrayList<>(); + SearchRequest searchRequest = new SearchRequest(INDEX) + .types(DOC_TYPE) + .scroll(scrollTimeout) + .source(new SearchSourceBuilder() + .size(scrollSize) + .sort(SortBuilders.fieldSort("_doc")) + .version(true)); + SearchResponse response = client.search(searchRequest, null); + try { + if (response.getTotalShards() != response.getSuccessfulShards()) { + throw new ElasticsearchException("Partial response while loading watches"); + } + + while (response.getHits().hits().length != 0) { + for (SearchHit hit : response.getHits()) { + String id = hit.getId(); + try { + Watch watch = parser.parse(id, true, hit.getSourceRef()); + watch.version(hit.version()); + watches.add(watch); + } catch (Exception e) { + logger.error((Supplier) () -> new ParameterizedMessage("couldn't load watch [{}], ignoring it...", id), e); + } + } + response = client.searchScroll(response.getScrollId(), scrollTimeout); + } + } finally { + client.clearScroll(response.getScrollId()); + } + return watches; } + + public WatcherState state() { return state.get(); } - /** - * Acks the watch if needed - */ - public WatchStatus ackWatch(String id, String[] actionIds) throws IOException { - ensureStarted(); - if (actionIds == null || actionIds.length == 0) { - actionIds = new String[] { Watch.ALL_ACTIONS_ID }; - } - Watch watch = watchStore.get(id); - if (watch == null) { - throw illegalArgument("watch [{}] does not exist", id); - } - // we need to create a safe copy of the status - if (watch.ack(new DateTime(clock.millis(), UTC), actionIds)) { - try { - watchStore.updateStatus(watch); - } catch (IOException ioe) { - throw ioException("failed to update the watch [{}] on ack", ioe, watch.id()); - } catch (VersionConflictEngineException vcee) { - throw illegalState("failed to update the watch [{}] on ack, perhaps it was force deleted", vcee, watch.id()); - } - } - return new WatchStatus(watch.status()); - } - - public WatchStatus activateWatch(String id) throws IOException { - return setWatchState(id, true); - } - - public WatchStatus deactivateWatch(String id) throws IOException { - return setWatchState(id, false); - } - - WatchStatus setWatchState(String id, boolean active) throws IOException { - ensureStarted(); - // for now, when a watch is deactivated we don't remove its runtime representation - // that is, the store will still keep the watch in memory. We only mark the watch - // as inactive (both in runtime and also update the watch in the watches index) - // and remove the watch from the trigger service, such that it will not be triggered - // nor its trigger be evaluated. - // - // later on we can consider removing the watch runtime representation from memory - // as well. This will mean that the in-memory loaded watches will no longer be a - // complete representation of the watches in the index. This requires careful thought - // to make sure, such incompleteness doesn't hurt any other part of watcher (we need - // to run this exercise anyway... and make sure that nothing in watcher relies on the - // fact that the watch store holds all watches in memory. - - Watch watch = watchStore.get(id); - if (watch == null) { - throw illegalArgument("watch [{}] does not exist", id); - } - if (watch.setState(active, new DateTime(clock.millis(), UTC))) { - try { - watchStore.updateStatus(watch); - if (active) { - triggerService.add(watch); - } else { - triggerService.remove(watch.id()); - } - } catch (IOException ioe) { - throw ioException("failed to update the watch [{}] on ack", ioe, watch.id()); - } catch (VersionConflictEngineException vcee) { - throw illegalState("failed to update the watch [{}] on ack, perhaps it was force deleted", vcee, watch.id()); - } - } - // we need to create a safe copy of the status - return new WatchStatus(watch.status()); - } - - public long watchesCount() { - return watchStore.watches().size(); - } - - private void ensureStarted() { - if (state.get() != WatcherState.STARTED) { - throw new IllegalStateException("not started"); - } - } - public Map usageStats() { Map innerMap = executionService.usageStats(); - innerMap.putAll(watchStore.usageStats()); return innerMap; } /** - * Something deleted or closed the {@link WatchStore#INDEX} and thus we need to do some cleanup to prevent further execution of watches + * Something deleted or closed the {@link Watch#INDEX} and thus we need to do some cleanup to prevent further execution of watches * as those watches cannot be updated anymore */ public void watchIndexDeletedOrClosed() { - watchStore.clearWatchesInMemory(); executionService.clearExecutions(); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionStatus.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionStatus.java index 4d2cf2732f5..ec37f5e6d5b 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionStatus.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/ActionStatus.java @@ -74,11 +74,7 @@ public class ActionStatus implements ToXContent { @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; + return Objects.hash(ackStatus, lastExecution, lastSuccessfulExecution, lastThrottle); } public void update(DateTime timestamp, Action.Result result) { @@ -238,15 +234,12 @@ public class ActionStatus implements ToXContent { AckStatus ackStatus = (AckStatus) o; - if (!timestamp.equals(ackStatus.timestamp)) return false; - return state == ackStatus.state; + return Objects.equals(timestamp, ackStatus.timestamp) && Objects.equals(state, ackStatus.state); } @Override public int hashCode() { - int result = timestamp.hashCode(); - result = 31 * result + state.hashCode(); - return result; + return Objects.hash(timestamp, state); } @Override diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/email/EmailAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/email/EmailAction.java index 52476186834..36df738f784 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/email/EmailAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/actions/email/EmailAction.java @@ -11,10 +11,7 @@ import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.xpack.watcher.actions.Action; import org.elasticsearch.xpack.common.secret.Secret; -import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams; -import org.elasticsearch.xpack.watcher.support.xcontent.WatcherXContentParser; import org.elasticsearch.xpack.notification.email.Authentication; import org.elasticsearch.xpack.notification.email.DataAttachment; import org.elasticsearch.xpack.notification.email.Email; @@ -22,6 +19,9 @@ import org.elasticsearch.xpack.notification.email.EmailTemplate; import org.elasticsearch.xpack.notification.email.Profile; import org.elasticsearch.xpack.notification.email.attachment.EmailAttachments; import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentsParser; +import org.elasticsearch.xpack.watcher.actions.Action; +import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams; +import org.elasticsearch.xpack.watcher.support.xcontent.WatcherXContentParser; import java.io.IOException; import java.util.Locale; @@ -105,7 +105,7 @@ public class EmailAction implements Action { } if (auth != null) { builder.field(Field.USER.getPreferredName(), auth.user()); - if (!WatcherParams.hideSecrets(params)) { + if (WatcherParams.hideSecrets(params) == false) { builder.field(Field.PASSWORD.getPreferredName(), auth.password(), params); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/condition/ScriptCondition.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/condition/ScriptCondition.java index 2ebe2d34279..351c36a83c5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/condition/ScriptCondition.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/condition/ScriptCondition.java @@ -33,19 +33,20 @@ public final class ScriptCondition extends Condition { private final ScriptService scriptService; private final Script script; + private final CompiledScript compiledScript; public ScriptCondition(Script script) { super(TYPE); this.script = script; scriptService = null; + compiledScript = null; } ScriptCondition(Script script, ScriptService scriptService) { super(TYPE); this.scriptService = scriptService; this.script = script; - // try to compile so we catch syntax errors early - scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap()); + compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap()); } public Script getScript() { @@ -72,7 +73,6 @@ public final class ScriptCondition extends Condition { if (script.getParams() != null && !script.getParams().isEmpty()) { parameters.putAll(script.getParams()); } - CompiledScript compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap()); ExecutableScript executable = scriptService.executable(compiledScript, parameters); Object value = executable.run(); if (value instanceof Boolean) { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java index 2880385780a..b9120b1f4b6 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/ExecutionService.java @@ -7,9 +7,10 @@ package org.elasticsearch.xpack.watcher.execution; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; -import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.lease.Releasable; import org.elasticsearch.common.metrics.MeanMetric; @@ -25,13 +26,15 @@ import org.elasticsearch.xpack.watcher.condition.Condition; import org.elasticsearch.xpack.watcher.history.HistoryStore; import org.elasticsearch.xpack.watcher.history.WatchRecord; import org.elasticsearch.xpack.watcher.input.Input; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.transform.Transform; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchLockService; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.WatchStoreUtils; import org.joda.time.DateTime; +import java.io.IOException; import java.time.Clock; import java.util.ArrayList; import java.util.BitSet; @@ -39,7 +42,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -58,28 +60,31 @@ public class ExecutionService extends AbstractComponent { private final HistoryStore historyStore; private final TriggeredWatchStore triggeredWatchStore; private final WatchExecutor executor; - private final WatchStore watchStore; private final WatchLockService watchLockService; private final Clock clock; private final TimeValue defaultThrottlePeriod; private final TimeValue maxStopTimeout; private final ThreadPool threadPool; + private final Watch.Parser parser; + private final WatcherClientProxy client; private volatile CurrentExecutions currentExecutions; private final AtomicBoolean started = new AtomicBoolean(false); public ExecutionService(Settings settings, HistoryStore historyStore, TriggeredWatchStore triggeredWatchStore, WatchExecutor executor, - WatchStore watchStore, WatchLockService watchLockService, Clock clock, ThreadPool threadPool) { + WatchLockService watchLockService, Clock clock, ThreadPool threadPool, Watch.Parser parser, + WatcherClientProxy client) { super(settings); this.historyStore = historyStore; this.triggeredWatchStore = triggeredWatchStore; this.executor = executor; - this.watchStore = watchStore; this.watchLockService = watchLockService; this.clock = clock; this.defaultThrottlePeriod = DEFAULT_THROTTLE_PERIOD_SETTING.get(settings); this.maxStopTimeout = Watcher.MAX_STOP_TIMEOUT_SETTING.get(settings); this.threadPool = threadPool; + this.parser = parser; + this.client = client; } public void start(ClusterState state) throws Exception { @@ -105,7 +110,16 @@ public class ExecutionService extends AbstractComponent { } public boolean validate(ClusterState state) { - return triggeredWatchStore.validate(state); + boolean triggeredWatchStoreReady = triggeredWatchStore.validate(state); + try { + IndexMetaData indexMetaData = WatchStoreUtils.getConcreteIndex(Watch.INDEX, state.metaData()); + if (indexMetaData != null) { + return triggeredWatchStoreReady && state.routingTable().index(indexMetaData.getIndex()).allPrimaryShardsActive(); + } + } catch (Exception e) { + return false; + } + return triggeredWatchStoreReady; } public void stop() { @@ -171,42 +185,40 @@ public class ExecutionService extends AbstractComponent { if (!started.get()) { throw new IllegalStateException("not started"); } - final LinkedList triggeredWatches = new LinkedList<>(); - final LinkedList contexts = new LinkedList<>(); - + final List triggeredWatches = new ArrayList<>(); + final List contexts = new ArrayList<>(); DateTime now = new DateTime(clock.millis(), UTC); - for (TriggerEvent event : events) { - Watch watch = watchStore.get(event.jobName()); - if (watch == null) { - 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, defaultThrottlePeriod); - contexts.add(ctx); - triggeredWatches.add(new TriggeredWatch(ctx.id(), event)); - } - logger.debug("saving watch records [{}]", triggeredWatches.size()); - - triggeredWatchStore.putAll(triggeredWatches, new ActionListener() { - @Override - public void onResponse(BitSet slots) { - int slot = 0; - while ((slot = slots.nextSetBit(slot)) != -1) { - executeAsync(contexts.get(slot), triggeredWatches.get(slot)); - slot++; - } - } - - @Override - public void onFailure(Exception e) { - Throwable cause = ExceptionsHelper.unwrapCause(e); - if (cause instanceof EsRejectedExecutionException) { - logger.debug("failed to store watch records due to overloaded threadpool [{}]", ExceptionsHelper.detailedMessage(e)); + threadPool.generic().execute(() -> { + for (TriggerEvent event : events) { + GetResponse response = client.getWatch(event.jobName()); + if (response.isExists() == false) { + logger.warn("unable to find watch [{}] in watch index, perhaps it has been deleted", event.jobName()); } else { - logger.warn("failed to store watch records", e); + try { + Watch watch = parser.parseWithSecrets(response.getId(), true, response.getSourceAsBytesRef(), now); + TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event, defaultThrottlePeriod); + contexts.add(ctx); + triggeredWatches.add(new TriggeredWatch(ctx.id(), event)); + } catch (IOException e) { + logger.warn("unable to parse watch [{}]", event.jobName()); + } } } + + if (triggeredWatches.isEmpty() == false) { + logger.debug("saving triggered [{}] watches", triggeredWatches.size()); + + triggeredWatchStore.putAll(triggeredWatches, ActionListener.wrap( + (slots) -> { + int slot = 0; + while ((slot = slots.nextSetBit(slot)) != -1) { + executeAsync(contexts.get(slot), triggeredWatches.get(slot)); + slot++; + } + }, + (e) -> logger.warn("failed to store watch [] records", e))); + } }); } @@ -214,31 +226,30 @@ public class ExecutionService extends AbstractComponent { if (!started.get()) { throw new IllegalStateException("not started"); } - final LinkedList triggeredWatches = new LinkedList<>(); - final LinkedList contexts = new LinkedList<>(); + final List triggeredWatches = new ArrayList<>(); + final List contexts = new ArrayList<>(); DateTime now = new DateTime(clock.millis(), UTC); for (TriggerEvent event : events) { - Watch watch = watchStore.get(event.jobName()); - if (watch == null) { - logger.warn("unable to find watch [{}] in the watch store, perhaps it has been deleted", event.jobName()); + GetResponse response = client.getWatch(event.jobName()); + if (response.isExists() == false) { + logger.warn("unable to find watch [{}] in watch index, perhaps it has been deleted", event.jobName()); continue; } + Watch watch = parser.parseWithSecrets(response.getId(), true, response.getSourceAsBytesRef(), now); TriggeredExecutionContext ctx = new TriggeredExecutionContext(watch, now, event, defaultThrottlePeriod); contexts.add(ctx); triggeredWatches.add(new TriggeredWatch(ctx.id(), event)); } - logger.debug("saving watch records [{}]", triggeredWatches.size()); - if (triggeredWatches.size() == 0) { - return; - } - - BitSet slots = triggeredWatchStore.putAll(triggeredWatches); - int slot = 0; - while ((slot = slots.nextSetBit(slot)) != -1) { - executeAsync(contexts.get(slot), triggeredWatches.get(slot)); - slot++; + if (triggeredWatches.isEmpty() == false) { + logger.debug("saving triggered [{}] watches", triggeredWatches.size()); + BitSet slots = triggeredWatchStore.putAll(triggeredWatches); + int slot = 0; + while ((slot = slots.nextSetBit(slot)) != -1) { + executeAsync(contexts.get(slot), triggeredWatches.get(slot)); + slot++; + } } } @@ -250,7 +261,10 @@ public class ExecutionService extends AbstractComponent { } try { currentExecutions.put(ctx.watch().id(), new WatchExecution(ctx, Thread.currentThread())); - if (ctx.knownWatch() && watchStore.get(ctx.watch().id()) == null) { + final AtomicBoolean watchExists = new AtomicBoolean(true); + client.getWatch(ctx.watch().id(), ActionListener.wrap((r) -> watchExists.set(r.isExists()), (e) -> watchExists.set(false))); + + if (ctx.knownWatch() && watchExists.get() == false) { // fail fast if we are trying to execute a deleted watch String message = "unable to find watch for record [" + ctx.id() + "], perhaps it has been deleted, ignoring..."; logger.warn("{}", message); @@ -261,7 +275,7 @@ public class ExecutionService extends AbstractComponent { record = executeInner(ctx); if (ctx.recordExecution()) { - watchStore.updateStatus(ctx.watch()); + client.updateWatchStatus(ctx.watch()); } } } catch (Exception e) { @@ -330,33 +344,28 @@ public class ExecutionService extends AbstractComponent { try { executor.execute(new WatchExecutionTask(ctx)); } catch (EsRejectedExecutionException e) { - // we are still in the transport thread here most likely, so we cannot run heavy operations - // this means some offloading needs to be done for indexing into the history and delete the triggered watches entry - threadPool.generic().execute(() -> { - String message = "failed to run triggered watch [" + triggeredWatch.id() + "] due to thread pool capacity"; - logger.debug("{}", message); - WatchRecord record = ctx.abortBeforeExecution(ExecutionState.FAILED, message); - try { - if (ctx.overrideRecordOnConflict()) { - historyStore.forcePut(record); - } else { - historyStore.put(record); - } - } catch (Exception exc) { - logger.error((Supplier) () -> - new ParameterizedMessage("Error storing watch history record for watch [{}] after thread pool rejection", - triggeredWatch.id()), exc); + String message = "failed to run triggered watch [" + triggeredWatch.id() + "] due to thread pool capacity"; + WatchRecord record = ctx.abortBeforeExecution(ExecutionState.FAILED, message); + try { + if (ctx.overrideRecordOnConflict()) { + historyStore.forcePut(record); + } else { + historyStore.put(record); } + } catch (Exception exc) { + logger.error((Supplier) () -> + new ParameterizedMessage("Error storing watch history record for watch [{}] after thread pool rejection", + triggeredWatch.id()), exc); + } - try { - triggeredWatchStore.delete(triggeredWatch.id()); - } catch (Exception exc) { - logger.error((Supplier) () -> - new ParameterizedMessage("Error deleting triggered watch store record for watch [{}] after thread pool " + - "rejection", triggeredWatch.id()), exc); - } - }); - } + try { + triggeredWatchStore.delete(triggeredWatch.id()); + } catch (Exception exc) { + logger.error((Supplier) () -> + new ParameterizedMessage("Error deleting triggered watch store record for watch [{}] after thread pool " + + "rejection", triggeredWatch.id()), exc); + } + }; } WatchRecord executeInner(WatchExecutionContext ctx) { @@ -417,8 +426,8 @@ public class ExecutionService extends AbstractComponent { assert triggeredWatches != null; int counter = 0; for (TriggeredWatch triggeredWatch : triggeredWatches) { - Watch watch = watchStore.get(triggeredWatch.id().watchId()); - if (watch == null) { + GetResponse response = client.getWatch(triggeredWatch.id().watchId()); + if (response.isExists() == false) { String message = "unable to find watch for record [" + triggeredWatch.id().watchId() + "]/[" + triggeredWatch.id() + "], perhaps it has been deleted, ignoring..."; WatchRecord record = new WatchRecord.MessageWatchRecord(triggeredWatch.id(), triggeredWatch.triggerEvent(), @@ -426,8 +435,10 @@ public class ExecutionService extends AbstractComponent { historyStore.forcePut(record); triggeredWatchStore.delete(triggeredWatch.id()); } else { - TriggeredExecutionContext ctx = new StartupExecutionContext(watch, new DateTime(clock.millis(), UTC), - triggeredWatch.triggerEvent(), defaultThrottlePeriod); + DateTime now = new DateTime(clock.millis(), UTC); + Watch watch = parser.parseWithSecrets(response.getId(), true, response.getSourceAsBytesRef(), now); + TriggeredExecutionContext ctx = + new StartupExecutionContext(watch, now, triggeredWatch.triggerEvent(), defaultThrottlePeriod); executeAsync(ctx, triggeredWatch); counter++; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStore.java index eb2252dcece..41f38ef587b 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/execution/TriggeredWatchStore.java @@ -15,7 +15,6 @@ import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.cluster.ClusterState; @@ -24,7 +23,6 @@ import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortBuilders; @@ -75,8 +73,9 @@ public class TriggeredWatchStore extends AbstractComponent { public boolean validate(ClusterState state) { try { IndexMetaData indexMetaData = WatchStoreUtils.getConcreteIndex(INDEX_NAME, state.metaData()); - return state.routingTable().index(indexMetaData.getIndex()).allPrimaryShardsActive(); - } catch (IndexNotFoundException e) { + if (indexMetaData != null) { + return state.routingTable().index(indexMetaData.getIndex()).allPrimaryShardsActive(); + } } catch (IllegalStateException e) { logger.trace((Supplier) () -> new ParameterizedMessage("error getting index meta data [{}]: ", INDEX_NAME), e); return false; @@ -108,29 +107,20 @@ public class TriggeredWatchStore extends AbstractComponent { } } - public void put(final TriggeredWatch triggeredWatch, final ActionListener listener) throws Exception { + public void put(final TriggeredWatch triggeredWatch, final ActionListener listener) { ensureStarted(); try { IndexRequest request = new IndexRequest(INDEX_NAME, DOC_TYPE, triggeredWatch.id().value()) .source(XContentFactory.jsonBuilder().value(triggeredWatch)) .opType(IndexRequest.OpType.CREATE); - client.index(request, new ActionListener() { - @Override - public void onResponse(IndexResponse response) { - listener.onResponse(true); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); + client.index(request, ActionListener.wrap(response -> listener.onResponse(true), listener::onFailure)); } catch (IOException e) { - throw ioException("failed to persist triggered watch [{}]", e, triggeredWatch); + logger.warn((Supplier) () -> new ParameterizedMessage("could not index triggered watch [{}], ignoring it...", + triggeredWatch.id()), e); } } - public void putAll(final List triggeredWatches, final ActionListener listener) throws Exception { + public void putAll(final List triggeredWatches, final ActionListener listener) { if (triggeredWatches.isEmpty()) { listener.onResponse(new BitSet(0)); @@ -138,55 +128,39 @@ public class TriggeredWatchStore extends AbstractComponent { } if (triggeredWatches.size() == 1) { - put(triggeredWatches.get(0), new ActionListener() { - @Override - public void onResponse(Boolean success) { - BitSet bitSet = new BitSet(1); - bitSet.set(0); - listener.onResponse(bitSet); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); + put(triggeredWatches.get(0), ActionListener.wrap(success -> { + BitSet bitSet = new BitSet(1); + bitSet.set(0); + listener.onResponse(bitSet); + }, listener::onFailure)); return; } ensureStarted(); - try { - BulkRequest request = new BulkRequest(); - for (TriggeredWatch triggeredWatch : triggeredWatches) { + BulkRequest request = new BulkRequest(); + for (TriggeredWatch triggeredWatch : triggeredWatches) { + try { IndexRequest indexRequest = new IndexRequest(INDEX_NAME, DOC_TYPE, triggeredWatch.id().value()); indexRequest.source(XContentFactory.jsonBuilder().value(triggeredWatch)); indexRequest.opType(IndexRequest.OpType.CREATE); request.add(indexRequest); + } catch (IOException e) { + logger.warn("could not create JSON to store triggered watch [{}]", triggeredWatch.id().value()); } - client.bulk(request, new ActionListener() { - @Override - public void onResponse(BulkResponse response) { - BitSet successFullSlots = new BitSet(triggeredWatches.size()); - for (int i = 0; i < response.getItems().length; i++) { - BulkItemResponse itemResponse = response.getItems()[i]; - if (itemResponse.isFailed()) { - logger.error("could store triggered watch with id [{}], because failed [{}]", itemResponse.getId(), - itemResponse.getFailureMessage()); - } else { - successFullSlots.set(i); - } - } - listener.onResponse(successFullSlots); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); - } catch (IOException e) { - throw ioException("failed to persist triggered watches", e); } + client.bulk(request, ActionListener.wrap(response -> { + BitSet successFullSlots = new BitSet(triggeredWatches.size()); + for (int i = 0; i < response.getItems().length; i++) { + BulkItemResponse itemResponse = response.getItems()[i]; + if (itemResponse.isFailed()) { + logger.error("could not store triggered watch with id [{}], failed [{}]", itemResponse.getId(), + itemResponse.getFailureMessage()); + } else { + successFullSlots.set(i); + } + } + listener.onResponse(successFullSlots); + }, listener::onFailure)); } public BitSet putAll(final List triggeredWatches) throws Exception { @@ -229,10 +203,8 @@ public class TriggeredWatchStore extends AbstractComponent { } public Collection loadTriggeredWatches(ClusterState state) { - IndexMetaData indexMetaData; - try { - indexMetaData = WatchStoreUtils.getConcreteIndex(INDEX_NAME, state.metaData()); - } catch (IndexNotFoundException e) { + IndexMetaData indexMetaData = WatchStoreUtils.getConcreteIndex(INDEX_NAME, state.metaData()); + if (indexMetaData == null) { return Collections.emptySet(); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestGetWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestGetWatchAction.java index 8f6797062f6..27a20c83963 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestGetWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestGetWatchAction.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.watcher.rest.action; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestController; @@ -16,10 +17,8 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestBuilderListener; import org.elasticsearch.xpack.watcher.client.WatcherClient; import org.elasticsearch.xpack.watcher.rest.WatcherRestHandler; -import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; -import org.elasticsearch.xpack.watcher.watch.WatchStatus; import java.io.IOException; @@ -48,11 +47,9 @@ public class RestGetWatchAction extends WatcherRestHandler { .field("found", response.isFound()) .field("_id", response.getId()); if (response.isFound()) { - WatcherParams params = WatcherParams.builder(request) - .put(WatchStatus.INCLUDE_VERSION_KEY, true) - .build(); - builder.field("_status", response.getStatus(), params); - builder.field("watch", response.getSource(), params); + ToXContent.MapParams xContentParams = new ToXContent.MapParams(request.params()); + builder.field("_status", response.getStatus(), xContentParams); + builder.field("watch", response.getSource(), xContentParams); } builder.endObject(); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestHijackOperationAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestHijackOperationAction.java index e375c129a55..4a5d9824e09 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestHijackOperationAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/rest/action/RestHijackOperationAction.java @@ -15,7 +15,7 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xpack.watcher.client.WatcherClient; import org.elasticsearch.xpack.watcher.rest.WatcherRestHandler; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import java.io.IOException; @@ -33,18 +33,18 @@ public class RestHijackOperationAction extends WatcherRestHandler { super(settings); if (!settings.getAsBoolean(ALLOW_DIRECT_ACCESS_TO_WATCH_INDEX_SETTING, false)) { WatcherRestHandler unsupportedHandler = new UnsupportedHandler(settings); - controller.registerHandler(POST, WatchStore.INDEX + "/watch", this); - controller.registerHandler(POST, WatchStore.INDEX + "/watch/{id}", this); - controller.registerHandler(PUT, WatchStore.INDEX + "/watch/{id}", this); - controller.registerHandler(POST, WatchStore.INDEX + "/watch/{id}/_update", this); - controller.registerHandler(DELETE, WatchStore.INDEX + "/watch/_query", this); - controller.registerHandler(DELETE, WatchStore.INDEX + "/watch/{id}", this); - controller.registerHandler(GET, WatchStore.INDEX + "/watch/{id}", this); - controller.registerHandler(POST, WatchStore.INDEX + "/watch/_bulk", unsupportedHandler); - controller.registerHandler(POST, WatchStore.INDEX + "/_bulk", unsupportedHandler); - controller.registerHandler(PUT, WatchStore.INDEX + "/watch/_bulk", unsupportedHandler); - controller.registerHandler(PUT, WatchStore.INDEX + "/_bulk", unsupportedHandler); - controller.registerHandler(DELETE, WatchStore.INDEX, unsupportedHandler); + controller.registerHandler(POST, Watch.INDEX + "/watch", this); + controller.registerHandler(POST, Watch.INDEX + "/watch/{id}", this); + controller.registerHandler(PUT, Watch.INDEX + "/watch/{id}", this); + controller.registerHandler(POST, Watch.INDEX + "/watch/{id}/_update", this); + controller.registerHandler(DELETE, Watch.INDEX + "/watch/_query", this); + controller.registerHandler(DELETE, Watch.INDEX + "/watch/{id}", this); + controller.registerHandler(GET, Watch.INDEX + "/watch/{id}", this); + controller.registerHandler(POST, Watch.INDEX + "/watch/_bulk", unsupportedHandler); + controller.registerHandler(POST, Watch.INDEX + "/_bulk", unsupportedHandler); + controller.registerHandler(PUT, Watch.INDEX + "/watch/_bulk", unsupportedHandler); + controller.registerHandler(PUT, Watch.INDEX + "/_bulk", unsupportedHandler); + controller.registerHandler(DELETE, Watch.INDEX, unsupportedHandler); } } @@ -56,7 +56,7 @@ public class RestHijackOperationAction extends WatcherRestHandler { } XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); jsonBuilder.startObject().field("error", "This endpoint is not supported for " + - request.method().name() + " on " + WatchStore.INDEX + " index. Please use " + + request.method().name() + " on " + Watch.INDEX + " index. Please use " + request.method().name() + " " + URI_BASE + "/watch/ instead"); jsonBuilder.field("status", RestStatus.BAD_REQUEST.getStatus()); jsonBuilder.endObject(); @@ -77,7 +77,7 @@ public class RestHijackOperationAction extends WatcherRestHandler { } XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); jsonBuilder.startObject().field("error", "This endpoint is not supported for " + - request.method().name() + " on " + WatchStore.INDEX + " index."); + request.method().name() + " on " + Watch.INDEX + " index."); jsonBuilder.field("status", RestStatus.BAD_REQUEST.getStatus()); jsonBuilder.endObject(); return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, jsonBuilder)); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/init/proxy/WatcherClientProxy.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/init/proxy/WatcherClientProxy.java index f6b24cfdd6d..7303481b833 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/init/proxy/WatcherClientProxy.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/init/proxy/WatcherClientProxy.java @@ -14,6 +14,8 @@ import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.ClearScrollRequest; @@ -21,13 +23,23 @@ import org.elasticsearch.action.search.ClearScrollResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.routing.Preference; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.index.engine.DocumentMissingException; import org.elasticsearch.xpack.common.init.proxy.ClientProxy; import org.elasticsearch.xpack.security.InternalClient; +import org.elasticsearch.xpack.watcher.watch.Watch; + +import java.io.IOException; +import java.util.Collections; /** * A lazily initialized proxy to an elasticsearch {@link Client}. Inject this proxy whenever a client @@ -65,6 +77,10 @@ public class WatcherClientProxy extends ClientProxy { return client.update(preProcess(request)).actionGet(defaultIndexTimeout); } + public void update(UpdateRequest request, ActionListener listener) { + client.update(preProcess(request), listener); + } + public BulkResponse bulk(BulkRequest request, TimeValue timeout) { if (timeout == null) { timeout = defaultBulkTimeout; @@ -110,4 +126,47 @@ public class WatcherClientProxy extends ClientProxy { preProcess(request); client.admin().indices().putTemplate(request, listener); } + + public GetResponse getWatch(String id) { + PlainActionFuture future = PlainActionFuture.newFuture(); + getWatch(id, future); + return future.actionGet(); + } + + public void getWatch(String id, ActionListener listener) { + GetRequest getRequest = new GetRequest(Watch.INDEX, Watch.DOC_TYPE, id).preference(Preference.LOCAL.type()).realtime(true); + client.get(preProcess(getRequest), listener); + } + + public void deleteWatch(String id, ActionListener listener) { + DeleteRequest request = new DeleteRequest(Watch.INDEX, Watch.DOC_TYPE, id); + client.delete(preProcess(request), listener); + } + + /** + * Updates and persists the status of the given watch + * + * If the watch is missing (because it might have been deleted by the user during an execution), then this method + * does nothing and just returns without throwing an exception + */ + public void updateWatchStatus(Watch watch) throws IOException { + // at the moment we store the status together with the watch, + // so we just need to update the watch itself + ToXContent.MapParams params = new ToXContent.MapParams(Collections.singletonMap(Watch.INCLUDE_STATUS_KEY, "true")); + XContentBuilder source = JsonXContent.contentBuilder(). + startObject() + .field(Watch.Field.STATUS.getPreferredName(), watch.status(), params) + .endObject(); + + UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, watch.id()); + updateRequest.doc(source); + updateRequest.version(watch.version()); + try { + this.update(updateRequest); + } catch (DocumentMissingException e) { + // do not rethrow this exception, otherwise the watch history will contain an exception + // even though the execution might have been fine + } + } + } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/xcontent/WatcherParams.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/xcontent/WatcherParams.java index f7d5f791d48..7135f27d09c 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/xcontent/WatcherParams.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/support/xcontent/WatcherParams.java @@ -25,10 +25,6 @@ public class WatcherParams extends ToXContent.DelegatingMapParams { return wrap(params).hideSecrets(); } - public static boolean collapseArrays(ToXContent.Params params) { - return wrap(params).collapseArrays(); - } - public static boolean debug(ToXContent.Params params) { return wrap(params).debug(); } @@ -41,10 +37,6 @@ public class WatcherParams extends ToXContent.DelegatingMapParams { return paramAsBoolean(HIDE_SECRETS_KEY, false); } - public boolean collapseArrays() { - return paramAsBoolean(COLLAPSE_ARRAYS_KEY, false); - } - public boolean debug() { return paramAsBoolean(DEBUG_KEY, false); } @@ -77,11 +69,6 @@ public class WatcherParams extends ToXContent.DelegatingMapParams { return this; } - public Builder collapseArrays(boolean collapseArrays) { - params.put(COLLAPSE_ARRAYS_KEY, String.valueOf(collapseArrays)); - return this; - } - public Builder debug(boolean debug) { params.put(DEBUG_KEY, String.valueOf(debug)); return this; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/ack/TransportAckWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/ack/TransportAckWatchAction.java index af14ac8fe74..edfd1c00f18 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/ack/TransportAckWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/ack/TransportAckWatchAction.java @@ -6,8 +6,10 @@ package org.elasticsearch.xpack.watcher.transport.actions.ack; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; @@ -15,29 +17,43 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.watcher.WatcherService; +import org.elasticsearch.xpack.watcher.actions.ActionWrapper; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; -import org.elasticsearch.xpack.watcher.watch.WatchStatus; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; +import org.joda.time.DateTime; + +import java.time.Clock; +import java.util.Arrays; +import java.util.List; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.joda.time.DateTimeZone.UTC; /** * Performs the ack operation. */ public class TransportAckWatchAction extends WatcherTransportAction { - private final WatcherService watcherService; + private final Clock clock; + private final Watch.Parser parser; + private final WatcherClientProxy client; @Inject public TransportAckWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, - ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, WatcherService watcherService, - XPackLicenseState licenseState) { + ThreadPool threadPool, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, Clock clock, XPackLicenseState licenseState, + Watch.Parser parser, WatcherClientProxy client) { super(settings, AckWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, licenseState, AckWatchRequest::new); - this.watcherService = watcherService; + this.clock = clock; + this.parser = parser; + this.client = client; } @Override @@ -53,19 +69,56 @@ public class TransportAckWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { - try { - WatchStatus watchStatus = watcherService.ackWatch(request.getWatchId(), request.getActionIds()); - AckWatchResponse response = new AckWatchResponse(watchStatus); - listener.onResponse(response); - } catch (Exception e) { - listener.onFailure(e); - } + client.getWatch(request.getWatchId(), ActionListener.wrap((response) -> { + if (response.isExists() == false) { + listener.onFailure(new ResourceNotFoundException("Watch with id [{}] does not exit", request.getWatchId())); + } else { + DateTime now = new DateTime(clock.millis(), UTC); + Watch watch = parser.parseWithSecrets(request.getWatchId(), true, response.getSourceAsBytesRef(), now); + watch.version(response.getVersion()); + watch.status().version(response.getVersion()); + String[] actionIds = request.getActionIds(); + if (actionIds == null || actionIds.length == 0) { + actionIds = new String[]{Watch.ALL_ACTIONS_ID}; + } + + // exit early in case nothing changes + boolean isChanged = watch.ack(now, actionIds); + if (isChanged == false) { + listener.onResponse(new AckWatchResponse(watch.status())); + return; + } + + UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getWatchId()); + // this may reject this action, but prevents concurrent updates from a watch execution + updateRequest.version(response.getVersion()); + XContentBuilder builder = jsonBuilder(); + builder.startObject() + .startObject(Watch.Field.STATUS.getPreferredName()) + .startObject("actions"); + + List actionIdsAsList = Arrays.asList(actionIds); + boolean updateAll = actionIdsAsList.contains("_all"); + for (ActionWrapper actionWrapper : watch.actions()) { + if (updateAll || actionIdsAsList.contains(actionWrapper.id())) { + builder.startObject(actionWrapper.id()) + .field("ack", watch.status().actionStatus(actionWrapper.id()).ackStatus(), ToXContent.EMPTY_PARAMS) + .endObject(); + } + } + + builder.endObject().endObject().endObject(); + updateRequest.doc(builder); + + client.update(updateRequest, ActionListener.wrap( + (updateResponse) -> listener.onResponse(new AckWatchResponse(watch.status())), + listener::onFailure)); + } + }, listener::onFailure)); } @Override protected ClusterBlockException checkBlock(AckWatchRequest request, ClusterState state) { - return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, WatchStore.INDEX); + return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, Watch.INDEX); } - - } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/activate/TransportActivateWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/activate/TransportActivateWatchAction.java index 44c55331e62..44a4c616767 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/activate/TransportActivateWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/activate/TransportActivateWatchAction.java @@ -6,8 +6,10 @@ package org.elasticsearch.xpack.watcher.transport.actions.activate; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; @@ -15,29 +17,46 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.watcher.WatcherService; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; +import org.elasticsearch.xpack.watcher.trigger.TriggerService; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchStatus; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.joda.time.DateTime; + +import java.io.IOException; +import java.time.Clock; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.xpack.watcher.support.WatcherDateTimeUtils.writeDate; +import static org.joda.time.DateTimeZone.UTC; /** * Performs the watch de/activation operation. */ public class TransportActivateWatchAction extends WatcherTransportAction { - private final WatcherService watcherService; + private final Clock clock; + private final TriggerService triggerService; + private final Watch.Parser parser; + private final WatcherClientProxy client; @Inject public TransportActivateWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, WatcherService watcherService, - XPackLicenseState licenseState) { + IndexNameExpressionResolver indexNameExpressionResolver, Clock clock, + XPackLicenseState licenseState, TriggerService triggerService, Watch.Parser parser, + WatcherClientProxy client) { super(settings, ActivateWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, licenseState, ActivateWatchRequest::new); - this.watcherService = watcherService; + this.clock = clock; + this.triggerService = triggerService; + this.parser = parser; + this.client = client; } @Override @@ -54,20 +73,50 @@ public class TransportActivateWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { try { - WatchStatus watchStatus = request.isActivate() ? - watcherService.activateWatch(request.getWatchId()) : - watcherService.deactivateWatch(request.getWatchId()); - ActivateWatchResponse response = new ActivateWatchResponse(watchStatus); - listener.onResponse(response); - } catch (Exception e) { + // if this is about deactivation, remove this immediately from the trigger service, no need to wait for all those async calls + if (request.isActivate() == false) { + triggerService.remove(request.getWatchId()); + } + + DateTime now = new DateTime(clock.millis(), UTC); + UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getWatchId()); + XContentBuilder builder = activateWatchBuilder(request.isActivate(), now); + updateRequest.doc(builder); + + client.update(updateRequest, ActionListener.wrap(updateResponse -> { + client.getWatch(request.getWatchId(), ActionListener.wrap(getResponse -> { + if (getResponse.isExists()) { + Watch watch = parser.parseWithSecrets(request.getWatchId(), true, getResponse.getSourceAsBytesRef(), now); + watch.version(getResponse.getVersion()); + watch.status().version(getResponse.getVersion()); + + if (request.isActivate()) { + triggerService.add(watch); + } + listener.onResponse(new ActivateWatchResponse(watch.status())); + } else { + listener.onFailure(new ResourceNotFoundException("Watch with id [{}] does not exist", request.getWatchId())); + } + }, listener::onFailure)); + }, listener::onFailure)); + } catch (IOException e) { listener.onFailure(e); } } - @Override - protected ClusterBlockException checkBlock(ActivateWatchRequest request, ClusterState state) { - return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, WatchStore.INDEX); + private XContentBuilder activateWatchBuilder(boolean active, DateTime now) throws IOException { + XContentBuilder builder = jsonBuilder().startObject() + .startObject(Watch.Field.STATUS.getPreferredName()) + .startObject(WatchStatus.Field.STATE.getPreferredName()) + .field(WatchStatus.Field.ACTIVE.getPreferredName(), active); + + writeDate(WatchStatus.Field.TIMESTAMP.getPreferredName(), builder, now); + builder.endObject().endObject().endObject(); + return builder; } - + @Override + protected ClusterBlockException checkBlock(ActivateWatchRequest request, ClusterState state) { + return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, Watch.INDEX); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/delete/TransportDeleteWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/delete/TransportDeleteWatchAction.java index f0d43b1e8a9..6488d4b1b64 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/delete/TransportDeleteWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/delete/TransportDeleteWatchAction.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.watcher.transport.actions.delete; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.DocWriteResponse; -import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; @@ -20,25 +19,28 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.watcher.WatcherService; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.trigger.TriggerService; +import org.elasticsearch.xpack.watcher.watch.Watch; /** * Performs the delete operation. */ public class TransportDeleteWatchAction extends WatcherTransportAction { - private final WatcherService watcherService; + private final WatcherClientProxy client; + private final TriggerService triggerService; @Inject public TransportDeleteWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, WatcherService watcherService, - XPackLicenseState licenseState) { + IndexNameExpressionResolver indexNameExpressionResolver, WatcherClientProxy client, + XPackLicenseState licenseState, TriggerService triggerService) { super(settings, DeleteWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, licenseState, DeleteWatchRequest::new); - this.watcherService = watcherService; + this.client = client; + this.triggerService = triggerService; } @Override @@ -54,20 +56,19 @@ public class TransportDeleteWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { - try { - DeleteResponse deleteResponse = watcherService.deleteWatch(request.getId()).deleteResponse(); - boolean deleted = deleteResponse.getResult() == DocWriteResponse.Result.DELETED; - DeleteWatchResponse response = new DeleteWatchResponse(deleteResponse.getId(), deleteResponse.getVersion(), deleted); - listener.onResponse(response); - } catch (Exception e) { - listener.onFailure(e); - } + client.deleteWatch(request.getId(), ActionListener.wrap(deleteResponse -> { + boolean deleted = deleteResponse.getResult() == DocWriteResponse.Result.DELETED; + DeleteWatchResponse response = new DeleteWatchResponse(deleteResponse.getId(), deleteResponse.getVersion(), deleted); + if (deleted) { + triggerService.remove(request.getId()); + } + listener.onResponse(response); + }, + listener::onFailure)); } @Override protected ClusterBlockException checkBlock(DeleteWatchRequest request, ClusterState state) { - return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, WatchStore.INDEX); + return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, Watch.INDEX); } - - } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/execute/TransportExecuteWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/execute/TransportExecuteWatchAction.java index cee88e84dd6..c035e5ecd8b 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/execute/TransportExecuteWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/execute/TransportExecuteWatchAction.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; @@ -29,6 +30,7 @@ import org.elasticsearch.xpack.watcher.execution.ExecutionService; import org.elasticsearch.xpack.watcher.execution.ManualExecutionContext; import org.elasticsearch.xpack.watcher.history.WatchRecord; import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; @@ -36,13 +38,12 @@ import org.elasticsearch.xpack.watcher.trigger.TriggerService; import org.elasticsearch.xpack.watcher.trigger.manual.ManualTriggerEvent; import org.elasticsearch.xpack.watcher.watch.Payload; import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.joda.time.DateTime; +import java.io.IOException; import java.time.Clock; import java.util.Map; -import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalArgument; import static org.joda.time.DateTimeZone.UTC; /** @@ -51,24 +52,24 @@ import static org.joda.time.DateTimeZone.UTC; public class TransportExecuteWatchAction extends WatcherTransportAction { private final ExecutionService executionService; - private final WatchStore watchStore; private final Clock clock; private final TriggerService triggerService; private final Watch.Parser watchParser; + private final WatcherClientProxy client; @Inject public TransportExecuteWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver, ExecutionService executionService, - Clock clock, XPackLicenseState licenseState, WatchStore watchStore, TriggerService triggerService, - Watch.Parser watchParser) { + Clock clock, XPackLicenseState licenseState, TriggerService triggerService, + Watch.Parser watchParser, WatcherClientProxy client) { super(settings, ExecuteWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, licenseState, ExecuteWatchRequest::new); this.executionService = executionService; - this.watchStore = watchStore; this.clock = clock; this.triggerService = triggerService; this.watchParser = watchParser; + this.client = client; } @Override @@ -84,57 +85,61 @@ public class TransportExecuteWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { - try { - Watch watch; - boolean knownWatch; - if (request.getId() != null) { - watch = watchStore.get(request.getId()); - if (watch == null) { - //todo we need to find a better std exception for this one - throw new ElasticsearchException("watch [{}] does not exist", request.getId()); - } - knownWatch = true; - } else if (request.getWatchSource() != null) { + if (request.getId() != null) { + try { + // should be executed async in the future + GetResponse getResponse = client.getWatch(request.getId()); + Watch watch = watchParser.parse(request.getId(), true, getResponse.getSourceAsBytesRef()); + ExecuteWatchResponse executeWatchResponse = executeWatch(request, watch, true); + listener.onResponse(executeWatchResponse); + } catch (IOException e) { + listener.onFailure(e); + } + } else if (request.getWatchSource() != null) { + try { assert !request.isRecordExecution(); - watch = watchParser.parse(ExecuteWatchRequest.INLINE_WATCH_ID, false, request.getWatchSource()); - knownWatch = false; - } else { - throw illegalArgument("no watch provided"); + Watch watch = watchParser.parse(ExecuteWatchRequest.INLINE_WATCH_ID, true, request.getWatchSource()); + ExecuteWatchResponse response = executeWatch(request, watch, false); + listener.onResponse(response); + } catch (Exception e) { + logger.error((Supplier) () -> new ParameterizedMessage("failed to execute [{}]", request.getId()), e); + listener.onFailure(e); } - - String triggerType = watch.trigger().type(); - TriggerEvent triggerEvent = triggerService.simulateEvent(triggerType, watch.id(), request.getTriggerData()); - - ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, knownWatch, - new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent), executionService.defaultThrottlePeriod()); - - DateTime executionTime = new DateTime(clock.millis(), UTC); - ctxBuilder.executionTime(executionTime); - for (Map.Entry entry : request.getActionModes().entrySet()) { - ctxBuilder.actionMode(entry.getKey(), entry.getValue()); - } - if (request.getAlternativeInput() != null) { - ctxBuilder.withInput(new SimpleInput.Result(new Payload.Simple(request.getAlternativeInput()))); - } - if (request.isIgnoreCondition()) { - ctxBuilder.withCondition(AlwaysCondition.RESULT_INSTANCE); - } - ctxBuilder.recordExecution(request.isRecordExecution()); - - WatchRecord record = executionService.execute(ctxBuilder.build()); - XContentBuilder builder = XContentFactory.jsonBuilder(); - record.toXContent(builder, WatcherParams.builder().hideSecrets(true).debug(request.isDebug()).build()); - ExecuteWatchResponse response = new ExecuteWatchResponse(record.id().value(), builder.bytes(), XContentType.JSON); - listener.onResponse(response); - } catch (Exception e) { - logger.error((Supplier) () -> new ParameterizedMessage("failed to execute [{}]", request.getId()), e); - listener.onFailure(e); + } else { + listener.onFailure(new IllegalArgumentException("no watch provided")); } } + private ExecuteWatchResponse executeWatch(ExecuteWatchRequest request, Watch watch, boolean knownWatch) throws IOException { + String triggerType = watch.trigger().type(); + TriggerEvent triggerEvent = triggerService.simulateEvent(triggerType, watch.id(), request.getTriggerData()); + + ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, knownWatch, + new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent), executionService.defaultThrottlePeriod()); + + DateTime executionTime = new DateTime(clock.millis(), UTC); + ctxBuilder.executionTime(executionTime); + for (Map.Entry entry : request.getActionModes().entrySet()) { + ctxBuilder.actionMode(entry.getKey(), entry.getValue()); + } + if (request.getAlternativeInput() != null) { + ctxBuilder.withInput(new SimpleInput.Result(new Payload.Simple(request.getAlternativeInput()))); + } + if (request.isIgnoreCondition()) { + ctxBuilder.withCondition(AlwaysCondition.RESULT_INSTANCE); + } + ctxBuilder.recordExecution(request.isRecordExecution()); + + WatchRecord record = executionService.execute(ctxBuilder.build()); + XContentBuilder builder = XContentFactory.jsonBuilder(); + + record.toXContent(builder, WatcherParams.builder().hideSecrets(true).debug(request.isDebug()).build()); + return new ExecuteWatchResponse(record.id().value(), builder.bytes(), XContentType.JSON); + } + @Override protected ClusterBlockException checkBlock(ExecuteWatchRequest request, ClusterState state) { - return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, WatchStore.INDEX); + return state.blocks().indexBlockedException(ClusterBlockLevel.WRITE, Watch.INDEX); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java index 94ea8a0a58c..719db950922 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java @@ -5,8 +5,6 @@ */ package org.elasticsearch.xpack.watcher.transport.actions.get; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; @@ -15,7 +13,6 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -25,36 +22,38 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.watcher.WatcherService; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.joda.time.DateTime; -import java.io.IOException; +import java.time.Clock; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.joda.time.DateTimeZone.UTC; -/** - * Performs the get operation. - */ public class TransportGetWatchAction extends WatcherTransportAction { - private final WatcherService watcherService; + private final Watch.Parser parser; + private final Clock clock; + private final WatcherClientProxy client; @Inject public TransportGetWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, WatcherService watcherService, - XPackLicenseState licenseState) { + IndexNameExpressionResolver indexNameExpressionResolver, XPackLicenseState licenseState, + Watch.Parser parser, Clock clock, WatcherClientProxy client) { super(settings, GetWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, - indexNameExpressionResolver, licenseState, GetWatchRequest::new); - this.watcherService = watcherService; + indexNameExpressionResolver, licenseState, GetWatchRequest::new); + this.parser = parser; + this.clock = clock; + this.client = client; } @Override protected String executor() { - return ThreadPool.Names.SAME; // Super lightweight operation, so don't fork + return ThreadPool.Names.MANAGEMENT; } @Override @@ -70,32 +69,30 @@ public class TransportGetWatchAction extends WatcherTransportAction { + if (getResponse.isExists() == false) { listener.onResponse(new GetWatchResponse(request.getId())); - return; } try (XContentBuilder builder = jsonBuilder()) { - // When we return the watch via the get api, we want to return the watch as was specified in the put api, + // When we return the watch via the Get Watch REST API, we want to return the watch as was specified in the put api, // we don't include the status in the watch source itself, but as a separate top level field, so that // it indicates the the status is managed by watcher itself. - watch.toXContent(builder, WatcherParams.builder().hideSecrets(true).build()); - BytesReference watchSource = builder.bytes(); - listener.onResponse(new GetWatchResponse(watch.id(), watch.status(), watchSource, XContentType.JSON)); - } catch (IOException e) { - listener.onFailure(e); + DateTime now = new DateTime(clock.millis(), UTC); + Watch watch = parser.parseWithSecrets(request.getId(), true, getResponse.getSourceAsBytesRef(), now); + watch.toXContent(builder, WatcherParams.builder() + .hideSecrets(true) + .put(Watch.INCLUDE_STATUS_KEY, false) + .build()); + watch.version(getResponse.getVersion()); + watch.status().version(getResponse.getVersion()); + listener.onResponse(new GetWatchResponse(watch.id(), watch.status(), builder.bytes(), XContentType.JSON)); } - - } catch (Exception e) { - logger.error((Supplier) () -> new ParameterizedMessage("failed to get watch [{}]", request.getId()), e); - throw e; - } + }, listener::onFailure)); } @Override protected ClusterBlockException checkBlock(GetWatchRequest request, ClusterState state) { - return state.blocks().indexBlockedException(ClusterBlockLevel.READ, WatchStore.INDEX); + return state.blocks().indexBlockedException(ClusterBlockLevel.READ, Watch.INDEX); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/put/TransportPutWatchAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/put/TransportPutWatchAction.java index 3f11c1dd721..a85e521f47e 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/put/TransportPutWatchAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/put/TransportPutWatchAction.java @@ -8,36 +8,53 @@ package org.elasticsearch.xpack.watcher.transport.actions.put; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.DocWriteResponse; -import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.watcher.WatcherService; +import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.watcher.transport.actions.WatcherTransportAction; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.trigger.TriggerService; +import org.elasticsearch.xpack.watcher.watch.Payload; +import org.elasticsearch.xpack.watcher.watch.Watch; +import org.joda.time.DateTime; + +import java.time.Clock; +import java.util.Collections; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.joda.time.DateTimeZone.UTC; public class TransportPutWatchAction extends WatcherTransportAction { - private final WatcherService watcherService; + private final Clock clock; + private final TriggerService triggerService; + private final Watch.Parser parser; + private final InternalClient client; @Inject public TransportPutWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, WatcherService watcherService, - XPackLicenseState licenseState) { + IndexNameExpressionResolver indexNameExpressionResolver, Clock clock, XPackLicenseState licenseState, + TriggerService triggerService, Watch.Parser parser, InternalClient client) { super(settings, PutWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, - indexNameExpressionResolver, licenseState, PutWatchRequest::new); - this.watcherService = watcherService; + indexNameExpressionResolver, licenseState, PutWatchRequest::new); + this.clock = clock; + this.triggerService = triggerService; + this.parser = parser; + this.client = client; } @Override @@ -53,15 +70,30 @@ public class TransportPutWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { - if (licenseState.isWatcherAllowed() == false) { - listener.onFailure(LicenseUtils.newComplianceException(XPackPlugin.WATCHER)); - return; - } try { - IndexResponse indexResponse = watcherService.putWatch(request.getId(), request.getSource(), request.isActive()); - boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED; - listener.onResponse(new PutWatchResponse(indexResponse.getId(), indexResponse.getVersion(), created)); + DateTime now = new DateTime(clock.millis(), UTC); + Watch watch = parser.parseWithSecrets(request.getId(), false, request.getSource(), now); + watch.setState(request.isActive(), now); + + try (XContentBuilder builder = jsonBuilder()) { + Payload.XContent.MapParams params = new ToXContent.MapParams(Collections.singletonMap(Watch.INCLUDE_STATUS_KEY, "true")); + watch.toXContent(builder, params); + BytesReference bytesReference = builder.bytes(); + + IndexRequest indexRequest = new IndexRequest(Watch.INDEX).type(Watch.DOC_TYPE).id(request.getId()); + indexRequest.source(bytesReference); + + client.index(indexRequest, ActionListener.wrap(indexResponse -> { + boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED; + if (request.isActive()) { + triggerService.add(watch); + } else { + triggerService.remove(request.getId()); + } + listener.onResponse(new PutWatchResponse(indexResponse.getId(), indexResponse.getVersion(), created)); + }, listener::onFailure)); + } } catch (Exception e) { listener.onFailure(e); } @@ -69,7 +101,7 @@ public class TransportPutWatchAction extends WatcherTransportAction listener) throws ElasticsearchException { + WatcherStatsResponse statsResponse = new WatcherStatsResponse(); statsResponse.setWatcherState(watcherService.state()); statsResponse.setThreadPoolQueueSize(executionService.executionThreadPoolQueueSize()); - statsResponse.setWatchesCount(watcherService.watchesCount()); statsResponse.setThreadPoolMaxSize(executionService.executionThreadPoolMaxSize()); statsResponse.setWatcherMetaData(lifeCycleService.watcherMetaData()); - if (request.includeCurrentWatches()) { statsResponse.setSnapshots(executionService.currentExecutions()); } @@ -73,7 +78,13 @@ public class TransportWatcherStatsAction extends WatcherTransportAction { + statsResponse.setWatchesCount(searchResponse.getHits().totalHits()); + listener.onResponse(statsResponse); + }, + e -> listener.onResponse(statsResponse))); } @Override diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/TriggerService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/TriggerService.java index 4e7faab306c..01a326da208 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/TriggerService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/TriggerService.java @@ -9,6 +9,7 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.watcher.watch.Watch; import java.io.IOException; import java.util.Collection; @@ -37,9 +38,9 @@ public class TriggerService extends AbstractComponent { this.engines = unmodifiableMap(builder); } - public synchronized void start(Collection jobs) throws Exception { + public synchronized void start(Collection watches) throws Exception { for (TriggerEngine engine : engines.values()) { - engine.start(jobs); + engine.start(watches); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/SchedulerScheduleTriggerEngine.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/SchedulerScheduleTriggerEngine.java index 5fa3f115028..862244bb7cb 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/SchedulerScheduleTriggerEngine.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/SchedulerScheduleTriggerEngine.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.watcher.trigger.schedule.engine; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.scheduler.SchedulerEngine; -import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleRegistry; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEngine; @@ -72,7 +71,7 @@ public class SchedulerScheduleTriggerEngine extends ScheduleTriggerEngine { final ScheduleTriggerEvent event = new ScheduleTriggerEvent(name, new DateTime(triggeredTime, UTC), new DateTime(scheduledTime, UTC)); for (Listener listener : listeners) { - listener.triggered(Collections.singletonList(event)); + listener.triggered(Collections.singletonList(event)); } } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/Watch.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/Watch.java index d08a968cafd..3ef8453b099 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/Watch.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/Watch.java @@ -59,6 +59,8 @@ public class Watch implements TriggerEngine.Job, ToXContent { public static final String ALL_ACTIONS_ID = "_all"; public static final String INCLUDE_STATUS_KEY = "include_status"; + public static final String INDEX = ".watches"; + public static final String DOC_TYPE = "watch"; private final String id; private final Trigger trigger; @@ -250,7 +252,6 @@ public class Watch implements TriggerEngine.Job, ToXContent { * This method is only called once - when the user adds a new watch. From that moment on, all representations * of the watch in the system will be use secrets for sensitive data. * - * @see org.elasticsearch.xpack.watcher.WatcherService#putWatch(String, BytesReference, boolean) */ public Watch parseWithSecrets(String id, boolean includeStatus, BytesReference source, DateTime now) throws IOException { return parse(id, includeStatus, true, source, now); @@ -317,7 +318,7 @@ public class Watch implements TriggerEngine.Job, ToXContent { metatdata = parser.map(); } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.STATUS)) { if (includeStatus) { - status = WatchStatus.parse(id, parser); + status = WatchStatus.parse(id, parser, clock); } else { parser.skipChildren(); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStatus.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStatus.java index beee60eb1bf..b5ed38af7ac 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStatus.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStatus.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.time.Clock; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; @@ -37,18 +38,13 @@ import static org.joda.time.DateTimeZone.UTC; public class WatchStatus implements ToXContent, Streamable { - public static final String INCLUDE_VERSION_KEY = "include_version"; - - private transient long version; - private State state; @Nullable private DateTime lastChecked; @Nullable private DateTime lastMetCondition; + @Nullable private long version; private Map actions; - private volatile boolean dirty = false; - // for serialization private WatchStatus() { } @@ -73,14 +69,6 @@ public class WatchStatus implements ToXContent, Streamable { return state; } - public long version() { - return version; - } - - public void version(long version) { - this.version = version; - } - public boolean checked() { return lastChecked != null; } @@ -93,19 +81,12 @@ public class WatchStatus implements ToXContent, Streamable { 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; + public long version() { + return version; } - /** - * @return does this Watch.Status needs to be persisted to the index - */ - public boolean dirty() { - return dirty; + public void version(long version) { + this.version = version; } @Override @@ -115,20 +96,15 @@ public class WatchStatus implements ToXContent, Streamable { 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); + return Objects.equals(lastChecked, that.lastChecked) && + Objects.equals(lastMetCondition, that.lastMetCondition) && + Objects.equals(version, that.version) && + Objects.equals(actions, that.actions); } @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; + return Objects.hash(lastChecked, lastMetCondition, actions, version); } /** @@ -139,7 +115,6 @@ public class WatchStatus implements ToXContent, Streamable { */ public void onCheck(boolean metCondition, DateTime timestamp) { lastChecked = timestamp; - dirty = true; if (metCondition) { lastMetCondition = timestamp; } @@ -148,7 +123,6 @@ public class WatchStatus implements ToXContent, Streamable { public void onActionResult(String actionId, DateTime timestamp, Action.Result result) { ActionStatus status = actions.get(actionId); status.update(timestamp, result); - dirty = true; } /** @@ -173,7 +147,6 @@ public class WatchStatus implements ToXContent, Streamable { for (ActionStatus status : actions.values()) { changed |= status.onAck(timestamp); } - dirty |= changed; return changed; } @@ -183,14 +156,13 @@ public class WatchStatus implements ToXContent, Streamable { changed |= status.onAck(timestamp); } } - dirty |= changed; + return changed; } boolean setActive(boolean active, DateTime now) { boolean change = this.state.active != active; if (change) { - this.dirty = true; this.state = new State(active, now); } return change; @@ -233,9 +205,6 @@ public class WatchStatus implements ToXContent, Streamable { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - if (params.paramAsBoolean(INCLUDE_VERSION_KEY, false)) { - builder.field(Field.VERSION.getPreferredName(), version); - } builder.field(Field.STATE.getPreferredName(), state, params); if (lastChecked != null) { builder.field(Field.LAST_CHECKED.getPreferredName(), lastChecked); @@ -250,14 +219,16 @@ public class WatchStatus implements ToXContent, Streamable { } builder.endObject(); } + builder.field(Field.VERSION.getPreferredName(), version); return builder.endObject(); } - public static WatchStatus parse(String watchId, XContentParser parser) throws IOException { + public static WatchStatus parse(String watchId, XContentParser parser, Clock clock) throws IOException { State state = null; DateTime lastChecked = null; DateTime lastMetCondition = null; Map actions = null; + long version = -1; String currentFieldName = null; XContentParser.Token token; @@ -266,11 +237,18 @@ public class WatchStatus implements ToXContent, Streamable { currentFieldName = parser.currentName(); } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.STATE)) { try { - state = State.parse(parser); + state = State.parse(parser, clock); } catch (ElasticsearchParseException e) { throw new ElasticsearchParseException("could not parse watch status for [{}]. failed to parse field [{}]", e, watchId, currentFieldName); } + } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.VERSION)) { + if (token.isValue()) { + version = parser.longValue(); + } else { + throw new ElasticsearchParseException("could not parse watch status for [{}]. expecting field [{}] to hold a long " + + "value, found [{}] instead", watchId, currentFieldName, token); + } } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.LAST_CHECKED)) { if (token.isValue()) { lastChecked = parseDate(currentFieldName, parser, UTC); @@ -311,7 +289,7 @@ public class WatchStatus implements ToXContent, Streamable { } actions = actions == null ? emptyMap() : unmodifiableMap(actions); - return new WatchStatus(-1, state, lastChecked, lastMetCondition, actions); + return new WatchStatus(version, state, lastChecked, lastMetCondition, actions); } public static class State implements ToXContent { @@ -340,12 +318,12 @@ public class WatchStatus implements ToXContent, Streamable { return builder.endObject(); } - public static State parse(XContentParser parser) throws IOException { + public static State parse(XContentParser parser, Clock clock) throws IOException { if (parser.currentToken() != XContentParser.Token.START_OBJECT) { throw new ElasticsearchParseException("expected an object but found [{}] instead", parser.currentToken()); } boolean active = true; - DateTime timestamp = new DateTime(Clock.systemUTC().millis(), UTC); + DateTime timestamp = new DateTime(clock.millis(), UTC); String currentFieldName = null; XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { @@ -361,13 +339,13 @@ public class WatchStatus implements ToXContent, Streamable { } } - interface Field { - ParseField VERSION = new ParseField("version"); + public interface Field { ParseField STATE = new ParseField("state"); ParseField ACTIVE = new ParseField("active"); ParseField TIMESTAMP = new ParseField("timestamp"); ParseField LAST_CHECKED = new ParseField("last_checked"); ParseField LAST_MET_CONDITION = new ParseField("last_met_condition"); ParseField ACTIONS = new ParseField("actions"); + ParseField VERSION = new ParseField("version"); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStore.java deleted file mode 100644 index afcd0f90209..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStore.java +++ /dev/null @@ -1,368 +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.xpack.watcher.watch; - -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.util.Supplier; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; -import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; -import org.elasticsearch.action.delete.DeleteRequest; -import org.elasticsearch.action.delete.DeleteResponse; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.action.update.UpdateResponse; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.lucene.uid.Versions; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.util.concurrent.ConcurrentCollections; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.json.JsonXContent; -import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.index.engine.DocumentMissingException; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.builder.SearchSourceBuilder; -import org.elasticsearch.search.sort.SortBuilders; -import org.elasticsearch.xpack.common.stats.Counters; -import org.elasticsearch.xpack.watcher.actions.ActionWrapper; -import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; -import org.elasticsearch.xpack.watcher.trigger.schedule.Schedule; -import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalState; - -public class WatchStore extends AbstractComponent { - - public static final String INDEX = ".watches"; - public static final String DOC_TYPE = "watch"; - - private final WatcherClientProxy client; - private final Watch.Parser watchParser; - - private final ConcurrentMap watches; - private final AtomicBoolean started = new AtomicBoolean(false); - - private final int scrollSize; - private final TimeValue scrollTimeout; - - public WatchStore(Settings settings, WatcherClientProxy client, Watch.Parser watchParser) { - super(settings); - this.client = client; - this.watchParser = watchParser; - this.watches = ConcurrentCollections.newConcurrentMap(); - - this.scrollTimeout = settings.getAsTime("xpack.watcher.watch.scroll.timeout", TimeValue.timeValueSeconds(30)); - this.scrollSize = settings.getAsInt("xpack.watcher.watch.scroll.size", 100); - } - - public void start(ClusterState state) throws Exception { - if (started.get()) { - logger.debug("watch store already started"); - return; - } - - try { - IndexMetaData indexMetaData = WatchStoreUtils.getConcreteIndex(INDEX, state.metaData()); - int count = loadWatches(indexMetaData.getNumberOfShards()); - logger.debug("loaded [{}] watches from the watches index [{}]", count, indexMetaData.getIndex().getName()); - } catch (IndexNotFoundException e) { - } catch (Exception e) { - logger.debug((Supplier) () -> new ParameterizedMessage("failed to load watches for watch index [{}]", INDEX), e); - watches.clear(); - throw e; - } - - started.set(true); - } - - public boolean validate(ClusterState state) { - IndexMetaData watchesIndexMetaData; - try { - watchesIndexMetaData = WatchStoreUtils.getConcreteIndex(INDEX, state.metaData()); - } catch (IndexNotFoundException e) { - return true; - } catch (IllegalStateException e) { - logger.trace((Supplier) () -> new ParameterizedMessage("error getting index meta data [{}]: ", INDEX), e); - return false; - } - - return state.routingTable().index(watchesIndexMetaData.getIndex().getName()).allPrimaryShardsActive(); - } - - public boolean started() { - return started.get(); - } - - public void stop() { - if (started.compareAndSet(true, false)) { - watches.clear(); - logger.info("stopped watch store"); - } - } - - /** - * Returns the watch with the specified id otherwise null is returned. - */ - public Watch get(String id) { - ensureStarted(); - return watches.get(id); - } - - /** - * Creates an watch if this watch already exists it will be overwritten - */ - public WatchPut put(Watch watch) throws IOException { - ensureStarted(); - IndexRequest indexRequest = createIndexRequest(watch.id(), watch.getAsBytes(), Versions.MATCH_ANY); - IndexResponse response = client.index(indexRequest, (TimeValue) null); - watch.status().version(response.getVersion()); - watch.version(response.getVersion()); - Watch previous = watches.put(watch.id(), watch); - return new WatchPut(previous, watch, response); - } - - /** - * Updates and persists the status of the given watch - */ - public void updateStatus(Watch watch) throws IOException { - ensureStarted(); - if (!watch.status().dirty()) { - return; - } - - // at the moment we store the status together with the watch, - // so we just need to update the watch itself - XContentBuilder source = JsonXContent.contentBuilder(). - startObject() - .field(Watch.Field.STATUS.getPreferredName(), watch.status(), ToXContent.EMPTY_PARAMS) - .endObject(); - - UpdateRequest updateRequest = new UpdateRequest(INDEX, DOC_TYPE, watch.id()); - updateRequest.doc(source); - updateRequest.version(watch.version()); - try { - UpdateResponse response = client.update(updateRequest); - watch.status().version(response.getVersion()); - watch.version(response.getVersion()); - watch.status().resetDirty(); - } catch (DocumentMissingException e) { - // do not rethrow an exception, otherwise the watch history will contain an exception - // even though the execution might has been fine - logger.warn("Watch [{}] was deleted during watch execution, not updating watch status", watch.id()); - } - } - - /** - * Deletes the watch with the specified id if exists - */ - public WatchDelete delete(String id) { - ensureStarted(); - Watch watch = watches.remove(id); - // even if the watch was not found in the watch map, we should still try to delete it - // from the index, just to make sure we don't leave traces of it - DeleteRequest request = new DeleteRequest(INDEX, DOC_TYPE, id); - DeleteResponse response = client.delete(request); - // Another operation may hold the Watch instance, so lets set the version for consistency: - if (watch != null) { - watch.version(response.getVersion()); - } - return new WatchDelete(response); - } - - public Collection watches() { - return watches.values(); - } - - public Collection activeWatches() { - Set watches = new HashSet<>(); - for (Watch watch : watches()) { - if (watch.status().state().isActive()) { - watches.add(watch); - } - } - return watches; - } - - public Map usageStats() { - Counters counters = new Counters("count.total", "count.active"); - for (Watch watch : watches.values()) { - boolean isActive = watch.status().state().isActive(); - addToCounters("count", isActive, counters); - - // schedule - if (watch.trigger() != null) { - addToCounters("watch.trigger._all", isActive, counters); - if ("schedule".equals(watch.trigger().type())) { - Schedule schedule = ((ScheduleTrigger) watch.trigger()).getSchedule(); - addToCounters("watch.trigger.schedule._all", isActive, counters); - addToCounters("watch.trigger.schedule." + schedule.type(), isActive, counters); - } - } - - // input - if (watch.input() != null) { - String type = watch.input().type(); - addToCounters("watch.input._all", isActive, counters); - addToCounters("watch.input." + type, isActive, counters); - } - - // condition - if (watch.condition() != null) { - String type = watch.condition().type(); - addToCounters("watch.condition._all", isActive, counters); - addToCounters("watch.condition." + type, isActive, counters); - } - - // actions - for (ActionWrapper actionWrapper : watch.actions()) { - String type = actionWrapper.action().type(); - addToCounters("watch.action." + type, isActive, counters); - if (actionWrapper.transform() != null) { - String transformType = actionWrapper.transform().type(); - addToCounters("watch.transform." + transformType, isActive, counters); - } - } - - // transform - if (watch.transform() != null) { - String type = watch.transform().type(); - addToCounters("watch.transform." + type, isActive, counters); - } - - // metadata - if (watch.metadata() != null && watch.metadata().size() > 0) { - addToCounters("watch.metadata", isActive, counters); - } - } - - return counters.toMap(); - } - - private void addToCounters(String name, boolean isActive, Counters counters) { - counters.inc(name + ".total"); - if (isActive) { - counters.inc(name + ".active"); - } - } - - IndexRequest createIndexRequest(String id, BytesReference source, long version) { - IndexRequest indexRequest = new IndexRequest(INDEX, DOC_TYPE, id); - indexRequest.source(BytesReference.toBytes(source)); - indexRequest.version(version); - return indexRequest; - } - - /** - * scrolls all the watch documents in the watches index, parses them, and loads them into - * the given map. - */ - int loadWatches(int numPrimaryShards) { - assert watches.isEmpty() : "no watches should reside, but there are [" + watches.size() + "] watches."; - RefreshResponse refreshResponse = client.refresh(new RefreshRequest(INDEX)); - if (refreshResponse.getSuccessfulShards() < numPrimaryShards) { - throw illegalState("not all required shards have been refreshed"); - } - - int count = 0; - SearchRequest searchRequest = new SearchRequest(INDEX) - .types(DOC_TYPE) - .preference("_primary") - .scroll(scrollTimeout) - .source(new SearchSourceBuilder() - .size(scrollSize) - .sort(SortBuilders.fieldSort("_doc")) - .version(true)); - SearchResponse response = client.search(searchRequest, null); - try { - if (response.getTotalShards() != response.getSuccessfulShards()) { - throw new ElasticsearchException("Partial response while loading watches"); - } - - while (response.getHits().hits().length != 0) { - for (SearchHit hit : response.getHits()) { - String id = hit.getId(); - try { - Watch watch = watchParser.parse(id, true, hit.getSourceRef()); - watch.status().version(hit.version()); - watch.version(hit.version()); - watches.put(id, watch); - count++; - } catch (Exception e) { - logger.error((Supplier) () -> new ParameterizedMessage("couldn't load watch [{}], ignoring it...", id), e); - } - } - response = client.searchScroll(response.getScrollId(), scrollTimeout); - } - } finally { - client.clearScroll(response.getScrollId()); - } - return count; - } - - private void ensureStarted() { - if (!started.get()) { - throw new IllegalStateException("watch store not started"); - } - } - - public void clearWatchesInMemory() { - watches.clear(); - } - - public class WatchPut { - - private final Watch previous; - private final Watch current; - private final IndexResponse response; - - public WatchPut(Watch previous, Watch current, IndexResponse response) { - this.current = current; - this.previous = previous; - this.response = response; - } - - public Watch current() { - return current; - } - - public Watch previous() { - return previous; - } - - public IndexResponse indexResponse() { - return response; - } - } - - public class WatchDelete { - - private final DeleteResponse response; - - public WatchDelete(DeleteResponse response) { - this.response = response; - } - - public DeleteResponse deleteResponse() { - return response; - } - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStoreUtils.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStoreUtils.java index 91230af2ec2..151d3c59b6f 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStoreUtils.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/watcher/watch/WatchStoreUtils.java @@ -24,7 +24,7 @@ public class WatchStoreUtils { public static IndexMetaData getConcreteIndex(String name, MetaData metaData) { AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(name); if (aliasOrIndex == null) { - throw new IndexNotFoundException(name); + return null; } if (aliasOrIndex.isAlias() && aliasOrIndex.getIndices().size() > 1) { diff --git a/elasticsearch/src/test/java/org/elasticsearch/script/LatchScriptEngine.java b/elasticsearch/src/test/java/org/elasticsearch/script/LatchScriptEngine.java index f9262f81710..410f99520b1 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/script/LatchScriptEngine.java +++ b/elasticsearch/src/test/java/org/elasticsearch/script/LatchScriptEngine.java @@ -95,6 +95,7 @@ public class LatchScriptEngine implements ScriptEngineService { } public static class LatchScriptPlugin extends Plugin implements ScriptPlugin { + @Override public ScriptEngineService getScriptEngineService(Settings settings) { return INSTANCE; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/notification/email/EmailSecretsIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/notification/email/EmailSecretsIntegrationTests.java index 352c3e1f64e..aa578da7999 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/notification/email/EmailSecretsIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/notification/email/EmailSecretsIntegrationTests.java @@ -19,7 +19,7 @@ import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchRes import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.After; @@ -84,7 +84,7 @@ public class EmailSecretsIntegrationTests extends AbstractWatcherIntegrationTest .get(); // verifying the email password is stored encrypted in the index - GetResponse response = client().prepareGet(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id").get(); + GetResponse response = client().prepareGet(Watch.INDEX, Watch.DOC_TYPE, "_id").get(); assertThat(response, notNullValue()); assertThat(response.getId(), is("_id")); Map source = response.getSource(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleServiceTests.java index 94ce3a0f6a9..d8069b88244 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherLifeCycleServiceTests.java @@ -21,7 +21,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.junit.Before; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -177,7 +177,7 @@ public class WatcherLifeCycleServiceTests extends ESTestCase { // old cluster state that contains watcher index Settings indexSettings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); ClusterState oldClusterState = ClusterState.builder(new ClusterName("my-cluster")) - .metaData(new MetaData.Builder().put(IndexMetaData.builder(WatchStore.INDEX) + .metaData(new MetaData.Builder().put(IndexMetaData.builder(Watch.INDEX) .settings(indexSettings).numberOfReplicas(0).numberOfShards(1))) .nodes(discoveryNodes).build(); @@ -196,13 +196,13 @@ public class WatcherLifeCycleServiceTests extends ESTestCase { // old cluster state that contains watcher index Settings indexSettings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build(); ClusterState oldClusterState = ClusterState.builder(new ClusterName("my-cluster")) - .metaData(new MetaData.Builder().put(IndexMetaData.builder(WatchStore.INDEX) + .metaData(new MetaData.Builder().put(IndexMetaData.builder(Watch.INDEX) .settings(indexSettings).numberOfReplicas(0).numberOfShards(1))) .nodes(discoveryNodes).build(); // new cluster state with a closed watcher index ClusterState newClusterState = ClusterState.builder(new ClusterName("my-cluster")) - .metaData(new MetaData.Builder().put(IndexMetaData.builder(WatchStore.INDEX).state(IndexMetaData.State.CLOSE) + .metaData(new MetaData.Builder().put(IndexMetaData.builder(Watch.INDEX).state(IndexMetaData.State.CLOSE) .settings(indexSettings).numberOfReplicas(0).numberOfShards(1))) .nodes(discoveryNodes).build(); when(watcherService.state()).thenReturn(WatcherState.STARTED); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherServiceTests.java deleted file mode 100644 index 9e1aaba5837..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/WatcherServiceTests.java +++ /dev/null @@ -1,307 +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.xpack.watcher; - -import org.elasticsearch.action.DocWriteResponse; -import org.elasticsearch.action.delete.DeleteResponse; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.support.clock.ClockMock; -import org.elasticsearch.xpack.watcher.execution.ExecutionService; -import org.elasticsearch.xpack.watcher.support.WatcherIndexTemplateRegistry; -import org.elasticsearch.xpack.watcher.trigger.Trigger; -import org.elasticsearch.xpack.watcher.trigger.TriggerEngine; -import org.elasticsearch.xpack.watcher.trigger.TriggerService; -import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchLockService; -import org.elasticsearch.xpack.watcher.watch.WatchStatus; -import org.elasticsearch.xpack.watcher.watch.WatchStore; -import org.joda.time.DateTime; -import org.junit.Before; - -import java.time.Clock; -import java.util.concurrent.atomic.AtomicReference; - -import static java.util.Collections.emptyMap; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.sameInstance; -import static org.joda.time.DateTimeZone.UTC; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class WatcherServiceTests extends ESTestCase { - private TriggerService triggerService; - private WatchStore watchStore; - private Watch.Parser watchParser; - private WatcherService watcherService; - private ClockMock clock; - - @Before - public void init() throws Exception { - triggerService = mock(TriggerService.class); - watchStore = mock(WatchStore.class); - watchParser = mock(Watch.Parser.class); - ExecutionService executionService = mock(ExecutionService.class); - WatchLockService watchLockService = mock(WatchLockService.class); - clock = ClockMock.frozen(); - WatcherIndexTemplateRegistry watcherIndexTemplateRegistry = mock(WatcherIndexTemplateRegistry.class); - watcherService = new WatcherService(Settings.EMPTY, clock, triggerService, watchStore, watchParser, executionService, - watchLockService, watcherIndexTemplateRegistry); - AtomicReference state = watcherService.state; - state.set(WatcherState.STARTED); - } - - public void testPutWatch() throws Exception { - boolean activeByDefault = randomBoolean(); - - IndexResponse indexResponse = mock(IndexResponse.class); - Watch newWatch = mock(Watch.class); - WatchStatus status = mock(WatchStatus.class); - when(status.state()).thenReturn(new WatchStatus.State(activeByDefault, new DateTime(clock.millis(), UTC))); - when(newWatch.status()).thenReturn(status); - - WatchStore.WatchPut watchPut = mock(WatchStore.WatchPut.class); - when(watchPut.indexResponse()).thenReturn(indexResponse); - when(watchPut.current()).thenReturn(newWatch); - - when(watchParser.parseWithSecrets(any(String.class), eq(false), any(BytesReference.class), any(DateTime.class))) - .thenReturn(newWatch); - when(watchStore.put(newWatch)).thenReturn(watchPut); - IndexResponse response = watcherService.putWatch("_id", new BytesArray("{}"), activeByDefault); - assertThat(response, sameInstance(indexResponse)); - - verify(newWatch, times(1)).setState(activeByDefault, new DateTime(clock.millis(), UTC)); - if (activeByDefault) { - verify(triggerService, times(1)).add(any(TriggerEngine.Job.class)); - } else { - verifyZeroInteractions(triggerService); - } - } - - public void testPutWatchDifferentActiveStates() throws Exception { - Trigger trigger = mock(Trigger.class); - - IndexResponse indexResponse = mock(IndexResponse.class); - - Watch watch = mock(Watch.class); - when(watch.id()).thenReturn("_id"); - WatchStatus status = mock(WatchStatus.class); - boolean active = randomBoolean(); - DateTime now = new DateTime(clock.millis(), UTC); - when(status.state()).thenReturn(new WatchStatus.State(active, now)); - when(watch.status()).thenReturn(status); - when(watch.trigger()).thenReturn(trigger); - WatchStore.WatchPut watchPut = mock(WatchStore.WatchPut.class); - when(watchPut.indexResponse()).thenReturn(indexResponse); - when(watchPut.current()).thenReturn(watch); - - Watch previousWatch = mock(Watch.class); - WatchStatus previousStatus = mock(WatchStatus.class); - boolean prevActive = randomBoolean(); - when(previousStatus.state()).thenReturn(new WatchStatus.State(prevActive, now)); - when(previousWatch.status()).thenReturn(previousStatus); - when(previousWatch.trigger()).thenReturn(trigger); - when(watchPut.previous()).thenReturn(previousWatch); - - when(watchParser.parseWithSecrets(any(String.class), eq(false), any(BytesReference.class), eq(now))).thenReturn(watch); - when(watchStore.put(watch)).thenReturn(watchPut); - - IndexResponse response = watcherService.putWatch("_id", new BytesArray("{}"), active); - assertThat(response, sameInstance(indexResponse)); - - if (!active) { - // we should always remove the watch from the trigger service, just to be safe - verify(triggerService, times(1)).remove("_id"); - } else if (prevActive) { - // if both the new watch and the prev one are active, we should do nothing - verifyZeroInteractions(triggerService); - } else { - // if the prev watch was not active and the new one is active, we should add the watch - verify(triggerService, times(1)).add(watch); - } - } - - public void testDeleteWatch() throws Exception { - WatchStore.WatchDelete expectedWatchDelete = mock(WatchStore.WatchDelete.class); - DeleteResponse deleteResponse = mock(DeleteResponse.class); - when(deleteResponse.getResult()).thenReturn(DocWriteResponse.Result.DELETED); - when(expectedWatchDelete.deleteResponse()).thenReturn(deleteResponse); - when(watchStore.delete("_id")).thenReturn(expectedWatchDelete); - WatchStore.WatchDelete watchDelete = watcherService.deleteWatch("_id"); - - assertThat(watchDelete, sameInstance(expectedWatchDelete)); - verify(triggerService, times(1)).remove("_id"); - } - - public void testDeleteWatchNotFound() throws Exception { - WatchStore.WatchDelete expectedWatchDelete = mock(WatchStore.WatchDelete.class); - DeleteResponse deleteResponse = mock(DeleteResponse.class); - when(deleteResponse.getResult()).thenReturn(DocWriteResponse.Result.NOOP); - when(expectedWatchDelete.deleteResponse()).thenReturn(deleteResponse); - when(watchStore.delete("_id")).thenReturn(expectedWatchDelete); - WatchStore.WatchDelete watchDelete = watcherService.deleteWatch("_id"); - - assertThat(watchDelete, sameInstance(expectedWatchDelete)); - verifyZeroInteractions(triggerService); - } - - public void testAckWatch() throws Exception { - DateTime now = new DateTime(UTC); - clock.setTime(now); - Watch watch = mock(Watch.class); - when(watch.ack(now, "_all")).thenReturn(true); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watchStore.get("_id")).thenReturn(watch); - - WatchStatus result = watcherService.ackWatch("_id", Strings.EMPTY_ARRAY); - assertThat(result, not(sameInstance(status))); - - verify(watchStore, times(1)).updateStatus(watch); - } - - public void testActivate() throws Exception { - WatcherService service = spy(watcherService); - WatchStatus expectedStatus = mock(WatchStatus.class); - doReturn(expectedStatus).when(service).setWatchState("_id", true); - WatchStatus actualStatus = service.activateWatch("_id"); - assertThat(actualStatus, sameInstance(expectedStatus)); - verify(service, times(1)).setWatchState("_id", true); - } - - public void testDeactivate() throws Exception { - WatcherService service = spy(watcherService); - WatchStatus expectedStatus = mock(WatchStatus.class); - doReturn(expectedStatus).when(service).setWatchState("_id", false); - WatchStatus actualStatus = service.deactivateWatch("_id"); - assertThat(actualStatus, sameInstance(expectedStatus)); - verify(service, times(1)).setWatchState("_id", false); - } - - public void testSetWatchStateSetActiveOnCurrentlyActive() throws Exception { - // trying to activate a watch that is already active: - // - the watch status should not change - // - the watch doesn't need to be updated in the store - // - the watch should not be removed or re-added to the trigger service - DateTime now = new DateTime(UTC); - clock.setTime(now); - - Watch watch = mock(Watch.class); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watch.setState(true, now)).thenReturn(false); - - when(watchStore.get("_id")).thenReturn(watch); - - - WatchStatus result = watcherService.setWatchState("_id", true); - assertThat(result, not(sameInstance(status))); - - verifyZeroInteractions(triggerService); - verify(watchStore, never()).updateStatus(watch); - } - - public void testSetWatchStateSetActiveOnCurrentlyInactive() throws Exception { - // activating a watch that is currently inactive: - // - the watch status should be updated - // - the watch needs to be updated in the store - // - the watch should be re-added to the trigger service (the assumption is that it's not there) - - DateTime now = new DateTime(UTC); - clock.setTime(now); - - Watch watch = mock(Watch.class); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watch.setState(true, now)).thenReturn(true); - - when(watchStore.get("_id")).thenReturn(watch); - - WatchStatus result = watcherService.setWatchState("_id", true); - assertThat(result, not(sameInstance(status))); - - verify(triggerService, times(1)).add(watch); - verify(watchStore, times(1)).updateStatus(watch); - } - - public void testSetWatchStateSetInactiveOnCurrentlyActive() throws Exception { - // deactivating a watch that is currently active: - // - the watch status should change - // - the watch needs to be updated in the store - // - the watch should be removed from the trigger service - DateTime now = new DateTime(UTC); - clock.setTime(now); - - Watch watch = mock(Watch.class); - when(watch.id()).thenReturn("_id"); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watch.setState(false, now)).thenReturn(true); - - when(watchStore.get("_id")).thenReturn(watch); - - WatchStatus result = watcherService.setWatchState("_id", false); - assertThat(result, not(sameInstance(status))); - - verify(triggerService, times(1)).remove("_id"); - verify(watchStore, times(1)).updateStatus(watch); - } - - public void testSetWatchStateSetInactiveOnCurrentlyInactive() throws Exception { - // trying to deactivate a watch that is currently inactive: - // - the watch status should not be updated - // - the watch should not be updated in the store - // - the watch should be re-added or removed to/from the trigger service - DateTime now = new DateTime(UTC); - clock.setTime(now); - - Watch watch = mock(Watch.class); - when(watch.id()).thenReturn("_id"); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watch.setState(false, now)).thenReturn(false); - - when(watchStore.get("_id")).thenReturn(watch); - - WatchStatus result = watcherService.setWatchState("_id", false); - assertThat(result, not(sameInstance(status))); - - verifyZeroInteractions(triggerService); - verify(watchStore, never()).updateStatus(watch); - } - - public void testAckWatchNotAck() throws Exception { - DateTime now = new DateTime(Clock.systemUTC().millis(), UTC); - Watch watch = mock(Watch.class); - when(watch.ack(now)).thenReturn(false); - WatchStatus status = new WatchStatus(now, emptyMap()); - when(watch.status()).thenReturn(status); - when(watchStore.get("_id")).thenReturn(watch); - - WatchStatus result = watcherService.ackWatch("_id", Strings.EMPTY_ARRAY); - assertThat(result, not(sameInstance(status))); - - verify(watchStore, never()).updateStatus(watch); - } - - public void testAckWatchNoWatch() throws Exception { - when(watchStore.get("_id")).thenReturn(null); - expectThrows(IllegalArgumentException.class, () -> watcherService.ackWatch("_id", Strings.EMPTY_ARRAY)); - verify(watchStore, never()).updateStatus(any(Watch.class)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/ActionErrorIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/ActionErrorIntegrationTests.java index 83311bf69a0..eac652868b0 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/ActionErrorIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/ActionErrorIntegrationTests.java @@ -52,14 +52,11 @@ public class ActionErrorIntegrationTests extends AbstractWatcherIntegrationTestC flush(); // there should be a single history record with a failure status for the action: - assertBusy(new Runnable() { - @Override - public void run() { - long count = watchRecordCount(QueryBuilders.boolQuery() - .must(termsQuery("result.actions.id", "_action")) - .must(termsQuery("result.actions.status", "failure"))); - assertThat(count, is(1L)); - } + assertBusy(() -> { + long count = watchRecordCount(QueryBuilders.boolQuery() + .must(termsQuery("result.actions.id", "_action")) + .must(termsQuery("result.actions.status", "failure"))); + assertThat(count, is(1L)); }); // now we'll trigger the watch again and make sure that it's not throttled and instead @@ -72,14 +69,11 @@ public class ActionErrorIntegrationTests extends AbstractWatcherIntegrationTestC flush(); // there should be a single history record with a failure status for the action: - assertBusy(new Runnable() { - @Override - public void run() { - long count = watchRecordCount(QueryBuilders.boolQuery() - .must(termsQuery("result.actions.id", "_action")) - .must(termsQuery("result.actions.status", "failure"))); - assertThat(count, is(2L)); - } + assertBusy(() -> { + long count = watchRecordCount(QueryBuilders.boolQuery() + .must(termsQuery("result.actions.id", "_action")) + .must(termsQuery("result.actions.status", "failure"))); + assertThat(count, is(2L)); }); // now lets confirm that the ack status of the action is awaits_successful_execution diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/throttler/ActionThrottleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/throttler/ActionThrottleTests.java index 3982b211f5e..ce366b89115 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/throttler/ActionThrottleTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/actions/throttler/ActionThrottleTests.java @@ -7,11 +7,11 @@ package org.elasticsearch.xpack.watcher.actions.throttler; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.common.http.HttpMethod; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; import org.elasticsearch.xpack.common.text.TextTemplate; import org.elasticsearch.xpack.notification.email.EmailTemplate; import org.elasticsearch.xpack.watcher.actions.Action; -import org.elasticsearch.xpack.watcher.actions.ActionWrapper; import org.elasticsearch.xpack.watcher.actions.email.EmailAction; import org.elasticsearch.xpack.watcher.actions.index.IndexAction; import org.elasticsearch.xpack.watcher.actions.logging.LoggingAction; @@ -19,24 +19,23 @@ import org.elasticsearch.xpack.watcher.actions.webhook.WebhookAction; import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder; import org.elasticsearch.xpack.watcher.execution.ActionExecutionMode; import org.elasticsearch.xpack.watcher.execution.ExecutionState; -import org.elasticsearch.xpack.watcher.execution.ManualExecutionContext; -import org.elasticsearch.xpack.watcher.history.WatchRecord; import org.elasticsearch.xpack.watcher.support.xcontent.ObjectPath; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; +import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchRequestBuilder; import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchResponse; -import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchRequest; -import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; import org.elasticsearch.xpack.watcher.trigger.manual.ManualTriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.io.IOException; -import java.time.Clock; import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -47,12 +46,15 @@ import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBu import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule; import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.interval; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { - public void testSingleActionAckThrottle() throws Exception { - boolean useClientForAcking = randomBoolean(); + @Override + protected boolean timeWarped() { + return true; + } + + public void testSingleActionAckThrottle() throws Exception { WatchSourceBuilder watchSourceBuilder = watchBuilder() .trigger(schedule(interval("60m"))); @@ -60,38 +62,37 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { Action.Builder action = availableAction.action(); watchSourceBuilder.addAction("test_id", action); - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); + watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); + refresh(Watch.INDEX); - assertThat(watcherClient().prepareGetWatch("_id").get().isFound(), equalTo(true)); - ManualExecutionContext ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - WatchRecord watchRecord = executionService().execute(ctx); + ExecuteWatchRequestBuilder executeWatchRequestBuilder = watcherClient().prepareExecuteWatch("_id") + .setRecordExecution(true) + .setActionMode("test_id", ActionExecutionMode.SIMULATE); + + Map responseMap = executeWatchRequestBuilder.get().getRecordSource().getAsMap(); + String status = ObjectPath.eval("result.actions.0.status", responseMap); + assertThat(status, equalTo(Action.Result.Status.SIMULATED.toString().toLowerCase(Locale.ROOT))); + + timeWarp().clock().fastForward(TimeValue.timeValueSeconds(15)); - assertThat(watchRecord.result().actionsResults().get("test_id").action().status(), equalTo(Action.Result.Status.SIMULATED)); - if (timeWarped()) { - timeWarp().clock().fastForward(TimeValue.timeValueSeconds(1)); - } boolean ack = randomBoolean(); if (ack) { - if (useClientForAcking) { - watcherClient().prepareAckWatch("_id").setActionIds("test_id").get(); - } else { - watchService().ackWatch("_id", new String[] { "test_id" }); - } + watcherClient().prepareAckWatch("_id").setActionIds("test_id").get(); } - ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - watchRecord = executionService().execute(ctx); + + executeWatchRequestBuilder = watcherClient().prepareExecuteWatch("_id") + .setRecordExecution(true) + .setActionMode("test_id", ActionExecutionMode.SIMULATE); + responseMap = executeWatchRequestBuilder.get().getRecordSource().getAsMap(); + status = ObjectPath.eval("result.actions.0.status", responseMap); if (ack) { - assertThat(watchRecord.result().actionsResults().get("test_id").action().status(), equalTo(Action.Result.Status.THROTTLED)); + assertThat(status, equalTo(Action.Result.Status.THROTTLED.toString().toLowerCase(Locale.ROOT))); } else { - assertThat(watchRecord.result().actionsResults().get("test_id").action().status(), equalTo(Action.Result.Status.SIMULATED)); + assertThat(status, equalTo(Action.Result.Status.SIMULATED.toString().toLowerCase(Locale.ROOT))); } } public void testRandomMultiActionAckThrottle() throws Exception { - boolean useClientForAcking = randomBoolean(); - WatchSourceBuilder watchSourceBuilder = watchBuilder() .trigger(schedule(interval("60m"))); @@ -105,38 +106,35 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { } } - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); - - assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true)); - ManualExecutionContext ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - executionService().execute(ctx); + watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); + refresh(Watch.INDEX); + executeWatch("_id"); for (String actionId : ackingActions) { - if (useClientForAcking) { - watcherClient().prepareAckWatch("_id").setActionIds(actionId).get(); - } else { - watchService().ackWatch("_id", new String[]{actionId}); - } + watcherClient().prepareAckWatch("_id").setActionIds(actionId).get(); } - if (timeWarped()) { - timeWarp().clock().fastForwardSeconds(5); - } + timeWarp().clock().fastForwardSeconds(15); - ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - WatchRecord watchRecord = executionService().execute(ctx); - for (ActionWrapper.Result result : watchRecord.result().actionsResults().values()) { - if (ackingActions.contains(result.id())) { - assertThat(result.action().status(), equalTo(Action.Result.Status.THROTTLED)); + Map responseMap = executeWatch("_id"); + List> actions = ObjectPath.eval("result.actions", responseMap); + for (Map result : actions) { + if (ackingActions.contains(result.get("id"))) { + assertThat(result.get("status"), equalTo(Action.Result.Status.THROTTLED.toString().toLowerCase(Locale.ROOT))); } else { - assertThat(result.action().status(), equalTo(Action.Result.Status.SIMULATED)); + assertThat(result.get("status"), equalTo(Action.Result.Status.SIMULATED.toString().toLowerCase(Locale.ROOT))); } } } + private Map executeWatch(String id) { + return watcherClient().prepareExecuteWatch(id) + .setRecordExecution(true) + .setActionMode("_all", ActionExecutionMode.SIMULATE).get().getRecordSource().getAsMap(); + } + public void testDifferentThrottlePeriods() throws Exception { + timeWarp().clock().setTime(DateTime.now(DateTimeZone.UTC)); WatchSourceBuilder watchSourceBuilder = watchBuilder() .trigger(schedule(interval("60m"))); @@ -145,46 +143,34 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { watchSourceBuilder.addAction("fifteen_sec_throttle", new TimeValue(15, TimeUnit.SECONDS), randomFrom(AvailableAction.values()).action()); - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); - assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true)); + watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); + refresh(Watch.INDEX); - if (timeWarped()) { - timeWarp().clock().setTime(new DateTime(DateTimeZone.UTC)); + timeWarp().clock().fastForwardSeconds(1); + Map responseMap = executeWatch("_id"); + List> actions = ObjectPath.eval("result.actions", responseMap); + for (Map result : actions) { + assertThat(result.get("status"), equalTo(Action.Result.Status.SIMULATED.toString().toLowerCase(Locale.ROOT))); + } + timeWarp().clock().fastForwardSeconds(1); + + responseMap = executeWatch("_id"); + actions = ObjectPath.eval("result.actions", responseMap); + for (Map result : actions) { + assertThat(result.get("status"), equalTo(Action.Result.Status.THROTTLED.toString().toLowerCase(Locale.ROOT))); } - ManualExecutionContext ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - WatchRecord watchRecord = executionService().execute(ctx); - long firstExecution = System.currentTimeMillis(); - for(ActionWrapper.Result actionResult : watchRecord.result().actionsResults().values()) { - assertThat(actionResult.action().status(), equalTo(Action.Result.Status.SIMULATED)); - } - ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - watchRecord = executionService().execute(ctx); - for(ActionWrapper.Result actionResult : watchRecord.result().actionsResults().values()) { - assertThat(actionResult.action().status(), equalTo(Action.Result.Status.THROTTLED)); - } + timeWarp().clock().fastForwardSeconds(10); - if (timeWarped()) { - timeWarp().clock().fastForwardSeconds(11); - } - - assertBusy(new Runnable() { - @Override - public void run() { - ManualExecutionContext ctx = getManualExecutionContext(new TimeValue(0, TimeUnit.SECONDS)); - WatchRecord watchRecord = executionService().execute(ctx); - for (ActionWrapper.Result actionResult : watchRecord.result().actionsResults().values()) { - if ("ten_sec_throttle".equals(actionResult.id())) { - assertThat(actionResult.action().status(), equalTo(Action.Result.Status.SIMULATED)); - } else { - assertThat(actionResult.action().status(), equalTo(Action.Result.Status.THROTTLED)); - } - } + responseMap = executeWatch("_id"); + actions = ObjectPath.eval("result.actions", responseMap); + for (Map result : actions) { + if ("ten_sec_throttle".equals(result.get("id"))) { + assertThat(result.get("status"), equalTo(Action.Result.Status.SIMULATED.toString().toLowerCase(Locale.ROOT))); + } else { + assertThat(result.get("status"), equalTo(Action.Result.Status.THROTTLED.toString().toLowerCase(Locale.ROOT))); } - }, 11000 - (System.currentTimeMillis() - firstExecution), TimeUnit.MILLISECONDS); - + } } public void testDefaultThrottlePeriod() throws Exception { @@ -194,9 +180,8 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { AvailableAction availableAction = randomFrom(AvailableAction.values()); watchSourceBuilder.addAction("default_global_throttle", availableAction.action()); - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); + watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); + refresh(Watch.INDEX); if (timeWarped()) { timeWarp().clock().setTime(new DateTime(DateTimeZone.UTC)); @@ -208,9 +193,9 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) .setRecordExecution(true) .get(); - Map watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - Object resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("simulated")); + + String status = ObjectPath.eval("result.actions.0.status", executeWatchResponse.getRecordSource().getAsMap()); + assertThat(status, equalTo("simulated")); if (timeWarped()) { timeWarp().clock().fastForwardSeconds(1); @@ -222,30 +207,25 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) .setRecordExecution(true) .get(); - watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("throttled")); + status = ObjectPath.eval("result.actions.0.status", executeWatchResponse.getRecordSource().getAsMap()); + assertThat(status, equalTo("throttled")); if (timeWarped()) { timeWarp().clock().fastForwardSeconds(5); } - assertBusy(new Runnable() { - @Override - public void run() { - try { - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ManualTriggerEvent("execute_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))) - .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) - .setRecordExecution(true) - .get(); - Map watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - Object resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("simulated")); - } catch (IOException ioe) { - throw new ElasticsearchException("failed to execute", ioe); - } + assertBusy(() -> { + try { + ExecuteWatchResponse executeWatchResponse1 = watcherClient().prepareExecuteWatch("_id") + .setTriggerEvent(new ManualTriggerEvent("execute_id", + new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))) + .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) + .setRecordExecution(true) + .get(); + String currentStatus = ObjectPath.eval("result.actions.0.status", executeWatchResponse1.getRecordSource().getAsMap()); + assertThat(currentStatus, equalTo("simulated")); + } catch (IOException ioe) { + throw new ElasticsearchException("failed to execute", ioe); } }, 6, TimeUnit.SECONDS); } @@ -258,9 +238,8 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { AvailableAction availableAction = randomFrom(AvailableAction.values()); watchSourceBuilder.addAction("default_global_throttle", availableAction.action()); - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); + watcherClient().putWatch(new PutWatchRequest("_id", watchSourceBuilder)).actionGet(); + refresh(Watch.INDEX); if (timeWarped()) { timeWarp().clock().setTime(new DateTime(DateTimeZone.UTC)); @@ -272,9 +251,8 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) .setRecordExecution(true) .get(); - Map watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - Object resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("simulated")); + String status = ObjectPath.eval("result.actions.0.status", executeWatchResponse.getRecordSource().getAsMap()); + assertThat(status, equalTo("simulated")); if (timeWarped()) { timeWarp().clock().fastForwardSeconds(1); @@ -286,30 +264,25 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) .setRecordExecution(true) .get(); - watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("throttled")); + status = ObjectPath.eval("result.actions.0.status", executeWatchResponse.getRecordSource().getAsMap()); + assertThat(status, equalTo("throttled")); if (timeWarped()) { timeWarp().clock().fastForwardSeconds(20); } - assertBusy(new Runnable() { - @Override - public void run() { - try { - //Since the default throttle period is 5 seconds but we have overridden the period in the watch this should trigger - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ManualTriggerEvent("execute_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))) - .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) - .setRecordExecution(true) - .get(); - Map watchRecordMap = executeWatchResponse.getRecordSource().getAsMap(); - Object resultStatus = getExecutionStatus(watchRecordMap); - assertThat(resultStatus.toString(), equalTo("simulated")); - } catch (IOException ioe) { - throw new ElasticsearchException("failed to execute", ioe); - } + assertBusy(() -> { + try { + //Since the default throttle period is 5 seconds but we have overridden the period in the watch this should trigger + ExecuteWatchResponse executeWatchResponse1 = watcherClient().prepareExecuteWatch("_id") + .setTriggerEvent(new ManualTriggerEvent("execute_id", + new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))) + .setActionMode("default_global_throttle", ActionExecutionMode.SIMULATE) + .setRecordExecution(true) + .get(); + String status1 = ObjectPath.eval("result.actions.0.status", executeWatchResponse1.getRecordSource().getAsMap()); + assertThat(status1, equalTo("simulated")); + } catch (IOException ioe) { + throw new ElasticsearchException("failed to execute", ioe); } }, 20, TimeUnit.SECONDS); } @@ -317,7 +290,7 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { public void testFailingActionDoesGetThrottled() throws Exception { TimeValue throttlePeriod = new TimeValue(60, TimeUnit.MINUTES); - PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder() + watcherClient().preparePutWatch("_id").setSource(watchBuilder() .trigger(new ScheduleTrigger(new IntervalSchedule( new IntervalSchedule.Interval(60, IntervalSchedule.Interval.Unit.MINUTES)))) .defaultThrottlePeriod(throttlePeriod) @@ -325,46 +298,50 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { // no DNS resolution here please .addAction("failing_hook", webhookAction(HttpRequestTemplate.builder("http://127.0.0.1/foobar", 80)))) .get(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); + refresh(Watch.INDEX); - ManualTriggerEvent triggerEvent = new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, - throttlePeriod); - ctxBuilder.recordExecution(true); + { + Map responseMap = watcherClient().prepareExecuteWatch("_id") + .setRecordExecution(true) + .get().getRecordSource().getAsMap(); - ManualExecutionContext ctx = ctxBuilder.build(); - WatchRecord watchRecord = executionService().execute(ctx); + String state = ObjectPath.eval("state", responseMap); - assertThat(watchRecord.state(), equalTo(ExecutionState.EXECUTED)); - assertThat(watchRecord.result().actionsResults().get("logging").action().status(), equalTo(Action.Result.Status.SUCCESS)); - assertThat(watchRecord.result().actionsResults().get("failing_hook").action().status(), equalTo(Action.Result.Status.FAILURE)); + String firstId = ObjectPath.eval("result.actions.0.id", responseMap); + String statusLogging, statusFailingHook; + if ("logging".equals(firstId)) { + statusLogging = ObjectPath.eval("result.actions.0.status", responseMap); + statusFailingHook = ObjectPath.eval("result.actions.1.status", responseMap); + } else { + statusFailingHook = ObjectPath.eval("result.actions.0.status", responseMap); + statusLogging = ObjectPath.eval("result.actions.1.status", responseMap); + } - triggerEvent = new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - ctxBuilder = ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, throttlePeriod); - ctxBuilder.recordExecution(true); + assertThat(state, equalTo(ExecutionState.EXECUTED.toString().toLowerCase(Locale.ROOT))); + assertThat(statusLogging, equalTo(Action.Result.Status.SUCCESS.toString().toLowerCase(Locale.ROOT))); + assertThat(statusFailingHook, equalTo(Action.Result.Status.FAILURE.toString().toLowerCase(Locale.ROOT))); + } - ctx = ctxBuilder.build(); - watchRecord = executionService().execute(ctx); - assertThat(watchRecord.result().actionsResults().get("logging").action().status(), equalTo(Action.Result.Status.THROTTLED)); - assertThat(watchRecord.result().actionsResults().get("failing_hook").action().status(), equalTo(Action.Result.Status.FAILURE)); - assertThat(watchRecord.state(), equalTo(ExecutionState.THROTTLED)); - } + { + Map responseMap = watcherClient().prepareExecuteWatch("_id") + .setRecordExecution(true) + .get().getRecordSource().getAsMap(); + String state = ObjectPath.eval("state", responseMap); - private String getExecutionStatus(Map watchRecordMap) { - return ObjectPath.eval("result.actions.0.status", watchRecordMap); - } + String firstId = ObjectPath.eval("result.actions.0.id", responseMap); + String statusLogging, statusFailingHook; + if ("logging".equals(firstId)) { + statusLogging = ObjectPath.eval("result.actions.0.status", responseMap); + statusFailingHook = ObjectPath.eval("result.actions.1.status", responseMap); + } else { + statusFailingHook = ObjectPath.eval("result.actions.0.status", responseMap); + statusLogging = ObjectPath.eval("result.actions.1.status", responseMap); + } - private ManualExecutionContext getManualExecutionContext(TimeValue throttlePeriod) { - ManualTriggerEvent triggerEvent = new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - return ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, throttlePeriod) - .executionTime(timeWarped() ? new DateTime(timeWarp().clock().millis()) : new DateTime(Clock.systemUTC().millis())) - .allActionsMode(ActionExecutionMode.SIMULATE) - .recordExecution(true) - .build(); + assertThat(state, equalTo(ExecutionState.THROTTLED.toString().toLowerCase(Locale.ROOT))); + assertThat(statusLogging, equalTo(Action.Result.Status.THROTTLED.toString().toLowerCase(Locale.ROOT))); + assertThat(statusFailingHook, equalTo(Action.Result.Status.FAILURE.toString().toLowerCase(Locale.ROOT))); + } } enum AvailableAction { @@ -386,7 +363,9 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase { WEBHOOK { @Override public Action.Builder action() throws Exception { - HttpRequestTemplate.Builder requestBuilder = HttpRequestTemplate.builder("foo.bar.com", 1234); + HttpRequestTemplate.Builder requestBuilder = HttpRequestTemplate.builder("localhost", 1234) + .path("/") + .method(HttpMethod.GET); return WebhookAction.builder(requestBuilder.build()); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java index b85e1b6ee2b..6dbb6313ec0 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ExecutionServiceTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.watcher.execution; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.lease.Releasable; @@ -28,6 +29,7 @@ import org.elasticsearch.xpack.watcher.history.HistoryStore; import org.elasticsearch.xpack.watcher.history.WatchRecord; import org.elasticsearch.xpack.watcher.input.ExecutableInput; import org.elasticsearch.xpack.watcher.input.Input; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.transform.ExecutableTransform; import org.elasticsearch.xpack.watcher.transform.Transform; @@ -36,7 +38,6 @@ import org.elasticsearch.xpack.watcher.watch.Payload; import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchLockService; import org.elasticsearch.xpack.watcher.watch.WatchStatus; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.Before; @@ -72,7 +73,6 @@ public class ExecutionServiceTests extends ESTestCase { private ExecutableInput input; private Input.Result inputResult; - private WatchStore watchStore; private TriggeredWatchStore triggeredWatchStore; private WatchExecutor executor; private HistoryStore historyStore; @@ -80,6 +80,8 @@ public class ExecutionServiceTests extends ESTestCase { private ExecutionService executionService; private Clock clock; private ThreadPool threadPool; + private WatcherClientProxy client; + private Watch.Parser parser; @Before public void init() throws Exception { @@ -90,7 +92,6 @@ public class ExecutionServiceTests extends ESTestCase { when(inputResult.payload()).thenReturn(payload); when(input.execute(any(WatchExecutionContext.class), any(Payload.class))).thenReturn(inputResult); - watchStore = mock(WatchStore.class); triggeredWatchStore = mock(TriggeredWatchStore.class); historyStore = mock(HistoryStore.class); @@ -100,8 +101,11 @@ public class ExecutionServiceTests extends ESTestCase { watchLockService = mock(WatchLockService.class); clock = ClockMock.frozen(); threadPool = mock(ThreadPool.class); - executionService = new ExecutionService(Settings.EMPTY, historyStore, triggeredWatchStore, executor, watchStore, - watchLockService, clock, threadPool); + + client = mock(WatcherClientProxy.class); + parser = mock(Watch.Parser.class); + executionService = new ExecutionService(Settings.EMPTY, historyStore, triggeredWatchStore, executor, watchLockService, clock, + threadPool, parser, client); ClusterState clusterState = mock(ClusterState.class); when(triggeredWatchStore.loadTriggeredWatches(clusterState)).thenReturn(new ArrayList<>()); @@ -114,7 +118,9 @@ public class ExecutionServiceTests extends ESTestCase { Watch watch = mock(Watch.class); when(watch.id()).thenReturn("_id"); - when(watchStore.get("_id")).thenReturn(watch); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(client.getWatch("_id")).thenReturn(getResponse); DateTime now = new DateTime(clock.millis()); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -205,9 +211,12 @@ public class ExecutionServiceTests extends ESTestCase { Releasable releasable = mock(Releasable.class); when(watchLockService.acquire("_id")).thenReturn(releasable); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(client.getWatch("_id")).thenReturn(getResponse); + Watch watch = mock(Watch.class); when(watch.id()).thenReturn("_id"); - when(watchStore.get("_id")).thenReturn(watch); DateTime now = new DateTime(clock.millis()); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -277,7 +286,9 @@ public class ExecutionServiceTests extends ESTestCase { Watch watch = mock(Watch.class); when(watch.id()).thenReturn("_id"); - when(watchStore.get("_id")).thenReturn(watch); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(client.getWatch("_id")).thenReturn(getResponse); DateTime now = new DateTime(clock.millis()); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -343,7 +354,9 @@ public class ExecutionServiceTests extends ESTestCase { Watch watch = mock(Watch.class); when(watch.id()).thenReturn("_id"); - when(watchStore.get("_id")).thenReturn(watch); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(client.getWatch("_id")).thenReturn(getResponse); DateTime now = new DateTime(clock.millis()); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -408,7 +421,9 @@ public class ExecutionServiceTests extends ESTestCase { Watch watch = mock(Watch.class); when(watch.id()).thenReturn("_id"); - when(watchStore.get("_id")).thenReturn(watch); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(client.getWatch("_id")).thenReturn(getResponse); DateTime now = new DateTime(clock.millis()); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -773,7 +788,11 @@ public class ExecutionServiceTests extends ESTestCase { Watch watch = mock(Watch.class); when(watch.id()).thenReturn("foo"); when(watch.nonce()).thenReturn(1L); - when(watchStore.get(any())).thenReturn(watch); + GetResponse getResponse = mock(GetResponse.class); + when(getResponse.isExists()).thenReturn(true); + when(getResponse.getId()).thenReturn("foo"); + when(client.getWatch(any())).thenReturn(getResponse); + when(parser.parseWithSecrets(eq("foo"), eq(true), any(), any())).thenReturn(watch); // execute needs to fail as well as storing the history doThrow(new EsRejectedExecutionException()).when(executor).execute(any()); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ManualExecutionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ManualExecutionTests.java index 823ae59ad71..bd96904640b 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ManualExecutionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/execution/ManualExecutionTests.java @@ -5,54 +5,40 @@ */ package org.elasticsearch.xpack.watcher.execution; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.MockScriptPlugin; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; -import org.elasticsearch.xpack.watcher.WatcherService; import org.elasticsearch.xpack.watcher.actions.ActionStatus; -import org.elasticsearch.xpack.watcher.actions.logging.LoggingAction; import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder; import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; import org.elasticsearch.xpack.watcher.condition.NeverCondition; import org.elasticsearch.xpack.watcher.condition.ScriptCondition; import org.elasticsearch.xpack.watcher.history.HistoryStore; -import org.elasticsearch.xpack.watcher.history.WatchRecord; -import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; import org.elasticsearch.xpack.watcher.support.xcontent.ObjectPath; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; -import org.elasticsearch.xpack.watcher.transport.actions.delete.DeleteWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchRequestBuilder; import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchRequest; -import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; -import org.elasticsearch.xpack.watcher.trigger.manual.ManualTriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; -import org.elasticsearch.xpack.watcher.watch.Payload; import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.time.Clock; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import static java.util.Collections.singletonMap; -import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.loggingAction; import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; @@ -61,13 +47,9 @@ import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule; import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.cron; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { @@ -125,79 +107,44 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .condition(conditionAlwaysTrue ? AlwaysCondition.INSTANCE : NeverCondition.INSTANCE) .addAction("log", loggingAction("foobar")); - ManualExecutionContext.Builder ctxBuilder; - Watch parsedWatch = null; - ManualTriggerEvent triggerEvent = new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - if (recordExecution) { - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); - assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true)); - //If we are persisting the state we need to use the exact watch that is in memory - ctxBuilder = ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, timeValueSeconds(5)); - } else { - parsedWatch = watchParser().parse("_id", false, watchBuilder.buildAsBytes(XContentType.JSON)); - ctxBuilder = ManualExecutionContext.builder(parsedWatch, false, triggerEvent, timeValueSeconds(5)); - } + watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); - if (ignoreCondition) { - ctxBuilder.withCondition(AlwaysCondition.RESULT_INSTANCE); - } - - ctxBuilder.recordExecution(recordExecution); - - - if ("_all".equals(action)) { - ctxBuilder.allActionsMode(ActionExecutionMode.SIMULATE); - } else { - ctxBuilder.actionMode(action, ActionExecutionMode.SIMULATE); - } - - ManualExecutionContext ctx = ctxBuilder.build(); + ExecuteWatchRequestBuilder executeWatchRequestBuilder = watcherClient().prepareExecuteWatch("_id"); + executeWatchRequestBuilder.setIgnoreCondition(ignoreCondition); + executeWatchRequestBuilder.setRecordExecution(recordExecution); + executeWatchRequestBuilder.setActionMode(action, ActionExecutionMode.SIMULATE); refresh(); long oldRecordCount = docCount(HistoryStore.INDEX_PREFIX_WITH_TEMPLATE + "*", HistoryStore.DOC_TYPE, matchAllQuery()); - WatchRecord watchRecord = executionService().execute(ctx); + ExecuteWatchResponse executeWatchResponse = executeWatchRequestBuilder.get(); + Map responseMap = executeWatchResponse.getRecordSource().getAsMap(); refresh(); - long newRecordCount = docCount(HistoryStore.INDEX_PREFIX_WITH_TEMPLATE + "*", HistoryStore.DOC_TYPE, matchAllQuery()); long expectedCount = oldRecordCount + (recordExecution ? 1 : 0); assertThat("the expected count of history records should be [" + expectedCount + "]", newRecordCount, equalTo(expectedCount)); + List> actions = ObjectPath.eval("result.actions", responseMap); if (ignoreCondition) { - assertThat("The action should have run", watchRecord.result().actionsResults().size(), equalTo(1)); + assertThat("The action should have run", actions.size(), equalTo(1)); } else if (!conditionAlwaysTrue) { - assertThat("The action should not have run", watchRecord.result().actionsResults().size(), equalTo(0)); + assertThat("The action should not have run", actions.size(), equalTo(0)); } - if ((ignoreCondition || conditionAlwaysTrue) && action == null) { - assertThat("The action should have run non simulated", watchRecord.result().actionsResults().get("log").action(), - not(instanceOf(LoggingAction.Result.Simulated.class)) ); + if (ignoreCondition || conditionAlwaysTrue) { + assertThat("The action should have run simulated", actions.get(0).get("status"), is("simulated")); } - if ((ignoreCondition || conditionAlwaysTrue) && action != null ) { - assertThat("The action should have run simulated", - watchRecord.result().actionsResults().get("log").action(), instanceOf(LoggingAction.Result.Simulated.class)); - } - - Watch testWatch = watchService().getWatch("_id"); if (recordExecution) { - refresh(); + GetWatchResponse response = watcherClient().getWatch(new GetWatchRequest("_id")).actionGet(); if (ignoreCondition || conditionAlwaysTrue) { - assertThat(testWatch.status().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.ACKABLE)); - GetWatchResponse response = watcherClient().getWatch(new GetWatchRequest("_id")).actionGet(); - assertThat(response.getStatus().actionStatus("log").ackStatus().state(), equalTo(ActionStatus.AckStatus.State.ACKABLE)); + assertThat(response.getStatus().actionStatus("log").ackStatus().state(), is(ActionStatus.AckStatus.State.ACKABLE)); } else { - assertThat(testWatch.status().actionStatus("log").ackStatus().state(), - equalTo(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); + assertThat(response.getStatus().actionStatus("log").ackStatus().state(), + is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); } - } else { - assertThat(parsedWatch.status().actionStatus("log").ackStatus().state(), - equalTo(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); } } @@ -214,7 +161,8 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { builder.setRecordExecution(false); } if (randomBoolean()) { - builder.setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); + DateTime now = new DateTime(DateTimeZone.UTC); + builder.setTriggerEvent(new ScheduleTriggerEvent(now, now)); } ExecuteWatchResponse executeWatchResponse = builder.get(); @@ -231,16 +179,13 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .condition(AlwaysCondition.INSTANCE) .addAction("log", loggingAction("foobar")); - try { - watcherClient().prepareExecuteWatch() - .setWatchSource(watchBuilder) - .setRecordExecution(true) - .setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))) - .get(); - fail(); - } catch (ActionRequestValidationException e) { - assertThat(e.getMessage(), containsString("the execution of an inline watch cannot be recorded")); - } + ActionRequestValidationException e = expectThrows(ActionRequestValidationException.class, () -> + watcherClient().prepareExecuteWatch() + .setWatchSource(watchBuilder) + .setRecordExecution(true) + .setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))) + .get()); + assertThat(e.getMessage(), containsString("the execution of an inline watch cannot be recorded")); } public void testExecutionWithInlineWatch_withWatchId() throws Exception { @@ -269,10 +214,8 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .trigger(schedule(cron("0 0 0 1 * ? 2099"))) .addAction("log", loggingAction("foobar")); - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); - assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true)); + watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); + refresh(Watch.INDEX); Map map1 = new HashMap<>(); map1.put("foo", "bar"); @@ -280,28 +223,24 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { Map map2 = new HashMap<>(); map2.put("foo", map1); - ManualTriggerEvent triggerEvent = new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - ManualExecutionContext.Builder ctxBuilder1 = ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, - timeValueSeconds(5)); - ctxBuilder1.actionMode("_all", ActionExecutionMode.SIMULATE); + ExecuteWatchResponse firstResponse = watcherClient().prepareExecuteWatch("_id") + .setActionMode("_all", ActionExecutionMode.SIMULATE) + .setAlternativeInput(map1) + .setRecordExecution(true) + .setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))) + .get(); - ctxBuilder1.withInput(new SimpleInput.Result(new Payload.Simple(map1))); - ctxBuilder1.recordExecution(true); + ExecuteWatchResponse secondResponse = watcherClient().prepareExecuteWatch("_id") + .setActionMode("_all", ActionExecutionMode.SIMULATE) + .setAlternativeInput(map2) + .setRecordExecution(true) + .setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))) + .get(); - WatchRecord watchRecord1 = executionService().execute(ctxBuilder1.build()); - - ManualExecutionContext.Builder ctxBuilder2 = ManualExecutionContext.builder(watchService().getWatch("_id"), true, triggerEvent, - timeValueSeconds(5)); - ctxBuilder2.actionMode("_all", ActionExecutionMode.SIMULATE); - - ctxBuilder2.withInput(new SimpleInput.Result(new Payload.Simple(map2))); - ctxBuilder2.recordExecution(true); - - WatchRecord watchRecord2 = executionService().execute(ctxBuilder2.build()); - - assertThat(watchRecord1.result().inputResult().payload().data().get("foo").toString(), equalTo("bar")); - assertThat(watchRecord2.result().inputResult().payload().data().get("foo"), instanceOf(Map.class)); + String firstPayload = ObjectPath.eval("result.input.payload.foo", firstResponse.getRecordSource().getAsMap()); + assertThat(firstPayload, is("bar")); + Map secondPayload = ObjectPath.eval("result.input.payload", secondResponse.getRecordSource().getAsMap()); + assertThat(secondPayload, instanceOf(Map.class)); } public void testExecutionRequestDefaults() throws Exception { @@ -309,7 +248,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .trigger(schedule(cron("0 0 0 1 * ? 2099"))) .input(simpleInput("foo", "bar")) .condition(NeverCondition.INSTANCE) - .defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS)) + .defaultThrottlePeriod(TimeValue.timeValueHours(1)) .addAction("log", loggingAction("foobar")); watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); @@ -328,7 +267,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .trigger(schedule(cron("0 0 0 1 * ? 2099"))) .input(simpleInput("foo", "bar")) .condition(AlwaysCondition.INSTANCE) - .defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS)) + .defaultThrottlePeriod(TimeValue.timeValueHours(1)) .addAction("log", loggingAction("foobar")); watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); @@ -355,90 +294,13 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase { .condition(new ScriptCondition(script)) .addAction("log", loggingAction("foobar")); - Watch watch = watchParser().parse("_id", false, watchBuilder.buildAsBytes(XContentType.JSON)); - ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, false, new ManualTriggerEvent("_id", - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))), - new TimeValue(1, TimeUnit.HOURS)); - WatchRecord record = executionService().execute(ctxBuilder.build()); - assertThat(record.result().executionDurationMs(), greaterThanOrEqualTo(100L)); + watcherClient().preparePutWatch("_id").setSource(watchBuilder).get(); + refresh(Watch.INDEX); + + ScheduleTriggerEvent triggerEvent = new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)); + ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id").setTriggerEvent(triggerEvent).get(); + Integer duration = ObjectPath.eval("result.execution_duration", executeWatchResponse.getRecordSource().getAsMap()); + + assertThat(duration, greaterThanOrEqualTo(100)); } - - public void testForceDeletionOfLongRunningWatch() throws Exception { - Script script = new Script(ScriptType.INLINE, WATCHER_LANG, "sleep", singletonMap("millis", 10000L)); - WatchSourceBuilder watchBuilder = watchBuilder() - .trigger(schedule(cron("0 0 0 1 * ? 2099"))) - .input(simpleInput("foo", "bar")) - .condition(new ScriptCondition(script)) - .defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS)) - .addAction("log", loggingAction("foobar")); - - PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet(); - assertThat(putWatchResponse.getVersion(), greaterThan(0L)); - refresh(); - assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true)); - - int numberOfThreads = scaledRandomIntBetween(1, 5); - CountDownLatch startLatch = new CountDownLatch(1); - - List threads = new ArrayList<>(); - for (int i = 0; i < numberOfThreads; ++i) { - threads.add(new Thread(new ExecutionRunner(watchService(), executionService(), "_id", startLatch))); - } - - for (Thread thread : threads) { - thread.start(); - } - DeleteWatchResponse deleteWatchResponse = watcherClient().prepareDeleteWatch("_id").get(); - assertThat(deleteWatchResponse.isFound(), is(true)); - - deleteWatchResponse = watcherClient().prepareDeleteWatch("_id").get(); - assertThat(deleteWatchResponse.isFound(), is(false)); - - startLatch.countDown(); - - long startJoin = System.currentTimeMillis(); - for (Thread thread : threads) { - thread.join(30_000L); - assertFalse(thread.isAlive()); - } - long endJoin = System.currentTimeMillis(); - TimeValue tv = new TimeValue(10 * (numberOfThreads+1), TimeUnit.SECONDS); - assertThat("Shouldn't take longer than [" + tv.getSeconds() + "] seconds for all the threads to stop", (endJoin - startJoin), - lessThan(tv.getMillis())); - } - - public static class ExecutionRunner implements Runnable { - - final WatcherService watcherService; - final ExecutionService executionService; - final String watchId; - final CountDownLatch startLatch; - final ManualExecutionContext.Builder ctxBuilder; - - public ExecutionRunner(WatcherService watcherService, ExecutionService executionService, String watchId, - CountDownLatch startLatch) { - this.watcherService = watcherService; - this.executionService = executionService; - this.watchId = watchId; - this.startLatch = startLatch; - ManualTriggerEvent triggerEvent = new ManualTriggerEvent(watchId, - new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))); - ctxBuilder = ManualExecutionContext.builder(watcherService.getWatch(watchId), true, triggerEvent, timeValueSeconds(5)); - ctxBuilder.recordExecution(true); - ctxBuilder.actionMode("_all", ActionExecutionMode.FORCE_EXECUTE); - } - - @Override - public void run() { - try { - startLatch.await(); - WatchRecord record = executionService.execute(ctxBuilder.build()); - assertThat(record, notNullValue()); - assertThat(record.state(), is(ExecutionState.NOT_EXECUTED_WATCH_MISSING)); - } catch (Exception e) { - throw new ElasticsearchException("Failure mode execution of [{}] failed in an unexpected way", e, watchId); - } - } - } - } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputIntegrationTests.java index d78af52f78a..79201522b8d 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputIntegrationTests.java @@ -11,14 +11,13 @@ import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.transport.Netty4Plugin; -import org.elasticsearch.xpack.watcher.client.WatcherClient; -import org.elasticsearch.xpack.watcher.condition.CompareCondition; -import org.elasticsearch.xpack.watcher.history.HistoryStore; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth; import org.elasticsearch.xpack.common.text.TextTemplate; +import org.elasticsearch.xpack.watcher.client.WatcherClient; +import org.elasticsearch.xpack.watcher.condition.CompareCondition; +import org.elasticsearch.xpack.watcher.history.HistoryStore; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; @@ -57,7 +56,6 @@ public class HttpInputIntegrationTests extends AbstractWatcherIntegrationTestCas return plugins; } - @TestLogging("org.elasticsearch.watcher.support.http:TRACE") public void testHttpInput() throws Exception { createIndex("index"); client().prepareIndex("index", "type", "id").setSource("{}").setRefreshPolicy(IMMEDIATE).get(); @@ -71,7 +69,7 @@ public class HttpInputIntegrationTests extends AbstractWatcherIntegrationTestCas .body(jsonBuilder().startObject().field("size", 1).endObject().string()) .auth(securityEnabled() ? new BasicAuth("test", "changeme".toCharArray()) : null))) .condition(new CompareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 1L)) - .addAction("_id", loggingAction("watch [{{ctx.watch_id}}] matched"))) + .addAction("_id", loggingAction("anything"))) .get(); if (timeWarped()) { @@ -90,7 +88,7 @@ public class HttpInputIntegrationTests extends AbstractWatcherIntegrationTestCas .path("/_cluster/stats") .auth(securityEnabled() ? new BasicAuth("test", "changeme".toCharArray()) : null))) .condition(new CompareCondition("ctx.payload.nodes.count.total", CompareCondition.Op.GTE, 1L)) - .addAction("_id", loggingAction("watch [{{ctx.watch_id}}] matched"))) + .addAction("_id", loggingAction("anything"))) .get(); assertTrue(putWatchResponse.isCreated()); @@ -101,7 +99,6 @@ public class HttpInputIntegrationTests extends AbstractWatcherIntegrationTestCas assertWatchWithMinimumPerformedActionsCount("_name", 1, false); } - @TestLogging("org.elasticsearch.watcher.support.http:TRACE") public void testInputFiltering() throws Exception { WatcherClient watcherClient = watcherClient(); createIndex("idx"); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/security/BasicSecurityTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/security/BasicSecurityTests.java index b390047e077..8a9f68fc361 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/security/BasicSecurityTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/security/BasicSecurityTests.java @@ -18,10 +18,12 @@ import org.elasticsearch.xpack.watcher.transport.actions.stats.WatcherStatsRespo import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import java.util.Collections; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule; @@ -56,6 +58,8 @@ public class BasicSecurityTests extends AbstractWatcherIntegrationTestCase { } public void testWatcherMonitorRole() throws Exception { + assertAcked(client().admin().indices().prepareCreate(Watch.INDEX)); + // stats and get watch apis require at least monitor role: String token = basicAuthHeaderValue("test", new SecuredString("changeme".toCharArray())); try { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java index 1aee712f9fd..26f28b0a30e 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java @@ -10,6 +10,7 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.IndicesOptions; @@ -69,11 +70,10 @@ import org.elasticsearch.xpack.watcher.execution.ExecutionState; import org.elasticsearch.xpack.watcher.execution.TriggeredWatchStore; import org.elasticsearch.xpack.watcher.history.HistoryStore; import org.elasticsearch.xpack.watcher.support.WatcherIndexTemplateRegistry; +import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.trigger.ScheduleTriggerEngineMock; -import org.elasticsearch.xpack.watcher.trigger.TriggerService; import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.hamcrest.Matcher; import org.junit.After; import org.junit.AfterClass; @@ -91,6 +91,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -128,8 +129,8 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase protected TestCluster buildTestCluster(Scope scope, long seed) throws IOException { if (securityEnabled == null) { securityEnabled = enableSecurity(); - scheduleEngineName = randomFrom("ticker", "scheduler"); } + scheduleEngineName = randomFrom("ticker", "scheduler"); return super.buildTestCluster(scope, seed); } @@ -327,7 +328,7 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase CreateIndexResponse response = client().admin().indices().prepareCreate(newIndex) .setCause("Index to test aliases with .watches index") - .addAlias(new Alias(WatchStore.INDEX)) + .addAlias(new Alias(Watch.INDEX)) .setSettings((Map) parserMap.get("settings")) .addMapping("watch", (Map) allMappings.get("watch")) .get(); @@ -395,18 +396,6 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase return getInstanceFromMaster(Watch.Parser.class); } - protected ExecutionService executionService() { - return getInstanceFromMaster(ExecutionService.class); - } - - protected WatcherService watchService() { - return getInstanceFromMaster(WatcherService.class); - } - - protected TriggerService triggerService() { - return getInstanceFromMaster(TriggerService.class); - } - public AbstractWatcherIntegrationTestCase() { super(); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/ScheduleEngineTriggerBenchmark.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/ScheduleEngineTriggerBenchmark.java index e72837af034..aff9da5a106 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/ScheduleEngineTriggerBenchmark.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/ScheduleEngineTriggerBenchmark.java @@ -9,14 +9,12 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.watcher.trigger.Trigger; import org.elasticsearch.xpack.watcher.trigger.TriggerEngine; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; -import org.elasticsearch.xpack.watcher.trigger.schedule.Schedule; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleRegistry; -import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEngine; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; +import org.elasticsearch.xpack.watcher.trigger.schedule.engine.BaseTriggerEngineTestCase; import org.elasticsearch.xpack.watcher.trigger.schedule.engine.SchedulerScheduleTriggerEngine; import org.elasticsearch.xpack.watcher.trigger.schedule.engine.TickerScheduleTriggerEngine; @@ -59,7 +57,7 @@ public class ScheduleEngineTriggerBenchmark { .build(); List jobs = new ArrayList<>(numWatches); for (int i = 0; i < numWatches; i++) { - jobs.add(new SimpleJob("job_" + i, interval(interval + "s"))); + jobs.add(new BaseTriggerEngineTestCase.SimpleJob("job_" + i, interval(interval + "s"))); } ScheduleRegistry scheduleRegistry = new ScheduleRegistry(emptySet()); List impls = new ArrayList<>(Arrays.asList(new String[]{"schedule", "ticker"})); @@ -143,28 +141,6 @@ public class ScheduleEngineTriggerBenchmark { } } - - static class SimpleJob implements TriggerEngine.Job { - - private final String name; - private final ScheduleTrigger trigger; - - public SimpleJob(String name, Schedule schedule) { - this.name = name; - this.trigger = new ScheduleTrigger(schedule); - } - - @Override - public String id() { - return name; - } - - @Override - public Trigger trigger() { - return trigger; - } - } - static class Stats { final String implementation; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherScheduleEngineBenchmark.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherScheduleEngineBenchmark.java index 4933f8dd8c8..cdf41561a35 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherScheduleEngineBenchmark.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/bench/WatcherScheduleEngineBenchmark.java @@ -33,7 +33,7 @@ import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder; import org.elasticsearch.xpack.watcher.client.WatcherClient; import org.elasticsearch.xpack.watcher.condition.ScriptCondition; import org.elasticsearch.xpack.watcher.history.HistoryStore; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.XPackPlugin; import java.io.IOException; @@ -106,7 +106,7 @@ public class WatcherScheduleEngineBenchmark { System.out.println("===============> indexing [" + numWatches + "] watches"); for (int i = 0; i < numWatches; i++) { final String id = "_id_" + i; - client.prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, id) + client.prepareIndex(Watch.INDEX, Watch.DOC_TYPE, id) .setSource(new WatchSourceBuilder() .trigger(schedule(interval(interval + "s"))) .input(searchInput(templateRequest(new SearchSourceBuilder(), "test"))) @@ -115,7 +115,7 @@ public class WatcherScheduleEngineBenchmark { .buildAsBytes(XContentType.JSON) ).get(); } - client.admin().indices().prepareFlush(WatchStore.INDEX, "test").get(); + client.admin().indices().prepareFlush(Watch.INDEX, "test").get(); System.out.println("===============> indexed [" + numWatches + "] watches"); } } @@ -137,7 +137,7 @@ public class WatcherScheduleEngineBenchmark { try (final Client client = node.client()) { client.admin().cluster().prepareHealth().setWaitForNodes("2").get(); client.admin().indices().prepareDelete(HistoryStore.INDEX_PREFIX_WITH_TEMPLATE + "*").get(); - client.admin().cluster().prepareHealth(WatchStore.INDEX, "test").setWaitForYellowStatus().get(); + client.admin().cluster().prepareHealth(Watch.INDEX, "test").setWaitForYellowStatus().get(); Clock clock = node.injector().getInstance(Clock.class); WatcherClient watcherClient = node.injector().getInstance(WatcherClient.class); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java index 013185e6aaf..e620106b3bf 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder; import org.elasticsearch.xpack.watcher.client.WatcherClient; import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; @@ -27,11 +28,10 @@ import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule; import org.elasticsearch.xpack.watcher.trigger.schedule.Schedules; import org.elasticsearch.xpack.watcher.trigger.schedule.support.MonthTimes; import org.elasticsearch.xpack.watcher.trigger.schedule.support.WeekTimes; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import java.time.Clock; - import java.util.Collections; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -61,6 +61,11 @@ import static org.hamcrest.Matchers.notNullValue; public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { + @Override + protected boolean enableSecurity() { + return false; + } + @Override protected boolean timeWarped() { return true; @@ -130,7 +135,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { assertThat(deleteWatchResponse.isFound(), is(true)); refresh(); - assertHitCount(client().prepareSearch(WatchStore.INDEX).setSize(0).get(), 0L); + assertHitCount(client().prepareSearch(Watch.INDEX).setSize(0).get(), 0L); // Deleting the same watch for the second time deleteWatchResponse = watcherClient.prepareDeleteWatch("_name").get(); @@ -163,7 +168,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { // In watch store we fail parsing if an watch contains undefined fields. } try { - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_name") + client().prepareIndex(Watch.INDEX, Watch.DOC_TYPE, "_name") .setSource(watchSource) .get(); fail(); @@ -247,6 +252,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { equalTo(before)); } + @TestLogging("org.elasticsearch.xpack.watcher.trigger:DEBUG") public void testConditionSearchWithSource() throws Exception { SearchSourceBuilder searchSourceBuilder = searchSource().query(matchQuery("level", "a")); testConditionSearch(templateRequest(searchSourceBuilder, "events")); @@ -394,6 +400,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { .get(); refresh(); + timeWarp().clock().fastForwardSeconds(1); timeWarp().scheduler().trigger(watchName); assertWatchWithNoActionNeeded(watchName, 1); @@ -401,6 +408,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { .setSource("level", "b") .get(); refresh(); + timeWarp().clock().fastForwardSeconds(1); timeWarp().scheduler().trigger(watchName); assertWatchWithNoActionNeeded(watchName, 2); @@ -408,6 +416,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { .setSource("level", "a") .get(); refresh(); + timeWarp().clock().fastForwardSeconds(1); timeWarp().scheduler().trigger(watchName); assertWatchWithMinimumPerformedActionsCount(watchName, 1); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BootStrapTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BootStrapTests.java index 6367546bf02..e5170a90db9 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BootStrapTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BootStrapTests.java @@ -7,13 +7,13 @@ package org.elasticsearch.xpack.watcher.test.integration; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xpack.watcher.WatcherState; -import org.elasticsearch.xpack.watcher.condition.Condition; import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; import org.elasticsearch.xpack.watcher.condition.CompareCondition; +import org.elasticsearch.xpack.watcher.condition.Condition; import org.elasticsearch.xpack.watcher.execution.ExecutionState; import org.elasticsearch.xpack.watcher.execution.TriggeredWatch; import org.elasticsearch.xpack.watcher.execution.TriggeredWatchStore; @@ -25,7 +25,6 @@ import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.transport.actions.stats.WatcherStatsResponse; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.hamcrest.Matchers; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -36,6 +35,7 @@ import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDI import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.indexAction; import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; @@ -51,62 +51,11 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { @Override protected boolean timeWarped() { - // timewarping isn't necessary here, because we aren't testing triggering or throttling return false; } - public void testLoadMalformedWatch() throws Exception { - // valid watch - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id0") - .setSource(jsonBuilder().startObject() - .startObject(Watch.Field.TRIGGER.getPreferredName()) - .startObject("schedule") - .field("interval", "1s") - .endObject() - .endObject() - .startObject(Watch.Field.ACTIONS.getPreferredName()) - .endObject() - .endObject()) - .get(); - - // invalid interval - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id2") - .setSource(jsonBuilder().startObject() - .startObject(Watch.Field.TRIGGER.getPreferredName()) - .startObject("schedule") - .field("interval", true) - .endObject() - .endObject() - .startObject(Watch.Field.ACTIONS.getPreferredName()) - .endObject() - .endObject()) - .get(); - - // illegal top level field - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id3") - .setSource(jsonBuilder().startObject() - .startObject(Watch.Field.TRIGGER.getPreferredName()) - .startObject("schedule") - .field("interval", "1s") - .endObject() - .startObject("illegal_field").endObject() - .endObject() - .startObject(Watch.Field.ACTIONS.getPreferredName()).endObject() - .endObject()) - .get(); - - stopWatcher(); - startWatcher(); - - WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); - assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); - // Only the valid watch should been loaded - assertThat(response.getWatchesCount(), equalTo(1L)); - assertThat(watcherClient().prepareGetWatch("_id0").get().getId(), Matchers.equalTo("_id0")); - } - public void testLoadMalformedWatchRecord() throws Exception { - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id") + client().prepareIndex(Watch.INDEX, Watch.DOC_TYPE, "_id") .setSource(jsonBuilder().startObject() .startObject(Watch.Field.TRIGGER.getPreferredName()) .startObject("schedule") @@ -185,6 +134,8 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { } public void testDeletedWhileQueued() throws Exception { + assertAcked(client().admin().indices().prepareCreate(".watches")); + DateTime now = DateTime.now(UTC); Wid wid = new Wid("_id", 1, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); @@ -215,7 +166,7 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { WatcherSearchTemplateRequest request = templateRequest(searchSource().query(termQuery("field", "value")), "my-index"); for (int i = 0; i < numWatches; i++) { - client().prepareIndex(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id" + i) + client().prepareIndex(Watch.INDEX, Watch.DOC_TYPE, "_id" + i) .setSource(watchBuilder() .trigger(schedule(cron("0 0/5 * * * ? 2050"))) .input(searchInput(request)) @@ -235,15 +186,19 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { assertThat(response.getWatchesCount(), equalTo((long) numWatches)); } - @TestLogging("org.elasticsearch.watcher.actions:DEBUG") public void testTriggeredWatchLoading() throws Exception { createIndex("output"); + client().prepareIndex("my-index", "foo", "bar") + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource("field", "value").get(); + WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); assertThat(response.getWatchesCount(), equalTo(0L)); WatcherSearchTemplateRequest request = templateRequest(searchSource().query(termQuery("field", "value")), "my-index"); + int numWatches = 8; for (int i = 0; i < numWatches; i++) { String watchId = "_id" + i; @@ -273,27 +228,27 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { stopWatcher(); startWatcher(); - assertBusy(new Runnable() { + assertBusy(() -> { + // We need to wait until all the records are processed from the internal execution queue, only then we can assert + // that numRecords watch records have been processed as part of starting up. + WatcherStatsResponse response1 = watcherClient().prepareWatcherStats().get(); + assertThat(response1.getWatcherState(), equalTo(WatcherState.STARTED)); + assertThat(response1.getThreadPoolQueueSize(), equalTo(0L)); - @Override - public void run() { - // We need to wait until all the records are processed from the internal execution queue, only then we can assert - // that numRecords watch records have been processed as part of starting up. - WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); - assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); - assertThat(response.getThreadPoolQueueSize(), equalTo(0L)); - - // but even then since the execution of the watch record is async it may take a little bit before - // the actual documents are in the output index - refresh(); - SearchResponse searchResponse = client().prepareSearch("output").get(); - assertHitCount(searchResponse, numRecords); - } + // but even then since the execution of the watch record is async it may take a little bit before + // the actual documents are in the output index + refresh(); + SearchResponse searchResponse = client().prepareSearch("output").get(); + assertHitCount(searchResponse, numRecords); }, 30, TimeUnit.SECONDS); } public void testMixedTriggeredWatchLoading() throws Exception { createIndex("output"); + client().prepareIndex("my-index", "foo", "bar") + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource("field", "value").get(); + WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); assertThat(response.getWatchesCount(), equalTo(0L)); @@ -324,21 +279,18 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { stopWatcher(); startWatcher(); - assertBusy(new Runnable() { - @Override - public void run() { - // We need to wait until all the records are processed from the internal execution queue, only then we can assert - // that numRecords watch records have been processed as part of starting up. - WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); - assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); - assertThat(response.getThreadPoolQueueSize(), equalTo(0L)); + assertBusy(() -> { + // We need to wait until all the records are processed from the internal execution queue, only then we can assert + // that numRecords watch records have been processed as part of starting up. + WatcherStatsResponse response1 = watcherClient().prepareWatcherStats().get(); + assertThat(response1.getWatcherState(), equalTo(WatcherState.STARTED)); + assertThat(response1.getThreadPoolQueueSize(), equalTo(0L)); - // but even then since the execution of the watch record is async it may take a little bit before - // the actual documents are in the output index - refresh(); - SearchResponse searchResponse = client().prepareSearch("output").get(); - assertHitCount(searchResponse, numRecords); - } + // but even then since the execution of the watch record is async it may take a little bit before + // the actual documents are in the output index + refresh(); + SearchResponse searchResponse = client().prepareSearch("output").get(); + assertHitCount(searchResponse, numRecords); }); } @@ -353,10 +305,16 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { assertThat(response.getWatcherMetaData().manuallyStopped(), is(false)); } + @Override + protected boolean enableSecurity() { + return false; + } + public void testWatchRecordSavedTwice() throws Exception { // Watcher could prevent to start if a watch record tried to executed twice or more and the watch didn't exist // for that watch record or the execution threadpool rejected the watch record. // A watch record without a watch is the easiest to simulate, so that is what this test does. + assertAcked(client().admin().indices().prepareCreate(Watch.INDEX)); DateTime triggeredTime = new DateTime(2015, 11, 5, 0, 0, 0, 0, DateTimeZone.UTC); final String watchRecordIndex = HistoryStore.getHistoryIndexNameForTime(triggeredTime); @@ -380,24 +338,21 @@ public class BootStrapTests extends AbstractWatcherIntegrationTestCase { stopWatcher(); startWatcher(); - assertBusy(new Runnable() { - @Override - public void run() { - // We need to wait until all the records are processed from the internal execution queue, only then we can assert - // that numRecords watch records have been processed as part of starting up. - WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); - assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); - assertThat(response.getThreadPoolQueueSize(), equalTo(0L)); + assertBusy(() -> { + // We need to wait until all the records are processed from the internal execution queue, only then we can assert + // that numRecords watch records have been processed as part of starting up. + WatcherStatsResponse response = watcherClient().prepareWatcherStats().get(); + assertThat(response.getWatcherState(), equalTo(WatcherState.STARTED)); + assertThat(response.getThreadPoolQueueSize(), equalTo(0L)); - // but even then since the execution of the watch record is async it may take a little bit before - // the actual documents are in the output index - refresh(); - SearchResponse searchResponse = client().prepareSearch(watchRecordIndex).setSize(numRecords).get(); - assertThat(searchResponse.getHits().getTotalHits(), Matchers.equalTo((long) numRecords)); - for (int i = 0; i < numRecords; i++) { - assertThat(searchResponse.getHits().getAt(i).getSource().get("state"), Matchers.equalTo("executed_multiple_times")); - } + // but even then since the execution of the watch record is async it may take a little bit before + // the actual documents are in the output index + refresh(); + SearchResponse searchResponse = client().prepareSearch(watchRecordIndex).setSize(numRecords).get(); + assertThat(searchResponse.getHits().getTotalHits(), Matchers.equalTo((long) numRecords)); + for (int i = 0; i < numRecords; i++) { + assertThat(searchResponse.getHits().getAt(i).getSource().get("state"), is(ExecutionState.EXECUTED_MULTIPLE_TIMES.id())); } }); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/HttpSecretsIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/HttpSecretsIntegrationTests.java index 83aabd7930b..0f8d46ad1b4 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/HttpSecretsIntegrationTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/HttpSecretsIntegrationTests.java @@ -23,7 +23,7 @@ import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchRes import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; -import org.elasticsearch.xpack.watcher.watch.WatchStore; +import org.elasticsearch.xpack.watcher.watch.Watch; import org.joda.time.DateTime; import org.junit.After; import org.junit.Before; @@ -92,7 +92,7 @@ public class HttpSecretsIntegrationTests extends AbstractWatcherIntegrationTestC // verifying the basic auth password is stored encrypted in the index when security // is enabled, and when it's not enabled, it's stored in plain text - GetResponse response = client().prepareGet(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id").get(); + GetResponse response = client().prepareGet(Watch.INDEX, Watch.DOC_TYPE, "_id").get(); assertThat(response, notNullValue()); assertThat(response.getId(), is("_id")); Map source = response.getSource(); @@ -156,7 +156,7 @@ public class HttpSecretsIntegrationTests extends AbstractWatcherIntegrationTestC // verifying the basic auth password is stored encrypted in the index when security // is enabled, when it's not enabled, the the passowrd should be stored in plain text - GetResponse response = client().prepareGet(WatchStore.INDEX, WatchStore.DOC_TYPE, "_id").get(); + GetResponse response = client().prepareGet(Watch.INDEX, Watch.DOC_TYPE, "_id").get(); assertThat(response, notNullValue()); assertThat(response.getId(), is("_id")); Map source = response.getSource(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/WatchAckTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/WatchAckTests.java index 13343e066dd..e09ec267d66 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/WatchAckTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/test/integration/WatchAckTests.java @@ -26,7 +26,6 @@ import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; import org.elasticsearch.xpack.watcher.watch.Watch; -import org.elasticsearch.xpack.watcher.watch.WatchStore; import org.hamcrest.Matchers; import org.junit.Before; @@ -96,8 +95,8 @@ public class WatchAckTests extends AbstractWatcherIntegrationTestCase { refresh(); long a1CountAfterAck = docCount("actions", "action1", matchAllQuery()); long a2CountAfterAck = docCount("actions", "action2", matchAllQuery()); - assertThat(a1CountAfterAck, greaterThanOrEqualTo((long) 1)); - assertThat(a2CountAfterAck, greaterThanOrEqualTo((long) 1)); + assertThat(a1CountAfterAck, greaterThan(0L)); + assertThat(a2CountAfterAck, greaterThan(0L)); timeWarp().scheduler().trigger("_id", 4, TimeValue.timeValueSeconds(5)); flush(); @@ -227,7 +226,7 @@ public class WatchAckTests extends AbstractWatcherIntegrationTestCase { assertThat(watchResponse.getStatus().actionStatus("_id").ackStatus().state(), Matchers.equalTo(ActionStatus.AckStatus.State.ACKED)); refresh(); - GetResponse getResponse = client().get(new GetRequest(WatchStore.INDEX, WatchStore.DOC_TYPE, "_name")).actionGet(); + GetResponse getResponse = client().get(new GetRequest(Watch.INDEX, Watch.DOC_TYPE, "_name")).actionGet(); Watch indexedWatch = watchParser().parse("_name", true, getResponse.getSourceAsBytesRef()); assertThat(watchResponse.getStatus().actionStatus("_id").ackStatus().state(), equalTo(indexedWatch.status().actionStatus("_id").ackStatus().state())); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/activate/ActivateWatchTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/activate/ActivateWatchTests.java index cd71d2dfd36..50b04d8f009 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/activate/ActivateWatchTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/activate/ActivateWatchTests.java @@ -15,7 +15,6 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.xpack.watcher.client.WatcherClient; -import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; import org.elasticsearch.xpack.watcher.execution.ExecutionState; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; @@ -38,12 +37,13 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; public class ActivateWatchTests extends AbstractWatcherIntegrationTestCase { + @Override protected boolean timeWarped() { return false; } - @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/4002") + // FIXME not to be sleep based public void testDeactivateAndActivate() throws Exception { WatcherClient watcherClient = watcherClient(); @@ -52,7 +52,6 @@ public class ActivateWatchTests extends AbstractWatcherIntegrationTestCase { .setSource(watchBuilder() .trigger(schedule(interval("1s"))) .input(simpleInput("foo", "bar")) - .condition(AlwaysCondition.INSTANCE) .addAction("_a1", indexAction("actions", "action1")) .defaultThrottlePeriod(new TimeValue(0, TimeUnit.SECONDS))) .get(); @@ -109,7 +108,6 @@ public class ActivateWatchTests extends AbstractWatcherIntegrationTestCase { .setSource(watchBuilder() .trigger(schedule(cron("0 0 0 1 1 ? 2050"))) // some time in 2050 .input(simpleInput("foo", "bar")) - .condition(AlwaysCondition.INSTANCE) .addAction("_a1", indexAction("actions", "action1")) .defaultThrottlePeriod(new TimeValue(0, TimeUnit.SECONDS))) .get(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/delete/DeleteWatchTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/delete/DeleteWatchTests.java index 5a20e3a5c51..60444113462 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/delete/DeleteWatchTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/delete/DeleteWatchTests.java @@ -38,6 +38,7 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; public class DeleteWatchTests extends AbstractWatcherIntegrationTestCase { + public void testDelete() throws Exception { ensureWatcherStarted(); PutWatchResponse putResponse = watcherClient().preparePutWatch("_name").setSource(watchBuilder() diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/get/GetWatchTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/get/GetWatchTests.java index 72041295ed1..8dfa39838e8 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/get/GetWatchTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/get/GetWatchTests.java @@ -5,21 +5,27 @@ */ package org.elasticsearch.xpack.watcher.transport.action.get; +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; +import org.elasticsearch.xpack.watcher.watch.Watch; import java.util.Map; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.loggingAction; import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; import static org.elasticsearch.xpack.watcher.input.InputBuilders.simpleInput; import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule; import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.interval; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; @@ -41,7 +47,6 @@ public class GetWatchTests extends AbstractWatcherIntegrationTestCase { assertThat(getResponse, notNullValue()); assertThat(getResponse.isFound(), is(true)); assertThat(getResponse.getId(), is("_name")); - assertThat(getResponse.getStatus().version(), is(putResponse.getVersion())); Map source = getResponse.getSource().getAsMap(); assertThat(source, notNullValue()); assertThat(source, hasKey("trigger")); @@ -51,7 +56,13 @@ public class GetWatchTests extends AbstractWatcherIntegrationTestCase { assertThat(source, not(hasKey("status"))); } + public void testGetNotFoundOnNonExistingIndex() throws Exception { + Exception e = expectThrows(Exception.class, () -> watcherClient().getWatch(new GetWatchRequest("_name")).get()); + assertThat(e.getMessage(), containsString("no such index")); + } + public void testGetNotFound() throws Exception { + assertAcked(client().admin().indices().prepareCreate(Watch.INDEX)); GetWatchResponse getResponse = watcherClient().getWatch(new GetWatchRequest("_name")).get(); assertThat(getResponse, notNullValue()); assertThat(getResponse.getId(), is("_name")); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/stats/WatchStatsTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/stats/WatchStatsTests.java index 7a964a13532..d5318ef78a3 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/stats/WatchStatsTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/transport/action/stats/WatchStatsTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.LatchScriptEngine; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xpack.watcher.WatcherState; import org.elasticsearch.xpack.watcher.actions.ActionBuilders; import org.elasticsearch.xpack.watcher.condition.ScriptCondition; @@ -47,6 +48,11 @@ public class WatchStatsTests extends AbstractWatcherIntegrationTestCase { return false; } + @Override + protected boolean enableSecurity() { + return false; + } + @Override protected Collection> nodePlugins() { List> plugins = new ArrayList<>(super.nodePlugins()); @@ -73,6 +79,7 @@ public class WatchStatsTests extends AbstractWatcherIntegrationTestCase { getLatchScriptEngine().finishScriptExecution(); } + @TestLogging("org.elasticsearch.xpack.watcher.trigger.schedule.engine:TRACE,org.elasticsearch.xpack.scheduler:TRACE") public void testCurrentWatches() throws Exception { watcherClient().preparePutWatch("_id").setSource(watchBuilder() .trigger(schedule(interval("1s"))) diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/BaseTriggerEngineTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/BaseTriggerEngineTestCase.java index f2874a53d39..6904ec1a63f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/BaseTriggerEngineTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/trigger/schedule/engine/BaseTriggerEngineTestCase.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.daily; import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.interval; @@ -184,17 +185,42 @@ public abstract class BaseTriggerEngineTestCase extends ESTestCase { } } - public void testAddSameJobSeveralTimes() { + public void testAddSameJobSeveralTimesAndExecutedOnce() throws InterruptedException { engine.start(Collections.emptySet()); - engine.register(events -> logger.info("triggered job")); + + final CountDownLatch firstLatch = new CountDownLatch(1); + final CountDownLatch secondLatch = new CountDownLatch(1); + AtomicInteger counter = new AtomicInteger(0); + engine.register(events -> { + events.forEach(event -> { + if (counter.getAndIncrement() == 0) { + firstLatch.countDown(); + } else { + secondLatch.countDown(); + } + }); + }); int times = scaledRandomIntBetween(3, 30); for (int i = 0; i < times; i++) { - engine.add(new SimpleJob("_id", interval("10s"))); + engine.add(new SimpleJob("_id", interval("1s"))); } + + advanceClockIfNeeded(new DateTime(clock.millis(), UTC).plusMillis(1100)); + if (!firstLatch.await(3, TimeUnit.SECONDS)) { + fail("waiting too long for all watches to be triggered"); + } + + advanceClockIfNeeded(new DateTime(clock.millis(), UTC).plusMillis(1100)); + if (!secondLatch.await(3, TimeUnit.SECONDS)) { + fail("waiting too long for all watches to be triggered"); + } + + // ensure job was only called twice independent from its name + assertThat(counter.get(), is(2)); } - static class SimpleJob implements TriggerEngine.Job { + public static class SimpleJob implements TriggerEngine.Job { private final String name; private final ScheduleTrigger trigger; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStatusTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStatusTests.java deleted file mode 100644 index 5ed5efb2cb9..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStatusTests.java +++ /dev/null @@ -1,50 +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.xpack.watcher.watch; - -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.watcher.actions.ActionStatus; -import org.elasticsearch.xpack.watcher.actions.ActionStatus.AckStatus.State; - -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.core.Is.is; -import static org.joda.time.DateTime.now; - -public class WatchStatusTests extends ESTestCase { - - public void testThatWatchStatusDirtyOnConditionCheck() throws Exception { - // no actions, met condition - WatchStatus status = new WatchStatus(now(), new HashMap<>()); - status.onCheck(true, now()); - assertThat(status.dirty(), is(true)); - - // no actions, unmet condition - status = new WatchStatus(now(), new HashMap<>()); - status.onCheck(false, now()); - assertThat(status.dirty(), is(true)); - - // actions, no action with reset ack status, unmet condition - Map actions = new HashMap<>(); - actions.put(randomAsciiOfLength(10), new ActionStatus(now())); - status = new WatchStatus(now(), actions); - status.onCheck(false, now()); - assertThat(status.dirty(), is(true)); - - // actions, one action with state other than AWAITS_SUCCESSFUL_EXECUTION, unmet condition - actions.clear(); - ActionStatus.AckStatus ackStatus = new ActionStatus.AckStatus(now(), randomFrom(State.ACKED, State.ACKABLE)); - actions.put(randomAsciiOfLength(10), new ActionStatus(ackStatus, null, null, null)); - actions.put(randomAsciiOfLength(11), new ActionStatus(now())); - status = new WatchStatus(now(), actions); - status.onCheck(false, now()); - assertThat(status.dirty(), is(true)); - - status.resetDirty(); - assertThat(status.dirty(), is(false)); - } -} \ No newline at end of file diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStoreTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStoreTests.java deleted file mode 100644 index c6e102bfc0b..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchStoreTests.java +++ /dev/null @@ -1,528 +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.xpack.watcher.watch; - -import org.elasticsearch.Version; -import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; -import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; -import org.elasticsearch.action.search.ClearScrollResponse; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.AliasMetaData; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.cluster.routing.IndexRoutingTable; -import org.elasticsearch.cluster.routing.IndexShardRoutingTable; -import org.elasticsearch.cluster.routing.RoutingTable; -import org.elasticsearch.cluster.routing.ShardRoutingState; -import org.elasticsearch.cluster.routing.TestShardRouting; -import org.elasticsearch.cluster.routing.UnassignedInfo; -import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.text.Text; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.Index; -import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.search.SearchHitField; -import org.elasticsearch.search.internal.InternalSearchHit; -import org.elasticsearch.search.internal.InternalSearchHits; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.watcher.actions.ActionWrapper; -import org.elasticsearch.xpack.watcher.actions.ExecutableAction; -import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; -import org.elasticsearch.xpack.watcher.condition.NeverCondition; -import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.input.none.ExecutableNoneInput; -import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; -import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; -import org.elasticsearch.xpack.watcher.transform.ExecutableTransform; -import org.elasticsearch.xpack.watcher.transform.Transform; -import org.elasticsearch.xpack.watcher.trigger.schedule.Schedule; -import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; -import org.junit.Before; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class WatchStoreTests extends ESTestCase { - private WatchStore watchStore; - private WatcherClientProxy clientProxy; - private Watch.Parser parser; - - @Before - public void init() { - clientProxy = mock(WatcherClientProxy.class); - parser = mock(Watch.Parser.class); - watchStore = new WatchStore(Settings.EMPTY, clientProxy, parser); - } - - public void testStartNoPreviousWatchesIndex() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - MetaData.Builder metaDataBuilder = MetaData.builder(); - csBuilder.metaData(metaDataBuilder); - ClusterState cs = csBuilder.build(); - - assertThat(watchStore.validate(cs), is(true)); - watchStore.start(cs); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches().size(), equalTo(0)); - verifyZeroInteractions(clientProxy); - - watchStore.start(cs); - verifyZeroInteractions(clientProxy); - } - - public void testStartPrimaryShardNotReady() { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - MetaData.Builder metaDataBuilder = MetaData.builder(); - RoutingTable.Builder routingTableBuilder = RoutingTable.builder(); - Settings settings = settings(Version.CURRENT) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) - .build(); - metaDataBuilder.put(IndexMetaData.builder(WatchStore.INDEX).settings(settings).numberOfShards(1).numberOfReplicas(1)); - final Index index = metaDataBuilder.get(WatchStore.INDEX).getIndex(); - IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index); - indexRoutingTableBuilder.addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(index, 0)) - .addShard(TestShardRouting.newShardRouting(WatchStore.INDEX, 0, "_node_id", null, true, - ShardRoutingState.UNASSIGNED, new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, ""))) - .build()); - indexRoutingTableBuilder.addReplica(); - routingTableBuilder.add(indexRoutingTableBuilder.build()); - csBuilder.metaData(metaDataBuilder); - csBuilder.routingTable(routingTableBuilder.build()); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(false)); - verifyZeroInteractions(clientProxy); - } - - public void testStartRefreshFailed() { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 0); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - ClusterState cs = csBuilder.build(); - - assertThat(watchStore.validate(cs), is(true)); - try { - watchStore.start(cs); - } catch (Exception e) { - assertThat(e.getMessage(), equalTo("not all required shards have been refreshed")); - } - verify(clientProxy, times(1)).refresh(any(RefreshRequest.class)); - verify(clientProxy, never()).search(any(SearchRequest.class), any(TimeValue.class)); - verify(clientProxy, never()).clearScroll(anyString()); - } - - public void testStartSearchFailed() { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - SearchResponse searchResponse = mockSearchResponse(1, 0, 0); - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse); - - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(true)); - try { - watchStore.start(cs); - } catch (Exception e) { - assertThat(e.getMessage(), equalTo("Partial response while loading watches")); - } - verify(clientProxy, times(1)).refresh(any(RefreshRequest.class)); - verify(clientProxy, times(1)).search(any(SearchRequest.class), any(TimeValue.class)); - verify(clientProxy, times(1)).clearScroll(anyString()); - } - - public void testStartNoWatchStored() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - SearchResponse searchResponse = mockSearchResponse(1, 1, 0); - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse); - - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(true)); - watchStore.start(cs); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches().size(), equalTo(0)); - verify(clientProxy, times(1)).refresh(any(RefreshRequest.class)); - verify(clientProxy, times(1)).search(any(SearchRequest.class), any(TimeValue.class)); - verify(clientProxy, times(1)).clearScroll(anyString()); - } - - public void testStartWatchStored() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - BytesReference source = new BytesArray("{}"); - InternalSearchHit hit1 = new InternalSearchHit(0, "_id1", new Text("type"), Collections.emptyMap()); - hit1.sourceRef(source); - InternalSearchHit hit2 = new InternalSearchHit(1, "_id2", new Text("type"), Collections.emptyMap()); - hit2.sourceRef(source); - SearchResponse searchResponse1 = mockSearchResponse(1, 1, 2, hit1, hit2); - - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse1); - - InternalSearchHit hit3 = new InternalSearchHit(2, "_id3", new Text("type"), Collections.emptyMap()); - hit3.sourceRef(source); - InternalSearchHit hit4 = new InternalSearchHit(3, "_id4", new Text("type"), Collections.emptyMap()); - hit4.sourceRef(source); - SearchResponse searchResponse2 = mockSearchResponse(1, 1, 2, hit3, hit4); - SearchResponse searchResponse3 = mockSearchResponse(1, 1, 2); - when(clientProxy.searchScroll(anyString(), any(TimeValue.class))).thenReturn(searchResponse2, searchResponse3); - - Watch watch1 = mock(Watch.class); - WatchStatus status = mock(WatchStatus.class); - when(watch1.status()).thenReturn(status); - Watch watch2 = mock(Watch.class); - when(watch2.status()).thenReturn(status); - Watch watch3 = mock(Watch.class); - when(watch3.status()).thenReturn(status); - Watch watch4 = mock(Watch.class); - when(watch4.status()).thenReturn(status); - when(parser.parse("_id1", true, source)).thenReturn(watch1); - when(parser.parse("_id2", true, source)).thenReturn(watch2); - when(parser.parse("_id3", true, source)).thenReturn(watch3); - when(parser.parse("_id4", true, source)).thenReturn(watch4); - - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(true)); - watchStore.start(cs); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches().size(), equalTo(4)); - verify(clientProxy, times(1)).refresh(any(RefreshRequest.class)); - verify(clientProxy, times(1)).search(any(SearchRequest.class), any(TimeValue.class)); - verify(clientProxy, times(2)).searchScroll(anyString(), any(TimeValue.class)); - verify(clientProxy, times(1)).clearScroll(anyString()); - } - - public void testUsageStats() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - BytesReference source = new BytesArray("{}"); - int hitCount = randomIntBetween(50, 100); - int activeHitCount = 0; - - List hits = new ArrayList<>(); - for (int i = 0; i < hitCount; i++) { - InternalSearchHit hit = new InternalSearchHit(0, "_id" + i, new Text("type"), Collections.emptyMap()); - hits.add(hit.sourceRef(source)); - - Watch watch = mock(Watch.class); - WatchStatus status = mock(WatchStatus.class); - when(watch.status()).thenReturn(status); - - boolean isActive = usually(); - WatchStatus.State state = mock(WatchStatus.State.class); - when(state.isActive()).thenReturn(isActive); - when(status.state()).thenReturn(state); - if (isActive) { - activeHitCount++; - } - - // random schedule - ScheduleTrigger mockTricker = mock(ScheduleTrigger.class); - when(watch.trigger()).thenReturn(mockTricker); - when(mockTricker.type()).thenReturn("schedule"); - String scheduleType = randomFrom("a", "b", "c"); - Schedule mockSchedule = mock(Schedule.class); - when(mockSchedule.type()).thenReturn(scheduleType); - when(mockTricker.getSchedule()).thenReturn(mockSchedule); - - // either a none input, or null - when(watch.input()).thenReturn(randomFrom(new ExecutableNoneInput(logger), null)); - - // random conditions - when(watch.condition()).thenReturn(randomFrom(AlwaysCondition.INSTANCE, null, - NeverCondition.INSTANCE)); - - // random actions - ActionWrapper actionWrapper = mock(ActionWrapper.class); - ExecutableAction action = mock(ExecutableAction.class); - when(actionWrapper.action()).thenReturn(action); - when(action.type()).thenReturn(randomFrom("a", "b", "c")); - when(watch.actions()).thenReturn(Arrays.asList(actionWrapper)); - - // random transform, not always set - Transform mockTransform = mock(Transform.class); - when(mockTransform.type()).thenReturn("TYPE"); - - @SuppressWarnings("unchecked") - ExecutableTransform testTransform = new ExecutableTransform(mockTransform, logger) { - @Override - public Transform.Result execute(WatchExecutionContext ctx, Payload payload) { - return null; - } - }; - when(watch.transform()).thenReturn(randomFrom(testTransform, null)); - - when(parser.parse("_id" + i, true, source)).thenReturn(watch); - } - - SearchResponse searchResponse = mockSearchResponse(1, 1, hitCount, hits.toArray(new InternalSearchHit[] {})); - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse); - SearchResponse noHitsResponse = mockSearchResponse(1, 1, 2); - when(clientProxy.searchScroll(anyString(), any(TimeValue.class))).thenReturn(noHitsResponse); - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - watchStore.start(cs); - - XContentSource stats = new XContentSource(jsonBuilder().map(watchStore.usageStats())); - - assertThat(stats.getValue("count.total"), is(hitCount)); - assertThat(stats.getValue("count.active"), is(activeHitCount)); - - // schedule count - int scheduleCountA = stats.getValue("watch.trigger.schedule.a.active"); - int scheduleCountB = stats.getValue("watch.trigger.schedule.b.active"); - int scheduleCountC = stats.getValue("watch.trigger.schedule.c.active"); - assertThat(scheduleCountA + scheduleCountB + scheduleCountC, is(activeHitCount)); - - // input count - assertThat(stats.getValue("watch.input.none.active"), is(greaterThan(0))); - assertThat(stats.getValue("watch.input.none.total"), is(greaterThan(0))); - assertThat(stats.getValue("watch.input.none.total"), is(lessThan(activeHitCount))); - - // condition count - assertThat(stats.getValue("watch.condition.never.active"), is(greaterThan(0))); - assertThat(stats.getValue("watch.condition.always.active"), is(greaterThan(0))); - - // action count - int actionCountA = stats.getValue("watch.action.a.active"); - int actionCountB = stats.getValue("watch.action.b.active"); - int actionCountC = stats.getValue("watch.action.c.active"); - assertThat(actionCountA + actionCountB + actionCountC, is(activeHitCount)); - - // transform count - assertThat(stats.getValue("watch.transform.TYPE.active"), is(greaterThan(0))); - } - - public void testThatCleaningWatchesWorks() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - createWatchIndexMetaData(csBuilder); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - BytesReference source = new BytesArray("{}"); - InternalSearchHit hit = new InternalSearchHit(0, "_id1", new Text("type"), Collections.emptyMap()); - hit.sourceRef(source); - - SearchResponse searchResponse = mockSearchResponse(1, 1, 1, hit); - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse); - - SearchResponse finalSearchResponse = mockSearchResponse(1, 1, 0); - when(clientProxy.searchScroll(anyString(), any(TimeValue.class))).thenReturn(finalSearchResponse); - - Watch watch = mock(Watch.class); - WatchStatus status = mock(WatchStatus.class); - when(watch.status()).thenReturn(status); - when(parser.parse("_id1", true, source)).thenReturn(watch); - - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(true)); - watchStore.start(cs); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches(), hasSize(1)); - - watchStore.clearWatchesInMemory(); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches(), hasSize(0)); - assertThat(watchStore.activeWatches(), hasSize(0)); - } - - // the elasticsearch migration helper is doing reindex using aliases, so we have to - // make sure that the watch store supports a single alias pointing to the watch index - public void testThatStartingWithWatchesIndexAsAliasWorks() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - - MetaData.Builder metaDataBuilder = MetaData.builder(); - RoutingTable.Builder routingTableBuilder = RoutingTable.builder(); - Settings settings = settings(Version.CURRENT) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) - .build(); - metaDataBuilder.put(IndexMetaData.builder("watches-alias").settings(settings).numberOfShards(1).numberOfReplicas(1) - .putAlias(new AliasMetaData.Builder(WatchStore.INDEX).build())); - - final Index index = metaDataBuilder.get("watches-alias").getIndex(); - IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index); - indexRoutingTableBuilder.addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(index, 0)) - .addShard(TestShardRouting.newShardRouting("watches-alias", 0, "_node_id", null, true, ShardRoutingState.STARTED)) - .build()); - indexRoutingTableBuilder.addReplica(); - routingTableBuilder.add(indexRoutingTableBuilder.build()); - csBuilder.metaData(metaDataBuilder); - csBuilder.routingTable(routingTableBuilder.build()); - - RefreshResponse refreshResponse = mockRefreshResponse(1, 1); - when(clientProxy.refresh(any(RefreshRequest.class))).thenReturn(refreshResponse); - - BytesReference source = new BytesArray("{}"); - InternalSearchHit hit1 = new InternalSearchHit(0, "_id1", new Text("type"), Collections.emptyMap()); - hit1.sourceRef(source); - InternalSearchHit hit2 = new InternalSearchHit(1, "_id2", new Text("type"), Collections.emptyMap()); - hit2.sourceRef(source); - SearchResponse searchResponse1 = mockSearchResponse(1, 1, 2, hit1, hit2); - - when(clientProxy.search(any(SearchRequest.class), any(TimeValue.class))).thenReturn(searchResponse1); - - InternalSearchHit hit3 = new InternalSearchHit(2, "_id3", new Text("type"), Collections.emptyMap()); - hit3.sourceRef(source); - InternalSearchHit hit4 = new InternalSearchHit(3, "_id4", new Text("type"), Collections.emptyMap()); - hit4.sourceRef(source); - SearchResponse searchResponse2 = mockSearchResponse(1, 1, 2, hit3, hit4); - SearchResponse searchResponse3 = mockSearchResponse(1, 1, 2); - when(clientProxy.searchScroll(anyString(), any(TimeValue.class))).thenReturn(searchResponse2, searchResponse3); - - Watch watch1 = mock(Watch.class); - WatchStatus status = mock(WatchStatus.class); - when(watch1.status()).thenReturn(status); - Watch watch2 = mock(Watch.class); - when(watch2.status()).thenReturn(status); - Watch watch3 = mock(Watch.class); - when(watch3.status()).thenReturn(status); - Watch watch4 = mock(Watch.class); - when(watch4.status()).thenReturn(status); - when(parser.parse("_id1", true, source)).thenReturn(watch1); - when(parser.parse("_id2", true, source)).thenReturn(watch2); - when(parser.parse("_id3", true, source)).thenReturn(watch3); - when(parser.parse("_id4", true, source)).thenReturn(watch4); - - when(clientProxy.clearScroll(anyString())).thenReturn(new ClearScrollResponse(true, 0)); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(true)); - watchStore.start(cs); - assertThat(watchStore.started(), is(true)); - assertThat(watchStore.watches().size(), equalTo(4)); - verify(clientProxy, times(1)).refresh(any(RefreshRequest.class)); - verify(clientProxy, times(1)).search(any(SearchRequest.class), any(TimeValue.class)); - verify(clientProxy, times(1)).clearScroll(anyString()); - } - - // the elasticsearch migration helper is doing reindex using aliases, so we have to - // make sure that the watch store supports only a single index in an alias - public void testThatWatchesIndexWithTwoAliasesFails() throws Exception { - ClusterState.Builder csBuilder = new ClusterState.Builder(new ClusterName("_name")); - - MetaData.Builder metaDataBuilder = MetaData.builder(); - RoutingTable.Builder routingTableBuilder = RoutingTable.builder(); - Settings settings = settings(Version.CURRENT) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) - .build(); - metaDataBuilder.put(IndexMetaData.builder("watches-alias").settings(settings).numberOfShards(1).numberOfReplicas(1) - .putAlias(new AliasMetaData.Builder(WatchStore.INDEX).build())); - metaDataBuilder.put(IndexMetaData.builder("whatever").settings(settings).numberOfShards(1).numberOfReplicas(1) - .putAlias(new AliasMetaData.Builder(WatchStore.INDEX).build())); - - final Index index = metaDataBuilder.get("watches-alias").getIndex(); - IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index); - indexRoutingTableBuilder.addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(index, 0)) - .addShard(TestShardRouting.newShardRouting("watches-alias", 0, "_node_id", null, true, ShardRoutingState.STARTED)) - .build()); - indexRoutingTableBuilder.addReplica(); - final Index otherIndex = metaDataBuilder.get("whatever").getIndex(); - IndexRoutingTable.Builder otherIndexRoutingTableBuilder = IndexRoutingTable.builder(otherIndex); - otherIndexRoutingTableBuilder.addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(index, 0)) - .addShard(TestShardRouting.newShardRouting("whatever", 0, "_node_id", null, true, ShardRoutingState.STARTED)) - .build()); - otherIndexRoutingTableBuilder.addReplica(); - routingTableBuilder.add(otherIndexRoutingTableBuilder.build()); - csBuilder.metaData(metaDataBuilder); - csBuilder.routingTable(routingTableBuilder.build()); - - ClusterState cs = csBuilder.build(); - assertThat(watchStore.validate(cs), is(false)); - IllegalStateException exception = expectThrows(IllegalStateException.class, () -> watchStore.start(cs)); - assertThat(exception.getMessage(), is("Alias [.watches] points to more than one index")); - } - - /* - * Creates the standard cluster state metadata for the watches index - * with shards/replicas being marked as started - */ - private void createWatchIndexMetaData(ClusterState.Builder builder) { - MetaData.Builder metaDataBuilder = MetaData.builder(); - RoutingTable.Builder routingTableBuilder = RoutingTable.builder(); - Settings settings = settings(Version.CURRENT) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) - .build(); - metaDataBuilder.put(IndexMetaData.builder(WatchStore.INDEX).settings(settings).numberOfShards(1).numberOfReplicas(1)); - final Index index = metaDataBuilder.get(WatchStore.INDEX).getIndex(); - IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index); - indexRoutingTableBuilder.addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(index, 0)) - .addShard(TestShardRouting.newShardRouting(WatchStore.INDEX, 0, "_node_id", null, true, ShardRoutingState.STARTED)) - .build()); - indexRoutingTableBuilder.addReplica(); - routingTableBuilder.add(indexRoutingTableBuilder.build()); - builder.metaData(metaDataBuilder); - builder.routingTable(routingTableBuilder.build()); - } - - private RefreshResponse mockRefreshResponse(int total, int successful) { - RefreshResponse refreshResponse = mock(RefreshResponse.class); - when(refreshResponse.getTotalShards()).thenReturn(total); - when(refreshResponse.getSuccessfulShards()).thenReturn(successful); - return refreshResponse; - } - - private SearchResponse mockSearchResponse(int total, int successful, int totalHits, InternalSearchHit... hits) { - InternalSearchHits internalSearchHits = new InternalSearchHits(hits, totalHits, 1f); - SearchResponse searchResponse = mock(SearchResponse.class); - when(searchResponse.getTotalShards()).thenReturn(total); - when(searchResponse.getSuccessfulShards()).thenReturn(successful); - when(searchResponse.getHits()).thenReturn(internalSearchHits); - return searchResponse; - } -} diff --git a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/activate_watch/10_basic.yaml b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/activate_watch/10_basic.yaml index 70fea686a1d..3cfd4badf5c 100644 --- a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/activate_watch/10_basic.yaml +++ b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/activate_watch/10_basic.yaml @@ -35,10 +35,6 @@ - match: { _id: "my_watch" } - - do: - cluster.health: - wait_for_status: yellow - - do: xpack.watcher.get_watch: id: "my_watch" @@ -51,7 +47,7 @@ xpack.watcher.deactivate_watch: watch_id: "my_watch" - - match: { "_status.state.active" : false } + - match: { _status.state.active : false } - do: xpack.watcher.get_watch: @@ -64,7 +60,7 @@ xpack.watcher.activate_watch: watch_id: "my_watch" - - match: { "_status.state.active" : true } + - match: { _status.state.active : true } - do: xpack.watcher.get_watch: diff --git a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/10_basic.yaml b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/10_basic.yaml index 355a52a71aa..cf529866f20 100644 --- a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/10_basic.yaml +++ b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/10_basic.yaml @@ -52,5 +52,5 @@ teardown: id: "my_watch" - match: { found : true} - match: { _id: "my_watch" } - - is_true: _status.version + - is_true: watch - is_false: watch.status diff --git a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/20_missing.yaml b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/20_missing.yaml index c4805bcbcfe..a357a903e43 100644 --- a/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/20_missing.yaml +++ b/elasticsearch/src/test/resources/rest-api-spec/test/watcher/get_watch/20_missing.yaml @@ -4,6 +4,9 @@ cluster.health: wait_for_status: yellow + - do: + indices.create: + index: .watches - do: catch: missing diff --git a/elasticsearch/src/test/resources/rest-api-spec/test/xpack/15_basic.yaml b/elasticsearch/src/test/resources/rest-api-spec/test/xpack/15_basic.yaml index 6aeb3b87872..5e61f98bbc2 100644 --- a/elasticsearch/src/test/resources/rest-api-spec/test/xpack/15_basic.yaml +++ b/elasticsearch/src/test/resources/rest-api-spec/test/xpack/15_basic.yaml @@ -93,8 +93,6 @@ - is_true: graph.available - is_true: monitoring.enabled - is_true: monitoring.available - - gte: { watcher.count.total: 0 } - - gte: { watcher.count.active: 0 } - do: xpack.info: diff --git a/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/actions/10_webhook.yaml b/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/actions/10_webhook.yaml index 1a7ade5b783..1f2c140be4f 100644 --- a/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/actions/10_webhook.yaml +++ b/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/actions/10_webhook.yaml @@ -4,11 +4,6 @@ cluster.health: wait_for_status: yellow - - - do: {xpack.watcher.stats:{}} - - match: { "watcher_state": "started" } - - match: { "watch_count": 0 } - - do: xpack.watcher.put_watch: id: "test_watch" @@ -47,9 +42,6 @@ - match: { _id: "test_watch" } - match: { created: true } - - do: {xpack.watcher.stats:{}} - - match: { "watch_count": 1 } - # Simulate a Thread.sleep() - do: catch: request_timeout diff --git a/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/watcher_mustache/20_array_access.yaml b/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/watcher_mustache/20_array_access.yaml index bd051ab6d87..e71cd7869ad 100644 --- a/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/watcher_mustache/20_array_access.yaml +++ b/qa/smoke-test-watcher-with-mustache/src/test/resources/rest-api-spec/test/watcher_mustache/20_array_access.yaml @@ -4,11 +4,6 @@ cluster.health: wait_for_status: yellow - - - do: {xpack.watcher.stats:{}} - - match: { "watcher_state": "started" } - - match: { "watch_count": 0 } - - do: xpack.watcher.put_watch: id: "test_watch" @@ -45,9 +40,6 @@ - match: { _id: "test_watch" } - match: { created: true } - - do: {xpack.watcher.stats:{}} - - match: { "watch_count": 1 } - # Simulate a Thread.sleep() - do: catch: request_timeout diff --git a/qa/smoke-test-watcher-with-security/roles.yml b/qa/smoke-test-watcher-with-security/roles.yml index 144f8cb2fe5..3f4ce41164f 100644 --- a/qa/smoke-test-watcher-with-security/roles.yml +++ b/qa/smoke-test-watcher-with-security/roles.yml @@ -10,7 +10,7 @@ watcher_manager: cluster: - manage indices: - - names: '.watcher-history-*' + - names: '.watch*' privileges: - all run_as: diff --git a/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml b/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml index 871fba78e5f..a95474808df 100644 --- a/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml +++ b/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml @@ -64,6 +64,10 @@ teardown: - match: { _id: "cluster_health_watch" } - match: { created: true } + - do: + indices.refresh: + index: .watches + - do: xpack.watcher.stats: {} - match: { "watch_count": 1 } @@ -113,6 +117,9 @@ teardown: id: "cluster_health_watch" - match: { found: true } + - do: + indices.refresh: + index: .watches - do: xpack.watcher.stats: {}