fix(server): resolve Low code-review findings (Server-004,006,008,012,014,015)
- Server-004: pass the role-derived display name to UserIdentity's base ctor (the SDK's DisplayName has no public setter) and drop the dead Display property; make RoleBasedIdentity internal sealed. - Server-006: derive a bounded CancellationToken from the SDK's OperationContext.OperationDeadline in OnReadValue / OnWriteValue so a stalled driver call can no longer pin the request thread. - Server-008: mark handled slots via CallMethodRequest.Processed = true in RouteScriptedAlarmMethodCalls (the SDK skips on Processed, not on a Good error slot). - Server-012: PeerHttpProbeLoop.ProbeAsync stops mutating client.Timeout per call; uses a per-request CancellationTokenSource linked to the shutdown token instead. - Server-014: wire SealedBootstrap into Program.cs via AddSealedBootstrap + OpcUaServerService so the generation-sealed cache + stale-config flag + resilient reader actually run; /healthz now reflects cache-fallback state. - Server-015: replace the stale 'PR 16 / PR 17 minimum-viable scope' class summaries on OtOpcUaServer and OpcUaServerOptions with the shipped LDAP + anonymous-role + configurable security-profile prose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
src/Server/ZB.MOM.WW.OtOpcUa.Server/ServerWiring.cs
Normal file
57
src/Server/ZB.MOM.WW.OtOpcUa.Server/ServerWiring.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Server;
|
||||
|
||||
/// <summary>
|
||||
/// DI registration helpers consumed by <c>Program.cs</c>. Extracted so tests can assert
|
||||
/// the production wire-up actually composes without spinning up the full <c>Host</c>.
|
||||
/// </summary>
|
||||
public static class ServerWiring
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-014 — registers the Phase 6.1 Stream D generation-sealed bootstrap chain:
|
||||
/// <see cref="GenerationSealedCache"/>, <see cref="StaleConfigFlag"/>,
|
||||
/// <see cref="ResilientConfigReader"/>, and <see cref="SealedBootstrap"/>. Without these
|
||||
/// registrations <c>OpcUaServerService</c> cannot consume the sealed bootstrap and the
|
||||
/// <see cref="StaleConfigFlag"/> stays inert — <c>/healthz</c>'s <c>usingStaleConfig</c>
|
||||
/// never flips on a DB outage with a warm cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The cache root is sourced from <see cref="NodeOptions.LocalCachePath"/> — same path
|
||||
/// the legacy <see cref="NodeBootstrap"/> uses for its LiteDB cache, so both bootstrap
|
||||
/// paths persist alongside each other while the migration completes.
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddSealedBootstrap(this IServiceCollection services, NodeOptions options)
|
||||
{
|
||||
// Use a sibling directory off LocalCachePath so the LiteDB file and the
|
||||
// GenerationSealedCache snapshots don't clash. The cache root is a directory;
|
||||
// LocalCachePath is canonically the LiteDB file path.
|
||||
var cacheRoot = ResolveCacheRoot(options.LocalCachePath);
|
||||
// Register NodeOptions only if the caller hasn't already done so — Program.cs
|
||||
// registers it earlier in its DI chain, but the wiring helper supports standalone
|
||||
// unit tests that want to compose just the SealedBootstrap chain.
|
||||
if (!services.Any(d => d.ServiceType == typeof(NodeOptions)))
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton(new GenerationSealedCache(cacheRoot));
|
||||
services.AddSingleton<StaleConfigFlag>();
|
||||
services.AddSingleton<ResilientConfigReader>();
|
||||
services.AddSingleton<SealedBootstrap>();
|
||||
return services;
|
||||
}
|
||||
|
||||
private static string ResolveCacheRoot(string localCachePath)
|
||||
{
|
||||
// LocalCachePath is the LiteDB file (e.g. "config_cache.db"); the sealed cache is a
|
||||
// directory. Pick a sibling folder so the two don't share a path.
|
||||
if (string.IsNullOrWhiteSpace(localCachePath))
|
||||
return Path.Combine(Path.GetTempPath(), "otopcua-sealed-cache");
|
||||
|
||||
var dir = Path.GetDirectoryName(localCachePath);
|
||||
var name = Path.GetFileNameWithoutExtension(localCachePath);
|
||||
var root = string.IsNullOrEmpty(dir)
|
||||
? $"{name}.sealed"
|
||||
: Path.Combine(dir, $"{name}.sealed");
|
||||
return root;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user