test(mesh): step 7 — replace the boundary test with MeshGrpcBoundaryTests and sweep the comments
MeshGrpcBoundaryTests replaces the MeshClusterClientBoundaryTests deleted in step 1. Three
self-seeded single-member clusters sharing the ActorSystem name, joined ONLY by gRPC, driving
the REAL CentralCommunicationActor -> MeshGrpcCommandDialer -> Kestrel h2c ->
MeshCommandAuthInterceptor -> NodeCommandGrpcService -> NodeCommunicationActor -> EventStream:
1. No_cross_mesh_Akka_association_forms_between_central_and_either_site — the program's own
point, kept as a test rather than a live-gate note: each cluster stays single-member and
none ever holds another's address as a member.
2. One_dispatch_through_central_reaches_a_node_in_every_cluster_mesh — the fan, with each
site holding its OWN per-cluster PSK, so a dial that selected the wrong cluster's key
would be refused at the far side.
3. Falsifiability_control_a_dial_carrying_the_wrong_cluster_key_delivers_nothing — without
it, (2) cannot distinguish "the gRPC boundary carried it" from "something else in the
process did". The same node then accepts the right key, so the silence is the auth gate
and not a dead endpoint.
All three run fully offline — no Docker fixture — and pass.
Comment sweep across every remaining clusterclient|receptionist hit in src/ and tests/:
the program label becomes "mesh gRPC command plane", and every comment that described the
deleted transport as live now describes what actually runs (Dps = pair-local default,
Grpc = cross-mesh). mesh_transport.proto's header/service comments are reworded; no message,
field or enum is touched — the contract is locked. MeshCommActorPathTests is reshaped rather
than retired: the two paths still exist and still matter, but what a mismatch costs is now a
misleading Unavailable from the gRPC ingress rather than a silently dropped receptionist send.
The only surviving mentions of the old names are the ones that must survive: the fail-closed
rules that name "ClusterClient" as a retired value refused at boot, their tests, and this
test class's own account of what it replaces.
This commit is contained in:
@@ -12,14 +12,14 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
|
||||
|
||||
/// <summary>
|
||||
/// Posts this node's <see cref="ApplyAck"/> to central's <c>CentralCommandService</c> as a unary
|
||||
/// gRPC call (ClusterClient→gRPC migration Task 1B.2), the <c>MeshTransport:Mode = Grpc</c>
|
||||
/// replacement for the ClusterClient <c>Send</c>.
|
||||
/// gRPC call (mesh gRPC command plane, Task 1B.2), the <c>MeshTransport:Mode = Grpc</c>
|
||||
/// cross-mesh ack transport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>One ordered pass, stopping at the first success.</b>
|
||||
/// <see cref="MeshTransportOptions.CentralGrpcEndpoints"/> is an availability list, not a
|
||||
/// fan-out list — the semantic of the <c>ClusterClient.Send</c> being replaced (<c>Send</c>,
|
||||
/// fan-out list — the single-target semantic this leg owes (one ack per node per dispatch,
|
||||
/// deliberately not <c>SendToAll</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -35,7 +35,7 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
|
||||
/// depth and a bound on wasted work, not a correctness dependency.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>No retry beyond that single pass, and no queue.</b> The receptionist buffered nothing
|
||||
/// <b>No retry beyond that single pass, and no queue.</b> The transport buffers nothing
|
||||
/// and retried nothing, and this is a like-for-like replacement, not an upgrade to delivery
|
||||
/// guarantees. The deploy path's own apply deadline stays the only recovery mechanism; a
|
||||
/// retry loop here would instead pile acks up behind an unreachable central and deliver a
|
||||
@@ -43,7 +43,7 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>All endpoints failing is a DROP — but a loud one.</b> That is the same outcome the
|
||||
/// ClusterClient path had when it could not reach central; what changes is that the operator
|
||||
/// node has when it cannot reach central; what changes is that the operator
|
||||
/// now gets the node, the deployment, the correlation id and the status each endpoint
|
||||
/// answered with, which is what distinguishes "central is down" from "central rejected this
|
||||
/// node's key".
|
||||
|
||||
@@ -4,15 +4,15 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
|
||||
|
||||
/// <summary>
|
||||
/// Posts this node's deployment acknowledgment to central over the <c>mesh_transport.v1</c>
|
||||
/// command plane (ClusterClient→gRPC migration Phase 1B). The seam
|
||||
/// command plane (mesh gRPC command plane, Phase 1B). The seam
|
||||
/// <see cref="NodeCommunicationActor"/> uses under <c>MeshTransport:Mode = Grpc</c>, in place of
|
||||
/// the <c>ClusterClient.Send</c> it uses under <c>ClusterClient</c>.
|
||||
/// the node-local DistributedPubSub publish it uses under <c>Dps</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>One ack per node per dispatch.</b> Whatever the implementation does about redundancy, it
|
||||
/// posts to <i>one</i> central endpoint at a time rather than fanning out — the semantic of the
|
||||
/// <c>ClusterClient.Send</c> this replaces (<c>Send</c>, deliberately not <c>SendToAll</c>).
|
||||
/// one-ack-per-node-per-dispatch semantic the deploy coordinator counts on.
|
||||
/// This is <b>defence in depth, not a correctness dependency</b>: the coordinator counts acks
|
||||
/// in a <c>NodeId</c>-keyed dictionary behind an expected-set membership guard
|
||||
/// (<c>ConfigPublishCoordinator.HandleAck</c>), so a duplicate from one node overwrites its own
|
||||
|
||||
@@ -374,9 +374,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
/// <param name="dbFactory">Database context factory for configuration database access.</param>
|
||||
/// <param name="localNode">The local cluster node identifier.</param>
|
||||
/// <param name="ackRouter">Where ApplyAcks are sent. A direct coordinator handle in tests; under
|
||||
/// <c>MeshTransport:Mode = ClusterClient</c> <b>and</b> <c>= Grpc</c> the node's
|
||||
/// <c>NodeCommunicationActor</c>, which relays across the boundary over whichever transport it was
|
||||
/// built with. Null publishes on the <c>deployment-acks</c> DPS topic.</param>
|
||||
/// <c>MeshTransport:Mode = Grpc</c> the node's <c>NodeCommunicationActor</c>, which relays the ack
|
||||
/// across the mesh boundary. Null publishes on the <c>deployment-acks</c> DPS topic.</param>
|
||||
/// <param name="driverFactory">Optional driver factory; defaults to null factory if not provided.</param>
|
||||
/// <param name="localRoles">Optional set of roles assigned to the local node.</param>
|
||||
/// <param name="dependencyMux">Optional actor reference for dependency multiplexing.</param>
|
||||
@@ -459,9 +458,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
/// <param name="dbFactory">Database context factory for configuration database access.</param>
|
||||
/// <param name="localNode">The local cluster node identifier.</param>
|
||||
/// <param name="ackRouter">Where ApplyAcks are sent. A direct coordinator handle in tests; under
|
||||
/// <c>MeshTransport:Mode = ClusterClient</c> <b>and</b> <c>= Grpc</c> the node's
|
||||
/// <c>NodeCommunicationActor</c>, which relays across the boundary over whichever transport it was
|
||||
/// built with. Null publishes on the <c>deployment-acks</c> DPS topic.</param>
|
||||
/// <c>MeshTransport:Mode = Grpc</c> the node's <c>NodeCommunicationActor</c>, which relays the ack
|
||||
/// across the mesh boundary. Null publishes on the <c>deployment-acks</c> DPS topic.</param>
|
||||
/// <param name="driverFactory">Optional driver factory; defaults to null factory if not provided.</param>
|
||||
/// <param name="localRoles">Optional set of roles assigned to the local node.</param>
|
||||
/// <param name="dependencyMux">Optional actor reference for dependency multiplexing.</param>
|
||||
@@ -584,11 +582,11 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
_mediator.Tell(
|
||||
new Subscribe(ZB.MOM.WW.OtOpcUa.Runtime.OpcUa.OpcUaPublishActor.RedundancyStateTopic, Self));
|
||||
|
||||
// Per-cluster mesh Phase 2 dark switch. Under MeshTransport:Mode = ClusterClient these same
|
||||
// commands arrive from NodeCommunicationActor on the node-local EventStream instead of the
|
||||
// mesh-wide DPS topic. Subscribing to BOTH unconditionally is deliberate: exactly one of the
|
||||
// two ever publishes, so the transport flag lives entirely on the publishing side and the
|
||||
// switch can be flipped (or reverted) without touching any subscriber.
|
||||
// Per-cluster mesh Phase 2. Under MeshTransport:Mode = Grpc these same commands arrive from
|
||||
// NodeCommunicationActor on the node-local EventStream instead of the mesh-wide DPS topic.
|
||||
// Subscribing to BOTH unconditionally is deliberate: exactly one of the two ever publishes, so
|
||||
// the transport choice lives entirely on the publishing side and the mode can be flipped (or
|
||||
// reverted) without touching any subscriber.
|
||||
Context.System.EventStream.Subscribe(Self, typeof(DispatchDeployment));
|
||||
Context.System.EventStream.Subscribe(Self, typeof(RestartDriver));
|
||||
Context.System.EventStream.Subscribe(Self, typeof(ReconnectDriver));
|
||||
@@ -2492,11 +2490,11 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
var ack = new ApplyAck(deploymentId, _localNode, outcome, failureReason, correlation);
|
||||
if (_ackRouter is not null)
|
||||
{
|
||||
// Either a direct coordinator handle (tests) or, under MeshTransport:Mode = ClusterClient
|
||||
// and = Grpc alike, the NodeCommunicationActor that relays this across the boundary (it
|
||||
// owns the choice of transport, not this actor). Renamed from _coordinatorOverride in
|
||||
// per-cluster mesh Phase 2: under those two modes this ref is emphatically NOT a
|
||||
// coordinator, and the old name would send the next reader looking for one.
|
||||
// Either a direct coordinator handle (tests) or, under MeshTransport:Mode = Grpc, the
|
||||
// NodeCommunicationActor that relays this across the mesh boundary (it owns the transport,
|
||||
// not this actor). Renamed from _coordinatorOverride in per-cluster mesh Phase 2: under
|
||||
// Grpc this ref is emphatically NOT a coordinator, and the old name would send the next
|
||||
// reader looking for one.
|
||||
_ackRouter.Tell(ack);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -627,7 +627,7 @@ public sealed class ScriptedAlarmHostActor : ReceiveActor
|
||||
|
||||
// Per-cluster mesh Phase 2 dark switch. An AdminUI-originated ack/shelve arrives from
|
||||
// NodeCommunicationActor on the node-local EventStream under
|
||||
// MeshTransport:Mode = ClusterClient; the DPS subscribe above still carries the node-LOCAL
|
||||
// MeshTransport:Mode = Grpc; the DPS subscribe above still carries the node-LOCAL
|
||||
// publisher (OtOpcUaServerHostedService, the OPC UA Part 9 method calls), which never
|
||||
// crosses the boundary and stays on DPS in both modes. Subscribing to both is safe: exactly
|
||||
// one publisher feeds each path.
|
||||
|
||||
Reference in New Issue
Block a user