diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 0425a6be..1736a465 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -1166,6 +1166,10 @@ If you use another form of security you might need to <> to the `ConfigServicePropertySourceLocator` (e.g. by grabbing it in the bootstrap context and injecting one). +==== Health Indicator + +The Config Client supplies a Spring Boot Health Indicator that attempts to load configuration from Config Server. The health indicator can be disabled by setting `health.config.enabled=false`. The response is also cached for performance reasons. The default cache time to live is 5 minutes. To change that value set the `health.config.time-to-live` property (in milliseconds). + [[custom-rest-template]] ==== Providing A Custom RestTemplate diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java index 7e6f7e9b..ed75bac1 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java @@ -53,6 +53,11 @@ public class ConfigClientAutoConfiguration { return client; } + @Bean + public ConfigClientHealthProperties configClientHealthProperties() { + return new ConfigClientHealthProperties(); + } + @Configuration @ConditionalOnClass(HealthIndicator.class) @ConditionalOnBean(ConfigServicePropertySourceLocator.class) @@ -61,24 +66,9 @@ public class ConfigClientAutoConfiguration { @Bean public ConfigServerHealthIndicator configServerHealthIndicator( - ConfigServicePropertySourceLocator locator, Environment environment) { - return new ConfigServerHealthIndicator(locator, environment); - } - } - - @ConfigurationProperties("health.config") - public static class Health { - /** - * Flag to indicate that the config server health indicator should be installed. - */ - boolean enabled; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; + ConfigServicePropertySourceLocator locator, + ConfigClientHealthProperties properties, Environment environment) { + return new ConfigServerHealthIndicator(locator, environment, properties); } } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java new file mode 100644 index 00000000..717b963e --- /dev/null +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientHealthProperties.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2016 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 org.springframework.cloud.config.client; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Spencer Gibb + */ +@ConfigurationProperties("health.config") +public class ConfigClientHealthProperties { + /** + * Flag to indicate that the config server health indicator should be installed. + */ + boolean enabled; + + /** + * Time to live for cached result, in milliseconds. Default 300000 (5 min). + */ + private long timeToLive = 60 * 5 * 1000; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public long getTimeToLive() { + return timeToLive; + } + + public void setTimeToLive(long timeToLive) { + this.timeToLive = timeToLive; + } +} diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java index 31932cba..b77b1a18 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerHealthIndicator.java @@ -16,17 +16,23 @@ import org.springframework.core.env.PropertySource; public class ConfigServerHealthIndicator extends AbstractHealthIndicator { private ConfigServicePropertySourceLocator locator; + private ConfigClientHealthProperties properties; private Environment environment; + private long lastAccess = 0; + + private PropertySource cached; + public ConfigServerHealthIndicator(ConfigServicePropertySourceLocator locator, - Environment environment) { + Environment environment, ConfigClientHealthProperties properties) { this.environment = environment; this.locator = locator; + this.properties = properties; } @Override protected void doHealthCheck(Builder builder) throws Exception { - PropertySource propertySource = locator.locate(this.environment); + PropertySource propertySource = getPropertySource(); builder.up(); if (propertySource instanceof CompositePropertySource) { List sources = new ArrayList<>(); @@ -40,4 +46,21 @@ public class ConfigServerHealthIndicator extends AbstractHealthIndicator { builder.unknown().withDetail("error", "no property sources located"); } } + + private PropertySource getPropertySource() { + long accessTime = System.currentTimeMillis(); + if (isCacheStale(accessTime)) { + this.lastAccess = accessTime; + this.cached = locator.locate(this.environment); + } + return this.cached; + } + + private boolean isCacheStale(long accessTime) { + if (this.cached == null) { + return true; + } + return (accessTime - this.lastAccess) >= this.properties.getTimeToLive(); + } + } diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java index 321d9891..3593e3fd 100644 --- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java +++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerHealthIndicatorTests.java @@ -17,11 +17,16 @@ package org.springframework.cloud.config.client; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import java.util.Collections; import org.junit.Test; -import org.mockito.Mockito; import org.springframework.boot.actuate.health.Status; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; @@ -34,11 +39,11 @@ import org.springframework.core.env.PropertySource; */ public class ConfigServerHealthIndicatorTests { - private ConfigServicePropertySourceLocator locator = Mockito - .mock(ConfigServicePropertySourceLocator.class); - private Environment environment = Mockito.mock(Environment.class); + private ConfigServicePropertySourceLocator locator = + mock(ConfigServicePropertySourceLocator.class); + private Environment environment = mock(Environment.class); private ConfigServerHealthIndicator indicator = new ConfigServerHealthIndicator( - locator, environment); + locator, environment, new ConfigClientHealthProperties()); @Test public void testDefaultStatus() { @@ -48,15 +53,32 @@ public class ConfigServerHealthIndicatorTests { @Test public void testExceptionStatus() { - Mockito.doThrow(new IllegalStateException()).when(locator).locate(Mockito.any(Environment.class)); + doThrow(new IllegalStateException()).when(locator).locate(any(Environment.class)); assertEquals(Status.DOWN, indicator.health().getStatus()); + verify(locator, times(1)).locate(any(Environment.class)); } @Test public void testServerUp() { PropertySource source = new MapPropertySource("foo", Collections.emptyMap()); - Mockito.doReturn(source).when(locator).locate(Mockito.any(Environment.class)); + doReturn(source).when(locator).locate(any(Environment.class)); assertEquals(Status.UP, indicator.health().getStatus()); + verify(locator, times(1)).locate(any(Environment.class)); } + @Test + public void healthIsCached() { + PropertySource source = new MapPropertySource("foo", Collections.emptyMap()); + doReturn(source).when(locator).locate(any(Environment.class)); + + // not cached + assertEquals(Status.UP, indicator.health().getStatus()); + + // cached + assertEquals(Status.UP, indicator.health().getStatus()); + + verify(locator, times(1)).locate(any(Environment.class)); + } + + }