Add EmailSettingsService to hold global email settings.

This change adds a service to hold the dynamically updateable email settings.
Added logging and made inner settings holding class static.

Original commit: elastic/x-pack-elasticsearch@e1690fa292
This commit is contained in:
Brian Murphy 2015-02-05 19:38:25 -05:00
parent da1f446b49
commit 0470fdf6af
3 changed files with 163 additions and 105 deletions

View File

@ -6,6 +6,7 @@
package org.elasticsearch.alerts.actions;
import org.elasticsearch.alerts.actions.email.EmailAction;
import org.elasticsearch.alerts.actions.email.EmailSettingsService;
import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.actions.webhook.HttpClient;
import org.elasticsearch.alerts.actions.webhook.WebhookAction;
@ -46,6 +47,7 @@ public class ActionModule extends AbstractModule {
bind(ActionRegistry.class).asEagerSingleton();
bind(HttpClient.class).asEagerSingleton();
bind(EmailSettingsService.class).asEagerSingleton();
}

View File

@ -9,8 +9,6 @@ import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.ActionException;
import org.elasticsearch.alerts.support.StringTemplateUtils;
import org.elasticsearch.cluster.settings.DynamicSettings;
import org.elasticsearch.cluster.settings.Validator;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
@ -19,7 +17,6 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.script.ScriptService;
import javax.mail.*;
@ -31,16 +28,10 @@ import java.util.*;
/**
*/
public class EmailAction extends Action<EmailAction.Result> implements NodeSettingsService.Listener {
public class EmailAction extends Action<EmailAction.Result> {
public static final String TYPE = "email";
static final String PORT_SETTING = "alerts.action.email.server.port";
static final String SERVER_SETTING = "alerts.action.email.server.name";
static final String FROM_SETTING = "alerts.action.email.from.address";
static final String USERNAME_SETTING = "alerts.action.email.from.username";
static final String PASSWORD_SETTING = "alerts.action.email.from.password";
private final List<Address> emailAddresses;
//Optional, can be null, will use defaults from emailSettings (EmailServiceConfig)
@ -48,9 +39,6 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
private final StringTemplateUtils.Template subjectTemplate;
private final StringTemplateUtils.Template messageTemplate;
private static final String DEFAULT_SERVER = "smtp.gmail.com";
private static final int DEFAULT_PORT = 578;
private static final StringTemplateUtils.Template DEFAULT_SUBJECT_TEMPLATE = new StringTemplateUtils.Template(
"Elasticsearch Alert {{alert_name}} triggered", null, "mustache", ScriptService.ScriptType.INLINE);
@ -59,24 +47,22 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
private final StringTemplateUtils templateUtils;
private volatile EmailServiceConfig emailSettings = new EmailServiceConfig(DEFAULT_SERVER, DEFAULT_PORT, null, null, null);
private final EmailSettingsService emailSettingsService;
protected EmailAction(ESLogger logger, Settings settings, NodeSettingsService nodeSettingsService,
protected EmailAction(ESLogger logger, EmailSettingsService emailSettingsService,
StringTemplateUtils templateUtils, @Nullable StringTemplateUtils.Template subjectTemplate,
@Nullable StringTemplateUtils.Template messageTemplate, @Nullable String fromAddress,
List<Address> emailAddresses) {
super(logger);
this.templateUtils = templateUtils;
this.emailSettingsService = emailSettingsService;
this.emailAddresses = new ArrayList<>();
this.emailAddresses.addAll(emailAddresses);
this.subjectTemplate = subjectTemplate;
this.messageTemplate = messageTemplate;
this.fromAddress = fromAddress;
nodeSettingsService.addListener(this);
updateSettings(settings);
}
@Override
@ -87,19 +73,21 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
@Override
public Result execute(Alert alert, Map<String, Object> data) throws IOException {
final EmailSettingsService.EmailServiceConfig emailSettings = emailSettingsService.emailServiceConfig();
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", emailSettings.host);
props.put("mail.smtp.port", emailSettings.port);
props.put("mail.smtp.host", emailSettings.host());
props.put("mail.smtp.port", emailSettings.port());
final Session session;
if (emailSettings.password != null) {
if (emailSettings.password() != null) {
final String username;
if (emailSettings.username != null) {
username = emailSettings.username;
if (emailSettings.username() != null) {
username = emailSettings.username();
} else {
username = emailSettings.defaultFromAddress;
username = emailSettings.defaultFromAddress();
}
if (username == null) {
@ -109,7 +97,7 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, emailSettings.password);
return new PasswordAuthentication(username, emailSettings.password());
}
});
} else {
@ -119,7 +107,7 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
try {
Message email = new MimeMessage(session);
String fromAddressToUse = emailSettings.defaultFromAddress;
String fromAddressToUse = emailSettings.defaultFromAddress();
if (fromAddress != null) {
fromAddressToUse = fromAddress;
}
@ -179,11 +167,6 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
}
@Override
public void onRefreshSettings(Settings settings) {
updateSettings(settings);
}
public static class Parser extends AbstractComponent implements Action.Parser<EmailAction> {
public static final ParseField FROM_FIELD = new ParseField("from");
@ -191,20 +174,14 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
public static final ParseField MESSAGE_TEMPLATE_FIELD = new ParseField("message_template");
public static final ParseField SUBJECT_TEMPLATE_FIELD = new ParseField("subject_template");
private final NodeSettingsService nodeSettingsService;
private final StringTemplateUtils templateUtils;
private final EmailSettingsService emailSettingsService;
@Inject
public Parser(Settings settings, DynamicSettings dynamicSettings, NodeSettingsService nodeSettingsService, StringTemplateUtils templateUtils) {
public Parser(Settings settings, EmailSettingsService emailSettingsService, StringTemplateUtils templateUtils) {
super(settings);
this.nodeSettingsService = nodeSettingsService;
this.templateUtils = templateUtils;
dynamicSettings.addDynamicSetting(PORT_SETTING, Validator.POSITIVE_INTEGER);
dynamicSettings.addDynamicSetting(SERVER_SETTING);
dynamicSettings.addDynamicSetting(FROM_SETTING);
dynamicSettings.addDynamicSetting(USERNAME_SETTING);
dynamicSettings.addDynamicSetting(PASSWORD_SETTING);
this.emailSettingsService = emailSettingsService;
}
@Override
@ -256,8 +233,8 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
throw new ActionException("could not parse email action. [addresses] was not found or was empty");
}
return new EmailAction(logger, settings, nodeSettingsService,
templateUtils, subjectTemplate, messageTemplate, fromAddress, addresses);
return new EmailAction(logger, emailSettingsService, templateUtils, subjectTemplate,
messageTemplate, fromAddress, addresses);
}
}
@ -306,66 +283,4 @@ public class EmailAction extends Action<EmailAction.Result> implements NodeSetti
}
}
// This is useful to change all settings at the same time. Otherwise we may change the username then email gets send
// and then change the password and then the email sending fails.
//
// Also this reduces the number of volatile writes
private class EmailServiceConfig {
private String host;
private int port;
private String username;
private String password;
private String defaultFromAddress;
private EmailServiceConfig(String host, int port, String userName, String password, String defaultFromAddress) {
this.host = host;
this.port = port;
this.username = userName;
this.password = password;
this.defaultFromAddress = defaultFromAddress;
}
}
private void updateSettings(Settings settings) {
boolean changed = false;
String host = emailSettings.host;
String newHost = settings.get(SERVER_SETTING);
if (newHost != null && !newHost.equals(host)) {
host = newHost;
changed = true;
}
int port = emailSettings.port;
int newPort = settings.getAsInt(PORT_SETTING, -1);
if (newPort != -1) {
port = newPort;
changed = true;
}
String fromAddress = emailSettings.defaultFromAddress;
String newFromAddress = settings.get(FROM_SETTING);
if (newFromAddress != null && !newFromAddress.equals(fromAddress)) {
fromAddress = newFromAddress;
changed = true;
}
String userName = emailSettings.username;
String newUserName = settings.get(USERNAME_SETTING);
if (newUserName != null && !newUserName.equals(userName)) {
userName = newFromAddress;
changed = true;
}
String password = emailSettings.password;
String newPassword = settings.get(PASSWORD_SETTING);
if (newPassword != null && !newPassword.equals(password)) {
password = newPassword;
changed = true;
}
if (changed) {
emailSettings = new EmailServiceConfig(host, port, fromAddress, userName, password);
}
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.alerts.actions.email;
import org.elasticsearch.cluster.settings.DynamicSettings;
import org.elasticsearch.cluster.settings.Validator;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.settings.NodeSettingsService;
/**
*/
public class EmailSettingsService extends AbstractComponent implements NodeSettingsService.Listener {
static final String PORT_SETTING = "alerts.action.email.server.port";
static final String SERVER_SETTING = "alerts.action.email.server.name";
static final String FROM_SETTING = "alerts.action.email.from.address";
static final String USERNAME_SETTING = "alerts.action.email.from.username";
static final String PASSWORD_SETTING = "alerts.action.email.from.password";
private static final String DEFAULT_SERVER = "smtp.gmail.com";
private static final int DEFAULT_PORT = 578;
private volatile EmailServiceConfig emailServiceConfig = new EmailServiceConfig(DEFAULT_SERVER, DEFAULT_PORT, null, null, null);
@Inject
public EmailSettingsService(Settings settings, DynamicSettings dynamicSettings, NodeSettingsService nodeSettingsService) {
super(settings);
//TODO Add validators for hosts and email addresses
dynamicSettings.addDynamicSetting(PORT_SETTING, Validator.POSITIVE_INTEGER);
dynamicSettings.addDynamicSetting(SERVER_SETTING);
dynamicSettings.addDynamicSetting(FROM_SETTING);
dynamicSettings.addDynamicSetting(USERNAME_SETTING);
dynamicSettings.addDynamicSetting(PASSWORD_SETTING);
nodeSettingsService.addListener(this);
updateSettings(settings);
}
public EmailServiceConfig emailServiceConfig() {
return emailServiceConfig;
}
// This is useful to change all settings at the same time. Otherwise we may change the username then email gets send
// and then change the password and then the email sending fails.
//
// Also this reduces the number of volatile writes
static class EmailServiceConfig {
private String host;
private int port;
private String username;
private String password;
private String defaultFromAddress;
public String host() {
return host;
}
public int port() {
return port;
}
public String username() {
return username;
}
public String password() {
return password;
}
public String defaultFromAddress() {
return defaultFromAddress;
}
private EmailServiceConfig(String host, int port, String userName, String password, String defaultFromAddress) {
this.host = host;
this.port = port;
this.username = userName;
this.password = password;
this.defaultFromAddress = defaultFromAddress;
}
}
@Override
public void onRefreshSettings(Settings settings) {
updateSettings(settings);
}
private void updateSettings(Settings settings) {
boolean changed = false;
String host = emailServiceConfig.host;
String newHost = settings.get(SERVER_SETTING);
if (newHost != null && !newHost.equals(host)) {
logger.info("host changed from [{}] to [{}]", host, newHost);
host = newHost;
changed = true;
}
int port = emailServiceConfig.port;
int newPort = settings.getAsInt(PORT_SETTING, -1);
if (newPort != -1) {
logger.info("port changed from [{}] to [{}]", port, newPort);
port = newPort;
changed = true;
}
String fromAddress = emailServiceConfig.defaultFromAddress;
String newFromAddress = settings.get(FROM_SETTING);
if (newFromAddress != null && !newFromAddress.equals(fromAddress)) {
logger.info("from changed from [{}] to [{}]", fromAddress, newFromAddress);
fromAddress = newFromAddress;
changed = true;
}
String userName = emailServiceConfig.username;
String newUserName = settings.get(USERNAME_SETTING);
if (newUserName != null && !newUserName.equals(userName)) {
logger.info("username changed from [{}] to [{}]", userName, newUserName);
userName = newFromAddress;
changed = true;
}
String password = emailServiceConfig.password;
String newPassword = settings.get(PASSWORD_SETTING);
if (newPassword != null && !newPassword.equals(password)) {
logger.info("password changed");
password = newPassword;
changed = true;
}
if (changed) {
logger.info("one or more settings have changed, updating the email service config");
emailServiceConfig = new EmailServiceConfig(host, port, fromAddress, userName, password);
}
}
}