From 4f97e72ac91ab8fbd0d7993d351a67a35577e251 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 11 Aug 2026 06:50:27 -0400 Subject: [PATCH] =?UTF-8?q?fix(overview):=20wonder=20registry=20=E2=80=94?= =?UTF-8?q?=20127.0.0.1=20BaseUrls=20so=20leader=20names=20cannot=20resolv?= =?UTF-8?q?e=20wrongly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live on the box, the ScadaBridge *site* card claimed "Leader · central". Central and site are separate Akka clusters, so that name was simply wrong. Cause is in the registry, not the app. ResolveToInstanceName matches a reported Akka leader address to an instance on HOST ONLY - correct by design, because an app's Akka remoting port and its HTTP port differ, so comparing ports would never match. It assumes what holds on the docker rig and not here: distinct hosts per instance. All five instances are on one box, and the scan is fleet-wide in registry order, so with every BaseUrl on "localhost" the first matching entry won the chip for every group. No host value makes this resolve CORRECTLY, because the instances really are co-located. So make it never match instead: ScadaBridge's Akka addresses use host "localhost" and OtOpcUa's uses the FQDN, and "127.0.0.1" is string-unequal to both. Every chip now falls back to the raw Akka address - unambiguous and always true. A raw address beats a confident wrong name. Using the FQDN for OtOpcUa's BaseUrl would have resolved its chip correctly and was rejected: that name resolves to a link-local IPv6 on this box, and routing the real health probe through it to improve a label trades the primary function for a cosmetic one. ManagementUrl values are unchanged - they are followed by the operator's browser from another machine and must keep the box address. --- .../appsettings.Wonder.json | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/ZB.MOM.WW.Overview/src/ZB.MOM.WW.Overview/appsettings.Wonder.json b/ZB.MOM.WW.Overview/src/ZB.MOM.WW.Overview/appsettings.Wonder.json index eaa4024..96744ae 100644 --- a/ZB.MOM.WW.Overview/src/ZB.MOM.WW.Overview/appsettings.Wonder.json +++ b/ZB.MOM.WW.Overview/src/ZB.MOM.WW.Overview/appsettings.Wonder.json @@ -16,6 +16,27 @@ // // Ports NOT to probe, confirmed 400 (h2c gRPC, which rejects an HTTP/1.1 health GET): // :8083 ScadaBridge central control plane, :8086. + // + // ── Why every BaseUrl is 127.0.0.1 and never "localhost" ──────────────────────────────────── + // LeaderResolver.ResolveToInstanceName maps a reported Akka leader address to an instance by + // matching on HOST ONLY — correct by design, since an app's Akka remoting port and its HTTP + // port differ, so comparing ports would never match. That resolution assumes what is true on + // the docker rig and false here: that instances have DISTINCT hosts. All five of these live on + // one box, and the scan is fleet-wide in registry order, so the first entry sharing the host + // wins the chip for EVERY group. + // + // Observed live with BaseUrl "localhost": the ScadaBridge *site* card claimed "Leader · central" + // — two separate Akka clusters, so that name was simply wrong. + // + // There is no host value that makes resolution CORRECT here, because the instances really are + // co-located. So the deliberate choice is to make it never match: ScadaBridge's Akka addresses + // use the host "localhost" and OtOpcUa's uses the FQDN, and "127.0.0.1" is string-unequal to + // both. Every leader chip then falls back to the raw Akka address, which is unambiguous and + // always true. A raw address beats a confident wrong name. + // + // Using the FQDN for OtOpcUa's BaseUrl WOULD resolve its chip correctly, and was rejected: the + // name resolves to a link-local IPv6 on this box, and routing the actual health probe through + // that to improve a label trades the primary function for a cosmetic one. "Serilog": { // Absolute, and deliberately OUTSIDE the Server directory the upgrade procedure replaces. @@ -59,10 +80,10 @@ // (it triggers on more than one DISTINCT reported address). "Name": "wonder", "Group": "MAIN", - // Probed by THIS service, on this box — localhost is the shortest path and is immune + // Probed by THIS service, on this box — loopback is the shortest path and is immune // to any host firewall rule. ManagementUrl is the opposite case: the operator's // browser follows it from their own machine, so it must carry the box address. - "BaseUrl": "http://localhost:9000", + "BaseUrl": "http://127.0.0.1:9000", "ManagementUrl": "http://10.220.157.247:9000/", "HasActiveRole": true } @@ -75,7 +96,7 @@ { "Name": "central", "Group": "central", - "BaseUrl": "http://localhost:8085", + "BaseUrl": "http://127.0.0.1:8085", "ManagementUrl": "http://10.220.157.247:8085/", "HasActiveRole": true }, @@ -85,7 +106,7 @@ // disagreement that does not exist. "Name": "site", "Group": "site", - "BaseUrl": "http://localhost:8084", + "BaseUrl": "http://127.0.0.1:8084", // No ManagementUrl: the site node serves no UI (/ returns 404), and a card linking to // a 404 is worse than a card with no link. "HasActiveRole": true @@ -101,7 +122,7 @@ // /health/active does answer 200, but only because the active tier runs an EMPTY // check set — an Active badge here would assert a role that does not exist. "Name": "wonder", - "BaseUrl": "http://localhost:5130", + "BaseUrl": "http://127.0.0.1:5130", "ManagementUrl": "http://10.220.157.247:5130/", "HasActiveRole": false } @@ -113,7 +134,7 @@ "Instances": [ { "Name": "wonder", - "BaseUrl": "http://localhost:5220", + "BaseUrl": "http://127.0.0.1:5220", "ManagementUrl": "http://10.220.157.247:5220/", "HasActiveRole": false, // Its readiness probe reaches the historian and the Galaxy SQL DB over the network,