From c20c16b7a0e68b067b8337911c3e217ebe9d5c5f Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 17 Feb 2020 21:10:24 +0000 Subject: [PATCH] Finished healthchecks --- .../Checks/CouchPotatoHealthCheck.cs | 53 ++++++++++++ .../Checks/OmbiPingHealthCheck.cs | 47 ++++++++++ .../Checks/OmbiPingHealthCheckOptions.cs | 16 ++++ .../Checks/SickrageHealthCheck.cs | 54 ++++++++++++ .../HealthCheckExtensions.cs | 36 ++++++++ .../Ombi.HealthChecks.csproj | 4 + .../Settings/Models/OmbiSettings.cs | 1 + .../ClientApp/src/app/interfaces/ISettings.ts | 1 + .../src/app/settings/ombi/ombi.component.html | 81 ++++++++++-------- .../src/app/settings/ombi/ombi.component.ts | 1 + src/Ombi/HealthCheck.css | 3 +- src/Ombi/Startup.cs | 39 +++++---- src/Ombi/healthchecksdb | Bin 45056 -> 0 bytes 13 files changed, 280 insertions(+), 56 deletions(-) create mode 100644 src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs create mode 100644 src/Ombi.HealthChecks/Checks/OmbiPingHealthCheck.cs create mode 100644 src/Ombi.HealthChecks/Checks/OmbiPingHealthCheckOptions.cs create mode 100644 src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs delete mode 100644 src/Ombi/healthchecksdb diff --git a/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs b/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs new file mode 100644 index 000000000..a68ce327b --- /dev/null +++ b/src/Ombi.HealthChecks/Checks/CouchPotatoHealthCheck.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Ombi.Api.CouchPotato; +using Ombi.Api.Emby; +using Ombi.Api.Emby.Models; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models.Status; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Settings.Settings.Models.External; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.HealthChecks.Checks +{ + public class CouchPotatoHealthCheck : BaseHealthCheck + { + public CouchPotatoHealthCheck(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) + { + } + public override async Task CheckHealthAsync( + HealthCheckContext context, + CancellationToken cancellationToken = default) + { + using (var scope = CreateScope()) + { + var settingsProvider = scope.ServiceProvider.GetRequiredService>(); + var api = scope.ServiceProvider.GetRequiredService(); + var settings = await settingsProvider.GetSettingsAsync(); + if (!settings.Enabled) + { + return HealthCheckResult.Healthy("CouchPotato is not configured."); + } + + try + { + var result = await api.Status(settings.ApiKey, settings.FullUri); + if (result != null) + { + return HealthCheckResult.Healthy(); + } + return HealthCheckResult.Degraded("Couldn't get the status from CouchPotato"); + } + catch (Exception e) + { + return HealthCheckResult.Unhealthy("Could not communicate with CouchPotato", e); + } + } + } + } +} diff --git a/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheck.cs b/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheck.cs new file mode 100644 index 000000000..726d02078 --- /dev/null +++ b/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheck.cs @@ -0,0 +1,47 @@ +using HealthChecks.Network; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.HealthChecks.Checks +{ + public class OmbiPingHealthCheck + : IHealthCheck + { + private readonly OmbiPingHealthCheckOptions _options; + public OmbiPingHealthCheck(OmbiPingHealthCheckOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + var configuredHosts = _options.ConfiguredHosts.Values; + + try + { + foreach (var (host, timeout, status) in configuredHosts) + { + using (var ping = new Ping()) + { + var pingReply = await ping.SendPingAsync(host, timeout); + + if (pingReply.Status != IPStatus.Success) + { + return new HealthCheckResult(status, description: $"Ping check for host {host} is failed with status reply:{pingReply.Status}"); + } + } + } + + return HealthCheckResult.Healthy(); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + } + } + } +} diff --git a/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheckOptions.cs b/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheckOptions.cs new file mode 100644 index 000000000..f89c71a52 --- /dev/null +++ b/src/Ombi.HealthChecks/Checks/OmbiPingHealthCheckOptions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using System.Collections.Generic; + +namespace Ombi.HealthChecks.Checks +{ + public class OmbiPingHealthCheckOptions + { + internal Dictionary ConfiguredHosts { get; } = new Dictionary(); + + public OmbiPingHealthCheckOptions AddHost(string host, int timeout, HealthStatus status) + { + ConfiguredHosts.Add(host, (host, timeout, status)); + return this; + } + } +} diff --git a/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs b/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs new file mode 100644 index 000000000..c348e8edf --- /dev/null +++ b/src/Ombi.HealthChecks/Checks/SickrageHealthCheck.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Ombi.Api.CouchPotato; +using Ombi.Api.Emby; +using Ombi.Api.Emby.Models; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models.Status; +using Ombi.Api.SickRage; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Settings.Settings.Models.External; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.HealthChecks.Checks +{ + public class SickrageHealthCheck : BaseHealthCheck + { + public SickrageHealthCheck(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) + { + } + public override async Task CheckHealthAsync( + HealthCheckContext context, + CancellationToken cancellationToken = default) + { + using (var scope = CreateScope()) + { + var settingsProvider = scope.ServiceProvider.GetRequiredService>(); + var api = scope.ServiceProvider.GetRequiredService(); + var settings = await settingsProvider.GetSettingsAsync(); + if (!settings.Enabled) + { + return HealthCheckResult.Healthy("SickRage is not configured."); + } + + try + { + var result = await api.Ping(settings.ApiKey, settings.FullUri); + if (result != null) + { + return HealthCheckResult.Healthy(); + } + return HealthCheckResult.Degraded("Couldn't get the status from SickRage"); + } + catch (Exception e) + { + return HealthCheckResult.Unhealthy("Could not communicate with SickRage", e); + } + } + } + } +} diff --git a/src/Ombi.HealthChecks/HealthCheckExtensions.cs b/src/Ombi.HealthChecks/HealthCheckExtensions.cs index 2b5dbb667..e608d5ec0 100644 --- a/src/Ombi.HealthChecks/HealthCheckExtensions.cs +++ b/src/Ombi.HealthChecks/HealthCheckExtensions.cs @@ -1,5 +1,8 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Ombi.HealthChecks.Checks; +using System; +using System.Collections.Generic; namespace Ombi.HealthChecks { @@ -12,8 +15,41 @@ namespace Ombi.HealthChecks builder.AddCheck("Lidarr", tags: new string[] { "DVR" }); builder.AddCheck("Sonarr", tags: new string[] { "DVR" }); builder.AddCheck("Radarr", tags: new string[] { "DVR" }); + builder.AddCheck("CouchPotato", tags: new string[] { "DVR" }); + builder.AddCheck("SickRage", tags: new string[] { "DVR" }); + builder.AddOmbiPingHealthCheck(options => + { + options.AddHost("www.google.co.uk", 5000, HealthStatus.Unhealthy); + options.AddHost("www.google.com", 3000, HealthStatus.Degraded); + }, "External Ping", tags: new string[] { "System" }); return builder; } + + /// + /// Add a health check for network ping. + /// + /// The . + /// The action to configure the ping parameters. + /// The health check name. Optional. If null the type name 'ping' will be used for the name. + /// + /// The that should be reported when the health check fails. Optional. If null then + /// the default status of will be reported. + /// + /// A list of tags that can be used to filter sets of health checks. Optional. + /// An optional System.TimeSpan representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddOmbiPingHealthCheck(this IHealthChecksBuilder builder, Action setup, string name = default, HealthStatus? failureStatus = default, IEnumerable tags = default, TimeSpan? timeout = default) + { + var options = new OmbiPingHealthCheckOptions(); + setup?.Invoke(options); + + return builder.Add(new HealthCheckRegistration( + name, + sp => new OmbiPingHealthCheck(options), + failureStatus, + tags, + timeout)); + } } } diff --git a/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj index 9b75bfed5..57c2ef059 100644 --- a/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj +++ b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj @@ -5,14 +5,18 @@ + + + + diff --git a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs index e0787326e..d7af8ffe4 100644 --- a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs +++ b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs @@ -10,6 +10,7 @@ public bool IgnoreCertificateErrors { get; set; } public bool DoNotSendNotificationsForAutoApprove { get; set; } public bool HideRequestsUsers { get; set; } + public bool DisableHealthChecks { get; set; } public string DefaultLanguageCode { get; set; } = "en"; } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index e56c517ca..8eb595a6c 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -16,6 +16,7 @@ export interface IOmbiSettings extends ISettings { doNotSendNotificationsForAutoApprove: boolean; hideRequestsUsers: boolean; defaultLanguageCode: string; + disableHealthChecks: boolean; } export interface IUpdateSettings extends ISettings { diff --git a/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.html b/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.html index 48a5d8883..a39250ff2 100644 --- a/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.html +++ b/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.html @@ -2,50 +2,57 @@
- Ombi Configuration - -
+ Ombi Configuration -
+ - - - +
+ + + -
- -
- -
-
-
+
+ +
+ + +
+
+
-
-
-
+
+
+
+
+
+
+
+ + Do not send Notifications if a User has the Auto Approve permission +
+
+ + Hide requests from other users + +
+
+ + Ignore any certificate errors + +
+
+ + Allow us to collect anonymous analytical data e.g. browser used + +
+
+ + Disable the health checks page /healthchecks-ui
-
-
-
- - Do not send Notifications if a User has the Auto Approve permission -
- - Hide requests from other users - -
- - Ignore any certificate errors - -
- - Allow us to collect anonymous analytical data e.g. browser used - -
+
-- @@ -62,4 +69,4 @@
-
+ \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.ts b/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.ts index 4aec1a57c..6439cd787 100644 --- a/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/ombi/ombi.component.ts @@ -29,6 +29,7 @@ export class OmbiComponent implements OnInit { doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove], hideRequestsUsers: [x.hideRequestsUsers], defaultLanguageCode: [x.defaultLanguageCode], + disableHealthChecks: [x.disableHealthChecks] }); }); this.langauges = languageData; diff --git a/src/Ombi/HealthCheck.css b/src/Ombi/HealthCheck.css index bcdfe4d06..fc303c8c9 100644 --- a/src/Ombi/HealthCheck.css +++ b/src/Ombi/HealthCheck.css @@ -10,4 +10,5 @@ #outer-container > aside > nav > a:nth-child(2) { display: none; -} \ No newline at end of file +} + diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 0b86d9000..2f7c6a346 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -79,10 +79,11 @@ namespace Ombi var hcBuilder = services.AddHealthChecks(); hcBuilder.AddOmbiHealthChecks(); services.ConfigureDatabases(hcBuilder); - services.AddHealthChecksUI(setupSettings: setup => - { - setup.AddHealthCheckEndpoint("Ombi", "http://localhost:3577/healthz"); - }); + // Need to wait until https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/410 is resolved + //services.AddHealthChecksUI(setupSettings: setup => + //{ + // setup.AddHealthCheckEndpoint("Ombi", "/health"); + //}); services.AddMemoryCache(); services.AddJwtAuthentication(Configuration); @@ -115,7 +116,7 @@ namespace Ombi } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider) + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IServiceProvider serviceProvider) { app.UseForwardedHeaders(new ForwardedHeadersOptions { @@ -212,24 +213,26 @@ namespace Ombi { endpoints.MapControllers(); endpoints.MapHub("/hubs/notification"); - endpoints.MapHealthChecks("/health"); - endpoints.MapHealthChecks("/healthz", new HealthCheckOptions + if (!settings.DisableHealthChecks) { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - endpoints.MapHealthChecksUI(opts => - { - opts.AddCustomStylesheet("HealthCheck.css"); - }); + endpoints.MapHealthChecks("/health", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + endpoints.MapHealthChecksUI(opts => + { + opts.AddCustomStylesheet("HealthCheck.css"); + }); + } }); app.UseSpa(spa => { -//#if DEBUG -// spa.Options.SourcePath = "ClientApp"; -// spa.UseProxyToSpaDevelopmentServer("http://localhost:3578"); -//#endif +#if DEBUG + spa.Options.SourcePath = "ClientApp"; + spa.UseProxyToSpaDevelopmentServer("http://localhost:3578"); +#endif }); } diff --git a/src/Ombi/healthchecksdb b/src/Ombi/healthchecksdb deleted file mode 100644 index 90c88cbb9adad8ec62732eb991a380463829d580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45056 zcmeI*&2Q6Y90zbaZsVjZ^>(NtBw!B_+lYYH&P!8QleR2%LqtfrG@Z%;MJ6%SBDKMG zplqiWH2w@OyY5feZRZ_!g3B~*(zMHV*kzlv!yYHjD^5Zgh%&xb;v{}v{5+rM_c$1< z&gQ2@-OP*U)h28TsZD1m zx1-f>DtlVJ<>;nyQMVn_(ly&Rwa}PY=2)(xWUC4(l-87O5_`^F#7MbBViR{1BWHax z6dJMk{?#xay_;hXB9r>57|tPk>6Ms!ran%tetZwTa+CV^;7o{*=JV{syrW)gY7?C% zCWgJ9RI{HHmFeJ-IYO5hC8pzBRq{%Ol*(07+A0=Fu3XxzRy|F}3VE&WCio!@^^*3`%RAW5^(p^!& z^=oy5#0_|Nx~wThg-*PRLWiM{t48MH56G?!x^mdA#P7i@n0_mV=>L!=+E|1m&% z6&)%(M#gel5KIoCwU(t0M%PH?eo)UJC|-YGahUAy1^MX7lkELmJ51J9z1gxfyZg!y z2S9f-4ZTB*cE8lrp&cL|JP3>oL{YV!lIiHzb&Vd4I>(8TECqY(R@P`#T85z;H=aH! z&>3=2oaLjZPqPo|?XLGHR{vdaxcmKt!vPGIn@TV(gv*ATLtD4Z-7$T4c{;ls%d=(a zV+v1TU(@Znc}KJEZEDsXy*_w|rkooy!RWbj?3FgAeY;6d$!fNCyQLX*ZRF$Z5bH?d zS%V(^&(o6e{kt3=jmO#h@phd#)3~nR@TRCe{Ap&W#@KM;;Rb88?9>5!36}fYEFaBe z*n`f&yH-=yFY7lt2PeDpO?uCFV_?|9H&36VSEqXv8=6L=-{@m18nEepn{HTUqg8i4 z(=2=7d)k|wG)xMCVDxH+W!nEZ^M+n+IcEEF&38XF$#=7RZttGYGZFe^g&!IEzybjX zKmY;|fB*y_009U<00Izzz-uaSisj^JLX>1ll9J*|GLcHxbVGM^wV7O$7Nt4bFIM=4 zp${w&fB*y_009U<00Izz00bZa0SLU-0>?Qy?Ejwto$LRP8R7A3-9a=S0uX=z1Rwwb z2tWV=5P$##An-;CywA=u`2}Xjac-?HEj7)$+T1a1XEmA5WR`Z?f4us6dH0&`p8s>g zH;nL`@B@9p0s#m>00Izz00bZa0SG_<0uX?}Q4@HV<8ti4Z9jVV%IMyLCKBLIhkIM? zcUUeS9==CI=lcI6MtF48I*0Z^00Izz00bZa0SG_<0uX=z1l|~d8XIERT$WzR4K9D_5aZPcZ~3p@U8Hba8tM>oSFZdZomQo2tWV=5P$##AOHafKmY;| zII;rq5YH^J%!Tl#<~a1XD{Zo-(%(N+Z7m>*tL?|4NbiE5lI1w>WmWbZ%`()cZ`QOd zrjqXkdFBJRR`(C)eX}h~vbb_;mS>K;*(z#7wXA_XrbRiO5i>Jx`bV^Jrs1Y{UWjfJ z!*od9`-MiWL0i~sswk$?;wjFnxTud*oLWvK#MFrZ&n&n*R(7x58`!NVCZy%m3D!&9 zXli={sZuf_FR!@g|G55tWCI!vg8&2|009U<00Izz00bZa0SLSh0r&U+Lc$Y9_(%9t z_=DaB@K@nydMCgqFQjr*0s#m>00Izz00bZa0SG_<0uX?}%N3a6!feQ0@$M?