fix(mesh): also scope the reconciler's observed membership to the own cluster
Task 3 review: scoping only the rows/enabled DB queries by ClusterId left the observed set (from Cluster.State.Members) filtered by the driver role alone. In the mixed-mesh window — DB scoping live via a cluster role, but the physical Akka mesh not yet split so central still sees foreign members via gossip — a foreign-cluster driver member stays in observed while its row is filtered out, hitting the RunningNodeHasNoRow branch, which logs at ERROR. That traded the Warning-level false positive for a worse Error-level one. Filter observed by the node's own cluster ROLE too. Extracted the projection into a static FilterOwnClusterDriverMembers(members, ownClusterRole) so the membership filter is unit-testable without seeding a live cluster (the actor harness joins as admin only, so observed is always empty). Sourced at registration from IClusterRoleInfo.ClusterRole alongside ClusterId. Null role (legacy, no cluster role) keeps every driver member — reconcile-all, unchanged. Also clarified the ownClusterId doc wording (non-null-and-non-empty). Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -61,13 +61,20 @@ public sealed record ClusterNodeAddress(string NodeId, string Host, int AkkaPort
|
||||
/// <b>Scoped to the admin node's own cluster (Phase 6).</b> Once the fleet splits into one
|
||||
/// mesh per cluster, an admin node no longer sees site members at all, and a fleet-wide sweep
|
||||
/// would report every other cluster's row as
|
||||
/// <see cref="AddressMismatchKind.EnabledRowNotInCluster"/> forever. The actor therefore loads
|
||||
/// only rows whose <c>ClusterId</c> matches the admin node's own
|
||||
/// (<see cref="ZB.MOM.WW.OtOpcUa.Commons.Interfaces.IClusterRoleInfo.ClusterId"/>) before
|
||||
/// calling <see cref="Reconcile"/>, so it never false-reports a cluster it cannot observe. A
|
||||
/// legacy admin node carrying no cluster role (null <c>ClusterId</c>) still sees every member
|
||||
/// via gossip and reconciles the full fleet unchanged. The pure function below is untouched by
|
||||
/// this — scoping happens entirely in the actor's DB queries.
|
||||
/// <see cref="AddressMismatchKind.EnabledRowNotInCluster"/> forever. The actor therefore scopes
|
||||
/// <b>both</b> inputs to this function to the admin node's own cluster: the <c>rows</c>/enabled
|
||||
/// set to rows whose <c>ClusterId</c> matches
|
||||
/// <see cref="ZB.MOM.WW.OtOpcUa.Commons.Interfaces.IClusterRoleInfo.ClusterId"/>, and the
|
||||
/// observed membership to members carrying its own
|
||||
/// <see cref="ZB.MOM.WW.OtOpcUa.Commons.Interfaces.IClusterRoleInfo.ClusterRole"/>
|
||||
/// (<c>FilterOwnClusterDriverMembers</c>). Scoping only the rows would swap the
|
||||
/// <see cref="AddressMismatchKind.EnabledRowNotInCluster"/> false positive for a worse
|
||||
/// Error-level <see cref="AddressMismatchKind.RunningNodeHasNoRow"/> during the mixed-mesh
|
||||
/// window, where the DB scope is live but central still sees foreign members via gossip. A
|
||||
/// legacy admin node carrying no cluster role (null <c>ClusterId</c>/<c>ClusterRole</c>) still
|
||||
/// sees every member via gossip and reconciles the full fleet unchanged. The pure function
|
||||
/// below is untouched — scoping happens entirely in the actor's DB queries and its
|
||||
/// membership filter.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class ClusterNodeAddressReconciler
|
||||
|
||||
Reference in New Issue
Block a user