Finished healthchecks

pull/3400/head
Jamie Rees 5 years ago
parent db2e3bd08c
commit c20c16b7a0

@ -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<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
using (var scope = CreateScope())
{
var settingsProvider = scope.ServiceProvider.GetRequiredService<ISettingsService<CouchPotatoSettings>>();
var api = scope.ServiceProvider.GetRequiredService<ICouchPotatoApi>();
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);
}
}
}
}
}

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

@ -0,0 +1,16 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Collections.Generic;
namespace Ombi.HealthChecks.Checks
{
public class OmbiPingHealthCheckOptions
{
internal Dictionary<string, (string Host, int TimeOut, HealthStatus status)> ConfiguredHosts { get; } = new Dictionary<string, (string, int, HealthStatus)>();
public OmbiPingHealthCheckOptions AddHost(string host, int timeout, HealthStatus status)
{
ConfiguredHosts.Add(host, (host, timeout, status));
return this;
}
}
}

@ -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<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
using (var scope = CreateScope())
{
var settingsProvider = scope.ServiceProvider.GetRequiredService<ISettingsService<SickRageSettings>>();
var api = scope.ServiceProvider.GetRequiredService<ISickRageApi>();
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);
}
}
}
}
}

@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Ombi.HealthChecks.Checks; using Ombi.HealthChecks.Checks;
using System;
using System.Collections.Generic;
namespace Ombi.HealthChecks namespace Ombi.HealthChecks
{ {
@ -12,8 +15,41 @@ namespace Ombi.HealthChecks
builder.AddCheck<LidarrHealthCheck>("Lidarr", tags: new string[] { "DVR" }); builder.AddCheck<LidarrHealthCheck>("Lidarr", tags: new string[] { "DVR" });
builder.AddCheck<SonarrHealthCheck>("Sonarr", tags: new string[] { "DVR" }); builder.AddCheck<SonarrHealthCheck>("Sonarr", tags: new string[] { "DVR" });
builder.AddCheck<RadarrHealthCheck>("Radarr", tags: new string[] { "DVR" }); builder.AddCheck<RadarrHealthCheck>("Radarr", tags: new string[] { "DVR" });
builder.AddCheck<CouchPotatoHealthCheck>("CouchPotato", tags: new string[] { "DVR" });
builder.AddCheck<SickrageHealthCheck>("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; return builder;
} }
/// <summary>
/// Add a health check for network ping.
/// </summary>
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="setup">The action to configure the ping parameters.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'ping' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <param name="timeout">An optional System.TimeSpan representing the timeout of the check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns></param>
public static IHealthChecksBuilder AddOmbiPingHealthCheck(this IHealthChecksBuilder builder, Action<OmbiPingHealthCheckOptions> setup, string name = default, HealthStatus? failureStatus = default, IEnumerable<string> 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));
}
} }
} }

@ -5,14 +5,18 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.Network" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="System.Collections" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" /> <ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" /> <ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" /> <ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
</ItemGroup> </ItemGroup>

@ -10,6 +10,7 @@
public bool IgnoreCertificateErrors { get; set; } public bool IgnoreCertificateErrors { get; set; }
public bool DoNotSendNotificationsForAutoApprove { get; set; } public bool DoNotSendNotificationsForAutoApprove { get; set; }
public bool HideRequestsUsers { get; set; } public bool HideRequestsUsers { get; set; }
public bool DisableHealthChecks { get; set; }
public string DefaultLanguageCode { get; set; } = "en"; public string DefaultLanguageCode { get; set; } = "en";
} }
} }

@ -16,6 +16,7 @@ export interface IOmbiSettings extends ISettings {
doNotSendNotificationsForAutoApprove: boolean; doNotSendNotificationsForAutoApprove: boolean;
hideRequestsUsers: boolean; hideRequestsUsers: boolean;
defaultLanguageCode: string; defaultLanguageCode: string;
disableHealthChecks: boolean;
} }
export interface IUpdateSettings extends ISettings { export interface IUpdateSettings extends ISettings {

@ -17,8 +17,7 @@
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">Api Key</label> <label for="ApiKey" class="control-label">Api Key</label>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control form-control-custom" id="ApiKey" name="ApiKey" formControlName="apiKey" <input type="text" class="form-control form-control-custom" id="ApiKey" name="ApiKey" formControlName="apiKey" readonly="readonly" #apiKey>
readonly="readonly" #apiKey>
<div class="input-group-addon"> <div class="input-group-addon">
<div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key" pTooltip="This will invalidate the old API key"></div> <div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key" pTooltip="This will invalidate the old API key"></div>
@ -30,22 +29,30 @@
</div> </div>
</div> </div>
<br /> <br />
<div> <div>
<mat-checkbox formControlName="doNotSendNotificationsForAutoApprove"> <mat-checkbox formControlName="doNotSendNotificationsForAutoApprove">
Do not send Notifications if a User has the Auto Approve permission</mat-checkbox> Do not send Notifications if a User has the Auto Approve permission</mat-checkbox>
</div><div> </div>
<div>
<mat-checkbox formControlName="hideRequestsUsers"> <mat-checkbox formControlName="hideRequestsUsers">
Hide requests from other users Hide requests from other users
</mat-checkbox> </mat-checkbox>
</div><div> </div>
<div>
<mat-checkbox formControlName="ignoreCertificateErrors" matTooltip="Enable if you are having connectivity problems over SSL"> <mat-checkbox formControlName="ignoreCertificateErrors" matTooltip="Enable if you are having connectivity problems over SSL">
Ignore any certificate errors Ignore any certificate errors
</mat-checkbox> </mat-checkbox>
</div><div> </div>
<div>
<mat-checkbox formControlName="collectAnalyticData" matTooltip="This will allow us to have a better understanding of the userbase so we know what we should be supporting"> <mat-checkbox formControlName="collectAnalyticData" matTooltip="This will allow us to have a better understanding of the userbase so we know what we should be supporting">
Allow us to collect anonymous analytical data e.g. browser used Allow us to collect anonymous analytical data e.g. browser used
</mat-checkbox> </mat-checkbox>
</div><div> </div>
<div>
<mat-checkbox formControlName="disableHealthChecks">
Disable the health checks page <a href="/healthchecks-ui" target="_blank">/healthchecks-ui</href></mat-checkbox>
</div>
<div>
<mat-form-field *ngIf="langauges"> <mat-form-field *ngIf="langauges">
<mat-select placeholder="Language" formControlName="defaultLanguageCode"> <mat-select placeholder="Language" formControlName="defaultLanguageCode">
<mat-option>--</mat-option> <mat-option>--</mat-option>

@ -29,6 +29,7 @@ export class OmbiComponent implements OnInit {
doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove], doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove],
hideRequestsUsers: [x.hideRequestsUsers], hideRequestsUsers: [x.hideRequestsUsers],
defaultLanguageCode: [x.defaultLanguageCode], defaultLanguageCode: [x.defaultLanguageCode],
disableHealthChecks: [x.disableHealthChecks]
}); });
}); });
this.langauges = <ILanguageRefine[]><any>languageData; this.langauges = <ILanguageRefine[]><any>languageData;

@ -11,3 +11,4 @@
#outer-container > aside > nav > a:nth-child(2) { #outer-container > aside > nav > a:nth-child(2) {
display: none; display: none;
} }

@ -79,10 +79,11 @@ namespace Ombi
var hcBuilder = services.AddHealthChecks(); var hcBuilder = services.AddHealthChecks();
hcBuilder.AddOmbiHealthChecks(); hcBuilder.AddOmbiHealthChecks();
services.ConfigureDatabases(hcBuilder); services.ConfigureDatabases(hcBuilder);
services.AddHealthChecksUI(setupSettings: setup => // Need to wait until https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/410 is resolved
{ //services.AddHealthChecksUI(setupSettings: setup =>
setup.AddHealthCheckEndpoint("Ombi", "http://localhost:3577/healthz"); //{
}); // setup.AddHealthCheckEndpoint("Ombi", "/health");
//});
services.AddMemoryCache(); services.AddMemoryCache();
services.AddJwtAuthentication(Configuration); 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. // 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 app.UseForwardedHeaders(new ForwardedHeadersOptions
{ {
@ -212,8 +213,9 @@ namespace Ombi
{ {
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/hubs/notification"); endpoints.MapHub<NotificationHub>("/hubs/notification");
endpoints.MapHealthChecks("/health"); if (!settings.DisableHealthChecks)
endpoints.MapHealthChecks("/healthz", new HealthCheckOptions {
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{ {
Predicate = _ => true, Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
@ -222,14 +224,15 @@ namespace Ombi
{ {
opts.AddCustomStylesheet("HealthCheck.css"); opts.AddCustomStylesheet("HealthCheck.css");
}); });
}
}); });
app.UseSpa(spa => app.UseSpa(spa =>
{ {
//#if DEBUG #if DEBUG
// spa.Options.SourcePath = "ClientApp"; spa.Options.SourcePath = "ClientApp";
// spa.UseProxyToSpaDevelopmentServer("http://localhost:3578"); spa.UseProxyToSpaDevelopmentServer("http://localhost:3578");
//#endif #endif
}); });
} }

Binary file not shown.
Loading…
Cancel
Save