Netcore: Health check notifier hosted service (#9295)
* Implemented health check notifier as a hosted service. Added validation to health check settings. * Registered health check notifier as a hosted service. Modified health check nested settings to use concrete classes to align with other configuration models. * Resolved issues with email sending using development server. * PR review comments and fixed failing unit test. * Changed period and delay millisecond and hourly values to TimeSpans. Changed configuration of first run time for health check notifications to use H:mm format. * Set up SecureSocketOptions as a locally defined enum. * Tightened up time format validation to verify input is an actual time (with hours and minutes only) and not a timespan. * Aligned naming and namespace of health check configuration related classes with other configuration classes. * Created constants for hex colors used in formatting health check results as HTML. * Revert "Tightened up time format validation to verify input is an actual time (with hours and minutes only) and not a timespan." This reverts commit f9bb8a7a825bcb58146879f18b47922e09453e2d. * Renamed method to be clear validation is of a TimeSpan and not a time. Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthCheckSettingsExtensionsTests
|
||||
{
|
||||
[Test]
|
||||
public void Returns_Notification_Delay_From_Provided_Time()
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
FirstRunTime = "1230",
|
||||
}
|
||||
};
|
||||
var now = DateTime.Now.Date.AddHours(12);
|
||||
var result = settings.GetNotificationDelay(now, TimeSpan.Zero);
|
||||
Assert.AreEqual(30, result.Minutes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_Notification_Delay_From_Default_When_Provided_Time_Too_Close_To_Current_Time()
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
FirstRunTime = "1230",
|
||||
}
|
||||
};
|
||||
var now = DateTime.Now.Date.AddHours(12).AddMinutes(25);
|
||||
var result = settings.GetNotificationDelay(now, TimeSpan.FromMinutes(10));
|
||||
Assert.AreEqual(10, result.Minutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Validation;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Validation;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthChecksSettingsValidationTests
|
||||
{
|
||||
[Test]
|
||||
public void Returns_Success_ForValid_Configuration()
|
||||
{
|
||||
var validator = new HealthChecksSettingsValidator();
|
||||
var options = BuildHealthChecksSettings();
|
||||
var result = validator.Validate("settings", options);
|
||||
Assert.True(result.Succeeded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_Fail_For_Configuration_With_Invalid_Notification_FirstRunTime()
|
||||
{
|
||||
var validator = new HealthChecksSettingsValidator();
|
||||
var options = BuildHealthChecksSettings(firstRunTime: "25:00");
|
||||
var result = validator.Validate("settings", options);
|
||||
Assert.False(result.Succeeded);
|
||||
}
|
||||
|
||||
private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "12:00")
|
||||
{
|
||||
return new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
Enabled = true,
|
||||
FirstRunTime = firstRunTime,
|
||||
Period = TimeSpan.FromHours(1),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,14 +293,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper
|
||||
Assert.AreEqual(expected, output);
|
||||
}
|
||||
|
||||
#region Cases
|
||||
[TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")]
|
||||
[TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")]
|
||||
#endregion
|
||||
public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected)
|
||||
{
|
||||
var output = input.ReplaceMany(toReplace.ToArray(), replacement);
|
||||
Assert.AreEqual(expected, output);
|
||||
}
|
||||
|
||||
[TestCase("", false)]
|
||||
[TestCase("12:34", true)]
|
||||
[TestCase("1:14:23", true)]
|
||||
[TestCase("25:03", false)]
|
||||
[TestCase("18:61", false)]
|
||||
public void IsValidTimeSpan(string input, bool expected)
|
||||
{
|
||||
var result = input.IsValidTimeSpan();
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Infrastructure.HealthCheck;
|
||||
using Umbraco.Infrastructure.HostedServices;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck.NotificationMethods;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthCheckNotifierTests
|
||||
{
|
||||
private Mock<IHealthCheckNotificationMethod> _mockNotificationMethod;
|
||||
|
||||
private const string Check1Id = "00000000-0000-0000-0000-000000000001";
|
||||
private const string Check2Id = "00000000-0000-0000-0000-000000000002";
|
||||
private const string Check3Id = "00000000-0000-0000-0000-000000000003";
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Not_Enabled()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(enabled: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Runtime_State_Is_Not_Run()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(runtimeLevel: RuntimeLevel.Boot);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Server_Role_Is_Replica()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Server_Role_Is_Unknown()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Not_Main_Dom()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(isMainDom: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_With_No_Enabled_Notification_Methods()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(notificationEnabled: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Executes_With_Enabled_Notification_Methods()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier();
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Executes_Only_Enabled_Checks()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier();
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.Is<HealthCheckResults>(
|
||||
y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once);
|
||||
}
|
||||
|
||||
private HealthCheckNotifier CreateHealthCheckNotifier(
|
||||
bool enabled = true,
|
||||
RuntimeLevel runtimeLevel = RuntimeLevel.Run,
|
||||
ServerRole serverRole = ServerRole.Single,
|
||||
bool isMainDom = true,
|
||||
bool notificationEnabled = true)
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
Enabled = enabled,
|
||||
DisabledChecks = new List<DisabledHealthCheckSettings>
|
||||
{
|
||||
new DisabledHealthCheckSettings { Id = Guid.Parse(Check3Id) }
|
||||
}
|
||||
},
|
||||
DisabledChecks = new List<DisabledHealthCheckSettings>
|
||||
{
|
||||
new DisabledHealthCheckSettings { Id = Guid.Parse(Check2Id) }
|
||||
}
|
||||
};
|
||||
var checks = new HealthCheckCollection(new List<HealthCheck>
|
||||
{
|
||||
new TestHealthCheck1(),
|
||||
new TestHealthCheck2(),
|
||||
new TestHealthCheck3(),
|
||||
});
|
||||
|
||||
_mockNotificationMethod = new Mock<IHealthCheckNotificationMethod>();
|
||||
_mockNotificationMethod.SetupGet(x => x.Enabled).Returns(notificationEnabled);
|
||||
var notifications = new HealthCheckNotificationMethodCollection(new List<IHealthCheckNotificationMethod> { _mockNotificationMethod.Object });
|
||||
|
||||
var mockRunTimeState = new Mock<IRuntimeState>();
|
||||
mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel);
|
||||
|
||||
var mockServerRegistrar = new Mock<IServerRegistrar>();
|
||||
mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole);
|
||||
|
||||
var mockMainDom = new Mock<IMainDom>();
|
||||
mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom);
|
||||
|
||||
var mockScopeProvider = new Mock<IScopeProvider>();
|
||||
var mockLogger = new Mock<ILogger<HealthCheckNotifier>>();
|
||||
var mockProfilingLogger = new Mock<IProfilingLogger>();
|
||||
|
||||
return new HealthCheckNotifier(Options.Create(settings), checks, notifications,
|
||||
mockRunTimeState.Object, mockServerRegistrar.Object, mockMainDom.Object, mockScopeProvider.Object,
|
||||
mockLogger.Object, mockProfilingLogger.Object);
|
||||
}
|
||||
|
||||
[HealthCheck(Check1Id, "Check1")]
|
||||
private class TestHealthCheck1 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
[HealthCheck(Check2Id, "Check2")]
|
||||
private class TestHealthCheck2 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
[HealthCheck(Check3Id, "Check3")]
|
||||
private class TestHealthCheck3 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
private class TestHealthCheck : HealthCheck
|
||||
{
|
||||
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
|
||||
{
|
||||
return new HealthCheckStatus("Check message");
|
||||
}
|
||||
|
||||
public override IEnumerable<HealthCheckStatus> GetStatus()
|
||||
{
|
||||
return Enumerable.Empty<HealthCheckStatus>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user