Skip to content

Support multi disk space #18413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +30,7 @@
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Leo Li
* @since 1.2.0
*/
@ConfigurationProperties(prefix = "management.health.diskspace")
Expand All @@ -36,20 +39,27 @@ public class DiskSpaceHealthIndicatorProperties {
/**
* Path used to compute the available disk space.
*/
private File path = new File(".");
private List<File> path = new ArrayList<File>() {
{
add(new File("."));
}
};

/**
* Minimum disk space that should be available.
*/
private DataSize threshold = DataSize.ofMegabytes(10);

public File getPath() {
public List<File> 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<File> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -116,7 +117,9 @@ HealthEndpoint healthEndpoint(Map<String, HealthContributor> healthContributors)

@Bean
DiskSpaceHealthIndicator diskSpaceHealthIndicator() {
return new DiskSpaceHealthIndicator(new File("."), DataSize.ofMegabytes(10));
List<File> path = new ArrayList<>();
path.add(new File("."));
return new DiskSpaceHealthIndicator(path, DataSize.ofMegabytes(10));
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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<File> path;

private final DataSize threshold;

Expand All @@ -49,25 +54,45 @@ 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<File> path, DataSize threshold) {
super("DiskSpace health check failed");
this.path = path;
this.threshold = threshold;
}

@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
long diskFreeInBytes = this.path.getUsableSpace();
if (diskFreeInBytes >= this.threshold.toBytes()) {
boolean status = true;
Map<File, Long> 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<String, Map<String, Long>> 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<String, Long> 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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,48 +38,61 @@
*
* @author Mattias Severson
* @author Stephane Nicoll
* @author Leo Li
*/
class DiskSpaceHealthIndicatorTests {

private static final DataSize THRESHOLD = DataSize.ofKilobytes(1);

private static final DataSize TOTAL_SPACE = DataSize.ofKilobytes(10);

private static final String MOCK_FILE_PATH = ".";

@Mock
private File fileMock;

private List<File> 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());
});
}

}