fix(audit): maintenance-login roll-forward/backfill, archive orphan cleanup, head-export health (ScadaBridge#113 #114 #115)
#113: AuditLogPartitionMaintenance (monthly SPLIT RANGE) and the backfill-source-node endpoint now use the AuditLog:MaintenanceConnectionString login when set (unset = unchanged). Migration AuditMaintenanceGrants gives scadabridge_audit_purger ALTER ANY DATASPACE and column-level UPDATE (SourceNode). #114: archive cleanup deletes the writer's own *.tmp files and manifest-less data files older than 24 h inside {Directory}/{YYYY} (DeletedOrphan, dry-run aware). #115: head-export failures bump scadabridge.audit.chain_head_export.failures and Degrade the /health/ready entry audit-chain-head-export (failing or stale > 2 intervals). Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central.HeadExport;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Host.Health;
|
||||
|
||||
/// <summary>
|
||||
/// /health/ready entry (ScadaBridge#115): Degraded while the off-box AuditLog chain-head export
|
||||
/// (<c>AuditLog:HashChain:HeadExport</c>) is failing, or has had no success for more than two
|
||||
/// intervals. Degraded maps to HTTP 200, so it shows on the Overview dashboard without taking
|
||||
/// the node out of rotation. Healthy when the export is disabled.
|
||||
/// </summary>
|
||||
public sealed class AuditChainHeadExportHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly AuditChainHeadExportStatus _status;
|
||||
private readonly TimeProvider _time;
|
||||
|
||||
/// <summary>Initializes the check.</summary>
|
||||
/// <param name="status">The export status singleton.</param>
|
||||
/// <param name="time">Clock (defaults to the system clock).</param>
|
||||
public AuditChainHeadExportHealthCheck(AuditChainHeadExportStatus status, TimeProvider? time = null)
|
||||
{
|
||||
_status = status ?? throw new ArgumentNullException(nameof(status));
|
||||
_time = time ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_status.Enabled)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Healthy("Chain-head export disabled."));
|
||||
}
|
||||
|
||||
var now = _time.GetUtcNow().UtcDateTime;
|
||||
var data = new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
{
|
||||
["totalFailures"] = _status.TotalFailures,
|
||||
["consecutiveFailures"] = _status.ConsecutiveFailures,
|
||||
["intervalMinutes"] = _status.IntervalMinutes,
|
||||
};
|
||||
if (_status.LastSuccessUtc is { } ok)
|
||||
{
|
||||
data["lastSuccessUtc"] = ok;
|
||||
}
|
||||
|
||||
if (_status.ConsecutiveFailures > 0)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Degraded(
|
||||
$"Chain-head export failing ({_status.ConsecutiveFailures} consecutive): {_status.LastError}", data: data));
|
||||
}
|
||||
|
||||
var limit = TimeSpan.FromMinutes(2L * _status.IntervalMinutes);
|
||||
var reference = _status.LastSuccessUtc ?? _status.StartedUtc;
|
||||
if (reference is { } r && now - r > limit)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Degraded(
|
||||
$"No successful chain-head export for more than 2 intervals (last {(_status.LastSuccessUtc is null ? "never" : _status.LastSuccessUtc.Value.ToString("O"))}).", data: data));
|
||||
}
|
||||
|
||||
return Task.FromResult(HealthCheckResult.Healthy("Chain-head export OK.", data));
|
||||
}
|
||||
}
|
||||
@@ -579,6 +579,12 @@ try
|
||||
"bundle-signing-policy",
|
||||
failureStatus: null,
|
||||
tags: new[] { ZbHealthTags.Ready })
|
||||
// ScadaBridge#115: Degraded (HTTP 200) while the off-box chain-head export is failing
|
||||
// or stale; healthy when the export is disabled.
|
||||
.AddTypeActivatedCheck<AuditChainHeadExportHealthCheck>(
|
||||
"audit-chain-head-export",
|
||||
failureStatus: null,
|
||||
tags: new[] { ZbHealthTags.Ready })
|
||||
// Oldest-Up-member check (review 01 [High]), unscoped: every central member competes for
|
||||
// the one active slot. Since ZB.MOM.WW.Health 0.3.0 this IS the shared check — it selects
|
||||
// by age, so the host-local OldestNodeActiveHealthCheck that existed only to avoid the old
|
||||
|
||||
Reference in New Issue
Block a user