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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var specs = DeploymentArtifact.ParseDriverInstances(blob);
|
var specs = DeploymentArtifact.ParseDriverInstances(blob, _localNode.Value);
|
||||||
var snapshots = _children.ToDictionary(
|
var snapshots = _children.ToDictionary(
|
||||||
kv => kv.Key,
|
kv => kv.Key,
|
||||||
kv => new DriverChildSnapshot(kv.Value.DriverType, kv.Value.LastConfigJson),
|
kv => new DriverChildSnapshot(kv.Value.DriverType, kv.Value.LastConfigJson),
|
||||||
@@ -429,7 +429,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
Phase7CompositionResult composition;
|
Phase7CompositionResult composition;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
composition = DeploymentArtifact.ParseComposition(blob);
|
composition = DeploymentArtifact.ParseComposition(blob, _localNode.Value);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,6 +132,86 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase
|
|||||||
snap.Drivers.Count.ShouldBe(1);
|
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(
|
private static DeploymentId SeedDeploymentWithDrivers(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
||||||
RevisionHash rev,
|
RevisionHash rev,
|
||||||
|
|||||||
Reference in New Issue
Block a user