Merge branch 'master' into upgrade_to_es_20

Conflicts:
	src/main/java/org/elasticsearch/watcher/support/http/HttpClient.java
	src/main/java/org/elasticsearch/watcher/support/http/HttpRequestTemplate.java
	src/main/java/org/elasticsearch/watcher/transport/actions/execute/ExecuteWatchRequestBuilder.java
	src/main/java/org/elasticsearch/watcher/transport/actions/execute/TransportExecuteWatchAction.java
	src/test/java/org/elasticsearch/watcher/actions/email/EmailActionTests.java
	src/test/java/org/elasticsearch/watcher/actions/email/service/HtmlSanitizeTests.java
	src/test/java/org/elasticsearch/watcher/actions/webhook/WebhookHttpsIntegrationTests.java
	src/test/java/org/elasticsearch/watcher/execution/ManualExecutionTests.java
	src/test/java/org/elasticsearch/watcher/support/http/HttpClientTests.java
	src/test/java/org/elasticsearch/watcher/support/http/HttpRequestTemplateTests.java

Original commit: elastic/x-pack-elasticsearch@61885ed389
This commit is contained in:
Simon Willnauer 2015-06-16 17:48:06 +02:00
commit 10c5ac6353
87 changed files with 1754 additions and 412 deletions

View File

@ -4,12 +4,11 @@
"methods": [ "PUT", "POST" ],
"url": {
"path": "/_watcher/watch/{id}/_execute",
"paths": [ "/_watcher/watch/{id}/_execute" ],
"paths": [ "/_watcher/watch/{id}/_execute", "/_watcher/watch/_execute" ],
"parts": {
"id": {
"type" : "string",
"description" : "Watch ID",
"required" : true
"description" : "Watch ID"
}
},
"params": {

View File

@ -77,8 +77,12 @@
- match: { "watch_record.trigger_event.type": "manual" }
- match: { "watch_record.trigger_event.triggered_time": "2015-05-05T20:58:02.443Z" }
- match: { "watch_record.trigger_event.manual.schedule.scheduled_time": "2015-05-05T20:58:02.443Z" }
- match: { "watch_record.result.condition.met": true }
- match: { "watch_record.result.input.type": "simple" }
- match: { "watch_record.result.input.status": "success" }
- match: { "watch_record.result.input.payload.foo": "bar" }
- match: { "watch_record.result.condition.type": "always" }
- match: { "watch_record.result.condition.status": "success" }
- match: { "watch_record.result.condition.met": true }
- match: { "watch_record.result.actions.0.id" : "email_admin" }
- match: { "watch_record.result.actions.0.status" : "simulated" }
- match: { "watch_record.result.actions.0.type" : "email" }

View File

@ -36,6 +36,11 @@
- match: { "watch_record.watch_id": "my_logging_watch" }
- match: { "watch_record.state": "executed" }
- match: { "watch_record.result.input.type": "simple" }
- match: { "watch_record.result.input.status": "success" }
- match: { "watch_record.result.input.payload.count": 1 }
- match: { "watch_record.result.condition.type": "script" }
- match: { "watch_record.result.condition.status": "success" }
- match: { "watch_record.result.condition.met": true }
- match: { "watch_record.result.actions.0.id" : "logging" }
- match: { "watch_record.result.actions.0.type" : "logging" }

View File

@ -0,0 +1,74 @@
---
"Test execute watch api with an inline watch":
- do:
cluster.health:
wait_for_status: green
- do:
watcher.execute_watch:
body: >
{
"trigger_data" : {
"scheduled_time" : "2015-05-05T20:58:02.443Z",
"triggered_time" : "2015-05-05T20:58:02.443Z"
},
"alternative_input" : {
"foo" : "bar"
},
"ignore_condition" : true,
"action_modes" : {
"_all" : "force_simulate"
},
"watch" : {
"trigger" : {
"schedule" : { "cron" : "0 0 0 1 * ? 2099" }
},
"input" : {
"search" : {
"request" : {
"indices" : [ "logstash*" ],
"body" : {
"query" : {
"filtered": {
"query": {
"match": {
"response": 404
}
},
"filter": {
"range": {
"@timestamp" : {
"from": "{{ctx.trigger.scheduled_time}}||-5m",
"to": "{{ctx.trigger.triggered_time}}"
}
}
}
}
}
}
}
}
},
"condition" : {
"script" : {
"inline" : "ctx.payload.hits.total > 1"
}
},
"actions" : {
"email_admin" : {
"email" : {
"to" : "someone@domain.host.com",
"subject" : "404 recently encountered"
}
}
}
}
}
- match: { "watch_record.state": "executed" }
- match: { "watch_record.trigger_event.manual.schedule.scheduled_time": "2015-05-05T20:58:02.443Z" }
- match: { "watch_record.result.input.type": "simple" }
- match: { "watch_record.result.input.payload.foo": "bar" }
- match: { "watch_record.result.condition.met": true }
- match: { "watch_record.result.actions.0.id" : "email_admin" }
- match: { "watch_record.result.actions.0.status" : "simulated" }
- match: { "watch_record.result.actions.0.email.email.subject" : "404 recently encountered" }

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.watcher.actions.email.EmailAction;
import org.elasticsearch.watcher.actions.email.EmailActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer;
import org.elasticsearch.watcher.actions.email.service.InternalEmailService;
import org.elasticsearch.watcher.actions.index.IndexAction;
import org.elasticsearch.watcher.actions.index.IndexActionFactory;
@ -54,6 +55,7 @@ public class ActionModule extends AbstractModule {
}
bind(ActionRegistry.class).asEagerSingleton();
bind(HtmlSanitizer.class).asEagerSingleton();
bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton();
}

View File

@ -78,9 +78,13 @@ public class ActionWrapper implements ToXContent {
if (transform != null) {
try {
transformResult = transform.execute(ctx, payload);
if (transformResult.status() == Transform.Result.Status.FAILURE) {
action.logger().error("failed to execute action [{}/{}]. failed to transform payload. {}", ctx.watch().id(), id, transformResult.reason());
return new ActionWrapper.Result(id, transformResult, new Action.Result.Failure(action.type(), "Failed to transform payload"));
}
payload = transformResult.payload();
} catch (Exception e) {
action.logger.error("failed to execute action [{}/{}]. failed to transform payload.", e, ctx.watch().id(), id);
action.logger().error("failed to execute action [{}/{}]. failed to transform payload.", e, ctx.watch().id(), id);
return new ActionWrapper.Result(id, new Action.Result.Failure(action.type(), "Failed to transform payload. error: " + ExceptionsHelper.detailedMessage(e)));
}
}
@ -88,7 +92,7 @@ public class ActionWrapper implements ToXContent {
Action.Result actionResult = action.execute(id, ctx, payload);
return new ActionWrapper.Result(id, transformResult, actionResult);
} catch (Exception e) {
action.logger.error("failed to execute action [{}/{}]", e, ctx.watch().id(), id);
action.logger().error("failed to execute action [{}/{}]", e, ctx.watch().id(), id);
return new ActionWrapper.Result(id, new Action.Result.Failure(action.type(), ExceptionsHelper.detailedMessage(e)));
}
}

View File

@ -36,6 +36,13 @@ public abstract class ExecutableAction<A extends Action> implements ToXContent {
return action;
}
/**
* yack... needed to expose that for testing purposes
*/
public ESLogger logger() {
return logger;
}
public abstract Action.Result execute(String actionId, WatchExecutionContext context, Payload payload) throws Exception;
@Override

View File

@ -113,8 +113,8 @@ public class EmailAction implements Action {
return builder.endObject();
}
public static EmailAction parse(String watchId, String actionId, XContentParser parser, boolean sanitizeHtmlBody) throws IOException {
EmailTemplate.Parser emailParser = new EmailTemplate.Parser(sanitizeHtmlBody);
public static EmailAction parse(String watchId, String actionId, XContentParser parser) throws IOException {
EmailTemplate.Parser emailParser = new EmailTemplate.Parser();
String account = null;
String user = null;
Secret password = null;

View File

@ -9,10 +9,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import java.io.IOException;
@ -24,16 +23,14 @@ public class EmailActionFactory extends ActionFactory<EmailAction, ExecutableEma
private final EmailService emailService;
private final TemplateEngine templateEngine;
private final boolean sanitizeHtmlBodyOfEmails;
private static String SANITIZE_HTML_SETTING = "watcher.actions.email.sanitize_html";
private final HtmlSanitizer htmlSanitizer;
@Inject
public EmailActionFactory(Settings settings, EmailService emailService, TemplateEngine templateEngine) {
public EmailActionFactory(Settings settings, EmailService emailService, TemplateEngine templateEngine, HtmlSanitizer htmlSanitizer) {
super(Loggers.getLogger(ExecutableEmailAction.class, settings));
this.emailService = emailService;
this.templateEngine = templateEngine;
sanitizeHtmlBodyOfEmails = settings.getAsBoolean(SANITIZE_HTML_SETTING, true);
this.htmlSanitizer = htmlSanitizer;
}
@Override
@ -43,11 +40,11 @@ public class EmailActionFactory extends ActionFactory<EmailAction, ExecutableEma
@Override
public EmailAction parseAction(String watchId, String actionId, XContentParser parser) throws IOException {
return EmailAction.parse(watchId, actionId, parser, sanitizeHtmlBodyOfEmails);
return EmailAction.parse(watchId, actionId, parser);
}
@Override
public ExecutableEmailAction createExecutable(EmailAction action) {
return new ExecutableEmailAction(action, actionLogger, emailService, templateEngine);
return new ExecutableEmailAction(action, actionLogger, emailService, templateEngine, htmlSanitizer);
}
}

View File

@ -11,6 +11,7 @@ import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.actions.email.service.Attachment;
import org.elasticsearch.watcher.actions.email.service.Email;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.template.TemplateEngine;
@ -25,11 +26,13 @@ public class ExecutableEmailAction extends ExecutableAction<EmailAction> {
final EmailService emailService;
final TemplateEngine templateEngine;
final HtmlSanitizer htmlSanitizer;
public ExecutableEmailAction(EmailAction action, ESLogger logger, EmailService emailService, TemplateEngine templateEngine) {
public ExecutableEmailAction(EmailAction action, ESLogger logger, EmailService emailService, TemplateEngine templateEngine, HtmlSanitizer htmlSanitizer) {
super(action, logger);
this.emailService = emailService;
this.templateEngine = templateEngine;
this.htmlSanitizer = htmlSanitizer;
}
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
@ -42,7 +45,7 @@ public class ExecutableEmailAction extends ExecutableAction<EmailAction> {
attachments.put(attachment.id(), attachment);
}
Email.Builder email = action.getEmail().render(templateEngine, model, attachments);
Email.Builder email = action.getEmail().render(templateEngine, model, htmlSanitizer, attachments);
email.id(ctx.id().value());
if (ctx.simulateAction(actionId)) {

View File

@ -11,9 +11,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.owasp.html.*;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.mail.internet.AddressException;
import java.io.IOException;
import java.util.*;
@ -32,11 +30,10 @@ public class EmailTemplate implements ToXContent {
final Template subject;
final Template textBody;
final Template htmlBody;
final boolean sanitizeHtmlBody;
public EmailTemplate(Template from, Template[] replyTo, Template priority, Template[] to,
Template[] cc, Template[] bcc, Template subject, Template textBody,
Template htmlBody, boolean sanitizeHtmlBody) {
Template htmlBody) {
this.from = from;
this.replyTo = replyTo;
this.priority = priority;
@ -46,7 +43,6 @@ public class EmailTemplate implements ToXContent {
this.subject = subject;
this.textBody = textBody;
this.htmlBody = htmlBody;
this.sanitizeHtmlBody = sanitizeHtmlBody;
}
public Template from() {
@ -85,11 +81,7 @@ public class EmailTemplate implements ToXContent {
return htmlBody;
}
public boolean sanitizeHtmlBody() {
return sanitizeHtmlBody;
}
public Email.Builder render(TemplateEngine engine, Map<String, Object> model, Map<String, Attachment> attachments) throws AddressException {
public Email.Builder render(TemplateEngine engine, Map<String, Object> model, HtmlSanitizer htmlSanitizer, Map<String, Attachment> attachments) throws AddressException {
Email.Builder builder = Email.builder();
if (from != null) {
builder.from(engine.render(from, model));
@ -126,9 +118,7 @@ public class EmailTemplate implements ToXContent {
}
if (htmlBody != null) {
String renderedHtml = engine.render(htmlBody, model);
if (sanitizeHtmlBody && htmlBody != null) {
renderedHtml = sanitizeHtml(renderedHtml, attachments);
}
renderedHtml = htmlSanitizer.sanitize(renderedHtml);
builder.htmlBody(renderedHtml);
}
return builder;
@ -236,7 +226,6 @@ public class EmailTemplate implements ToXContent {
private Template subject;
private Template textBody;
private Template htmlBody;
private boolean sanitizeHtmlBody = true;
private Builder() {
}
@ -377,81 +366,27 @@ public class EmailTemplate implements ToXContent {
return this;
}
public Builder htmlBody(String html, boolean sanitizeHtmlBody) {
return htmlBody(Template.defaultType(html), sanitizeHtmlBody);
public Builder htmlBody(String html) {
return htmlBody(Template.defaultType(html));
}
public Builder htmlBody(Template.Builder html, boolean sanitizeHtmlBody) {
return htmlBody(html.build(), sanitizeHtmlBody);
public Builder htmlBody(Template.Builder html) {
return htmlBody(html.build());
}
public Builder htmlBody(Template html, boolean sanitizeHtmlBody) {
public Builder htmlBody(Template html) {
this.htmlBody = html;
this.sanitizeHtmlBody = sanitizeHtmlBody;
return this;
}
public EmailTemplate build() {
return new EmailTemplate(from, replyTo, priority, to, cc, bcc, subject, textBody, htmlBody, sanitizeHtmlBody);
}
}
static String sanitizeHtml(String html, final Map<String, Attachment> attachments){
ElementPolicy onlyCIDImgPolicy = new AttachementVerifyElementPolicy(attachments);
PolicyFactory policy = Sanitizers.FORMATTING
.and(new HtmlPolicyBuilder()
.allowElements("img", "table", "tr", "td", "style", "body", "head", "hr")
.allowAttributes("src").onElements("img")
.allowAttributes("class").onElements("style")
.allowUrlProtocols("cid")
.allowCommonInlineFormattingElements()
.allowElements(onlyCIDImgPolicy, "img")
.allowStyling(CssSchema.DEFAULT)
.toFactory())
.and(Sanitizers.LINKS)
.and(Sanitizers.BLOCKS);
return policy.sanitize(html);
}
private static class AttachementVerifyElementPolicy implements ElementPolicy {
private final Map<String, Attachment> attachments;
AttachementVerifyElementPolicy(Map<String, Attachment> attachments) {
this.attachments = attachments;
}
@Override
public String apply(@ParametersAreNonnullByDefault String elementName, @ParametersAreNonnullByDefault List<String> attrs) {
if (attrs.size() == 0) {
return elementName;
}
for (int i = 0; i < attrs.size(); ++i) {
if(attrs.get(i).equals("src") && i < attrs.size() - 1) {
String srcValue = attrs.get(i+1);
if (!srcValue.startsWith("cid:")) {
return null; //Disallow anything other than content ids
}
String contentId = srcValue.substring(4);
if (attachments.containsKey(contentId)) {
return elementName;
} else {
return null; //This cid wasn't found
}
}
}
return elementName;
return new EmailTemplate(from, replyTo, priority, to, cc, bcc, subject, textBody, htmlBody);
}
}
public static class Parser {
private final EmailTemplate.Builder builder = builder();
private final boolean sanitizeHtmlBody;
public Parser(boolean sanitizeHtmlBody) {
this.sanitizeHtmlBody = sanitizeHtmlBody;
}
public boolean handle(String fieldName, XContentParser parser) throws IOException {
if (Email.Field.FROM.match(fieldName)) {
@ -514,7 +449,7 @@ public class EmailTemplate implements ToXContent {
} else if (Email.Field.BODY_TEXT.match(currentFieldName)) {
builder.textBody(Template.parse(parser));
} else if (Email.Field.BODY_HTML.match(currentFieldName)) {
builder.htmlBody(Template.parse(parser), sanitizeHtmlBody);
builder.htmlBody(Template.parse(parser));
} else {
throw new ParseException("could not parse email template. unknown field [{}.{}] field", fieldName, currentFieldName);
}

View File

@ -0,0 +1,189 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.email.service;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.owasp.html.CssSchema;
import org.owasp.html.ElementPolicy;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
/**
*
*/
public class HtmlSanitizer {
static final String[] FORMATTING_TAGS = new String[] {
"b", "i", "s", "u", "o", "sup", "sub", "ins", "del", "strong",
"strike", "tt", "code", "big", "small", "br", "span", "em"
};
static final String[] BLOCK_TAGS = new String[] {
"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "blockquote"
};
static final String[] TABLE_TAGS = new String[] {
"table", "hr", "tr", "td"
};
static final String[] DEFAULT_ALLOWED = new String[] {
"body", "head", "_tables", "_links", "_blocks", "_formatting", "img:embedded"
};
private final boolean enabled;
private final PolicyFactory policy;
@Inject
public HtmlSanitizer(Settings settings) {
enabled = settings.getAsBoolean("watcher.actions.email.html.sanitization.enabled", true);
String[] allow = settings.getAsArray("watcher.actions.email.html.sanitization.allow", DEFAULT_ALLOWED);
String[] disallow = settings.getAsArray("watcher.actions.email.html.sanitization.disallow", Strings.EMPTY_ARRAY);
policy = createCommonPolicy(allow, disallow);
}
public String sanitize(String html) {
if (!enabled) {
return html;
}
return policy.sanitize(html);
}
static PolicyFactory createCommonPolicy(String[] allow, String[] disallow) {
HtmlPolicyBuilder policyBuilder = new HtmlPolicyBuilder();
if (Arrays.binarySearch(allow, "_all") > -1) {
return policyBuilder
.allowElements(TABLE_TAGS)
.allowElements(BLOCK_TAGS)
.allowElements(FORMATTING_TAGS)
.allowStyling(CssSchema.DEFAULT)
.allowStandardUrlProtocols().allowElements("a")
.allowAttributes("href").onElements("a").requireRelNofollowOnLinks()
.allowElements("img")
.allowAttributes("src").onElements("img")
.allowStandardUrlProtocols()
.allowUrlProtocols("cid")
.toFactory();
}
EnumSet<Images> images = EnumSet.noneOf(Images.class);
for (String tag : allow) {
tag = tag.toLowerCase(Locale.ROOT);
switch (tag) {
case "_tables":
policyBuilder.allowElements(TABLE_TAGS);
break;
case "_links":
policyBuilder.allowElements("a")
.allowAttributes("href").onElements("a")
.allowStandardUrlProtocols()
.requireRelNofollowOnLinks();
break;
case "_blocks":
policyBuilder.allowElements(BLOCK_TAGS);
break;
case "_formatting":
policyBuilder.allowElements(FORMATTING_TAGS);
break;
case "_styles":
policyBuilder.allowStyling(CssSchema.DEFAULT);
break;
case "img:all":
case "img":
images.add(Images.ALL);
break;
case "img:embedded":
images.add(Images.EMBEDDED);
break;
default:
policyBuilder.allowElements(tag);
}
}
for (String tag : disallow) {
tag = tag.toLowerCase(Locale.ROOT);
switch (tag) {
case "_tables":
policyBuilder.disallowElements(TABLE_TAGS);
break;
case "_links":
policyBuilder.disallowElements("a");
break;
case "_blocks":
policyBuilder.disallowElements(BLOCK_TAGS);
break;
case "_formatting":
policyBuilder.disallowElements(FORMATTING_TAGS);
break;
case "_styles":
policyBuilder.disallowAttributes("style");
break;
case "img:all":
case "img":
images.remove(Images.ALL);
break;
case "img:embedded":
images.remove(Images.EMBEDDED);
break;
default:
policyBuilder.disallowElements(tag);
}
}
if (!images.isEmpty()) {
policyBuilder.allowAttributes("src").onElements("img").allowUrlProtocols("cid");
if (images.contains(Images.ALL)) {
policyBuilder.allowElements("img");
policyBuilder.allowStandardUrlProtocols();
} else {
// embedded
policyBuilder.allowElements(EmbeddedImgOnlyPolicy.INSTANCE, "img");
}
}
return policyBuilder.toFactory();
}
/**
* An {@code img} tag policy that only accept {@code cid:} values in its {@code src} attribute.
* If such value is found, the content id is verified against the available attachements of the
* email and if the content/attachment is not found, the element is dropped.
*/
private static class EmbeddedImgOnlyPolicy implements ElementPolicy {
private static EmbeddedImgOnlyPolicy INSTANCE = new EmbeddedImgOnlyPolicy();
@Override
public String apply(String elementName, List<String> attrs) {
if (!"img".equals(elementName) || attrs.size() == 0) {
return elementName;
}
String attrName = null;
for (String attr : attrs) {
if (attrName == null) {
attrName = attr.toLowerCase(Locale.ROOT);
continue;
}
// reject external image source (only allow embedded ones)
if ("src".equals(attrName) && !attr.startsWith("cid:")) {
return null;
}
}
return elementName;
}
}
enum Images {
ALL,
EMBEDDED
}
}

View File

@ -5,11 +5,13 @@
*/
package org.elasticsearch.watcher.condition;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Locale;
/**
*
@ -20,25 +22,64 @@ public interface Condition extends ToXContent {
abstract class Result implements ToXContent {
public enum Status {
SUCCESS, FAILURE
}
protected final String type;
protected final Status status;
private final String reason;
protected final boolean met;
public Result(String type, boolean met) {
this.status = Status.SUCCESS;
this.type = type;
this.met = met;
this.reason = null;
}
protected Result(String type, Exception e) {
this.status = Status.FAILURE;
this.type = type;
this.met = false;
this.reason = ExceptionsHelper.detailedMessage(e);
}
public String type() {
return type;
}
public boolean met() { return met; }
public Status status() {
return status;
}
public boolean met() {
assert status == Status.SUCCESS;
return met;
}
public String reason() {
assert status == Status.FAILURE;
return reason;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.TYPE.getPreferredName(), type);
builder.field(Field.MET.getPreferredName(), met);
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
switch (status) {
case SUCCESS:
assert reason == null;
builder.field(Field.MET.getPreferredName(), met);
break;
case FAILURE:
assert reason != null && !met;
builder.field(Field.REASON.getPreferredName(), reason);
break;
default:
assert false;
}
typeXContent(builder, params);
return builder.endObject();
}
@ -53,6 +94,8 @@ public interface Condition extends ToXContent {
interface Field {
ParseField TYPE = new ParseField("type");
ParseField STATUS = new ParseField("status");
ParseField MET = new ParseField("met");
ParseField REASON = new ParseField("reason");
}
}

View File

@ -39,7 +39,7 @@ public abstract class ExecutableCondition<C extends Condition, R extends Conditi
/**
* Executes this condition
*/
public abstract R execute(WatchExecutionContext ctx) throws IOException;
public abstract R execute(WatchExecutionContext ctx);
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {

View File

@ -21,7 +21,7 @@ public class ExecutableAlwaysCondition extends ExecutableCondition<AlwaysConditi
}
@Override
public AlwaysCondition.Result execute(WatchExecutionContext ctx) throws IOException {
public AlwaysCondition.Result execute(WatchExecutionContext ctx) {
return AlwaysCondition.Result.INSTANCE;
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.watcher.condition.compare;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.joda.time.DateTime;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -125,19 +126,27 @@ public class CompareCondition implements Condition {
public static class Result extends Condition.Result {
private final Map<String, Object> resolveValues;
private final @Nullable Map<String, Object> resolveValues;
Result(Map<String, Object> resolveValues, boolean met) {
super(TYPE, met);
this.resolveValues = resolveValues;
}
Result(@Nullable Map<String, Object> resolveValues, Exception e) {
super(TYPE, e);
this.resolveValues = resolveValues;
}
public Map<String, Object> getResolveValues() {
return resolveValues;
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
if (resolveValues == null) {
return builder;
}
return builder.startObject(type)
.field(Field.RESOLVED_VALUES.getPreferredName(), resolveValues)
.endObject();

View File

@ -7,6 +7,7 @@ package org.elasticsearch.watcher.condition.compare;
import org.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.email.DataAttachment;
import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
@ -39,10 +40,21 @@ public class ExecutableCompareCondition extends ExecutableCondition<CompareCondi
}
@Override
public CompareCondition.Result execute(WatchExecutionContext ctx) throws IOException {
Map<String, Object> model = Variables.createCtxModel(ctx, ctx.payload());
public CompareCondition.Result execute(WatchExecutionContext ctx) {
Map<String, Object> resolvedValues = new HashMap<>();
try {
return doExecute(ctx, resolvedValues);
} catch (Exception e) {
logger.error("failed to execute [{}] condition for [{}]", CompareCondition.TYPE, ctx.id());
if (resolvedValues.isEmpty()) {
resolvedValues = null;
}
return new CompareCondition.Result(resolvedValues, e);
}
}
public CompareCondition.Result doExecute(WatchExecutionContext ctx, Map<String, Object> resolvedValues) throws Exception {
Map<String, Object> model = Variables.createCtxModel(ctx, ctx.payload());
Object configuredValue = condition.getValue();

View File

@ -21,7 +21,7 @@ public class ExecutableNeverCondition extends ExecutableCondition<NeverCondition
}
@Override
public NeverCondition.Result execute(WatchExecutionContext ctx) throws IOException {
public NeverCondition.Result execute(WatchExecutionContext ctx) {
return NeverCondition.Result.INSTANCE;
}

View File

@ -13,7 +13,6 @@ import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import java.io.IOException;
import java.util.Map;
/**
@ -35,20 +34,25 @@ public class ExecutableScriptCondition extends ExecutableCondition<ScriptConditi
}
@Override
public ScriptCondition.Result execute(WatchExecutionContext ctx) throws IOException {
public ScriptCondition.Result execute(WatchExecutionContext ctx) {
try {
Map<String, Object> parameters = Variables.createCtxModel(ctx, ctx.payload());
if (condition.script.params() != null && !condition.script.params().isEmpty()) {
parameters.putAll(condition.script.params());
}
ExecutableScript executable = scriptService.executable(compiledScript, parameters);
Object value = executable.run();
if (value instanceof Boolean) {
return (Boolean) value ? ScriptCondition.Result.MET : ScriptCondition.Result.UNMET;
}
throw new ScriptConditionException("failed to execute [{}] condition for watch [{}]. script [{}] must return a boolean value (true|false) but instead returned [{}]", type(), ctx.watch().id(), condition.script.script(), value);
return doExecute(ctx);
} catch (Exception e) {
throw new ScriptConditionException("failed to execute [{}] condition for watch [{}]. script [{}] threw an exception", e, type(), ctx.watch().id(), condition.script.script());
logger.error("failed to execute [{}] condition for [{}]", ScriptCondition.TYPE, ctx.id());
return new ScriptCondition.Result(e);
}
}
public ScriptCondition.Result doExecute(WatchExecutionContext ctx) throws Exception {
Map<String, Object> parameters = Variables.createCtxModel(ctx, ctx.payload());
if (condition.script.params() != null && !condition.script.params().isEmpty()) {
parameters.putAll(condition.script.params());
}
ExecutableScript executable = scriptService.executable(compiledScript, parameters);
Object value = executable.run();
if (value instanceof Boolean) {
return (Boolean) value ? ScriptCondition.Result.MET : ScriptCondition.Result.UNMET;
}
throw new ScriptConditionException("script [{}] must return a boolean value (true|false) but instead returned [{}]", type(), ctx.watch().id(), condition.script.script(), value);
}
}

View File

@ -76,6 +76,10 @@ public class ScriptCondition implements Condition {
super(TYPE, met);
}
Result(Exception e) {
super(TYPE, e);
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
return builder;

View File

@ -328,6 +328,9 @@ public class ExecutionService extends AbstractComponent {
conditionResult = watch.condition().execute(ctx);
ctx.onConditionResult(conditionResult);
}
if (conditionResult.status() == Condition.Result.Status.FAILURE) {
return ctx.abortFailedExecution("failed to execute watch condition");
}
if (conditionResult.met()) {

View File

@ -121,6 +121,9 @@ public abstract class WatchExecutionContext {
}
beforeWatchTransform();
this.transformResult = watch.transform().execute(this, payload);
if (this.transformResult.status() == Transform.Result.Status.FAILURE) {
throw new WatchExecutionException("failed to execute watch level transform for [{}]", id);
}
this.payload = transformResult.payload();
this.transformedPayload = this.payload;
return transformedPayload;

View File

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.execution;
import org.elasticsearch.watcher.WatcherException;
/**
*
*/
public class WatchExecutionException extends WatcherException {
public WatchExecutionException(String msg, Object... args) {
super(msg, args);
}
public WatchExecutionException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}

View File

@ -27,10 +27,10 @@ public interface Input extends ToXContent {
SUCCESS, FAILURE
}
protected final Status status;
protected final String type;
private final Payload payload;
protected final Status status;
private final String reason;
private final Payload payload;
protected Result(String type, Payload payload) {
this.status = Status.SUCCESS;
@ -67,8 +67,8 @@ public interface Input extends ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
builder.field(Field.TYPE.getPreferredName(), type);
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
switch (status) {
case SUCCESS:
assert payload != null;

View File

@ -37,6 +37,8 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
super(settings, controller, client);
controller.registerHandler(RestRequest.Method.POST, URI_BASE + "/watch/{id}/_execute", this);
controller.registerHandler(RestRequest.Method.PUT, URI_BASE + "/watch/{id}/_execute", this);
controller.registerHandler(RestRequest.Method.POST, URI_BASE + "/watch/_execute", this);
controller.registerHandler(RestRequest.Method.PUT, URI_BASE + "/watch/_execute", this);
this.triggerService = triggerService;
}
@ -58,9 +60,8 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
//This tightly binds the REST API to the java API
private ExecuteWatchRequest parseRequest(RestRequest request, WatcherClient client) throws IOException {
String watchId = request.param("id");
ExecuteWatchRequestBuilder builder = client.prepareExecuteWatch(watchId);
ExecuteWatchRequestBuilder builder = client.prepareExecuteWatch();
builder.setId(request.param("id"));
if (request.content() == null || request.content().length() == 0) {
return builder.request();
}
@ -79,13 +80,17 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
} else if (Field.RECORD_EXECUTION.match(currentFieldName)) {
builder.setRecordExecution(parser.booleanValue());
} else {
throw new ParseException("could not parse watch execution request for [{}]. unexpected boolean field [{}]", watchId, currentFieldName);
throw new ParseException("could not parse watch execution request. unexpected boolean field [{}]", currentFieldName);
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Field.ALTERNATIVE_INPUT.match(currentFieldName)) {
builder.setAlternativeInput(parser.map());
} else if (Field.TRIGGER_DATA.match(currentFieldName)) {
builder.setTriggerData(parser.map());
} else if (Field.WATCH.match(currentFieldName)) {
XContentBuilder watcherSource = XContentBuilder.builder(parser.contentType().xContent());
XContentHelper.copyCurrentStructure(watcherSource.generator(), parser);
builder.setWatchSource(watcherSource.bytes());
} else if (Field.ACTION_MODES.match(currentFieldName)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -95,17 +100,17 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
ActionExecutionMode mode = ActionExecutionMode.resolve(parser.textOrNull());
builder.setActionMode(currentFieldName, mode);
} catch (WatcherException we) {
throw new ParseException("could not parse watch execution request for [{}].", watchId, we);
throw new ParseException("could not parse watch execution request", we);
}
} else {
throw new ParseException("could not parse watch execution request for [{}]. unexpected array field [{}]", watchId, currentFieldName);
throw new ParseException("could not parse watch execution request. unexpected array field [{}]", currentFieldName);
}
}
} else {
throw new ParseException("could not parse watch execution request for [{}]. unexpected object field [{}]", watchId, currentFieldName);
throw new ParseException("could not parse watch execution request. unexpected object field [{}]", currentFieldName);
}
} else {
throw new ParseException("could not parse watch execution request for [{}]. unexpected token [{}]", watchId, token);
throw new ParseException("could not parse watch execution request. unexpected token [{}]", token);
}
}
@ -132,5 +137,6 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
ParseField ALTERNATIVE_INPUT = new ParseField("alternative_input");
ParseField IGNORE_CONDITION = new ParseField("ignore_condition");
ParseField TRIGGER_DATA = new ParseField("trigger_data");
ParseField WATCH = new ParseField("watch");
}
}

View File

@ -14,6 +14,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.support.http.auth.ApplicableHttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
@ -57,6 +58,8 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
private final HttpAuthRegistry httpAuthRegistry;
private final Environment env;
private final TimeValue defaultConnectionTimeout;
private final TimeValue defaultReadTimeout;
private SSLSocketFactory sslSocketFactory;
@ -65,6 +68,8 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
super(settings);
this.httpAuthRegistry = httpAuthRegistry;
this.env = env;
defaultConnectionTimeout = settings.getAsTime("watcher.http.default_connection_timeout", TimeValue.timeValueSeconds(10));
defaultReadTimeout = settings.getAsTime("watcher.http.default_read_timeout", TimeValue.timeValueSeconds(10));
}
@Override
@ -145,6 +150,13 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
urlConnection.getOutputStream().write(bytes);
urlConnection.getOutputStream().close();
}
TimeValue connectionTimeout = request.connectionTimeout != null ? request.connectionTimeout : defaultConnectionTimeout;
urlConnection.setConnectTimeout((int) connectionTimeout.millis());
TimeValue readTimeout = request.readTimeout != null ? request.readTimeout : defaultReadTimeout;
urlConnection.setReadTimeout((int) readTimeout.millis());
urlConnection.connect();
final int statusCode = urlConnection.getResponseCode();

View File

@ -9,10 +9,12 @@ import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.inject.Inject;
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.watcher.WatcherException;
import org.elasticsearch.watcher.support.WatcherDateTimeUtils;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.http.auth.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
@ -31,10 +33,12 @@ public class HttpRequest implements ToXContent {
final ImmutableMap<String, String> headers;
final @Nullable HttpAuth auth;
final @Nullable String body;
final @Nullable TimeValue connectionTimeout;
final @Nullable TimeValue readTimeout;
public HttpRequest(String host, int port, @Nullable Scheme scheme, @Nullable HttpMethod method, @Nullable String path,
@Nullable ImmutableMap<String, String> params, @Nullable ImmutableMap<String, String> headers,
@Nullable HttpAuth auth, @Nullable String body) {
@Nullable HttpAuth auth, @Nullable String body, @Nullable TimeValue connectionTimeout, @Nullable TimeValue readTimeout) {
this.host = host;
this.port = port;
this.scheme = scheme != null ? scheme : Scheme.HTTP;
@ -44,6 +48,8 @@ public class HttpRequest implements ToXContent {
this.headers = headers != null ? headers : ImmutableMap.<String, String>of();
this.auth = auth;
this.body = body;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
}
public Scheme scheme() {
@ -86,6 +92,14 @@ public class HttpRequest implements ToXContent {
return body;
}
public TimeValue connectionTimeout() {
return connectionTimeout;
}
public TimeValue readTimeout() {
return readTimeout;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
@ -108,6 +122,12 @@ public class HttpRequest implements ToXContent {
if (body != null) {
builder.field(Field.BODY.getPreferredName(), body);
}
if (connectionTimeout != null) {
builder.field(Field.CONNECTION_TIMEOUT.getPreferredName(), connectionTimeout.toString());
}
if (readTimeout != null) {
builder.field(Field.READ_TIMEOUT.getPreferredName(), readTimeout.toString());
}
return builder.endObject();
}
@ -126,6 +146,8 @@ public class HttpRequest implements ToXContent {
if (!params.equals(that.params)) return false;
if (!headers.equals(that.headers)) return false;
if (auth != null ? !auth.equals(that.auth) : that.auth != null) return false;
if (connectionTimeout != null ? !connectionTimeout.equals(that.connectionTimeout) : that.connectionTimeout != null) return false;
if (readTimeout != null ? !readTimeout.equals(that.readTimeout) : that.readTimeout != null) return false;
return !(body != null ? !body.equals(that.body) : that.body != null);
}
@ -140,6 +162,8 @@ public class HttpRequest implements ToXContent {
result = 31 * result + params.hashCode();
result = 31 * result + headers.hashCode();
result = 31 * result + (auth != null ? auth.hashCode() : 0);
result = 31 * result + (connectionTimeout != null ? connectionTimeout.hashCode() : 0);
result = 31 * result + (readTimeout != null ? readTimeout.hashCode() : 0);
result = 31 * result + (body != null ? body.hashCode() : 0);
return result;
}
@ -153,6 +177,8 @@ public class HttpRequest implements ToXContent {
"], method=[" + method +
"], port=[" + port +
"], host=[" + host + '\'' +
"], connection_timeout=[" + connectionTimeout + '\'' +
"], read_timeout=[" + readTimeout + '\'' +
"]}";
}
@ -178,6 +204,18 @@ public class HttpRequest implements ToXContent {
currentFieldName = parser.currentName();
} else if (Field.AUTH.match(currentFieldName)) {
builder.auth(httpAuthRegistry.parse(parser));
} else if (Field.CONNECTION_TIMEOUT.match(currentFieldName)) {
try {
builder.connectionTimeout(WatcherDateTimeUtils.parseTimeValue(parser, null, Field.CONNECTION_TIMEOUT.toString()));
} catch (WatcherDateTimeUtils.ParseException pe) {
throw new ParseException("could not parse http request. invalid time value for [{}] field", pe, currentFieldName);
}
} else if (Field.READ_TIMEOUT.match(currentFieldName)) {
try {
builder.readTimeout(WatcherDateTimeUtils.parseTimeValue(parser, null, Field.READ_TIMEOUT.toString()));
} catch (WatcherDateTimeUtils.ParseException pe) {
throw new ParseException("could not parse http request. invalid time value for [{}] field", pe, currentFieldName);
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (Field.HEADERS.match(currentFieldName)) {
builder.setHeaders((Map) WatcherUtils.flattenModel(parser.map()));
@ -186,7 +224,7 @@ public class HttpRequest implements ToXContent {
} else if (Field.BODY.match(currentFieldName)) {
builder.body(parser.text());
} else {
throw new ParseException("could not parse http request. unexpected object field [" + currentFieldName + "]");
throw new ParseException("could not parse http request. unexpected object field [{}]", currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.SCHEME.match(currentFieldName)) {
@ -200,25 +238,25 @@ public class HttpRequest implements ToXContent {
} else if (Field.BODY.match(currentFieldName)) {
builder.body(parser.text());
} else {
throw new ParseException("could not parse http request. unexpected string field [" + currentFieldName + "]");
throw new ParseException("could not parse http request. unexpected string field [{}]", currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_NUMBER) {
if (Field.PORT.match(currentFieldName)) {
builder.port = parser.intValue();
} else {
throw new ParseException("could not parse http request. unexpected numeric field [" + currentFieldName + "]");
throw new ParseException("could not parse http request. unexpected numeric field [{}]", currentFieldName);
}
} else {
throw new ParseException("could not parse http request. unexpected token [" + token + "]");
throw new ParseException("could not parse http request. unexpected token [{}]", token);
}
}
if (builder.host == null) {
throw new ParseException("could not parse http request. missing required [host] field");
throw new ParseException("could not parse http request. missing required [{}] field", Field.HOST.getPreferredName());
}
if (builder.port < 0) {
throw new ParseException("could not parse http request. missing required [port] field");
throw new ParseException("could not parse http request. missing required [{}] field", Field.PORT.getPreferredName());
}
return builder.build();
@ -226,12 +264,12 @@ public class HttpRequest implements ToXContent {
public static class ParseException extends WatcherException {
public ParseException(String msg) {
super(msg);
public ParseException(String msg, Object... args) {
super(msg, args);
}
public ParseException(String msg, Throwable cause) {
super(msg, cause);
public ParseException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
}
@ -247,6 +285,8 @@ public class HttpRequest implements ToXContent {
private ImmutableMap.Builder<String, String> headers = ImmutableMap.builder();
private HttpAuth auth;
private String body;
private TimeValue connectionTimeout;
private TimeValue readTimeout;
private Builder(String host, int port) {
this.host = host;
@ -301,13 +341,22 @@ public class HttpRequest implements ToXContent {
return this;
}
public HttpRequest build() {
return new HttpRequest(host, port, scheme, method, path, params.build(), headers.build(), auth, body);
public Builder connectionTimeout(TimeValue timeout) {
this.connectionTimeout = timeout;
return this;
}
public Builder readTimeout(TimeValue timeout) {
this.readTimeout = timeout;
return this;
}
public HttpRequest build() {
return new HttpRequest(host, port, scheme, method, path, params.build(), headers.build(), auth, body, connectionTimeout, readTimeout);
}
}
interface Field {
public interface Field {
ParseField SCHEME = new ParseField("scheme");
ParseField HOST = new ParseField("host");
ParseField PORT = new ParseField("port");
@ -317,5 +366,7 @@ public class HttpRequest implements ToXContent {
ParseField HEADERS = new ParseField("headers");
ParseField AUTH = new ParseField("auth");
ParseField BODY = new ParseField("body");
ParseField CONNECTION_TIMEOUT = new ParseField("connection_timeout");
ParseField READ_TIMEOUT = new ParseField("read_timeout");
}
}

View File

@ -7,13 +7,15 @@ package org.elasticsearch.watcher.support.http;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.inject.Inject;
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.watcher.WatcherException;
import org.elasticsearch.watcher.support.WatcherDateTimeUtils;
import org.elasticsearch.watcher.support.http.HttpRequest.Field;
import org.elasticsearch.watcher.support.http.auth.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.template.Template;
@ -39,10 +41,12 @@ public class HttpRequestTemplate implements ToXContent {
private final ImmutableMap<String, Template> headers;
private final HttpAuth auth;
private final Template body;
private final @Nullable TimeValue connectionTimeout;
private final @Nullable TimeValue readTimeout;
public HttpRequestTemplate(String host, int port, @Nullable Scheme scheme, @Nullable HttpMethod method, @Nullable Template path,
Map<String, Template> params, Map<String, Template> headers, HttpAuth auth,
Template body) {
Template body, @Nullable TimeValue connectionTimeout, @Nullable TimeValue readTimeout) {
this.host = host;
this.port = port;
this.scheme = scheme != null ? scheme :Scheme.HTTP;
@ -52,6 +56,8 @@ public class HttpRequestTemplate implements ToXContent {
this.headers = headers != null ? ImmutableMap.copyOf(headers) : ImmutableMap.<String, Template>of();
this.auth = auth;
this.body = body;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
}
public Scheme scheme() {
@ -90,6 +96,14 @@ public class HttpRequestTemplate implements ToXContent {
return body;
}
public TimeValue connectionTimeout() {
return connectionTimeout;
}
public TimeValue readTimeout() {
return readTimeout;
}
public HttpRequest render(TemplateEngine engine, Map<String, Object> model) {
HttpRequest.Builder request = HttpRequest.builder(host, port);
request.method(method);
@ -123,39 +137,51 @@ public class HttpRequestTemplate implements ToXContent {
if (body != null) {
request.body(engine.render(body, model));
}
if (connectionTimeout != null) {
request.connectionTimeout(connectionTimeout);
}
if (readTimeout != null) {
request.readTimeout(readTimeout);
}
return request.build();
}
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(Parser.SCHEME_FIELD.getPreferredName(), scheme, params);
builder.field(Parser.HOST_FIELD.getPreferredName(), host);
builder.field(Parser.PORT_FIELD.getPreferredName(), port);
builder.field(Parser.METHOD_FIELD.getPreferredName(), method, params);
builder.field(Field.SCHEME.getPreferredName(), scheme, params);
builder.field(Field.HOST.getPreferredName(), host);
builder.field(Field.PORT.getPreferredName(), port);
builder.field(Field.METHOD.getPreferredName(), method, params);
if (path != null) {
builder.field(Parser.PATH_FIELD.getPreferredName(), path, params);
builder.field(Field.PATH.getPreferredName(), path, params);
}
if (this.params != null) {
builder.startObject(Parser.PARAMS_FIELD.getPreferredName());
builder.startObject(Field.PARAMS.getPreferredName());
for (Map.Entry<String, Template> entry : this.params.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params);
}
builder.endObject();
}
if (headers != null) {
builder.startObject(Parser.HEADERS_FIELD.getPreferredName());
builder.startObject(Field.HEADERS.getPreferredName());
for (Map.Entry<String, Template> entry : headers.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params);
}
builder.endObject();
}
if (auth != null) {
builder.startObject(Parser.AUTH_FIELD.getPreferredName())
builder.startObject(Field.AUTH.getPreferredName())
.field(auth.type(), auth, params)
.endObject();
}
if (body != null) {
builder.field(Parser.BODY_FIELD.getPreferredName(), body, params);
builder.field(Field.BODY.getPreferredName(), body, params);
}
if (connectionTimeout != null) {
builder.field(Field.CONNECTION_TIMEOUT.getPreferredName(), connectionTimeout.toString());
}
if (readTimeout != null) {
builder.field(Field.READ_TIMEOUT.getPreferredName(), readTimeout.toString());
}
return builder.endObject();
}
@ -175,6 +201,8 @@ public class HttpRequestTemplate implements ToXContent {
if (params != null ? !params.equals(that.params) : that.params != null) return false;
if (headers != null ? !headers.equals(that.headers) : that.headers != null) return false;
if (auth != null ? !auth.equals(that.auth) : that.auth != null) return false;
if (connectionTimeout != null ? !connectionTimeout.equals(that.connectionTimeout) : that.connectionTimeout != null) return false;
if (readTimeout != null ? !readTimeout.equals(that.readTimeout) : that.readTimeout != null) return false;
return body != null ? body.equals(that.body) : that.body == null;
}
@ -189,6 +217,8 @@ public class HttpRequestTemplate implements ToXContent {
result = 31 * result + (headers != null ? headers.hashCode() : 0);
result = 31 * result + (auth != null ? auth.hashCode() : 0);
result = 31 * result + (body != null ? body.hashCode() : 0);
result = 31 * result + (connectionTimeout != null ? connectionTimeout.hashCode() : 0);
result = 31 * result + (readTimeout != null ? readTimeout.hashCode() : 0);
return result;
}
@ -198,17 +228,6 @@ public class HttpRequestTemplate implements ToXContent {
public static class Parser {
public static final ParseField SCHEME_FIELD = new ParseField("scheme");
public static final ParseField HOST_FIELD = new ParseField("host");
public static final ParseField PORT_FIELD = new ParseField("port");
public static final ParseField METHOD_FIELD = new ParseField("method");
public static final ParseField PATH_FIELD = new ParseField("path");
public static final ParseField PARAMS_FIELD = new ParseField("params");
public static final ParseField HEADERS_FIELD = new ParseField("headers");
public static final ParseField AUTH_FIELD = new ParseField("auth");
public static final ParseField BODY_FIELD = new ParseField("body");
public static final ParseField XBODY_FIELD = new ParseField("xbody");
private final HttpAuthRegistry httpAuthRegistry;
@Inject
@ -225,32 +244,44 @@ public class HttpRequestTemplate implements ToXContent {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (PATH_FIELD.match(currentFieldName)) {
} else if (Field.PATH.match(currentFieldName)) {
builder.path(parseFieldTemplate(currentFieldName, parser));
} else if (HEADERS_FIELD.match(currentFieldName)) {
} else if (Field.HEADERS.match(currentFieldName)) {
builder.putHeaders(parseFieldTemplates(currentFieldName, parser));
} else if (PARAMS_FIELD.match(currentFieldName)) {
} else if (Field.PARAMS.match(currentFieldName)) {
builder.putParams(parseFieldTemplates(currentFieldName, parser));
} else if (BODY_FIELD.match(currentFieldName)) {
} else if (Field.BODY.match(currentFieldName)) {
builder.body(parseFieldTemplate(currentFieldName, parser));
} else if (Field.CONNECTION_TIMEOUT.match(currentFieldName)) {
try {
builder.connectionTimeout(WatcherDateTimeUtils.parseTimeValue(parser, null, Field.CONNECTION_TIMEOUT.toString()));
} catch (WatcherDateTimeUtils.ParseException pe) {
throw new ParseException("could not parse http request template. invalid time value for [{}] field", pe, currentFieldName);
}
} else if (Field.READ_TIMEOUT.match(currentFieldName)) {
try {
builder.readTimeout(WatcherDateTimeUtils.parseTimeValue(parser, null, Field.READ_TIMEOUT.toString()));
} catch (WatcherDateTimeUtils.ParseException pe) {
throw new ParseException("could not parse http request template. invalid time value for [{}] field", pe, currentFieldName);
}
} else if (token == XContentParser.Token.START_OBJECT) {
if (AUTH_FIELD.match(currentFieldName)) {
if (Field.AUTH.match(currentFieldName)) {
builder.auth(httpAuthRegistry.parse(parser));
} else {
throw new ParseException("could not parse http request template. unexpected object field [{}]", currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (SCHEME_FIELD.match(currentFieldName)) {
if (Field.SCHEME.match(currentFieldName)) {
builder.scheme(Scheme.parse(parser.text()));
} else if (METHOD_FIELD.match(currentFieldName)) {
} else if (Field.METHOD.match(currentFieldName)) {
builder.method(HttpMethod.parse(parser.text()));
} else if (HOST_FIELD.match(currentFieldName)) {
} else if (Field.HOST.match(currentFieldName)) {
builder.host = parser.text();
} else {
throw new ParseException("could not parse http request template. unexpected string field [{}]", currentFieldName);
}
} else if (token == XContentParser.Token.VALUE_NUMBER) {
if (PORT_FIELD.match(currentFieldName)) {
if (Field.PORT.match(currentFieldName)) {
builder.port = parser.intValue();
} else {
throw new ParseException("could not parse http request template. unexpected numeric field [{}]", currentFieldName);
@ -261,10 +292,10 @@ public class HttpRequestTemplate implements ToXContent {
}
if (builder.host == null) {
throw new ParseException("could not parse http request template. missing required [{}] string field", HOST_FIELD.getPreferredName());
throw new ParseException("could not parse http request template. missing required [{}] string field", Field.HOST.getPreferredName());
}
if (builder.port <= 0) {
throw new ParseException("could not parse http request template. missing required [{}] numeric field", PORT_FIELD.getPreferredName());
throw new ParseException("could not parse http request template. missing required [{}] numeric field", Field.PORT.getPreferredName());
}
return builder.build();
@ -317,6 +348,8 @@ public class HttpRequestTemplate implements ToXContent {
private final ImmutableMap.Builder<String, Template> headers = ImmutableMap.builder();
private HttpAuth auth;
private Template body;
private TimeValue connectionTimeout;
private TimeValue readTimeout;
private Builder() {
}
@ -407,8 +440,18 @@ public class HttpRequestTemplate implements ToXContent {
return body(Template.inline(content));
}
public Builder connectionTimeout(TimeValue timeout) {
this.connectionTimeout = timeout;
return this;
}
public Builder readTimeout(TimeValue timeout) {
this.readTimeout = timeout;
return this;
}
public HttpRequestTemplate build() {
return new HttpRequestTemplate(host, port, scheme, method, path, params.build(), headers.build(), auth, body);
return new HttpRequestTemplate(host, port, scheme, method, path, params.build(), headers.build(), auth, body, connectionTimeout, readTimeout);
}
}

View File

@ -34,7 +34,7 @@ public abstract class ExecutableTransform<T extends Transform, R extends Transfo
return transform;
}
public abstract R execute(WatchExecutionContext ctx, Payload payload) throws IOException;
public abstract R execute(WatchExecutionContext ctx, Payload payload);
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.watcher.transform;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -21,27 +23,64 @@ public interface Transform extends ToXContent {
abstract class Result implements ToXContent {
public enum Status {
SUCCESS, FAILURE
}
protected final String type;
protected final Payload payload;
protected final Status status;
protected final @Nullable Payload payload;
protected final @Nullable String reason;
public Result(String type, Payload payload) {
this.type = type;
this.status = Status.SUCCESS;
this.payload = payload;
this.reason = null;
}
public Result(String type, Exception e) {
this.type = type;
this.status = Status.FAILURE;
this.reason = ExceptionsHelper.detailedMessage(e);
this.payload = null;
}
public String type() {
return type;
}
public Status status() {
return status;
}
public Payload payload() {
assert status == Status.SUCCESS;
return payload;
}
public String reason() {
assert status == Status.FAILURE;
return reason;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.TYPE.getPreferredName(), type);
builder.field(Field.PAYLOAD.getPreferredName(), payload, params);
builder.field(Field.STATUS.getPreferredName(), status);
switch (status) {
case SUCCESS:
assert reason == null;
builder.field(Field.PAYLOAD.getPreferredName(), payload, params);
break;
case FAILURE:
assert payload == null;
builder.field(Field.REASON.getPreferredName(), reason);
break;
default:
assert false;
}
typeXContent(builder, params);
return builder.endObject();
}
@ -56,8 +95,12 @@ public interface Transform extends ToXContent {
}
interface Field {
ParseField TYPE = new ParseField("type");
ParseField PAYLOAD = new ParseField("payload");
ParseField TRANSFORM = new ParseField("transform");
ParseField TYPE = new ParseField("type");
ParseField STATUS = new ParseField("status");
ParseField PAYLOAD = new ParseField("payload");
ParseField REASON = new ParseField("reason");
}
}

View File

@ -101,19 +101,27 @@ public class ChainTransform implements Transform {
this.results = results;
}
public Result(Exception e, ImmutableList<Transform.Result> results) {
super(TYPE, e);
this.results = results;
}
public ImmutableList<Transform.Result> results() {
return results;
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(type);
builder.startArray(Field.RESULTS.getPreferredName());
for (Transform.Result result : results) {
result.toXContent(builder, params);
if (!results.isEmpty()) {
builder.startObject(type);
builder.startArray(Field.RESULTS.getPreferredName());
for (Transform.Result result : results) {
result.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
}
builder.endArray();
return builder.endObject();
return builder;
}
}

View File

@ -32,11 +32,24 @@ public class ExecutableChainTransform extends ExecutableTransform<ChainTransform
}
@Override
public ChainTransform.Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
public ChainTransform.Result execute(WatchExecutionContext ctx, Payload payload) {
ImmutableList.Builder<Transform.Result> results = ImmutableList.builder();
try {
return doExecute(ctx, payload, results);
} catch (Exception e) {
logger.error("failed to execute [{}] transform for [{}]", e, ChainTransform.TYPE, ctx.id());
return new ChainTransform.Result(e, results.build());
}
}
ChainTransform.Result doExecute(WatchExecutionContext ctx, Payload payload, ImmutableList.Builder<Transform.Result> results) throws IOException {
for (ExecutableTransform transform : transforms) {
Transform.Result result = transform.execute(ctx, payload);
results.add(result);
if (result.status() == Transform.Result.Status.FAILURE) {
throw new ChainTransformException("failed to execute [{}] transform. failed to execute sub-transform [{}]", ChainTransform.TYPE, transform.type());
}
payload = result.payload();
}
return new ChainTransform.Result(payload, results.build());

View File

@ -40,7 +40,17 @@ public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransfo
}
@Override
public ScriptTransform.Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
public ScriptTransform.Result execute(WatchExecutionContext ctx, Payload payload) {
try {
return doExecute(ctx, payload);
} catch (Exception e) {
logger.error("failed to execute [{}] transform for [{}]", e, ScriptTransform.TYPE, ctx.id());
return new ScriptTransform.Result(e);
}
}
ScriptTransform.Result doExecute(WatchExecutionContext ctx, Payload payload) throws IOException {
Script script = transform.getScript();
Map<String, Object> model = new HashMap<>();
model.putAll(script.params());

View File

@ -74,6 +74,10 @@ public class ScriptTransform implements Transform {
super(TYPE, payload);
}
public Result(Exception e) {
super(TYPE, e);
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
return builder;

View File

@ -15,8 +15,6 @@ import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.transform.ExecutableTransform;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
/**
*
*/
@ -32,10 +30,16 @@ public class ExecutableSearchTransform extends ExecutableTransform<SearchTransfo
}
@Override
public SearchTransform.Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
SearchRequest req = WatcherUtils.createSearchRequestFromPrototype(transform.request, ctx, payload);
SearchResponse resp = client.search(req);
return new SearchTransform.Result(req, new Payload.XContent(resp));
public SearchTransform.Result execute(WatchExecutionContext ctx, Payload payload) {
SearchRequest request = null;
try {
request = WatcherUtils.createSearchRequestFromPrototype(transform.request, ctx, payload);
SearchResponse resp = client.search(request);
return new SearchTransform.Result(request, new Payload.XContent(resp));
} catch (Exception e) {
logger.error("failed to execute [{}] transform for [{}]", e, SearchTransform.TYPE, ctx.id());
return new SearchTransform.Result(request, e);
}
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.watcher.transform.search;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
@ -74,23 +75,31 @@ public class SearchTransform implements Transform {
public static class Result extends Transform.Result {
private final SearchRequest request;
private final @Nullable SearchRequest request;
public Result(SearchRequest request, Payload payload) {
super(TYPE, payload);
this.request = request;
}
public Result(SearchRequest request, Exception e) {
super(TYPE, e);
this.request = request;
}
public SearchRequest executedRequest() {
return request;
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(type);
builder.field(Field.REQUEST.getPreferredName());
WatcherUtils.writeSearchRequest(request, builder, params);
return builder.endObject();
if (request != null) {
builder.startObject(type);
builder.field(Field.REQUEST.getPreferredName());
WatcherUtils.writeSearchRequest(request, builder, params);
builder.endObject();
}
return builder;
}
}

View File

@ -9,8 +9,11 @@ import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ValidateActions;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.watcher.client.WatchSourceBuilder;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import org.elasticsearch.watcher.support.validation.Validation;
import org.elasticsearch.watcher.trigger.TriggerEvent;
@ -24,12 +27,15 @@ import java.util.Map;
*/
public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchRequest> {
public static final String INLINE_WATCH_ID = "_inlined_";
private String id;
private boolean ignoreCondition = false;
private boolean recordExecution = false;
private @Nullable Map<String, Object> triggerData = null;
private @Nullable Map<String, Object> alternativeInput = null;
private Map<String, ActionExecutionMode> actionModes = new HashMap<>();
private BytesReference watchSource;
ExecuteWatchRequest() {
}
@ -120,6 +126,26 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
return triggerData;
}
/**
* @return the source of the watch to execute
*/
public BytesReference getWatchSource() {
return watchSource;
}
/**
* @param watchSource instead of using an existing watch use this non persisted watch
*/
public void setWatchSource(BytesReference watchSource) {
this.watchSource = watchSource;
}
/**
* @param watchSource instead of using an existing watch use this non persisted watch
*/
public void setWatchSource(WatchSourceBuilder watchSource) {
this.watchSource = watchSource.buildAsBytes(XContentType.JSON);
}
/**
*
@ -143,26 +169,34 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (id == null){
if (id == null && watchSource == null){
validationException = ValidateActions.addValidationError("watch id is missing", validationException);
}
Validation.Error error = Validation.watchId(id);
if (error != null) {
validationException = ValidateActions.addValidationError(error.message(), validationException);
}
for (Map.Entry<String, ActionExecutionMode> modes : actionModes.entrySet()) {
error = Validation.actionId(modes.getKey());
if (id != null) {
Validation.Error error = Validation.watchId(id);
if (error != null) {
validationException = ValidateActions.addValidationError(error.message(), validationException);
}
}
for (Map.Entry<String, ActionExecutionMode> modes : actionModes.entrySet()) {
Validation.Error error = Validation.actionId(modes.getKey());
if (error != null) {
validationException = ValidateActions.addValidationError(error.message(), validationException);
}
}
if (watchSource != null && id != null) {
validationException = ValidateActions.addValidationError("a watch execution request must either have a watch id or an inline watch source but not both", validationException);
}
if (watchSource != null && recordExecution) {
validationException = ValidateActions.addValidationError("the execution of an inline watch cannot be recorded", validationException);
}
return validationException;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
id = in.readString();
id = in.readOptionalString();
ignoreCondition = in.readBoolean();
recordExecution = in.readBoolean();
if (in.readBoolean()){
@ -176,13 +210,16 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
for (int i = 0; i < actionModesCount; i++) {
actionModes.put(in.readString(), ActionExecutionMode.resolve(in.readByte()));
}
if (in.readBoolean()) {
watchSource = in.readBytesReference();
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(id);
out.writeOptionalString(id);
out.writeBoolean(ignoreCondition);
out.writeBoolean(recordExecution);
out.writeBoolean(alternativeInput != null);
@ -198,6 +235,10 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
out.writeString(entry.getKey());
out.writeByte(entry.getValue().id());
}
out.writeBoolean(watchSource != null);
if (watchSource != null) {
out.writeBytesReference(watchSource);
}
}
@Override

View File

@ -5,11 +5,10 @@
*/
package org.elasticsearch.watcher.transport.actions.execute;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.watcher.client.WatchSourceBuilder;
import org.elasticsearch.watcher.execution.ActionExecutionMode;
import org.elasticsearch.watcher.trigger.TriggerEvent;
@ -76,6 +75,22 @@ public class ExecuteWatchRequestBuilder extends MasterNodeOperationRequestBuilde
return this;
}
/**
* @param watchSource instead of using an existing watch use this non persisted watch
*/
public ExecuteWatchRequestBuilder setWatchSource(BytesReference watchSource) {
request.setWatchSource(watchSource);
return this;
}
/**
* @param watchSource instead of using an existing watch use this non persisted watch
*/
public ExecuteWatchRequestBuilder setWatchSource(WatchSourceBuilder watchSource) {
request.setWatchSource(watchSource);
return this;
}
/**
* Sets the mode in which the given action (identified by its id) will be handled.
*
@ -86,5 +101,4 @@ public class ExecuteWatchRequestBuilder extends MasterNodeOperationRequestBuilde
request.setActionMode(actionId, actionMode);
return this;
}
}

View File

@ -49,16 +49,19 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
private final WatchStore watchStore;
private final Clock clock;
private final TriggerService triggerService;
private final Watch.Parser watchParser;
@Inject
public TransportExecuteWatchAction(Settings settings, TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters, ExecutionService executionService,
Clock clock, LicenseService licenseService, WatchStore watchStore, TriggerService triggerService) {
Clock clock, LicenseService licenseService, WatchStore watchStore, TriggerService triggerService,
Watch.Parser watchParser) {
super(settings, ExecuteWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, licenseService, ExecuteWatchRequest.class);
this.executionService = executionService;
this.watchStore = watchStore;
this.clock = clock;
this.triggerService = triggerService;
this.watchParser = watchParser;
}
@Override
@ -74,15 +77,26 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
@Override
protected void masterOperation(ExecuteWatchRequest request, ClusterState state, ActionListener<ExecuteWatchResponse> listener) throws ElasticsearchException {
try {
Watch watch = watchStore.get(request.getId());
if (watch == null) {
throw new WatcherException("watch [{}] does not exist", request.getId());
Watch watch;
boolean knownWatch;
if (request.getId() != null) {
watch = watchStore.get(request.getId());
if (watch == null) {
throw new WatcherException("watch [{}] does not exist", request.getId());
}
knownWatch = true;
} else if (request.getWatchSource() != null) {
assert !request.isRecordExecution();
watch = watchParser.parse(ExecuteWatchRequest.INLINE_WATCH_ID, false, request.getWatchSource());
knownWatch = false;
} else {
throw new WatcherException("no watch provided");
}
String triggerType = watch.trigger().type();
TriggerEvent triggerEvent = triggerService.simulateEvent(triggerType, watch.id(), request.getTriggerData());
ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, true, new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent), executionService.defaultThrottlePeriod());
ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, knownWatch, new ManualTriggerEvent(triggerEvent.jobName(), triggerEvent), executionService.defaultThrottlePeriod());
DateTime executionTime = clock.now(DateTimeZone.UTC);
ctxBuilder.executionTime(executionTime);

View File

@ -105,7 +105,9 @@ public class TriggerService extends AbstractComponent {
public Trigger parseTrigger(String jobName, String type, XContentParser parser) throws IOException {
TriggerEngine engine = engines.get(type);
assert engine != null;
if (engine == null) {
throw new TriggerException("could not parse trigger [{}] for [{}]. unknown trigger type [{}]", type, jobName, type);
}
return engine.parseTrigger(jobName, parser);
}

View File

@ -252,6 +252,7 @@ public class Watch implements TriggerEngine.Job, ToXContent {
if (withSecrets) {
parser = new SensitiveXContentParser(parser, secretService);
}
parser.nextToken();
return parse(id, includeStatus, parser);
} catch (IOException ioe) {
throw new WatcherException("could not parse watch [{}]", ioe, id);
@ -273,7 +274,7 @@ public class Watch implements TriggerEngine.Job, ToXContent {
WatchStatus status = null;
String currentFieldName = null;
XContentParser.Token token = parser.nextToken();
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == null ) {
throw new ParseException("could not parse watch [{}]. null token", id);

View File

@ -108,14 +108,14 @@
"type" : "string",
"index" : "not_analyzed"
},
"payload" : {
"type" : "object",
"enabled" : false
},
"status" : {
"type" : "string",
"index" : "not_analyzed"
},
"payload" : {
"type" : "object",
"enabled" : false
},
"search": {
"type": "object",
"dynamic": true,
@ -170,6 +170,10 @@
"type" : "string",
"index" : "not_analyzed"
},
"status" : {
"type" : "string",
"index" : "not_analyzed"
},
"met" : {
"type" : "boolean"
},

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.actions;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.index.IndexResponse;
import org.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.actions.email;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.MapBuilder;
@ -57,6 +56,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
}
};
TemplateEngine engine = mock(TemplateEngine.class);
HtmlSanitizer htmlSanitizer = mock(HtmlSanitizer.class);
EmailTemplate.Builder emailBuilder = EmailTemplate.builder();
Template subject = null;
@ -72,7 +72,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
Template htmlBody = null;
if (randomBoolean()) {
htmlBody = Template.inline("_html_body").build();
emailBuilder.htmlBody(htmlBody, true);
emailBuilder.htmlBody(htmlBody);
}
EmailTemplate email = emailBuilder.build();
@ -82,7 +82,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
DataAttachment dataAttachment = randomDataAttachment();
EmailAction action = new EmailAction(email, account, auth, profile, dataAttachment);
ExecutableEmailAction executable = new ExecutableEmailAction(action, logger, service, engine);
ExecutableEmailAction executable = new ExecutableEmailAction(action, logger, service, engine, htmlSanitizer);
Map<String, Object> data = new HashMap<>();
Payload payload = new Payload.Simple(data);
@ -120,6 +120,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
when(engine.render(textBody, expectedModel)).thenReturn(textBody.getTemplate());
}
if (htmlBody != null) {
when(htmlSanitizer.sanitize(htmlBody.getTemplate())).thenReturn(htmlBody.getTemplate());
when(engine.render(htmlBody, expectedModel)).thenReturn(htmlBody.getTemplate());
}
@ -142,6 +143,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
@Test
public void testParser() throws Exception {
TemplateEngine engine = mock(TemplateEngine.class);
HtmlSanitizer htmlSanitizer = mock(HtmlSanitizer.class);
EmailService emailService = mock(EmailService.class);
Profile profile = randomFrom(Profile.values());
Email.Priority priority = randomFrom(Email.Priority.values());
@ -241,7 +243,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
ExecutableEmailAction executable = new EmailActionFactory(Settings.EMPTY, emailService, engine)
ExecutableEmailAction executable = new EmailActionFactory(Settings.EMPTY, emailService, engine, htmlSanitizer)
.parseExecutable(randomAsciiOfLength(8), randomAsciiOfLength(3), parser);
assertThat(executable, notNullValue());
@ -289,6 +291,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
public void testParser_SelfGenerated() throws Exception {
EmailService service = mock(EmailService.class);
TemplateEngine engine = mock(TemplateEngine.class);
HtmlSanitizer htmlSanitizer = mock(HtmlSanitizer.class);
EmailTemplate.Builder emailTemplate = EmailTemplate.builder();
if (randomBoolean()) {
emailTemplate.from("from@domain");
@ -312,7 +315,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
emailTemplate.textBody("_text_body");
}
if (randomBoolean()) {
emailTemplate.htmlBody("_html_body", randomBoolean());
emailTemplate.htmlBody("_html_body");
}
EmailTemplate email = emailTemplate.build();
Authentication auth = randomBoolean() ? null : new Authentication("_user", new Secret("_passwd".toCharArray()));
@ -321,7 +324,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
DataAttachment dataAttachment = randomDataAttachment();
EmailAction action = new EmailAction(email, account, auth, profile, dataAttachment);
ExecutableEmailAction executable = new ExecutableEmailAction(action, logger, service, engine);
ExecutableEmailAction executable = new ExecutableEmailAction(action, logger, service, engine, htmlSanitizer);
boolean hideSecrets = randomBoolean();
ToXContent.Params params = WatcherParams.builder().hideSecrets(hideSecrets).build();
@ -332,7 +335,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
logger.info(bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
ExecutableEmailAction parsed = new EmailActionFactory(Settings.EMPTY, service, engine)
ExecutableEmailAction parsed = new EmailActionFactory(Settings.EMPTY, service, engine, htmlSanitizer)
.parseExecutable(randomAsciiOfLength(4), randomAsciiOfLength(10), parser);
if (!hideSecrets) {
@ -357,10 +360,11 @@ public class EmailActionTests extends ElasticsearchTestCase {
public void testParser_Invalid() throws Exception {
EmailService emailService = mock(EmailService.class);
TemplateEngine engine = mock(TemplateEngine.class);
HtmlSanitizer htmlSanitizer = mock(HtmlSanitizer.class);
XContentBuilder builder = jsonBuilder().startObject().field("unknown_field", "value");
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
new EmailActionFactory(Settings.EMPTY, emailService, engine)
new EmailActionFactory(Settings.EMPTY, emailService, engine, htmlSanitizer)
.parseExecutable(randomAsciiOfLength(3), randomAsciiOfLength(7), parser);
}

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.actions.email.service;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
@ -43,25 +42,18 @@ public class EmailTemplateTests extends ElasticsearchTestCase {
Template[] cc = randomFrom(possibleList, null);
Template[] bcc = randomFrom(possibleList, null);
Template priority = Template.inline(randomFrom(Email.Priority.values()).name()).build();
boolean sanitizeHtml = randomBoolean();
Template templatedSubject = Template.inline("Templated Subject {{foo}}").build();
String renderedTemplatedSubject = "Templated Subject bar";
Template subjectTemplate = Template.inline("Templated Subject {{foo}}").build();
String subject = "Templated Subject bar";
Template templatedBody = Template.inline("Templated Body {{foo}}").build();
String renderedTemplatedBody = "Templated Body bar";
Template textBodyTemplate = Template.inline("Templated Body {{foo}}").build();
String textBody = "Templated Body bar";
Template templatedHtmlBodyGood = Template.inline("Templated Html Body <hr />").build();
String renderedTemplatedHtmlBodyGood = "Templated Html Body <hr /> bar";
Template htmlBodyTemplate = Template.inline("Templated Html Body <script>nefarious scripting</script>").build();
String htmlBody = "Templated Html Body <script>nefarious scripting</script>";
String sanitizedHtmlBody = "Templated Html Body";
Template templatedHtmlBodyBad = Template.inline("Templated Html Body <script>nefarious scripting</script>").build();
String renderedTemplatedHtmlBodyBad = "Templated Html Body<script>nefarious scripting</script>";
String renderedSanitizedHtmlBodyBad = "Templated Html Body";
Template htmlBody = randomFrom(templatedHtmlBodyGood, templatedHtmlBodyBad);
EmailTemplate emailTemplate = new EmailTemplate(from, replyTo, priority, to, cc, bcc,
templatedSubject, templatedBody, htmlBody, sanitizeHtml);
EmailTemplate emailTemplate = new EmailTemplate(from, replyTo, priority, to, cc, bcc, subjectTemplate, textBodyTemplate, htmlBodyTemplate);
XContentBuilder builder = XContentFactory.jsonBuilder();
emailTemplate.toXContent(builder, ToXContent.EMPTY_PARAMS);
@ -69,7 +61,7 @@ public class EmailTemplateTests extends ElasticsearchTestCase {
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
EmailTemplate.Parser emailTemplateParser = new EmailTemplate.Parser(sanitizeHtml);
EmailTemplate.Parser emailTemplateParser = new EmailTemplate.Parser();
String currentFieldName = null;
XContentParser.Token token;
@ -83,11 +75,14 @@ public class EmailTemplateTests extends ElasticsearchTestCase {
EmailTemplate parsedEmailTemplate = emailTemplateParser.parsedTemplate();
Map<String, Object> model = new HashMap<>();
HtmlSanitizer htmlSanitizer = mock(HtmlSanitizer.class);
when(htmlSanitizer.sanitize(htmlBody)).thenReturn(sanitizedHtmlBody);
TemplateEngine templateEngine = mock(TemplateEngine.class);
when(templateEngine.render(templatedSubject, model)).thenReturn(renderedTemplatedSubject);
when(templateEngine.render(templatedBody, model)).thenReturn(renderedTemplatedBody);
when(templateEngine.render(templatedHtmlBodyGood, model)).thenReturn(renderedTemplatedHtmlBodyGood);
when(templateEngine.render(templatedHtmlBodyBad, model)).thenReturn(renderedTemplatedHtmlBodyBad);
when(templateEngine.render(subjectTemplate, model)).thenReturn(subject);
when(templateEngine.render(textBodyTemplate, model)).thenReturn(textBody);
when(templateEngine.render(htmlBodyTemplate, model)).thenReturn(htmlBody);
for (Template possibleAddress : possibleList) {
when(templateEngine.render(possibleAddress, model)).thenReturn(possibleAddress.getTemplate());
}
@ -96,7 +91,7 @@ public class EmailTemplateTests extends ElasticsearchTestCase {
}
when(templateEngine.render(priority, model)).thenReturn(priority.getTemplate());
Email.Builder emailBuilder = parsedEmailTemplate.render(templateEngine, model, new HashMap<String, Attachment>());
Email.Builder emailBuilder = parsedEmailTemplate.render(templateEngine, model, htmlSanitizer, new HashMap<String, Attachment>());
assertThat(emailTemplate.from, equalTo(parsedEmailTemplate.from));
assertThat(emailTemplate.replyTo, equalTo(parsedEmailTemplate.replyTo));
@ -110,17 +105,9 @@ public class EmailTemplateTests extends ElasticsearchTestCase {
emailBuilder.id("_id");
Email email = emailBuilder.build();
assertThat(email.subject, equalTo(renderedTemplatedSubject));
assertThat(email.textBody, equalTo(renderedTemplatedBody));
if (htmlBody.equals(templatedHtmlBodyBad)) {
if (sanitizeHtml) {
assertThat(email.htmlBody, equalTo(renderedSanitizedHtmlBodyBad));
} else {
assertThat(email.htmlBody, equalTo(renderedTemplatedHtmlBodyBad));
}
} else {
assertThat(email.htmlBody, equalTo(renderedTemplatedHtmlBodyGood));
}
assertThat(email.subject, equalTo(subject));
assertThat(email.textBody, equalTo(textBody));
assertThat(email.htmlBody, equalTo(sanitizedHtmlBody));
}

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.actions.email.service;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;
import org.elasticsearch.common.xcontent.ToXContent;

View File

@ -1,79 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.email.service;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.elasticsearch.watcher.actions.email.service.EmailTemplate.sanitizeHtml;
import static org.hamcrest.Matchers.equalTo;
/**
*/
public class HtmlSanitizeTests extends ElasticsearchTestCase {
@Test
public void test_HtmlSanitizer_onclick() {
String badHtml = "<button type=\"button\"" +
"onclick=\"document.getElementById('demo').innerHTML = Date()\">" +
"Click me to display Date and Time.</button>";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(badHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo("Click me to display Date and Time."));
}
@Test
public void test_HtmlSanitizer_Nonattachment_img() {
String badHtml = "<img src=\"http://test.com/nastyimage.jpg\"/>This is a bad image";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(badHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo("This is a bad image"));
}
@Test
public void test_HtmlSanitizer_Goodattachment_img() {
String goodHtml = "<img src=\"cid:foo\" />This is a good image";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(goodHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo(goodHtml));
}
@Test
public void test_HtmlSanitizer_table() {
String goodHtml = "<table><tr><td>cell1</td><td>cell2</td></tr></table>";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(goodHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo(goodHtml));
}
@Test
public void test_HtmlSanitizer_Badattachment_img() {
String goodHtml = "<img src=\"cid:bad\" />This is a bad image";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(goodHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo("This is a bad image"));
}
@Test
public void test_HtmlSanitizer_Script() {
String badHtml = "<script>doSomethingNefarious()</script>This was a dangerous script";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(badHtml, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo("This was a dangerous script"));
}
@Test
public void test_HtmlSanitizer_FullHtmlWithMetaString() {
String needsSanitation = "<html><head></head><body><h1>Hello {{ctx.metadata.name}}</h1> meta <a href='https://www.google.com/search?q={{ctx.metadata.name}}'>Testlink</a>meta</body></html>";
byte[] bytes = new byte[0];
String sanitizedHtml = sanitizeHtml(needsSanitation, ImmutableMap.of("foo", (Attachment) new Attachment.Bytes("foo", bytes, "")));
assertThat(sanitizedHtml, equalTo("<head></head><body><h1>Hello {{ctx.metadata.name}}</h1> meta <a href=\"https://www.google.com/search?q&#61;{{ctx.metadata.name}}\" rel=\"nofollow\">Testlink</a>meta</body>"));
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.email.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class HtmlSanitizerTests extends ElasticsearchTestCase {
@Test
public void testDefault_WithTemplatePlaceholders() {
String blockTag = randomFrom(HtmlSanitizer.BLOCK_TAGS);
while (blockTag.equals("li")) {
blockTag = randomFrom(HtmlSanitizer.BLOCK_TAGS);
}
String html =
"<html>" +
"<head></head>" +
"<body>" +
"<" + blockTag + ">Hello {{ctx.metadata.name}}</" + blockTag + ">" +
"<ul><li>item1</li></ul>" +
"<ol><li>item2</li></ol>" +
"meta <a href='https://www.google.com/search?q={{ctx.metadata.name}}'>Testlink</a> meta" +
"</body>" +
"</html>";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(html);
if (blockTag.equals("ol") || blockTag.equals("ul")) {
assertThat(sanitizedHtml, equalTo(
"<head></head><body>" +
"<" + blockTag + "><li>Hello {{ctx.metadata.name}}</li></" + blockTag + ">" +
"<ul><li>item1</li></ul>" +
"<ol><li>item2</li></ol>" +
"meta <a href=\"https://www.google.com/search?q&#61;{{ctx.metadata.name}}\" rel=\"nofollow\">Testlink</a> meta" +
"</body>"));
} else {
assertThat(sanitizedHtml, equalTo(
"<head></head><body>" +
"<" + blockTag + ">Hello {{ctx.metadata.name}}</" + blockTag + ">" +
"<ul><li>item1</li></ul>" +
"<ol><li>item2</li></ol>" +
"meta <a href=\"https://www.google.com/search?q&#61;{{ctx.metadata.name}}\" rel=\"nofollow\">Testlink</a> meta" +
"</body>"));
}
}
@Test
public void testDefault_onclick_Disallowed() {
String badHtml = "<button type=\"button\"" +
"onclick=\"document.getElementById('demo').innerHTML = Date()\">" +
"Click me to display Date and Time.</button>";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(badHtml);
assertThat(sanitizedHtml, equalTo("Click me to display Date and Time."));
}
@Test
public void testDefault_ExternalImage_Disallowed() {
String html = "<img src=\"http://test.com/nastyimage.jpg\"/>This is a bad image";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo("This is a bad image"));
}
@Test
public void testDefault_EmbeddedImage_Allowed() {
String html = "<img src=\"cid:foo\" />This is a good image";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo(html));
}
@Test
public void testDefault_Tables_Allowed() {
String html = "<table><tr><td>cell1</td><td>cell2</td></tr></table>";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo(html));
}
@Test
public void testDefault_Scipts_Disallowed() {
String html = "<script>doSomethingNefarious()</script>This was a dangerous script";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.EMPTY);
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo("This was a dangerous script"));
}
@Test
public void testCustom_Disabled() {
String html = "<img src=\"http://test.com/nastyimage.jpg\" />This is a bad image";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.builder()
.put("watcher.actions.email.html.sanitization.enabled", false)
.build());
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo(html));
}
@Test
public void testCustom_AllImage_Allowed() {
String html = "<img src=\"http://test.com/nastyimage.jpg\" />This is a bad image";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.builder()
.put("watcher.actions.email.html.sanitization.allow", "img:all")
.build());
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo(html));
}
@Test
public void testCustom_Tables_Disallowed() {
String html = "<table><tr><td>cell1</td><td>cell2</td></tr></table>";
HtmlSanitizer sanitizer = new HtmlSanitizer(Settings.builder()
.put("watcher.actions.email.html.sanitization.disallow", "_tables")
.build());
String sanitizedHtml = sanitizer.sanitize(html);
assertThat(sanitizedHtml, equalTo("cell1cell2"));
}
}

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.actions.logging;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;

View File

@ -5,9 +5,7 @@
*/
package org.elasticsearch.watcher.actions.throttler;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.joda.time.DateTime;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.watcher.actions.Action;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.actions.throttler;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.joda.time.DateTime;
import org.joda.time.PeriodType;
import org.elasticsearch.common.unit.TimeValue;

View File

@ -201,9 +201,9 @@ public class WebhookActionTests extends ElasticsearchTestCase {
public void testParser_Failure() throws Exception {
XContentBuilder builder = jsonBuilder().startObject();
if (randomBoolean()) {
builder.field(HttpRequestTemplate.Parser.HOST_FIELD.getPreferredName(), TEST_HOST);
builder.field(HttpRequest.Field.HOST.getPreferredName(), TEST_HOST);
} else {
builder.field(HttpRequestTemplate.Parser.PORT_FIELD.getPreferredName(), TEST_PORT);
builder.field(HttpRequest.Field.PORT.getPreferredName(), TEST_PORT);
}
builder.endObject();

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.actions.webhook;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.QueueDispatcher;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.condition.compare;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.condition.script;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
@ -20,6 +20,7 @@ import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
@ -34,7 +35,9 @@ import java.io.IOException;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.test.WatcherTestUtils.getScriptServiceProxy;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
*/
@ -146,25 +149,28 @@ public class ScriptConditionTests extends ElasticsearchTestCase {
fail("expected a condition validation exception trying to create an executable with an invalid language");
}
@Test(expected = ScriptConditionException.class)
public void testScriptCondition_throwException() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("assert false").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
condition.execute(ctx);
fail("expected a ScriptConditionException trying to execute a script that throws an exception");
ScriptCondition.Result result = condition.execute(ctx);
assertThat(result, notNullValue());
assertThat(result.status(), is(Condition.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.reason(), containsString("Assertion"));
}
@Test(expected = ScriptConditionException.class)
public void testScriptCondition_returnObject() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("return new Object()").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
condition.execute(ctx);
fail();
fail("expected a ScriptConditionException trying to execute a script that returns an object");
ScriptCondition.Result result = condition.execute(ctx);
assertThat(result, notNullValue());
assertThat(result.status(), is(Condition.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.reason(), containsString("ScriptConditionException"));
}
@Test

View File

@ -100,6 +100,7 @@ public class ExecutionServiceTests extends ElasticsearchTestCase {
// watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.status()).thenReturn(Transform.Result.Status.SUCCESS);
when(watchTransformResult.payload()).thenReturn(payload);
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
@ -223,6 +224,216 @@ public class ExecutionServiceTests extends ElasticsearchTestCase {
verify(action, never()).execute("_action", context, payload);
}
@Test
public void testExecute_FailedCondition() throws Exception {
WatchLockService.Lock lock = mock(WatchLockService.Lock.class);
when(watchLockService.acquire("_id")).thenReturn(lock);
Watch watch = mock(Watch.class);
when(watch.id()).thenReturn("_id");
when(watchStore.get("_id")).thenReturn(watch);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", clock.nowUTC(), clock.nowUTC());
WatchExecutionContext context = new TriggeredExecutionContext(watch, clock.nowUTC(), event, timeValueSeconds(5));
ExecutableCondition condition = mock(ExecutableCondition.class);
Condition.Result conditionResult = mock(Condition.Result.class);
when(conditionResult.status()).thenReturn(Condition.Result.Status.FAILURE);
when(conditionResult.reason()).thenReturn("_reason");
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
// watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.payload()).thenReturn(payload);
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
// action throttler
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(false);
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
// action level transform
Transform.Result actionTransformResult = mock(Transform.Result.class);
when(actionTransformResult.payload()).thenReturn(payload);
ExecutableTransform actionTransform = mock(ExecutableTransform.class);
when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
// the action
Action.Result actionResult = mock(Action.Result.class);
when(actionResult.type()).thenReturn("_action_type");
when(actionResult.status()).thenReturn(Action.Result.Status.SUCCESS);
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(clock.nowUTC())));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.transform()).thenReturn(watchTransform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
WatchRecord watchRecord = executionService.execute(context);
assertThat(watchRecord.result().inputResult(), is(inputResult));
assertThat(watchRecord.result().conditionResult(), is(conditionResult));
assertThat(watchRecord.result().transformResult(), nullValue());
assertThat(watchRecord.result().actionsResults(), notNullValue());
assertThat(watchRecord.result().actionsResults().count(), is(0));
verify(historyStore, times(1)).put(watchRecord);
verify(lock, times(1)).release();
verify(input, times(1)).execute(context);
verify(condition, times(1)).execute(context);
verify(watchTransform, never()).execute(context, payload);
verify(action, never()).execute("_action", context, payload);
}
@Test
public void testExecute_FailedWatchTransform() throws Exception {
WatchLockService.Lock lock = mock(WatchLockService.Lock.class);
when(watchLockService.acquire("_id")).thenReturn(lock);
Watch watch = mock(Watch.class);
when(watch.id()).thenReturn("_id");
when(watchStore.get("_id")).thenReturn(watch);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", clock.nowUTC(), clock.nowUTC());
WatchExecutionContext context = new TriggeredExecutionContext(watch, clock.nowUTC(), event, timeValueSeconds(5));
Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
// watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.status()).thenReturn(Transform.Result.Status.FAILURE);
when(watchTransformResult.reason()).thenReturn("_reason");
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
// action throttler
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(false);
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
// action level transform
Transform.Result actionTransformResult = mock(Transform.Result.class);
when(actionTransformResult.payload()).thenReturn(payload);
ExecutableTransform actionTransform = mock(ExecutableTransform.class);
when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
// the action
Action.Result actionResult = mock(Action.Result.class);
when(actionResult.type()).thenReturn("_action_type");
when(actionResult.status()).thenReturn(Action.Result.Status.SUCCESS);
ExecutableAction action = mock(ExecutableAction.class);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(clock.nowUTC())));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.transform()).thenReturn(watchTransform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
WatchRecord watchRecord = executionService.execute(context);
assertThat(watchRecord.result().inputResult(), is(inputResult));
assertThat(watchRecord.result().conditionResult(), is(conditionResult));
assertThat(watchRecord.result().transformResult(), is(watchTransformResult));
assertThat(watchRecord.result().actionsResults(), notNullValue());
assertThat(watchRecord.result().actionsResults().count(), is(0));
verify(historyStore, times(1)).put(watchRecord);
verify(lock, times(1)).release();
verify(input, times(1)).execute(context);
verify(condition, times(1)).execute(context);
verify(watchTransform, times(1)).execute(context, payload);
verify(action, never()).execute("_action", context, payload);
}
@Test
public void testExecute_FailedActionTransform() throws Exception {
WatchLockService.Lock lock = mock(WatchLockService.Lock.class);
when(watchLockService.acquire("_id")).thenReturn(lock);
Watch watch = mock(Watch.class);
when(watch.id()).thenReturn("_id");
when(watchStore.get("_id")).thenReturn(watch);
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", clock.nowUTC(), clock.nowUTC());
WatchExecutionContext context = new TriggeredExecutionContext(watch, clock.nowUTC(), event, timeValueSeconds(5));
Condition.Result conditionResult = AlwaysCondition.Result.INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class);
when(condition.execute(any(WatchExecutionContext.class))).thenReturn(conditionResult);
// watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.status()).thenReturn(Transform.Result.Status.SUCCESS);
when(watchTransformResult.payload()).thenReturn(payload);
ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
// action throttler
Throttler.Result throttleResult = mock(Throttler.Result.class);
when(throttleResult.throttle()).thenReturn(false);
ActionThrottler throttler = mock(ActionThrottler.class);
when(throttler.throttle("_action", context)).thenReturn(throttleResult);
// action level transform
Transform.Result actionTransformResult = mock(Transform.Result.class);
when(actionTransformResult.status()).thenReturn(Transform.Result.Status.FAILURE);
when(actionTransformResult.reason()).thenReturn("_reason");
ExecutableTransform actionTransform = mock(ExecutableTransform.class);
when(actionTransform.execute(context, payload)).thenReturn(actionTransformResult);
// the action
Action.Result actionResult = mock(Action.Result.class);
when(actionResult.type()).thenReturn("_action_type");
when(actionResult.status()).thenReturn(Action.Result.Status.SUCCESS);
ExecutableAction action = mock(ExecutableAction.class);
when(action.logger()).thenReturn(logger);
when(action.execute("_action", context, payload)).thenReturn(actionResult);
ActionWrapper actionWrapper = new ActionWrapper("_action", throttler, actionTransform, action);
ExecutableActions actions = new ExecutableActions(Arrays.asList(actionWrapper));
WatchStatus watchStatus = new WatchStatus(ImmutableMap.of("_action", new ActionStatus(clock.nowUTC())));
when(watch.input()).thenReturn(input);
when(watch.condition()).thenReturn(condition);
when(watch.transform()).thenReturn(watchTransform);
when(watch.actions()).thenReturn(actions);
when(watch.status()).thenReturn(watchStatus);
WatchRecord watchRecord = executionService.execute(context);
assertThat(watchRecord.result().inputResult(), is(inputResult));
assertThat(watchRecord.result().conditionResult(), is(conditionResult));
assertThat(watchRecord.result().transformResult(), is(watchTransformResult));
assertThat(watchRecord.result().actionsResults(), notNullValue());
assertThat(watchRecord.result().actionsResults().count(), is(1));
assertThat(watchRecord.result().actionsResults().get("_action").transform(), is(actionTransformResult));
assertThat(watchRecord.result().actionsResults().get("_action").action().status(), is(Action.Result.Status.FAILURE));
verify(historyStore, times(1)).put(watchRecord);
verify(lock, times(1)).release();
verify(input, times(1)).execute(context);
verify(condition, times(1)).execute(context);
verify(watchTransform, times(1)).execute(context, payload);
// the action level transform is executed before the action itself
verify(action, never()).execute("_action", context, payload);
}
@Test
public void testExecuteInner() throws Exception {
DateTime now = DateTime.now(DateTimeZone.UTC);

View File

@ -5,9 +5,8 @@
*/
package org.elasticsearch.watcher.execution;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.apache.lucene.util.LuceneTestCase.Slow;
import org.joda.time.DateTime;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.watcher.WatcherException;
@ -24,6 +23,9 @@ import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.xcontent.ObjectPath;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.transport.actions.delete.DeleteWatchResponse;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchRequest;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchRequestBuilder;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchResponse;
import org.elasticsearch.watcher.transport.actions.get.GetWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
@ -62,7 +64,6 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
@Test
public void testExecuteWatch() throws Exception {
ensureWatcherStarted();
boolean ignoreCondition = randomBoolean();
boolean recordExecution = randomBoolean();
boolean conditionAlwaysTrue = randomBoolean();
@ -146,9 +147,73 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
}
}
@Test
public void testExecutionWithInlineWatch() throws Exception {
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(alwaysCondition())
.addAction("log", loggingAction("foobar"));
ExecuteWatchRequestBuilder builder = watcherClient().prepareExecuteWatch()
.setWatchSource(watchBuilder);
if (randomBoolean()) {
builder.setRecordExecution(false);
}
if (randomBoolean()) {
builder.setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)));
}
ExecuteWatchResponse executeWatchResponse = builder.get();
assertThat(executeWatchResponse.getRecordId(), startsWith(ExecuteWatchRequest.INLINE_WATCH_ID));
assertThat(executeWatchResponse.getRecordSource().getValue("watch_id").toString(), equalTo(ExecuteWatchRequest.INLINE_WATCH_ID));
assertThat(executeWatchResponse.getRecordSource().getValue("state").toString(), equalTo("executed"));
assertThat(executeWatchResponse.getRecordSource().getValue("trigger_event.type").toString(), equalTo("manual"));
}
@Test
public void testExecutionWithInlineWatch_withRecordExecutionEnabled() throws Exception {
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(alwaysCondition())
.addAction("log", loggingAction("foobar"));
try {
watcherClient().prepareExecuteWatch()
.setWatchSource(watchBuilder)
.setRecordExecution(true)
.setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))
.get();
fail();
} catch (ActionRequestValidationException e) {
assertThat(e.getMessage(), containsString("the execution of an inline watch cannot be recorded"));
}
}
@Test
public void testExecutionWithInlineWatch_withWatchId() throws Exception {
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(alwaysCondition())
.addAction("log", loggingAction("foobar"));
try {
watcherClient().prepareExecuteWatch()
.setId("_id")
.setWatchSource(watchBuilder)
.setRecordExecution(false)
.setTriggerEvent(new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC)))
.get();
fail();
} catch (ActionRequestValidationException e) {
assertThat(e.getMessage(), containsString("a watch execution request must either have a watch id or an inline watch source but not both"));
}
}
@Test
public void testDifferentAlternativeInputs() throws Exception {
ensureWatcherStarted();
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.addAction("log", loggingAction("foobar"));
@ -187,8 +252,6 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
@Test
public void testExecutionRequestDefaults() throws Exception {
ensureWatcherStarted();
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.input.http;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.MapBuilder;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.support;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.support;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.support;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;

View File

@ -41,7 +41,7 @@ import static org.hamcrest.core.Is.is;
/**
*/
public class HttpClientTest extends ElasticsearchTestCase {
public class HttpClientTests extends ElasticsearchTestCase {
private MockWebServer webServer;
private HttpClient httpClient;
@ -163,7 +163,7 @@ public class HttpClientTest extends ElasticsearchTestCase {
// We can't use the client created above for the server since it is only a truststore
webServer.useHttps(new HttpClient(Settings.builder()
.put(HttpClient.SETTINGS_SSL_KEYSTORE, PathUtils.get(HttpClientTest.class.getResource("/org/elasticsearch/shield/keystore/testnode.jks").toURI()))
.put(HttpClient.SETTINGS_SSL_KEYSTORE, getDataPath("/org/elasticsearch/shield/keystore/testnode.jks"))
.put(HttpClient.SETTINGS_SSL_KEYSTORE_PASSWORD, "testnode")
.build(), authRegistry, environment)
.start()

View File

@ -0,0 +1,106 @@
/*
* 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.http;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.junit.Test;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.mockito.Mockito.mock;
/**
*/
public class HttpConnectionTimeoutTests extends ElasticsearchTestCase {
// setting an unroutable IP to simulate a connection timeout
private static final String UNROUTABLE_IP = "192.168.255.255";
@Test @Slow
public void testDefaultTimeout() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.EMPTY, mock(HttpAuthRegistry.class), environment).start();
HttpRequest request = HttpRequest.builder(UNROUTABLE_IP, 12345)
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected timeout exception");
} catch (ElasticsearchTimeoutException ete) {
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 10, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(8L));
assertThat(timeout.seconds(), lessThan(12L));
// expected
}
}
@Test @Slow
public void testDefaultTimeout_Custom() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.builder()
.put("watcher.http.default_connection_timeout", "5s")
.build()
, mock(HttpAuthRegistry.class), environment).start();
HttpRequest request = HttpRequest.builder(UNROUTABLE_IP, 12345)
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected timeout exception");
} catch (ElasticsearchTimeoutException ete) {
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 7, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(3L));
assertThat(timeout.seconds(), lessThan(7L));
// expected
}
}
@Test @Slow
public void testTimeout_CustomPerRequest() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.builder()
.put("watcher.http.default_connection_timeout", "10s")
.build()
, mock(HttpAuthRegistry.class), environment).start();
HttpRequest request = HttpRequest.builder(UNROUTABLE_IP, 12345)
.connectionTimeout(TimeValue.timeValueSeconds(5))
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected timeout exception");
} catch (ElasticsearchTimeoutException ete) {
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 7, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(3L));
assertThat(timeout.seconds(), lessThan(7L));
// expected
}
}
}

View File

@ -0,0 +1,157 @@
/*
* 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.http;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.secret.SecretService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.BindException;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.mockito.Mockito.mock;
/**
*/
public class HttpReadTimeoutTests extends ElasticsearchTestCase {
private MockWebServer webServer;
private SecretService secretService;
private int webPort;
@Before
public void init() throws Exception {
secretService = new SecretService.PlainText();
for (webPort = 9200; webPort < 9300; webPort++) {
try {
webServer = new MockWebServer();
webServer.start(webPort);
return;
} catch (BindException be) {
logger.warn("port [{}] was already in use trying next port", webPort);
}
}
throw new ElasticsearchException("unable to find open port between 9200 and 9300");
}
@After
public void after() throws Exception {
webServer.shutdown();
}
@Test
public void testDefaultTimeout() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.EMPTY, mock(HttpAuthRegistry.class), environment).start();
// we're not going to enqueue an response... so the server will just hang
HttpRequest request = HttpRequest.builder("localhost", webPort)
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected read timeout after 10 seconds (default)");
} catch (ElasticsearchTimeoutException ete) {
// expected
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 10, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(8L));
assertThat(timeout.seconds(), lessThan(12L));
// lets enqueue a response to relese the server.
webServer.enqueue(new MockResponse());
}
}
@Test
public void testDefaultTimeout_Custom() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.builder()
.put("watcher.http.default_read_timeout", "5s")
.build()
, mock(HttpAuthRegistry.class), environment).start();
// we're not going to enqueue an response... so the server will just hang
HttpRequest request = HttpRequest.builder("localhost", webPort)
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected read timeout after 5 seconds (default)");
} catch (ElasticsearchTimeoutException ete) {
// expected
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 5, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(3L));
assertThat(timeout.seconds(), lessThan(7L));
// lets enqueue a response to relese the server.
webServer.enqueue(new MockResponse());
}
}
@Test
public void testTimeout_CustomPerRequest() throws Exception {
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
HttpClient httpClient = new HttpClient(Settings.builder()
.put("watcher.http.default_read_timeout", "10s")
.build()
, mock(HttpAuthRegistry.class), environment).start();
// we're not going to enqueue an response... so the server will just hang
HttpRequest request = HttpRequest.builder("localhost", webPort)
.readTimeout(TimeValue.timeValueSeconds(5))
.method(HttpMethod.POST)
.path("/" + randomAsciiOfLength(5))
.build();
long start = System.nanoTime();
try {
httpClient.execute(request);
fail("expected read timeout after 5 seconds (default)");
} catch (ElasticsearchTimeoutException ete) {
// expected
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
logger.info("http connection timed out after {}", timeout.format());
// it's supposed to be 5, but we'll give it an error margin of 2 seconds
assertThat(timeout.seconds(), greaterThan(3L));
assertThat(timeout.seconds(), lessThan(7L));
// lets enqueue a response to relese the server.
webServer.enqueue(new MockResponse());
}
}
}

View File

@ -5,8 +5,8 @@
*/
package org.elasticsearch.watcher.support.http;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
@ -79,6 +79,14 @@ public class HttpRequestTemplateTests extends ElasticsearchTestCase {
if (randomBoolean()) {
builder.putHeader("_key", Template.inline("_value"));
}
long connectionTimeout = randomBoolean() ? 0 : randomIntBetween(5, 10);
if (connectionTimeout > 0) {
builder.connectionTimeout(TimeValue.timeValueSeconds(connectionTimeout));
}
long readTimeout = randomBoolean() ? 0 : randomIntBetween(5, 10);
if (readTimeout > 0) {
builder.readTimeout(TimeValue.timeValueSeconds(readTimeout));
}
HttpRequestTemplate template = builder.build();

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.support.http;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.support.template;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.Settings;
@ -86,7 +86,7 @@ public class TemplateTests extends ElasticsearchTestCase {
assertThat(engine.render(template, model), is("rendered_text"));
}
@Test @Repeat(iterations = 5)
@Test
public void testParser() throws Exception {
ScriptType type = randomScriptType();
Template template = templateBuilder(type, "_template").params(ImmutableMap.<String, Object>of("param_key", "param_val")).build();

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.support.template.xmustache;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchTestCase;

View File

@ -6,7 +6,7 @@
package org.elasticsearch.watcher.support.template.xmustache;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableList;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.support.xcontent;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.test.ElasticsearchTestCase;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.support.xcontent;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;

View File

@ -11,7 +11,6 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.Strings;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptContextRegistry;
import org.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
@ -22,7 +21,6 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
@ -35,10 +33,7 @@ import org.elasticsearch.watcher.actions.ActionWrapper;
import org.elasticsearch.watcher.actions.ExecutableActions;
import org.elasticsearch.watcher.actions.email.EmailAction;
import org.elasticsearch.watcher.actions.email.ExecutableEmailAction;
import org.elasticsearch.watcher.actions.email.service.Authentication;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate;
import org.elasticsearch.watcher.actions.email.service.Profile;
import org.elasticsearch.watcher.actions.email.service.*;
import org.elasticsearch.watcher.actions.webhook.ExecutableWebhookAction;
import org.elasticsearch.watcher.actions.webhook.WebhookAction;
import org.elasticsearch.watcher.condition.script.ExecutableScriptCondition;
@ -200,7 +195,7 @@ public final class WatcherTestUtils {
Authentication auth = new Authentication("testname", new Secret("testpassword".toCharArray()));
EmailAction action = new EmailAction(email, "testaccount", auth, Profile.STANDARD, null);
ExecutableEmailAction executale = new ExecutableEmailAction(action, logger, emailService, templateEngine);
ExecutableEmailAction executale = new ExecutableEmailAction(action, logger, emailService, templateEngine, new HtmlSanitizer(Settings.EMPTY));
actions.add(new ActionWrapper("_email", executale));

View File

@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock;
public class ChainTransformTests extends ElasticsearchTestCase {
@Test
public void testApply() throws Exception {
public void testExecute() throws Exception {
ChainTransform transform = new ChainTransform(ImmutableList.<Transform>of(
new NamedExecutableTransform.Transform("name1"),
new NamedExecutableTransform.Transform("name2"),
@ -51,7 +51,21 @@ public class ChainTransformTests extends ElasticsearchTestCase {
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Payload payload = new Payload.Simple(new HashMap<String, Object>());
Transform.Result result = executable.execute(ctx, payload);
ChainTransform.Result result = executable.execute(ctx, payload);
assertThat(result.status(), is(Transform.Result.Status.SUCCESS));
assertThat(result.results(), hasSize(3));
assertThat(result.results().get(0), instanceOf(NamedExecutableTransform.Result.class));
assertThat(result.results().get(0).status(), is(Transform.Result.Status.SUCCESS));
assertThat((List<String>) result.results().get(0).payload().data().get("names"), hasSize(1));
assertThat((List<String>) result.results().get(0).payload().data().get("names"), contains("name1"));
assertThat(result.results().get(1), instanceOf(NamedExecutableTransform.Result.class));
assertThat(result.results().get(1).status(), is(Transform.Result.Status.SUCCESS));
assertThat((List<String>) result.results().get(1).payload().data().get("names"), hasSize(2));
assertThat((List<String>) result.results().get(1).payload().data().get("names"), contains("name1", "name2"));
assertThat(result.results().get(2), instanceOf(NamedExecutableTransform.Result.class));
assertThat(result.results().get(2).status(), is(Transform.Result.Status.SUCCESS));
assertThat((List<String>) result.results().get(2).payload().data().get("names"), hasSize(3));
assertThat((List<String>) result.results().get(2).payload().data().get("names"), contains("name1", "name2", "name3"));
Map<String, Object> data = result.payload().data();
assertThat(data, notNullValue());
@ -62,6 +76,39 @@ public class ChainTransformTests extends ElasticsearchTestCase {
assertThat(names, contains("name1", "name2", "name3"));
}
@Test
public void testExecute_Failure() throws Exception {
ChainTransform transform = new ChainTransform(ImmutableList.of(
new NamedExecutableTransform.Transform("name1"),
new NamedExecutableTransform.Transform("name2"),
new FailingExecutableTransform.Transform()
));
ExecutableChainTransform executable = new ExecutableChainTransform(transform, logger, ImmutableList.<ExecutableTransform>of(
new NamedExecutableTransform("name1"),
new NamedExecutableTransform("name2"),
new FailingExecutableTransform(logger)));
WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Payload payload = new Payload.Simple(new HashMap<String, Object>());
ChainTransform.Result result = executable.execute(ctx, payload);
assertThat(result.status(), is(Transform.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.results(), hasSize(3));
assertThat(result.results().get(0), instanceOf(NamedExecutableTransform.Result.class));
assertThat(result.results().get(0).status(), is(Transform.Result.Status.SUCCESS));
assertThat((List<String>) result.results().get(0).payload().data().get("names"), hasSize(1));
assertThat((List<String>) result.results().get(0).payload().data().get("names"), contains("name1"));
assertThat(result.results().get(1), instanceOf(NamedExecutableTransform.Result.class));
assertThat(result.results().get(1).status(), is(Transform.Result.Status.SUCCESS));
assertThat((List<String>) result.results().get(1).payload().data().get("names"), hasSize(2));
assertThat((List<String>) result.results().get(1).payload().data().get("names"), contains("name1", "name2"));
assertThat(result.results().get(2), instanceOf(FailingExecutableTransform.Result.class));
assertThat(result.results().get(2).status(), is(Transform.Result.Status.FAILURE));
assertThat(result.results().get(2).reason(), containsString("_error"));
}
@Test
public void testParser() throws Exception {
Map<String, TransformFactory> factories = ImmutableMap.<String, TransformFactory>builder()
@ -103,14 +150,16 @@ public class ChainTransformTests extends ElasticsearchTestCase {
}
@Override
public Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
Map<String, Object> data = new HashMap<>(payload.data());
List<String> names = (List<String>) data.get("names");
public Result execute(WatchExecutionContext ctx, Payload payload) {
List<String> names = (List<String>) payload.data().get("names");
if (names == null) {
names = new ArrayList<>();
data.put("names", names);
} else {
names = new ArrayList<>(names);
}
names.add(transform.name);
Map<String, Object> data = new HashMap<>();
data.put("names", names);
return new Result("named", new Payload.Simple(data));
}
@ -178,4 +227,68 @@ public class ChainTransformTests extends ElasticsearchTestCase {
}
}
}
private static class FailingExecutableTransform extends ExecutableTransform<FailingExecutableTransform.Transform, FailingExecutableTransform.Result> {
private static final String TYPE = "throwing";
public FailingExecutableTransform(ESLogger logger) {
super(new Transform(), logger);
}
@Override
public Result execute(WatchExecutionContext ctx, Payload payload) {
return new Result(TYPE);
}
public static class Transform implements org.elasticsearch.watcher.transform.Transform {
@Override
public String type() {
return TYPE;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endArray();
}
}
public static class Result extends Transform.Result {
public Result(String type) {
super(type, new Exception("_error"));
}
@Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
return builder;
}
}
public static class Factory extends TransformFactory<Transform, Result, FailingExecutableTransform> {
public Factory(ESLogger transformLogger) {
super(transformLogger);
}
@Override
public String type() {
return TYPE;
}
@Override
public Transform parseTransform(String watchId, XContentParser parser) throws IOException {
assert parser.currentToken() == XContentParser.Token.START_OBJECT;
XContentParser.Token token = parser.nextToken();
assert token == XContentParser.Token.END_OBJECT;
return new Transform();
}
@Override
public FailingExecutableTransform createExecutable(Transform transform) {
return new FailingExecutableTransform(transformLogger);
}
}
}
}

View File

@ -7,7 +7,7 @@ package org.elasticsearch.watcher.transform.script;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableList;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableSet;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -58,7 +58,7 @@ public class ScriptTransformTests extends ElasticsearchTestCase {
@Test
public void testApply_MapValue() throws Exception {
public void testExecute_MapValue() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptType type = randomFrom(ScriptType.values());
Map<String, Object> params = Collections.emptyMap();
@ -84,11 +84,39 @@ public class ScriptTransformTests extends ElasticsearchTestCase {
Transform.Result result = transform.execute(ctx, payload);
assertThat(result, notNullValue());
assertThat(result.type(), is(ScriptTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.SUCCESS));
assertThat(result.payload().data(), equalTo(transformed));
}
@Test
public void testApply_NonMapValue() throws Exception {
public void testExecute_MapValue_Failure() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptType type = randomFrom(ScriptType.values());
Map<String, Object> params = Collections.emptyMap();
Script script = scriptBuilder(type, "_script").lang("_lang").params(params).build();
CompiledScript compiledScript = mock(CompiledScript.class);
when(service.compile(script)).thenReturn(compiledScript);
ExecutableScriptTransform transform = new ExecutableScriptTransform(new ScriptTransform(script), logger, service);
WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD);
Payload payload = simplePayload("key", "value");
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
ExecutableScript executable = mock(ExecutableScript.class);
when(executable.run()).thenThrow(new RuntimeException("_error"));
when(service.executable(compiledScript, model)).thenReturn(executable);
Transform.Result result = transform.execute(ctx, payload);
assertThat(result, notNullValue());
assertThat(result.type(), is(ScriptTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.FAILURE));
assertThat(result.reason(), containsString("_error"));
}
@Test
public void testExecute_NonMapValue() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptType type = randomFrom(ScriptType.values());

View File

@ -114,7 +114,7 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
}
@Test
public void testApply() throws Exception {
public void testExecute() throws Exception {
index("idx", "type", "1");
ensureGreen("idx");
@ -133,6 +133,7 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
Transform.Result result = transform.execute(ctx, EMPTY_PAYLOAD);
assertThat(result, notNullValue());
assertThat(result.type(), is(SearchTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.SUCCESS));
SearchResponse response = client().search(request).get();
Payload expectedPayload = new Payload.XContent(response);
@ -149,7 +150,33 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
}
@Test
public void testApply_MustacheTemplate() throws Exception {
public void testExecute_Failure() throws Exception {
index("idx", "type", "1");
ensureGreen("idx");
refresh();
// create a bad request
SearchRequest request = Requests.searchRequest("idx").source(jsonBuilder().startObject()
.startObject("query")
.startObject("_unknown_query_").endObject()
.endObject()
.endObject());
SearchTransform searchTransform = TransformBuilders.searchTransform(request).build();
ExecutableSearchTransform transform = new ExecutableSearchTransform(searchTransform, logger, ClientProxy.of(client()));
WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD);
SearchTransform.Result result = transform.execute(ctx, EMPTY_PAYLOAD);
assertThat(result, notNullValue());
assertThat(result.type(), is(SearchTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.executedRequest().templateSource().toUtf8(), containsString("_unknown_query_"));
}
@Test
public void testExecute_MustacheTemplate() throws Exception {
// The rational behind this test:
//

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.transport.action.ack;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.transport.action.execute;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.ActionRequestValidationException;
import com.google.common.collect.ImmutableMap;
import org.joda.time.DateTime;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.transport.action.put;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.client.WatchSourceBuilder;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.primitives.Ints;
import org.elasticsearch.common.xcontent.XContentBuilder;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.Collections2;
import com.google.common.primitives.Ints;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.primitives.Ints;
import org.elasticsearch.common.xcontent.XContentBuilder;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.base.Joiner;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.primitives.Ints;

View File

@ -5,7 +5,7 @@
*/
package org.elasticsearch.watcher.trigger.schedule;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.base.Joiner;
import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.primitives.Ints;

View File

@ -5,7 +5,6 @@
*/
package org.elasticsearch.watcher.trigger.schedule.tool;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.junit.Test;

View File

@ -25,6 +25,7 @@ import org.elasticsearch.watcher.actions.email.EmailActionFactory;
import org.elasticsearch.watcher.actions.email.ExecutableEmailAction;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate;
import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer;
import org.elasticsearch.watcher.actions.email.service.Profile;
import org.elasticsearch.watcher.actions.index.ExecutableIndexAction;
import org.elasticsearch.watcher.actions.index.IndexAction;
@ -113,6 +114,7 @@ public class WatchTests extends ElasticsearchTestCase {
private HttpClient httpClient;
private EmailService emailService;
private TemplateEngine templateEngine;
private HtmlSanitizer htmlSanitizer;
private HttpAuthRegistry authRegistry;
private SecretService secretService;
private LicenseService licenseService;
@ -126,6 +128,7 @@ public class WatchTests extends ElasticsearchTestCase {
httpClient = mock(HttpClient.class);
emailService = mock(EmailService.class);
templateEngine = mock(TemplateEngine.class);
htmlSanitizer = mock(HtmlSanitizer.class);
secretService = mock(SecretService.class);
licenseService = mock(LicenseService.class);
authRegistry = new HttpAuthRegistry(ImmutableMap.of("basic", (HttpAuthFactory) new BasicAuthFactory(secretService)));
@ -383,7 +386,7 @@ public class WatchTests extends ElasticsearchTestCase {
if (randomBoolean()) {
ExecutableTransform transform = randomTransform();
EmailAction action = new EmailAction(EmailTemplate.builder().build(), null, null, Profile.STANDARD, randomFrom(DataAttachment.JSON, DataAttachment.YAML, null));
list.add(new ActionWrapper("_email_" + randomAsciiOfLength(8), randomThrottler(), transform, new ExecutableEmailAction(action, logger, emailService, templateEngine)));
list.add(new ActionWrapper("_email_" + randomAsciiOfLength(8), randomThrottler(), transform, new ExecutableEmailAction(action, logger, emailService, templateEngine, htmlSanitizer)));
}
if (randomBoolean()) {
IndexAction aciton = new IndexAction("_index", "_type", null);
@ -405,7 +408,7 @@ public class WatchTests extends ElasticsearchTestCase {
for (ActionWrapper action : actions) {
switch (action.action().type()) {
case EmailAction.TYPE:
parsers.put(EmailAction.TYPE, new EmailActionFactory(settings, emailService, templateEngine));
parsers.put(EmailAction.TYPE, new EmailActionFactory(settings, emailService, templateEngine, htmlSanitizer));
break;
case IndexAction.TYPE:
parsers.put(IndexAction.TYPE, new IndexActionFactory(settings, client));