Task #125 / #137. The hosted service + scheduler classes already shipped; this commit connects them to the published-generation driver list so a Tier C driver with `RecycleIntervalSeconds` in its `ResilienceConfig` actually gets an armed scheduler at bootstrap. Wiring: - `DriverFactoryRegistry.Register` gains an optional `DriverTier` parameter (default Tier.A). Existing call sites unchanged — `GalaxyProxyDriverFactoryExtensions.Register` explicitly passes Tier.C so the bootstrapper can identify out-of-process drivers without a per-driver-type allow-list. - `DriverResilienceOptions` + parser grow `RecycleIntervalSeconds`. Tier A/B values are rejected with a diagnostic (decision #74 — recycling an in-process driver would kill every OPC UA session). Non-positive values are rejected the same way. - `DriverInstanceBootstrapper` auto-arms a `ScheduledRecycleScheduler` after a successful driver register when: (1) the registered tier is C, (2) the row's ResilienceConfig carries a positive recycle interval, (3) DI has an `IDriverSupervisor` keyed by that `DriverInstanceId`. Missing supervisor → warn + skip (no crash). That keeps the wiring harmless by default: no driver ships a supervisor today, so the hosted service runs with zero schedulers out of the box. - `Program.cs` registers `ScheduledRecycleHostedService` as singleton (shared with `DriverInstanceBootstrapper`) + hosted service (drives the tick loop). Constructor changes on the bootstrapper ripple into DI resolution automatically. Tests: 4 new parser tests covering RecycleIntervalSeconds on Tier C happy path, null default, Tier A/B rejection, non-positive rejection. Existing 283 Server.Tests + 200 Core.Tests all still green. No behavioural change for existing deployments: Galaxy driver + any future Tier C driver gain the opt-in automatically; Tier A/B drivers (FOCAS, Modbus, S7, AB CIP, AB Legacy, TwinCAT) are structurally excluded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
62 lines
2.9 KiB
C#
62 lines
2.9 KiB
C#
using System.Text.Json;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy;
|
|
|
|
/// <summary>
|
|
/// Static factory registration helper for <see cref="GalaxyProxyDriver"/>. Server's
|
|
/// Program.cs calls <see cref="Register"/> once at startup; the bootstrapper (task #248)
|
|
/// then materialises Galaxy DriverInstance rows from the central config DB into live
|
|
/// driver instances. No dependency on Microsoft.Extensions.DependencyInjection so the
|
|
/// driver project stays free of DI machinery.
|
|
/// </summary>
|
|
public static class GalaxyProxyDriverFactoryExtensions
|
|
{
|
|
public const string DriverTypeName = "Galaxy";
|
|
|
|
/// <summary>
|
|
/// Register the Galaxy driver factory in the supplied <see cref="DriverFactoryRegistry"/>.
|
|
/// Throws if 'Galaxy' is already registered — single-instance per process.
|
|
/// </summary>
|
|
public static void Register(DriverFactoryRegistry registry)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(registry);
|
|
// Galaxy is Tier C — out-of-process MXAccess Host, scheduled recycle is allowed.
|
|
registry.Register(DriverTypeName, CreateInstance, DriverTier.C);
|
|
}
|
|
|
|
internal static GalaxyProxyDriver CreateInstance(string driverInstanceId, string driverConfigJson)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
|
|
|
|
// DriverConfig column is a JSON object that mirrors GalaxyProxyOptions.
|
|
// Required: PipeName, SharedSecret. Optional: ConnectTimeoutMs (defaults to 10s).
|
|
// The DriverInstanceId from the row wins over any value in the JSON — the row
|
|
// is the authoritative identity per the schema's UX_DriverInstance_Generation_LogicalId.
|
|
using var doc = JsonDocument.Parse(driverConfigJson);
|
|
var root = doc.RootElement;
|
|
|
|
string pipeName = root.TryGetProperty("PipeName", out var p) && p.ValueKind == JsonValueKind.String
|
|
? p.GetString()!
|
|
: throw new InvalidOperationException(
|
|
$"GalaxyProxyDriver config for '{driverInstanceId}' missing required PipeName");
|
|
string sharedSecret = root.TryGetProperty("SharedSecret", out var s) && s.ValueKind == JsonValueKind.String
|
|
? s.GetString()!
|
|
: throw new InvalidOperationException(
|
|
$"GalaxyProxyDriver config for '{driverInstanceId}' missing required SharedSecret");
|
|
var connectTimeout = root.TryGetProperty("ConnectTimeoutMs", out var t) && t.ValueKind == JsonValueKind.Number
|
|
? TimeSpan.FromMilliseconds(t.GetInt32())
|
|
: TimeSpan.FromSeconds(10);
|
|
|
|
return new GalaxyProxyDriver(new GalaxyProxyOptions
|
|
{
|
|
DriverInstanceId = driverInstanceId,
|
|
PipeName = pipeName,
|
|
SharedSecret = sharedSecret,
|
|
ConnectTimeout = connectTimeout,
|
|
});
|
|
}
|
|
}
|