feat(controlplane): RedundancyStateActor broadcast override + un-skip tests (F6)
Mirrors the publisher-injection pattern from FleetStatusBroadcaster and PeerOpcUaProbeActor: Props accepts an optional Action<object> override so tests can use a TestProbe sink instead of bootstrapping DistributedPubSub (unreliable single-node in TestKit). Un-skips the two RedundancyStateActor tests deferred under F6.
This commit is contained in:
@@ -24,15 +24,20 @@ public sealed class RedundancyStateActor : ReceiveActor, IWithTimers
|
||||
|
||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||
private readonly Akka.Cluster.Cluster _cluster;
|
||||
private readonly Action<object>? _broadcastOverride;
|
||||
private bool _dirty;
|
||||
|
||||
public ITimerScheduler Timers { get; set; } = null!;
|
||||
|
||||
public static Props Props() => Akka.Actor.Props.Create(() => new RedundancyStateActor());
|
||||
public static Props Props(Action<object>? broadcast = null) =>
|
||||
Akka.Actor.Props.Create(() => new RedundancyStateActor(broadcast));
|
||||
|
||||
public RedundancyStateActor()
|
||||
public RedundancyStateActor() : this(broadcast: null) { }
|
||||
|
||||
public RedundancyStateActor(Action<object>? broadcast)
|
||||
{
|
||||
_cluster = Akka.Cluster.Cluster.Get(Context.System);
|
||||
_broadcastOverride = broadcast;
|
||||
|
||||
Receive<ClusterEvent.IMemberEvent>(_ => MarkDirty());
|
||||
Receive<ClusterEvent.LeaderChanged>(_ => MarkDirty());
|
||||
@@ -68,7 +73,8 @@ public sealed class RedundancyStateActor : ReceiveActor, IWithTimers
|
||||
|
||||
var snapshot = BuildSnapshot();
|
||||
var msg = new RedundancyStateChanged(snapshot, CorrelationId.NewId());
|
||||
DistributedPubSub.Get(Context.System).Mediator.Tell(new Publish(Topic, msg));
|
||||
if (_broadcastOverride is not null) _broadcastOverride(msg);
|
||||
else DistributedPubSub.Get(Context.System).Mediator.Tell(new Publish(Topic, msg));
|
||||
_log.Debug("Published RedundancyStateChanged with {Count} nodes", snapshot.Count);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Akka.Actor;
|
||||
using Akka.Cluster.Tools.PublishSubscribe;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Redundancy;
|
||||
@@ -8,36 +7,32 @@ using ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies <see cref="RedundancyStateActor"/> publishes a <see cref="RedundancyStateChanged"/>
|
||||
/// snapshot in response to cluster events, and that the 250ms debounce coalesces bursts.
|
||||
/// The actor accepts an <c>Action<object></c> broadcast override so tests can use a
|
||||
/// TestProbe sink instead of bootstrapping DistributedPubSub (which is flaky single-node).
|
||||
/// </summary>
|
||||
public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase
|
||||
{
|
||||
[Fact(Skip = "Single-node DistributedPubSub bootstrap is flaky in TestKit; tracked as F6.")]
|
||||
public void Self_join_triggers_RedundancyStateChanged_on_pubsub_topic()
|
||||
[Fact]
|
||||
public void Self_join_triggers_RedundancyStateChanged_via_broadcast_override()
|
||||
{
|
||||
// Subscribe a probe to the redundancy-state topic.
|
||||
var probe = CreateTestProbe("redundancy-listener");
|
||||
var mediator = DistributedPubSub.Get(Sys).Mediator;
|
||||
mediator.Tell(new Subscribe(RedundancyStateActor.Topic, probe.Ref));
|
||||
probe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(3));
|
||||
|
||||
// Start the actor — its PreStart subscribes to cluster events, which immediately fires
|
||||
// a CurrentClusterState replay (InitialStateAsEvents). After the 250ms debounce window,
|
||||
// a RedundancyStateChanged should land on the topic.
|
||||
Sys.ActorOf(RedundancyStateActor.Props(), "redundancy-actor");
|
||||
Sys.ActorOf(RedundancyStateActor.Props(broadcast: msg => probe.Ref.Tell(msg)),
|
||||
"redundancy-actor");
|
||||
|
||||
var msg = probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3));
|
||||
msg.Nodes.ShouldNotBeNull();
|
||||
msg.CorrelationId.Value.ShouldNotBe(Guid.Empty);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Same root cause as the prior test; tracked as F6.")]
|
||||
[Fact]
|
||||
public void Multiple_back_to_back_events_debounce_to_single_publish()
|
||||
{
|
||||
var probe = CreateTestProbe("dedup-listener");
|
||||
var mediator = DistributedPubSub.Get(Sys).Mediator;
|
||||
mediator.Tell(new Subscribe(RedundancyStateActor.Topic, probe.Ref));
|
||||
probe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(3));
|
||||
|
||||
Sys.ActorOf(RedundancyStateActor.Props(), "redundancy-debounce");
|
||||
Sys.ActorOf(RedundancyStateActor.Props(broadcast: msg => probe.Ref.Tell(msg)),
|
||||
"redundancy-debounce");
|
||||
|
||||
// First publish should arrive within the debounce window.
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3));
|
||||
|
||||
Reference in New Issue
Block a user