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:
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);
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
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));
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
{
|
||||
public enum SeriesStatusType
|
||||
{
|
||||
Deleted = -1,
|
||||
Continuing = 0,
|
||||
Ended = 1
|
||||
Ended = 1,
|
||||
Upcoming = 2
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue