feat(controlplane): ServiceLevelCalculator + ControlPlane.Tests harness

This commit is contained in:
Joseph Doherty
2026-05-26 04:43:59 -04:00
parent 32574b3e4e
commit 14acab5a58
6 changed files with 201 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
using Akka.Cluster;
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Redundancy;
public readonly record struct NodeHealthInputs(
MemberStatus MemberState,
bool DbReachable,
bool OpcUaProbeOk,
bool Stale,
bool IsDriverRoleLeader);
/// <summary>
/// Pure ServiceLevel computation per design §6. Output range 0255, where higher = "more
/// authoritative." The OPC UA SDK exposes this as the node's <c>ServiceLevel</c> Variable and
/// redundant clients use it to pick which server to subscribe to.
///
/// Tiering:
/// - Member not Up/Joining: 0 (cluster cannot trust this node).
/// - DB reachable + OPC UA probe ok + not stale: 240 (full service).
/// - Stale config (DB reachable or not, OPC UA probe state ignored): 100 or 200 depending on DB.
/// - +10 bonus when this node holds the role-leader lease for the "driver" role.
/// </summary>
public static class ServiceLevelCalculator
{
public static byte Compute(NodeHealthInputs h)
{
if (h.MemberState is not (MemberStatus.Up or MemberStatus.Joining))
return 0;
var basis = (h.DbReachable, h.OpcUaProbeOk, h.Stale) switch
{
(true, true, false) => 240,
(true, _, true) => 200,
(false, _, true) => 100,
_ => 0,
};
return (byte)Math.Clamp(basis + (h.IsDriverRoleLeader ? 10 : 0), 0, 255);
}
}

View File

@@ -11,7 +11,10 @@
<ItemGroup>
<PackageReference Include="Akka.Hosting"/>
<PackageReference Include="Akka.Cluster"/>
<PackageReference Include="Akka.Cluster.Hosting"/>
<PackageReference Include="Akka.Cluster.Tools"/>
<PackageReference Include="Microsoft.EntityFrameworkCore"/>
</ItemGroup>
<ItemGroup>