From c802d5ef5fba1ba8dfbcb8c3741af2ba15e9d1aa Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 31 Oct 2018 12:51:14 -0700 Subject: [PATCH] Redesign HealthStatus (again) (#520) * Redesign HealthStatus (again) This change brings back the ability to return Healthy/Degraded/Unhealthy in a HealthCheckResult. We tried making this pass/fail in 2.2.0-preview3 and folks writing health checks for their own use pointed out (rightly so) that it was too limited. It's still possible for the app developer to configure the failure status of a health check, but it requires the health check author to cooperate. I also got rid of HealthStatus.Failed since it raises more questions than it answers. It's really not clear that it's valuable for a health check for behave different when throwing an unhandled exception. We would still recommend that a health check library handle exceptions that they know about and return `context.Registration.FailureStatus`. --- .../DbConnectionHealthCheck.cs | 4 +- .../HealthChecksSample/GCInfoHealthCheck.cs | 8 ++- .../SlowDependencyHealthCheck.cs | 6 +- .../HealthCheckOptions.cs | 3 - .../HealthCheckResult.cs | 45 ++++++++------ .../HealthReport.cs | 2 +- .../HealthReportEntry.cs | 3 +- .../HealthStatus.cs | 18 ++---- .../DbContextHealthCheck.cs | 4 +- .../DefaultHealthCheckService.cs | 8 +-- .../HealthChecksBuilderDelegateExtensions.cs | 28 ++------- .../HealthCheckMiddlewareTests.cs | 46 +++++++-------- .../DbContextHealthCheckTest.cs | 45 ++++++++++---- .../DefaultHealthCheckServiceTest.cs | 58 ++++++++++--------- .../HealthChecksBuilderTest.cs | 30 +++++----- .../HealthCheckPublisherHostedServiceTest.cs | 4 +- .../HealthReportTest.cs | 1 - 17 files changed, 158 insertions(+), 155 deletions(-) diff --git a/samples/HealthChecksSample/DbConnectionHealthCheck.cs b/samples/HealthChecksSample/DbConnectionHealthCheck.cs index 5a0ddcdd55..cb865f67c0 100644 --- a/samples/HealthChecksSample/DbConnectionHealthCheck.cs +++ b/samples/HealthChecksSample/DbConnectionHealthCheck.cs @@ -51,11 +51,11 @@ namespace HealthChecksSample } catch (DbException ex) { - return HealthCheckResult.Failed(exception: ex); + return new HealthCheckResult(status: context.Registration.FailureStatus, exception: ex); } } - return HealthCheckResult.Passed(); + return HealthCheckResult.Healthy(); } } } diff --git a/samples/HealthChecksSample/GCInfoHealthCheck.cs b/samples/HealthChecksSample/GCInfoHealthCheck.cs index c708f3ed3c..91519af452 100644 --- a/samples/HealthChecksSample/GCInfoHealthCheck.cs +++ b/samples/HealthChecksSample/GCInfoHealthCheck.cs @@ -65,13 +65,15 @@ namespace HealthChecksSample { "Gen2Collections", GC.CollectionCount(2) }, }; - // Report failure if the allocated memory is >= the threshold. Negated because true == success - var result = !(allocated >= options.Threshold); + // Report failure if the allocated memory is >= the threshold. + // + // Using context.Registration.FailureStatus means that the application developer can configure + // how they want failures to appear. + var result = allocated >= options.Threshold ? context.Registration.FailureStatus : HealthStatus.Healthy; return Task.FromResult(new HealthCheckResult( result, description: "reports degraded status if allocated bytes >= 1gb", - exception: null, data: data)); } } diff --git a/samples/HealthChecksSample/SlowDependencyHealthCheck.cs b/samples/HealthChecksSample/SlowDependencyHealthCheck.cs index e319952cf0..e14aeb210c 100644 --- a/samples/HealthChecksSample/SlowDependencyHealthCheck.cs +++ b/samples/HealthChecksSample/SlowDependencyHealthCheck.cs @@ -21,10 +21,12 @@ namespace HealthChecksSample { if (_task.IsCompleted) { - return Task.FromResult(HealthCheckResult.Passed("Dependency is ready")); + return Task.FromResult(HealthCheckResult.Healthy("Dependency is ready")); } - return Task.FromResult(HealthCheckResult.Failed("Dependency is still initializing")); + return Task.FromResult(new HealthCheckResult( + status: context.Registration.FailureStatus, + description: "Dependency is still initializing")); } } } diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs index bf43676a5c..16b81c2b96 100644 --- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs +++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs @@ -33,9 +33,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks { HealthStatus.Healthy, StatusCodes.Status200OK }, { HealthStatus.Degraded, StatusCodes.Status200OK }, { HealthStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable }, - - // This means that a health check failed, so 500 is appropriate. This is an error. - { HealthStatus.Failed, StatusCodes.Status500InternalServerError }, }; /// diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthCheckResult.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthCheckResult.cs index 12cd38d08e..e01cb5aceb 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthCheckResult.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthCheckResult.cs @@ -14,16 +14,16 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks private static readonly IReadOnlyDictionary _emptyReadOnlyDictionary = new Dictionary(); /// - /// Creates a new with the specified values for , , - /// , and . + /// Creates a new with the specified values for , + /// , , and . /// - /// A value indicating the pass/fail status of the component that was checked. + /// A value indicating the status of the component that was checked. /// A human-readable description of the status of the component that was checked. /// An representing the exception that was thrown when checking for status (if any). /// Additional key-value pairs describing the health of the component. - public HealthCheckResult(bool result, string description, Exception exception, IReadOnlyDictionary data) + public HealthCheckResult(HealthStatus status, string description = null, Exception exception = null, IReadOnlyDictionary data = null) { - Result = result; + Status = status; Description = description; Exception = exception; Data = data ?? _emptyReadOnlyDictionary; @@ -45,33 +45,44 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public Exception Exception { get; } /// - /// Gets a value indicating the pass/fail status of the component that was checked. If true, then the component - /// is considered to have passed health validation. A false value will be mapped to the configured - /// by the health check system. + /// Gets a value indicating the status of the component that was checked. /// - public bool Result { get; } + public HealthStatus Status { get; } /// - /// Creates a representing a passing component. + /// Creates a representing a healthy component. /// - /// A representing a passing component. /// A human-readable description of the status of the component that was checked. Optional. /// Additional key-value pairs describing the health of the component. Optional. - public static HealthCheckResult Passed(string description = null, IReadOnlyDictionary data = null) + /// A representing a healthy component. + public static HealthCheckResult Healthy(string description = null, IReadOnlyDictionary data = null) { - return new HealthCheckResult(result: true, description, exception: null, data); + return new HealthCheckResult(status: HealthStatus.Healthy, description, exception: null, data); } + /// - /// Creates a representing an failing component. + /// Creates a representing a degraded component. /// /// A human-readable description of the status of the component that was checked. Optional. /// An representing the exception that was thrown when checking for status. Optional. /// Additional key-value pairs describing the health of the component. Optional. - /// A representing an failing component. - public static HealthCheckResult Failed(string description = null, Exception exception = null, IReadOnlyDictionary data = null) + /// A representing a degraged component. + public static HealthCheckResult Degraded(string description = null, Exception exception = null, IReadOnlyDictionary data = null) { - return new HealthCheckResult(result: false, description, exception, data); + return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: null, data); + } + + /// + /// Creates a representing an unhealthy component. + /// + /// A human-readable description of the status of the component that was checked. Optional. + /// An representing the exception that was thrown when checking for status. Optional. + /// Additional key-value pairs describing the health of the component. Optional. + /// A representing an unhealthy component. + public static HealthCheckResult Unhealthy(string description = null, Exception exception = null, IReadOnlyDictionary data = null) + { + return new HealthCheckResult(status: HealthStatus.Unhealthy, description, exception, data); } } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReport.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReport.cs index a0e6fac1cd..91ed798811 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReport.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReport.cs @@ -54,7 +54,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks currentValue = entry.Status; } - if (currentValue == HealthStatus.Failed) + if (currentValue == HealthStatus.Unhealthy) { // Game over, man! Game over! // (We hit the worst possible status, so there's no need to keep iterating) diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReportEntry.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReportEntry.cs index 2898c93aa4..6e7d6c6b8e 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReportEntry.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReportEntry.cs @@ -52,8 +52,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public Exception Exception { get; } /// - /// Gets the health status of the component that was checked. The is based on the pass/fail value of - /// and the configured value of . + /// Gets the health status of the component that was checked. /// public HealthStatus Status { get; } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthStatus.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthStatus.cs index ad3f40a201..61b76d54fa 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthStatus.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthStatus.cs @@ -8,10 +8,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// /// /// - /// A health status is derived the pass/fail result of an () - /// and the corresponding value of . - /// - /// /// A status of should be considered the default value for a failing health check. Application /// developers may configure a health check to report a different status as desired. /// @@ -23,23 +19,19 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public enum HealthStatus { /// - /// Indicates that an unexpected exception was thrown when running the health check. + /// Indicates that the health check determined that the component was unhealthy, or an unhandled + /// exception was thrown while executing the health check. /// - Failed = 0, - - /// - /// Indicates that the health check determined that the component was unhealthy. - /// - Unhealthy = 1, + Unhealthy = 0, /// /// Indicates that the health check determined that the component was in a degraded state. /// - Degraded = 2, + Degraded = 1, /// /// Indicates that the health check determined that the component was healthy. /// - Healthy = 3, + Healthy = 2, } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/DbContextHealthCheck.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/DbContextHealthCheck.cs index 9d581e89d3..7fa998f296 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/DbContextHealthCheck.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/DbContextHealthCheck.cs @@ -47,10 +47,10 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks if (await testQuery(_dbContext, cancellationToken)) { - return HealthCheckResult.Passed(); + return HealthCheckResult.Healthy(); } - return HealthCheckResult.Failed(); + return HealthCheckResult.Unhealthy(); } } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs index aa12d97418..d5d71d9cb4 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs @@ -76,7 +76,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var duration = stopwatch.GetElapsedTime(); entry = new HealthReportEntry( - status: result.Result ? HealthStatus.Healthy : registration.FailureStatus, + status: result.Status, description: result.Description, duration: duration, exception: result.Exception, @@ -91,7 +91,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { var duration = stopwatch.GetElapsedTime(); entry = new HealthReportEntry( - status: HealthStatus.Failed, + status: HealthStatus.Unhealthy, description: ex.Message, duration: duration, exception: ex, @@ -212,10 +212,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks case HealthStatus.Unhealthy: _healthCheckEndUnhealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null); break; - - case HealthStatus.Failed: - _healthCheckEndFailed(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null); - break; } } diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 70edd0649d..d7dfdd90ae 100644 --- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -19,10 +19,6 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The . /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . @@ -30,7 +26,6 @@ namespace Microsoft.Extensions.DependencyInjection this IHealthChecksBuilder builder, string name, Func check, - HealthStatus? failureStatus = null, IEnumerable tags = null) { if (builder == null) @@ -49,7 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); } /// @@ -57,10 +52,6 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The . /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . @@ -68,7 +59,6 @@ namespace Microsoft.Extensions.DependencyInjection this IHealthChecksBuilder builder, string name, Func check, - HealthStatus? failureStatus = null, IEnumerable tags = null) { if (builder == null) @@ -87,7 +77,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); } /// @@ -95,10 +85,6 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The . /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . @@ -106,7 +92,6 @@ namespace Microsoft.Extensions.DependencyInjection this IHealthChecksBuilder builder, string name, Func> check, - HealthStatus? failureStatus = null, IEnumerable tags = null) { if (builder == null) @@ -125,7 +110,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); } /// @@ -133,10 +118,6 @@ namespace Microsoft.Extensions.DependencyInjection /// /// The . /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . @@ -144,7 +125,6 @@ namespace Microsoft.Extensions.DependencyInjection this IHealthChecksBuilder builder, string name, Func> check, - HealthStatus? failureStatus = null, IEnumerable tags = null) { if (builder == null) @@ -163,7 +143,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); } } } diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs index a3036f9b70..9bcefafbaa 100644 --- a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs @@ -110,9 +110,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddCheck("Foo", () => HealthCheckResult.Passed("A-ok!")) - .AddCheck("Bar", () => HealthCheckResult.Passed("A-ok!")) - .AddCheck("Baz", () => HealthCheckResult.Passed("A-ok!")); + .AddCheck("Foo", () => HealthCheckResult.Healthy("A-ok!")) + .AddCheck("Bar", () => HealthCheckResult.Healthy("A-ok!")) + .AddCheck("Baz", () => HealthCheckResult.Healthy("A-ok!")); }); var server = new TestServer(builder); var client = server.CreateClient(); @@ -135,9 +135,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddCheck("Foo", () => HealthCheckResult.Passed("A-ok!")) - .AddCheck("Bar", () => HealthCheckResult.Failed("Not so great."), failureStatus: HealthStatus.Degraded) - .AddCheck("Baz", () => HealthCheckResult.Passed("A-ok!")); + .AddCheck("Foo", () => HealthCheckResult.Healthy("A-ok!")) + .AddCheck("Bar", () => HealthCheckResult.Degraded("Not so great.")) + .AddCheck("Baz", () => HealthCheckResult.Healthy("A-ok!")); }); var server = new TestServer(builder); var client = server.CreateClient(); @@ -160,9 +160,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))) - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad."))) - .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))) + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad."))) + .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))); }); var server = new TestServer(builder); var client = server.CreateClient(); @@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks } [Fact] - public async Task StatusCodeIs500IfCheckIsFailed() + public async Task StatusCodeIs503IfCheckHasUnhandledException() { var builder = new WebHostBuilder() .Configure(app => @@ -185,18 +185,18 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))) + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))) .AddAsyncCheck("Bar", () => throw null) - .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))); + .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))); }); var server = new TestServer(builder); var client = server.CreateClient(); var response = await client.GetAsync("/health"); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode); Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString()); - Assert.Equal("Failed", await response.Content.ReadAsStringAsync()); + Assert.Equal("Unhealthy", await response.Content.ReadAsStringAsync()); } [Fact] @@ -223,9 +223,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))) - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad."))) - .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))) + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad."))) + .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))); }); var server = new TestServer(builder); var client = server.CreateClient(); @@ -252,9 +252,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))) - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad."))) - .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))) + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad."))) + .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))); }); var server = new TestServer(builder); var client = server.CreateClient(); @@ -357,10 +357,10 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks .ConfigureServices(services => { services.AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))) + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))) // Will get filtered out - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("A-ok!"))) - .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!"))); + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!"))) + .AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!"))); }); var server = new TestServer(builder); var client = server.CreateClient(); diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests/DbContextHealthCheckTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests/DbContextHealthCheckTest.cs index 23b17d4939..b564a62795 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests/DbContextHealthCheckTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.Tests/DbContextHealthCheckTest.cs @@ -14,9 +14,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { public class DbContextHealthCheckTest { - // Just testing pass here since it would be complicated to simulate a failure. All of that logic lives in EF anyway. + // Just testing healthy here since it would be complicated to simulate a failure. All of that logic lives in EF anyway. [Fact] - public async Task CheckAsync_DefaultTest_Pass() + public async Task CheckAsync_DefaultTest_Healthy() { // Arrange var services = CreateServices(); @@ -29,12 +29,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); // Assert - Assert.True(result.Result, "Health check passed"); + Assert.Equal(HealthStatus.Healthy, result.Status); } } [Fact] - public async Task CheckAsync_CustomTest_Pass() + public async Task CheckAsync_CustomTest_Healthy() { // Arrange var services = CreateServices(async (c, ct) => @@ -56,19 +56,18 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); // Assert - Assert.True(result.Result, "Health check passed"); + Assert.Equal(HealthStatus.Healthy, result.Status); } } - [Fact] - public async Task CheckAsync_CustomTest_Fail() + public async Task CheckAsync_CustomTest_Degraded() { // Arrange var services = CreateServices(async (c, ct) => { return 0 < await c.Blogs.CountAsync(); - }); + }, failureStatus: HealthStatus.Degraded); using (var scope = services.GetRequiredService().CreateScope()) { @@ -79,17 +78,41 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); // Assert - Assert.False(result.Result, "Health check failed"); + Assert.Equal(HealthStatus.Unhealthy, result.Status); } } - private static IServiceProvider CreateServices(Func> testQuery = null) + [Fact] + public async Task CheckAsync_CustomTest_Unhealthy() + { + // Arrange + var services = CreateServices(async (c, ct) => + { + return 0 < await c.Blogs.CountAsync(); + }, failureStatus: HealthStatus.Unhealthy); + + using (var scope = services.GetRequiredService().CreateScope()) + { + var registration = Assert.Single(services.GetRequiredService>().Value.Registrations); + var check = ActivatorUtilities.CreateInstance>(scope.ServiceProvider); + + // Act + var result = await check.CheckHealthAsync(new HealthCheckContext() { Registration = registration, }); + + // Assert + Assert.Equal(HealthStatus.Unhealthy, result.Status); + } + } + + private static IServiceProvider CreateServices( + Func> testQuery = null, + HealthStatus failureStatus = HealthStatus.Unhealthy) { var serviceCollection = new ServiceCollection(); serviceCollection.AddDbContext(o => o.UseInMemoryDatabase("Test")); var builder = serviceCollection.AddHealthChecks(); - builder.AddDbContextCheck("test", HealthStatus.Degraded, new[] { "tag1", "tag2", }, testQuery); + builder.AddDbContextCheck("test", failureStatus, new[] { "tag1", "tag2", }, testQuery); return serviceCollection.BuildServiceProvider(); } } diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DefaultHealthCheckServiceTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DefaultHealthCheckServiceTest.cs index 6c74ddcdf0..9ab991204e 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DefaultHealthCheckServiceTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DefaultHealthCheckServiceTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -26,11 +27,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks serviceCollection.AddLogging(); serviceCollection.AddOptions(); serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed()))); + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); var services = serviceCollection.BuildServiceProvider(); @@ -63,16 +64,25 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Passed(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Failed(DegradedMessage)), failureStatus: HealthStatus.Degraded); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Failed(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act var results = await service.CheckHealthAsync(); // Assert - Assert.Collection(results.Entries, + Assert.Collection( + results.Entries.OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("DegradedCheck", actual.Key); + Assert.Equal(DegradedMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Degraded, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Empty(actual.Value.Data); + }, actual => { Assert.Equal("HealthyCheck", actual.Key); @@ -86,14 +96,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks }); }, actual => - { - Assert.Equal("DegradedCheck", actual.Key); - Assert.Equal(DegradedMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Degraded, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Empty(actual.Value.Data); - }, - actual => { Assert.Equal("UnhealthyCheck", actual.Key); Assert.Equal(UnhealthyMessage, actual.Value.Description); @@ -121,9 +123,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Passed(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Failed(DegradedMessage)), failureStatus: HealthStatus.Degraded); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Failed(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act @@ -201,7 +203,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks insideCheck.SetResult(null); await Task.Delay(10000, ct); - return HealthCheckResult.Failed(); + return HealthCheckResult.Unhealthy(); }); }); @@ -218,7 +220,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } [Fact] - public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToFailedResultAsync() + public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResultAsync() { // Arrange var thrownException = new InvalidOperationException("Whoops!"); @@ -228,7 +230,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { b.AddAsyncCheck("Throws", ct => throw thrownException); b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); - b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Passed())); + b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); }); // Act @@ -241,14 +243,14 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { Assert.Equal("Throws", actual.Key); Assert.Equal(thrownException.Message, actual.Value.Description); - Assert.Equal(HealthStatus.Failed, actual.Value.Status); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); Assert.Same(thrownException, actual.Value.Exception); }, actual => { Assert.Equal("Faults", actual.Key); Assert.Equal(faultedException.Message, actual.Value.Description); - Assert.Equal(HealthStatus.Failed, actual.Value.Status); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); Assert.Same(faultedException, actual.Value.Exception); }, actual => @@ -278,7 +280,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Assert.Equal("TestScope", item.Value); }); }); - return Task.FromResult(HealthCheckResult.Passed()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var loggerFactory = new TestLoggerFactory(sink, enabled: true); @@ -398,7 +400,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { - return Task.FromResult(HealthCheckResult.Passed()); + return Task.FromResult(HealthCheckResult.Healthy()); } } @@ -410,7 +412,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { { "name", context.Registration.Name }, }; - return Task.FromResult(HealthCheckResult.Passed(data: data)); + return Task.FromResult(HealthCheckResult.Healthy(data: data)); } } } diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DependencyInjection/HealthChecksBuilderTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DependencyInjection/HealthChecksBuilderTest.cs index c4974013c7..4235f152a2 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DependencyInjection/HealthChecksBuilderTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/DependencyInjection/HealthChecksBuilderTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection // Arrange var instance = new DelegateHealthCheck((_) => { - return Task.FromResult(HealthCheckResult.Passed()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var services = CreateServices(); @@ -112,9 +112,9 @@ namespace Microsoft.Extensions.DependencyInjection { // Arrange var services = CreateServices(); - services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }, check: () => + services.AddHealthChecks().AddCheck("test", tags: new[] { "tag", }, check: () => { - return HealthCheckResult.Passed(); + return HealthCheckResult.Healthy(); }); var serviceProvider = services.BuildServiceProvider(); @@ -125,7 +125,7 @@ namespace Microsoft.Extensions.DependencyInjection // Assert var registration = Assert.Single(options.Registrations); Assert.Equal("test", registration.Name); - Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); Assert.Equal(new[] { "tag", }, registration.Tags); Assert.IsType(registration.Factory(serviceProvider)); } @@ -137,8 +137,8 @@ namespace Microsoft.Extensions.DependencyInjection var services = CreateServices(); services.AddHealthChecks().AddCheck("test", (_) => { - return HealthCheckResult.Passed(); - }, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }); + return HealthCheckResult.Degraded(); + }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -148,7 +148,7 @@ namespace Microsoft.Extensions.DependencyInjection // Assert var registration = Assert.Single(options.Registrations); Assert.Equal("test", registration.Name); - Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); Assert.Equal(new[] { "tag", }, registration.Tags); Assert.IsType(registration.Factory(serviceProvider)); } @@ -160,8 +160,8 @@ namespace Microsoft.Extensions.DependencyInjection var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", () => { - return Task.FromResult(HealthCheckResult.Passed()); - }, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }); + return Task.FromResult(HealthCheckResult.Healthy()); + }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -171,7 +171,7 @@ namespace Microsoft.Extensions.DependencyInjection // Assert var registration = Assert.Single(options.Registrations); Assert.Equal("test", registration.Name); - Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); Assert.Equal(new[] { "tag", }, registration.Tags); Assert.IsType(registration.Factory(serviceProvider)); } @@ -183,8 +183,8 @@ namespace Microsoft.Extensions.DependencyInjection var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", (_) => { - return Task.FromResult(HealthCheckResult.Passed()); - }, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }); + return Task.FromResult(HealthCheckResult.Unhealthy()); + }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -194,7 +194,7 @@ namespace Microsoft.Extensions.DependencyInjection // Assert var registration = Assert.Single(options.Registrations); Assert.Equal("test", registration.Name); - Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); Assert.Equal(new[] { "tag", }, registration.Tags); Assert.IsType(registration.Factory(serviceProvider)); } @@ -205,10 +205,10 @@ namespace Microsoft.Extensions.DependencyInjection var services = new ServiceCollection(); services .AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed())); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); services .AddHealthChecks() - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Passed())); + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); // Act var options = services.BuildServiceProvider().GetRequiredService>(); diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthCheckPublisherHostedServiceTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthCheckPublisherHostedServiceTest.cs index 49d57916eb..94687efcb8 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthCheckPublisherHostedServiceTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthCheckPublisherHostedServiceTest.cs @@ -448,8 +448,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks serviceCollection.AddOptions(); serviceCollection.AddLogging(); serviceCollection.AddHealthChecks() - .AddCheck("one", () => { return HealthCheckResult.Passed(); }) - .AddCheck("two", () => { return HealthCheckResult.Passed(); }); + .AddCheck("one", () => { return HealthCheckResult.Healthy(); }) + .AddCheck("two", () => { return HealthCheckResult.Healthy(); }); // Choosing big values for tests to make sure that we're not dependent on the defaults. // All of the tests that rely on the timer will set their own values for speed. diff --git a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs index 453398e639..07f8e5a8e3 100644 --- a/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs +++ b/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests/HealthReportTest.cs @@ -13,7 +13,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks [InlineData(HealthStatus.Healthy)] [InlineData(HealthStatus.Degraded)] [InlineData(HealthStatus.Unhealthy)] - [InlineData(HealthStatus.Failed)] public void Status_MatchesWorstStatusInResults(HealthStatus status) { var result = new HealthReport(new Dictionary()