fix(redundancy): key redundancy-state snapshot by canonical host:port NodeId (was host-only — broke ServiceLevel + scripted-alarm emit gate)
This commit is contained in:
@@ -104,7 +104,7 @@ public sealed class RedundancyStateActor : ReceiveActor, IWithTimers
|
||||
: CommonsRedundancyRole.Detached;
|
||||
|
||||
list.Add(new NodeRedundancyState(
|
||||
NodeId.Parse(host),
|
||||
ToNodeId(member.Address),
|
||||
role,
|
||||
IsClusterLeader: clusterLeader == member.Address,
|
||||
IsRoleLeaderForDriver: driverLeader == member.Address,
|
||||
@@ -113,6 +113,16 @@ public sealed class RedundancyStateActor : ReceiveActor, IWithTimers
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the canonical cluster node id (<c>host:port</c>) for an Akka member address — the SAME
|
||||
/// format <c>ClusterRoleInfo.LocalNode</c>/<c>ToNodeId</c> use, so redundancy-state consumers
|
||||
/// (OPC UA ServiceLevel, scripted-alarm emit gate, historian gate) can match their local node.
|
||||
/// </summary>
|
||||
/// <param name="address">The cluster member's address.</param>
|
||||
/// <returns>The canonical <c>host:port</c> node id.</returns>
|
||||
public static NodeId ToNodeId(Akka.Actor.Address address) =>
|
||||
NodeId.Parse($"{address.Host ?? string.Empty}:{address.Port ?? 0}");
|
||||
|
||||
public sealed class RecomputeNow
|
||||
{
|
||||
public static readonly RecomputeNow Instance = new();
|
||||
|
||||
@@ -42,4 +42,30 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase
|
||||
// After debounce settles, no more events are fired by a quiescent cluster.
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression guard: the snapshot node id MUST be the canonical <c>host:port</c> form (matching
|
||||
/// ClusterRoleInfo.LocalNode/ToNodeId), or every consumer's
|
||||
/// <c>n.NodeId == _localNode.Value</c> match fails and no node ever learns its role.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ToNodeId_uses_canonical_host_and_port()
|
||||
{
|
||||
var nodeId = RedundancyStateActor.ToNodeId(
|
||||
new Address("akka.tcp", "otopcua", "central-2", 4053));
|
||||
|
||||
nodeId.Value.ShouldBe("central-2:4053");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Documents the host-less/port-less fallback (<c>:0</c>). Such members are skipped by
|
||||
/// BuildSnapshot's guard, but the helper must still format deterministically.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ToNodeId_handles_missing_port()
|
||||
{
|
||||
var nodeId = RedundancyStateActor.ToNodeId(new Address("akka.tcp", "otopcua"));
|
||||
|
||||
nodeId.Value.ShouldBe(":0");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user