Watcher: Compile scripts on each invocation (elastic/elasticsearch#4239)

Transform and condition scripts were only compiled on its initial creation, so
when a new watch is created or when the master node loads all the watches. However
changing a script (like a stored one) did not lead to any changes in the in memory
watch store and thus the old script was executed again.

We do however have a mechanism in Elasticsearch's ScriptService that already does some
caching, and should reuse that one.

Closes elastic/elasticsearch#4237

Original commit: elastic/x-pack-elasticsearch@477548e237
This commit is contained in:
Alexander Reelsen 2016-12-02 10:36:05 +01:00 committed by GitHub
parent 946d943868
commit 7c04897392
5 changed files with 143 additions and 15 deletions

View File

@ -32,21 +32,20 @@ public final class ScriptCondition extends Condition {
private static final Result UNMET = new Result(null, TYPE, false); private static final Result UNMET = new Result(null, TYPE, false);
private final ScriptService scriptService; private final ScriptService scriptService;
private final CompiledScript compiledScript;
private final Script script; private final Script script;
public ScriptCondition(Script script) { public ScriptCondition(Script script) {
super(TYPE); super(TYPE);
this.script = script; this.script = script;
scriptService = null; scriptService = null;
compiledScript = null;
} }
ScriptCondition(Script script, ScriptService scriptService) { ScriptCondition(Script script, ScriptService scriptService) {
super(TYPE); super(TYPE);
this.scriptService = scriptService; this.scriptService = scriptService;
this.script = script; this.script = script;
compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap()); // try to compile so we catch syntax errors early
scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap());
} }
public Script getScript() { public Script getScript() {
@ -73,6 +72,7 @@ public final class ScriptCondition extends Condition {
if (script.getParams() != null && !script.getParams().isEmpty()) { if (script.getParams() != null && !script.getParams().isEmpty()) {
parameters.putAll(script.getParams()); parameters.putAll(script.getParams());
} }
CompiledScript compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap());
ExecutableScript executable = scriptService.executable(compiledScript, parameters); ExecutableScript executable = scriptService.executable(compiledScript, parameters);
Object value = executable.run(); Object value = executable.run();
if (value instanceof Boolean) { if (value instanceof Boolean) {

View File

@ -28,13 +28,13 @@ import static org.elasticsearch.xpack.watcher.transform.script.ScriptTransform.T
public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransform, ScriptTransform.Result> { public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransform, ScriptTransform.Result> {
private final ScriptService scriptService; private final ScriptService scriptService;
private final CompiledScript compiledScript;
public ExecutableScriptTransform(ScriptTransform transform, Logger logger, ScriptService scriptService) { public ExecutableScriptTransform(ScriptTransform transform, Logger logger, ScriptService scriptService) {
super(transform, logger); super(transform, logger);
this.scriptService = scriptService; this.scriptService = scriptService;
Script script = transform.getScript(); Script script = transform.getScript();
compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap()); // try to compile so we catch syntax errors early
scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap());
} }
@Override @Override
@ -47,7 +47,6 @@ public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransfo
} }
} }
ScriptTransform.Result doExecute(WatchExecutionContext ctx, Payload payload) throws IOException { ScriptTransform.Result doExecute(WatchExecutionContext ctx, Payload payload) throws IOException {
Script script = transform.getScript(); Script script = transform.getScript();
Map<String, Object> model = new HashMap<>(); Map<String, Object> model = new HashMap<>();
@ -55,6 +54,7 @@ public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransfo
model.putAll(script.getParams()); model.putAll(script.getParams());
} }
model.putAll(createCtxModel(ctx, payload)); model.putAll(createCtxModel(ctx, payload));
CompiledScript compiledScript = scriptService.compile(script, Watcher.SCRIPT_CONTEXT, Collections.emptyMap());
ExecutableScript executable = scriptService.executable(compiledScript, model); ExecutableScript executable = scriptService.executable(compiledScript, model);
Object value = executable.run(); Object value = executable.run();
if (value instanceof Map) { if (value instanceof Map) {

View File

@ -9,19 +9,16 @@ import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptSettings;
import org.elasticsearch.xpack.watcher.transform.TransformFactory; import org.elasticsearch.xpack.watcher.transform.TransformFactory;
import java.io.IOException; import java.io.IOException;
public class ScriptTransformFactory extends TransformFactory<ScriptTransform, ScriptTransform.Result, ExecutableScriptTransform> { public class ScriptTransformFactory extends TransformFactory<ScriptTransform, ScriptTransform.Result, ExecutableScriptTransform> {
private final Settings settings;
private final ScriptService scriptService; private final ScriptService scriptService;
public ScriptTransformFactory(Settings settings, ScriptService scriptService) { public ScriptTransformFactory(Settings settings, ScriptService scriptService) {
super(Loggers.getLogger(ExecutableScriptTransform.class, settings)); super(Loggers.getLogger(ExecutableScriptTransform.class, settings));
this.settings = settings;
this.scriptService = scriptService; this.scriptService = scriptService;
} }

View File

@ -25,7 +25,6 @@ import org.elasticsearch.xpack.watcher.support.Variables;
import org.elasticsearch.xpack.watcher.transform.Transform; import org.elasticsearch.xpack.watcher.transform.Transform;
import org.elasticsearch.xpack.watcher.watch.Payload; import org.elasticsearch.xpack.watcher.watch.Payload;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -50,12 +49,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class ScriptTransformTests extends ESTestCase { public class ScriptTransformTests extends ESTestCase {
ThreadPool tp = null;
@Before private final ThreadPool tp = new TestThreadPool(ThreadPool.Names.SAME);
public void init() {
tp = new TestThreadPool(ThreadPool.Names.SAME);
}
@After @After
public void cleanup() { public void cleanup() {

View File

@ -0,0 +1,136 @@
# When a script is specified in a watch, updates should be taken into account
# See https://github.com/elastic/x-plugins/issues/4237
---
"Test transform scripts are updated on execution":
- do:
cluster.health:
wait_for_status: yellow
- do:
put_script:
id: transform-script
lang: painless
body: >
{
"script":"return [ 'email': 'foo@bar.org' ]"
}
- do:
xpack.watcher.put_watch:
id: "my_watch"
body: >
{
"trigger": {
"schedule": {
"interval": "1h"
}
},
"input": {
"simple": {}
},
"transform": {
"script": {
"stored": "transform-script"
}
},
"actions": {
"my_log": {
"logging": {
"text": "{{ctx}}"
}
}
}
}
- match: { _id: "my_watch" }
- do:
xpack.watcher.execute_watch:
id: "my_watch"
- match: { "watch_record.watch_id": "my_watch" }
- match: { "watch_record.result.transform.payload.email": "foo@bar.org" }
- do:
put_script:
id: transform-script
lang: painless
body: >
{
"script":"return [ 'email': 'foo@example.org' ]"
}
- do:
xpack.watcher.execute_watch:
id: "my_watch"
- match: { "watch_record.watch_id": "my_watch" }
- match: { "watch_record.result.transform.payload.email": "foo@example.org" }
---
"Test condition scripts are updated on execution":
- do:
cluster.health:
wait_for_status: yellow
- do:
put_script:
id: condition-script
lang: painless
body: >
{
"script":"return false"
}
- do:
xpack.watcher.put_watch:
id: "my_watch"
body: >
{
"trigger": {
"schedule": {
"interval": "1h"
}
},
"input": {
"simple": {}
},
"condition": {
"script": {
"stored": "condition-script"
}
},
"actions": {
"my_log": {
"logging": {
"text": "{{ctx}}"
}
}
}
}
- match: { _id: "my_watch" }
- do:
xpack.watcher.execute_watch:
id: "my_watch"
- match: { "watch_record.watch_id": "my_watch" }
- match: { "watch_record.result.condition.met": false }
- do:
put_script:
id: condition-script
lang: painless
body: >
{
"script":"return true"
}
- do:
xpack.watcher.execute_watch:
id: "my_watch"
- match: { "watch_record.watch_id": "my_watch" }
- match: { "watch_record.result.condition.met": true }