f5579e6ef2
A failed SDK start (port in use, PKI unwritable, bad OpcUa:* override) was one Error line: the
node stayed /health/ready 200, joined its mesh, could be elected driver Primary and acked deploys
through the Null sink while every client got connection-refused.
- IOpcUaEndpointState / OpcUaEndpointState: the start outcome is a fact the hosted service
maintains (NotStarted / Started / Failed / Stopped + reason).
- OpcUaEndpointHealthCheck ("opcua-endpoint", Ready + Active) is Unhealthy unless Started;
registered by AddOtOpcUaHealth(hasAdmin, hasDriver) on driver-role nodes.
- A driver-only node rethrows the start failure (Program.cs's fatal handler exits non-zero so
the supervisor restarts it); a fused node keeps its AdminUI up but is visibly not ready.
- StopAsync detaches the sinks and then STOPS the SDK (the port closes, clients get
connection-refused rather than a stale, inert endpoint); DisposeAsync is idempotent with it.
- The ServiceLevel calculator / redundancy election live in Cluster + Runtime (not this lane);
the fact is exposed for the Wave 3 follow-up to consume.
Tests: port-in-use ⇒ state Failed, probe Unhealthy, real /health/ready and /health/active 503
(positive control: 200 once Started); driver-only rethrows; StopAsync closes the port and
DisposeAsync twice is a no-op; the probe registration is on both tiers for driver nodes only.
Refs #733
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
74 lines
2.8 KiB
C#
74 lines
2.8 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa;
|
|
|
|
/// <summary>Lifecycle of this node's OPC UA endpoint as <see cref="OtOpcUaServerHostedService"/> drives it.</summary>
|
|
public enum OpcUaEndpointStatus
|
|
{
|
|
/// <summary>The hosted service has not run <c>StartAsync</c> yet.</summary>
|
|
NotStarted,
|
|
|
|
/// <summary>The SDK is listening and the address-space / ServiceLevel sinks are bound.</summary>
|
|
Started,
|
|
|
|
/// <summary>The SDK failed to start (port in use, PKI unwritable, bad <c>OpcUa:*</c> override, …).</summary>
|
|
Failed,
|
|
|
|
/// <summary>The hosted service stopped the SDK; the port is released.</summary>
|
|
Stopped,
|
|
}
|
|
|
|
/// <summary>
|
|
/// The one place the "did the OPC UA SDK actually start?" fact lives (R03:2.1). Before it, a failed
|
|
/// start was one Error line: <c>/health/ready</c> stayed 200, the node joined its mesh, could be
|
|
/// elected driver Primary and acked deploys as Applied through the Null sink — while clients got
|
|
/// connection-refused from the node the fleet called healthy.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Consumed by <c>OpcUaEndpointHealthCheck</c> (Ready + Active tiers). The redundancy election and the
|
|
/// ServiceLevel calculator do not read it yet — they live outside the Host (<c>Cluster</c> /
|
|
/// <c>Runtime</c>); feeding them is the Wave 3 follow-up the lane report names. Until then a fused
|
|
/// node whose SDK failed is visible (503) but still Primary-eligible.
|
|
/// </remarks>
|
|
public interface IOpcUaEndpointState
|
|
{
|
|
/// <summary>The endpoint's current lifecycle status.</summary>
|
|
OpcUaEndpointStatus Status { get; }
|
|
|
|
/// <summary>Why the start failed, when <see cref="Status"/> is <see cref="OpcUaEndpointStatus.Failed"/>.</summary>
|
|
string? FailureReason { get; }
|
|
}
|
|
|
|
/// <summary>Mutable <see cref="IOpcUaEndpointState"/>; written by the hosted service, read by the health check.</summary>
|
|
public sealed class OpcUaEndpointState : IOpcUaEndpointState
|
|
{
|
|
private volatile OpcUaEndpointStatus _status = OpcUaEndpointStatus.NotStarted;
|
|
private volatile string? _failureReason;
|
|
|
|
/// <inheritdoc />
|
|
public OpcUaEndpointStatus Status => _status;
|
|
|
|
/// <inheritdoc />
|
|
public string? FailureReason => _failureReason;
|
|
|
|
/// <summary>Records a successful SDK start.</summary>
|
|
public void MarkStarted()
|
|
{
|
|
_failureReason = null;
|
|
_status = OpcUaEndpointStatus.Started;
|
|
}
|
|
|
|
/// <summary>Records a failed SDK start.</summary>
|
|
/// <param name="reason">The failure, as the health probe should report it.</param>
|
|
public void MarkFailed(string reason)
|
|
{
|
|
_failureReason = reason;
|
|
_status = OpcUaEndpointStatus.Failed;
|
|
}
|
|
|
|
/// <summary>Records that the hosted service stopped the SDK.</summary>
|
|
public void MarkStopped()
|
|
{
|
|
_failureReason = null;
|
|
_status = OpcUaEndpointStatus.Stopped;
|
|
}
|
|
}
|