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" ], "methods": [ "PUT", "POST" ],
"url": { "url": {
"path": "/_watcher/watch/{id}/_execute", "path": "/_watcher/watch/{id}/_execute",
"paths": [ "/_watcher/watch/{id}/_execute" ], "paths": [ "/_watcher/watch/{id}/_execute", "/_watcher/watch/_execute" ],
"parts": { "parts": {
"id": { "id": {
"type" : "string", "type" : "string",
"description" : "Watch ID", "description" : "Watch ID"
"required" : true
} }
}, },
"params": { "params": {

View File

@ -77,8 +77,12 @@
- match: { "watch_record.trigger_event.type": "manual" } - 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.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.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.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.id" : "email_admin" }
- match: { "watch_record.result.actions.0.status" : "simulated" } - match: { "watch_record.result.actions.0.status" : "simulated" }
- match: { "watch_record.result.actions.0.type" : "email" } - 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.watch_id": "my_logging_watch" }
- match: { "watch_record.state": "executed" } - 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.condition.met": true }
- match: { "watch_record.result.actions.0.id" : "logging" } - match: { "watch_record.result.actions.0.id" : "logging" }
- match: { "watch_record.result.actions.0.type" : "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.EmailAction;
import org.elasticsearch.watcher.actions.email.EmailActionFactory; import org.elasticsearch.watcher.actions.email.EmailActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService; 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.email.service.InternalEmailService;
import org.elasticsearch.watcher.actions.index.IndexAction; import org.elasticsearch.watcher.actions.index.IndexAction;
import org.elasticsearch.watcher.actions.index.IndexActionFactory; import org.elasticsearch.watcher.actions.index.IndexActionFactory;
@ -54,6 +55,7 @@ public class ActionModule extends AbstractModule {
} }
bind(ActionRegistry.class).asEagerSingleton(); bind(ActionRegistry.class).asEagerSingleton();
bind(HtmlSanitizer.class).asEagerSingleton();
bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton(); bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton();
} }

View File

@ -78,9 +78,13 @@ public class ActionWrapper implements ToXContent {
if (transform != null) { if (transform != null) {
try { try {
transformResult = transform.execute(ctx, payload); 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(); payload = transformResult.payload();
} catch (Exception e) { } 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))); 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); Action.Result actionResult = action.execute(id, ctx, payload);
return new ActionWrapper.Result(id, transformResult, actionResult); return new ActionWrapper.Result(id, transformResult, actionResult);
} catch (Exception e) { } 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))); 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; 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; public abstract Action.Result execute(String actionId, WatchExecutionContext context, Payload payload) throws Exception;
@Override @Override

View File

@ -113,8 +113,8 @@ public class EmailAction implements Action {
return builder.endObject(); return builder.endObject();
} }
public static EmailAction parse(String watchId, String actionId, XContentParser parser, boolean sanitizeHtmlBody) throws IOException { public static EmailAction parse(String watchId, String actionId, XContentParser parser) throws IOException {
EmailTemplate.Parser emailParser = new EmailTemplate.Parser(sanitizeHtmlBody); EmailTemplate.Parser emailParser = new EmailTemplate.Parser();
String account = null; String account = null;
String user = null; String user = null;
Secret password = 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.logging.Loggers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionFactory; import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService; 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 org.elasticsearch.watcher.support.template.TemplateEngine;
import java.io.IOException; import java.io.IOException;
@ -24,16 +23,14 @@ public class EmailActionFactory extends ActionFactory<EmailAction, ExecutableEma
private final EmailService emailService; private final EmailService emailService;
private final TemplateEngine templateEngine; private final TemplateEngine templateEngine;
private final boolean sanitizeHtmlBodyOfEmails; private final HtmlSanitizer htmlSanitizer;
private static String SANITIZE_HTML_SETTING = "watcher.actions.email.sanitize_html";
@Inject @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)); super(Loggers.getLogger(ExecutableEmailAction.class, settings));
this.emailService = emailService; this.emailService = emailService;
this.templateEngine = templateEngine; this.templateEngine = templateEngine;
sanitizeHtmlBodyOfEmails = settings.getAsBoolean(SANITIZE_HTML_SETTING, true); this.htmlSanitizer = htmlSanitizer;
} }
@Override @Override
@ -43,11 +40,11 @@ public class EmailActionFactory extends ActionFactory<EmailAction, ExecutableEma
@Override @Override
public EmailAction parseAction(String watchId, String actionId, XContentParser parser) throws IOException { 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 @Override
public ExecutableEmailAction createExecutable(EmailAction action) { 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.Attachment;
import org.elasticsearch.watcher.actions.email.service.Email; import org.elasticsearch.watcher.actions.email.service.Email;
import org.elasticsearch.watcher.actions.email.service.EmailService; 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.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables; import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.template.TemplateEngine; import org.elasticsearch.watcher.support.template.TemplateEngine;
@ -25,11 +26,13 @@ public class ExecutableEmailAction extends ExecutableAction<EmailAction> {
final EmailService emailService; final EmailService emailService;
final TemplateEngine templateEngine; 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); super(action, logger);
this.emailService = emailService; this.emailService = emailService;
this.templateEngine = templateEngine; this.templateEngine = templateEngine;
this.htmlSanitizer = htmlSanitizer;
} }
public Action.Result execute(String actionId, WatchExecutionContext ctx, Payload payload) throws Exception { 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); 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()); email.id(ctx.id().value());
if (ctx.simulateAction(actionId)) { 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.WatcherException;
import org.elasticsearch.watcher.support.template.Template; import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.support.template.TemplateEngine; import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.owasp.html.*;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.mail.internet.AddressException; import javax.mail.internet.AddressException;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -32,11 +30,10 @@ public class EmailTemplate implements ToXContent {
final Template subject; final Template subject;
final Template textBody; final Template textBody;
final Template htmlBody; final Template htmlBody;
final boolean sanitizeHtmlBody;
public EmailTemplate(Template from, Template[] replyTo, Template priority, Template[] to, public EmailTemplate(Template from, Template[] replyTo, Template priority, Template[] to,
Template[] cc, Template[] bcc, Template subject, Template textBody, Template[] cc, Template[] bcc, Template subject, Template textBody,
Template htmlBody, boolean sanitizeHtmlBody) { Template htmlBody) {
this.from = from; this.from = from;
this.replyTo = replyTo; this.replyTo = replyTo;
this.priority = priority; this.priority = priority;
@ -46,7 +43,6 @@ public class EmailTemplate implements ToXContent {
this.subject = subject; this.subject = subject;
this.textBody = textBody; this.textBody = textBody;
this.htmlBody = htmlBody; this.htmlBody = htmlBody;
this.sanitizeHtmlBody = sanitizeHtmlBody;
} }
public Template from() { public Template from() {
@ -85,11 +81,7 @@ public class EmailTemplate implements ToXContent {
return htmlBody; return htmlBody;
} }
public boolean sanitizeHtmlBody() { public Email.Builder render(TemplateEngine engine, Map<String, Object> model, HtmlSanitizer htmlSanitizer, Map<String, Attachment> attachments) throws AddressException {
return sanitizeHtmlBody;
}
public Email.Builder render(TemplateEngine engine, Map<String, Object> model, Map<String, Attachment> attachments) throws AddressException {
Email.Builder builder = Email.builder(); Email.Builder builder = Email.builder();
if (from != null) { if (from != null) {
builder.from(engine.render(from, model)); builder.from(engine.render(from, model));
@ -126,9 +118,7 @@ public class EmailTemplate implements ToXContent {
} }
if (htmlBody != null) { if (htmlBody != null) {
String renderedHtml = engine.render(htmlBody, model); String renderedHtml = engine.render(htmlBody, model);
if (sanitizeHtmlBody && htmlBody != null) { renderedHtml = htmlSanitizer.sanitize(renderedHtml);
renderedHtml = sanitizeHtml(renderedHtml, attachments);
}
builder.htmlBody(renderedHtml); builder.htmlBody(renderedHtml);
} }
return builder; return builder;
@ -236,7 +226,6 @@ public class EmailTemplate implements ToXContent {
private Template subject; private Template subject;
private Template textBody; private Template textBody;
private Template htmlBody; private Template htmlBody;
private boolean sanitizeHtmlBody = true;
private Builder() { private Builder() {
} }
@ -377,81 +366,27 @@ public class EmailTemplate implements ToXContent {
return this; return this;
} }
public Builder htmlBody(String html, boolean sanitizeHtmlBody) { public Builder htmlBody(String html) {
return htmlBody(Template.defaultType(html), sanitizeHtmlBody); return htmlBody(Template.defaultType(html));
} }
public Builder htmlBody(Template.Builder html, boolean sanitizeHtmlBody) { public Builder htmlBody(Template.Builder html) {
return htmlBody(html.build(), sanitizeHtmlBody); return htmlBody(html.build());
} }
public Builder htmlBody(Template html, boolean sanitizeHtmlBody) { public Builder htmlBody(Template html) {
this.htmlBody = html; this.htmlBody = html;
this.sanitizeHtmlBody = sanitizeHtmlBody;
return this; return this;
} }
public EmailTemplate build() { public EmailTemplate build() {
return new EmailTemplate(from, replyTo, priority, to, cc, bcc, subject, textBody, htmlBody, sanitizeHtmlBody); return new EmailTemplate(from, replyTo, priority, to, cc, bcc, subject, textBody, htmlBody);
}
}
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;
} }
} }
public static class Parser { public static class Parser {
private final EmailTemplate.Builder builder = builder(); 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 { public boolean handle(String fieldName, XContentParser parser) throws IOException {
if (Email.Field.FROM.match(fieldName)) { if (Email.Field.FROM.match(fieldName)) {
@ -514,7 +449,7 @@ public class EmailTemplate implements ToXContent {
} else if (Email.Field.BODY_TEXT.match(currentFieldName)) { } else if (Email.Field.BODY_TEXT.match(currentFieldName)) {
builder.textBody(Template.parse(parser)); builder.textBody(Template.parse(parser));
} else if (Email.Field.BODY_HTML.match(currentFieldName)) { } else if (Email.Field.BODY_HTML.match(currentFieldName)) {
builder.htmlBody(Template.parse(parser), sanitizeHtmlBody); builder.htmlBody(Template.parse(parser));
} else { } else {
throw new ParseException("could not parse email template. unknown field [{}.{}] field", fieldName, currentFieldName); 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; package org.elasticsearch.watcher.condition;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
/** /**
* *
@ -20,25 +22,64 @@ public interface Condition extends ToXContent {
abstract class Result implements ToXContent { abstract class Result implements ToXContent {
public enum Status {
SUCCESS, FAILURE
}
protected final String type; protected final String type;
protected final Status status;
private final String reason;
protected final boolean met; protected final boolean met;
public Result(String type, boolean met) { public Result(String type, boolean met) {
this.status = Status.SUCCESS;
this.type = type; this.type = type;
this.met = met; 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() { public String type() {
return 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 @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Field.TYPE.getPreferredName(), type); 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); typeXContent(builder, params);
return builder.endObject(); return builder.endObject();
} }
@ -53,6 +94,8 @@ public interface Condition extends ToXContent {
interface Field { interface Field {
ParseField TYPE = new ParseField("type"); ParseField TYPE = new ParseField("type");
ParseField STATUS = new ParseField("status");
ParseField MET = new ParseField("met"); 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 * Executes this condition
*/ */
public abstract R execute(WatchExecutionContext ctx) throws IOException; public abstract R execute(WatchExecutionContext ctx);
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {

View File

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

View File

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

View File

@ -7,6 +7,7 @@ package org.elasticsearch.watcher.condition.compare;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.email.DataAttachment;
import org.elasticsearch.watcher.condition.ExecutableCondition; import org.elasticsearch.watcher.condition.ExecutableCondition;
import org.elasticsearch.watcher.execution.WatchExecutionContext; import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables; import org.elasticsearch.watcher.support.Variables;
@ -39,10 +40,21 @@ public class ExecutableCompareCondition extends ExecutableCondition<CompareCondi
} }
@Override @Override
public CompareCondition.Result execute(WatchExecutionContext ctx) throws IOException { public CompareCondition.Result execute(WatchExecutionContext ctx) {
Map<String, Object> model = Variables.createCtxModel(ctx, ctx.payload());
Map<String, Object> resolvedValues = new HashMap<>(); 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(); Object configuredValue = condition.getValue();

View File

@ -21,7 +21,7 @@ public class ExecutableNeverCondition extends ExecutableCondition<NeverCondition
} }
@Override @Override
public NeverCondition.Result execute(WatchExecutionContext ctx) throws IOException { public NeverCondition.Result execute(WatchExecutionContext ctx) {
return NeverCondition.Result.INSTANCE; 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.Variables;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy; import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import java.io.IOException;
import java.util.Map; import java.util.Map;
/** /**
@ -35,20 +34,25 @@ public class ExecutableScriptCondition extends ExecutableCondition<ScriptConditi
} }
@Override @Override
public ScriptCondition.Result execute(WatchExecutionContext ctx) throws IOException { public ScriptCondition.Result execute(WatchExecutionContext ctx) {
try { try {
Map<String, Object> parameters = Variables.createCtxModel(ctx, ctx.payload()); return doExecute(ctx);
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);
} catch (Exception e) { } 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); super(TYPE, met);
} }
Result(Exception e) {
super(TYPE, e);
}
@Override @Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException { protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
return builder; return builder;

View File

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

View File

@ -121,6 +121,9 @@ public abstract class WatchExecutionContext {
} }
beforeWatchTransform(); beforeWatchTransform();
this.transformResult = watch.transform().execute(this, payload); 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.payload = transformResult.payload();
this.transformedPayload = this.payload; this.transformedPayload = this.payload;
return transformedPayload; 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 SUCCESS, FAILURE
} }
protected final Status status;
protected final String type; protected final String type;
private final Payload payload; protected final Status status;
private final String reason; private final String reason;
private final Payload payload;
protected Result(String type, Payload payload) { protected Result(String type, Payload payload) {
this.status = Status.SUCCESS; this.status = Status.SUCCESS;
@ -67,8 +67,8 @@ public interface Input extends ToXContent {
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
builder.field(Field.TYPE.getPreferredName(), type); builder.field(Field.TYPE.getPreferredName(), type);
builder.field(Field.STATUS.getPreferredName(), status.name().toLowerCase(Locale.ROOT));
switch (status) { switch (status) {
case SUCCESS: case SUCCESS:
assert payload != null; assert payload != null;

View File

@ -37,6 +37,8 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
super(settings, controller, client); super(settings, controller, client);
controller.registerHandler(RestRequest.Method.POST, URI_BASE + "/watch/{id}/_execute", this); 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.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; this.triggerService = triggerService;
} }
@ -58,9 +60,8 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
//This tightly binds the REST API to the java API //This tightly binds the REST API to the java API
private ExecuteWatchRequest parseRequest(RestRequest request, WatcherClient client) throws IOException { private ExecuteWatchRequest parseRequest(RestRequest request, WatcherClient client) throws IOException {
String watchId = request.param("id"); ExecuteWatchRequestBuilder builder = client.prepareExecuteWatch();
ExecuteWatchRequestBuilder builder = client.prepareExecuteWatch(watchId); builder.setId(request.param("id"));
if (request.content() == null || request.content().length() == 0) { if (request.content() == null || request.content().length() == 0) {
return builder.request(); return builder.request();
} }
@ -79,13 +80,17 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
} else if (Field.RECORD_EXECUTION.match(currentFieldName)) { } else if (Field.RECORD_EXECUTION.match(currentFieldName)) {
builder.setRecordExecution(parser.booleanValue()); builder.setRecordExecution(parser.booleanValue());
} else { } 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) { } else if (token == XContentParser.Token.START_OBJECT) {
if (Field.ALTERNATIVE_INPUT.match(currentFieldName)) { if (Field.ALTERNATIVE_INPUT.match(currentFieldName)) {
builder.setAlternativeInput(parser.map()); builder.setAlternativeInput(parser.map());
} else if (Field.TRIGGER_DATA.match(currentFieldName)) { } else if (Field.TRIGGER_DATA.match(currentFieldName)) {
builder.setTriggerData(parser.map()); 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)) { } else if (Field.ACTION_MODES.match(currentFieldName)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
@ -95,17 +100,17 @@ public class RestExecuteWatchAction extends WatcherRestHandler {
ActionExecutionMode mode = ActionExecutionMode.resolve(parser.textOrNull()); ActionExecutionMode mode = ActionExecutionMode.resolve(parser.textOrNull());
builder.setActionMode(currentFieldName, mode); builder.setActionMode(currentFieldName, mode);
} catch (WatcherException we) { } 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 { } 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 { } 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 { } 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 ALTERNATIVE_INPUT = new ParseField("alternative_input");
ParseField IGNORE_CONDITION = new ParseField("ignore_condition"); ParseField IGNORE_CONDITION = new ParseField("ignore_condition");
ParseField TRIGGER_DATA = new ParseField("trigger_data"); 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.io.Streams;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment; 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.ApplicableHttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry; import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
@ -57,6 +58,8 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
private final HttpAuthRegistry httpAuthRegistry; private final HttpAuthRegistry httpAuthRegistry;
private final Environment env; private final Environment env;
private final TimeValue defaultConnectionTimeout;
private final TimeValue defaultReadTimeout;
private SSLSocketFactory sslSocketFactory; private SSLSocketFactory sslSocketFactory;
@ -65,6 +68,8 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
super(settings); super(settings);
this.httpAuthRegistry = httpAuthRegistry; this.httpAuthRegistry = httpAuthRegistry;
this.env = env; 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 @Override
@ -145,6 +150,13 @@ public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
urlConnection.getOutputStream().write(bytes); urlConnection.getOutputStream().write(bytes);
urlConnection.getOutputStream().close(); 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(); urlConnection.connect();
final int statusCode = urlConnection.getResponseCode(); 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.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException; import org.elasticsearch.watcher.WatcherException;
import org.elasticsearch.watcher.support.WatcherDateTimeUtils;
import org.elasticsearch.watcher.support.WatcherUtils; import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.http.auth.HttpAuth; import org.elasticsearch.watcher.support.http.auth.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry; import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
@ -31,10 +33,12 @@ public class HttpRequest implements ToXContent {
final ImmutableMap<String, String> headers; final ImmutableMap<String, String> headers;
final @Nullable HttpAuth auth; final @Nullable HttpAuth auth;
final @Nullable String body; 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, 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 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.host = host;
this.port = port; this.port = port;
this.scheme = scheme != null ? scheme : Scheme.HTTP; 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.headers = headers != null ? headers : ImmutableMap.<String, String>of();
this.auth = auth; this.auth = auth;
this.body = body; this.body = body;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
} }
public Scheme scheme() { public Scheme scheme() {
@ -86,6 +92,14 @@ public class HttpRequest implements ToXContent {
return body; return body;
} }
public TimeValue connectionTimeout() {
return connectionTimeout;
}
public TimeValue readTimeout() {
return readTimeout;
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject(); builder.startObject();
@ -108,6 +122,12 @@ public class HttpRequest implements ToXContent {
if (body != null) { if (body != null) {
builder.field(Field.BODY.getPreferredName(), body); 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(); return builder.endObject();
} }
@ -126,6 +146,8 @@ public class HttpRequest implements ToXContent {
if (!params.equals(that.params)) return false; if (!params.equals(that.params)) return false;
if (!headers.equals(that.headers)) return false; if (!headers.equals(that.headers)) return false;
if (auth != null ? !auth.equals(that.auth) : that.auth != 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); 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 + params.hashCode();
result = 31 * result + headers.hashCode(); result = 31 * result + headers.hashCode();
result = 31 * result + (auth != null ? auth.hashCode() : 0); 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); result = 31 * result + (body != null ? body.hashCode() : 0);
return result; return result;
} }
@ -153,6 +177,8 @@ public class HttpRequest implements ToXContent {
"], method=[" + method + "], method=[" + method +
"], port=[" + port + "], port=[" + port +
"], host=[" + host + '\'' + "], host=[" + host + '\'' +
"], connection_timeout=[" + connectionTimeout + '\'' +
"], read_timeout=[" + readTimeout + '\'' +
"]}"; "]}";
} }
@ -178,6 +204,18 @@ public class HttpRequest implements ToXContent {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (Field.AUTH.match(currentFieldName)) { } else if (Field.AUTH.match(currentFieldName)) {
builder.auth(httpAuthRegistry.parse(parser)); 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) { } else if (token == XContentParser.Token.START_OBJECT) {
if (Field.HEADERS.match(currentFieldName)) { if (Field.HEADERS.match(currentFieldName)) {
builder.setHeaders((Map) WatcherUtils.flattenModel(parser.map())); builder.setHeaders((Map) WatcherUtils.flattenModel(parser.map()));
@ -186,7 +224,7 @@ public class HttpRequest implements ToXContent {
} else if (Field.BODY.match(currentFieldName)) { } else if (Field.BODY.match(currentFieldName)) {
builder.body(parser.text()); builder.body(parser.text());
} else { } 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) { } else if (token == XContentParser.Token.VALUE_STRING) {
if (Field.SCHEME.match(currentFieldName)) { if (Field.SCHEME.match(currentFieldName)) {
@ -200,25 +238,25 @@ public class HttpRequest implements ToXContent {
} else if (Field.BODY.match(currentFieldName)) { } else if (Field.BODY.match(currentFieldName)) {
builder.body(parser.text()); builder.body(parser.text());
} else { } 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) { } else if (token == XContentParser.Token.VALUE_NUMBER) {
if (Field.PORT.match(currentFieldName)) { if (Field.PORT.match(currentFieldName)) {
builder.port = parser.intValue(); builder.port = parser.intValue();
} else { } 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 { } 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) { 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) { 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(); return builder.build();
@ -226,12 +264,12 @@ public class HttpRequest implements ToXContent {
public static class ParseException extends WatcherException { public static class ParseException extends WatcherException {
public ParseException(String msg) { public ParseException(String msg, Object... args) {
super(msg); super(msg, args);
} }
public ParseException(String msg, Throwable cause) { public ParseException(String msg, Throwable cause, Object... args) {
super(msg, cause); super(msg, cause, args);
} }
} }
} }
@ -247,6 +285,8 @@ public class HttpRequest implements ToXContent {
private ImmutableMap.Builder<String, String> headers = ImmutableMap.builder(); private ImmutableMap.Builder<String, String> headers = ImmutableMap.builder();
private HttpAuth auth; private HttpAuth auth;
private String body; private String body;
private TimeValue connectionTimeout;
private TimeValue readTimeout;
private Builder(String host, int port) { private Builder(String host, int port) {
this.host = host; this.host = host;
@ -301,13 +341,22 @@ public class HttpRequest implements ToXContent {
return this; return this;
} }
public HttpRequest build() { public Builder connectionTimeout(TimeValue timeout) {
return new HttpRequest(host, port, scheme, method, path, params.build(), headers.build(), auth, body); 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 SCHEME = new ParseField("scheme");
ParseField HOST = new ParseField("host"); ParseField HOST = new ParseField("host");
ParseField PORT = new ParseField("port"); ParseField PORT = new ParseField("port");
@ -317,5 +366,7 @@ public class HttpRequest implements ToXContent {
ParseField HEADERS = new ParseField("headers"); ParseField HEADERS = new ParseField("headers");
ParseField AUTH = new ParseField("auth"); ParseField AUTH = new ParseField("auth");
ParseField BODY = new ParseField("body"); 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 com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.WatcherException; 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.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry; import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import org.elasticsearch.watcher.support.template.Template; import org.elasticsearch.watcher.support.template.Template;
@ -39,10 +41,12 @@ public class HttpRequestTemplate implements ToXContent {
private final ImmutableMap<String, Template> headers; private final ImmutableMap<String, Template> headers;
private final HttpAuth auth; private final HttpAuth auth;
private final Template body; 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, 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, Map<String, Template> params, Map<String, Template> headers, HttpAuth auth,
Template body) { Template body, @Nullable TimeValue connectionTimeout, @Nullable TimeValue readTimeout) {
this.host = host; this.host = host;
this.port = port; this.port = port;
this.scheme = scheme != null ? scheme :Scheme.HTTP; 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.headers = headers != null ? ImmutableMap.copyOf(headers) : ImmutableMap.<String, Template>of();
this.auth = auth; this.auth = auth;
this.body = body; this.body = body;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
} }
public Scheme scheme() { public Scheme scheme() {
@ -90,6 +96,14 @@ public class HttpRequestTemplate implements ToXContent {
return body; return body;
} }
public TimeValue connectionTimeout() {
return connectionTimeout;
}
public TimeValue readTimeout() {
return readTimeout;
}
public HttpRequest render(TemplateEngine engine, Map<String, Object> model) { public HttpRequest render(TemplateEngine engine, Map<String, Object> model) {
HttpRequest.Builder request = HttpRequest.builder(host, port); HttpRequest.Builder request = HttpRequest.builder(host, port);
request.method(method); request.method(method);
@ -123,39 +137,51 @@ public class HttpRequestTemplate implements ToXContent {
if (body != null) { if (body != null) {
request.body(engine.render(body, model)); request.body(engine.render(body, model));
} }
if (connectionTimeout != null) {
request.connectionTimeout(connectionTimeout);
}
if (readTimeout != null) {
request.readTimeout(readTimeout);
}
return request.build(); return request.build();
} }
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Parser.SCHEME_FIELD.getPreferredName(), scheme, params); builder.field(Field.SCHEME.getPreferredName(), scheme, params);
builder.field(Parser.HOST_FIELD.getPreferredName(), host); builder.field(Field.HOST.getPreferredName(), host);
builder.field(Parser.PORT_FIELD.getPreferredName(), port); builder.field(Field.PORT.getPreferredName(), port);
builder.field(Parser.METHOD_FIELD.getPreferredName(), method, params); builder.field(Field.METHOD.getPreferredName(), method, params);
if (path != null) { if (path != null) {
builder.field(Parser.PATH_FIELD.getPreferredName(), path, params); builder.field(Field.PATH.getPreferredName(), path, params);
} }
if (this.params != null) { if (this.params != null) {
builder.startObject(Parser.PARAMS_FIELD.getPreferredName()); builder.startObject(Field.PARAMS.getPreferredName());
for (Map.Entry<String, Template> entry : this.params.entrySet()) { for (Map.Entry<String, Template> entry : this.params.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params); builder.field(entry.getKey(), entry.getValue(), params);
} }
builder.endObject(); builder.endObject();
} }
if (headers != null) { if (headers != null) {
builder.startObject(Parser.HEADERS_FIELD.getPreferredName()); builder.startObject(Field.HEADERS.getPreferredName());
for (Map.Entry<String, Template> entry : headers.entrySet()) { for (Map.Entry<String, Template> entry : headers.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params); builder.field(entry.getKey(), entry.getValue(), params);
} }
builder.endObject(); builder.endObject();
} }
if (auth != null) { if (auth != null) {
builder.startObject(Parser.AUTH_FIELD.getPreferredName()) builder.startObject(Field.AUTH.getPreferredName())
.field(auth.type(), auth, params) .field(auth.type(), auth, params)
.endObject(); .endObject();
} }
if (body != null) { 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(); 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 (params != null ? !params.equals(that.params) : that.params != null) return false;
if (headers != null ? !headers.equals(that.headers) : that.headers != 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 (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; 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 + (headers != null ? headers.hashCode() : 0);
result = 31 * result + (auth != null ? auth.hashCode() : 0); result = 31 * result + (auth != null ? auth.hashCode() : 0);
result = 31 * result + (body != null ? body.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; return result;
} }
@ -198,17 +228,6 @@ public class HttpRequestTemplate implements ToXContent {
public static class Parser { 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; private final HttpAuthRegistry httpAuthRegistry;
@Inject @Inject
@ -225,32 +244,44 @@ public class HttpRequestTemplate implements ToXContent {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (PATH_FIELD.match(currentFieldName)) { } else if (Field.PATH.match(currentFieldName)) {
builder.path(parseFieldTemplate(currentFieldName, parser)); builder.path(parseFieldTemplate(currentFieldName, parser));
} else if (HEADERS_FIELD.match(currentFieldName)) { } else if (Field.HEADERS.match(currentFieldName)) {
builder.putHeaders(parseFieldTemplates(currentFieldName, parser)); builder.putHeaders(parseFieldTemplates(currentFieldName, parser));
} else if (PARAMS_FIELD.match(currentFieldName)) { } else if (Field.PARAMS.match(currentFieldName)) {
builder.putParams(parseFieldTemplates(currentFieldName, parser)); builder.putParams(parseFieldTemplates(currentFieldName, parser));
} else if (BODY_FIELD.match(currentFieldName)) { } else if (Field.BODY.match(currentFieldName)) {
builder.body(parseFieldTemplate(currentFieldName, parser)); 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) { } else if (token == XContentParser.Token.START_OBJECT) {
if (AUTH_FIELD.match(currentFieldName)) { if (Field.AUTH.match(currentFieldName)) {
builder.auth(httpAuthRegistry.parse(parser)); builder.auth(httpAuthRegistry.parse(parser));
} else { } else {
throw new ParseException("could not parse http request template. unexpected object field [{}]", currentFieldName); throw new ParseException("could not parse http request template. unexpected object field [{}]", currentFieldName);
} }
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
if (SCHEME_FIELD.match(currentFieldName)) { if (Field.SCHEME.match(currentFieldName)) {
builder.scheme(Scheme.parse(parser.text())); builder.scheme(Scheme.parse(parser.text()));
} else if (METHOD_FIELD.match(currentFieldName)) { } else if (Field.METHOD.match(currentFieldName)) {
builder.method(HttpMethod.parse(parser.text())); builder.method(HttpMethod.parse(parser.text()));
} else if (HOST_FIELD.match(currentFieldName)) { } else if (Field.HOST.match(currentFieldName)) {
builder.host = parser.text(); builder.host = parser.text();
} else { } else {
throw new ParseException("could not parse http request template. unexpected string field [{}]", currentFieldName); throw new ParseException("could not parse http request template. unexpected string field [{}]", currentFieldName);
} }
} else if (token == XContentParser.Token.VALUE_NUMBER) { } else if (token == XContentParser.Token.VALUE_NUMBER) {
if (PORT_FIELD.match(currentFieldName)) { if (Field.PORT.match(currentFieldName)) {
builder.port = parser.intValue(); builder.port = parser.intValue();
} else { } else {
throw new ParseException("could not parse http request template. unexpected numeric field [{}]", currentFieldName); 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) { 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) { 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(); return builder.build();
@ -317,6 +348,8 @@ public class HttpRequestTemplate implements ToXContent {
private final ImmutableMap.Builder<String, Template> headers = ImmutableMap.builder(); private final ImmutableMap.Builder<String, Template> headers = ImmutableMap.builder();
private HttpAuth auth; private HttpAuth auth;
private Template body; private Template body;
private TimeValue connectionTimeout;
private TimeValue readTimeout;
private Builder() { private Builder() {
} }
@ -407,8 +440,18 @@ public class HttpRequestTemplate implements ToXContent {
return body(Template.inline(content)); 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() { 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; return transform;
} }
public abstract R execute(WatchExecutionContext ctx, Payload payload) throws IOException; public abstract R execute(WatchExecutionContext ctx, Payload payload);
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.watcher.transform; package org.elasticsearch.watcher.transform;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -21,27 +23,64 @@ public interface Transform extends ToXContent {
abstract class Result implements ToXContent { abstract class Result implements ToXContent {
public enum Status {
SUCCESS, FAILURE
}
protected final String type; 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) { public Result(String type, Payload payload) {
this.type = type; this.type = type;
this.status = Status.SUCCESS;
this.payload = payload; 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() { public String type() {
return type; return type;
} }
public Status status() {
return status;
}
public Payload payload() { public Payload payload() {
assert status == Status.SUCCESS;
return payload; return payload;
} }
public String reason() {
assert status == Status.FAILURE;
return reason;
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Field.TYPE.getPreferredName(), type); 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); typeXContent(builder, params);
return builder.endObject(); return builder.endObject();
} }
@ -56,8 +95,12 @@ public interface Transform extends ToXContent {
} }
interface Field { interface Field {
ParseField TYPE = new ParseField("type");
ParseField PAYLOAD = new ParseField("payload");
ParseField TRANSFORM = new ParseField("transform"); 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; this.results = results;
} }
public Result(Exception e, ImmutableList<Transform.Result> results) {
super(TYPE, e);
this.results = results;
}
public ImmutableList<Transform.Result> results() { public ImmutableList<Transform.Result> results() {
return results; return results;
} }
@Override @Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException { protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(type); if (!results.isEmpty()) {
builder.startArray(Field.RESULTS.getPreferredName()); builder.startObject(type);
for (Transform.Result result : results) { builder.startArray(Field.RESULTS.getPreferredName());
result.toXContent(builder, params); for (Transform.Result result : results) {
result.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
} }
builder.endArray(); return builder;
return builder.endObject();
} }
} }

View File

@ -32,11 +32,24 @@ public class ExecutableChainTransform extends ExecutableTransform<ChainTransform
} }
@Override @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(); 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) { for (ExecutableTransform transform : transforms) {
Transform.Result result = transform.execute(ctx, payload); Transform.Result result = transform.execute(ctx, payload);
results.add(result); 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(); payload = result.payload();
} }
return new ChainTransform.Result(payload, results.build()); return new ChainTransform.Result(payload, results.build());

View File

@ -40,7 +40,17 @@ public class ExecutableScriptTransform extends ExecutableTransform<ScriptTransfo
} }
@Override @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(); Script script = transform.getScript();
Map<String, Object> model = new HashMap<>(); Map<String, Object> model = new HashMap<>();
model.putAll(script.params()); model.putAll(script.params());

View File

@ -74,6 +74,10 @@ public class ScriptTransform implements Transform {
super(TYPE, payload); super(TYPE, payload);
} }
public Result(Exception e) {
super(TYPE, e);
}
@Override @Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException { protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
return builder; 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.transform.ExecutableTransform;
import org.elasticsearch.watcher.watch.Payload; import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
/** /**
* *
*/ */
@ -32,10 +30,16 @@ public class ExecutableSearchTransform extends ExecutableTransform<SearchTransfo
} }
@Override @Override
public SearchTransform.Result execute(WatchExecutionContext ctx, Payload payload) throws IOException { public SearchTransform.Result execute(WatchExecutionContext ctx, Payload payload) {
SearchRequest req = WatcherUtils.createSearchRequestFromPrototype(transform.request, ctx, payload); SearchRequest request = null;
SearchResponse resp = client.search(req); try {
return new SearchTransform.Result(req, new Payload.XContent(resp)); 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; package org.elasticsearch.watcher.transform.search;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
@ -74,23 +75,31 @@ public class SearchTransform implements Transform {
public static class Result extends Transform.Result { public static class Result extends Transform.Result {
private final SearchRequest request; private final @Nullable SearchRequest request;
public Result(SearchRequest request, Payload payload) { public Result(SearchRequest request, Payload payload) {
super(TYPE, payload); super(TYPE, payload);
this.request = request; this.request = request;
} }
public Result(SearchRequest request, Exception e) {
super(TYPE, e);
this.request = request;
}
public SearchRequest executedRequest() { public SearchRequest executedRequest() {
return request; return request;
} }
@Override @Override
protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException { protected XContentBuilder typeXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(type); if (request != null) {
builder.field(Field.REQUEST.getPreferredName()); builder.startObject(type);
WatcherUtils.writeSearchRequest(request, builder, params); builder.field(Field.REQUEST.getPreferredName());
return builder.endObject(); 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.ValidateActions;
import org.elasticsearch.action.support.master.MasterNodeReadRequest; import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; 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.execution.ActionExecutionMode;
import org.elasticsearch.watcher.support.validation.Validation; import org.elasticsearch.watcher.support.validation.Validation;
import org.elasticsearch.watcher.trigger.TriggerEvent; import org.elasticsearch.watcher.trigger.TriggerEvent;
@ -24,12 +27,15 @@ import java.util.Map;
*/ */
public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchRequest> { public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchRequest> {
public static final String INLINE_WATCH_ID = "_inlined_";
private String id; private String id;
private boolean ignoreCondition = false; private boolean ignoreCondition = false;
private boolean recordExecution = false; private boolean recordExecution = false;
private @Nullable Map<String, Object> triggerData = null; private @Nullable Map<String, Object> triggerData = null;
private @Nullable Map<String, Object> alternativeInput = null; private @Nullable Map<String, Object> alternativeInput = null;
private Map<String, ActionExecutionMode> actionModes = new HashMap<>(); private Map<String, ActionExecutionMode> actionModes = new HashMap<>();
private BytesReference watchSource;
ExecuteWatchRequest() { ExecuteWatchRequest() {
} }
@ -120,6 +126,26 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
return triggerData; 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 @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; ActionRequestValidationException validationException = null;
if (id == null){ if (id == null && watchSource == null){
validationException = ValidateActions.addValidationError("watch id is missing", validationException); validationException = ValidateActions.addValidationError("watch id is missing", validationException);
} }
Validation.Error error = Validation.watchId(id); if (id != null) {
if (error != null) { Validation.Error error = Validation.watchId(id);
validationException = ValidateActions.addValidationError(error.message(), validationException);
}
for (Map.Entry<String, ActionExecutionMode> modes : actionModes.entrySet()) {
error = Validation.actionId(modes.getKey());
if (error != null) { if (error != null) {
validationException = ValidateActions.addValidationError(error.message(), validationException); 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; return validationException;
} }
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
id = in.readString(); id = in.readOptionalString();
ignoreCondition = in.readBoolean(); ignoreCondition = in.readBoolean();
recordExecution = in.readBoolean(); recordExecution = in.readBoolean();
if (in.readBoolean()){ if (in.readBoolean()){
@ -176,13 +210,16 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
for (int i = 0; i < actionModesCount; i++) { for (int i = 0; i < actionModesCount; i++) {
actionModes.put(in.readString(), ActionExecutionMode.resolve(in.readByte())); actionModes.put(in.readString(), ActionExecutionMode.resolve(in.readByte()));
} }
if (in.readBoolean()) {
watchSource = in.readBytesReference();
}
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeString(id); out.writeOptionalString(id);
out.writeBoolean(ignoreCondition); out.writeBoolean(ignoreCondition);
out.writeBoolean(recordExecution); out.writeBoolean(recordExecution);
out.writeBoolean(alternativeInput != null); out.writeBoolean(alternativeInput != null);
@ -198,6 +235,10 @@ public class ExecuteWatchRequest extends MasterNodeReadRequest<ExecuteWatchReque
out.writeString(entry.getKey()); out.writeString(entry.getKey());
out.writeByte(entry.getValue().id()); out.writeByte(entry.getValue().id());
} }
out.writeBoolean(watchSource != null);
if (watchSource != null) {
out.writeBytesReference(watchSource);
}
} }
@Override @Override

View File

@ -5,11 +5,10 @@
*/ */
package org.elasticsearch.watcher.transport.actions.execute; package org.elasticsearch.watcher.transport.actions.execute;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder; import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ElasticsearchClient; 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.execution.ActionExecutionMode;
import org.elasticsearch.watcher.trigger.TriggerEvent; import org.elasticsearch.watcher.trigger.TriggerEvent;
@ -76,6 +75,22 @@ public class ExecuteWatchRequestBuilder extends MasterNodeOperationRequestBuilde
return this; 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. * 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); request.setActionMode(actionId, actionMode);
return this; return this;
} }
} }

View File

@ -49,16 +49,19 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
private final WatchStore watchStore; private final WatchStore watchStore;
private final Clock clock; private final Clock clock;
private final TriggerService triggerService; private final TriggerService triggerService;
private final Watch.Parser watchParser;
@Inject @Inject
public TransportExecuteWatchAction(Settings settings, TransportService transportService, ClusterService clusterService, public TransportExecuteWatchAction(Settings settings, TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters, ExecutionService executionService, 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); super(settings, ExecuteWatchAction.NAME, transportService, clusterService, threadPool, actionFilters, licenseService, ExecuteWatchRequest.class);
this.executionService = executionService; this.executionService = executionService;
this.watchStore = watchStore; this.watchStore = watchStore;
this.clock = clock; this.clock = clock;
this.triggerService = triggerService; this.triggerService = triggerService;
this.watchParser = watchParser;
} }
@Override @Override
@ -74,15 +77,26 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
@Override @Override
protected void masterOperation(ExecuteWatchRequest request, ClusterState state, ActionListener<ExecuteWatchResponse> listener) throws ElasticsearchException { protected void masterOperation(ExecuteWatchRequest request, ClusterState state, ActionListener<ExecuteWatchResponse> listener) throws ElasticsearchException {
try { try {
Watch watch = watchStore.get(request.getId()); Watch watch;
if (watch == null) { boolean knownWatch;
throw new WatcherException("watch [{}] does not exist", request.getId()); 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(); String triggerType = watch.trigger().type();
TriggerEvent triggerEvent = triggerService.simulateEvent(triggerType, watch.id(), request.getTriggerData()); 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); DateTime executionTime = clock.now(DateTimeZone.UTC);
ctxBuilder.executionTime(executionTime); 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 { public Trigger parseTrigger(String jobName, String type, XContentParser parser) throws IOException {
TriggerEngine engine = engines.get(type); 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); return engine.parseTrigger(jobName, parser);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,7 @@ public class ExecutionServiceTests extends ElasticsearchTestCase {
// watch level transform // watch level transform
Transform.Result watchTransformResult = mock(Transform.Result.class); Transform.Result watchTransformResult = mock(Transform.Result.class);
when(watchTransformResult.status()).thenReturn(Transform.Result.Status.SUCCESS);
when(watchTransformResult.payload()).thenReturn(payload); when(watchTransformResult.payload()).thenReturn(payload);
ExecutableTransform watchTransform = mock(ExecutableTransform.class); ExecutableTransform watchTransform = mock(ExecutableTransform.class);
when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult); when(watchTransform.execute(context, payload)).thenReturn(watchTransformResult);
@ -223,6 +224,216 @@ public class ExecutionServiceTests extends ElasticsearchTestCase {
verify(action, never()).execute("_action", context, payload); 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 @Test
public void testExecuteInner() throws Exception { public void testExecuteInner() throws Exception {
DateTime now = DateTime.now(DateTimeZone.UTC); DateTime now = DateTime.now(DateTimeZone.UTC);

View File

@ -5,9 +5,8 @@
*/ */
package org.elasticsearch.watcher.execution; 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.joda.time.DateTime;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.watcher.WatcherException; 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.support.xcontent.ObjectPath;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests; import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.transport.actions.delete.DeleteWatchResponse; 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.get.GetWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchRequest; import org.elasticsearch.watcher.transport.actions.put.PutWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse; import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
@ -62,7 +64,6 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
@Test @Test
public void testExecuteWatch() throws Exception { public void testExecuteWatch() throws Exception {
ensureWatcherStarted();
boolean ignoreCondition = randomBoolean(); boolean ignoreCondition = randomBoolean();
boolean recordExecution = randomBoolean(); boolean recordExecution = randomBoolean();
boolean conditionAlwaysTrue = 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 @Test
public void testDifferentAlternativeInputs() throws Exception { public void testDifferentAlternativeInputs() throws Exception {
ensureWatcherStarted();
WatchSourceBuilder watchBuilder = watchBuilder() WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099"))) .trigger(schedule(cron("0 0 0 1 * ? 2099")))
.addAction("log", loggingAction("foobar")); .addAction("log", loggingAction("foobar"));
@ -187,8 +252,6 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTests {
@Test @Test
public void testExecutionRequestDefaults() throws Exception { public void testExecutionRequestDefaults() throws Exception {
ensureWatcherStarted();
WatchSourceBuilder watchBuilder = watchBuilder() WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099"))) .trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar")) .input(simpleInput("foo", "bar"))

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
*/ */
package org.elasticsearch.watcher.support; package org.elasticsearch.watcher.support;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions; 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 MockWebServer webServer;
private HttpClient httpClient; 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 // We can't use the client created above for the server since it is only a truststore
webServer.useHttps(new HttpClient(Settings.builder() 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") .put(HttpClient.SETTINGS_SSL_KEYSTORE_PASSWORD, "testnode")
.build(), authRegistry, environment) .build(), authRegistry, environment)
.start() .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; package org.elasticsearch.watcher.support.http;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
@ -79,6 +79,14 @@ public class HttpRequestTemplateTests extends ElasticsearchTestCase {
if (randomBoolean()) { if (randomBoolean()) {
builder.putHeader("_key", Template.inline("_value")); 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(); HttpRequestTemplate template = builder.build();

View File

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

View File

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

View File

@ -6,7 +6,7 @@
package org.elasticsearch.watcher.support.template.xmustache; package org.elasticsearch.watcher.support.template.xmustache;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableList; 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 com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock;
public class ChainTransformTests extends ElasticsearchTestCase { public class ChainTransformTests extends ElasticsearchTestCase {
@Test @Test
public void testApply() throws Exception { public void testExecute() throws Exception {
ChainTransform transform = new ChainTransform(ImmutableList.<Transform>of( ChainTransform transform = new ChainTransform(ImmutableList.<Transform>of(
new NamedExecutableTransform.Transform("name1"), new NamedExecutableTransform.Transform("name1"),
new NamedExecutableTransform.Transform("name2"), new NamedExecutableTransform.Transform("name2"),
@ -51,7 +51,21 @@ public class ChainTransformTests extends ElasticsearchTestCase {
WatchExecutionContext ctx = mock(WatchExecutionContext.class); WatchExecutionContext ctx = mock(WatchExecutionContext.class);
Payload payload = new Payload.Simple(new HashMap<String, Object>()); 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(); Map<String, Object> data = result.payload().data();
assertThat(data, notNullValue()); assertThat(data, notNullValue());
@ -62,6 +76,39 @@ public class ChainTransformTests extends ElasticsearchTestCase {
assertThat(names, contains("name1", "name2", "name3")); 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 @Test
public void testParser() throws Exception { public void testParser() throws Exception {
Map<String, TransformFactory> factories = ImmutableMap.<String, TransformFactory>builder() Map<String, TransformFactory> factories = ImmutableMap.<String, TransformFactory>builder()
@ -103,14 +150,16 @@ public class ChainTransformTests extends ElasticsearchTestCase {
} }
@Override @Override
public Result execute(WatchExecutionContext ctx, Payload payload) throws IOException { public Result execute(WatchExecutionContext ctx, Payload payload) {
Map<String, Object> data = new HashMap<>(payload.data()); List<String> names = (List<String>) payload.data().get("names");
List<String> names = (List<String>) data.get("names");
if (names == null) { if (names == null) {
names = new ArrayList<>(); names = new ArrayList<>();
data.put("names", names); } else {
names = new ArrayList<>(names);
} }
names.add(transform.name); names.add(transform.name);
Map<String, Object> data = new HashMap<>();
data.put("names", names);
return new Result("named", new Payload.Simple(data)); 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.ImmutableList;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableSet; 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 com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -58,7 +58,7 @@ public class ScriptTransformTests extends ElasticsearchTestCase {
@Test @Test
public void testApply_MapValue() throws Exception { public void testExecute_MapValue() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class); ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptType type = randomFrom(ScriptType.values()); ScriptType type = randomFrom(ScriptType.values());
Map<String, Object> params = Collections.emptyMap(); Map<String, Object> params = Collections.emptyMap();
@ -84,11 +84,39 @@ public class ScriptTransformTests extends ElasticsearchTestCase {
Transform.Result result = transform.execute(ctx, payload); Transform.Result result = transform.execute(ctx, payload);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result.type(), is(ScriptTransform.TYPE)); assertThat(result.type(), is(ScriptTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.SUCCESS));
assertThat(result.payload().data(), equalTo(transformed)); assertThat(result.payload().data(), equalTo(transformed));
} }
@Test @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); ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptType type = randomFrom(ScriptType.values()); ScriptType type = randomFrom(ScriptType.values());

View File

@ -114,7 +114,7 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
} }
@Test @Test
public void testApply() throws Exception { public void testExecute() throws Exception {
index("idx", "type", "1"); index("idx", "type", "1");
ensureGreen("idx"); ensureGreen("idx");
@ -133,6 +133,7 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
Transform.Result result = transform.execute(ctx, EMPTY_PAYLOAD); Transform.Result result = transform.execute(ctx, EMPTY_PAYLOAD);
assertThat(result, notNullValue()); assertThat(result, notNullValue());
assertThat(result.type(), is(SearchTransform.TYPE)); assertThat(result.type(), is(SearchTransform.TYPE));
assertThat(result.status(), is(Transform.Result.Status.SUCCESS));
SearchResponse response = client().search(request).get(); SearchResponse response = client().search(request).get();
Payload expectedPayload = new Payload.XContent(response); Payload expectedPayload = new Payload.XContent(response);
@ -149,7 +150,33 @@ public class SearchTransformTests extends ElasticsearchIntegrationTest {
} }
@Test @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: // The rational behind this test:
// //

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.watcher.trigger.schedule.tool; package org.elasticsearch.watcher.trigger.schedule.tool;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.cli.CliTool; import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase; import org.elasticsearch.common.cli.CliToolTestCase;
import org.junit.Test; 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.ExecutableEmailAction;
import org.elasticsearch.watcher.actions.email.service.EmailService; import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate; 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.email.service.Profile;
import org.elasticsearch.watcher.actions.index.ExecutableIndexAction; import org.elasticsearch.watcher.actions.index.ExecutableIndexAction;
import org.elasticsearch.watcher.actions.index.IndexAction; import org.elasticsearch.watcher.actions.index.IndexAction;
@ -113,6 +114,7 @@ public class WatchTests extends ElasticsearchTestCase {
private HttpClient httpClient; private HttpClient httpClient;
private EmailService emailService; private EmailService emailService;
private TemplateEngine templateEngine; private TemplateEngine templateEngine;
private HtmlSanitizer htmlSanitizer;
private HttpAuthRegistry authRegistry; private HttpAuthRegistry authRegistry;
private SecretService secretService; private SecretService secretService;
private LicenseService licenseService; private LicenseService licenseService;
@ -126,6 +128,7 @@ public class WatchTests extends ElasticsearchTestCase {
httpClient = mock(HttpClient.class); httpClient = mock(HttpClient.class);
emailService = mock(EmailService.class); emailService = mock(EmailService.class);
templateEngine = mock(TemplateEngine.class); templateEngine = mock(TemplateEngine.class);
htmlSanitizer = mock(HtmlSanitizer.class);
secretService = mock(SecretService.class); secretService = mock(SecretService.class);
licenseService = mock(LicenseService.class); licenseService = mock(LicenseService.class);
authRegistry = new HttpAuthRegistry(ImmutableMap.of("basic", (HttpAuthFactory) new BasicAuthFactory(secretService))); authRegistry = new HttpAuthRegistry(ImmutableMap.of("basic", (HttpAuthFactory) new BasicAuthFactory(secretService)));
@ -383,7 +386,7 @@ public class WatchTests extends ElasticsearchTestCase {
if (randomBoolean()) { if (randomBoolean()) {
ExecutableTransform transform = randomTransform(); ExecutableTransform transform = randomTransform();
EmailAction action = new EmailAction(EmailTemplate.builder().build(), null, null, Profile.STANDARD, randomFrom(DataAttachment.JSON, DataAttachment.YAML, null)); 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()) { if (randomBoolean()) {
IndexAction aciton = new IndexAction("_index", "_type", null); IndexAction aciton = new IndexAction("_index", "_type", null);
@ -405,7 +408,7 @@ public class WatchTests extends ElasticsearchTestCase {
for (ActionWrapper action : actions) { for (ActionWrapper action : actions) {
switch (action.action().type()) { switch (action.action().type()) {
case EmailAction.TYPE: case EmailAction.TYPE:
parsers.put(EmailAction.TYPE, new EmailActionFactory(settings, emailService, templateEngine)); parsers.put(EmailAction.TYPE, new EmailActionFactory(settings, emailService, templateEngine, htmlSanitizer));
break; break;
case IndexAction.TYPE: case IndexAction.TYPE:
parsers.put(IndexAction.TYPE, new IndexActionFactory(settings, client)); parsers.put(IndexAction.TYPE, new IndexActionFactory(settings, client));