Polish disk space health indicator

- Supply auto-configuration for the new indicator
 - As suggested in the pull request, include the free disk space and
   configured threshold in the health details
 - Update the documentation to describe the indicator and its
   two configuration settings
 - Use @ConfigurationProperties to bind the indicator's configuration.
   This should make the changes sympathetic to the work being done
   to automate the configuration properties documentation

Closes gh-1297
pull/1638/merge
Andy Wilkinson 10 years ago
parent 78d7fe9cb5
commit 97178915a4

@ -30,6 +30,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator;
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator;
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator;
@ -52,6 +54,7 @@ import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
@ -256,4 +259,22 @@ public class HealthIndicatorAutoConfiguration {
} }
} }
@Configuration
@ConditionalOnExpression("${health.diskspace.enabled:true}")
public static class DiskSpaceHealthIndicatorConfiguration {
@Bean
@ConditionalOnMissingBean(name = "diskSpaceHealthIndicator")
public HealthIndicator diskSpaceHealthIndicator(
DiskSpaceHealthIndicatorProperties properties) {
return new DiskSpaceHealthIndicator(properties);
}
@Bean
@ConfigurationProperties("health.diskspace")
public DiskSpaceHealthIndicatorProperties diskSpaceHealthIndicatorProperties() {
return new DiskSpaceHealthIndicatorProperties();
}
}
} }

@ -16,67 +16,45 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.io.File;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/** /**
* A {@link HealthIndicator} that checks for available disk space. * A {@link HealthIndicator} that checks available disk space and reports a status of
* {@link Status#DOWN} when it drops below a configurable threshold.
* *
* @author Mattias Severson * @author Mattias Severson
* @author Andy Wilkinson
* @since 1.2.0 * @since 1.2.0
*/ */
@Component
public class DiskSpaceHealthIndicator extends AbstractHealthIndicator { public class DiskSpaceHealthIndicator extends AbstractHealthIndicator {
private static Log logger = LogFactory.getLog(DiskSpaceHealthIndicator.class); private static Log logger = LogFactory.getLog(DiskSpaceHealthIndicator.class);
private final File path; private final DiskSpaceHealthIndicatorProperties properties;
private final long thresholdBytes;
/** /**
* Constructor. * Create a new {@code DiskSpaceHealthIndicator}
* @param path The path to a directory for checking available disk space. The
* application must have read access to this path. Additionally, the
* {@link java.lang.SecurityManager#checkRead(String)} will be called
* if a security manager is used.
* By default, it uses the system property {@code user.dir}, i.e. the
* current directory when the JVM was started.
* @param thresholdBytes The threshold (in Bytes) of remaining disk space that will
* trigger this health indicator when exceeded.
* Default value is 10485760 Bytes (10 MB).
*/ */
@Autowired @Autowired
public DiskSpaceHealthIndicator(@Value("${health.path?:${user.dir}}") String path, public DiskSpaceHealthIndicator(DiskSpaceHealthIndicatorProperties properties) {
@Value("${health.threshold.bytes:10485760}") long thresholdBytes) { this.properties = properties;
this.path = new File(path);
this.thresholdBytes = thresholdBytes;
verifyPathIsAccessible(this.path);
Assert.isTrue(thresholdBytes >= 0, "thresholdBytes must be greater than 0");
} }
@Override @Override
protected void doHealthCheck(Health.Builder builder) throws Exception { protected void doHealthCheck(Health.Builder builder) throws Exception {
long diskFreeInBytes = this.path.getFreeSpace(); long diskFreeInBytes = this.properties.getPath().getFreeSpace();
if (diskFreeInBytes >= this.thresholdBytes) { if (diskFreeInBytes >= this.properties.getThreshold()) {
builder.up(); builder.up();
} else {
logger.warn(String.format("Free disk space threshold exceeded. " +
"Available: %d bytes (threshold: %d bytes).",
diskFreeInBytes, this.thresholdBytes));
builder.down();
}
} }
else {
private static void verifyPathIsAccessible(File path) { logger.warn(String.format("Free disk space below threshold. "
if (!path.exists()) { + "Available: %d bytes (threshold: %d bytes)", diskFreeInBytes,
throw new IllegalArgumentException(String.format("Path does not exist: %s", path)); this.properties.getThreshold()));
} builder.down();
if (!path.canRead()) {
throw new IllegalStateException(String.format("Path cannot be read: %s", path));
} }
builder.withDetail("free", diskFreeInBytes).withDetail("threshold",
this.properties.getThreshold());
} }
} }

@ -0,0 +1,60 @@
/*
* Copyright 2012-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 org.springframework.boot.actuate.health;
import java.io.File;
import org.springframework.util.Assert;
/**
* External configuration properties for {@link DiskSpaceHealthIndicator}
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public class DiskSpaceHealthIndicatorProperties {
private File path = new File(".");
private long threshold = 10 * 1024 * 1024;
public File getPath() {
return this.path;
}
public void setPath(File path) {
if (!path.exists()) {
throw new IllegalArgumentException(String.format("Path '%s' does not exist",
path));
}
if (!path.canRead()) {
throw new IllegalStateException(String.format("Path '%s' cannot be read",
path));
}
this.path = path;
}
public long getThreshold() {
return this.threshold;
}
public void setThreshold(long threshold) {
Assert.isTrue(threshold >= 0, "threshold must be greater than 0");
this.threshold = threshold;
}
}

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.io.File; import java.io.File;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -24,7 +25,6 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -49,10 +49,10 @@ public class DiskSpaceHealthIndicatorTests {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
this.healthIndicator = when(this.fileMock.exists()).thenReturn(true);
new DiskSpaceHealthIndicator(DiskSpaceHealthIndicatorTests.class.getResource("").getPath(), when(this.fileMock.canRead()).thenReturn(true);
THRESHOLD_BYTES); this.healthIndicator = new DiskSpaceHealthIndicator(createProperties(
ReflectionTestUtils.setField(this.healthIndicator, "path", this.fileMock); this.fileMock, THRESHOLD_BYTES));
} }
@Test @Test
@ -61,6 +61,8 @@ public class DiskSpaceHealthIndicatorTests {
Health health = this.healthIndicator.health(); Health health = this.healthIndicator.health();
assertEquals(Status.UP, health.getStatus()); assertEquals(Status.UP, health.getStatus());
assertEquals(THRESHOLD_BYTES, health.getDetails().get("threshold"));
assertEquals(THRESHOLD_BYTES + 10, health.getDetails().get("free"));
} }
@Test @Test
@ -69,21 +71,14 @@ public class DiskSpaceHealthIndicatorTests {
Health health = this.healthIndicator.health(); Health health = this.healthIndicator.health();
assertEquals(Status.DOWN, health.getStatus()); assertEquals(Status.DOWN, health.getStatus());
assertEquals(THRESHOLD_BYTES, health.getDetails().get("threshold"));
assertEquals(THRESHOLD_BYTES - 10, health.getDetails().get("free"));
} }
@Test private DiskSpaceHealthIndicatorProperties createProperties(File path, long threshold) {
public void throwsExceptionForUnknownPath() { DiskSpaceHealthIndicatorProperties properties = new DiskSpaceHealthIndicatorProperties();
exception.expect(IllegalArgumentException.class); properties.setPath(path);
exception.expectMessage("Path does not exist: an_path_that_does_not_exist"); properties.setThreshold(threshold);
return properties;
new DiskSpaceHealthIndicator("an_path_that_does_not_exist", THRESHOLD_BYTES);
}
@Test
public void throwsExceptionForNegativeThreshold() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("thresholdBytes must be greater than 0");
new DiskSpaceHealthIndicator(DiskSpaceHealthIndicatorTests.class.getResource("").getPath(), -1);
} }
} }

@ -387,6 +387,10 @@ content into your application; rather pick only the properties that you need.
endpoints.trace.sensitive=true endpoints.trace.sensitive=true
endpoints.trace.enabled=true endpoints.trace.enabled=true
# HEALTH INDICATORS
health.diskspace.path=.
health.diskspace.threshold=10485760
# MVC ONLY ENDPOINTS # MVC ONLY ENDPOINTS
endpoints.jolokia.path=jolokia endpoints.jolokia.path=jolokia
endpoints.jolokia.sensitive=true endpoints.jolokia.sensitive=true

@ -165,10 +165,10 @@ To provide custom health information you can register a Spring bean that impleme
Spring Boot provides a Spring Boot provides a
{sc-spring-boot-actuator}/health/DataSourceHealthIndicator.{sc-ext}[`DataSourceHealthIndicator`] {sc-spring-boot-actuator}/health/DataSourceHealthIndicator.{sc-ext}[`DataSourceHealthIndicator`]
implementation that attempts a simple database test (reusing the validation query set on the data implementation that attempts a simple database test (reusing the validation query set on the data
source, if any) as well as implementations for Redis, MongoDB and RabbitMQ. source, if any) as well as implementations for Redis, MongoDB and RabbitMQ. Spring Boot adds the
`HealthIndicator` instances automatically if beans of type `DataSource`, `MongoTemplate`,
Spring Boot adds the `HealthIndicator` instances automatically if beans of type `DataSource`, `RedisConnectionFactory`, and `RabbitTemplate` respectively are present in the
`MongoTemplate`, `RedisConnectionFactory`, `RabbitTemplate` are present in the `ApplicationContext`. `ApplicationContext`. A health indicator that checks free disk space is also provided.
Besides implementing custom a `HealthIndicator` type and using out-of-box {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] Besides implementing custom a `HealthIndicator` type and using out-of-box {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`]
types, it is also possible to introduce custom `Status` types for different or more complex system types, it is also possible to introduce custom `Status` types for different or more complex system

Loading…
Cancel
Save