feat(drivers): TCP-connect IDriverProbe for all 9 driver types
Cheap-and-fast probe: open TCP socket to the configured endpoint, close immediately. Surfaces SocketError on failure, latency on success, "timed out" on caller cancel. Sufficient for the AdminUI Test Connect "can we reach the host?" question. Richer protocol- level probes (OPC UA session open, FOCAS handshake, gRPC ping) are a documented follow-up. Each probe registered as AddSingleton<IDriverProbe, X> in DriverFactoryBootstrap so they flow through DI into AdminOperationsActor. Historian.Wonderware returns a clean "TCP probe not applicable" result because it communicates over a Windows named pipe, not TCP. Also adds OpcUaClient + Historian.Wonderware.Client project references to Host.csproj (both were missing from the driver ItemGroup).
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
|
||||
|
||||
/// <summary>
|
||||
/// Cheap TCP-connect probe for the <see cref="OpcUaClientDriverOptions"/>-shaped driver config.
|
||||
/// Parses the first endpoint URL (from <see cref="OpcUaClientDriverOptions.EndpointUrls"/> or
|
||||
/// the convenience <see cref="OpcUaClientDriverOptions.EndpointUrl"/> fallback), opens a
|
||||
/// socket to the OPC UA server host + port and closes immediately. Surfaces a green tick +
|
||||
/// latency on success; red chip + SocketError on failure; "timed out" on the caller's
|
||||
/// cancellation. Does NOT open an OPC UA session — a richer session-open probe is a
|
||||
/// documented follow-up.
|
||||
/// </summary>
|
||||
public sealed class OpcUaClientDriverProbe : IDriverProbe
|
||||
{
|
||||
private static readonly JsonSerializerOptions _opts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DriverType => "OpcUaClient";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DriverProbeResult> ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
|
||||
{
|
||||
OpcUaClientDriverOptions? opts;
|
||||
try { opts = JsonSerializer.Deserialize<OpcUaClientDriverOptions>(configJson, _opts); }
|
||||
catch (Exception ex) { return new(false, $"Config JSON is invalid: {ex.Message}", null); }
|
||||
if (opts is null) return new(false, "Config JSON deserialized to null.", null);
|
||||
|
||||
var (host, port) = ExtractTarget(opts);
|
||||
if (string.IsNullOrWhiteSpace(host) || port <= 0)
|
||||
return new(false, "Config has no host/port to probe.", null);
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
await socket.ConnectAsync(host, port, ct);
|
||||
sw.Stop();
|
||||
return new(true, null, sw.Elapsed);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
return new(false, $"Connect failed: {ex.SocketErrorCode}", null);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new(false, $"Probe timed out after {timeout.TotalSeconds:F0}s.", null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(false, ex.Message, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static (string host, int port) ExtractTarget(OpcUaClientDriverOptions opts)
|
||||
{
|
||||
// EndpointUrls wins over the convenience EndpointUrl when both are set.
|
||||
var endpointUrl = opts.EndpointUrls.FirstOrDefault()
|
||||
?? (string.IsNullOrWhiteSpace(opts.EndpointUrl) ? null : opts.EndpointUrl);
|
||||
if (endpointUrl is null) return (string.Empty, 0);
|
||||
|
||||
// Parse as a URI — opc.tcp://host:port is a valid URI.
|
||||
if (!Uri.TryCreate(endpointUrl, UriKind.Absolute, out var uri))
|
||||
return (string.Empty, 0);
|
||||
|
||||
var host = uri.Host;
|
||||
var port = uri.IsDefaultPort ? 4840 : uri.Port;
|
||||
return (host, port);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user