Commit 76df8b0a by Johannes Edmeier

Merge remote-tracking branch 'upstream/master' into 2.x

parents 788c9ba6 0a029fc9
......@@ -32,13 +32,13 @@ This application provides a simple UI to administrate Spring Boot applications.
## Getting Started
[A quick guide](http://codecentric.github.io/spring-boot-admin/1.5.2/#getting-started) to get started can be found in our docs.
[A quick guide](http://codecentric.github.io/spring-boot-admin/1.5.3/#getting-started) to get started can be found in our docs.
## Getting Help
Having trouble with Spring Boot Admin? We’d like to help!
* Check the [reference documentation](http://codecentric.github.io/spring-boot-admin/1.5.2/).
* Check the [reference documentation](http://codecentric.github.io/spring-boot-admin/1.5.3/).
* Ask a question on [stackoverflow.com](http://stackoverflow.com/questions/tagged/spring-boot-admin) - we monitor questions tagged with `spring-boot-admin`.
......@@ -47,7 +47,7 @@ Having trouble with Spring Boot Admin? We’d like to help!
* Report bugs with Spring Boot Admin at http://github.com/codecentric/spring-boot-admin/issues.
## Reference Guide
[Version 1.5.2](http://codecentric.github.io/spring-boot-admin/1.5.2/)
[Version 1.5.3](http://codecentric.github.io/spring-boot-admin/1.5.3/)
[Version 1.4.6](http://codecentric.github.io/spring-boot-admin/1.4.6/)
......
......@@ -36,12 +36,12 @@
<main.basedir>${basedir}</main.basedir>
<spring-boot.version>2.0.0.M3</spring-boot.version>
<spring-cloud.version>Finchley.BUILD-SNAPSHOT</spring-cloud.version>
<build-plugin.jacoco.version>0.7.7.201606060606</build-plugin.jacoco.version>
<build-plugin.coveralls.version>4.2.0</build-plugin.coveralls.version>
<build-plugin.jacoco.version>0.7.9</build-plugin.jacoco.version>
<build-plugin.coveralls.version>4.3.0</build-plugin.coveralls.version>
<build-plugin.gpg.version>1.6</build-plugin.gpg.version>
<build-plugin.asciidoctor.version>1.5.3</build-plugin.asciidoctor.version>
<build-plugin.exec.version>1.4.0</build-plugin.exec.version>
<build-plugin.git-commit-id.version>2.2.1</build-plugin.git-commit-id.version>
<build-plugin.asciidoctor.version>1.5.5</build-plugin.asciidoctor.version>
<build-plugin.exec.version>1.6.0</build-plugin.exec.version>
<build-plugin.git-commit-id.version>2.2.2</build-plugin.git-commit-id.version>
</properties>
<modules>
<module>spring-boot-admin-server</module>
......
......@@ -85,7 +85,15 @@ spring.boot.admin.client.password
| Interval for repeating the registration (in ms).
| `10.000`
| spring.boot.admin.client.auto-registration
| spring.boot.admin.connectTimeout
| Connect timeout for the registration (in ms).
| `5.000`
| spring.boot.admin.readTimeout
| Read timeout for the registration (in ms).
| `5.000`
| spring.boot.admin.auto-registration
| If set to true the periodic task to register the application is automatically scheduled after the application is ready.
| `true`
......
......@@ -44,7 +44,7 @@ See also the https://github.com/codecentric/spring-boot-admin/tree/master/spring
[[register-client-applications]]
=== Registering client applications ===
To register your application at the SBA Server you can either include the SBA Client or use http://projects.spring.io/spring-cloud/spring-cloud.html[Spring Cloud Discovery] (e.g. Eureka)
To register your application at the SBA Server you can either include the SBA Client or use http://projects.spring.io/spring-cloud/spring-cloud.html[Spring Cloud Discovery] (e.g. Eureka, Consul, ...). There is also a <<spring-cloud-discovery-static-config,simple option using a static configuration on the SBA Server side>>.
[[register-clients-via-spring-boot-admin]]
==== spring-boot-admin-starter-client ====
......
[[spring-cloud-discovery-support]]
=== Spring Cloud Discovery ===
The Spring Boot Admin Server can use Spring Clouds `DiscoveryClient` to discover applications. The advantage is that the clients don't have to include the `spring-boot-admin-starter-client`. You just have to add a DiscoveryClient to your admin server - everything else is done by AutoConfiguration.
The setup is explained <<discover-clients-via-spring-cloud-discovery,above>>.
==== ServiceInstanceConverter ====
The Spring Boot Admin Server can use Spring Clouds `DiscoveryClient` to discover applications. The advantage is that the clients don't have to include the `spring-boot-admin-starter-client`. You just have to add a `DiscoveryClient` implementation to your admin server - everything else is done by AutoConfiguration.
[[spring-cloud-discovery-static-config]]
==== SimpleDiscoveryClient configuration ====
Spring Boot Admin ships with the `SimpleDiscoveryClient` included. This allows you to specify client applications via configuration, without adding the SBA Client or a DiscoveryClient implementation to your monitored applications:
[source,yml]
.application.yml
----
spring:
cloud:
discovery:
client:
simple:
instances:
test:
- uri: http://instance1.intern:8080
metadata:
management.context-path: /actuator
- uri: http://instance2.intern:8080
metadata:
management.context-path: /actuator
----
==== Other DiscoveryClient implementations (Eureka, Zookeeper, Consul, ...) ====
Spring Boot Admin supports all other implementation of Spring Cloud's `DiscoveryClient`. You need to add it to the Spring Boot Admin Server and configure it properly.
An <<discover-clients-via-spring-cloud-discovery,example setup using Eureka>> is shown above.
==== Converting ServiceInstances into monitored applications ====
The information from the service registry are converted by the `ServiceInstanceConverter`. Spring Boot Admin ships with a default and Eureka converter implementation. The correct one is selected by AutoConfiguration.
......@@ -12,6 +38,28 @@ TIP: You can modify how the information from the registry is used to register th
NOTE: When using Eureka, the `healthCheckUrl` known to Eureka is used for health-checking, which can be set on your client using `eureka.instance.healthCheckUrl`.
.Instance metadata options
|===
| Key |Value |Default value
| user.name +
user.password
| Credentials being used to access the endpoints.
|
| management.port
| The port is substituted in the service URL and will be used for accessing the actuator endpoints.
|
| management.context-path
| The path is appended to the service URL and will be used for accessing the actuator endpoints.
| `${spring.boot.admin.discovery.converter.mangement-context-path}`
| health.path
| The path is appended to the service URL and will be used for the health-checking. Ignored by the `EurekaServiceInstanceConverter`.
| `${spring.boot.admin.discovery.converter.health-endpoint}`
|===
.Discovery configuration options
|===
| Property name |Description |Default value
......@@ -36,25 +84,3 @@ NOTE: When using Eureka, the `healthCheckUrl` known to Eureka is used for health
| This services will be included when using discovery and registered as application. Supports simple patterns (e.g. "foo*", "*bar", "foo*bar*").
| `"*"`
|===
.Instance metadata options
|===
| Key |Value |Default value
| user.name +
user.password
| Credentials being used to access the endpoints.
|
| management.port
| The port is substituted in the service URL and will be used for accessing the actuator endpoints.
|
| management.context-path
| The path is appended to the service URL and will be used for accessing the actuator endpoints.
| `${spring.boot.admin.discovery.converter.mangement-context-path}`
| health.path
| The path is appended to the service URL and will be used for the health-checking. Ignored by the `EurekaServiceInstanceConverter`.
| `${spring.boot.admin.discovery.converter.health-endpoint}`
|===
......@@ -165,6 +165,60 @@ To enable pagerduty notifications you just have to add a generic service to your
|
|===
[[opsgenie-notifications]]
==== OpsGenie notifications ====
To enable OpsGenie notifications you just have to add a new JSON Rest API integration to your OpsGenie account and set `spring.boot.admin.notify.opsgenie.api-key` to the apiKey you received.
.OpsGenie notifications configuration options
|===
| Property name |Description |Default value
| spring.boot.admin.notify.opsgenie.enabled
| Enable OpsGenie notifications
| `true`
| spring.boot.admin.notify.opsgenie.ignore-changes
| Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.
| `"UNKNOWN:UP"`
| spring.boot.admin.notify.opsgenie.api-key
| apiKey you received when creating the integration
|
| spring.boot.admin.notify.opsgenie.url
| OpsGenie Alert API url
| `+++"https://api.opsgenie.com/v1/json/alert"+++`
| spring.boot.admin.notify.opsgenie.description
| Description to use in the event. SpEL-expressions are supported
| `+++"#{application.name}/#{application.id} is #{to.status}"+++`
| spring.boot.admin.notify.opsgenie.recipients
| User, group, schedule or escalation names to calculate which users will receive the notifications of the alert.
|
| spring.boot.admin.notify.opsgenie.actions
| Comma separated list of actions that can be executed.
|
| spring.boot.admin.notify.opsgenie.source
| Field to specify source of alert. By default, it will be assigned to IP address of incoming request.
|
| spring.boot.admin.notify.opsgenie.tags
| Comma separated list of labels attached to the alert.
|
| spring.boot.admin.notify.opsgenie.entity
| The entity the alert is related to.
|
| spring.boot.admin.notify.opsgenie.user
| Default owner of the execution. If user is not specified, the system becomes owner of the execution.
|
|===
[hipchat-notifications]
==== Hipchat notifications ====
To enable Hipchat notifications you need to create an API token from you Hipchat account and set the appropriate configuration properties.
......
......@@ -76,7 +76,7 @@ include::{samples-dir}/spring-boot-admin-sample-eureka/src/main/resources/applic
==== Activiti UI Module ====
The Activiti module shows information from the `/activti` endpoint.
The Activiti module shows information from the `/activiti` endpoint.
. Add the ui module to your classpath:
+
......
......@@ -21,7 +21,7 @@
<parent>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-samples</artifactId>
......
......@@ -22,7 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootAdminApplication.class})
@SpringBootTest(classes = {SpringBootAdminApplication.class}, properties = {"spring.cloud.consul.enabled=false"})
public class SpringBootAdminApplicationTest {
@Test
public void contextLoads() {
......
......@@ -22,7 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootAdminApplication.class})
@SpringBootTest(classes = {SpringBootAdminApplication.class}, properties = {"spring.cloud.zookeeper.enabled=false"})
public class SpringBootAdminApplicationTest {
@Test
public void contextLoads() {
......
......@@ -23,6 +23,7 @@ import de.codecentric.boot.admin.server.notify.LetsChatNotifier;
import de.codecentric.boot.admin.server.notify.MailNotifier;
import de.codecentric.boot.admin.server.notify.NotificationTrigger;
import de.codecentric.boot.admin.server.notify.Notifier;
import de.codecentric.boot.admin.server.notify.OpsGenieNotifier;
import de.codecentric.boot.admin.server.notify.SlackNotifier;
import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController;
......@@ -153,4 +154,16 @@ public class NotifierConfiguration {
return new LetsChatNotifier(repository);
}
}
@Configuration
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.opsgenie", name = "api-key")
@AutoConfigureBefore({NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class})
public static class OpsGenieNotifierConfiguration {
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("spring.boot.admin.notify.opsgenie")
public OpsGenieNotifier opsgenieNotifier(ApplicationRepository repository) {
return new OpsGenieNotifier(repository);
}
}
}
......@@ -29,6 +29,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
......@@ -73,6 +74,11 @@ public class ApplicationDiscoveryListener {
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
discover();
}
@EventListener
public void onInstanceRegistered(InstanceRegisteredEvent<?> event) {
discover();
}
......
/*
* Copyright 2014-2017 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.server.notify;
import de.codecentric.boot.admin.server.domain.entities.Application;
import de.codecentric.boot.admin.server.domain.entities.ApplicationRepository;
import de.codecentric.boot.admin.server.domain.events.ClientApplicationEvent;
import de.codecentric.boot.admin.server.domain.events.ClientApplicationStatusChangedEvent;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.expression.MapAccessor;
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
/**
* Notifier submitting events to opsgenie.com.
*
* @author Fernando Sure
*/
public class OpsGenieNotifier extends AbstractStatusChangeNotifier {
private static final URI DEFAULT_URI = URI.create("https://api.opsgenie.com/v1/json/alert");
private static final String DEFAULT_MESSAGE = "#{application.registration.name}/#{application.id} is #{application.statusInfo.status}";
private final SpelExpressionParser parser = new SpelExpressionParser();
private RestTemplate restTemplate = new RestTemplate();
/**
* BASE URL for OpsGenie API
*/
private URI url = DEFAULT_URI;
/**
* Integration ApiKey
*/
private String apiKey;
/**
* Comma separated list of users, groups, schedules or escalation names
* to calculate which users will receive the notifications of the alert.
*/
private String recipients;
/**
* Comma separated list of actions that can be executed.
*/
private String actions;
/**
* Field to specify source of alert. By default, it will be assigned to IP address of incoming request
*/
private String source;
/**
* Comma separated list of labels attached to the alert
*/
private String tags;
/**
* The entity the alert is related to.
*/
private String entity;
/**
* Default owner of the execution. If user is not specified, the system becomes owner of the execution.
*/
private String user;
/**
* Trigger description. SpEL template using event as root;
*/
private Expression description;
public OpsGenieNotifier(ApplicationRepository repositpry) {
super(repositpry);
this.description = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
}
@Override
protected Mono<Void> doNotify(ClientApplicationEvent event, Application application) {
return Mono.fromRunnable(
() -> restTemplate.exchange(buildUrl(event), HttpMethod.POST, createRequest(event, application),
Void.class));
}
protected String buildUrl(ClientApplicationEvent event) {
if ((event instanceof ClientApplicationStatusChangedEvent) &&
("UP".equals(((ClientApplicationStatusChangedEvent) event).getStatusInfo().getStatus()))) {
return String.format("%s/close", url.toString());
}
return url.toString();
}
protected HttpEntity createRequest(ClientApplicationEvent event, Application application) {
Map<String, Object> body = new HashMap<>();
body.put("apiKey", apiKey);
body.put("message", getMessage(event, application));
body.put("alias", application.getRegistration().getName() + "/" + application.getId());
body.put("description", getDescription(event, application));
if (event instanceof ClientApplicationStatusChangedEvent &&
!"UP".equals(((ClientApplicationStatusChangedEvent) event).getStatusInfo().getStatus())) {
if (recipients != null) {
body.put("recipients", recipients);
}
if (actions != null) {
body.put("actions", actions);
}
if (source != null) {
body.put("source", source);
}
if (tags != null) {
body.put("tags", tags);
}
if (entity != null) {
body.put("entity", entity);
}
if (user != null) {
body.put("user", user);
}
Map<String, Object> details = new HashMap<>();
details.put("type", "link");
details.put("href", application.getRegistration().getHealthUrl());
details.put("text", "Application health-endpoint");
body.put("details", details);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(body, headers);
}
protected String getMessage(ClientApplicationEvent event, Application application) {
Map<String, Object> root = new HashMap<>();
root.put("event", event);
root.put("application", application);
root.put("lastStatus", getLastStatus(event.getApplication()));
StandardEvaluationContext context = new StandardEvaluationContext(root);
context.addPropertyAccessor(new MapAccessor());
return description.getValue(context, String.class);
}
protected String getDescription(ClientApplicationEvent event, Application application) {
return String.format("Application %s (%s) went from %s to %s", application.getRegistration().getName(),
application.getId(), getLastStatus(application.getId()),
((ClientApplicationStatusChangedEvent) event).getStatusInfo().getStatus());
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiKey() {
return apiKey;
}
public void setDescription(String description) {
this.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);
}
public String getMessage() {
return description.getExpressionString();
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getRecipients() {
return recipients;
}
public void setRecipients(String recipients) {
this.recipients = recipients;
}
public String getActions() {
return actions;
}
public void setActions(String actions) {
this.actions = actions;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTags() {
return tags;
}
public void setTags(String tags) {
this.tags = tags;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
}
......@@ -25,6 +25,7 @@ import de.codecentric.boot.admin.server.notify.HipchatNotifier;
import de.codecentric.boot.admin.server.notify.MailNotifier;
import de.codecentric.boot.admin.server.notify.NotificationTrigger;
import de.codecentric.boot.admin.server.notify.Notifier;
import de.codecentric.boot.admin.server.notify.OpsGenieNotifier;
import de.codecentric.boot.admin.server.notify.SlackNotifier;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
......@@ -34,6 +35,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
......@@ -43,6 +45,8 @@ import org.springframework.context.annotation.Primary;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
public class NotifierConfigurationTest {
private static final ClientApplicationEvent APP_DOWN = new ClientApplicationStatusChangedEvent(
......@@ -95,6 +99,13 @@ public class NotifierConfigurationTest {
assertThat(context.getBean(SlackNotifier.class)).isInstanceOf(SlackNotifier.class);
}
@Test
public void test_opsgenie() {
load(null, "spring.boot.admin.notify.opsgenie.api-key:foo");
Assert.assertThat(context.getBean(OpsGenieNotifier.class), is(instanceOf(OpsGenieNotifier.class)));
}
@Test
public void test_multipleNotifiers() {
load(TestMultipleNotifierConfig.class);
......
......@@ -23,6 +23,7 @@ import de.codecentric.boot.admin.server.services.HashingApplicationUrlIdGenerato
import reactor.test.StepVerifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
......@@ -56,6 +57,20 @@ public class ApplicationDiscoveryListenerTest {
}
@Test
public void test_application_ready() {
when(discovery.getServices()).thenReturn(Collections.singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
Collections.singletonList(new DefaultServiceInstance("service", "localhost", 80, false)));
listener.onApplicationReady(null);
StepVerifier.create(registry.getApplications())
.assertNext(a -> assertThat(a.getRegistration().getName()).isEqualTo("service"))
.verifyComplete();
}
@Test
public void test_ignore() {
when(discovery.getServices()).thenReturn(singletonList("service"));
when(discovery.getInstances("service")).thenReturn(
......
/*
* Copyright 2014-2017 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.server.notify;
import de.codecentric.boot.admin.server.domain.entities.Application;
import de.codecentric.boot.admin.server.domain.entities.ApplicationRepository;
import de.codecentric.boot.admin.server.domain.events.ClientApplicationStatusChangedEvent;
import de.codecentric.boot.admin.server.domain.values.ApplicationId;
import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class OpsGenieNotifierTest {
private OpsGenieNotifier notifier;
private RestTemplate restTemplate;
private ApplicationRepository repository;
private static final String appName = "App";
private static final Application application = Application.create(ApplicationId.of("-id-"))
.register(Registration.create(appName, "http://health")
.build());
@Before
public void setUp() {
repository = mock(ApplicationRepository.class);
when(repository.find(application.getId())).thenReturn(Mono.just(application));
restTemplate = mock(RestTemplate.class);
notifier = new OpsGenieNotifier(repository);
notifier.setApiKey("--service--");
notifier.setRecipients("--recipients--");
notifier.setRestTemplate(restTemplate);
}
@Test
public void test_onApplicationEvent_resolve() {
StepVerifier.create(notifier.notify(
new ClientApplicationStatusChangedEvent(application.getId(), application.getVersion() + 1,
StatusInfo.ofDown()))).verifyComplete();
reset(restTemplate);
when(repository.find(application.getId())).thenReturn(Mono.just(application.withStatusInfo(StatusInfo.ofUp())));
StepVerifier.create(notifier.notify(
new ClientApplicationStatusChangedEvent(application.getId(), application.getVersion() + 2,
StatusInfo.ofUp()))).verifyComplete();
verify(restTemplate).exchange(eq("https://api.opsgenie.com/v1/json/alert/close"), eq(HttpMethod.POST),
eq(expectedRequest("DOWN", "UP")), eq(Void.class));
}
@Test
public void test_onApplicationEvent_trigger() {
StepVerifier.create(notifier.notify(
new ClientApplicationStatusChangedEvent(application.getId(), application.getVersion() + 1,
StatusInfo.ofUp()))).verifyComplete();
reset(restTemplate);
when(repository.find(application.getId())).thenReturn(
Mono.just(application.withStatusInfo(StatusInfo.ofDown())));
StepVerifier.create(notifier.notify(
new ClientApplicationStatusChangedEvent(application.getId(), application.getVersion() + 2,
StatusInfo.ofDown()))).verifyComplete();
verify(restTemplate).exchange(eq("https://api.opsgenie.com/v1/json/alert"), eq(HttpMethod.POST),
eq(expectedRequest("UP", "DOWN")), eq(Void.class));
}
private String getMessage(String expectedStatus) {
return String.format("App/-id- is %s", expectedStatus);
}
private String getDescription(String expectedOldStatus, String expectedNewStatus) {
return String.format("Application App (-id-) went from %s to %s", expectedOldStatus, expectedNewStatus);
}
private HttpEntity expectedRequest(String expectedOldStatus, String expectedNewStatus) {
Map<String, Object> expected = new HashMap<>();
expected.put("apiKey", "--service--");
expected.put("message", getMessage(expectedNewStatus));
expected.put("alias", "App/-id-");
expected.put("description", getDescription(expectedOldStatus, expectedNewStatus));
if (!"UP".equals(expectedNewStatus)) {
expected.put("recipients", "--recipients--");
Map<String, Object> details = new HashMap<>();
details.put("type", "link");
details.put("href", "http://health");
details.put("text", "Application health-endpoint");
expected.put("details", details);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(expected, headers);
}
}
......@@ -37,6 +37,16 @@ public class ClientProperties {
private long period = 10_000L;
/**
* Connect timeout (in ms) for the registration.
*/
private int connectTimeout = 5_000;
/**
* Read timeout (in ms) for the registration.
*/
private int readTimeout = 5_000;
/**
* Username for basic authentication on admin server
*/
private String username;
......
......@@ -19,10 +19,9 @@ import de.codecentric.boot.admin.client.registration.ApplicationFactory;
import de.codecentric.boot.admin.client.registration.ApplicationRegistrator;
import de.codecentric.boot.admin.client.registration.DefaultApplicationFactory;
import de.codecentric.boot.admin.client.registration.RegistrationApplicationListener;
import javax.servlet.ServletContext;
import de.codecentric.boot.admin.client.registration.ServletApplicationFactory;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
......@@ -66,7 +65,9 @@ public class SpringBootAdminClientAutoConfiguration {
ApplicationFactory applicationFactory,
RestTemplateBuilder restTemplBuilder) {
RestTemplateBuilder builder = restTemplBuilder.messageConverters(new MappingJackson2HttpMessageConverter())
.requestFactory(SimpleClientHttpRequestFactory.class);
.requestFactory(SimpleClientHttpRequestFactory.class)
.setConnectTimeout(client.getConnectTimeout())
.setReadTimeout(client.getReadTimeout());
if (client.getUsername() != null) {
builder = builder.basicAuthorization(client.getUsername(), client.getPassword());
}
......
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