diff --git a/Application/EdFi.Ods.AdminApi.UnitTests/Infrastructure/HealthCheckServiceExtensionsTests.cs b/Application/EdFi.Ods.AdminApi.UnitTests/Infrastructure/HealthCheckServiceExtensionsTests.cs new file mode 100644 index 000000000..3d6b56908 --- /dev/null +++ b/Application/EdFi.Ods.AdminApi.UnitTests/Infrastructure/HealthCheckServiceExtensionsTests.cs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// Licensed to the Ed-Fi Alliance under one or more agreements. +// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. +// See the LICENSE and NOTICES files in the project root for more information. + +using EdFi.Ods.AdminApi.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Shouldly; +using System.Collections.Generic; +using System.Linq; + +namespace EdFi.Ods.AdminApi.UnitTests.Infrastructure; + +[TestFixture] +public class HealthCheckServiceExtensionsTests +{ + [Test] + public void AddHealthCheck_ShouldRegisterBothAdminAndSecurityHealthChecks_WhenMultiTenancyDisabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddLogging(); // Required for health checks + var configuration = CreateTestConfiguration(multiTenancy: false); + + // Act + services.AddHealthCheck(configuration); + + // Assert - Check that health check services are registered + var healthCheckServiceDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(HealthCheckService)); + healthCheckServiceDescriptor.ShouldNotBeNull(); + } + + [Test] + public void AddHealthCheck_ShouldRegisterMultiTenantHealthChecks_WhenMultiTenancyEnabled() + { + // Arrange + var services = new ServiceCollection(); + services.AddLogging(); // Required for health checks + var configuration = CreateTestConfiguration(multiTenancy: true); + + // Act + services.AddHealthCheck(configuration); + + // Assert - Check that health check services are registered + var healthCheckServiceDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(HealthCheckService)); + healthCheckServiceDescriptor.ShouldNotBeNull(); + } + + private static IConfigurationRoot CreateTestConfiguration(bool multiTenancy) + { + var configData = new Dictionary + { + ["AppSettings:DatabaseEngine"] = "SqlServer", + ["AppSettings:MultiTenancy"] = multiTenancy.ToString(), + ["ConnectionStrings:EdFi_Admin"] = "Data Source=test;Initial Catalog=EdFi_Admin_Test;Integrated Security=True", + ["ConnectionStrings:EdFi_Security"] = "Data Source=test;Initial Catalog=EdFi_Security_Test;Integrated Security=True" + }; + + if (multiTenancy) + { + configData["Tenants:tenant1:ConnectionStrings:EdFi_Admin"] = "Data Source=test;Initial Catalog=EdFi_Admin_Tenant1;Integrated Security=True"; + configData["Tenants:tenant1:ConnectionStrings:EdFi_Security"] = "Data Source=test;Initial Catalog=EdFi_Security_Tenant1;Integrated Security=True"; + configData["Tenants:tenant2:ConnectionStrings:EdFi_Admin"] = "Data Source=test;Initial Catalog=EdFi_Admin_Tenant2;Integrated Security=True"; + configData["Tenants:tenant2:ConnectionStrings:EdFi_Security"] = "Data Source=test;Initial Catalog=EdFi_Security_Tenant2;Integrated Security=True"; + } + + return new ConfigurationBuilder() + .AddInMemoryCollection(configData) + .Build(); + } +} diff --git a/Application/EdFi.Ods.AdminApi.V1/Infrastructure/Helpers/HealthCheckServiceExtensions.cs b/Application/EdFi.Ods.AdminApi.V1/Infrastructure/Helpers/HealthCheckServiceExtensions.cs index 240f15273..ba81eefff 100644 --- a/Application/EdFi.Ods.AdminApi.V1/Infrastructure/Helpers/HealthCheckServiceExtensions.cs +++ b/Application/EdFi.Ods.AdminApi.V1/Infrastructure/Helpers/HealthCheckServiceExtensions.cs @@ -23,4 +23,26 @@ public static IServiceCollection AddHealthCheck(this IServiceCollection services return services; } + + public static IServiceCollection AddHealthCheck( + this IServiceCollection services, + string adminConnectionString, + string securityConnectionString, + bool isSqlServer) + { + var hcBuilder = services.AddHealthChecks(); + + if (isSqlServer) + { + hcBuilder.AddSqlServer(adminConnectionString, name: "EdFi_Admin", tags: ["Databases"]); + hcBuilder.AddSqlServer(securityConnectionString, name: "EdFi_Security", tags: ["Databases"]); + } + else + { + hcBuilder.AddNpgSql(adminConnectionString, name: "EdFi_Admin", tags: ["Databases"]); + hcBuilder.AddNpgSql(securityConnectionString, name: "EdFi_Security", tags: ["Databases"]); + } + + return services; + } } diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/HealthCheckServiceExtensions.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/HealthCheckServiceExtensions.cs index 09a63cfec..40d433983 100644 --- a/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/HealthCheckServiceExtensions.cs +++ b/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/HealthCheckServiceExtensions.cs @@ -4,7 +4,6 @@ // See the LICENSE and NOTICES files in the project root for more information. using EdFi.Ods.AdminApi.Common.Infrastructure.ErrorHandling; -using EdFi.Ods.AdminApi.Infrastructure.Extensions; using EdFi.Ods.AdminApi.Common.Infrastructure; using EdFi.Ods.AdminApi.Common.Infrastructure.Extensions; using EdFi.Ods.AdminApi.Common.Settings; @@ -18,10 +17,31 @@ public static IServiceCollection AddHealthCheck( IConfigurationRoot configuration ) { - Dictionary connectionStrings; var databaseEngine = configuration.Get("AppSettings:DatabaseEngine", "SqlServer"); var multiTenancyEnabled = configuration.Get("AppSettings:MultiTenancy", false); - var connectionStringName = "EdFi_Admin"; + + if (!string.IsNullOrEmpty(databaseEngine)) + { + var isSqlServer = DatabaseEngineEnum.Parse(databaseEngine).Equals(DatabaseEngineEnum.SqlServer); + var hcBuilder = services.AddHealthChecks(); + + // Add health checks for both EdFi_Admin and EdFi_Security databases + AddDatabaseHealthChecks(hcBuilder, configuration, "EdFi_Admin", multiTenancyEnabled, isSqlServer); + AddDatabaseHealthChecks(hcBuilder, configuration, "EdFi_Security", multiTenancyEnabled, isSqlServer); + } + + return services; + } + + private static void AddDatabaseHealthChecks( + IHealthChecksBuilder hcBuilder, + IConfigurationRoot configuration, + string connectionStringName, + bool multiTenancyEnabled, + bool isSqlServer + ) + { + Dictionary connectionStrings; if (multiTenancyEnabled) { @@ -42,24 +62,20 @@ IConfigurationRoot configuration }; } - if (!string.IsNullOrEmpty(databaseEngine)) + foreach (var connectionString in connectionStrings) { - var isSqlServer = DatabaseEngineEnum.Parse(databaseEngine).Equals(DatabaseEngineEnum.SqlServer); - var hcBuilder = services.AddHealthChecks(); + var healthCheckName = multiTenancyEnabled + ? $"{connectionString.Key}_{connectionStringName}" + : connectionStringName; - foreach (var connectionString in connectionStrings) + if (isSqlServer) + { + hcBuilder.AddSqlServer(connectionString.Value, name: healthCheckName, tags: ["Databases"]); + } + else { - if (isSqlServer) - { - hcBuilder.AddSqlServer(connectionString.Value, name: connectionString.Key); - } - else - { - hcBuilder.AddNpgSql(connectionString.Value, name: connectionString.Key); - } + hcBuilder.AddNpgSql(connectionString.Value, name: healthCheckName, tags: ["Databases"]); } } - - return services; } } diff --git a/Application/EdFi.Ods.AdminApi/Program.cs b/Application/EdFi.Ods.AdminApi/Program.cs index 365d6f56f..5d544d3b8 100644 --- a/Application/EdFi.Ods.AdminApi/Program.cs +++ b/Application/EdFi.Ods.AdminApi/Program.cs @@ -3,6 +3,7 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. +using System.Net; using EdFi.Ods.AdminApi.Common.Constants; using EdFi.Ods.AdminApi.Common.Infrastructure; using EdFi.Ods.AdminApi.Common.Infrastructure.MultiTenancy; @@ -10,6 +11,8 @@ using EdFi.Ods.AdminApi.Infrastructure; using log4net; using log4net.Config; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); @@ -61,7 +64,28 @@ app.MapFeatureEndpoints(); app.MapControllers(); -app.UseHealthChecks("/health"); +app.UseHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + + // 200 OK if all are healthy, 503 Service Unavailable if any are unhealthy + context.Response.StatusCode = report.Status == HealthStatus.Unhealthy ? (int)HttpStatusCode.ServiceUnavailable : (int)HttpStatusCode.OK; + + var response = new + { + Status = report.Status.ToString(), + Results = report.Entries.GroupBy(x => x.Value.Tags.FirstOrDefault()).Select(x => new + { + Name = x.Key, + Status = x.Min(y => y.Value.Status).ToString() + }) + }; + + await context.Response.WriteAsJsonAsync(response); + } +}); if (app.Configuration.GetValue("SwaggerSettings:EnableSwagger")) {