watcher: Removed custom xmustache script and use mustache script engine in lang-mustache module

The lang-mustache module has been extended to meet Watcher's needs:
* The ability to refer the specific slots in arrays.
* An `content_type` option controls whether json string escaping is used. Otherwise there is no escaping.

Closes elastic/elasticsearch#1116

Other changes:
* I changed tests that were just using mustache just because it was around to not use mustache
* I moved tests to `test-xpack-with-mustache` module that were testing mustache with Watcher
* added smoke test for watcher and mustache
* moved some tests around
* instead of using DefaultTextTemplateEngine in watcher tests use MockTextTemplateEngine
* added a mock mustache script engine
* Cleanup some messy tests to not rely on mustache and move them back into xpack module
* moved array access test to smoke test watcher with mustache module
* test: simplified the condition search test to take the time component out of it, while still simulation a condition
* removed the mustache dependency in the messy-test-watcher-with-groovy module

Original commit: elastic/x-pack-elasticsearch@6a2a4e885f
This commit is contained in:
Martijn van Groningen 2015-12-24 12:06:36 +01:00
parent 29fbaf233f
commit e617d8365f
43 changed files with 714 additions and 1110 deletions

View File

@ -9,6 +9,4 @@ apply plugin: 'elasticsearch.messy-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'testArtifacts')
testCompile project(path: ':modules:lang-groovy', configuration: 'runtime')
// some tests depend on both groovy and mustache! this is really bad!
testCompile project(path: ':modules:lang-mustache', configuration: 'runtime')
}

View File

@ -27,6 +27,7 @@ import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.transform.TransformBuilders.scriptTransform;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -57,12 +58,12 @@ public class ExecutionVarsIntegrationTests extends AbstractWatcherIntegrationTes
.transform(scriptTransform("ctx.vars.watch_transform_value = ctx.vars.condition_value + 5; return ctx.payload;"))
.addAction(
"a1",
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a1_transform_value={{ctx.vars.a1_transform_value}}"))
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; ctx.payload.a1_transformed_value = ctx.vars.a1_transform_value; return ctx.payload;"),
loggingAction("_text"))
.addAction(
"a2",
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a2_transform_value={{ctx.vars.a2_transform_value}}")))
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; ctx.payload.a2_transformed_value = ctx.vars.a2_transform_value; return ctx.payload;"),
loggingAction("_text")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
@ -99,12 +100,12 @@ public class ExecutionVarsIntegrationTests extends AbstractWatcherIntegrationTes
case "a1":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a1_transform_value=25"));
assertValue(action, "transform.payload.a1_transformed_value", equalTo(25));
break;
case "a2":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a2_transform_value=35"));
assertValue(action, "transform.payload.a2_transformed_value", equalTo(35));
break;
default:
fail("there should not be an action result for action with an id other than a1 or a2");
@ -122,12 +123,12 @@ public class ExecutionVarsIntegrationTests extends AbstractWatcherIntegrationTes
.transform(scriptTransform("ctx.vars.watch_transform_value = ctx.vars.condition_value + 5; return ctx.payload;"))
.addAction(
"a1",
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a1_transform_value={{ctx.vars.a1_transform_value}}"))
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; ctx.payload.a1_transformed_value = ctx.vars.a1_transform_value; return ctx.payload;"),
loggingAction("_text"))
.addAction(
"a2",
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a2_transform_value={{ctx.vars.a2_transform_value}}")))
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; ctx.payload.a2_transformed_value = ctx.vars.a2_transform_value; return ctx.payload;"),
loggingAction("_text")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
@ -161,12 +162,12 @@ public class ExecutionVarsIntegrationTests extends AbstractWatcherIntegrationTes
case "a1":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a1_transform_value=25"));
assertValue(action, "transform.payload.a1_transformed_value", equalTo(25));
break;
case "a2":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a2_transform_value=35"));
assertValue(action, "transform.payload.a2_transformed_value", equalTo(35));
break;
default:
fail("there should not be an action result for action with an id other than a1 or a2");

View File

@ -10,7 +10,6 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.sort.SortOrder;
@ -49,7 +48,6 @@ public class IndexActionIntegrationTests extends AbstractWatcherIntegrationTestC
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
types.add(MustachePlugin.class);
return types;
}

View File

@ -18,7 +18,6 @@ import org.elasticsearch.script.groovy.GroovyScriptEngineService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheScriptEngineService;
import org.junit.Ignore;
import java.util.Arrays;
@ -34,15 +33,12 @@ public final class MessyTestUtils {
.put("script.indexed", "true")
.put("path.home", LuceneTestCase.createTempDir())
.build();
XMustacheScriptEngineService mustacheScriptEngineService = new XMustacheScriptEngineService(settings);
GroovyScriptEngineService groovyScriptEngineService = new GroovyScriptEngineService(settings);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
engineServiceSet.add(groovyScriptEngineService);
ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry(
Arrays.asList(
new ScriptEngineRegistry.ScriptEngineRegistration(GroovyScriptEngineService.class, GroovyScriptEngineService.TYPES),
new ScriptEngineRegistry.ScriptEngineRegistration(XMustacheScriptEngineService.class, XMustacheScriptEngineService.TYPES)
new ScriptEngineRegistry.ScriptEngineRegistration(GroovyScriptEngineService.class, GroovyScriptEngineService.TYPES)
)
);
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Arrays.asList(ScriptServiceProxy.INSTANCE));

View File

@ -12,7 +12,6 @@ import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.test.WatcherTestUtils;
@ -52,7 +51,6 @@ public class TransformIntegrationTests extends AbstractWatcherIntegrationTestCas
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
types.add(MustachePlugin.class);
return types;
}

View File

@ -1,113 +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.messy.tests;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate;
import org.elasticsearch.watcher.actions.email.service.support.EmailServer;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.condition.compare.CompareCondition;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.trigger.schedule.IntervalSchedule;
import org.junit.After;
import javax.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.ESIntegTestCase.Scope.SUITE;
import static org.elasticsearch.watcher.actions.ActionBuilders.emailAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.compareCondition;
import static org.elasticsearch.watcher.input.InputBuilders.searchInput;
import static org.elasticsearch.watcher.test.WatcherTestUtils.newInputSearchRequest;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.interval;
import static org.hamcrest.Matchers.equalTo;
@TestLogging("subethamail:TRACE,watcher:TRACE")
@ESIntegTestCase.ClusterScope(scope = SUITE, numClientNodes = 0, transportClientRatio = 0, randomDynamicTemplates = false, numDataNodes = 1)
public class EmailActionIntegrationTests extends AbstractWatcherIntegrationTestCase {
static final String USERNAME = "_user";
static final String PASSWORD = "_passwd";
private EmailServer server;
@After
public void cleanup() throws Exception {
server.stop();
}
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = new ArrayList<>();
types.addAll(super.pluginTypes());
types.add(MustachePlugin.class);
return types;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
if(server == null) {
//Need to construct the Email Server here as this happens before init()
server = EmailServer.localhost("2500-2600", USERNAME, PASSWORD, logger);
}
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("watcher.actions.email.service.account.test.smtp.auth", true)
.put("watcher.actions.email.service.account.test.smtp.user", USERNAME)
.put("watcher.actions.email.service.account.test.smtp.password", PASSWORD)
.put("watcher.actions.email.service.account.test.smtp.port", server.port())
.put("watcher.actions.email.service.account.test.smtp.host", "localhost")
.build();
}
public void testArrayAccess() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
server.addListener(new EmailServer.Listener() {
@Override
public void on(MimeMessage message) throws Exception {
assertThat(message.getSubject(), equalTo("value"));
latch.countDown();
}
});
WatcherClient watcherClient = watcherClient();
createIndex("idx");
// Have a sample document in the index, the watch is going to evaluate
client().prepareIndex("idx", "type").setSource("field", "value").get();
refresh();
SearchRequest searchRequest = newInputSearchRequest("idx").source(searchSource().query(termQuery("field", "value")));
watcherClient.preparePutWatch("_id")
.setSource(watchBuilder()
.trigger(schedule(interval(5, IntervalSchedule.Interval.Unit.SECONDS)))
.input(searchInput(searchRequest))
.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.GT, 0L))
.addAction("_email", emailAction(EmailTemplate.builder().from("_from").to("_to")
.subject("{{ctx.payload.hits.hits.0._source.field}}")).setAuthentication(USERNAME, PASSWORD.toCharArray())))
.get();
if (timeWarped()) {
timeWarp().scheduler().trigger("_id");
refresh();
}
assertWatchWithMinimumPerformedActionsCount("_id", 1);
if (!latch.await(5, TimeUnit.SECONDS)) {
fail("waited too long for email to be received");
}
}
}

View File

@ -21,12 +21,8 @@
*/
// renames that took place:
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/test/integration/BasicWatcherTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/BasicWatcherTests.java
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/email/EmailActionIntegrationTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/EmailActionIntegrationTests.java
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/history/HistoryTemplateSearchInputMappingsTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/HistoryTemplateSearchInputMappingsTests.java
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/input/search/SearchInputTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchInputTests.java
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/transform/search/SearchTransformTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchTransformTests.java
// renamed: x-pack/shield/src/test/java/org/elasticsearch/integration/ShieldCachePermissionTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/ShieldCachePermissionTests.java
// renamed: x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/TimeThrottleIntegrationTests.java -> qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/TimeThrottleIntegrationTests.java
package org.elasticsearch.messy.tests;

View File

@ -0,0 +1,15 @@
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
testCompile project(path: ':modules:lang-mustache', configuration: 'runtime')
}
integTest {
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
systemProperty 'es.shield.enabled', 'false'
systemProperty 'es.marvel.enabled', 'false'
systemProperty 'es.http.port', '9400'
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.smoketest;
import com.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.RestTestCandidate;
import org.elasticsearch.test.rest.parser.RestTestParseException;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
public abstract class WatcherRestTestCase extends ESRestTestCase {
public WatcherRestTestCase(@Name("yaml") RestTestCandidate testCandidate) {
super(testCandidate);
}
@ParametersFactory
public static Iterable<Object[]> parameters() throws IOException, RestTestParseException {
return ESRestTestCase.createParameters(0, 1);
}
@Before
public void startWatcher() throws Exception {
try(CloseableHttpClient client = HttpClients.createMinimal(new BasicHttpClientConnectionManager())) {
URL url = getClusterUrls()[0];
HttpPut request = new HttpPut(new URI("http", null, url.getHost(), url.getPort(), "/_watcher/_start", null, null));
client.execute(request);
}
}
@After
public void stopWatcher() throws Exception {
try(CloseableHttpClient client = HttpClients.createMinimal(new BasicHttpClientConnectionManager())) {
URL url = getClusterUrls()[0];
HttpPut request = new HttpPut(new URI("http", null, url.getHost(), url.getPort(), "/_watcher/_stop", null, null));
client.execute(request);
}
}
}

View File

@ -0,0 +1,185 @@
/*
* 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.smoketest;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.script.ScriptContextRegistry;
import org.elasticsearch.script.ScriptEngineRegistry;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptSettings;
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.text.DefaultTextTemplateEngine;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.junit.Before;
import org.mockito.Mockito;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Locale;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
public class WatcherTemplateIT extends ESTestCase {
private TextTemplateEngine engine;
@Before
public void init() throws Exception {
Settings setting = Settings.builder().put(ScriptService.SCRIPT_AUTO_RELOAD_ENABLED_SETTING, true).build();
Environment environment = Mockito.mock(Environment.class);
Set<ScriptEngineService> engines = Collections.singleton(new MustacheScriptEngineService(setting));
ResourceWatcherService resourceWatcherService = Mockito.mock(ResourceWatcherService.class);
ScriptContextRegistry registry = new ScriptContextRegistry(Collections.singletonList(ScriptServiceProxy.INSTANCE));
ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry(
Arrays.asList(
new ScriptEngineRegistry.ScriptEngineRegistration(MustacheScriptEngineService.class, MustacheScriptEngineService.TYPES)
)
);
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, registry);
ScriptService scriptService = new ScriptService(setting, environment, engines, resourceWatcherService, scriptEngineRegistry, registry, scriptSettings);
engine = new DefaultTextTemplateEngine(Settings.EMPTY, ScriptServiceProxy.of(scriptService));
}
public void testEscaping() throws Exception {
XContentType contentType = randomFrom(XContentType.values());
if (rarely()) {
contentType = null;
}
Character[] specialChars = new Character[]{'\f', '\n', '\r', '"', '\\', (char) 11, '\t', '\b' };
int iters = scaledRandomIntBetween(100, 1000);
for (int i = 0; i < iters; i++) {
int rounds = scaledRandomIntBetween(1, 20);
StringWriter escaped = new StringWriter(); //This will be escaped as it is constructed
StringWriter unescaped = new StringWriter(); //This will be escaped at the end
for (int j = 0; j < rounds; j++) {
String s = getChars();
unescaped.write(s);
if (contentType == XContentType.JSON) {
escaped.write(JsonStringEncoder.getInstance().quoteAsString(s));
} else {
escaped.write(s);
}
char c = randomFrom(specialChars);
unescaped.append(c);
if (contentType == XContentType.JSON) {
escaped.write(JsonStringEncoder.getInstance().quoteAsString("" + c));
} else {
escaped.append(c);
}
}
if (contentType == XContentType.JSON) {
assertThat(escaped.toString(), equalTo(new String(JsonStringEncoder.getInstance().quoteAsString(unescaped.toString()))));
}
else {
assertThat(escaped.toString(), equalTo(unescaped.toString()));
}
String template = prepareTemplate("{{data}}", contentType);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("data", unescaped.toString());
String renderedTemplate = engine.render(new TextTemplate(template), dataMap);
assertThat(renderedTemplate, notNullValue());
if (contentType == XContentType.JSON) {
if (!escaped.toString().equals(renderedTemplate)) {
String escapedString = escaped.toString();
for (int l = 0; l < renderedTemplate.length() && l < escapedString.length(); ++l) {
if (renderedTemplate.charAt(l) != escapedString.charAt(l)) {
logger.error("at [{}] expected [{}] but got [{}]", l, renderedTemplate.charAt(l), escapedString.charAt(l));
}
}
}
assertThat(escaped.toString(), equalTo(renderedTemplate));
} else {
assertThat(unescaped.toString(), equalTo(renderedTemplate));
}
}
}
public void testSimpleParameterReplace() {
{
String template = "__json__::GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
Map<String, Object> vars = new HashMap<>();
vars.put("boost_val", "0.3");
String result = engine.render(new TextTemplate(template), vars);
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.3 } }}",
result);
}
{
String template = "__json__::GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"{{body_val}}\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
Map<String, Object> vars = new HashMap<>();
vars.put("boost_val", "0.3");
vars.put("body_val", "\"quick brown\"");
String result = engine.render(new TextTemplate(template), vars);
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"\\\"quick brown\\\"\"}}}, \"negative_boost\": 0.3 } }}",
result);
}
}
public void testInvalidPrefixes() throws Exception {
String[] specialStrings = new String[]{"\f", "\n", "\r", "\"", "\\", "\t", "\b", "__::", "__" };
String prefix = randomFrom("", "__", "____::", "___::", "____", "::", "++json__::", "__json__", "+_json__::", "__json__:");
String template = prefix + " {{test_var1}} {{test_var2}}";
Map<String, Object> vars = new HashMap<>();
Writer var1Writer = new StringWriter();
Writer var2Writer = new StringWriter();
for(int i = 0; i < scaledRandomIntBetween(10,1000); ++i) {
var1Writer.write(randomRealisticUnicodeOfCodepointLengthBetween(0, 10));
var2Writer.write(randomRealisticUnicodeOfCodepointLengthBetween(0, 10));
var1Writer.append(randomFrom(specialStrings));
var2Writer.append(randomFrom(specialStrings));
}
vars.put("test_var1", var1Writer.toString());
vars.put("test_var2", var2Writer.toString());
String s1 = engine.render(new TextTemplate(template), vars);
String s2 = prefix + " " + var1Writer.toString() + " " + var2Writer.toString();
assertThat(s1, equalTo(s2));
}
static String getChars() throws IOException {
return randomRealisticUnicodeOfCodepointLengthBetween(0, 10);
}
static String prepareTemplate(String template, @Nullable XContentType contentType) {
if (contentType == null) {
return template;
}
return new StringBuilder("__")
.append(contentType.shortName().toLowerCase(Locale.ROOT))
.append("__::")
.append(template)
.toString();
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.smoketest;
import com.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.RestTestCandidate;
import org.elasticsearch.test.rest.parser.RestTestParseException;
import java.io.IOException;
/** Runs rest tests against external cluster */
public class WatcherWithMustacheIT extends WatcherRestTestCase {
public WatcherWithMustacheIT(@Name("yaml") RestTestCandidate testCandidate) {
super(testCandidate);
}
@ParametersFactory
public static Iterable<Object[]> parameters() throws IOException, RestTestParseException {
return ESRestTestCase.createParameters(0, 1);
}
}

View File

@ -0,0 +1,67 @@
---
"Test webhook action with mustache integration":
- do:
cluster.health:
wait_for_status: yellow
- do: {watcher.stats:{}}
- match: { "watcher_state": "started" }
- match: { "watch_count": 0 }
- do:
watcher.put_watch:
id: "test_watch"
body: >
{
"trigger": {
"schedule": {
"interval": "1s"
}
},
"input": {
"simple" : {
"key" : "value"
}
},
"condition": {
"always" : {}
},
"actions": {
"output": {
"webhook" : {
"method" : "PUT",
"host" : "localhost",
"port" : 9400,
"path": "/my_index/my_type/{{ctx.watch_id}}",
"body" : {
"inline": {
"watch_id" : "{{ctx.watch_id}}"
},
"params": {}
}
}
}
}
}
- match: { _id: "test_watch" }
- match: { created: true }
- do: {watcher.stats:{}}
- match: { "watch_count": 1 }
# Simulate a Thread.sleep()
- do:
catch: request_timeout
cluster.health:
wait_for_nodes: 99
timeout: 10s
- match: { "timed_out": true }
- do:
get:
index: my_index
type: my_type
id: test_watch
- match: { _source: { watch_id: "test_watch" } }

View File

@ -0,0 +1,62 @@
---
"Test array access":
- do:
cluster.health:
wait_for_status: yellow
- do: {watcher.stats:{}}
- match: { "watcher_state": "started" }
- match: { "watch_count": 0 }
- do:
watcher.put_watch:
id: "test_watch"
body: >
{
"trigger": {
"schedule": {
"interval": "1s"
}
},
"input": {
"simple" : {
"objects" : [
{
"field": "value1"
},
{
"field": "value2"
}
]
}
},
"condition": {
"always" : {}
},
"actions": {
"output": {
"logging" : {
"text" : "{{ctx.payload.objects.0.field}} {{ctx.payload.objects.1.field}}"
}
}
}
}
- match: { _id: "test_watch" }
- match: { created: true }
- do: {watcher.stats:{}}
- match: { "watch_count": 1 }
# Simulate a Thread.sleep()
- do:
catch: request_timeout
cluster.health:
wait_for_nodes: 99
timeout: 10s
- match: { "timed_out": true }
- do:
search:
index: .watch_history-*
- match: { hits.hits.0._source.result.actions.0.logging.logged_text: "value1 value2" }

View File

@ -59,7 +59,6 @@ import org.elasticsearch.watcher.support.init.InitializingService;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.secret.SecretModule;
import org.elasticsearch.watcher.support.text.TextTemplateModule;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheScriptEngineService;
import org.elasticsearch.watcher.support.validation.WatcherSettingsValidation;
import org.elasticsearch.watcher.transform.TransformModule;
import org.elasticsearch.watcher.transport.actions.ack.AckWatchAction;
@ -182,9 +181,6 @@ public class WatcherPlugin extends Plugin {
public void onModule(ScriptModule module) {
module.registerScriptContext(ScriptServiceProxy.INSTANCE);
if (enabled && !transportClient) {
module.addScriptEngine(new ScriptEngineRegistry.ScriptEngineRegistration(XMustacheScriptEngineService.class, XMustacheScriptEngineService.TYPES));
}
}
public void onModule(SettingsModule module) {

View File

@ -45,12 +45,12 @@ public class ScriptServiceProxy implements InitializingService.Initializable {
public CompiledScript compile(Script script) {
return securityContext.executeAs(XPackUser.INSTANCE, () ->
compile(new org.elasticsearch.script.Script(script.script(), script.type(), script.lang(), script.params())));
compile(new org.elasticsearch.script.Script(script.script(), script.type(), script.lang(), script.params()), Collections.emptyMap()));
}
public CompiledScript compile(org.elasticsearch.script.Script script) {
public CompiledScript compile(org.elasticsearch.script.Script script, Map<String, String> compileParams) {
return securityContext.executeAs(XPackUser.INSTANCE, () ->
service.compile(script, WatcherScriptContext.CTX, Collections.emptyMap()));
service.compile(script, WatcherScriptContext.CTX, compileParams));
}
public ExecutableScript executable(CompiledScript compiledScript, Map<String, Object> vars) {

View File

@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.Template;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class DefaultTextTemplateEngine extends AbstractComponent implements TextTemplateEngine {
private final ScriptServiceProxy service;
@Inject
public DefaultTextTemplateEngine(Settings settings, ScriptServiceProxy service) {
super(settings);
this.service = service;
}
@Override
public String render(TextTemplate template, Map<String, Object> model) {
XContentType contentType = detectContentType(template);
Map<String, String> compileParams = compileParams(contentType);
template = trimContentType(template);
CompiledScript compiledScript = service.compile(convert(template, model), compileParams);
ExecutableScript executable = service.executable(compiledScript, model);
Object result = executable.run();
if (result instanceof BytesReference) {
return ((BytesReference) result).toUtf8();
}
return result.toString();
}
private TextTemplate trimContentType(TextTemplate textTemplate) {
String template = textTemplate.getTemplate();
if (!template.startsWith("__")){
return textTemplate; //Doesn't even start with __ so can't have a content type
}
int index = template.indexOf("__::", 3); //There must be a __<content_type__:: prefix so the minimum length before detecting '__::' is 3
if (index >= 0 && index < 12) { //Assume that the content type name is less than 10 characters long otherwise we may falsely detect strings that start with '__ and have '__::' somewhere in the content
if (template.length() == 6) {
template = "";
} else {
template = template.substring(index + 4);
}
}
return new TextTemplate(template, textTemplate.getContentType(), textTemplate.getType(), textTemplate.getParams());
}
private XContentType detectContentType(TextTemplate textTemplate) {
String template = textTemplate.getTemplate();
if (template.startsWith("__")) {
int endOfContentName = template.indexOf("__::", 3); //There must be a __<content_type__:: prefix so the minimum length before detecting '__::' is 3
if (endOfContentName != -1) {
return XContentType.fromRestContentType(template.substring(2, endOfContentName));
}
}
return null;
}
private Template convert(TextTemplate textTemplate, Map<String, Object> model) {
Map<String, Object> mergedModel = new HashMap<>();
mergedModel.putAll(textTemplate.getParams());
mergedModel.putAll(model);
return new Template(textTemplate.getTemplate(), textTemplate.getType(), "mustache", textTemplate.getContentType(), mergedModel);
}
private Map<String, String> compileParams(XContentType contentType) {
if (contentType == XContentType.JSON) {
return Collections.singletonMap("content_type", "application/json");
} else {
return Collections.singletonMap("content_type", "text/plain");
}
}
}

View File

@ -31,11 +31,11 @@ public class TextTemplate implements ToXContent {
private final @Nullable ScriptType type;
private final @Nullable Map<String, Object> params;
TextTemplate(String template) {
public TextTemplate(String template) {
this(template, null, null, null);
}
TextTemplate(String template, @Nullable XContentType contentType, @Nullable ScriptType type, @Nullable Map<String, Object> params) {
public TextTemplate(String template, @Nullable XContentType contentType, @Nullable ScriptType type, @Nullable Map<String, Object> params) {
this.template = template;
this.contentType = contentType;
this.type = type;

View File

@ -6,7 +6,6 @@
package org.elasticsearch.watcher.support.text;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheTextTemplateEngine;
/**
*
@ -15,7 +14,7 @@ public class TextTemplateModule extends AbstractModule {
@Override
protected void configure() {
bind(XMustacheTextTemplateEngine.class).asEagerSingleton();
bind(TextTemplateEngine.class).to(XMustacheTextTemplateEngine.class);
bind(DefaultTextTemplateEngine.class).asEagerSingleton();
bind(TextTemplateEngine.class).to(DefaultTextTemplateEngine.class);
}
}

View File

@ -1,164 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text.xmustache;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.MustacheException;
import com.github.mustachejava.reflect.ReflectionObjectHandler;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.watcher.support.ArrayObjectIterator;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* An extension to elasticsearch's {@code JsonEscapingMustacheFactory} that on top of applying json
* escapes it also enables support for navigating arrays using `array.X` notation (where `X` is the index
* of the element in the array).
*/
public class XMustacheFactory extends DefaultMustacheFactory {
final XContentType contentType;
public XMustacheFactory(XContentType contentType) {
this.contentType = contentType;
setObjectHandler(new ReflectionObjectHandler() {
@Override
public Object coerce(Object object) {
if (object != null) {
if (object.getClass().isArray()) {
return new ArrayMap(object);
} else if (object instanceof Collection) {
return new CollectionMap((Collection) object);
}
}
return super.coerce(object);
}
});
}
@Override
public void encode(String value, Writer writer) {
try {
if (contentType == XContentType.JSON) {
writer.write(JsonStringEncoder.getInstance().quoteAsString(value));
} else {
writer.write(value);
}
} catch (IOException e) {
throw new MustacheException("Failed to encode value: " + value);
}
}
static class ArrayMap extends AbstractMap<Object, Object> implements Iterable<Object> {
private final Object array;
public ArrayMap(Object array) {
this.array = array;
}
@Override
public Object get(Object key) {
if (key instanceof Number) {
return Array.get(array, ((Number) key).intValue());
}
try {
int index = Integer.parseInt(key.toString());
return Array.get(array, index);
} catch (NumberFormatException nfe) {
// if it's not a number it is as if the key doesn't exist
return null;
}
}
@Override
public boolean containsKey(Object key) {
return get(key) != null;
}
@Override
public Set<Entry<Object, Object>> entrySet() {
int length = Array.getLength(array);
Map<Object, Object> map = new HashMap<>(length);
for (int i = 0; i < length; i++) {
map.put(i, Array.get(array, i));
}
return map.entrySet();
}
/**
* Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
@Override
public Iterator<Object> iterator() {
return new ArrayObjectIterator(array);
}
}
static class CollectionMap extends AbstractMap<Object, Object> implements Iterable<Object> {
private final Collection col;
public CollectionMap(Collection col) {
this.col = col;
}
@Override
public Object get(Object key) {
if (key instanceof Number) {
return Iterables.get(col, ((Number) key).intValue());
}
try {
int index = Integer.parseInt(key.toString());
return Iterables.get(col, index);
} catch (NumberFormatException nfe) {
// if it's not a number it is as if the key doesn't exist
return null;
}
}
@Override
public boolean containsKey(Object key) {
return get(key) != null;
}
@Override
public Set<Entry<Object, Object>> entrySet() {
Map<Object, Object> map = new HashMap<>(col.size());
int i = 0;
for (Object item : col) {
map.put(i++, item);
}
return map.entrySet();
}
/**
* Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
@Override
public Iterator<Object> iterator() {
return col.iterator();
}
}
}

View File

@ -1,200 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text.xmustache;
import com.github.mustachejava.Mustache;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.FastStringReader;
import org.elasticsearch.common.io.UTF8StreamWriter;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.lookup.SearchLookup;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
*
*/
public class XMustacheScriptEngineService extends AbstractComponent implements ScriptEngineService {
public static final String NAME = "xmustache";
public static final List<String> TYPES = Collections.singletonList(NAME);
/**
* @param settings automatically wired by Guice.
* */
@Inject
public XMustacheScriptEngineService(Settings settings) {
super(settings);
}
/**
* Compile a template string to (in this case) a Mustache object than can
* later be re-used for execution to fill in missing parameter values.
*
* @param template
* a string representing the template to compile.
* @return a compiled template object for later execution.
* */
@Override
public Object compile(String template, Map<String, String> params) {
/** Factory to generate Mustache objects from. */
XContentType xContentType = detectContentType(template);
template = trimContentType(template);
return (new XMustacheFactory(xContentType)).compile(new FastStringReader(template), "query-template");
}
@Override
public List<String> getTypes() {
return TYPES;
}
@Override
public List<String> getExtensions() {
return TYPES;
}
@Override
public boolean isSandboxed() {
return true;
}
@Override
public ExecutableScript executable(CompiledScript compiledScript,
@Nullable Map<String, Object> vars) {
return new MustacheExecutableScript((Mustache) compiledScript.compiled(), vars);
}
@Override
public SearchScript search(CompiledScript compiledScript, SearchLookup lookup,
@Nullable Map<String, Object> vars) {
throw new UnsupportedOperationException();
}
@Override
public void close() {
// Nothing to do here
}
@Override
public void scriptRemoved(CompiledScript script) {
// Nothing to do here
}
public static String prepareTemplate(String template, @Nullable XContentType contentType) {
if (contentType == null) {
return template;
}
return new StringBuilder("__")
.append(contentType.shortName().toLowerCase(Locale.ROOT))
.append("__::")
.append(template)
.toString();
}
/**
* Used at query execution time by script service in order to execute a query template.
* */
private class MustacheExecutableScript implements ExecutableScript {
/** Compiled template object. */
private Mustache mustache;
/** Parameters to fill above object with. */
private Map<String, Object> vars;
/**
* @param mustache the compiled template object
* @param vars the parameters to fill above object with
**/
public MustacheExecutableScript(Mustache mustache,
Map<String, Object> vars) {
this.mustache = mustache;
this.vars = vars == null ? Collections.<String, Object>emptyMap() : vars;
}
@Override
public void setNextVar(String name, Object value) {
this.vars.put(name, value);
}
@Override
public Object run() {
BytesStreamOutput result = new BytesStreamOutput();
UTF8StreamWriter writer = utf8StreamWriter().setOutput(result);
mustache.execute(writer, vars);
try {
writer.flush();
} catch (IOException e) {
logger.error("Could not execute query template (failed to flush writer): ", e);
} finally {
try {
writer.close();
} catch (IOException e) {
logger.error("Could not execute query template (failed to close writer): ", e);
}
}
return result.bytes();
}
@Override
public Object unwrap(Object value) {
return value;
}
}
/** Thread local UTF8StreamWriter to store template execution results in, thread local to save object creation.*/
private static ThreadLocal<SoftReference<UTF8StreamWriter>> utf8StreamWriter = new ThreadLocal<>();
/** If exists, reset and return, otherwise create, reset and return a writer.*/
private static UTF8StreamWriter utf8StreamWriter() {
SoftReference<UTF8StreamWriter> ref = utf8StreamWriter.get();
UTF8StreamWriter writer = (ref == null) ? null : ref.get();
if (writer == null) {
writer = new UTF8StreamWriter(1024 * 4);
utf8StreamWriter.set(new SoftReference<>(writer));
}
writer.reset();
return writer;
}
private String trimContentType(String template) {
if (!template.startsWith("__")){
return template; //Doesn't even start with __ so can't have a content type
}
int index = template.indexOf("__::", 3); //There must be a __<content_type__:: prefix so the minimum length before detecting '__::' is 3
if (index >= 0 && index < 12) { //Assume that the content type name is less than 10 characters long otherwise we may falsely detect strings that start with '__ and have '__::' somewhere in the content
if (template.length() == 6) {
template = "";
} else {
template = template.substring(index + 4);
}
}
return template;
}
private XContentType detectContentType(String template) {
if (template.startsWith("__")) {
int endOfContentName = template.indexOf("__::", 3); //There must be a __<content_type__:: prefix so the minimum length before detecting '__::' is 3
if (endOfContentName != -1) {
return XContentType.fromRestContentType(template.substring(2, endOfContentName));
}
}
return null;
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text.xmustache;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class XMustacheTextTemplateEngine extends AbstractComponent implements TextTemplateEngine {
private final ScriptServiceProxy service;
@Inject
public XMustacheTextTemplateEngine(Settings settings, ScriptServiceProxy service) {
super(settings);
this.service = service;
}
@Override
public String render(TextTemplate template, Map<String, Object> model) {
Map<String, Object> mergedModel = new HashMap<>();
mergedModel.putAll(template.getParams());
mergedModel.putAll(model);
ExecutableScript executable = service.executable(new org.elasticsearch.script.Template(template.getTemplate(), template.getType(), XMustacheScriptEngineService.NAME, template.getContentType(), mergedModel));
Object result = executable.run();
if (result instanceof BytesReference) {
return ((BytesReference) result).toUtf8();
}
return result.toString();
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.script;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.search.lookup.SearchLookup;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A mock script engine that registers itself under the 'mustache' name so that {@link org.elasticsearch.watcher.support.text.DefaultTextTemplateEngine}
* uses it and adds validation that watcher tests don't rely on mustache templating/
*/
public class MockMustacheScriptEngine extends MockScriptEngine {
public static final String NAME = "mustache";
public static class TestPlugin extends MockScriptEngine.TestPlugin {
@Override
public String name() {
return NAME;
}
public void onModule(ScriptModule module) {
module.addScriptEngine(new ScriptEngineRegistry.ScriptEngineRegistration(MockMustacheScriptEngine.class, Collections.singletonList(NAME)));
}
}
@Override
public List<String> getTypes() {
return Collections.singletonList(NAME);
}
@Override
public List<String> getExtensions() {
return getTypes();
}
@Override
public Object compile(String script, Map<String, String> params) {
if (script.contains("{{") && script.contains("}}")) {
throw new IllegalArgumentException("Fix your test to not rely on mustache");
}
return script;
}
}

View File

@ -3,13 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.messy.tests;
package org.elasticsearch.watcher.actions;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.condition.compare.CompareCondition;
import org.elasticsearch.watcher.execution.ExecutionState;
@ -20,8 +17,6 @@ import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
@ -41,14 +36,6 @@ import static org.hamcrest.Matchers.is;
*/
public class TimeThrottleIntegrationTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = new ArrayList<>();
types.addAll(super.pluginTypes());
types.add(MustachePlugin.class);
return types;
}
private IndexResponse indexTestDoc() {
createIndex("actions", "events");
ensureGreen("actions", "events");

View File

@ -35,7 +35,6 @@ import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpRequestTemplate;
import org.elasticsearch.watcher.support.http.HttpRequestTemplateTests;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuthFactory;
@ -45,6 +44,7 @@ import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.support.xcontent.WatcherParams;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.test.MockTextTemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.joda.time.DateTime;
@ -90,7 +90,7 @@ public class EmailActionTests extends ESTestCase {
@Before
public void addEmailAttachmentParsers() {
emailAttachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, new HttpRequestTemplate.Parser(registry), new HttpRequestTemplateTests.MockTextTemplateEngine()));
emailAttachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, new HttpRequestTemplate.Parser(registry), new MockTextTemplateEngine()));
emailAttachmentParsers.put(DataAttachmentParser.TYPE, new DataAttachmentParser());
emailAttachmentParser = new EmailAttachmentsParser(emailAttachmentParsers);
}

View File

@ -3,7 +3,7 @@
* 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.messy.tests;
package org.elasticsearch.watcher.actions.email;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
@ -14,8 +14,6 @@ import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate;
import org.elasticsearch.watcher.actions.email.service.attachment.DataAttachment;
import org.elasticsearch.watcher.actions.email.service.attachment.EmailAttachmentParser;
@ -87,14 +85,6 @@ public class EmailAttachmentTests extends AbstractWatcherIntegrationTestCase {
webServer.shutdown();
}
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = new ArrayList<>();
types.addAll(super.pluginTypes());
types.add(MustachePlugin.class);
return types;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
if(server == null) {

View File

@ -13,12 +13,12 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpRequestTemplate;
import org.elasticsearch.watcher.support.http.HttpRequestTemplateTests;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuth;
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuthFactory;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.test.MockTextTemplateEngine;
import org.junit.Before;
import java.util.HashMap;
@ -54,7 +54,7 @@ public class HttpEmailAttachementParserTests extends ESTestCase {
public void testSerializationWorks() throws Exception {
Map<String, EmailAttachmentParser> attachmentParsers = new HashMap<>();
attachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, httpRequestTemplateParser, new HttpRequestTemplateTests.MockTextTemplateEngine()));
attachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, httpRequestTemplateParser, new MockTextTemplateEngine()));
EmailAttachmentsParser emailAttachmentsParser = new EmailAttachmentsParser(attachmentParsers);
String id = "some-id";

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.test.MockTextTemplateEngine;
import java.io.IOException;
import java.util.ArrayList;
@ -441,12 +442,7 @@ public class SlackMessageTests extends ESTestCase {
}
// relies on the fact that all the templates we use are inline templates without param place holders
TextTemplateEngine engine = new TextTemplateEngine() {
@Override
public String render(TextTemplate template, Map<String, Object> model) {
return template.getTemplate();
}
};
TextTemplateEngine engine = new MockTextTemplateEngine();
SlackMessage.Template template = templateBuilder.build();

View File

@ -385,7 +385,7 @@ public class ActionThrottleTests extends AbstractWatcherIntegrationTestCase {
LOGGING {
@Override
public Action.Builder action() throws Exception {
TextTemplate.Builder templateBuilder = new TextTemplate.Builder.Inline("{{ctx.watch_id}}");
TextTemplate.Builder templateBuilder = new TextTemplate.Builder.Inline("_logging");
return LoggingAction.builder(templateBuilder.build());
}

View File

@ -37,8 +37,9 @@ import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheTextTemplateEngine;
import org.elasticsearch.watcher.support.text.DefaultTextTemplateEngine;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.test.MockTextTemplateEngine;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
@ -50,7 +51,6 @@ import org.junit.Before;
import javax.mail.internet.AddressException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.singletonMap;
@ -78,8 +78,6 @@ public class WebhookActionTests extends ESTestCase {
static final String TEST_HOST = "test.com";
static final int TEST_PORT = 8089;
private ThreadPool tp = null;
private ScriptServiceProxy scriptService;
private TextTemplateEngine templateEngine;
private HttpAuthRegistry authRegistry;
private TextTemplate testBody;
@ -91,21 +89,13 @@ public class WebhookActionTests extends ESTestCase {
@Before
public void init() throws Exception {
tp = new ThreadPool(ThreadPool.Names.SAME);
Settings settings = Settings.EMPTY;
scriptService = WatcherTestUtils.getScriptServiceProxy(tp);
templateEngine = new XMustacheTextTemplateEngine(settings, scriptService);
templateEngine = new MockTextTemplateEngine();
SecretService secretService = mock(SecretService.class);
testBody = TextTemplate.inline(TEST_BODY_STRING).build();
testPath = TextTemplate.inline(TEST_PATH_STRING).build();
authRegistry = new HttpAuthRegistry(singletonMap("basic", new BasicAuthFactory(secretService)));
}
@After
public void cleanup() {
tp.shutdownNow();
}
public void testExecute() throws Exception {
ExecuteScenario scenario = randomFrom(ExecuteScenario.Success, ExecuteScenario.ErrorCode);
@ -236,56 +226,6 @@ public class WebhookActionTests extends ESTestCase {
return new WebhookActionFactory(Settings.EMPTY, client, new HttpRequestTemplate.Parser(authRegistry), templateEngine);
}
public void testTemplatedHttpRequest() throws Exception {
HttpClient httpClient = ExecuteScenario.Success.client();
String body = "{{ctx.watch_id}}";
String host = "testhost";
String path = randomFrom("{{ctx.execution_time}}", "{{ctx.trigger.scheduled_time}}", "{{ctx.trigger.triggered_time}}");
Map<String, TextTemplate> params = new HashMap<>();
params.put("foo", TextTemplate.inline(randomFrom("{{ctx.execution_time}}", "{{ctx.trigger.scheduled_time}}", "{{ctx.trigger.triggered_time}}")).build());
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
HttpRequestTemplate request = getHttpRequestTemplate(method, host, TEST_PORT, TextTemplate.inline(path).build(), TextTemplate.inline(body).build(), params);
String watchId = "_watch";
String actionId = randomAsciiOfLength(5);
WebhookAction action = WebhookAction.builder(request).build();
ExecutableWebhookAction webhookAction = new ExecutableWebhookAction(action, logger, httpClient, templateEngine);
DateTime time = new DateTime(UTC);
Watch watch = createWatch(watchId, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, time, new ScheduleTriggerEvent(watchId, time, time), timeValueSeconds(5));
Action.Result result = webhookAction.execute(actionId, ctx, Payload.EMPTY);
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Success.class));
WebhookAction.Result.Success success = (WebhookAction.Result.Success) result;
assertThat(success.request().body(), equalTo(watchId));
assertThat(success.request().path(), equalTo(time.toString()));
assertThat(success.request().params().get("foo"), equalTo(time.toString()));
}
public void testValidUrls() throws Exception {
HttpClient httpClient = ExecuteScenario.Success.client();
HttpMethod method = HttpMethod.POST;
TextTemplate path = TextTemplate.defaultType("/test_{{ctx.watch_id}}").build();
String host = "test.host";
HttpRequestTemplate requestTemplate = getHttpRequestTemplate(method, host, TEST_PORT, path, testBody, null);
WebhookAction action = new WebhookAction(requestTemplate);
ExecutableWebhookAction executable = new ExecutableWebhookAction(action, logger, httpClient, templateEngine);
String watchId = "test_url_encode" + randomAsciiOfLength(10);
Watch watch = createWatch(watchId, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(UTC), new ScheduleTriggerEvent(watchId, new DateTime(UTC), new DateTime(UTC)), timeValueSeconds(5));
Action.Result result = executable.execute("_id", ctx, new Payload.Simple());
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Success.class));
}
public void testThatSelectingProxyWorks() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.EMPTY, authRegistry, environment).start();
@ -311,10 +251,30 @@ public class WebhookActionTests extends ESTestCase {
}
}
public void testValidUrls() throws Exception {
HttpClient client = mock(HttpClient.class);
when(client.execute(any(HttpRequest.class)))
.thenReturn(new HttpResponse(randomIntBetween(200, 399)));
String watchId = "test_url_encode" + randomAsciiOfLength(10);
HttpMethod method = HttpMethod.POST;
TextTemplate path = TextTemplate.defaultType("/test_" + watchId).build();
String host = "test.host";
TextTemplate testBody = TextTemplate.inline("ERROR HAPPENED").build();
HttpRequestTemplate requestTemplate = getHttpRequestTemplate(method, host, TEST_PORT, path, testBody, null);
WebhookAction action = new WebhookAction(requestTemplate);
ExecutableWebhookAction executable = new ExecutableWebhookAction(action, logger, client, templateEngine);
Watch watch = createWatch(watchId, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new TriggeredExecutionContext(watch, new DateTime(UTC), new ScheduleTriggerEvent(watchId, new DateTime(UTC), new DateTime(UTC)), timeValueSeconds(5));
Action.Result result = executable.execute("_id", ctx, new Payload.Simple());
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Success.class));
}
private Watch createWatch(String watchId, ClientProxy client, final String account) throws AddressException, IOException {
return WatcherTestUtils.createTestWatch(watchId,
client,
scriptService,
ExecuteScenario.Success.client(),
new AbstractWatcherIntegrationTestCase.NoopEmailService() {
@Override

View File

@ -83,8 +83,8 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
HttpRequestTemplate.Builder builder = HttpRequestTemplate.builder("localhost", webPort)
.scheme(Scheme.HTTPS)
.path(TextTemplate.inline("/test/{{ctx.watch_id}}").build())
.body(TextTemplate.inline("{{ctx.payload}}").build());
.path(TextTemplate.inline("/test/_id").build())
.body(TextTemplate.inline("{key=value}").build());
watcherClient().preparePutWatch("_id")
.setSource(watchBuilder()
@ -126,8 +126,8 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest
HttpRequestTemplate.Builder builder = HttpRequestTemplate.builder("localhost", webPort)
.scheme(Scheme.HTTPS)
.auth(new BasicAuth("_username", "_password".toCharArray()))
.path(TextTemplate.inline("/test/{{ctx.watch_id}}").build())
.body(TextTemplate.inline("{{ctx.payload}}").build());
.path(TextTemplate.inline("/test/_id").build())
.body(TextTemplate.inline("{key=value}").build());
watcherClient().preparePutWatch("_id")
.setSource(watchBuilder()

View File

@ -70,10 +70,10 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTestCase
public void testWebhook() throws Exception {
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
HttpRequestTemplate.Builder builder = HttpRequestTemplate.builder("localhost", webPort)
.path(TextTemplate.inline("/test/{{ctx.watch_id}}"))
.path(TextTemplate.inline("/test/_id"))
.putParam("param1", TextTemplate.inline("value1"))
.putParam("watch_id", TextTemplate.inline("{{ctx.watch_id}}"))
.body(TextTemplate.inline("{{ctx.payload}}"));
.putParam("watch_id", TextTemplate.inline("_id"))
.body(TextTemplate.inline("_body"));
watcherClient().preparePutWatch("_id")
.setSource(watchBuilder()
@ -91,7 +91,7 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTestCase
assertWatchWithMinimumPerformedActionsCount("_id", 1, false);
RecordedRequest recordedRequest = webServer.takeRequest();
assertThat(recordedRequest.getPath(), anyOf(equalTo("/test/_id?watch_id=_id&param1=value1"), equalTo("/test/_id?param1=value1&watch_id=_id")));
assertThat(recordedRequest.getBody().readUtf8Line(), equalTo("{key=value}"));
assertThat(recordedRequest.getBody().readUtf8Line(), equalTo("_body"));
SearchResponse response = searchWatchRecords(new Callback<SearchRequestBuilder>() {
@Override
@ -114,10 +114,10 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTestCase
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
HttpRequestTemplate.Builder builder = HttpRequestTemplate.builder("localhost", webPort)
.auth(new BasicAuth("_username", "_password".toCharArray()))
.path(TextTemplate.inline("/test/{{ctx.watch_id}}").build())
.path(TextTemplate.inline("/test/_id").build())
.putParam("param1", TextTemplate.inline("value1").build())
.putParam("watch_id", TextTemplate.inline("{{ctx.watch_id}}").build())
.body(TextTemplate.inline("{{ctx.payload}}").build());
.putParam("watch_id", TextTemplate.inline("_id").build())
.body(TextTemplate.inline("_body").build());
watcherClient().preparePutWatch("_id")
.setSource(watchBuilder()
@ -135,7 +135,7 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTestCase
assertWatchWithMinimumPerformedActionsCount("_id", 1, false);
RecordedRequest recordedRequest = webServer.takeRequest();
assertThat(recordedRequest.getPath(), anyOf(equalTo("/test/_id?watch_id=_id&param1=value1"), equalTo("/test/_id?param1=value1&watch_id=_id")));
assertThat(recordedRequest.getBody().readUtf8Line(), equalTo("{key=value}"));
assertThat(recordedRequest.getBody().readUtf8Line(), equalTo("_body"));
assertThat(recordedRequest.getHeader("Authorization"), equalTo("Basic X3VzZXJuYW1lOl9wYXNzd29yZA=="));
}
}

View File

@ -21,7 +21,7 @@ import static org.hamcrest.Matchers.equalTo;
*/
public class TriggeredWatchTests extends AbstractWatcherIntegrationTestCase {
public void testParser() throws Exception {
Watch watch = WatcherTestUtils.createTestWatch("fired_test", scriptService(), watcherHttpClient(), noopEmailService(), logger);
Watch watch = WatcherTestUtils.createTestWatch("fired_test", watcherHttpClient(), noopEmailService(), logger);
ScheduleTriggerEvent event = new ScheduleTriggerEvent(watch.id(), DateTime.now(DateTimeZone.UTC), DateTime.now(DateTimeZone.UTC));
Wid wid = new Wid("_record", randomLong(), DateTime.now(DateTimeZone.UTC));
TriggeredWatch triggeredWatch = new TriggeredWatch(wid, event);

View File

@ -3,23 +3,17 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.messy.tests;
package org.elasticsearch.watcher.history;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.watcher.execution.ExecutionState;
import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
@ -38,14 +32,6 @@ import static org.hamcrest.Matchers.notNullValue;
*/
public class HistoryTemplateSearchInputMappingsTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = new ArrayList<>();
types.addAll(super.pluginTypes());
types.add(MustachePlugin.class);
return types;
}
@Override
protected boolean timeWarped() {
return true; // just to have better control over the triggers

View File

@ -47,7 +47,7 @@ public class ChainIntegrationTests extends AbstractWatcherIntegrationTestCase {
InetSocketAddress address = internalCluster().httpAddresses()[0];
HttpInput.Builder httpInputBuilder = httpInput(HttpRequestTemplate.builder(address.getHostString(), address.getPort())
.path("{{ctx.payload.first.url}}")
.path("/" + index + "/_search")
.body(jsonBuilder().startObject().field("size", 1).endObject())
.auth(shieldEnabled() ? new BasicAuth("test", "changeme".toCharArray()) : null));

View File

@ -5,27 +5,22 @@
*/
package org.elasticsearch.watcher.support.http;
import com.google.common.collect.Lists;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.search.aggregations.support.format.ValueParser;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuth;
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuthFactory;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.test.MockTextTemplateEngine;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
@ -66,6 +61,21 @@ public class HttpRequestTemplateTests extends ESTestCase {
assertThat(request.proxy().getPort(), is(8080));
}
public void testRender() {
HttpRequestTemplate template = HttpRequestTemplate.builder("_host", 1234)
.body(TextTemplate.inline("_body"))
.path(TextTemplate.inline("_path"))
.putParam("_key1", TextTemplate.inline("_value1"))
.putHeader("_key2", TextTemplate.inline("_value2"))
.build();
HttpRequest result = template.render(new MockTextTemplateEngine(), Collections.emptyMap());
assertThat(result.body(), equalTo("_body"));
assertThat(result.path(), equalTo("_path"));
assertThat(result.params(), equalTo(Collections.singletonMap("_key1", "_value1")));
assertThat(result.headers(), equalTo(Collections.singletonMap("_key2", "_value2")));
}
public void testProxyParsing() throws Exception {
HttpRequestTemplate.Builder builder = HttpRequestTemplate.builder("_host", 1234);
builder.path("/path");
@ -169,10 +179,4 @@ public class HttpRequestTemplateTests extends ESTestCase {
assertThat(parsedTemplate, is(urlParsedTemplate));
}
public static class MockTextTemplateEngine implements TextTemplateEngine {
@Override
public String render(TextTemplate template, Map<String, Object> model) {
return template.getTemplate();
}
}
}

View File

@ -12,13 +12,18 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.script.Template;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheTextTemplateEngine;
import org.elasticsearch.watcher.support.text.DefaultTextTemplateEngine;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.junit.Before;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -32,20 +37,18 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class TextTemplateTests extends ESTestCase {
private ScriptServiceProxy proxy;
private TextTemplateEngine engine;
private ExecutableScript script;
private final String lang = "xmustache";
private final String lang = "mustache";
@Before
public void init() throws Exception {
proxy = mock(ScriptServiceProxy.class);
script = mock(ExecutableScript.class);
engine = new XMustacheTextTemplateEngine(Settings.EMPTY, proxy);
engine = new DefaultTextTemplateEngine(Settings.EMPTY, proxy);
}
public void testRender() throws Exception {
@ -57,7 +60,9 @@ public class TextTemplateTests extends ESTestCase {
merged = unmodifiableMap(merged);
ScriptType type = randomFrom(ScriptType.values());
when(proxy.executable(new org.elasticsearch.script.Template(templateText, type, lang, null, merged))).thenReturn(script);
CompiledScript compiledScript = mock(CompiledScript.class);
when(proxy.compile(new Template(templateText, type, lang, null, merged), Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript);
when(proxy.executable(compiledScript, model)).thenReturn(script);
when(script.run()).thenReturn("rendered_text");
TextTemplate template = templateBuilder(type, templateText).params(params).build();
@ -70,7 +75,9 @@ public class TextTemplateTests extends ESTestCase {
Map<String, Object> model = singletonMap("key", "model_val");
ScriptType scriptType = randomFrom(ScriptType.values());
when(proxy.executable(new org.elasticsearch.script.Template(templateText, scriptType, lang, null, model))).thenReturn(script);
CompiledScript compiledScript = mock(CompiledScript.class);
when(proxy.compile(new Template(templateText, scriptType, lang, null, model), Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript);
when(proxy.executable(compiledScript, model)).thenReturn(script);
when(script.run()).thenReturn("rendered_text");
TextTemplate template = templateBuilder(scriptType, templateText).params(params).build();
@ -81,7 +88,9 @@ public class TextTemplateTests extends ESTestCase {
String templateText = "_template";
Map<String, Object> model = singletonMap("key", "model_val");
when(proxy.executable(new org.elasticsearch.script.Template(templateText, ScriptType.INLINE, lang, null, model))).thenReturn(script);
CompiledScript compiledScript = mock(CompiledScript.class);
when(proxy.compile(new Template(templateText, ScriptType.INLINE, lang, null, model), Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript);
when(proxy.executable(compiledScript, model)).thenReturn(script);
when(script.run()).thenReturn("rendered_text");
TextTemplate template = new TextTemplate(templateText);

View File

@ -1,83 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text.xmustache;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class XMustacheScriptEngineTests extends ESTestCase {
private XMustacheScriptEngineService engine;
@Before
public void setup() {
engine = new XMustacheScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
}
public void testSimpleParameterReplace() {
{
String template = "__json__::GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
Map<String, Object> vars = new HashMap<>();
vars.put("boost_val", "0.3");
CompiledScript compiledScript = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
BytesReference o = (BytesReference) engine.executable(compiledScript, vars).run();
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.3 } }}",
new String(o.toBytes(), Charset.forName("UTF-8")));
}
{
String template = "__json__::GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"{{body_val}}\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
Map<String, Object> vars = new HashMap<>();
vars.put("boost_val", "0.3");
vars.put("body_val", "\"quick brown\"");
CompiledScript compiledScript = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
BytesReference o = (BytesReference) engine.executable(compiledScript, vars).run();
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"\\\"quick brown\\\"\"}}}, \"negative_boost\": 0.3 } }}",
new String(o.toBytes(), Charset.forName("UTF-8")));
}
}
public void testInvalidPrefixes() throws Exception {
String[] specialStrings = new String[]{"\f", "\n", "\r", "\"", "\\", "\t", "\b", "__::", "__" };
String prefix = randomFrom("", "__", "____::", "___::", "____", "::", "++json__::", "__json__", "+_json__::", "__json__:");
String template = prefix + " {{test_var1}} {{test_var2}}";
Map<String, Object> vars = new HashMap<>();
Writer var1Writer = new StringWriter();
Writer var2Writer = new StringWriter();
for(int i = 0; i < scaledRandomIntBetween(10,1000); ++i) {
var1Writer.write(randomRealisticUnicodeOfCodepointLengthBetween(0, 10));
var2Writer.write(randomRealisticUnicodeOfCodepointLengthBetween(0, 10));
var1Writer.append(randomFrom(specialStrings));
var2Writer.append(randomFrom(specialStrings));
}
vars.put("test_var1", var1Writer.toString());
vars.put("test_var2", var2Writer.toString());
CompiledScript compiledScript = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
BytesReference o = (BytesReference) engine.executable(compiledScript, vars).run();
String s1 = o.toUtf8();
String s2 = prefix + " " + var1Writer.toString() + " " + var2Writer.toString();
assertEquals(s1, s2);
}
}

View File

@ -1,185 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.support.text.xmustache;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
/**
*
*/
public class XMustacheTests extends ESTestCase {
private ScriptEngineService engine;
@Before
public void init() throws Exception {
engine = new XMustacheScriptEngineService(Settings.EMPTY);
}
public void testArrayAccess() throws Exception {
String template = "{{data.0}} {{data.1}}";
CompiledScript mustache = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
Map<String, Object> vars = new HashMap<>();
Object data = randomFrom(
new String[] { "foo", "bar" },
Arrays.asList("foo", "bar"));
vars.put("data", data);
Object output = engine.executable(mustache, vars).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
BytesReference bytes = (BytesReference) output;
assertThat(bytes.toUtf8(), equalTo("foo bar"));
// Sets can come out in any order
Set<String> setData = new HashSet<>();
setData.add("foo");
setData.add("bar");
vars.put("data", setData);
output = engine.executable(mustache, vars).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
bytes = (BytesReference) output;
assertThat(bytes.toUtf8(), both(containsString("foo")).and(containsString("bar")));
}
public void testArrayInArrayAccess() throws Exception {
String template = "{{data.0.0}} {{data.0.1}}";
CompiledScript mustache = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
Map<String, Object> vars = new HashMap<>();
Object data = randomFrom(
new String[][] { new String[] { "foo", "bar" }},
Collections.singletonList(new String[] { "foo", "bar" }),
singleton(new String[] { "foo", "bar" })
);
vars.put("data", data);
Object output = engine.executable(mustache, vars).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
BytesReference bytes = (BytesReference) output;
assertThat(bytes.toUtf8(), equalTo("foo bar"));
}
public void testMapInArrayAccess() throws Exception {
String template = "{{data.0.key}} {{data.1.key}}";
CompiledScript mustache = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
Map<String, Object> vars = new HashMap<>();
Object data = randomFrom(
new Map[] { singletonMap("key", "foo"), singletonMap("key", "bar") },
Arrays.asList(singletonMap("key", "foo"), singletonMap("key", "bar")));
vars.put("data", data);
Object output = engine.executable(mustache, vars).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
BytesReference bytes = (BytesReference) output;
assertThat(bytes.toUtf8(), equalTo("foo bar"));
// HashSet iteration order isn't fixed
Set<Object> setData = new HashSet<>();
setData.add(singletonMap("key", "foo"));
setData.add(singletonMap("key", "bar"));
vars.put("data", setData);
output = engine.executable(mustache, vars).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
bytes = (BytesReference) output;
assertThat(bytes.toUtf8(), both(containsString("foo")).and(containsString("bar")));
}
public void testEscaping() throws Exception {
XContentType contentType = randomFrom(XContentType.values());
if (rarely()) {
contentType = null;
}
Character[] specialChars = new Character[]{'\f', '\n', '\r', '"', '\\', (char) 11, '\t', '\b' };
int iters = scaledRandomIntBetween(100, 1000);
for (int i = 0; i < iters; i++) {
int rounds = scaledRandomIntBetween(1, 20);
StringWriter escaped = new StringWriter(); //This will be escaped as it is constructed
StringWriter unescaped = new StringWriter(); //This will be escaped at the end
for (int j = 0; j < rounds; j++) {
String s = getChars();
unescaped.write(s);
if (contentType == XContentType.JSON) {
escaped.write(JsonStringEncoder.getInstance().quoteAsString(s));
} else {
escaped.write(s);
}
char c = randomFrom(specialChars);
unescaped.append(c);
if (contentType == XContentType.JSON) {
escaped.write(JsonStringEncoder.getInstance().quoteAsString("" + c));
} else {
escaped.append(c);
}
}
if (contentType == XContentType.JSON) {
assertThat(escaped.toString(), equalTo(new String(JsonStringEncoder.getInstance().quoteAsString(unescaped.toString()))));
}
else {
assertThat(escaped.toString(), equalTo(unescaped.toString()));
}
String template = XMustacheScriptEngineService.prepareTemplate("{{data}}", contentType);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("data", unescaped.toString());
CompiledScript mustache = new CompiledScript(ScriptService.ScriptType.INLINE, "inline", "mustache", engine.compile(template, Collections.emptyMap()));
Object output = engine.executable(mustache, dataMap).run();
assertThat(output, notNullValue());
assertThat(output, instanceOf(BytesReference.class));
BytesReference bytes = (BytesReference) output;
String renderedTemplate = bytes.toUtf8();
if (contentType == XContentType.JSON) {
if (!escaped.toString().equals(renderedTemplate)) {
String escapedString = escaped.toString();
for (int l = 0; l < renderedTemplate.length() && l < escapedString.length(); ++l) {
if (renderedTemplate.charAt(l) != escapedString.charAt(l)) {
logger.error("at [{}] expected [{}] but got [{}]", l, renderedTemplate.charAt(l), escapedString.charAt(l));
}
}
}
assertThat(escaped.toString(), equalTo(renderedTemplate));
} else {
assertThat(unescaped.toString(), equalTo(renderedTemplate));
}
}
}
private String getChars() throws IOException {
return randomRealisticUnicodeOfCodepointLengthBetween(0, 10);
}
}

View File

@ -24,6 +24,7 @@ import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.MockMustacheScriptEngine;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.shield.ShieldPlugin;
@ -152,6 +153,7 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase
plugins.remove(MockTransportService.TestPlugin.class); // shield has its own transport service
plugins.remove(AssertingLocalTransport.TestPlugin.class); // shield has its own transport
plugins.add(MockFSIndexStore.TestPlugin.class); // we have to explicitly add it otherwise we will fail to set the check_index_on_close setting
plugins.add(MockMustacheScriptEngine.TestPlugin.class);
return plugins;
}

View File

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.test;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import java.util.Map;
public class MockTextTemplateEngine implements TextTemplateEngine {
@Override
public String render(TextTemplate template, Map<String, Object> model) {
return template.getTemplate();
}
}

View File

@ -56,8 +56,7 @@ import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.secret.Secret;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheScriptEngineService;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheTextTemplateEngine;
import org.elasticsearch.watcher.support.text.DefaultTextTemplateEngine;
import org.elasticsearch.watcher.support.xcontent.ObjectPath;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.transform.search.ExecutableSearchTransform;
@ -175,12 +174,12 @@ public final class WatcherTestUtils {
}
public static Watch createTestWatch(String watchName, ScriptServiceProxy scriptService, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
return createTestWatch(watchName, ClientProxy.of(ESIntegTestCase.client()), scriptService, httpClient, emailService, logger);
public static Watch createTestWatch(String watchName, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
return createTestWatch(watchName, ClientProxy.of(ESIntegTestCase.client()), httpClient, emailService, logger);
}
public static Watch createTestWatch(String watchName, ClientProxy client, ScriptServiceProxy scriptService, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
public static Watch createTestWatch(String watchName, ClientProxy client, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
SearchRequest conditionRequest = newInputSearchRequest("my-condition-index").source(searchSource().query(matchAllQuery()));
SearchRequest transformRequest = newInputSearchRequest("my-payload-index").source(searchSource().query(matchAllQuery()));
@ -197,7 +196,7 @@ public final class WatcherTestUtils {
TextTemplate body = TextTemplate.inline("{{ctx.watch_id}} executed with {{ctx.payload.response.hits.total_hits}} hits").build();
httpRequest.body(body);
TextTemplateEngine engine = new XMustacheTextTemplateEngine(Settings.EMPTY, scriptService);
TextTemplateEngine engine = new MockTextTemplateEngine();
actions.add(new ActionWrapper("_webhook", new ExecutableWebhookAction(new WebhookAction(httpRequest.build()), logger, httpClient, engine)));
@ -209,12 +208,10 @@ public final class WatcherTestUtils {
.to(to)
.build();
TextTemplateEngine templateEngine = new XMustacheTextTemplateEngine(Settings.EMPTY, scriptService);
Authentication auth = new Authentication("testname", new Secret("testpassword".toCharArray()));
EmailAction action = new EmailAction(email, "testaccount", auth, Profile.STANDARD, null, null);
ExecutableEmailAction executale = new ExecutableEmailAction(action, logger, emailService, templateEngine, new HtmlSanitizer(Settings.EMPTY), Collections.emptyMap());
ExecutableEmailAction executale = new ExecutableEmailAction(action, logger, emailService, engine, new HtmlSanitizer(Settings.EMPTY), Collections.emptyMap());
actions.add(new ActionWrapper("_email", executale));
@ -246,15 +243,12 @@ public final class WatcherTestUtils {
.put("script.indexed", "true")
.put("path.home", createTempDir())
.build();
XMustacheScriptEngineService mustacheScriptEngineService = new XMustacheScriptEngineService(settings);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Arrays.asList(ScriptServiceProxy.INSTANCE));
ScriptEngineRegistry scriptEngineRegistry =
new ScriptEngineRegistry(Collections.singletonList(new ScriptEngineRegistry.ScriptEngineRegistration(XMustacheScriptEngineService.class, XMustacheScriptEngineService.TYPES)));
new ScriptEngineRegistry(Collections.emptyList());
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
return ScriptServiceProxy.of(new ScriptService(settings, new Environment(settings), engineServiceSet, new ResourceWatcherService(settings, tp), scriptEngineRegistry, scriptContextRegistry, scriptSettings));
return ScriptServiceProxy.of(new ScriptService(settings, new Environment(settings), Collections.emptySet(), new ResourceWatcherService(settings, tp), scriptEngineRegistry, scriptContextRegistry, scriptSettings));
}
public static SearchType getRandomSupportedSearchType() {

View File

@ -3,7 +3,7 @@
* 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.messy.tests;
package org.elasticsearch.watcher.test.integration;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.search.SearchRequest;
@ -13,10 +13,8 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.Callback;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.script.Template;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.watcher.client.WatchSourceBuilder;
import org.elasticsearch.watcher.client.WatcherClient;
@ -34,14 +32,9 @@ import org.elasticsearch.watcher.trigger.schedule.support.MonthTimes;
import org.elasticsearch.watcher.trigger.schedule.support.WeekTimes;
import org.elasticsearch.watcher.watch.WatchStore;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
@ -56,6 +49,7 @@ import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.test.WatcherTestUtils.newInputSearchRequest;
import static org.elasticsearch.watcher.test.WatcherTestUtils.xContentSource;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.daily;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.hourly;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.interval;
@ -70,13 +64,10 @@ import static org.hamcrest.Matchers.notNullValue;
public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = new ArrayList<>();
types.addAll(super.pluginTypes());
types.add(MustachePlugin.class);
return types;
protected boolean timeWarped() {
return true;
}
public void testIndexWatch() throws Exception {
WatcherClient watcherClient = watcherClient();
createIndex("idx");
@ -89,17 +80,10 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.trigger(schedule(interval(5, IntervalSchedule.Interval.Unit.SECONDS)))
.input(searchInput(searchRequest))
.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 1L))
.addAction("_logger", loggingAction("\n\n************\n" +
"total hits: {{ctx.payload.hits.total}}\n" +
"************\n")
.addAction("_logger", loggingAction("_logging")
.setCategory("_category")))
.get();
if (timeWarped()) {
timeWarp().scheduler().trigger("_name");
refresh();
}
timeWarp().scheduler().trigger("_name");
assertWatchWithMinimumPerformedActionsCount("_name", 1);
GetWatchResponse getWatchResponse = watcherClient().prepareGetWatch().setId("_name").get();
@ -116,12 +100,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.input(searchInput(searchRequest))
.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 1L)))
.get();
if (timeWarped()) {
timeWarp().scheduler().trigger("_name");
refresh();
}
timeWarp().scheduler().trigger("_name");
// The watch's condition won't meet because there is no data that matches with the query
assertWatchWithNoActionNeeded("_name", 1);
@ -143,20 +122,11 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
SearchRequest searchRequest = newInputSearchRequest("idx").source(searchSource().query(matchAllQuery()));
PutWatchResponse indexResponse = watcherClient.preparePutWatch("_name")
.setSource(watchBuilder()
.trigger(schedule(interval(5, IntervalSchedule.Interval.Unit.SECONDS)))
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(searchInput(searchRequest))
.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 1L)))
.get();
assertThat(indexResponse.isCreated(), is(true));
if (!timeWarped()) {
// Although there is no added benefit in this test for waiting for the watch to fire, however
// we need to wait here because of a test timing issue. When we tear down a test we delete the watch and delete all
// indices, but there may still be inflight fired watches, which may trigger the watch history to be created again, before
// we finished the tear down phase.
assertWatchWithNoActionNeeded("_name", 1);
}
DeleteWatchResponse deleteWatchResponse = watcherClient.prepareDeleteWatch("_name").get();
assertThat(deleteWatchResponse, notNullValue());
assertThat(deleteWatchResponse.isFound(), is(true));
@ -217,22 +187,17 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.setSource(source.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 1L)))
.get();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
refresh();
}
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
assertWatchWithMinimumPerformedActionsCount("_name", 0, false);
watcherClient().preparePutWatch("_name")
.setSource(source.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 0L)))
.get();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
refresh();
}
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
refresh();
assertWatchWithMinimumPerformedActionsCount("_name", 1, false);
watcherClient().preparePutWatch("_name")
@ -241,24 +206,12 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.condition(compareCondition("ctx.payload.hits.total", CompareCondition.Op.EQ, 0L)))
.get();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
refresh();
} else {
Thread.sleep(1000);
}
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
long count = findNumberOfPerformedActions("_name");
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
refresh();
} else {
Thread.sleep(1000);
}
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger("_name");
assertThat(count, equalTo(findNumberOfPerformedActions("_name")));
}
@ -272,7 +225,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.trigger(schedule(interval("1s")))
.input(simpleInput("key", "value"))
.defaultThrottlePeriod(TimeValue.timeValueSeconds(0))
.addAction("_id", loggingAction("hello {{ctx.watcher_id}}!"));
.addAction("_id", loggingAction("_logging"));
watcherClient().preparePutWatch("_name")
.setSource(source)
.get();
@ -284,7 +237,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.trigger(schedule(interval("100s")))
.defaultThrottlePeriod(TimeValue.timeValueSeconds(0))
.input(simpleInput("key", "value"))
.addAction("_id", loggingAction("hello {{ctx.watcher_id}}!"));
.addAction("_id", loggingAction("_logging"));
watcherClient().preparePutWatch("_name")
.setSource(source)
.get();
@ -297,24 +250,12 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
}
public void testConditionSearchWithSource() throws Exception {
String variable = randomFrom("ctx.execution_time", "ctx.trigger.scheduled_time", "ctx.trigger.triggered_time");
SearchSourceBuilder searchSourceBuilder = searchSource().query(boolQuery()
.must(matchQuery("level", "a"))
.must(rangeQuery("_timestamp")
.from("{{" + variable + "}}||-30s")
.to("{{" + variable + "}}")));
SearchSourceBuilder searchSourceBuilder = searchSource().query(matchQuery("level", "a"));
testConditionSearch(newInputSearchRequest("events").source(searchSourceBuilder));
}
public void testConditionSearchWithIndexedTemplate() throws Exception {
String variable = randomFrom("ctx.execution_time", "ctx.trigger.scheduled_time", "ctx.trigger.triggered_time");
SearchSourceBuilder searchSourceBuilder = searchSource().query(boolQuery()
.must(matchQuery("level", "a"))
.must(rangeQuery("_timestamp")
.from("{{" + variable + "}}||-30s")
.to("{{" + variable + "}}")));
SearchSourceBuilder searchSourceBuilder = searchSource().query(matchQuery("level", "a"));
client().preparePutIndexedScript()
.setScriptLang("mustache")
.setId("my-template")
@ -350,14 +291,8 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.condition(compareCondition("ctx.payload.hits.max_score", CompareCondition.Op.GTE, 0L)))
.get();
if (timeWarped()) {
timeWarp().scheduler().trigger("_name1");
timeWarp().scheduler().trigger("_name2");
refresh();
} else {
Thread.sleep(5000);
}
timeWarp().scheduler().trigger("_name1");
timeWarp().scheduler().trigger("_name2");
assertWatchWithMinimumPerformedActionsCount("_name1", 1);
assertWatchWithNoActionNeeded("_name2", 1);
@ -442,10 +377,8 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
}
private void testConditionSearch(SearchRequest request) throws Exception {
if (timeWarped()) {
// reset, so we don't miss event docs when we filter over the _timestamp field.
timeWarp().clock().setTime(SystemClock.INSTANCE.nowUTC());
}
// reset, so we don't miss event docs when we filter over the _timestamp field.
timeWarp().clock().setTime(SystemClock.INSTANCE.nowUTC());
String watchName = "_name";
assertAcked(prepareCreate("events").addMapping("event", "_timestamp", "enabled=true", "level", "type=string"));
@ -469,14 +402,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.get();
refresh();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger(watchName);
refresh();
} else {
Thread.sleep(5000);
}
timeWarp().scheduler().trigger(watchName);
assertWatchWithNoActionNeeded(watchName, 1);
client().prepareIndex("events", "event")
@ -484,13 +410,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.setSource("level", "b")
.get();
refresh();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger(watchName);
refresh();
} else {
Thread.sleep(5000);
}
timeWarp().scheduler().trigger(watchName);
assertWatchWithNoActionNeeded(watchName, 2);
client().prepareIndex("events", "event")
@ -498,13 +418,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase {
.setSource("level", "a")
.get();
refresh();
if (timeWarped()) {
timeWarp().clock().fastForwardSeconds(5);
timeWarp().scheduler().trigger(watchName);
refresh();
} else {
Thread.sleep(5000);
}
timeWarp().scheduler().trigger(watchName);
assertWatchWithMinimumPerformedActionsCount(watchName, 1);
}
}

View File

@ -81,7 +81,7 @@ public class WatchMetadataTests extends AbstractWatcherIntegrationTestCase {
metadata.put("foo", "bar");
metadata.put("logtext", "This is a test");
LoggingAction.Builder loggingAction = loggingAction(TextTemplate.inline("{{ctx.metadata.logtext}}"))
LoggingAction.Builder loggingAction = loggingAction(TextTemplate.inline("_logging"))
.setLevel(LoggingLevel.DEBUG)
.setCategory("test");
@ -102,6 +102,6 @@ public class WatchMetadataTests extends AbstractWatcherIntegrationTestCase {
assertThat(ObjectPath.<String>eval("metadata.foo", result), equalTo("bar"));
assertThat(ObjectPath.<String>eval("result.actions.0.id", result), equalTo("testLogger"));
assertThat(ObjectPath.<String>eval("result.actions.0.logging.logged_text", result), equalTo("This is a test"));
assertThat(ObjectPath.<String>eval("result.actions.0.logging.logged_text", result), equalTo("_logging"));
}
}