diff --git a/src/Ombi.HealthChecks/HealthCheckExtensions.cs b/src/Ombi.HealthChecks/HealthCheckExtensions.cs new file mode 100644 index 000000000..89a350cfe --- /dev/null +++ b/src/Ombi.HealthChecks/HealthCheckExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Ombi.HealthChecks +{ + public static class HealthCheckExtensions + { + + public static IHealthChecksBuilder AddOmbiHealthChecks(this IHealthChecksBuilder builder) + { + builder.AddCheck("Plex", tags: new string[] { "MediaServer" }); + + return builder; + } + } +} diff --git a/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj new file mode 100644 index 000000000..70b4dd959 --- /dev/null +++ b/src/Ombi.HealthChecks/Ombi.HealthChecks.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.1 + + + + + + + + + + + diff --git a/src/Ombi.HealthChecks/PlexHealthCheck.cs b/src/Ombi.HealthChecks/PlexHealthCheck.cs new file mode 100644 index 000000000..7015d9965 --- /dev/null +++ b/src/Ombi.HealthChecks/PlexHealthCheck.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models.Status; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.HealthChecks +{ + public class PlexHealthCheck : IHealthCheck + { + private readonly IPlexApi _plexApi; + private readonly ISettingsService _settings; + public PlexHealthCheck(IPlexApi plexApi, ISettingsService settings) + { + _plexApi = plexApi; + _settings = settings; + } + public async Task CheckHealthAsync( + HealthCheckContext context, + CancellationToken cancellationToken = default(CancellationToken)) + { + var settings = await _settings.GetSettingsAsync(); + if (settings == null) + { + return HealthCheckResult.Healthy("Plex is not confiured."); + } + + var taskResult = new List>(); + foreach (var server in settings.Servers) + { + taskResult.Add(_plexApi.GetStatus(server.PlexAuthToken, server.FullUri)); + } + + try + { + var result = await Task.WhenAll(taskResult.ToArray()); + return HealthCheckResult.Healthy(); + } + catch (Exception e) + { + return HealthCheckResult.Unhealthy("Could not communicate with Plex", e); + } + } + } +} diff --git a/src/Ombi.sln b/src/Ombi.sln index 78d3898ac..9602d5fd8 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -110,7 +110,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.GroupMe", "Ombi.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.MusicBrainz", "Ombi.Api.MusicBrainz\Ombi.Api.MusicBrainz.csproj", "{C5C1769B-4197-4410-A160-0EEF39EDDC98}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Twilio", "Ombi.Api.Twilio\Ombi.Api.Twilio.csproj", "{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Twilio", "Ombi.Api.Twilio\Ombi.Api.Twilio.csproj", "{34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.HealthChecks", "Ombi.HealthChecks\Ombi.HealthChecks.csproj", "{59D19538-0496-44EE-936E-EBBC22CF7B27}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -298,6 +300,10 @@ Global {34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3}.Release|Any CPU.Build.0 = Release|Any CPU + {59D19538-0496-44EE-936E-EBBC22CF7B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59D19538-0496-44EE-936E-EBBC22CF7B27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59D19538-0496-44EE-936E-EBBC22CF7B27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59D19538-0496-44EE-936E-EBBC22CF7B27}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -341,6 +347,7 @@ Global {9266403C-B04D-4C0F-AC39-82F12C781949} = {9293CA11-360A-4C20-A674-B9E794431BF5} {C5C1769B-4197-4410-A160-0EEF39EDDC98} = {9293CA11-360A-4C20-A674-B9E794431BF5} {34E5DD1A-6A90-448B-9E71-64D1ACD6C1A3} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {59D19538-0496-44EE-936E-EBBC22CF7B27} = {410F36CF-9C60-428A-B191-6FD90610991A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869} diff --git a/src/Ombi/Extensions/DatabaseExtensions.cs b/src/Ombi/Extensions/DatabaseExtensions.cs index 2c6d13d06..21680f8b9 100644 --- a/src/Ombi/Extensions/DatabaseExtensions.cs +++ b/src/Ombi/Extensions/DatabaseExtensions.cs @@ -2,6 +2,7 @@ using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Newtonsoft.Json; using Ombi.Helpers; using Ombi.Store.Context; @@ -17,7 +18,7 @@ namespace Ombi.Extensions public const string SqliteDatabase = "Sqlite"; public const string MySqlDatabase = "MySQL"; - public static void ConfigureDatabases(this IServiceCollection services) + public static void ConfigureDatabases(this IServiceCollection services, IHealthChecksBuilder hcBuilder) { var configuration = GetDatabaseConfiguration(); @@ -26,9 +27,11 @@ namespace Ombi.Extensions { case var type when type.Equals(SqliteDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureSqlite(x, configuration.OmbiDatabase)); + AddSqliteHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase); break; case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureMySql(x, configuration.OmbiDatabase)); + AddMySqlHealthCheck(hcBuilder, "Ombi Database", configuration.OmbiDatabase); break; } @@ -36,9 +39,11 @@ namespace Ombi.Extensions { case var type when type.Equals(SqliteDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureSqlite(x, configuration.ExternalDatabase)); + AddSqliteHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase); break; case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureMySql(x, configuration.ExternalDatabase)); + AddMySqlHealthCheck(hcBuilder, "External Database", configuration.ExternalDatabase); break; } @@ -46,9 +51,11 @@ namespace Ombi.Extensions { case var type when type.Equals(SqliteDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureSqlite(x, configuration.SettingsDatabase)); + AddSqliteHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase); break; case var type when type.Equals(MySqlDatabase, StringComparison.InvariantCultureIgnoreCase): services.AddDbContext(x => ConfigureMySql(x, configuration.SettingsDatabase)); + AddMySqlHealthCheck(hcBuilder, "Settings Database", configuration.SettingsDatabase); break; } } @@ -62,7 +69,7 @@ namespace Ombi.Extensions } var databaseFileLocation = Path.Combine(i.StoragePath, "database.json"); - + var configuration = new DatabaseConfiguration(i.StoragePath); if (File.Exists(databaseFileLocation)) { @@ -78,11 +85,36 @@ namespace Ombi.Extensions return configuration; } + + private static void AddSqliteHealthCheck(IHealthChecksBuilder builder, string dbName, PerDatabaseConfiguration config) + { + if (builder != null) + { + builder.AddSqlite( + sqliteConnectionString: config.ConnectionString, + name: dbName, + failureStatus: HealthStatus.Unhealthy, + tags: new string[] { "db" }); + } + } + + private static void AddMySqlHealthCheck(IHealthChecksBuilder builder, string dbName, PerDatabaseConfiguration config) + { + if (builder != null) + { + builder.AddMySql( + connectionString: config.ConnectionString, + name: dbName, + failureStatus: HealthStatus.Unhealthy, + tags: new string[] { "db" } + ); + } + } + public static void ConfigureSqlite(DbContextOptionsBuilder options, PerDatabaseConfiguration config) { SQLitePCL.Batteries.Init(); SQLitePCL.raw.sqlite3_config(raw.SQLITE_CONFIG_MULTITHREAD); - options.UseSqlite(config.ConnectionString); } diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 125325b0b..4f80b91c9 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -56,6 +56,10 @@ + + + + @@ -85,6 +89,7 @@ + diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index ac9c5bea0..6ddce97ae 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -59,7 +59,7 @@ namespace Ombi //CheckAndMigrate(); var services = new ServiceCollection(); - services.ConfigureDatabases(); + services.ConfigureDatabases(null); using (var provider = services.BuildServiceProvider()) { var settingsDb = provider.GetRequiredService(); diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 44d37509d..57f72be0e 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -28,6 +28,9 @@ using Microsoft.AspNetCore.StaticFiles.Infrastructure; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using ILogger = Serilog.ILogger; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Ombi.HealthChecks; namespace Ombi { @@ -73,8 +76,13 @@ namespace Ombi options.User.AllowedUserNameCharacters = string.Empty; }); - services.ConfigureDatabases(); - services.AddHealthChecks(); + var hcBuilder = services.AddHealthChecks(); + hcBuilder.AddOmbiHealthChecks(); + services.ConfigureDatabases(hcBuilder); + services.AddHealthChecksUI(setupSettings: setup => + { + setup.AddHealthCheckEndpoint("Ombi", "http://localhost:3577/healthz"); + }); services.AddMemoryCache(); services.AddJwtAuthentication(Configuration); @@ -205,17 +213,20 @@ namespace Ombi endpoints.MapControllers(); endpoints.MapHub("/hubs/notification"); endpoints.MapHealthChecks("/health"); + endpoints.MapHealthChecks("/healthz", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + endpoints.MapHealthChecksUI(); }); app.UseSpa(spa => { -#if DEBUG - //if (env.IsDevelopment()) - //{ - 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 new file mode 100644 index 000000000..8b2c7dd71 Binary files /dev/null and b/src/Ombi/healthchecksdb differ