Commit 9f753d92 by Johannes Stelzer

Added mail notification for status changes

The mail notification is enabled if a mailSender is present specified via spring.mail.* properties. On default the mail is sent to root@localhost and the chages from UNKNOWN to UP are ignored.
parent c02875e4
......@@ -18,6 +18,10 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
......
......@@ -48,7 +48,9 @@ import de.codecentric.boot.admin.registry.StatusUpdater;
import de.codecentric.boot.admin.registry.store.ApplicationStore;
@Configuration
@Import({RevereseZuulProxyConfiguration.class, HazelcastStoreConfiguration.class, SimpleStoreConfig.class, DiscoveryClientConfiguration.class})
@Import({ RevereseZuulProxyConfiguration.class, MailNotifierConfiguration.class,
HazelcastStoreConfiguration.class, SimpleStoreConfig.class,
DiscoveryClientConfiguration.class })
public class AdminServerWebConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {
......
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.config;
import javax.activation.MimeType;
import javax.mail.internet.MimeMessage;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender;
import de.codecentric.boot.admin.notify.MailNotifier;
@Configuration
@ConditionalOnClass({ MimeMessage.class, MimeType.class })
@ConditionalOnProperty(prefix = "spring.mail", value = "host")
@ConditionalOnMissingBean(MailSender.class)
@EnableConfigurationProperties(MailProperties.class)
public class MailNotifierConfiguration extends MailSenderAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.boot.admin.notify", name = "enabled", matchIfMissing = true)
@ConfigurationProperties("spring.boot.admin.notify")
public MailNotifier mailNotifier() {
return new MailNotifier(mailSender());
}
}
\ No newline at end of file
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.notify;
import java.util.Arrays;
import javax.mail.MessagingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
public class MailNotifier implements ApplicationListener<ClientApplicationStatusChangedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(MailNotifier.class);
private final String DEFAULT_SUBJECT = "#{application.name} (#{application.id}) is #{to.status}";
private final String DEFAULT_TEXT = "#{application.name} (#{application.id})\nstatus changed from #{from.status} to #{to.status}\n\n#{application.healthUrl}";
private final SpelExpressionParser parser = new SpelExpressionParser();
private MailSender sender;
/**
* recipients of the mail
*/
private String to[] = { "root@localhost" };
/**
* cc-recipients of the mail
*/
private String cc[];
/**
* sender of the change
*/
private String from = null;
/**
* Mail Text. SpEL template using event as root;
*/
private Expression text;
/**
* Mail Subject. SpEL template using event as root;
*/
private Expression subject;
/**
* List of changes to ignore. Must be in Format OLD:NEW, for any status use
* * as wildcard, e.g. *:UP or OFFLINE:*
*/
private String[] ignoreChanges = { "UNKNOWN:UP" };
/**
* Enables the mail notification.
*/
private boolean enabled = true;
public MailNotifier(MailSender sender) {
this.sender = sender;
this.subject = parser.parseExpression(DEFAULT_SUBJECT, ParserContext.TEMPLATE_EXPRESSION);
this.text = parser.parseExpression(DEFAULT_TEXT, ParserContext.TEMPLATE_EXPRESSION);
}
@Override
public void onApplicationEvent(ClientApplicationStatusChangedEvent event) {
if (enabled && shouldSendMail(event.getFrom().getStatus(), event.getTo().getStatus())) {
try {
sendMail(event);
} catch (Exception ex) {
LOGGER.error("Couldn't send mail for Statuschange {} ", event, ex);
}
}
}
private void sendMail(ClientApplicationStatusChangedEvent event) throws MessagingException {
EvaluationContext context = new StandardEvaluationContext(event);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setFrom(from);
message.setSubject(subject.getValue(context, String.class));
message.setText(text.getValue(context, String.class));
message.setCc(cc);
sender.send(message);
}
private boolean shouldSendMail(String from, String to) {
return Arrays.binarySearch(ignoreChanges, (from + ":" + to)) < 0
&& Arrays.binarySearch(ignoreChanges, ("*:" + to)) < 0
&& Arrays.binarySearch(ignoreChanges, (from + ":*")) < 0;
}
public void setSender(JavaMailSender sender) {
this.sender = sender;
}
public void setTo(String[] to) {
this.to = to;
}
public void setCc(String[] cc) {
this.cc = cc;
}
public void setFrom(String from) {
this.from = from;
}
public void setSubject(String subject) {
this.subject = parser.parseExpression(subject, ParserContext.TEMPLATE_EXPRESSION);
}
public void setText(String text) {
this.text = parser.parseExpression(text, ParserContext.TEMPLATE_EXPRESSION);
}
public void setIgnoreChanges(String[] ignoreChanges) {
String[] copy = Arrays.copyOf(ignoreChanges, ignoreChanges.length);
Arrays.sort(copy);
this.ignoreChanges = copy;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
......@@ -6,10 +6,6 @@
{
"name": "spring.boot.admin.discovery",
"sourceType": "de.codecentric.boot.admin.config.DiscoveryClientConfiguration"
},
{
"name": "spring.boot.admin.monitor",
"sourceType": "de.codecentric.boot.admin.config.AdminServerWebConfiguration"
}
],"properties": [
{
......
......@@ -20,6 +20,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import de.codecentric.boot.admin.discovery.ApplicationDiscoveryListener;
import de.codecentric.boot.admin.notify.MailNotifier;
import de.codecentric.boot.admin.registry.store.ApplicationStore;
import de.codecentric.boot.admin.registry.store.HazelcastApplicationStore;
import de.codecentric.boot.admin.registry.store.SimpleApplicationStore;
......@@ -65,6 +66,14 @@ public class AdminServerWebConfigurationTest {
load("spring.boot.admin.hazelcast.enabled:false", "spring.boot.admin.discovery.enabled:false");
assertTrue(context.getBean(ApplicationStore.class) instanceof SimpleApplicationStore);
assertTrue(context.getBeansOfType(ApplicationDiscoveryListener.class).isEmpty());
assertTrue(context.getBeansOfType(MailNotifier.class).isEmpty());
}
@Test
public void simpleConfig_mail() {
load("spring.mail.host:localhost", "spring.boot.admin.hazelcast.enabled:false",
"spring.boot.admin.discovery.enabled:false");
assertTrue(context.getBean(MailNotifier.class) instanceof MailNotifier);
}
@Test
......
package de.codecentric.boot.admin.notify;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.model.StatusInfo;
public class MailNotifierTest {
private MailSender sender;
private MailNotifier notifier;
@Before
public void setup() {
sender = mock(MailSender.class);
notifier = new MailNotifier(sender);
notifier.setTo(new String[] { "foo@bar.com" });
notifier.setCc(new String[] { "bar@foo.com" });
notifier.setFrom("SBA <no-reply@example.com>");
}
@Test
public void test_onApplicationEvent() {
notifier.onApplicationEvent(new ClientApplicationStatusChangedEvent(new Object(),
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
StatusInfo.ofDown(), StatusInfo.ofUp()));
SimpleMailMessage expected = new SimpleMailMessage();
expected.setTo(new String[] { "foo@bar.com" });
expected.setCc(new String[] { "bar@foo.com" });
expected.setFrom("SBA <no-reply@example.com>");
expected.setText("App (-id-)\nstatus changed from DOWN to UP\n\nhttp://health");
expected.setSubject("App (-id-) is UP");
verify(sender).send(eq(expected));
}
@Test
public void test_onApplicationEvent_disbaled() {
notifier.setEnabled(false);
notifier.onApplicationEvent(new ClientApplicationStatusChangedEvent(new Object(),
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
StatusInfo.ofDown(), StatusInfo.ofUp()));
verify(sender, never()).send(isA(SimpleMailMessage.class));
}
@Test
public void test_onApplicationEvent_noSend() {
notifier.onApplicationEvent(new ClientApplicationStatusChangedEvent(new Object(),
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
StatusInfo.ofUnknown(), StatusInfo.ofUp()));
verify(sender, never()).send(isA(SimpleMailMessage.class));
}
@Test
public void test_onApplicationEvent_noSend_wildcard() {
notifier.setIgnoreChanges(new String[] { "*:UP" });
notifier.onApplicationEvent(new ClientApplicationStatusChangedEvent(new Object(),
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
StatusInfo.ofOffline(), StatusInfo.ofUp()));
verify(sender, never()).send(isA(SimpleMailMessage.class));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment