feat(runtime): spawn+apply VirtualTagHostActor on deploy apply and restore
This commit is contained in:
+141
@@ -0,0 +1,141 @@
|
||||
using System.Text.Json;
|
||||
using Akka.Actor;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Fleet;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the live-deploy wiring of <see cref="VirtualTagHostActor"/>: the
|
||||
/// <see cref="DriverHostActor"/> must forward the composition's
|
||||
/// <c>EquipmentVirtualTags</c> to its spawned VirtualTag host via
|
||||
/// <see cref="VirtualTagHostActor.ApplyVirtualTags"/> on BOTH the fresh-apply path and the
|
||||
/// bootstrap-restore path (both route through <c>PushDesiredSubscriptions</c>). The host is
|
||||
/// injected as a <see cref="Akka.TestKit.TestProbe"/> via the Props override seam so the
|
||||
/// ApplyVirtualTags can be intercepted.
|
||||
/// </summary>
|
||||
public sealed class DriverHostActorVirtualTagTests : RuntimeActorTestBase
|
||||
{
|
||||
private static readonly NodeId TestNode = NodeId.Parse("driver-vt-test");
|
||||
private static readonly RevisionHash RevA = RevisionHash.Parse(new string('a', 64));
|
||||
|
||||
/// <summary>Fresh apply: dispatching a deployment whose artifact carries one Equipment
|
||||
/// VirtualTag forwards an <see cref="VirtualTagHostActor.ApplyVirtualTags"/> carrying that
|
||||
/// plan to the injected VirtualTag host.</summary>
|
||||
[Fact]
|
||||
public void Apply_forwards_EquipmentVirtualTags_to_virtual_tag_host()
|
||||
{
|
||||
var db = NewInMemoryDbFactory();
|
||||
var deploymentId = SeedDeploymentWithVirtualTag(db, RevA);
|
||||
|
||||
var coordinator = CreateTestProbe();
|
||||
var vtHost = CreateTestProbe();
|
||||
var actor = Sys.ActorOf(DriverHostActor.Props(
|
||||
db, TestNode, coordinator.Ref,
|
||||
localRoles: new HashSet<string> { "driver" },
|
||||
virtualTagEvaluator: NullVirtualTagEvaluator.Instance,
|
||||
virtualTagHostOverride: vtHost.Ref));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
var apply = vtHost.ExpectMsg<VirtualTagHostActor.ApplyVirtualTags>(TimeSpan.FromSeconds(5));
|
||||
var plan = apply.Plans.ShouldHaveSingleItem();
|
||||
plan.VirtualTagId.ShouldBe("vt-1");
|
||||
plan.EquipmentId.ShouldBe("eq-1");
|
||||
plan.Name.ShouldBe("Doubled");
|
||||
}
|
||||
|
||||
/// <summary>Bootstrap-restore: a node that already has an <c>Applied</c> NodeDeploymentState
|
||||
/// row for a VirtualTag-carrying deployment re-forwards the
|
||||
/// <see cref="VirtualTagHostActor.ApplyVirtualTags"/> on PreStart (no dispatch needed), so a
|
||||
/// restarted node restores its live VirtualTag children.</summary>
|
||||
[Fact]
|
||||
public void Restore_on_bootstrap_forwards_EquipmentVirtualTags_to_virtual_tag_host()
|
||||
{
|
||||
var db = NewInMemoryDbFactory();
|
||||
var deploymentId = SeedDeploymentWithVirtualTag(db, RevA);
|
||||
SeedAppliedNodeState(db, deploymentId);
|
||||
|
||||
var coordinator = CreateTestProbe();
|
||||
var vtHost = CreateTestProbe();
|
||||
// No DispatchDeployment — Bootstrap() should detect the Applied row and run RestoreApplied,
|
||||
// which routes through PushDesiredSubscriptions and forwards ApplyVirtualTags.
|
||||
Sys.ActorOf(DriverHostActor.Props(
|
||||
db, TestNode, coordinator.Ref,
|
||||
localRoles: new HashSet<string> { "driver" },
|
||||
virtualTagEvaluator: NullVirtualTagEvaluator.Instance,
|
||||
virtualTagHostOverride: vtHost.Ref));
|
||||
|
||||
var apply = vtHost.ExpectMsg<VirtualTagHostActor.ApplyVirtualTags>(TimeSpan.FromSeconds(5));
|
||||
apply.Plans.ShouldHaveSingleItem().VirtualTagId.ShouldBe("vt-1");
|
||||
}
|
||||
|
||||
private static DeploymentId SeedDeploymentWithVirtualTag(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext> db, RevisionHash rev)
|
||||
{
|
||||
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
Scripts = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
ScriptId = "scr-1",
|
||||
SourceCode = "return ctx.GetTag(\"TestMachine_001.TestDouble\").Value;",
|
||||
},
|
||||
},
|
||||
VirtualTags = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
VirtualTagId = "vt-1",
|
||||
EquipmentId = "eq-1",
|
||||
Name = "Doubled",
|
||||
DataType = "Float",
|
||||
ScriptId = "scr-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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 void SeedAppliedNodeState(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext> db, DeploymentId deploymentId)
|
||||
{
|
||||
using var ctx = db.CreateDbContext();
|
||||
ctx.NodeDeploymentStates.Add(new NodeDeploymentState
|
||||
{
|
||||
NodeId = TestNode.Value,
|
||||
DeploymentId = deploymentId.Value,
|
||||
Status = NodeDeploymentStatus.Applied,
|
||||
StartedAtUtc = DateTime.UtcNow,
|
||||
AppliedAtUtc = DateTime.UtcNow,
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user