Phase 6.1 Stream D follow-up — SealedBootstrap consumes ResilientConfigReader + GenerationSealedCache + StaleConfigFlag; /healthz surfaces the flag
Closes release blocker #2 from docs/v2/v2-release-readiness.md — the generation-sealed cache + resilient reader + stale-config flag shipped as unit-tested primitives in PR #81, but no production path consumed them until now. This PR wires them end-to-end. Server additions: - SealedBootstrap — Phase 6.1 Stream D consumption hook. Resolves the node's current generation through ResilientConfigReader's timeout → retry → fallback-to-sealed pipeline. On every successful central-DB fetch it seals a fresh snapshot to <cache-root>/<cluster>/<generationId>.db so a future cache-miss has a known-good fallback. Alongside the original NodeBootstrap (which still uses the single-file ILocalConfigCache); Program.cs can switch between them once operators are ready for the generation-sealed semantics. - OpcUaApplicationHost: new optional staleConfigFlag ctor parameter. When wired, HealthEndpointsHost consumes `flag.IsStale` via the existing usingStaleConfig Func<bool> hook. Means `/healthz` actually reports `usingStaleConfig: true` whenever a read fell back to the sealed cache — closes the loop between Stream D's flag + Stream C's /healthz body shape. Tests (4 new SealedBootstrapIntegrationTests, all pass): - Central-DB success path seals snapshot + flag stays fresh. - Central-DB failure falls back to sealed snapshot + flag flips stale (the SQL-kill scenario from Phase 6.1 Stream D.4.a). - No-snapshot + central-down throws GenerationCacheUnavailableException with a clear error (the first-boot scenario from D.4.c). - Next successful bootstrap after a fallback clears the stale flag. Full solution dotnet test: 1168 passing (was 1164, +4). Pre-existing Client.CLI Subscribe flake unchanged. Production activation: Program.cs wires SealedBootstrap (instead of NodeBootstrap), constructs OpcUaApplicationHost with the staleConfigFlag, and a HostedService polls sp_GetCurrentGenerationForCluster periodically so peer-published generations land in this node's sealed cache. The poller itself is Stream D.1.b follow-up. The sp_PublishGeneration SQL-side hook (where the publish commit itself could also write to a shared sealed cache) stays deferred — the per-node seal pattern shipped here is the correct v2 GA model: each Server node owns its own on-disk cache and refreshes from its own DB reads, matching the Phase 6.1 scope-table description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
||||
@@ -25,6 +26,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
private readonly DriverResiliencePipelineBuilder _pipelineBuilder;
|
||||
private readonly AuthorizationGate? _authzGate;
|
||||
private readonly NodeScopeResolver? _scopeResolver;
|
||||
private readonly StaleConfigFlag? _staleConfigFlag;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger<OpcUaApplicationHost> _logger;
|
||||
private ApplicationInstance? _application;
|
||||
@@ -36,7 +38,8 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
IUserAuthenticator authenticator, ILoggerFactory loggerFactory, ILogger<OpcUaApplicationHost> logger,
|
||||
DriverResiliencePipelineBuilder? pipelineBuilder = null,
|
||||
AuthorizationGate? authzGate = null,
|
||||
NodeScopeResolver? scopeResolver = null)
|
||||
NodeScopeResolver? scopeResolver = null,
|
||||
StaleConfigFlag? staleConfigFlag = null)
|
||||
{
|
||||
_options = options;
|
||||
_driverHost = driverHost;
|
||||
@@ -44,6 +47,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
_pipelineBuilder = pipelineBuilder ?? new DriverResiliencePipelineBuilder();
|
||||
_authzGate = authzGate;
|
||||
_scopeResolver = scopeResolver;
|
||||
_staleConfigFlag = staleConfigFlag;
|
||||
_loggerFactory = loggerFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -84,6 +88,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
_healthHost = new HealthEndpointsHost(
|
||||
_driverHost,
|
||||
_loggerFactory.CreateLogger<HealthEndpointsHost>(),
|
||||
usingStaleConfig: _staleConfigFlag is null ? null : () => _staleConfigFlag.IsStale,
|
||||
prefix: _options.HealthEndpointsPrefix);
|
||||
_healthHost.Start();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user