New: Event Driven HealthCheck Support

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
This commit is contained in:
Qstick 2020-09-06 23:29:24 -04:00
parent 8d8cbd07aa
commit d72014eb66
5 changed files with 141 additions and 21 deletions

View File

@ -1,4 +1,4 @@
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Instrumentation;
@ -17,9 +17,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
[TestCase(@"http://nzb.su/getnzb/2b51db35e1912ffc138825a12b9933d2.nzb&i=37292&r=2b51db35e1910123321025a12b9933d2")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
[TestCase(@"{ ""Name"" : ""Server1.Username"", ""Value"" : ""mySecret"" }, { ""Name"" : ""Server1.Password"", ""Value"" : ""mySecret"" }, ")]
// Sabnzbd
[TestCase(@"http://127.0.0.1:1234/api/call?vv=1&apikey=mySecret")]
[TestCase(@"http://127.0.0.1:1234/api/call?vv=1&ma_username=mySecret&ma_password=mySecret")]
@ -30,6 +32,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"""misc"":{""username"":""mySecret"",""api_key"":""mySecret"",""password"":""mySecret"",""nzb_key"":""mySecret""}")]
[TestCase(@"""servers"":[{""username"":""mySecret"",""password"":""mySecret""}]")]
[TestCase(@"""misc"":{""email_account"":""mySecret"",""email_to"":[],""email_from"":"""",""email_pwd"":""mySecret""}")]
// uTorrent
[TestCase(@"http://localhost:9091/gui/?token=wThmph5l0ZXfH-a6WOA4lqiLvyjCP0FpMrMeXmySecret_VXBO11HoKL751MAAAAA&list=1")]
[TestCase(@",[""boss_key"",0,""mySecret"",{""access"":""Y""}],[""boss_key_salt"",0,""mySecret"",{""access"":""W""}]")]
@ -37,16 +40,20 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@",[""webui.uconnect_username"",2,""mySecret"",{""access"":""Y""}],[""webui.uconnect_password"",2,""mySecret"",{""access"":""Y""}]")]
[TestCase(@",[""proxy.proxy"",2,""mySecret"",{""access"":""Y""}]")]
[TestCase(@",[""proxy.username"",2,""mySecret"",{""access"":""Y""}],[""proxy.password"",2,""mySecret"",{""access"":""Y""}]")]
// Deluge
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
[TestCase(@"auth.login(""mySecret"")")]
// BroadcastheNet
[TestCase(@"method: ""getTorrents"", ""params"": [ ""mySecret"",")]
[TestCase(@"getTorrents(""mySecret"", [asdfasdf], 100, 0)")]
[TestCase(@"""DownloadURL"":""https:\/\/broadcasthe.net\/torrents.php?action=download&id=123&authkey=mySecret&torrent_pass=mySecret""")]
// Spotify Refresh
[TestCase(@"https://spotify.lidarr.audio/renew?refresh_token=mySecret")]
// Plex
[TestCase(@" http://localhost:32400/library/metadata/12345/refresh?X-Plex-Client-Identifier=1234530f-422f-4aac-b6b3-01233210aaaa&X-Plex-Product=Sonarr&X-Plex-Platform=Windows&X-Plex-Platform-Version=7&X-Plex-Device-Name=Sonarr&X-Plex-Version=3.0.3.833&X-Plex-Token=mySecret")]
public void should_clean_message(string message)

View File

@ -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<HealthCheckService>
{
private FakeHealthCheck _healthCheck;
[SetUp]
public void SetUp()
{
_healthCheck = new FakeHealthCheck();
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
}
[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<FakeEvent>
{
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;
}
}
}

View File

@ -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<TEvent> : IEventDrivenHealthCheck
{
public IProvideHealthCheck HealthCheck { get; set; }
public CheckOnCondition Condition { get; set; }
public ICheckOnCondition<TEvent> EventFilter { get; set; }
public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition)
{
HealthCheck = healthCheck;
Condition = condition;
EventFilter = healthCheck as ICheckOnCondition<TEvent>;
}
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;
}
}
}

View File

@ -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<Type, EventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _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<Type, EventDrivenHealthCheck[]> GetEventDrivenHealthChecks()
private Dictionary<Type, IEventDrivenHealthCheck[]> GetEventDrivenHealthChecks()
{
return _healthChecks
.SelectMany(h => h.GetType().GetAttributes<CheckOnAttribute>().Select(a => Tuple.Create(a.EventType, new EventDrivenHealthCheck(h, a.Condition))))
.SelectMany(h => h.GetType().GetAttributes<CheckOnAttribute>().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

View File

@ -0,0 +1,7 @@
namespace NzbDrone.Core.HealthCheck
{
public interface ICheckOnCondition<TEvent>
{
bool ShouldCheckOnEvent(TEvent message);
}
}