review(ControlPlane): fix premature deploy-seal from unexpected-node ack
Review at HEAD 7286d320. ControlPlane-001 (Medium): ConfigPublishCoordinator.HandleAck
now discards acks from nodes not in _expectedAcks (prevented premature SealDeployment) +
regression test. -002 (flipped-node log count), -003 (redundant mapper arms) tidied.
This commit is contained in:
@@ -29,16 +29,10 @@ public static class AuditOutcomeMapper
|
||||
public static AuditOutcome FromAction(string action) => action switch
|
||||
{
|
||||
"OpcUaAccessDenied" or "CrossClusterNamespaceAttempt" => AuditOutcome.Denied,
|
||||
"DraftCreated"
|
||||
or "DraftEdited"
|
||||
or "Published"
|
||||
or "RolledBack"
|
||||
or "NodeApplied"
|
||||
or "ClusterCreated"
|
||||
or "NodeAdded"
|
||||
or "CredentialAdded"
|
||||
or "CredentialDisabled"
|
||||
or "ExternalIdReleased" => AuditOutcome.Success,
|
||||
// All other known config-write verbs (DraftCreated, DraftEdited, Published, RolledBack,
|
||||
// NodeApplied, ClusterCreated, NodeAdded, CredentialAdded, CredentialDisabled,
|
||||
// ExternalIdReleased) and any future verbs default to Success — config writes are the
|
||||
// overwhelming majority and the only non-success cases are the two Denied entries above.
|
||||
_ => AuditOutcome.Success,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,6 +160,16 @@ public sealed class ConfigPublishCoordinator : ReceiveActor, IWithTimers
|
||||
return;
|
||||
}
|
||||
|
||||
// Discard acks from nodes that were not in the expected-ack set at dispatch time.
|
||||
// Without this guard an unexpected-node ack inflates _acks.Count and can trigger a
|
||||
// premature seal/fail before every expected node has actually applied the deployment.
|
||||
if (!_expectedAcks.Contains(msg.NodeId))
|
||||
{
|
||||
_log.Debug("Discarding ApplyAck from unexpected node {Node} for {Id} (not in expected set)",
|
||||
msg.NodeId, msg.DeploymentId);
|
||||
return;
|
||||
}
|
||||
|
||||
_acks[msg.NodeId] = msg.Outcome;
|
||||
PersistNodeAck(msg);
|
||||
|
||||
|
||||
@@ -127,16 +127,16 @@ public sealed class FleetStatusBroadcaster : ReceiveActor, IWithTimers
|
||||
private void OnTick()
|
||||
{
|
||||
var stale = DateTime.UtcNow - HeartbeatTimeout;
|
||||
var changed = false;
|
||||
var flippedCount = 0;
|
||||
foreach (var kv in _nodes.ToList())
|
||||
{
|
||||
if (kv.Value.LastSeenUtc < stale && kv.Value.Health == FleetNodeHealth.Healthy)
|
||||
{
|
||||
_nodes[kv.Key] = kv.Value with { Health = FleetNodeHealth.Degraded };
|
||||
changed = true;
|
||||
flippedCount++;
|
||||
}
|
||||
}
|
||||
if (changed) _log.Debug("FleetStatusBroadcaster flipped {Count} nodes to Degraded", changed ? 1 : 0);
|
||||
if (flippedCount > 0) _log.Debug("FleetStatusBroadcaster flipped {Count} nodes to Degraded", flippedCount);
|
||||
PublishSnapshot();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user