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;
}
}