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,67 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Pure diff between the currently-running driver children (keyed by
|
||||
/// <c>DriverInstance.DriverInstanceId</c>) and the target spec list from a freshly-applied
|
||||
/// deployment artifact. The DriverHostActor consumes the three lists and calls
|
||||
/// spawn / ApplyDelta / stop on its child actors accordingly.
|
||||
/// </summary>
|
||||
/// <param name="ToSpawn">Specs with no current child — create a new actor.</param>
|
||||
/// <param name="ToApplyDelta">Specs whose child exists but config JSON or type differs.</param>
|
||||
/// <param name="ToStop">DriverInstanceIds currently running but missing from the new artifact, or now disabled.</param>
|
||||
public sealed record DriverSpawnPlan(
|
||||
IReadOnlyList<DriverInstanceSpec> ToSpawn,
|
||||
IReadOnlyList<DriverInstanceSpec> ToApplyDelta,
|
||||
IReadOnlyList<string> ToStop);
|
||||
|
||||
public static class DriverSpawnPlanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute the spawn/delta/stop sets. Disabled entries in <paramref name="target"/> are
|
||||
/// treated as "not desired here": if a child exists for the id it goes into ToStop,
|
||||
/// otherwise the row is dropped entirely (no spawn for a disabled driver).
|
||||
/// </summary>
|
||||
public static DriverSpawnPlan Compute(
|
||||
IReadOnlyDictionary<string, DriverChildSnapshot> current,
|
||||
IReadOnlyList<DriverInstanceSpec> target)
|
||||
{
|
||||
var toSpawn = new List<DriverInstanceSpec>();
|
||||
var toDelta = new List<DriverInstanceSpec>();
|
||||
var toStop = new List<string>();
|
||||
|
||||
var targetById = new Dictionary<string, DriverInstanceSpec>(StringComparer.Ordinal);
|
||||
foreach (var spec in target) targetById[spec.DriverInstanceId] = spec;
|
||||
|
||||
foreach (var (id, snap) in current)
|
||||
{
|
||||
if (!targetById.TryGetValue(id, out var spec) || !spec.Enabled)
|
||||
{
|
||||
toStop.Add(id);
|
||||
continue;
|
||||
}
|
||||
// Driver type changes can't be reinitialized in-place (factory-bound) — stop + respawn.
|
||||
if (!string.Equals(snap.DriverType, spec.DriverType, StringComparison.Ordinal))
|
||||
{
|
||||
toStop.Add(id);
|
||||
toSpawn.Add(spec);
|
||||
continue;
|
||||
}
|
||||
if (!string.Equals(snap.LastConfigJson, spec.DriverConfig, StringComparison.Ordinal))
|
||||
{
|
||||
toDelta.Add(spec);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (id, spec) in targetById)
|
||||
{
|
||||
if (!spec.Enabled) continue;
|
||||
if (current.ContainsKey(id)) continue;
|
||||
toSpawn.Add(spec);
|
||||
}
|
||||
|
||||
return new DriverSpawnPlan(toSpawn, toDelta, toStop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Snapshot of one running driver child as the host sees it. Used as the diff input.</summary>
|
||||
public sealed record DriverChildSnapshot(string DriverType, string LastConfigJson);
|
||||
Reference in New Issue
Block a user