Commit 77767bff by Johannes Edmeier

Add properties to configure timeouts when querying the applications

Until now the thread querying the application endpoint can be blocked for a very long time when the application is inresponsive. With this commit two new properties `spring.boot.admin.monitor.read-timeout` (default: 2s) and `spring.boot.admin.monitor.connect-timeout` (default: 5s) are added. closes #462
parent 39264b1b
......@@ -17,6 +17,14 @@
| Lifetime of application statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired.
| 10.000
| spring.boot.admin.monitor.connect-timeout
| Lifetime of application statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired.
| 2.000
| spring.boot.admin.monitor.read-timeout
| Lifetime of application statuses in ms. The applications /health-endpoint will not be queried until the lifetime has expired.
| 5.000
| spring.boot.admin.routes.endpoints
| The enpoints which will be available via spring boot admin zuul proxy. If you write ui modules using other endpoints you need to add them.
| `"env, metrics, trace, dump, jolokia, info, configprops, activiti, logfile, refresh, flyway, liquibase, loggers"`
......
......@@ -15,17 +15,6 @@
*/
package de.codecentric.boot.admin.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.DefaultResponseErrorHandler;
import de.codecentric.boot.admin.journal.ApplicationEventJournal;
import de.codecentric.boot.admin.journal.store.JournaledEventStore;
import de.codecentric.boot.admin.journal.store.SimpleJournaledEventStore;
......@@ -40,97 +29,104 @@ import de.codecentric.boot.admin.web.client.ApplicationOperations;
import de.codecentric.boot.admin.web.client.BasicAuthHttpHeaderProvider;
import de.codecentric.boot.admin.web.client.HttpHeadersProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.DefaultResponseErrorHandler;
@Configuration
@EnableConfigurationProperties(AdminServerProperties.class)
public class AdminServerCoreConfiguration {
private final AdminServerProperties adminServerProperties;
public AdminServerCoreConfiguration(AdminServerProperties adminServerProperties) {
this.adminServerProperties = adminServerProperties;
}
@Bean
@ConditionalOnMissingBean
public ApplicationRegistry applicationRegistry(ApplicationStore applicationStore,
ApplicationIdGenerator applicationIdGenerator) {
return new ApplicationRegistry(applicationStore, applicationIdGenerator);
}
@Bean
@ConditionalOnMissingBean
public ApplicationIdGenerator applicationIdGenerator() {
return new HashingApplicationUrlIdGenerator();
}
@Bean
@ConditionalOnMissingBean
public HttpHeadersProvider httpHeadersProvider() {
return new BasicAuthHttpHeaderProvider();
}
@Bean
@ConditionalOnMissingBean
public ApplicationOperations applicationOperations(RestTemplateBuilder restTemplBuilder,
HttpHeadersProvider headersProvider) {
RestTemplateBuilder builder = restTemplBuilder
.messageConverters(new MappingJackson2HttpMessageConverter())
.errorHandler(new DefaultResponseErrorHandler() {
@Override
protected boolean hasError(HttpStatus statusCode) {
return false;
}
});
return new ApplicationOperations(builder.build(), headersProvider);
};
@Bean
@ConditionalOnMissingBean
public StatusUpdater statusUpdater(ApplicationStore applicationStore,
ApplicationOperations applicationOperations) {
StatusUpdater statusUpdater = new StatusUpdater(applicationStore, applicationOperations);
statusUpdater.setStatusLifetime(adminServerProperties.getMonitor().getStatusLifetime());
return statusUpdater;
}
@Bean
@Qualifier("updateTaskScheduler")
public ThreadPoolTaskScheduler updateTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(1);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("updateTask");
return taskScheduler;
}
@Bean
@ConditionalOnMissingBean
public StatusUpdateApplicationListener statusUpdateApplicationListener(
StatusUpdater statusUpdater,
@Qualifier("updateTaskScheduler") ThreadPoolTaskScheduler taskScheduler) {
StatusUpdateApplicationListener listener = new StatusUpdateApplicationListener(
statusUpdater, taskScheduler);
listener.setUpdatePeriod(adminServerProperties.getMonitor().getPeriod());
return listener;
}
@Bean
@ConditionalOnMissingBean
public ApplicationEventJournal applicationEventJournal(
JournaledEventStore journaledEventStore) {
return new ApplicationEventJournal(journaledEventStore);
}
@Bean
@ConditionalOnMissingBean
public JournaledEventStore journaledEventStore() {
return new SimpleJournaledEventStore();
}
@Bean
@ConditionalOnMissingBean
public ApplicationStore applicationStore() {
return new SimpleApplicationStore();
}
private final AdminServerProperties adminServerProperties;
public AdminServerCoreConfiguration(AdminServerProperties adminServerProperties) {
this.adminServerProperties = adminServerProperties;
}
@Bean
@ConditionalOnMissingBean
public ApplicationRegistry applicationRegistry(ApplicationStore applicationStore,
ApplicationIdGenerator applicationIdGenerator) {
return new ApplicationRegistry(applicationStore, applicationIdGenerator);
}
@Bean
@ConditionalOnMissingBean
public ApplicationIdGenerator applicationIdGenerator() {
return new HashingApplicationUrlIdGenerator();
}
@Bean
@ConditionalOnMissingBean
public HttpHeadersProvider httpHeadersProvider() {
return new BasicAuthHttpHeaderProvider();
}
@Bean
@ConditionalOnMissingBean
public ApplicationOperations applicationOperations(RestTemplateBuilder restTemplBuilder,
HttpHeadersProvider headersProvider) {
RestTemplateBuilder builder = restTemplBuilder.messageConverters(new MappingJackson2HttpMessageConverter())
.errorHandler(new DefaultResponseErrorHandler() {
@Override
protected boolean hasError(HttpStatus statusCode) {
return false;
}
});
builder = builder.setConnectTimeout(adminServerProperties.getMonitor().getConnectTimeout())
.setReadTimeout(adminServerProperties.getMonitor().getReadTimeout());
return new ApplicationOperations(builder.build(), headersProvider);
}
@Bean
@ConditionalOnMissingBean
public StatusUpdater statusUpdater(ApplicationStore applicationStore, ApplicationOperations applicationOperations) {
StatusUpdater statusUpdater = new StatusUpdater(applicationStore, applicationOperations);
statusUpdater.setStatusLifetime(adminServerProperties.getMonitor().getStatusLifetime());
return statusUpdater;
}
@Bean
@Qualifier("updateTaskScheduler")
public ThreadPoolTaskScheduler updateTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(1);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("updateTask");
return taskScheduler;
}
@Bean
@ConditionalOnMissingBean
public StatusUpdateApplicationListener statusUpdateApplicationListener(StatusUpdater statusUpdater,
@Qualifier("updateTaskScheduler") ThreadPoolTaskScheduler taskScheduler) {
StatusUpdateApplicationListener listener = new StatusUpdateApplicationListener(statusUpdater, taskScheduler);
listener.setUpdatePeriod(adminServerProperties.getMonitor().getPeriod());
return listener;
}
@Bean
@ConditionalOnMissingBean
public ApplicationEventJournal applicationEventJournal(JournaledEventStore journaledEventStore) {
return new ApplicationEventJournal(journaledEventStore);
}
@Bean
@ConditionalOnMissingBean
public JournaledEventStore journaledEventStore() {
return new SimpleJournaledEventStore();
}
@Bean
@ConditionalOnMissingBean
public ApplicationStore applicationStore() {
return new SimpleApplicationStore();
}
}
package de.codecentric.boot.admin.config;
import java.util.Arrays;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.boot.admin")
public class AdminServerProperties {
/**
* The context-path prefixes the path where the Admin Servers statics assets and api should be
* served. Relative to the Dispatcher-Servlet.
*/
private String contextPath = "";
private MonitorProperties monitor = new MonitorProperties();
private RoutesProperties routes = new RoutesProperties();
public void setContextPath(String pathPrefix) {
if (!pathPrefix.startsWith("/") || pathPrefix.endsWith("/")) {
throw new IllegalArgumentException(
"ContextPath must start with '/' and not end with '/'");
}
this.contextPath = pathPrefix;
}
public String getContextPath() {
return contextPath;
}
public MonitorProperties getMonitor() {
return monitor;
}
public RoutesProperties getRoutes() {
return routes;
}
public static class MonitorProperties {
/**
* Time interval in ms to update the status of applications with expired statusInfo
*/
private long period = 10_000L;
/**
* Lifetime of status in ms. The status won't be updated as long the last status isn't
* expired.
*/
private long statusLifetime = 10_000L;
public void setPeriod(long period) {
this.period = period;
}
public long getPeriod() {
return period;
}
public void setStatusLifetime(long statusLifetime) {
this.statusLifetime = statusLifetime;
}
public long getStatusLifetime() {
return statusLifetime;
}
}
public static class RoutesProperties {
/**
* Endpoints to be proxified by spring boot admin.
*/
private String[] endpoints = { "env", "metrics", "trace", "dump", "jolokia", "info",
"logfile", "refresh", "flyway", "liquibase", "heapdump", "loggers", "auditevents" };
public String[] getEndpoints() {
return endpoints;
}
public void setEndpoints(String[] endpoints) {
this.endpoints = Arrays.copyOf(endpoints, endpoints.length);
}
}
/**
* The context-path prefixes the path where the Admin Servers statics assets and api should be
* served. Relative to the Dispatcher-Servlet.
*/
private String contextPath = "";
private MonitorProperties monitor = new MonitorProperties();
private RoutesProperties routes = new RoutesProperties();
public void setContextPath(String pathPrefix) {
if (!pathPrefix.startsWith("/") || pathPrefix.endsWith("/")) {
throw new IllegalArgumentException("ContextPath must start with '/' and not end with '/'");
}
this.contextPath = pathPrefix;
}
public String getContextPath() {
return contextPath;
}
public MonitorProperties getMonitor() {
return monitor;
}
public RoutesProperties getRoutes() {
return routes;
}
public static class MonitorProperties {
/**
* Time interval in ms to update the status of applications with expired statusInfo
*/
private long period = 10_000L;
/**
* Lifetime of status in ms. The status won't be updated as long the last status isn't
* expired.
*/
private long statusLifetime = 10_000L;
/**
* Connect timeout when querying the applications' status and info.
*/
private int connectTimeout = 2_000;
/**
* read timeout when querying the applications' status and info.
*/
private int readTimeout = 5_000;
public void setPeriod(long period) {
this.period = period;
}
public long getPeriod() {
return period;
}
public void setStatusLifetime(long statusLifetime) {
this.statusLifetime = statusLifetime;
}
public long getStatusLifetime() {
return statusLifetime;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
}
public static class RoutesProperties {
/**
* Endpoints to be proxified by spring boot admin.
*/
private String[] endpoints = {"env", "metrics", "trace", "dump", "jolokia", "info", "logfile", "refresh", "flyway", "liquibase", "heapdump", "loggers", "auditevents"};
public String[] getEndpoints() {
return endpoints;
}
public void setEndpoints(String[] endpoints) {
this.endpoints = Arrays.copyOf(endpoints, endpoints.length);
}
}
}
......@@ -15,12 +15,12 @@
*/
package de.codecentric.boot.admin.web.client;
import static java.util.Arrays.asList;
import de.codecentric.boot.admin.model.Application;
import java.io.Serializable;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
......@@ -31,50 +31,44 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import de.codecentric.boot.admin.model.Application;
/**
* Handles all rest operations invoked on a registered application.
*
* @author Johannes Edmeier
*/
public class ApplicationOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationOperations.class);
@SuppressWarnings("unchecked")
private static final Class<Map<String, Serializable>> RESPONSE_TYPE_MAP = (Class<Map<String, Serializable>>) (Class<?>) Map.class;
private final RestTemplate restTemplate;
private final HttpHeadersProvider httpHeadersProvider;
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationOperations.class);
@SuppressWarnings("unchecked")
private static final Class<Map<String, Serializable>> RESPONSE_TYPE_MAP = (Class<Map<String, Serializable>>) (Class<?>) Map.class;
private final RestTemplate restTemplate;
private final HttpHeadersProvider httpHeadersProvider;
public ApplicationOperations(RestTemplate restTemplate,
HttpHeadersProvider httpHeadersProvider) {
this.restTemplate = restTemplate;
this.httpHeadersProvider = httpHeadersProvider;
}
public ApplicationOperations(RestTemplate restTemplate, HttpHeadersProvider httpHeadersProvider) {
this.restTemplate = restTemplate;
this.httpHeadersProvider = httpHeadersProvider;
}
public ResponseEntity<Map<String, Serializable>> getInfo(
Application application) {
URI uri = UriComponentsBuilder.fromHttpUrl(application.getManagementUrl())
.pathSegment("info").build().toUri();
return doGet(application, uri, RESPONSE_TYPE_MAP);
}
public ResponseEntity<Map<String, Serializable>> getInfo(Application application) {
URI uri = UriComponentsBuilder.fromHttpUrl(application.getManagementUrl()).pathSegment("info").build().toUri();
return doGet(application, uri, RESPONSE_TYPE_MAP);
}
public ResponseEntity<Map<String, Serializable>> getHealth(
Application application) {
URI uri = UriComponentsBuilder.fromHttpUrl(application.getHealthUrl()).build().toUri();
return doGet(application, uri, RESPONSE_TYPE_MAP);
}
public ResponseEntity<Map<String, Serializable>> getHealth(Application application) {
URI uri = UriComponentsBuilder.fromHttpUrl(application.getHealthUrl()).build().toUri();
return doGet(application, uri, RESPONSE_TYPE_MAP);
}
protected <T> ResponseEntity<T> doGet(Application application, URI uri, Class<T> responseType) {
LOGGER.debug("Fetching '{}' for {}", uri, application);
protected <T> ResponseEntity<T> doGet(Application application, URI uri, Class<T> responseType) {
LOGGER.debug("Fetching '{}' for {}", uri, application);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(asList(MediaType.APPLICATION_JSON));
headers.putAll(httpHeadersProvider.getHeaders(application));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.putAll(httpHeadersProvider.getHeaders(application));
ResponseEntity<T> response = restTemplate.exchange(uri, HttpMethod.GET,
new HttpEntity<Void>(headers), responseType);
ResponseEntity<T> response = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<Void>(headers),
responseType);
LOGGER.debug("'{}' responded with {}", uri, response);
return response;
}
LOGGER.debug("'{}' responded with {}", uri, response);
return response;
}
}
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