refactor(configdb): drop ClusterNode.RedundancyRole (replaced by Akka leader)
Phase 1d of the v2 entity-model rewrite. The static RedundancyRole column
is replaced by Akka cluster's role-leader-of-"driver" election at runtime
(see RedundancyStateActor + ServiceLevelCalculator in Task 35).
Changes:
- Removed `public required RedundancyRole RedundancyRole` from
ClusterNode entity.
- Removed `e.Property(x => x.RedundancyRole).HasConversion<string>()...`
mapping from OtOpcUaConfigDbContext.ConfigureClusterNode.
- Removed the `UX_ClusterNode_Primary_Per_Cluster` filtered unique index
(filter referenced [RedundancyRole]='Primary').
- Dropped `using ZB.MOM.WW.OtOpcUa.Configuration.Enums` from ClusterNode.cs
(no longer needed).
- Deleted `Enums/RedundancyRole.cs` — the enum is unused in v2-kept code.
- DraftValidator: dropped the "exactly one Primary per cluster"
validation block. Comment in place explaining v2 picks primary at
runtime via Akka.
- DraftValidatorTests: dropped ValidateClusterTopology_flags_multiple_Primary
test; reworked BuildNode helper to no longer take a `role` argument.
Untouched (Server + Admin still reference RedundancyRole; accepted broken
per Task 56 policy):
src/Server/ZB.MOM.WW.OtOpcUa.Server/Redundancy/{ClusterTopologyLoader,
RedundancyStatePublisher, RedundancyTopology, ServiceLevelCalculator}.cs
src/Server/ZB.MOM.WW.OtOpcUa.Admin/Services/RedundancyMetrics.cs
DB-runtime tests will fail against the new schema (Task 14f's migration
drops the column) — to be updated in Task 14f's SchemaComplianceTests
update:
- SchemaComplianceTests.cs:55 (expected filtered index list)
- StoredProceduresTests.cs:263 (raw INSERT names the column)
Verification:
src/Core/ZB.MOM.WW.OtOpcUa.Configuration -> 0 errors
tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests -> 0 errors
whole solution -> 71 errors
(70 from Task 14b in Server/Admin, +1 new Server/Redundancy reference)
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
|
||||
/// <summary>Physical OPC UA server node within a <see cref="ServerCluster"/>.</summary>
|
||||
@@ -10,8 +8,6 @@ public sealed class ClusterNode
|
||||
|
||||
public required string ClusterId { get; set; }
|
||||
|
||||
public required RedundancyRole RedundancyRole { get; set; }
|
||||
|
||||
/// <summary>Machine hostname / IP.</summary>
|
||||
public required string Host { get; set; }
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
/// <summary>Per-node redundancy role within a cluster. Per decision #84.</summary>
|
||||
public enum RedundancyRole
|
||||
{
|
||||
Primary,
|
||||
Secondary,
|
||||
Standalone,
|
||||
}
|
||||
@@ -115,7 +115,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.HasKey(x => x.NodeId);
|
||||
e.Property(x => x.NodeId).HasMaxLength(64);
|
||||
e.Property(x => x.ClusterId).HasMaxLength(64);
|
||||
e.Property(x => x.RedundancyRole).HasConversion<string>().HasMaxLength(16);
|
||||
e.Property(x => x.Host).HasMaxLength(255);
|
||||
e.Property(x => x.ApplicationUri).HasMaxLength(256);
|
||||
e.Property(x => x.DriverConfigOverridesJson).HasColumnType("nvarchar(max)");
|
||||
@@ -130,10 +129,10 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
// Fleet-wide unique per decision #86
|
||||
e.HasIndex(x => x.ApplicationUri).IsUnique().HasDatabaseName("UX_ClusterNode_ApplicationUri");
|
||||
e.HasIndex(x => x.ClusterId).HasDatabaseName("IX_ClusterNode_ClusterId");
|
||||
// At most one Primary per cluster
|
||||
e.HasIndex(x => x.ClusterId).IsUnique()
|
||||
.HasFilter("[RedundancyRole] = 'Primary'")
|
||||
.HasDatabaseName("UX_ClusterNode_Primary_Per_Cluster");
|
||||
// v2: the "one Primary per cluster" filtered unique index (and the RedundancyRole
|
||||
// column it filtered on) are gone. Akka cluster leader-of-driver-role is the
|
||||
// authoritative primary signal (see RedundancyStateActor + ServiceLevelCalculator,
|
||||
// Task 35).
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -228,14 +228,9 @@ public static class DraftValidator
|
||||
$"Toggle the missing node(s) back on or change RedundancyMode/NodeCount to match.",
|
||||
cluster.ClusterId));
|
||||
|
||||
// Primary uniqueness — decision #84. Two Primary nodes is always an invariant violation
|
||||
// regardless of mode; catch it here so publish fails loud rather than the runtime
|
||||
// demoting both to ServiceLevelBand.InvalidTopology at boot.
|
||||
var primaryCount = clusterNodes.Count(n => n.Enabled && n.RedundancyRole == RedundancyRole.Primary);
|
||||
if (primaryCount > 1)
|
||||
errors.Add(new("ClusterMultiplePrimary",
|
||||
$"Cluster '{cluster.ClusterId}' has {primaryCount} Enabled Primary nodes. At most one Primary per cluster.",
|
||||
cluster.ClusterId));
|
||||
// v2: the v1 "exactly one Primary per cluster" invariant is gone. RedundancyRole was
|
||||
// dropped in Task 14d; in v2 the Akka cluster's role-leader-of-"driver" elects the
|
||||
// primary at runtime, so there is no static configuration to validate here.
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public sealed class DraftValidatorTests
|
||||
{
|
||||
var cluster = BuildCluster(nodeCount: nodeCount, mode: mode);
|
||||
var nodes = Enumerable.Range(0, enabledNodes)
|
||||
.Select(i => BuildNode($"n-{i}", enabled: true, role: i == 0 ? RedundancyRole.Primary : RedundancyRole.Secondary))
|
||||
.Select(i => BuildNode($"n-{i}", enabled: true))
|
||||
.ToList();
|
||||
|
||||
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
|
||||
@@ -175,33 +175,24 @@ public sealed class DraftValidatorTests
|
||||
var cluster = BuildCluster(nodeCount: 2, mode: RedundancyMode.Hot);
|
||||
var nodes = new[]
|
||||
{
|
||||
BuildNode("primary", enabled: true, role: RedundancyRole.Primary),
|
||||
BuildNode("backup", enabled: false, role: RedundancyRole.Secondary),
|
||||
BuildNode("primary", enabled: true),
|
||||
BuildNode("backup", enabled: false),
|
||||
};
|
||||
|
||||
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
|
||||
errors.ShouldContain(e => e.Code == "ClusterEnabledNodeCountMismatch");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateClusterTopology_flags_multiple_Primary()
|
||||
{
|
||||
var cluster = BuildCluster(nodeCount: 2, mode: RedundancyMode.Hot);
|
||||
var nodes = new[]
|
||||
{
|
||||
BuildNode("a", enabled: true, role: RedundancyRole.Primary),
|
||||
BuildNode("b", enabled: true, role: RedundancyRole.Primary),
|
||||
};
|
||||
|
||||
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
|
||||
errors.ShouldContain(e => e.Code == "ClusterMultiplePrimary");
|
||||
}
|
||||
// v2: the "exactly one Primary per cluster" check is gone — Akka cluster's
|
||||
// role-leader-of-"driver" elects the primary at runtime. The corresponding
|
||||
// ValidateClusterTopology_flags_multiple_Primary test (and the
|
||||
// ClusterMultiplePrimary error code it asserted) were removed alongside Task 14d.
|
||||
|
||||
[Fact]
|
||||
public void ValidateClusterTopology_returns_no_errors_on_valid_standalone()
|
||||
{
|
||||
var cluster = BuildCluster(nodeCount: 1, mode: RedundancyMode.None);
|
||||
var nodes = new[] { BuildNode("only", enabled: true, role: RedundancyRole.Primary) };
|
||||
var nodes = new[] { BuildNode("only", enabled: true) };
|
||||
|
||||
var errors = DraftValidator.ValidateClusterTopology(cluster, nodes);
|
||||
errors.ShouldBeEmpty();
|
||||
@@ -219,11 +210,10 @@ public sealed class DraftValidatorTests
|
||||
CreatedBy = "t",
|
||||
};
|
||||
|
||||
private static ClusterNode BuildNode(string id, bool enabled, RedundancyRole role) => new()
|
||||
private static ClusterNode BuildNode(string id, bool enabled) => new()
|
||||
{
|
||||
NodeId = id,
|
||||
ClusterId = "c-test",
|
||||
RedundancyRole = role,
|
||||
Host = "localhost",
|
||||
OpcUaPort = 4840,
|
||||
DashboardPort = 5001,
|
||||
|
||||
Reference in New Issue
Block a user