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

This commit is contained in:
Taloth Saldono 2019-10-19 17:15:28 +02:00
parent ceaaec5378
commit 8a2a41fab0
9 changed files with 231 additions and 25 deletions

View File

@ -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();
}
}
}

View File

@ -123,6 +123,30 @@ namespace NzbDrone.Core.Test.TvTests
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]
public void should_update_if_tvdb_id_changed()
{

View File

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

View File

@ -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;
}
}
}

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

@ -29,7 +29,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;
@ -58,10 +58,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());
}
@ -86,7 +92,7 @@ namespace NzbDrone.Core.HealthCheck
_eventAggregator.PublishEvent(new HealthCheckCompleteEvent());
}
public void Execute(CheckHealthCommand message)
{
if (message.Trigger == CommandTrigger.Manual)
@ -111,7 +117,7 @@ namespace NzbDrone.Core.HealthCheck
return;
}
EventDrivenHealthCheck[] checks;
IEventDrivenHealthCheck[] checks;
if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks))
{
return;
@ -122,26 +128,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);
}
}

View File

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

View File

@ -54,9 +54,26 @@ namespace NzbDrone.Core.Tv
{
_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)
{
@ -102,7 +119,7 @@ namespace NzbDrone.Core.Tv
series.Seasons = UpdateSeasons(series, seriesInfo);
_seriesService.UpdateSeries(series);
_refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2);
_refreshEpisodeService.RefreshEpisodeInfo(series, episodes);
_logger.Debug("Finished series refresh for {0}", series.Title);
_eventAggregator.PublishEvent(new SeriesUpdatedEvent(series));

View File

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