8a173bbaf6
Redundancy.md: KNOWN-LIMITATION (per-Akka-cluster election) marked RESOLVED;
new 'Per-cluster meshes (Phase 6)' section + pair-local secrets note; manual-
failover mesh-scope caveat removed; auto-down 1-vs-1 note updated (every mesh is
now two nodes, drill deferred to Phase 7). IManualFailoverService + ClusterRedundancy
razor caveats updated to pair-local reality. Configuration.md documents the
cluster-{ClusterId} role, per-pair self-first seeds, SplitTopologyTransportValidator,
and the intentional Dps default. Program + design status tables + CLAUDE.md marked
Phase 6 DONE.
(Task 8 subagent completed the edits but died on an API error before committing;
reviewed and committed by the controller.)
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
54 lines
2.6 KiB
C#
54 lines
2.6 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Redundancy;
|
|
|
|
/// <summary>
|
|
/// A read-only view of the cluster's driver membership, as the manual-failover control sees it.
|
|
/// </summary>
|
|
/// <param name="PrimaryAddress">
|
|
/// The current driver Primary — the oldest Up <c>driver</c> member, the same node
|
|
/// <see cref="RedundancyStateActor.SelectDriverPrimary"/> names — or <see langword="null"/> when
|
|
/// there is no Up driver member at all.
|
|
/// </param>
|
|
/// <param name="DriverAddresses">
|
|
/// Every Up <c>driver</c> member, oldest first. A manual failover needs at least two: with one
|
|
/// there is nothing to fail over to.
|
|
/// </param>
|
|
public sealed record ManualFailoverSnapshot(string? PrimaryAddress, IReadOnlyList<string> DriverAddresses)
|
|
{
|
|
/// <summary>Whether a failover can be performed — i.e. a peer exists to take over.</summary>
|
|
public bool CanFailOver => DriverAddresses.Count >= 2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Operator-initiated, graceful swap of the driver Primary.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The mechanism is a cluster <b>Leave</b> of the current Primary, never a <c>Down</c>. The
|
|
/// leaving node hands its singletons over through the normal cluster-leave phases,
|
|
/// <c>CoordinatedShutdown</c> runs, <c>ActorSystemTerminationWatchdog</c> exits the process,
|
|
/// the service supervisor restarts it, and it rejoins as the youngest member — so the
|
|
/// survivor becomes the oldest Up driver member and therefore Primary. ServiceLevel 250
|
|
/// moves with it and OPC UA clients re-select.
|
|
/// </para>
|
|
/// <para>
|
|
/// Post-Phase-6 (per-cluster mesh split): the Primary is elected per application
|
|
/// <c>Cluster</c>, because each cluster's redundant pair now runs its own independent
|
|
/// 2-node Akka mesh. This service therefore acts on <b>this pair's own</b> Primary.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IManualFailoverService
|
|
{
|
|
/// <summary>Reads the current driver membership from live cluster state.</summary>
|
|
ManualFailoverSnapshot GetSnapshot();
|
|
|
|
/// <summary>
|
|
/// Gracefully removes the current driver Primary from the cluster so its peer takes over.
|
|
/// </summary>
|
|
/// <param name="actor">The authenticated identity performing the failover; recorded in the audit event.</param>
|
|
/// <returns>
|
|
/// The address of the node asked to leave, or <see langword="null"/> when the failover was
|
|
/// refused because no driver peer exists (in which case nothing was changed and nothing audited).
|
|
/// </returns>
|
|
Task<string?> FailOverDriverPrimaryAsync(string actor);
|
|
}
|