feat(redundancy): manual failover service — graceful Leave of the driver Primary

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-22 06:40:58 -04:00
parent 983310edbb
commit 69b697bc3e
4 changed files with 416 additions and 0 deletions
@@ -13,6 +13,7 @@ using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;
using ZB.MOM.WW.OtOpcUa.Cluster;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.ControlPlane;
using ZB.MOM.WW.OtOpcUa.ControlPlane.Redundancy;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
@@ -372,6 +373,13 @@ if (hasAdmin)
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSignalR();
builder.Services.AddOtOpcUaAdminClients();
// Manual failover backs the Trigger-failover control on the cluster redundancy page. Admin-only:
// it is driven from the UI, and the node it acts on is chosen from live cluster state, so any
// admin node can drive it regardless of whether that node is the Primary. The ActorSystem comes
// through the Func<> accessor below (never resolved at construction — this runs before Akka's
// hosted service has built the system).
builder.Services.TryAddSingleton<Func<ActorSystem>>(sp => () => sp.GetRequiredService<ActorSystem>());
builder.Services.AddSingleton<IManualFailoverService, ManualFailoverService>();
}
// Registered unconditionally: driver-role nodes resolve Layer-B DriverConfig secrets and have no auth/DP/AdminUI.