123 lines
5.1 KiB
C#
123 lines
5.1 KiB
C#
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|
using ZB.MOM.WW.OtOpcUa.OpcUaServer.Security;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa;
|
|
|
|
/// <summary>
|
|
/// Owns the OPC UA SDK lifecycle on driver-role hosts. Reads
|
|
/// <see cref="OpcUaApplicationHostOptions"/> from the <c>OpcUa</c> config section, boots
|
|
/// an <see cref="OtOpcUaSdkServer"/> through <see cref="OpcUaApplicationHost"/>, then
|
|
/// swaps a real <see cref="SdkAddressSpaceSink"/> into the
|
|
/// <see cref="DeferredAddressSpaceSink"/> singleton so <c>OpcUaPublishActor</c>'s writes
|
|
/// start landing in the real address space.
|
|
///
|
|
/// Tests boot the OPC UA server directly via <see cref="OpcUaApplicationHost"/>; this
|
|
/// hosted service is the production wiring.
|
|
/// </summary>
|
|
public sealed class OtOpcUaServerHostedService : IHostedService, IAsyncDisposable
|
|
{
|
|
private readonly OpcUaApplicationHostOptions _options;
|
|
private readonly DeferredAddressSpaceSink _deferredSink;
|
|
private readonly DeferredServiceLevelPublisher _deferredServiceLevel;
|
|
private readonly IOpcUaUserAuthenticator _userAuthenticator;
|
|
private readonly ILoggerFactory _loggerFactory;
|
|
private readonly ILogger<OtOpcUaServerHostedService> _logger;
|
|
|
|
private OpcUaApplicationHost? _appHost;
|
|
private OtOpcUaSdkServer? _server;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the OtOpcUaServerHostedService class.
|
|
/// </summary>
|
|
/// <param name="options">The validated OPC UA host options (bound from the <c>OpcUa</c> section and validated at startup via <c>ValidateOnStart</c>).</param>
|
|
/// <param name="deferredSink">The deferred address space sink that receives the real sink once the server is ready.</param>
|
|
/// <param name="deferredServiceLevel">The deferred service level publisher that receives the real publisher once the server is ready.</param>
|
|
/// <param name="userAuthenticator">The OPC UA user authenticator.</param>
|
|
/// <param name="loggerFactory">The logger factory for creating loggers.</param>
|
|
public OtOpcUaServerHostedService(
|
|
IOptions<OpcUaApplicationHostOptions> options,
|
|
DeferredAddressSpaceSink deferredSink,
|
|
DeferredServiceLevelPublisher deferredServiceLevel,
|
|
IOpcUaUserAuthenticator userAuthenticator,
|
|
ILoggerFactory loggerFactory)
|
|
{
|
|
_options = options.Value;
|
|
_deferredSink = deferredSink;
|
|
_deferredServiceLevel = deferredServiceLevel;
|
|
_userAuthenticator = userAuthenticator;
|
|
_loggerFactory = loggerFactory;
|
|
_logger = loggerFactory.CreateLogger<OtOpcUaServerHostedService>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the OPC UA server asynchronously.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
public async Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_server = new OtOpcUaSdkServer();
|
|
_appHost = new OpcUaApplicationHost(
|
|
_options,
|
|
_loggerFactory.CreateLogger<OpcUaApplicationHost>(),
|
|
_userAuthenticator);
|
|
|
|
try
|
|
{
|
|
await _appHost.StartAsync(_server, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex,
|
|
"OtOpcUaServerHostedService: SDK start failed; OpcUaPublishActor writes will continue to no-op");
|
|
// Don't rethrow — the rest of the host (admin UI, driver actors, etc.) can still boot.
|
|
// Operators see the failure via the logs + can correct config without a process bounce
|
|
// of the whole binary.
|
|
return;
|
|
}
|
|
|
|
if (_server.NodeManager is null)
|
|
{
|
|
_logger.LogWarning(
|
|
"OtOpcUaServerHostedService: SDK reported started but NodeManager is null; sink stays Null");
|
|
return;
|
|
}
|
|
|
|
_deferredSink.SetSink(new SdkAddressSpaceSink(_server.NodeManager));
|
|
|
|
// ServiceLevel publisher needs IServerInternal — only available after Start.
|
|
if (_server.CurrentInstance is { } serverInternal)
|
|
{
|
|
_deferredServiceLevel.SetInner(new SdkServiceLevelPublisher(
|
|
serverInternal,
|
|
_loggerFactory.CreateLogger<SdkServiceLevelPublisher>()));
|
|
}
|
|
|
|
_logger.LogInformation("OtOpcUaServerHostedService: SDK started, address-space + ServiceLevel sinks bound");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the OPC UA server asynchronously.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
// Revert to Null adapters so any in-flight writes from a poison-pilled actor don't hit a
|
|
// half-disposed NodeManager.
|
|
_deferredSink.SetSink(null);
|
|
_deferredServiceLevel.SetInner(null);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the hosted service and its resources asynchronously.
|
|
/// </summary>
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_appHost is not null) await _appHost.DisposeAsync();
|
|
}
|
|
}
|