feat(runtime): DriverHost spawns + subscribes only its own ClusterId's drivers
This commit is contained in:
@@ -364,7 +364,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
return;
|
||||
}
|
||||
|
||||
var specs = DeploymentArtifact.ParseDriverInstances(blob);
|
||||
var specs = DeploymentArtifact.ParseDriverInstances(blob, _localNode.Value);
|
||||
var snapshots = _children.ToDictionary(
|
||||
kv => kv.Key,
|
||||
kv => new DriverChildSnapshot(kv.Value.DriverType, kv.Value.LastConfigJson),
|
||||
@@ -429,7 +429,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
Phase7CompositionResult composition;
|
||||
try
|
||||
{
|
||||
composition = DeploymentArtifact.ParseComposition(blob);
|
||||
composition = DeploymentArtifact.ParseComposition(blob, _localNode.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -132,6 +132,86 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase
|
||||
snap.Drivers.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies per-ClusterId scoping at the actor level: a 2-cluster artifact (MAIN + SITE-A,
|
||||
/// one driver each) dispatched to a node whose ClusterNode row puts it in SITE-A spawns ONLY
|
||||
/// the SITE-A driver — and the node still reaches Applied (the ack fires unconditionally even
|
||||
/// when a node's cluster slice is empty).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DriverHostActor_spawns_only_its_clusters_drivers()
|
||||
{
|
||||
var db = NewInMemoryDbFactory();
|
||||
var factory = new CountingDriverFactory("Modbus");
|
||||
// Both drivers are Modbus so the factory could create either — scoping, not type support,
|
||||
// must be what excludes the MAIN driver.
|
||||
var deploymentId = SeedMultiClusterDeployment(db, RevA,
|
||||
("main-modbus", "Modbus", "{}", true, "MAIN"),
|
||||
("sa-modbus", "Modbus", "{}", true, "SITE-A"));
|
||||
|
||||
// This node belongs to SITE-A per the Nodes (ClusterNode) rows.
|
||||
var siteANode = NodeId.Parse("site-a-1:4053");
|
||||
var coordinator = CreateTestProbe();
|
||||
var actor = Sys.ActorOf(DriverHostActor.Props(
|
||||
db, siteANode, coordinator.Ref,
|
||||
driverFactory: factory,
|
||||
localRoles: new HashSet<string> { "driver" }));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
|
||||
// The node still reaches Applied even though it hosts only its own cluster's slice.
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
// Only the SITE-A driver was constructed — the MAIN driver was filtered out by scoping.
|
||||
AwaitAssert(() => factory.CreateCount.ShouldBe(1), duration: TimeSpan.FromSeconds(3));
|
||||
|
||||
actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref);
|
||||
var snap = coordinator.ExpectMsg<Commons.Interfaces.NodeDiagnosticsSnapshot>(TimeSpan.FromSeconds(2));
|
||||
snap.Drivers.Count.ShouldBe(1);
|
||||
snap.Drivers[0].Name.ShouldBe("sa-modbus");
|
||||
}
|
||||
|
||||
private static DeploymentId SeedMultiClusterDeployment(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
||||
RevisionHash rev,
|
||||
params (string Id, string Type, string Config, bool Enabled, string ClusterId)[] drivers)
|
||||
{
|
||||
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
// >1 cluster + matching Nodes rows triggers ScopeTo (single-cluster would resolve to None).
|
||||
Clusters = new[] { new { ClusterId = "MAIN" }, new { ClusterId = "SITE-A" } },
|
||||
Nodes = new[]
|
||||
{
|
||||
new { NodeId = "central-1:4053", ClusterId = "MAIN" },
|
||||
new { NodeId = "site-a-1:4053", ClusterId = "SITE-A" },
|
||||
},
|
||||
DriverInstances = drivers.Select(d => new
|
||||
{
|
||||
DriverInstanceRowId = Guid.NewGuid(),
|
||||
DriverInstanceId = d.Id,
|
||||
Name = d.Id,
|
||||
DriverType = d.Type,
|
||||
Enabled = d.Enabled,
|
||||
DriverConfig = d.Config,
|
||||
ClusterId = d.ClusterId,
|
||||
}).ToArray(),
|
||||
});
|
||||
|
||||
var id = DeploymentId.NewId();
|
||||
using var ctx = db.CreateDbContext();
|
||||
ctx.Deployments.Add(new Deployment
|
||||
{
|
||||
DeploymentId = id.Value,
|
||||
RevisionHash = rev.Value,
|
||||
Status = DeploymentStatus.Sealed,
|
||||
CreatedBy = "test",
|
||||
SealedAtUtc = DateTime.UtcNow,
|
||||
ArtifactBlob = artifact,
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
return id;
|
||||
}
|
||||
|
||||
private static DeploymentId SeedDeploymentWithDrivers(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
||||
RevisionHash rev,
|
||||
|
||||
Reference in New Issue
Block a user