From 649ecd94ea06e5a502d1de982a21490b40b8d1b6 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 6 Sep 2020 23:29:24 -0400 Subject: [PATCH] New: Event Driven HealthCheck Support Co-Authored-By: Taloth Signed-off-by: Robin Dadswell --- .../CleanseLogMessageFixture.cs | 2 +- .../HealthCheck/HealthCheckServiceFixture.cs | 81 +++++++++++++++++++ .../HealthCheck/EventDrivenHealthCheck.cs | 34 +++++++- .../HealthCheck/HealthCheckService.cs | 31 +++---- .../HealthCheck/ICheckOnCondition.cs | 7 ++ 5 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 src/NzbDrone.Core.Test/HealthCheck/HealthCheckServiceFixture.cs create mode 100644 src/NzbDrone.Core/HealthCheck/ICheckOnCondition.cs diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index 07a55d397..220685d77 100644 --- a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs +++ b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.Instrumentation; diff --git a/src/NzbDrone.Core.Test/HealthCheck/HealthCheckServiceFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/HealthCheckServiceFixture.cs new file mode 100644 index 000000000..fcf9d58c8 --- /dev/null +++ b/src/NzbDrone.Core.Test/HealthCheck/HealthCheckServiceFixture.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.HealthCheck; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.HealthCheck +{ + public class HealthCheckServiceFixture : CoreTest + { + private FakeHealthCheck _healthCheck; + + [SetUp] + public void SetUp() + { + _healthCheck = new FakeHealthCheck(); + + Mocker.SetConstant>(new[] { _healthCheck }); + Mocker.SetConstant(Mocker.Resolve()); + } + + [Test] + public void should_not_execute_conditional() + { + Subject.HandleAsync(new FakeEvent()); + + _healthCheck.Executed.Should().BeFalse(); + } + + [Test] + public void should_execute_conditional() + { + Subject.HandleAsync(new FakeEvent() { ShouldExecute = true }); + + _healthCheck.Executed.Should().BeTrue(); + } + + [Test] + public void should_execute_unconditional() + { + Subject.HandleAsync(new FakeEvent2()); + + _healthCheck.Executed.Should().BeTrue(); + } + } + + public class FakeEvent : IEvent + { + public bool ShouldExecute { get; set; } + } + + public class FakeEvent2 : IEvent + { + public bool ShouldExecute { get; set; } + } + + [CheckOn(typeof(FakeEvent))] + [CheckOn(typeof(FakeEvent2))] + public class FakeHealthCheck : IProvideHealthCheck, ICheckOnCondition + { + public bool CheckOnStartup => false; + public bool CheckOnSchedule => false; + + public bool Executed { get; set; } + public bool Checked { get; set; } + + public Core.HealthCheck.HealthCheck Check() + { + Executed = true; + + return new Core.HealthCheck.HealthCheck(GetType()); + } + + public bool ShouldCheckOnEvent(FakeEvent message) + { + return message.ShouldExecute; + } + } +} diff --git a/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs b/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs index 0b55c1ff2..544c0d9c3 100644 --- a/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs @@ -1,14 +1,46 @@ +using System; +using NzbDrone.Common.Messaging; + namespace NzbDrone.Core.HealthCheck { - public class EventDrivenHealthCheck + public interface IEventDrivenHealthCheck + { + IProvideHealthCheck HealthCheck { get; } + + bool ShouldExecute(IEvent message, bool previouslyFailed); + } + + public class EventDrivenHealthCheck : IEventDrivenHealthCheck { public IProvideHealthCheck HealthCheck { get; set; } public CheckOnCondition Condition { get; set; } + public ICheckOnCondition EventFilter { get; set; } public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition) { HealthCheck = healthCheck; Condition = condition; + EventFilter = healthCheck as ICheckOnCondition; + } + + public bool ShouldExecute(IEvent message, bool previouslyFailed) + { + if (Condition == CheckOnCondition.SuccessfulOnly && previouslyFailed) + { + return false; + } + + if (Condition == CheckOnCondition.FailedOnly && !previouslyFailed) + { + return false; + } + + if (EventFilter != null && !EventFilter.ShouldCheckOnEvent((TEvent)message)) + { + return false; + } + + return true; } } } diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs index 49492c368..d4aa2f314 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Cache; -using NzbDrone.Common.Extensions; using NzbDrone.Common.Messaging; using NzbDrone.Common.Reflection; using NzbDrone.Core.Lifecycle; @@ -25,7 +24,7 @@ namespace NzbDrone.Core.HealthCheck private readonly IProvideHealthCheck[] _healthChecks; private readonly IProvideHealthCheck[] _startupHealthChecks; private readonly IProvideHealthCheck[] _scheduledHealthChecks; - private readonly Dictionary _eventDrivenHealthChecks; + private readonly Dictionary _eventDrivenHealthChecks; private readonly IEventAggregator _eventAggregator; private readonly ICacheManager _cacheManager; private readonly Logger _logger; @@ -54,10 +53,16 @@ namespace NzbDrone.Core.HealthCheck return _healthCheckResults.Values.ToList(); } - private Dictionary GetEventDrivenHealthChecks() + private Dictionary GetEventDrivenHealthChecks() { return _healthChecks - .SelectMany(h => h.GetType().GetAttributes().Select(a => Tuple.Create(a.EventType, new EventDrivenHealthCheck(h, a.Condition)))) + .SelectMany(h => h.GetType().GetAttributes().Select(a => + { + var eventDrivenType = typeof(EventDrivenHealthCheck<>).MakeGenericType(a.EventType); + var eventDriven = (IEventDrivenHealthCheck)Activator.CreateInstance(eventDrivenType, h, a.Condition); + + return Tuple.Create(a.EventType, eventDriven); + })) .GroupBy(t => t.Item1, t => t.Item2) .ToDictionary(g => g.Key, g => g.ToArray()); } @@ -122,7 +127,7 @@ namespace NzbDrone.Core.HealthCheck return; } - EventDrivenHealthCheck[] checks; + IEventDrivenHealthCheck[] checks; if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks)) { return; @@ -133,26 +138,14 @@ namespace NzbDrone.Core.HealthCheck foreach (var eventDrivenHealthCheck in checks) { - if (eventDrivenHealthCheck.Condition == CheckOnCondition.Always) - { - filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); - continue; - } - var healthCheckType = eventDrivenHealthCheck.HealthCheck.GetType(); + var previouslyFailed = healthCheckResults.Any(r => r.Source == healthCheckType); - if (eventDrivenHealthCheck.Condition == CheckOnCondition.FailedOnly && - healthCheckResults.Any(r => r.Source == healthCheckType)) + if (eventDrivenHealthCheck.ShouldExecute(message, previouslyFailed)) { filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); continue; } - - if (eventDrivenHealthCheck.Condition == CheckOnCondition.SuccessfulOnly && - healthCheckResults.None(r => r.Source == healthCheckType)) - { - filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); - } } // TODO: Add debounce diff --git a/src/NzbDrone.Core/HealthCheck/ICheckOnCondition.cs b/src/NzbDrone.Core/HealthCheck/ICheckOnCondition.cs new file mode 100644 index 000000000..fcd761dac --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/ICheckOnCondition.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.HealthCheck +{ + public interface ICheckOnCondition + { + bool ShouldCheckOnEvent(TEvent message); + } +}