New: Added health check warning to emphasis when a series was deleted instead of only logging it in System Events

pull/3357/head
Taloth Saldono 5 years ago
parent ceaaec5378
commit 8a2a41fab0

@ -0,0 +1,77 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class RemovedSeriesCheckFixture : CoreTest<RemovedSeriesCheck>
{
private void GivenSeries(int amount, int deleted)
{
List<Series> series;
if (amount == 0)
{
series = new List<Series>();
}
else if (deleted == 0)
{
series = Builder<Series>.CreateListOfSize(amount)
.All()
.With(v => v.Status = SeriesStatusType.Continuing)
.BuildList();
}
else
{
series = Builder<Series>.CreateListOfSize(amount)
.All()
.With(v => v.Status = SeriesStatusType.Continuing)
.Random(deleted)
.With(v => v.Status = SeriesStatusType.Deleted)
.BuildList();
}
Mocker.GetMock<ISeriesService>()
.Setup(v => v.GetAllSeries())
.Returns(series);
}
[Test]
public void should_return_error_if_series_no_longer_on_tvdb()
{
GivenSeries(4, 1);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_error_if_multiple_series_no_longer_on_tvdb()
{
GivenSeries(4, 2);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_ok_if_all_series_still_on_tvdb()
{
GivenSeries(4, 0);
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_if_no_series_exist()
{
GivenSeries(0, 0);
Subject.Check().ShouldBeOk();
}
}
}

@ -123,6 +123,30 @@ namespace NzbDrone.Core.Test.TvTests
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
} }
[Test]
public void should_mark_as_deleted_if_tvdb_id_not_found()
{
Subject.Execute(new RefreshSeriesCommand(_series.Id));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.Is<Series>(s => s.Status == SeriesStatusType.Deleted), It.IsAny<bool>()), Times.Once());
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_not_remark_as_deleted_if_tvdb_id_not_found()
{
_series.Status = SeriesStatusType.Deleted;
Subject.Execute(new RefreshSeriesCommand(_series.Id));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>(), It.IsAny<bool>()), Times.Never());
ExceptionVerification.ExpectedErrors(1);
}
[Test] [Test]
public void should_update_if_tvdb_id_changed() public void should_update_if_tvdb_id_changed()
{ {

@ -3,7 +3,7 @@ using System;
namespace NzbDrone.Core.HealthCheck namespace NzbDrone.Core.HealthCheck
{ {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CheckOnAttribute: Attribute public class CheckOnAttribute : Attribute
{ {
public Type EventType { get; set; } public Type EventType { get; set; }
public CheckOnCondition Condition { get; set; } public CheckOnCondition Condition { get; set; }

@ -0,0 +1,53 @@
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(SeriesUpdatedEvent))]
[CheckOn(typeof(SeriesDeletedEvent), CheckOnCondition.FailedOnly)]
public class RemovedSeriesCheck : HealthCheckBase, ICheckOnCondition<SeriesUpdatedEvent>, ICheckOnCondition<SeriesDeletedEvent>
{
private readonly ISeriesService _seriesService;
private readonly Logger _logger;
public RemovedSeriesCheck(ISeriesService seriesService, Logger logger)
{
_seriesService = seriesService;
_logger = logger;
}
public override HealthCheck Check()
{
var deletedSeries = _seriesService.GetAllSeries().Where(v => v.Status == SeriesStatusType.Deleted).ToList();
if (deletedSeries.Empty())
{
return new HealthCheck(GetType());
}
var seriesText = deletedSeries.Select(s => $"{s.Title} (tvdbid {s.TvdbId})").Join(", ");
if (deletedSeries.Count() == 1)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Series {seriesText} was removed from TheTVDB");
}
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Series {seriesText} were removed from TheTVDB");
}
public bool ShouldCheckOnEvent(SeriesDeletedEvent deletedEvent)
{
return deletedEvent.Series.Status == SeriesStatusType.Deleted;
}
public bool ShouldCheckOnEvent(SeriesUpdatedEvent updatedEvent)
{
return updatedEvent.Series.Status == SeriesStatusType.Deleted;
}
}
}

@ -1,14 +1,46 @@
using System;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.HealthCheck 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 IProvideHealthCheck HealthCheck { get; set; }
public CheckOnCondition Condition { get; set; } public CheckOnCondition Condition { get; set; }
public ICheckOnCondition<TEvent> EventFilter { get; set; }
public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition) public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition)
{ {
HealthCheck = healthCheck; HealthCheck = healthCheck;
Condition = condition; 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;
} }
} }
} }

@ -29,7 +29,7 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck[] _healthChecks; private readonly IProvideHealthCheck[] _healthChecks;
private readonly IProvideHealthCheck[] _startupHealthChecks; private readonly IProvideHealthCheck[] _startupHealthChecks;
private readonly IProvideHealthCheck[] _scheduledHealthChecks; private readonly IProvideHealthCheck[] _scheduledHealthChecks;
private readonly Dictionary<Type, EventDrivenHealthCheck[]> _eventDrivenHealthChecks; private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly ICacheManager _cacheManager; private readonly ICacheManager _cacheManager;
private readonly Logger _logger; private readonly Logger _logger;
@ -58,10 +58,16 @@ namespace NzbDrone.Core.HealthCheck
return _healthCheckResults.Values.ToList(); return _healthCheckResults.Values.ToList();
} }
private Dictionary<Type, EventDrivenHealthCheck[]> GetEventDrivenHealthChecks() private Dictionary<Type, IEventDrivenHealthCheck[]> GetEventDrivenHealthChecks()
{ {
return _healthChecks 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) .GroupBy(t => t.Item1, t => t.Item2)
.ToDictionary(g => g.Key, g => g.ToArray()); .ToDictionary(g => g.Key, g => g.ToArray());
} }
@ -86,7 +92,7 @@ namespace NzbDrone.Core.HealthCheck
_eventAggregator.PublishEvent(new HealthCheckCompleteEvent()); _eventAggregator.PublishEvent(new HealthCheckCompleteEvent());
} }
public void Execute(CheckHealthCommand message) public void Execute(CheckHealthCommand message)
{ {
if (message.Trigger == CommandTrigger.Manual) if (message.Trigger == CommandTrigger.Manual)
@ -111,7 +117,7 @@ namespace NzbDrone.Core.HealthCheck
return; return;
} }
EventDrivenHealthCheck[] checks; IEventDrivenHealthCheck[] checks;
if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks)) if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks))
{ {
return; return;
@ -122,26 +128,14 @@ namespace NzbDrone.Core.HealthCheck
foreach (var eventDrivenHealthCheck in checks) foreach (var eventDrivenHealthCheck in checks)
{ {
if (eventDrivenHealthCheck.Condition == CheckOnCondition.Always)
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
continue;
}
var healthCheckType = eventDrivenHealthCheck.HealthCheck.GetType(); var healthCheckType = eventDrivenHealthCheck.HealthCheck.GetType();
var previouslyFailed = healthCheckResults.Any(r => r.Source == healthCheckType);
if (eventDrivenHealthCheck.Condition == CheckOnCondition.FailedOnly && if (eventDrivenHealthCheck.ShouldExecute(message, previouslyFailed))
healthCheckResults.Any(r => r.Source == healthCheckType))
{ {
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
continue; continue;
} }
if (eventDrivenHealthCheck.Condition == CheckOnCondition.SuccessfulOnly &&
healthCheckResults.None(r => r.Source == healthCheckType))
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
}
} }

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

@ -54,9 +54,26 @@ namespace NzbDrone.Core.Tv
{ {
_logger.ProgressInfo("Updating {0}", series.Title); _logger.ProgressInfo("Updating {0}", series.Title);
var tuple = _seriesInfo.GetSeriesInfo(series.TvdbId); Series seriesInfo;
List<Episode> episodes;
var seriesInfo = tuple.Item1; try
{
var tuple = _seriesInfo.GetSeriesInfo(series.TvdbId);
seriesInfo = tuple.Item1;
episodes = tuple.Item2;
}
catch (SeriesNotFoundException)
{
if (series.Status != SeriesStatusType.Deleted)
{
series.Status = SeriesStatusType.Deleted;
_seriesService.UpdateSeries(series);
_logger.Debug("Series marked as deleted on tvdb for {0}", series.Title);
_eventAggregator.PublishEvent(new SeriesUpdatedEvent(series));
}
throw;
}
if (series.TvdbId != seriesInfo.TvdbId) if (series.TvdbId != seriesInfo.TvdbId)
{ {
@ -102,7 +119,7 @@ namespace NzbDrone.Core.Tv
series.Seasons = UpdateSeasons(series, seriesInfo); series.Seasons = UpdateSeasons(series, seriesInfo);
_seriesService.UpdateSeries(series); _seriesService.UpdateSeries(series);
_refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2); _refreshEpisodeService.RefreshEpisodeInfo(series, episodes);
_logger.Debug("Finished series refresh for {0}", series.Title); _logger.Debug("Finished series refresh for {0}", series.Title);
_eventAggregator.PublishEvent(new SeriesUpdatedEvent(series)); _eventAggregator.PublishEvent(new SeriesUpdatedEvent(series));

@ -2,7 +2,9 @@
{ {
public enum SeriesStatusType public enum SeriesStatusType
{ {
Deleted = -1,
Continuing = 0, Continuing = 0,
Ended = 1 Ended = 1,
Upcoming = 2
} }
} }

Loading…
Cancel
Save