diff --git a/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json b/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json index 9145edce..aebd3c96 100644 --- a/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json +++ b/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json @@ -43,7 +43,7 @@ { "id": "T6", "subject": "Warning parity: malformed-endpoint warning in ServerHistorianOptions.Validate() + Runtime.Tests case; commit 1 (S-11) after T1-T6 green", - "status": "pending", + "status": "completed", "blockedBy": [ "T4" ] diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs index 2ffedff4..98f4eb56 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs @@ -69,7 +69,16 @@ public sealed class ServerHistorianOptions public int MaxTieClusterOverfetch { get; init; } = 65536; /// Returns operator-facing misconfiguration warnings for an Enabled historian - /// (empty when disabled or correctly configured). Pure — the registration logs each entry. + /// (empty when disabled or correctly configured). Pure — the registration logs each entry. + /// + /// These are warnings only. The Host layers a fail-fast startup validator + /// (ServerHistorianOptionsValidator, wired via AddValidatedOptions) on top of this + /// for the provably-crashing subset — an empty/malformed Endpoint that is consumed + /// by an enabled historian or an enabled AlarmHistorian — so that config fails host start + /// with a named error instead of a raw UriFormatException crash-loop (archreview 06/S-11). + /// The empty-ApiKey / non-positive-MaxTieClusterOverfetch entries stay warnings + /// because they degrade rather than crash. + /// /// Zero or more human-readable warning messages (never carrying secret values). public IReadOnlyList Validate() { @@ -77,6 +86,13 @@ public sealed class ServerHistorianOptions if (!Enabled) return warnings; if (string.IsNullOrWhiteSpace(Endpoint)) warnings.Add("ServerHistorian:Endpoint is empty while the historian is enabled — the read client has no gateway address to dial."); + // archreview 06/S-11 warning parity: a non-empty but unparseable / non-absolute-http(s) Endpoint + // is just as fatal (it throws in the gateway client factory). Warn on it here so the Runtime-side + // registration logs describe malformed endpoints too, consistent with the Host's fail-fast + // ServerHistorianOptionsValidator (which turns this same condition into a startup failure). + else if (!Uri.TryCreate(Endpoint, UriKind.Absolute, out var uri) + || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) + warnings.Add($"ServerHistorian:Endpoint ('{Endpoint}') is not an absolute http(s) URI while the historian is enabled — the read client cannot dial it (e.g. https://host:5222)."); if (string.IsNullOrWhiteSpace(ApiKey)) warnings.Add("ServerHistorian:ApiKey is empty while the historian is enabled — the gateway gRPC surface will reject unauthenticated calls."); // MaxTieClusterOverfetch is intentionally checked AFTER the Enabled early-return above: diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs index d4e2eb04..88acedae 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs @@ -21,6 +21,16 @@ public sealed class ServerHistorianOptionsTests Assert.Contains(w, m => m.Contains("Endpoint")); } + [Fact] + public void Enabled_with_malformed_endpoint_warns() + { + // archreview 06/S-11 warning parity: a non-empty but unparseable / non-absolute Endpoint warns + // too, so the Runtime-side registration logs describe malformed endpoints (not just empty ones), + // keeping this surface consistent with the Host's fail-fast ServerHistorianOptionsValidator. + var w = new ServerHistorianOptions { Enabled = true, Endpoint = "not a uri", ApiKey = "histgw_x_y" }.Validate(); + Assert.Contains(w, m => m.Contains("Endpoint")); + } + [Fact] public void Enabled_without_apikey_warns() {