diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AdminRoleLeaderHealthCheck.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AdminRoleLeaderHealthCheck.cs deleted file mode 100644 index db217672..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AdminRoleLeaderHealthCheck.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Extensions.Diagnostics.HealthChecks; -using ZB.MOM.WW.OtOpcUa.Commons.Interfaces; - -namespace ZB.MOM.WW.OtOpcUa.Host.Health; - -/// -/// Reports Healthy on the admin-role leader, Degraded on a non-leader admin member. Used by -/// the /health/active endpoint so external load balancers can route admin-singleton -/// traffic to the current leader (cookie sessions still work on either node — DataProtection -/// keys are shared). -/// -public sealed class AdminRoleLeaderHealthCheck : IHealthCheck -{ - private readonly IClusterRoleInfo _roleInfo; - - /// Initializes a new instance of the AdminRoleLeaderHealthCheck class. - /// The cluster role information provider. - public AdminRoleLeaderHealthCheck(IClusterRoleInfo roleInfo) - { - _roleInfo = roleInfo; - } - - /// Checks the health status of the admin role leader. - /// The health check context. - /// The cancellation token. - /// A task representing the health check operation. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) - { - if (!_roleInfo.HasRole("admin")) - return Task.FromResult(HealthCheckResult.Healthy("Node does not carry admin role")); - - var leader = _roleInfo.RoleLeader("admin"); - var isLeader = leader is not null && leader.Value.Equals(_roleInfo.LocalNode); - - return Task.FromResult(isLeader - ? HealthCheckResult.Healthy($"Admin leader ({_roleInfo.LocalNode})") - : HealthCheckResult.Degraded($"Admin member but not leader (leader={leader?.Value ?? ""})")); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AkkaClusterHealthCheck.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AkkaClusterHealthCheck.cs deleted file mode 100644 index 905ea3e8..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/AkkaClusterHealthCheck.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Akka.Actor; -using Akka.Cluster; -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace ZB.MOM.WW.OtOpcUa.Host.Health; - -public sealed class AkkaClusterHealthCheck : IHealthCheck -{ - private readonly ActorSystem _system; - - /// - /// Initializes a new instance of the AkkaClusterHealthCheck class. - /// - /// The Akka actor system to check cluster health for. - public AkkaClusterHealthCheck(ActorSystem system) - { - _system = system; - } - - /// - /// Checks the health of the Akka cluster asynchronously. - /// - /// The health check context. - /// Cancellation token. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) - { - var cluster = Akka.Cluster.Cluster.Get(_system); - var selfUp = cluster.State.Members.Any(m => - m.Address == cluster.SelfAddress && m.Status == MemberStatus.Up); - - return Task.FromResult(selfUp - ? HealthCheckResult.Healthy($"Self Up; {cluster.State.Members.Count} member(s)") - : HealthCheckResult.Degraded("Self not yet Up in cluster")); - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/DatabaseHealthCheck.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/DatabaseHealthCheck.cs deleted file mode 100644 index b10f5c74..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/DatabaseHealthCheck.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using ZB.MOM.WW.OtOpcUa.Configuration; - -namespace ZB.MOM.WW.OtOpcUa.Host.Health; - -public sealed class DatabaseHealthCheck : IHealthCheck -{ - private readonly IDbContextFactory _dbFactory; - - /// - /// Initializes a new instance of the class. - /// - /// The database context factory for the config database. - public DatabaseHealthCheck(IDbContextFactory dbFactory) - { - _dbFactory = dbFactory; - } - - /// - /// Checks the health of the configuration database. - /// - /// The health check context. - /// Cancellation token. - public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) - { - try - { - await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken); - await db.Deployments.AsNoTracking().Take(1).ToListAsync(cancellationToken); - return HealthCheckResult.Healthy("ConfigDb reachable"); - } - catch (Exception ex) - { - return HealthCheckResult.Unhealthy("ConfigDb unreachable", ex); - } - } -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs index af627c7a..5e29f6c7 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs @@ -1,25 +1,40 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; +using ZB.MOM.WW.Health; +using ZB.MOM.WW.Health.Akka; +using ZB.MOM.WW.Health.EntityFrameworkCore; +using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.Host.Health; public static class HealthEndpoints { /// - /// Registers the standard ASP.NET Core health-check infrastructure plus the OtOpcUa-specific - /// probes. Mirrors ScadaLink's three-tier pattern: ready = boot ok; active = - /// fully serving traffic; healthz = bare process liveness. + /// Registers the shared ZB.MOM.WW health probes. Tier semantics preserved: configdb + akka on + /// ready+active; admin-leader on active only. /// - /// The service collection to register health checks with. public static IServiceCollection AddOtOpcUaHealth(this IServiceCollection services) { services.AddHealthChecks() - .AddCheck("configdb", tags: new[] { "ready", "active" }) - .AddCheck("akka", tags: new[] { "ready", "active" }) - .AddCheck("admin-leader", tags: new[] { "active" }); + .AddTypeActivatedCheck>( + "configdb", + failureStatus: null, + tags: new[] { ZbHealthTags.Ready, ZbHealthTags.Active }, + args: new DatabaseHealthCheckOptions + { + ProbeQuery = static (db, ct) => db.Deployments.AsNoTracking().Take(1).ToListAsync(ct), + }) + .AddTypeActivatedCheck( + "akka", + failureStatus: null, + tags: new[] { ZbHealthTags.Ready, ZbHealthTags.Active }, + args: AkkaClusterStatusPolicy.OtOpcUaCompat) + .AddTypeActivatedCheck( + "admin-leader", + failureStatus: null, + tags: new[] { ZbHealthTags.Active }, + args: "admin"); return services; } @@ -27,21 +42,7 @@ public static class HealthEndpoints /// The endpoint route builder. public static IEndpointRouteBuilder MapOtOpcUaHealth(this IEndpointRouteBuilder app) { - // AllowAnonymous on all three — Traefik / k8s liveness probes / load-balancers - // hit these without credentials. Without it the AddOtOpcUaAuth fallback policy - // 401s every probe and Traefik marks every backend unhealthy. - app.MapHealthChecks("/health/ready", new HealthCheckOptions - { - Predicate = c => c.Tags.Contains("ready"), - }).AllowAnonymous(); - app.MapHealthChecks("/health/active", new HealthCheckOptions - { - Predicate = c => c.Tags.Contains("active"), - }).AllowAnonymous(); - app.MapHealthChecks("/healthz", new HealthCheckOptions - { - Predicate = _ => false, // process-liveness only — no probes run. - }).AllowAnonymous(); + app.MapZbHealth(); return app; } }