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 0f69625b..c3a48688 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs @@ -84,7 +84,11 @@ public static class HealthEndpoints "akka", failureStatus: null, tags: new[] { ZbHealthTags.Ready, ZbHealthTags.Active }, - args: AkkaClusterStatusPolicy.OtOpcUaCompat) + // Shared SPEC Default policy (lmxopcua#771). OtOpcUaCompat was a migration shim that kept the + // pre-adoption two-way behaviour (anything not Up = Degraded), which can never fail /health/ready + // — Degraded passes. Nothing relies on that: Down/Removed/Joining-failed now answer 503 on the + // ready tier, Leaving/Exiting stay Degraded (a draining node keeps serving). + args: AkkaClusterStatusPolicy.Default) // "Is this node in charge of its mesh?" — Healthy on the one node that owns the active // work for its Cluster, Unhealthy (503) on its partner, so Traefik pins the AdminUI to the // node actually hosting the cluster singletons. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs index 45ff262b..bca0ea88 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs @@ -1,3 +1,6 @@ +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit.Xunit2; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Options; @@ -92,3 +95,31 @@ public sealed class ActiveTierRegistrationTests ClusterPrimaryRegistration(hasAdmin: true).Tags.ShouldNotContain(ZbHealthTags.Ready); } } + +/// lmxopcua#771 — the akka check must use the shared SPEC Default policy so a node that is not a live +/// cluster member makes /health/ready fail (the retired OtOpcUaCompat policy could only say Degraded). +public sealed class AkkaReadyPolicyTests : TestKit +{ + public AkkaReadyPolicyTests() + : base(ConfigurationFactory.ParseString( + "akka.actor.provider = cluster\nakka.remote.dot-netty.tcp.port = 0\nakka.remote.dot-netty.tcp.hostname = 127.0.0.1")) + { + } + + [Fact] + public async Task Akka_check_is_Unhealthy_when_self_has_not_joined_a_cluster() + { + var services = new ServiceCollection(); + services.AddSingleton(Sys); + services.AddOtOpcUaHealth(hasAdmin: true); + var sp = services.BuildServiceProvider(); + var registration = sp.GetRequiredService>() + .Value.Registrations.Single(r => r.Name == "akka"); + registration.Tags.ShouldContain(ZbHealthTags.Ready); + + var result = await registration.Factory(sp).CheckHealthAsync( + new HealthCheckContext { Registration = registration }, CancellationToken.None); + + result.Status.ShouldBe(HealthStatus.Unhealthy); + } +}