979dc102dd
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
74 lines
4.0 KiB
C#
74 lines
4.0 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Cluster;
|
|
|
|
public sealed class AkkaClusterOptions
|
|
{
|
|
public const string SectionName = "Cluster";
|
|
|
|
/// <summary>Gets or sets the Akka system name.</summary>
|
|
public string SystemName { get; set; } = "otopcua";
|
|
|
|
/// <summary>Gets or sets the hostname to bind to (default 0.0.0.0).</summary>
|
|
public string Hostname { get; set; } = "0.0.0.0";
|
|
|
|
/// <summary>Gets or sets the port to listen on (default 4053).</summary>
|
|
public int Port { get; set; } = 4053;
|
|
|
|
/// <summary>
|
|
/// Hostname advertised in cluster gossip. Must be reachable by other nodes.
|
|
/// In docker-compose this is the container DNS name; in bare metal it's the
|
|
/// host's stable LAN address.
|
|
/// </summary>
|
|
public string PublicHostname { get; set; } = "127.0.0.1";
|
|
|
|
/// <summary>Gets or sets the seed nodes for cluster bootstrapping.</summary>
|
|
public string[] SeedNodes { get; set; } = Array.Empty<string>();
|
|
|
|
/// <summary>
|
|
/// Cluster roles for this node. When empty the role list comes from
|
|
/// <c>OTOPCUA_ROLES</c> via <see cref="RoleParser"/>. Allowed values:
|
|
/// <c>admin</c>, <c>driver</c>, <c>dev</c>.
|
|
/// </summary>
|
|
public string[] Roles { get; set; } = Array.Empty<string>();
|
|
|
|
/// <summary>
|
|
/// How the cluster decides to down a node it can no longer reach. One of <c>auto-down</c>
|
|
/// (default) or <c>keep-oldest</c>; any other value fails the host at startup.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b><c>auto-down</c> — availability.</b> The leader among the <i>reachable</i> members
|
|
/// downs the unreachable peer after
|
|
/// <see cref="ServiceCollectionExtensions.DowningStableAfter"/>. A hard crash of either
|
|
/// node — including the oldest — fails over to the survivor with no operator action.
|
|
/// The trade is that a genuine network partition (both nodes alive, link cut) leaves
|
|
/// both sides running active until an operator restarts one.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b><c>keep-oldest</c> — partition-safety.</b> The SBR resolver sacrifices the younger
|
|
/// side of a split, so a partition can never run dual-active. <b>The cost is severe for a
|
|
/// two-node pair:</b> it cannot survive a crash of the oldest node at all. Akka.NET's
|
|
/// <c>KeepOldest.OldestDecision</c> only lets <c>down-if-alone</c> rescue a side holding
|
|
/// >= 2 members, so the 1-vs-1 survivor downs <i>itself</i> and shuts down — the
|
|
/// redundancy pair turns a single-node crash into a total outage. Choose this only for
|
|
/// clusters of three or more nodes, or where dual-active is genuinely worse than an
|
|
/// outage.
|
|
/// </para>
|
|
/// </remarks>
|
|
public string SplitBrainResolverStrategy { get; set; } = "auto-down";
|
|
|
|
/// <summary>
|
|
/// Bootstrap self-form fallback window (decision 2026-07-22, scadaproj/akka_failover.md §6.1).
|
|
/// Akka only lets the FIRST listed seed form a new cluster; a non-first seed cold-starting
|
|
/// while its peer is down loops on InitJoin forever ("auto-down removes the crash outage,
|
|
/// not this one" — docs/Redundancy.md). When this node has waited longer than this window
|
|
/// without cluster membership it joins itself — but ONLY if its own address appears in
|
|
/// <see cref="SeedNodes"/>; a non-seed node (e.g. a site node whose only seed is central-1)
|
|
/// stays waiting, because self-forming there creates a permanent island. Default 10s
|
|
/// (same-datacenter pair; a live peer answers InitJoin in milliseconds). <c>null</c> or a
|
|
/// non-positive value disables the fallback. Accepted trade: both pair nodes cold-starting
|
|
/// inside the window while mutually unreachable form two clusters — the same dual-active
|
|
/// class the auto-down strategy already accepts, same recovery (restart one side).
|
|
/// </summary>
|
|
public TimeSpan? SelfFormAfter { get; set; } = TimeSpan.FromSeconds(10);
|
|
}
|