diff --git a/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs b/src/NzbDrone.Common.Test/InstrumentationTests/CleanseLogMessageFixture.cs index bd925d05e..46c2e08b5 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; @@ -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) 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); + } +}