AspNetCore.Diagnostics.HealthChecks
AspNetCore.Diagnostics.HealthChecks copied to clipboard
DiskStorageHealthCheck uses weird approach that leads to errors.
It seems the DiskStorageHealthCheck uses an oddly convoluted and therefore incorrect way to determine the drive. It seemingly can only be used in certain cases where the user has more information about the system than one would reasonably expect.
What happened:
Error: "Configured drive /var/data/blob-storage is not present on system"
What you expected to happen:
Healthy status
How to reproduce it (as minimally and precisely as possible):
Run something like the sample below.
Source code sample:
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder
.ConfigureServices(services => {
// Normally this is loaded from the environment
var storageFolder = "/var/data/blob-storage";
services
.AddHealthChecks()
.AddDiskStorageHealthCheck(x => x.AddDrive(storageFolder));
});
webBuilder.Configure(app => app.UseEndpoints(x => x.MapHealthChecks("healthz")))
})
.Build()
.Run();
Anything else we need to know?:
The underlying reason is that DiskStorageHealthCheck iterates over the system drives. Which is weird.
Logically we're asking it to check on a number of filesystem locations, not to check all system drives when they match a certain string. At the application level, the developer probably doesn't have any preliminary information about the exact drive configuration of the system the software runs on. He might know in which filesystem location his application must be able to allocate a certain amount of data.
I think the functionality of CheckHealthAsync should do something closer to:
/// <inheritdoc/>
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
return Task.FromResult(
_options.ConfiguredDrives.Values.Select(kvp =>
new DriveInfo(kvp.Key) switch {
// Drive not ready means this folder probably doesn't exist.
{ IsReady: false } => _options.FailedDescription(kvp.Key, kvp.Value, null),
// Drive is ready, so asking for free space is will not throw DriveNotFoundException
{ AvailableFreeSpace: var freeBytes } when (freeBytes >> 20) < kvp.Value
=> _options.FailedDescription(kvp.Key, kvp.Value, (freeBytes >> 20))
// Everything is fine
_ => null
})
// Keep errors
.Where(result => result is not null)
// Take only first error unless CheckAllDrives is set
.TakeWhile((_, index) => index == 0 || _options.CheckAllDrives)
// Map to health state
.GetHealthState(context)
)
}
catch (Exception ex)
{
return Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, exception: ex));
}
}
Note: This mechanism doesn't preclude that a drive is listed twice. I couldn't find any way in the .NET API to correctly identify two DriveInfo objects as representing the same drive. The duplication then can arise when a user asks for multiple locations to be checked, and it is unknown whether these will be located on the same drive or different drives. Such a situation will of course not affect the correctness of the health check response. However, if two errors are given, you can never be sure whether two drives are out of space, or one drive is out of space, and listed twice.
Environment:
- .NET 8.0.101
- AspNetCore.HealthChecks.System 8.0.0
- Operative system:
Linux 6.1.0-17-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.69-1 (2023-12-30) x86_64 GNU/Linux, though actually where the health check runs varies. Production and staging deployments are based on the latestaspnet-8.0-alpineimages, but other environments can get run on Windows 11 and the latest MacOS too.