feat(runtime): F7 spawn lifecycle + F20 ShouldStub gate
DriverHostActor.ApplyAndAck now reads the deployment artifact and reconciles its set of DriverInstanceActor children — spawn the missing, ApplyDelta to those with changed config, stop the removed/disabled. The diff lives in pure DriverSpawnPlanner so it can be unit-tested without an ActorSystem. Adds IDriverFactory in Core.Abstractions (consumed by Runtime) + DriverFactoryRegistryAdapter in Core.Hosting that wraps the existing v1 DriverFactoryRegistry — Runtime stays decoupled from Polly/Serilog, the Host wires the adapter once driver assemblies have registered. ShouldStub(type, roles) is now actually called on every spawn — Galaxy + Wonderware-Historian boot stubbed on macOS/Linux or whenever the host carries the dev role. Missing factory ⇒ stub fallback, never a crash. Tests: 24 → 34 in Runtime (+10): - DriverSpawnPlannerTests x7 (diff cases, type change ⇒ stop+respawn) - DeploymentArtifactTests x5 (empty/malformed/missing fields tolerant) - DriverHostActorReconcileTests x4 (spawn count, stub fallback, ShouldStub gate, second-apply stops the removed) All 6 v2 test suites green: 120 tests passing. Closes F20 (ShouldStub wired). F7 marked partial — subscription publishing + write path still stubbed in DriverInstanceActor itself.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal driver-side view of the deployment artifact emitted by
|
||||
/// <c>ConfigComposer.SnapshotAndFlattenAsync</c>. The artifact JSON is the full snapshot —
|
||||
/// for driver spawning we only need the <c>DriverInstances</c> array. Reading just the
|
||||
/// subset keeps allocations cheap on every deploy.
|
||||
/// </summary>
|
||||
public sealed record DriverInstanceSpec(
|
||||
Guid DriverInstanceRowId,
|
||||
string DriverInstanceId,
|
||||
string Name,
|
||||
string DriverType,
|
||||
bool Enabled,
|
||||
string DriverConfig);
|
||||
|
||||
public static class DeploymentArtifact
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parse a deployment artifact blob into the list of driver-instance specs to spawn.
|
||||
/// Empty / malformed blobs return an empty list — callers log + treat as "no drivers".
|
||||
/// </summary>
|
||||
public static IReadOnlyList<DriverInstanceSpec> ParseDriverInstances(ReadOnlySpan<byte> blob)
|
||||
{
|
||||
if (blob.IsEmpty) return Array.Empty<DriverInstanceSpec>();
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(blob.ToArray());
|
||||
if (!doc.RootElement.TryGetProperty("DriverInstances", out var arr)
|
||||
|| arr.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return Array.Empty<DriverInstanceSpec>();
|
||||
}
|
||||
|
||||
var result = new List<DriverInstanceSpec>(arr.GetArrayLength());
|
||||
foreach (var el in arr.EnumerateArray())
|
||||
{
|
||||
if (el.ValueKind != JsonValueKind.Object) continue;
|
||||
var spec = TryReadSpec(el);
|
||||
if (spec is not null) result.Add(spec);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return Array.Empty<DriverInstanceSpec>();
|
||||
}
|
||||
}
|
||||
|
||||
private static DriverInstanceSpec? TryReadSpec(JsonElement el)
|
||||
{
|
||||
var rowId = el.TryGetProperty("DriverInstanceRowId", out var rowEl)
|
||||
&& rowEl.TryGetGuid(out var rid) ? rid : Guid.Empty;
|
||||
var id = el.TryGetProperty("DriverInstanceId", out var idEl) ? idEl.GetString() : null;
|
||||
var name = el.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null;
|
||||
var type = el.TryGetProperty("DriverType", out var typeEl) ? typeEl.GetString() : null;
|
||||
var enabled = !el.TryGetProperty("Enabled", out var enEl) || enEl.GetBoolean();
|
||||
var config = el.TryGetProperty("DriverConfig", out var cfgEl) ? cfgEl.GetString() : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(type)) return null;
|
||||
|
||||
return new DriverInstanceSpec(
|
||||
DriverInstanceRowId: rowId,
|
||||
DriverInstanceId: id!,
|
||||
Name: name ?? id!,
|
||||
DriverType: type!,
|
||||
Enabled: enabled,
|
||||
DriverConfig: config ?? "{}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user