From 0e3c1df1a7de08b3145ddbd86054a2842d169dc3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 8 Jul 2026 16:37:43 -0400 Subject: [PATCH] fix(host): dev site seed no longer targets the metrics port; validator rejects seed-vs-MetricsPort --- .../StartupValidator.cs | 8 +++++ .../appsettings.Site.json | 3 +- .../StartupValidatorTests.cs | 30 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs index a3387ae6..71f40fa0 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs @@ -124,6 +124,14 @@ public static class StartupValidator _ => SeedNodePort(seed) != grpcPort, $"entry '{seed}' must not target the gRPC port " + $"({grpcPort}); seed nodes must reference Akka remoting ports"); + + // Same failure mode as the gRPC guard: the Kestrel HTTP/1.1 + // metrics listener is not an Akka.Remote endpoint, so a seed on + // it dials a doomed association forever. + p.Require("ScadaBridge:Cluster:SeedNodes", + _ => SeedNodePort(seed) != metricsPort, + $"entry '{seed}' must not target the metrics port " + + $"({metricsPort}); seed nodes must reference Akka remoting ports"); } }) .ThrowIfInvalid(); diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/appsettings.Site.json b/src/ZB.MOM.WW.ScadaBridge.Host/appsettings.Site.json index ee0fa91b..cf8d3fed 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/appsettings.Site.json +++ b/src/ZB.MOM.WW.ScadaBridge.Host/appsettings.Site.json @@ -11,9 +11,10 @@ "NodeName": "node-a" }, "Cluster": { + "_seedNodes": "Host-0xx: second entry is the FUTURE node-b remoting port (8085) for a two-node localhost site. It must be an Akka remoting endpoint — never this node's GrpcPort (8083) or MetricsPort (8084); StartupValidator rejects both.", "SeedNodes": [ "akka.tcp://scadabridge@localhost:8082", - "akka.tcp://scadabridge@localhost:8084" + "akka.tcp://scadabridge@localhost:8085" ], "SplitBrainResolverStrategy": "keep-oldest", "StableAfter": "00:00:15", diff --git a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/StartupValidatorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/StartupValidatorTests.cs index 109d9db7..de2f8375 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/StartupValidatorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/StartupValidatorTests.cs @@ -319,6 +319,36 @@ public class StartupValidatorTests Assert.Contains("must not target the gRPC port", ex.Message); } + [Fact] + public void Site_SeedNodeOnMetricsPort_FailsValidation() + { + // Review 01 [Medium]: appsettings.Site.json shipped a seed pointing at the + // Kestrel HTTP/1.1 metrics listener (8084) — a doomed Akka.Remote + // association. The validator guarded GrpcPort but not MetricsPort. + var values = ValidSiteConfig(); + values["ScadaBridge:Node:GrpcPort"] = "8083"; + values["ScadaBridge:Node:MetricsPort"] = "8084"; + values["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@site-a-node1:8084"; + var config = BuildConfig(values); + + var ex = Assert.Throws(() => StartupValidator.Validate(config)); + Assert.Contains("must not target the metrics port", ex.Message); + } + + [Fact] + public void Site_SeedNodeOnDefaultMetricsPort_FailsValidation() + { + // MetricsPort absent => NodeOptions default 8084. A seed on 8084 must + // still be rejected. Keep GrpcPort distinct so only the metrics rule fires. + var values = ValidSiteConfig(); + values["ScadaBridge:Node:GrpcPort"] = "8083"; + values["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@site-a-node2:8084"; + var config = BuildConfig(values); + + var ex = Assert.Throws(() => StartupValidator.Validate(config)); + Assert.Contains("must not target the metrics port", ex.Message); + } + [Fact] public void Site_SeedNodeOnDefaultGrpcPort_FailsValidation() {