diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorProperties.java index 75a79304f39f..ad6bb5302f11 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorProperties.java @@ -17,6 +17,8 @@ package org.springframework.boot.actuate.autoconfigure.system; import java.io.File; +import java.util.ArrayList; +import java.util.List; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -28,6 +30,7 @@ * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Leo Li * @since 1.2.0 */ @ConfigurationProperties(prefix = "management.health.diskspace") @@ -36,20 +39,27 @@ public class DiskSpaceHealthIndicatorProperties { /** * Path used to compute the available disk space. */ - private File path = new File("."); + private List path = new ArrayList() { + { + add(new File(".")); + } + }; /** * Minimum disk space that should be available. */ private DataSize threshold = DataSize.ofMegabytes(10); - public File getPath() { + public List getPath() { return this.path; } - public void setPath(File path) { - Assert.isTrue(path.exists(), () -> "Path '" + path + "' does not exist"); - Assert.isTrue(path.canRead(), () -> "Path '" + path + "' cannot be read"); + public void setPath(List path) { + path.forEach((filePath) -> { + Assert.isTrue(filePath.exists(), () -> "Path '" + filePath + "' does not exist"); + Assert.isTrue(filePath.canRead(), () -> "Path '" + filePath + "' cannot be read"); + }); + path.add(0, new File(".")); this.path = path; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index 588bbe881ace..efe61d2977dd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; @@ -116,7 +117,9 @@ HealthEndpoint healthEndpoint(Map healthContributors) @Bean DiskSpaceHealthIndicator diskSpaceHealthIndicator() { - return new DiskSpaceHealthIndicator(new File("."), DataSize.ofMegabytes(10)); + List path = new ArrayList<>(); + path.add(new File(".")); + return new DiskSpaceHealthIndicator(path, DataSize.ofMegabytes(10)); } @Bean diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java index 46f44f2b48f2..9844e0b2a51b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java @@ -17,6 +17,9 @@ package org.springframework.boot.actuate.system; import java.io.File; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,6 +28,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; +import org.springframework.util.CollectionUtils; import org.springframework.util.unit.DataSize; /** @@ -34,13 +38,14 @@ * @author Mattias Severson * @author Andy Wilkinson * @author Stephane Nicoll + * @author Leo Li * @since 2.0.0 */ public class DiskSpaceHealthIndicator extends AbstractHealthIndicator { private static final Log logger = LogFactory.getLog(DiskSpaceHealthIndicator.class); - private final File path; + private final List path; private final DataSize threshold; @@ -49,7 +54,7 @@ public class DiskSpaceHealthIndicator extends AbstractHealthIndicator { * @param path the Path used to compute the available disk space * @param threshold the minimum disk space that should be available */ - public DiskSpaceHealthIndicator(File path, DataSize threshold) { + public DiskSpaceHealthIndicator(List path, DataSize threshold) { super("DiskSpace health check failed"); this.path = path; this.threshold = threshold; @@ -57,17 +62,37 @@ public DiskSpaceHealthIndicator(File path, DataSize threshold) { @Override protected void doHealthCheck(Health.Builder builder) throws Exception { - long diskFreeInBytes = this.path.getUsableSpace(); - if (diskFreeInBytes >= this.threshold.toBytes()) { + boolean status = true; + Map diskFreeInBytesMap = new LinkedHashMap<>(); + for (File file : this.path) { + long diskFreeInBytes = file.getUsableSpace(); + diskFreeInBytesMap.put(file, diskFreeInBytes); + if (status && diskFreeInBytes < this.threshold.toBytes()) { + logger.warn(String.format("Free disk space in %s below threshold. Available: %d bytes (threshold: %s)", + file.getPath(), diskFreeInBytes, this.threshold)); + builder.down(); + status = false; + } + } + if (status) { builder.up(); } - else { - logger.warn(String.format("Free disk space below threshold. Available: %d bytes (threshold: %s)", - diskFreeInBytes, this.threshold)); - builder.down(); + Map> details = new LinkedHashMap<>(); + diskFreeInBytesMap.forEach((file, diskFreeInBytes) -> { + if (".".equals(file.getPath())) { + builder.withDetail("total", file.getTotalSpace()).withDetail("free", diskFreeInBytes) + .withDetail("threshold", this.threshold.toBytes()); + } + else { + Map detail = new LinkedHashMap<>(); + detail.put("total", file.getTotalSpace()); + detail.put("free", diskFreeInBytes); + details.put(file.getPath(), detail); + } + }); + if (!CollectionUtils.isEmpty(details)) { + builder.withDetail("paths", details); } - builder.withDetail("total", this.path.getTotalSpace()).withDetail("free", diskFreeInBytes) - .withDetail("threshold", this.threshold.toBytes()); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicatorTests.java index 44b0b0c9fa9f..4a2159a9c934 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicatorTests.java @@ -17,6 +17,8 @@ package org.springframework.boot.actuate.system; import java.io.File; +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,6 +38,7 @@ * * @author Mattias Severson * @author Stephane Nicoll + * @author Leo Li */ class DiskSpaceHealthIndicatorTests { @@ -43,41 +46,53 @@ class DiskSpaceHealthIndicatorTests { private static final DataSize TOTAL_SPACE = DataSize.ofKilobytes(10); + private static final String MOCK_FILE_PATH = "."; + @Mock private File fileMock; + private List fileMocks = new ArrayList<>(); + private HealthIndicator healthIndicator; @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); - given(this.fileMock.exists()).willReturn(true); - given(this.fileMock.canRead()).willReturn(true); - this.healthIndicator = new DiskSpaceHealthIndicator(this.fileMock, THRESHOLD); + this.fileMocks.add(this.fileMock); + this.fileMocks.forEach((fileMock) -> { + given(fileMock.exists()).willReturn(true); + given(fileMock.canRead()).willReturn(true); + given(fileMock.getPath()).willReturn(MOCK_FILE_PATH); + }); + this.healthIndicator = new DiskSpaceHealthIndicator(this.fileMocks, THRESHOLD); } @Test void diskSpaceIsUp() { long freeSpace = THRESHOLD.toBytes() + 10; - given(this.fileMock.getUsableSpace()).willReturn(freeSpace); - given(this.fileMock.getTotalSpace()).willReturn(TOTAL_SPACE.toBytes()); - Health health = this.healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("threshold")).isEqualTo(THRESHOLD.toBytes()); - assertThat(health.getDetails().get("free")).isEqualTo(freeSpace); - assertThat(health.getDetails().get("total")).isEqualTo(TOTAL_SPACE.toBytes()); + this.fileMocks.forEach((fileMock) -> { + given(this.fileMock.getUsableSpace()).willReturn(freeSpace); + given(this.fileMock.getTotalSpace()).willReturn(TOTAL_SPACE.toBytes()); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("threshold")).isEqualTo(THRESHOLD.toBytes()); + assertThat(health.getDetails().get("free")).isEqualTo(freeSpace); + assertThat(health.getDetails().get("total")).isEqualTo(TOTAL_SPACE.toBytes()); + }); } @Test void diskSpaceIsDown() { long freeSpace = THRESHOLD.toBytes() - 10; - given(this.fileMock.getUsableSpace()).willReturn(freeSpace); - given(this.fileMock.getTotalSpace()).willReturn(TOTAL_SPACE.toBytes()); - Health health = this.healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat(health.getDetails().get("threshold")).isEqualTo(THRESHOLD.toBytes()); - assertThat(health.getDetails().get("free")).isEqualTo(freeSpace); - assertThat(health.getDetails().get("total")).isEqualTo(TOTAL_SPACE.toBytes()); + this.fileMocks.forEach((fileMock) -> { + given(this.fileMock.getUsableSpace()).willReturn(freeSpace); + given(this.fileMock.getTotalSpace()).willReturn(TOTAL_SPACE.toBytes()); + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails().get("threshold")).isEqualTo(THRESHOLD.toBytes()); + assertThat(health.getDetails().get("free")).isEqualTo(freeSpace); + assertThat(health.getDetails().get("total")).isEqualTo(TOTAL_SPACE.toBytes()); + }); } }