Skip to content
Merged
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
59 changes: 3 additions & 56 deletions src/Common/src/Net/WindowsNetworkFileShare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.ComponentModel;
using System.Net;

namespace Steeltoe.Common.Net;
Expand All @@ -12,57 +13,6 @@ namespace Steeltoe.Common.Net;
public sealed class WindowsNetworkFileShare : IDisposable
{
private const int NoError = 0;
private const int ErrorAccessDenied = 5;
private const int ErrorAlreadyAssigned = 85;
private const int ErrorPathNotFound = 53;
private const int ErrorBadDevice = 1200;
private const int ErrorBadNetName = 67;
private const int ErrorBadProvider = 1204;
private const int ErrorCancelled = 1223;
private const int ErrorExtendedError = 1208;
private const int ErrorInvalidAddress = 487;
private const int ErrorInvalidParameter = 87;
private const int ErrorInvalidPassword = 86;
private const int ErrorInvalidPasswordName = 1216;
private const int ErrorMoreData = 234;
private const int ErrorNoMoreItems = 259;
private const int ErrorNoNetOrBadPath = 1203;
private const int ErrorNoNetwork = 1222;
private const int ErrorBadProfile = 1206;
private const int ErrorCannotOpenProfile = 1205;
private const int ErrorDeviceInUse = 2404;
private const int ErrorNotConnected = 2250;
private const int ErrorOpenFiles = 2401;
private const int ErrorLogonFailure = 1326;

// Created with excel formula:
// ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
private static readonly Dictionary<int, string> ErrorMessageLookupTable = new()
{
[ErrorAccessDenied] = "Error: Access Denied",
[ErrorAlreadyAssigned] = "Error: Already Assigned",
[ErrorBadDevice] = "Error: Bad Device",
[ErrorBadNetName] = "Error: Bad Net Name",
[ErrorBadProvider] = "Error: Bad Provider",
[ErrorCancelled] = "Error: Cancelled",
[ErrorExtendedError] = "Error: Extended Error",
[ErrorInvalidAddress] = "Error: Invalid Address",
[ErrorInvalidParameter] = "Error: Invalid Parameter",
[ErrorInvalidPassword] = "Error: Invalid Password",
[ErrorInvalidPasswordName] = "Error: Invalid Password Format",
[ErrorMoreData] = "Error: More Data",
[ErrorNoMoreItems] = "Error: No More Items",
[ErrorNoNetOrBadPath] = "Error: No Net Or Bad Path",
[ErrorNoNetwork] = "Error: No Network",
[ErrorBadProfile] = "Error: Bad Profile",
[ErrorCannotOpenProfile] = "Error: Cannot Open Profile",
[ErrorDeviceInUse] = "Error: Device In Use",
[ErrorNotConnected] = "Error: Not Connected",
[ErrorOpenFiles] = "Error: Open Files",
[ErrorLogonFailure] = "The user name or password is incorrect",
[ErrorPathNotFound] = "The network path not found"
};

private readonly string _networkName;
private readonly IMultipleProviderRouter _multipleProviderRouter;

Expand Down Expand Up @@ -118,12 +68,9 @@ internal static void ThrowForNonZeroResult(int errorNumber, string operation)
{
if (errorNumber != NoError)
{
if (ErrorMessageLookupTable.TryGetValue(errorNumber, out string? errorMessage))
{
throw new IOException($"Failed to {operation} with error {errorNumber}: {errorMessage}.");
}
var innerException = new Win32Exception(errorNumber);

throw new IOException($"Failed to {operation} with error {errorNumber}.");
throw new IOException($"Failed to {operation}.", innerException);
}
}
}
16 changes: 12 additions & 4 deletions src/Common/test/Net.Test/WindowsNetworkFileShareTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.ComponentModel;
using System.Net;

namespace Steeltoe.Common.Net.Test;
Expand All @@ -12,17 +13,23 @@ public sealed class WindowsNetworkFileShareTest
public void GetErrorForKnownNumber_ReturnsKnownError()
{
Action action = () => WindowsNetworkFileShare.ThrowForNonZeroResult(5, "execute");
action.Should().ThrowExactly<IOException>().WithMessage("*Error: Access Denied*");

action.Should().ThrowExactly<IOException>().WithInnerException<Win32Exception>()
.WithMessage(Platform.IsWindows ? "Access is Denied*" : "Input/output error");

action = () => WindowsNetworkFileShare.ThrowForNonZeroResult(1222, "execute");
action.Should().ThrowExactly<IOException>().WithMessage("*Error: No Network*");

action.Should().ThrowExactly<IOException>().WithInnerException<Win32Exception>()
.WithMessage(Platform.IsWindows ? "The network is not present or not started." : "Unknown error 1222");
}

[Fact]
public void GetErrorForUnknownNumber_ReturnsUnKnownError()
{
Action action = () => WindowsNetworkFileShare.ThrowForNonZeroResult(9999, "execute");
action.Should().ThrowExactly<IOException>().WithMessage("Failed to execute with error 9999.");

action.Should().ThrowExactly<IOException>().WithInnerException<Win32Exception>()
.WithMessage(Platform.IsWindows ? "Unknown error (0x270f)" : "Unknown error 9999");
}

[Fact]
Expand Down Expand Up @@ -55,7 +62,8 @@ public void WindowsNetworkFileShare_Constructor_ThrowsOn_ConnectFail()
var router = new FakeMultipleProviderRouter(false);

var exception = Assert.Throws<IOException>(() => new WindowsNetworkFileShare("doesn't-matter", new NetworkCredential("user", "password"), router));
Assert.NotNull(exception.InnerException);

Assert.Equal("Failed to connect to network share with error 1200: Error: Bad Device.", exception.Message);
Assert.Equal(Platform.IsWindows ? "The specified device name is invalid." : "Unknown error 1200", exception.InnerException.Message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Options;
using Steeltoe.Common;
using Steeltoe.Common.HealthChecks;

namespace Steeltoe.Management.Endpoint.Actuators.Health.Contributors;
Expand Down Expand Up @@ -37,33 +38,22 @@ public DiskSpaceHealthContributor(IOptionsMonitor<DiskSpaceContributorOptions> o

if (!string.IsNullOrEmpty(options.Path))
{
string absolutePath = Path.GetFullPath(options.Path);
HealthCheckResult? networkDiskHealth = GetNetworkDiskSpaceHealth(options);

if (Directory.Exists(absolutePath))
if (networkDiskHealth != null)
{
DriveInfo[] systemDrives = DriveInfo.GetDrives();
DriveInfo? driveInfo = FindVolume(absolutePath, systemDrives);
return networkDiskHealth;
}

if (driveInfo != null)
{
long freeSpaceInBytes = driveInfo.TotalFreeSpace;
HealthCheckResult? localDiskHealth = GetLocalDiskSpaceHealth(options);

var result = new HealthCheckResult
{
Status = freeSpaceInBytes >= options.Threshold ? HealthStatus.Up : HealthStatus.Down
};

result.Details.Add("total", driveInfo.TotalSize);
result.Details.Add("free", freeSpaceInBytes);
result.Details.Add("threshold", options.Threshold);
result.Details.Add("path", absolutePath);
result.Details.Add("exists", driveInfo.RootDirectory.Exists);
return result;
}
if (localDiskHealth != null)
{
return localDiskHealth;
}
}

return new HealthCheckResult
var unknownDiskHealth = new HealthCheckResult
{
Status = HealthStatus.Unknown,
Description = "Failed to determine free disk space.",
Expand All @@ -72,6 +62,70 @@ public DiskSpaceHealthContributor(IOptionsMonitor<DiskSpaceContributorOptions> o
["error"] = "The configured path is invalid or does not exist."
}
};

if (!string.IsNullOrEmpty(options.Path))
{
unknownDiskHealth.Details["path"] = options.Path;
}

return unknownDiskHealth;
}

private static HealthCheckResult? GetNetworkDiskSpaceHealth(DiskSpaceContributorOptions options)
{
if (Platform.IsWindows && options.Path?.StartsWith(@"\\", StringComparison.Ordinal) == true)
{
bool directoryExists = Directory.Exists(options.Path);

if (directoryExists && NativeMethods.GetDiskFreeSpaceEx(options.Path, out ulong freeBytesAvailable, out ulong totalNumberOfBytes, out _))
{
return new HealthCheckResult
{
Status = freeBytesAvailable >= (ulong)options.Threshold ? HealthStatus.Up : HealthStatus.Down,
Details =
{
["total"] = totalNumberOfBytes,
["free"] = freeBytesAvailable,
["threshold"] = options.Threshold,
["path"] = options.Path,
["exists"] = true
}
};
}
}

return null;
}

private static HealthCheckResult? GetLocalDiskSpaceHealth(DiskSpaceContributorOptions options)
{
string absolutePath = Path.GetFullPath(options.Path!);

if (Directory.Exists(absolutePath))
{
DriveInfo[] systemDrives = DriveInfo.GetDrives();
DriveInfo? driveInfo = FindVolume(absolutePath, systemDrives);

if (driveInfo != null)
{
long freeSpaceInBytes = driveInfo.TotalFreeSpace;

return new HealthCheckResult
{
Status = freeSpaceInBytes >= options.Threshold ? HealthStatus.Up : HealthStatus.Down,
Details =
{
["total"] = driveInfo.TotalSize,
["free"] = freeSpaceInBytes,
["threshold"] = options.Threshold,
["path"] = absolutePath,
["exists"] = driveInfo.RootDirectory.Exists
}
};
}
}

return null;
}

internal static DriveInfo? FindVolume(string absolutePath, IEnumerable<DriveInfo> systemDrives)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Runtime.InteropServices;

namespace Steeltoe.Management.Endpoint.Actuators.Health.Contributors;

internal static partial class NativeMethods
{
[LibraryImport("kernel32.dll", EntryPoint = "GetDiskFreeSpaceExW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetDiskFreeSpaceEx(string lpDirectoryName, out ulong lpFreeBytesAvailableToCaller, out ulong lpTotalNumberOfBytes,
out ulong lpTotalNumberOfFreeBytes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Options;
using Steeltoe.Common;
using Steeltoe.Common.HealthChecks;
using Steeltoe.Common.TestResources;
using Steeltoe.Management.Endpoint.Actuators.Health.Contributors;
Expand Down Expand Up @@ -110,4 +111,29 @@ public void Selects_correct_volume(string path, string? expected, params Platfor
drive.RootDirectory.FullName.Should().Be(expected);
}
}

[Fact(Skip = "Integration test - Requires Windows file share")]
public async Task SupportsWindowsFileShare()
{
if (Platform.IsWindows)
{
Dictionary<string, string?> settings = new()
{
["Management:Endpoints:Health:DiskSpace:Path"] = @"\\localhost\steeltoe_network_share"
};

IOptionsMonitor<DiskSpaceContributorOptions> optionsMonitor = GetOptionsMonitorFromSettings<DiskSpaceContributorOptions>(settings);
var contributor = new DiskSpaceHealthContributor(optionsMonitor);
Assert.Equal("diskSpace", contributor.Id);
HealthCheckResult? result = await contributor.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.NotNull(result);
Assert.Equal(HealthStatus.Up, result.Status);
Assert.NotNull(result.Details);
Assert.True(result.Details.ContainsKey("total"));
Assert.True(result.Details.ContainsKey("free"));
Assert.True(result.Details.ContainsKey("threshold"));
Assert.True(result.Details.ContainsKey("path"));
Assert.True(result.Details.ContainsKey("exists"));
}
}
}