From 09b3bd14c0372460a21348e7c6a350855694bfe2 Mon Sep 17 00:00:00 2001 From: Matt Brailsford Date: Fri, 31 Jan 2025 15:13:49 +0000 Subject: [PATCH] v14: Async healthchecks (#17090) * Make block editor base classes public * Update BlockEditorValues.cs Change to trigger a new build for #16774 * Make healthchecks fully async * Updated obsolete comments to reference next but one major. --------- Co-authored-by: Kenn Jacobsen Co-authored-by: Andy Butland --- .../ExecuteActionHealthCheckController.cs | 2 +- .../Group/CheckHealthCheckGroupController.cs | 2 +- .../HealthCheckGroupPresentationFactory.cs | 15 ++++++++--- .../IHealthCheckGroupPresentationFactory.cs | 12 +++++++++ .../Checks/AbstractSettingsCheck.cs | 2 +- .../Checks/Data/DatabaseIntegrityCheck.cs | 2 +- .../FolderAndFilePermissionsCheck.cs | 2 +- .../Checks/Security/BaseHttpHeaderCheck.cs | 2 +- .../Checks/Security/ExcessiveHeadersCheck.cs | 2 +- .../HealthChecks/Checks/Security/HstsCheck.cs | 2 +- .../Checks/Security/HttpsCheck.cs | 2 +- .../Security/UmbracoApplicationUrlCheck.cs | 2 +- .../HealthChecks/Checks/Services/SmtpCheck.cs | 2 +- src/Umbraco.Core/HealthChecks/HealthCheck.cs | 26 +++++++++++++++++-- .../HealthChecks/HealthCheckResults.cs | 2 +- .../Jobs/HealthCheckNotifierJobTests.cs | 2 +- .../HealthChecks/HealthCheckResultsTests.cs | 4 +-- 17 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs index fcdea80420..6f3e5ea933 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/ExecuteActionHealthCheckController.cs @@ -61,7 +61,7 @@ public class ExecuteActionHealthCheckController : HealthCheckControllerBase return BadRequest(invalidModelProblem); } - HealthCheckStatus result = healthCheck.ExecuteAction(_umbracoMapper.Map(action)!); + HealthCheckStatus result = await healthCheck.ExecuteActionAsync(_umbracoMapper.Map(action)!); return await Task.FromResult(Ok(_umbracoMapper.Map(result))); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/CheckHealthCheckGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/CheckHealthCheckGroupController.cs index a578c60662..40075c074e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/CheckHealthCheckGroupController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/HealthCheck/Group/CheckHealthCheckGroupController.cs @@ -39,6 +39,6 @@ public class CheckHealthCheckGroupController : HealthCheckGroupControllerBase return HealthCheckGroupNotFound(); } - return await Task.FromResult(Ok(_healthCheckGroupPresentationFactory.CreateHealthCheckGroupWithResultViewModel(group))); + return Ok(await _healthCheckGroupPresentationFactory.CreateHealthCheckGroupWithResultViewModelAsync(group)); } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/HealthCheckGroupPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/HealthCheckGroupPresentationFactory.cs index f1dd6ec5d8..4f092c151c 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/HealthCheckGroupPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/HealthCheckGroupPresentationFactory.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Mapping; namespace Umbraco.Cms.Api.Management.Factories; +// TODO (V16): Make internal sealed. public class HealthCheckGroupPresentationFactory : IHealthCheckGroupPresentationFactory { private readonly HealthChecksSettings _healthChecksSettings; @@ -40,13 +41,21 @@ public class HealthCheckGroupPresentationFactory : IHealthCheckGroupPresentation return groups; } + [Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")] public HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping healthCheckGroup) + => CreateHealthCheckGroupWithResultViewModelAsync(healthCheckGroup).GetAwaiter().GetResult(); + + [Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")] + public HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck) => + CreateHealthCheckWithResultViewModelAsync(healthCheck).GetAwaiter().GetResult(); + + public async Task CreateHealthCheckGroupWithResultViewModelAsync(IGrouping healthCheckGroup) { var healthChecks = new List(); foreach (HealthCheck healthCheck in healthCheckGroup) { - healthChecks.Add(CreateHealthCheckWithResultViewModel(healthCheck)); + healthChecks.Add(await CreateHealthCheckWithResultViewModelAsync(healthCheck)); } var healthCheckGroupViewModel = new HealthCheckGroupWithResultResponseModel @@ -57,11 +66,11 @@ public class HealthCheckGroupPresentationFactory : IHealthCheckGroupPresentation return healthCheckGroupViewModel; } - public HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck) + public async Task CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck) { _logger.LogDebug($"Running health check: {healthCheck.Name}"); - IEnumerable results = healthCheck.GetStatus().Result; + IEnumerable results = await healthCheck.GetStatusAsync(); var healthCheckViewModel = new HealthCheckWithResultPresentationModel { diff --git a/src/Umbraco.Cms.Api.Management/Factories/IHealthCheckGroupPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IHealthCheckGroupPresentationFactory.cs index e8395e0ff4..fa03c2633a 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IHealthCheckGroupPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IHealthCheckGroupPresentationFactory.cs @@ -7,7 +7,19 @@ public interface IHealthCheckGroupPresentationFactory { IEnumerable> CreateGroupingFromHealthCheckCollection(); + [Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")] HealthCheckGroupWithResultResponseModel CreateHealthCheckGroupWithResultViewModel(IGrouping healthCheckGroup); + [Obsolete("Use CreateHealthCheckGroupWithResultViewModelAsync instead. Will be removed in v17.")] HealthCheckWithResultPresentationModel CreateHealthCheckWithResultViewModel(HealthCheck healthCheck); + + Task CreateHealthCheckGroupWithResultViewModelAsync(IGrouping healthCheckGroup) +#pragma warning disable CS0618 // Type or member is obsolete + => Task.FromResult(CreateHealthCheckGroupWithResultViewModel(healthCheckGroup)); +#pragma warning restore CS0618 // Type or member is obsolete + + Task CreateHealthCheckWithResultViewModelAsync(HealthCheck healthCheck) +#pragma warning disable CS0618 // Type or member is obsolete + => Task.FromResult(CreateHealthCheckWithResultViewModel(healthCheck)); +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs index 4dddf34270..390ae44647 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs @@ -63,7 +63,7 @@ public abstract class AbstractSettingsCheck : HealthCheck "healthcheck", "checkErrorMessageUnexpectedValue", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }); /// - public override Task> GetStatus() + public override Task> GetStatusAsync() { // update the successMessage with the CurrentValue var successMessage = string.Format(CheckSuccessMessage, ItemPath, Values, CurrentValue); diff --git a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs index 4c3936f6cb..8ee30d75ac 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs @@ -38,7 +38,7 @@ public class DatabaseIntegrityCheck : HealthCheck /// /// Get the status for this health check /// - public override Task> GetStatus() => + public override Task> GetStatusAsync() => Task.FromResult((IEnumerable)new[] { CheckDocuments(false), CheckMedia(false) }); /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs index 13a45c169c..f530144b99 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -35,7 +35,7 @@ public class FolderAndFilePermissionsCheck : HealthCheck /// /// Get the status for this health check /// - public override Task> GetStatus() + public override Task> GetStatusAsync() { _filePermissionHelper.RunFilePermissionTestSuite( out Dictionary> errors); diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index 138ba87789..b9d14a9131 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -62,7 +62,7 @@ public abstract class BaseHttpHeaderCheck : HealthCheck /// /// Get the status for this health check /// - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => await Task.WhenAll(CheckForHeader()); /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index 08088251e5..e527463929 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -35,7 +35,7 @@ public class ExcessiveHeadersCheck : HealthCheck /// /// Get the status for this health check /// - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => await Task.WhenAll(CheckForHeaders()); /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs index 0637404045..1535316fea 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs @@ -43,7 +43,7 @@ public class HstsCheck : BaseHttpHeaderCheck protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.HstsCheck; /// - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => new HealthCheckStatus[] { await CheckForHeader() }; /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index fbe5933b28..e28b821842 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -46,7 +46,7 @@ public class HttpsCheck : HealthCheck _hostingEnvironment = hostingEnvironment; } /// - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => await Task.WhenAll( CheckIfCurrentSchemeIsHttps(), CheckHttpsConfigurationSetting(), diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs index 55406b9c0a..e64a036cb0 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -35,7 +35,7 @@ public class UmbracoApplicationUrlCheck : HealthCheck /// /// Get the status for this health check /// - public override Task> GetStatus() => + public override Task> GetStatusAsync() => Task.FromResult(CheckUmbracoApplicationUrl().Yield()); private HealthCheckStatus CheckUmbracoApplicationUrl() diff --git a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs index 7c013cb958..856424563d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs @@ -34,7 +34,7 @@ public class SmtpCheck : HealthCheck /// /// Get the status for this health check /// - public override Task> GetStatus() => + public override Task> GetStatusAsync() => Task.FromResult(CheckSmtpSettings().Yield()); /// diff --git a/src/Umbraco.Core/HealthChecks/HealthCheck.cs b/src/Umbraco.Core/HealthChecks/HealthCheck.cs index 06a1bd27f3..076f214a74 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheck.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheck.cs @@ -46,12 +46,34 @@ public abstract class HealthCheck : IDiscoverable /// If there are possible actions to take to rectify this check, this method must be overridden by a sub class /// in order to explicitly provide those actions. /// - public abstract Task> GetStatus(); + [Obsolete("Use GetStatusAsync instead. Will be removed in v17")] + public virtual Task> GetStatus() => Task.FromResult(Enumerable.Empty()); + + /// + /// Get the status for this health check + /// + /// + /// + /// If there are possible actions to take to rectify this check, this method must be overridden by a sub class + /// in order to explicitly provide those actions. + /// +#pragma warning disable CS0618 // Type or member is obsolete + public virtual Task> GetStatusAsync() => GetStatus(); +#pragma warning restore CS0618 // Type or member is obsolete /// /// Executes the action and returns it's status /// /// /// - public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action); + public virtual HealthCheckStatus ExecuteAction(HealthCheckAction action) => + new HealthCheckStatus("Not implemented"); + + /// + /// Executes the action and returns it's status + /// + /// + /// + public virtual Task ExecuteActionAsync(HealthCheckAction action) => + Task.FromResult(ExecuteAction(action)); } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index f88b96c9d6..fedd1ecca4 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -26,7 +26,7 @@ public class HealthCheckResults { try { - return await t.GetStatus(); + return await t.GetStatusAsync(); } catch (Exception ex) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs index 316605a04e..9eb6862bfc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs @@ -136,6 +136,6 @@ public class HealthCheckNotifierJobTests { public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => new("Check message"); - public override async Task> GetStatus() => Enumerable.Empty(); + public override async Task> GetStatusAsync() => Enumerable.Empty(); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs index 6a5189ab6a..7e98623707 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs @@ -26,7 +26,7 @@ public class HealthCheckResultsTests public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new NotImplementedException(); - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => new List { new(_message) { ResultType = _resultType } }; } @@ -56,7 +56,7 @@ public class HealthCheckResultsTests { } - public override async Task> GetStatus() => + public override async Task> GetStatusAsync() => throw new Exception("Check threw exception"); }