c8e2f4da02
Central and each site are SEPARATE Akka clusters, so central cannot act on a
site's membership -- it asks. New TriggerSiteFailover/SiteFailoverAck contract
travels the existing ClusterClient command/control channel (mirroring the
RetryParkedOperation relay); the site's own SiteCommunicationActor performs the
graceful Leave and acks the outcome.
- ClusterFailoverCoordinator moved out of Host into Communication/ClusterState,
beside ActiveNodeEvaluator. Both paths now share ONE oldest-Up implementation;
SiteCommunicationActor cannot reference Host, and the two definitions must not
drift or the node asked to leave stops being the singleton host.
- Site scope is the SITE-SPECIFIC role (site-{SiteId}), not the base Site role --
site singletons are placed on the former, so the base role would move the wrong
node. Pinned by a unit test asserting the role string and by a real-cluster test.
- Site-side guards: refuses a command addressed to another site (a misroute must
never fail over a site the operator did not select), refuses when there is no
peer, and reports a fault as an ack rather than throwing into supervision --
a restart there would drop central's Ask into a bare timeout and lose the reason.
- Ack is sent before the Leave takes effect so it still reaches central.
- UI: the same control now serves both scopes via a SiteId parameter. The site
confirmation deliberately does NOT claim the admin's page will disconnect --
it won't, and crying wolf there devalues the central warning that is real. A
site refusal and an unreachable site surface distinctly.
- Rolling upgrade: a site on an older binary has no handler, so the message
dead-letters and the Ask times out, reported as "site did not respond". That
is the honest outcome; documented on the contract.
Fallout fixed: HealthPageTests now renders the page inside
CascadingAuthenticationState with the real policy set and IAuthorizationService,
because the cards embed an AuthorizeView. That mirrors production, where the
layout supplies the cascading value.
49 lines
2.7 KiB
C#
49 lines
2.7 KiB
C#
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
|
|
/// <summary>
|
|
/// Admin-triggered manual failover of the central pair. Declared here — and in terms of
|
|
/// plain strings — so CentralUI stays Akka-free; the Akka implementation lives in the Host
|
|
/// (<c>AkkaManualFailoverService</c>) and is registered only in the Central branch.
|
|
/// </summary>
|
|
public interface IManualFailoverService
|
|
{
|
|
/// <summary>
|
|
/// Gracefully fails over the central cluster: the current active (oldest Up) member
|
|
/// leaves, restarts via its supervisor, and rejoins as standby. The Leave is graceful,
|
|
/// never a Down, so cluster singletons hand over instead of being killed.
|
|
/// <para>
|
|
/// The caller is usually connected THROUGH the node being failed over (Traefik routes
|
|
/// the UI to the active node), so the calling Blazor circuit should expect to drop and
|
|
/// reconnect against the new active node.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="actor">Authenticated user name, recorded on the audit row.</param>
|
|
/// <returns>The address acted on, or <c>null</c> when there is no peer to fail over to
|
|
/// (failing over a lone node would be an outage, not a failover).</returns>
|
|
Task<string?> FailOverCentralAsync(string actor);
|
|
|
|
/// <summary>
|
|
/// Asks a SITE to gracefully fail over its own two-node pair. Central and each site are
|
|
/// separate Akka clusters, so this is a request relayed over the command/control channel —
|
|
/// the site performs the Leave itself and reports the outcome. Unlike central failover,
|
|
/// this does NOT disturb the caller's own UI session.
|
|
/// </summary>
|
|
/// <param name="siteId">The site whose pair should fail over.</param>
|
|
/// <param name="actor">Authenticated user name, recorded on the audit row.</param>
|
|
/// <returns>The site's outcome — accepted with a target, or refused with a reason.</returns>
|
|
Task<SiteFailoverOutcome> FailOverSiteAsync(string siteId, string actor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outcome of a site-pair failover request, in UI terms. Deliberately distinct from the wire
|
|
/// ack so CentralUI stays free of the Akka message contract.
|
|
/// </summary>
|
|
/// <param name="Accepted"><c>true</c> when the site issued the graceful Leave.</param>
|
|
/// <param name="TargetAddress">Address of the leaving site node; <c>null</c> when refused.</param>
|
|
/// <param name="ErrorMessage">
|
|
/// Why the request was refused or failed. A refusal (peer guard, misroute) is a definitive
|
|
/// answer FROM the site; an unreachable site surfaces here as a timeout message. Both are
|
|
/// reported to the operator rather than being flattened into a generic failure.
|
|
/// </param>
|
|
public sealed record SiteFailoverOutcome(bool Accepted, string? TargetAddress, string? ErrorMessage);
|