5d4853a1f0
OldestNodeActiveHealthCheck existed because the shared ActiveNodeHealthCheck
selected by cluster leadership (review 01 [High]): leadership is address-ordered
and diverges from singleton placement after a restart, and during a partition
both sides compute themselves leader so Traefik served both. Health 0.3.0 makes
the shared check age-based — it is now this host's own rule, promoted — so the
private copy is deleted and central registers the shared type.
ActiveNodeEvaluator, the "THE single definition of active node" this repo already
maintained, now delegates to the shared ClusterActiveNode rather than
re-implementing it. That keeps the delivery gate, the heartbeat IsActive stamp,
the inbound-API gate and the /health/active tier on one rule, and it means the
rule is shared with OtOpcUa, which had independently written a third copy.
Communication takes a ZB.MOM.WW.Health.Akka reference for it; the layering
trade-off is recorded at the PackageReference.
SitePairActiveNodeHealthCheck is deliberately KEPT. It is not a duplicate of the
rule — it is a thin adapter over the site's own IClusterNodeProvider, which
scopes to site-{SiteId} and is itself now backed by ClusterActiveNode. Registering
the shared check directly would have to re-derive the site role and would lose the
property that the tier and singleton placement come from the same provider.
Central stays unscoped: every central member competes for one active slot.
No behaviour change on any node — same rule, one implementation instead of two.
Verified: Host.Tests 439/439, Communication ActiveNode 2/2.
53 lines
2.1 KiB
C#
53 lines
2.1 KiB
C#
using Akka.Cluster;
|
|
using ZB.MOM.WW.ScadaBridge.Host.Actors;
|
|
using ZB.MOM.WW.ScadaBridge.InboundAPI;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Host.Health;
|
|
|
|
/// <summary>
|
|
/// Production implementation of
|
|
/// <see cref="IActiveNodeGate"/> backed by the running Akka.NET cluster.
|
|
///
|
|
/// The inbound API is "Central cluster only (active node)" — a standby central
|
|
/// node must not execute method scripts or <c>Route.To()</c> calls. Active =
|
|
/// the oldest Up member (the singleton host), evaluated by
|
|
/// <see cref="ClusterActivityEvaluator.SelfIsOldest(Cluster, string?)"/> — NOT
|
|
/// the cluster leader, whose address-ordered definition diverges from singleton
|
|
/// placement after a restart (review 01 [High]). This is the same evaluator
|
|
/// backing the shared <c>ActiveNodeHealthCheck</c> registered on the active tier
|
|
/// — both route to <c>ClusterActiveNode</c> — so
|
|
/// <see cref="InboundApiEndpointFilter"/> can return HTTP 503 on a standby.
|
|
///
|
|
/// Registered only in the Central-role branch of <c>Program.cs</c>. The gate
|
|
/// is resolved per request from <c>HttpContext.RequestServices</c>; while the
|
|
/// <c>AkkaHostedService</c> is still warming up (<c>ActorSystem == null</c>)
|
|
/// or the node has not yet reached <see cref="MemberStatus.Up"/>, this
|
|
/// implementation reports <c>IsActiveNode == false</c> — the safe-by-default
|
|
/// answer matching the standby case.
|
|
/// </summary>
|
|
public sealed class ActiveNodeGate : IActiveNodeGate
|
|
{
|
|
private readonly AkkaHostedService _akkaService;
|
|
|
|
/// <summary>Initializes a new <see cref="ActiveNodeGate"/> bound to the given Akka hosted service.</summary>
|
|
/// <param name="akkaService">The Akka hosted service exposing the cluster's <see cref="Akka.Actor.ActorSystem"/>.</param>
|
|
public ActiveNodeGate(AkkaHostedService akkaService)
|
|
{
|
|
_akkaService = akkaService;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool IsActiveNode
|
|
{
|
|
get
|
|
{
|
|
var system = _akkaService.ActorSystem;
|
|
if (system == null)
|
|
return false;
|
|
|
|
var cluster = Cluster.Get(system);
|
|
return ClusterActivityEvaluator.SelfIsOldest(cluster);
|
|
}
|
|
}
|
|
}
|