using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Options; using ZB.MOM.WW.LocalDb.Replication; namespace ZB.MOM.WW.OtOpcUa.Host.Health; /// /// Pure decision function for the LocalDb replication probe, factored out of /// so the whole matrix is table-testable without /// standing up a replication engine. /// public static class LocalDbReplicationDecision { /// /// Maps the resolved replication facts to a health status. /// /// /// True when this node participates in replication at all — it has a peer address to dial, or /// a sync listener bound. False means default-OFF, and default-OFF must never degrade a node. /// /// Whether a sync session is currently running. /// /// The unacked oplog backlog, or when it could not be read. Null is /// deliberately not treated as zero: a failed poll is "unknown", not "caught up". /// /// /// Backlog at or above which a connected pair is judged to be falling behind. /// /// The status plus a human-readable reason. public static (HealthStatus Status, string Description) Evaluate( bool peerConfigured, bool connected, long? oplogBacklog, long degradedThreshold) { if (!peerConfigured) return (HealthStatus.Healthy, "LocalDb replication is not configured on this node (default-OFF)."); if (!connected) return (HealthStatus.Degraded, "LocalDb replication is configured but no sync session is connected."); if (oplogBacklog is null) return (HealthStatus.Degraded, "LocalDb replication is connected but its oplog backlog is unknown (the poll failed)."); if (oplogBacklog.Value >= degradedThreshold) return (HealthStatus.Degraded, $"LocalDb replication is connected but its oplog backlog ({oplogBacklog.Value}) is at or above the degraded threshold ({degradedThreshold})."); return (HealthStatus.Healthy, $"LocalDb replication is connected; oplog backlog {oplogBacklog.Value}."); } } /// /// Reports the health of this node's LocalDb replication link. Registered unconditionally with /// the shared health pipeline; when LocalDb is not present at all (admin-only graphs) it reports /// rather than degrading — matching the /// ActiveNodeHealthCheck precedent of "not applicable ⇒ Healthy". /// /// /// A node running LocalDb with replication switched off (no peer, no listener) is also Healthy: /// that is the default posture for most of the fleet, and it must not look degraded. Only a node /// that is meant to be replicating and is not — or is connected but cannot confirm it is /// draining — is surfaced as Degraded. It never reports Unhealthy: a replication problem does not /// stop the node serving its address space, so it must not fail a readiness gate. /// public sealed class LocalDbReplicationHealthCheck : IHealthCheck { /// /// Backlog at or above which a connected pair is reported Degraded. Well below the library's /// MaxOplogRows default (1,000,000, where a snapshot resync is forced), so the probe /// flags a pair that is falling behind long before the engine itself intervenes. /// public const long DefaultBacklogDegradedThreshold = 100_000; private readonly ISyncStatus? _syncStatus; private readonly ReplicationOptions _options; private readonly int _syncListenPort; private readonly long _degradedThreshold; /// Creates the health check. /// /// The replication status singleton, or when the replication engine is /// not registered (admin-only nodes) — in which case the check is a Healthy no-op. /// /// The bound replication options (peer address, etc.). /// /// The configured sync listener port; a value greater than zero counts as "replication /// configured" even when this node is the passive side with no peer address. /// /// Backlog degraded threshold; defaults to . public LocalDbReplicationHealthCheck( ISyncStatus? syncStatus, IOptions options, int syncListenPort, long degradedThreshold = DefaultBacklogDegradedThreshold) { ArgumentNullException.ThrowIfNull(options); _syncStatus = syncStatus; _options = options.Value; _syncListenPort = syncListenPort; _degradedThreshold = degradedThreshold; } /// public Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { // No replication engine registered at all → not applicable → Healthy. Admin-only nodes and // any graph that did not call AddOtOpcUaLocalDb land here. if (_syncStatus is null) return Task.FromResult(HealthCheckResult.Healthy( "LocalDb replication is not present on this node.")); var peerConfigured = !string.IsNullOrWhiteSpace(_options.PeerAddress) || _syncListenPort > 0; var (status, description) = LocalDbReplicationDecision.Evaluate( peerConfigured, _syncStatus.Connected, _syncStatus.OplogBacklog, _degradedThreshold); var result = status switch { HealthStatus.Healthy => HealthCheckResult.Healthy(description), _ => HealthCheckResult.Degraded(description), }; return Task.FromResult(result); } }