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);
}
}