feat(scripted-alarms): spawn + apply ScriptedAlarmHostActor in DriverHostActor (T10)
This commit is contained in:
@@ -3,6 +3,8 @@ using Akka.Actor;
|
|||||||
using Akka.Cluster.Tools.PublishSubscribe;
|
using Akka.Cluster.Tools.PublishSubscribe;
|
||||||
using Akka.Event;
|
using Akka.Event;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
|
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
|
||||||
using ZB.MOM.WW.OtOpcUa.Commons.Interfaces;
|
using ZB.MOM.WW.OtOpcUa.Commons.Interfaces;
|
||||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
|
||||||
@@ -14,7 +16,10 @@ using ZB.MOM.WW.OtOpcUa.Configuration;
|
|||||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
||||||
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Runtime.ScriptedAlarms;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
||||||
using CommonsNodeId = ZB.MOM.WW.OtOpcUa.Commons.Types.NodeId;
|
using CommonsNodeId = ZB.MOM.WW.OtOpcUa.Commons.Types.NodeId;
|
||||||
|
|
||||||
@@ -56,6 +61,9 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
private readonly IDriverHealthPublisher _healthPublisher;
|
private readonly IDriverHealthPublisher _healthPublisher;
|
||||||
private readonly IVirtualTagEvaluator _virtualTagEvaluator;
|
private readonly IVirtualTagEvaluator _virtualTagEvaluator;
|
||||||
private readonly IActorRef? _virtualTagHostOverride;
|
private readonly IActorRef? _virtualTagHostOverride;
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private readonly ScriptRootLogger? _scriptRootLogger;
|
||||||
|
private readonly IActorRef? _scriptedAlarmHostOverride;
|
||||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||||
|
|
||||||
/// <summary>The single VirtualTag-host child that spawns/reconciles Equipment-namespace
|
/// <summary>The single VirtualTag-host child that spawns/reconciles Equipment-namespace
|
||||||
@@ -64,6 +72,14 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
/// <see cref="VirtualTagHostActor.ApplyVirtualTags"/> from <see cref="PushDesiredSubscriptions"/>.</summary>
|
/// <see cref="VirtualTagHostActor.ApplyVirtualTags"/> from <see cref="PushDesiredSubscriptions"/>.</summary>
|
||||||
private IActorRef? _virtualTagHost;
|
private IActorRef? _virtualTagHost;
|
||||||
|
|
||||||
|
/// <summary>The single ScriptedAlarm-host child that owns the per-node
|
||||||
|
/// <see cref="ScriptedAlarmEngine"/>, feeds it live tag values, and bridges its emissions onto the
|
||||||
|
/// OPC UA publish actor + the cluster <c>alerts</c> topic. Spawned in <see cref="PreStart"/>
|
||||||
|
/// alongside the VirtualTag host (requires both an OPC UA publish actor and a
|
||||||
|
/// <see cref="ScriptRootLogger"/>); receives <see cref="ScriptedAlarmHostActor.ApplyScriptedAlarms"/>
|
||||||
|
/// from <see cref="PushDesiredSubscriptions"/>.</summary>
|
||||||
|
private IActorRef? _scriptedAlarmHost;
|
||||||
|
|
||||||
private RevisionHash? _currentRevision;
|
private RevisionHash? _currentRevision;
|
||||||
private DeploymentId? _applyingDeploymentId;
|
private DeploymentId? _applyingDeploymentId;
|
||||||
|
|
||||||
@@ -103,6 +119,16 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
/// VirtualTag host instead of spawning a real <see cref="VirtualTagHostActor"/> child, so tests
|
/// VirtualTag host instead of spawning a real <see cref="VirtualTagHostActor"/> child, so tests
|
||||||
/// can intercept the <see cref="VirtualTagHostActor.ApplyVirtualTags"/> message. Null in
|
/// can intercept the <see cref="VirtualTagHostActor.ApplyVirtualTags"/> message. Null in
|
||||||
/// production (the real host is spawned).</param>
|
/// production (the real host is spawned).</param>
|
||||||
|
/// <param name="loggerFactory">Optional logger factory used to create the
|
||||||
|
/// <see cref="EfAlarmConditionStateStore"/>'s logger when spawning the ScriptedAlarm host;
|
||||||
|
/// defaults to <see cref="NullLoggerFactory"/> when not provided.</param>
|
||||||
|
/// <param name="scriptRootLogger">Optional root script logger required to spawn the ScriptedAlarm
|
||||||
|
/// host (the engine + its script logging hang off it). When null the ScriptedAlarm host is left
|
||||||
|
/// unspawned — the graceful dev/None-deployment path.</param>
|
||||||
|
/// <param name="scriptedAlarmHostOverride">Test seam: when supplied, this actor is used as the
|
||||||
|
/// ScriptedAlarm host instead of spawning a real <see cref="ScriptedAlarmHostActor"/> child, so
|
||||||
|
/// tests can intercept the <see cref="ScriptedAlarmHostActor.ApplyScriptedAlarms"/> message. Null
|
||||||
|
/// in production (the real host is spawned).</param>
|
||||||
public static Props Props(
|
public static Props Props(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
||||||
CommonsNodeId localNode,
|
CommonsNodeId localNode,
|
||||||
@@ -113,10 +139,14 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
IActorRef? opcUaPublishActor = null,
|
IActorRef? opcUaPublishActor = null,
|
||||||
IDriverHealthPublisher? healthPublisher = null,
|
IDriverHealthPublisher? healthPublisher = null,
|
||||||
IVirtualTagEvaluator? virtualTagEvaluator = null,
|
IVirtualTagEvaluator? virtualTagEvaluator = null,
|
||||||
IActorRef? virtualTagHostOverride = null) =>
|
IActorRef? virtualTagHostOverride = null,
|
||||||
|
ILoggerFactory? loggerFactory = null,
|
||||||
|
ScriptRootLogger? scriptRootLogger = null,
|
||||||
|
IActorRef? scriptedAlarmHostOverride = null) =>
|
||||||
Akka.Actor.Props.Create(() => new DriverHostActor(
|
Akka.Actor.Props.Create(() => new DriverHostActor(
|
||||||
dbFactory, localNode, coordinator, driverFactory, localRoles, dependencyMux, opcUaPublishActor,
|
dbFactory, localNode, coordinator, driverFactory, localRoles, dependencyMux, opcUaPublishActor,
|
||||||
healthPublisher, virtualTagEvaluator, virtualTagHostOverride));
|
healthPublisher, virtualTagEvaluator, virtualTagHostOverride,
|
||||||
|
loggerFactory, scriptRootLogger, scriptedAlarmHostOverride));
|
||||||
|
|
||||||
/// <summary>Initializes a new DriverHostActor with the specified dependencies.</summary>
|
/// <summary>Initializes a new DriverHostActor with the specified dependencies.</summary>
|
||||||
/// <param name="dbFactory">Database context factory for configuration database access.</param>
|
/// <param name="dbFactory">Database context factory for configuration database access.</param>
|
||||||
@@ -131,6 +161,12 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
/// defaults to <see cref="NullVirtualTagEvaluator"/>.</param>
|
/// defaults to <see cref="NullVirtualTagEvaluator"/>.</param>
|
||||||
/// <param name="virtualTagHostOverride">Test seam: when supplied, used as the VirtualTag host
|
/// <param name="virtualTagHostOverride">Test seam: when supplied, used as the VirtualTag host
|
||||||
/// instead of spawning a real <see cref="VirtualTagHostActor"/> child.</param>
|
/// instead of spawning a real <see cref="VirtualTagHostActor"/> child.</param>
|
||||||
|
/// <param name="loggerFactory">Optional logger factory used to create the
|
||||||
|
/// <see cref="EfAlarmConditionStateStore"/>'s logger; defaults to <see cref="NullLoggerFactory"/>.</param>
|
||||||
|
/// <param name="scriptRootLogger">Optional root script logger required to spawn the ScriptedAlarm
|
||||||
|
/// host; when null the host is left unspawned.</param>
|
||||||
|
/// <param name="scriptedAlarmHostOverride">Test seam: when supplied, used as the ScriptedAlarm host
|
||||||
|
/// instead of spawning a real <see cref="ScriptedAlarmHostActor"/> child.</param>
|
||||||
public DriverHostActor(
|
public DriverHostActor(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
||||||
CommonsNodeId localNode,
|
CommonsNodeId localNode,
|
||||||
@@ -141,7 +177,10 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
IActorRef? opcUaPublishActor = null,
|
IActorRef? opcUaPublishActor = null,
|
||||||
IDriverHealthPublisher? healthPublisher = null,
|
IDriverHealthPublisher? healthPublisher = null,
|
||||||
IVirtualTagEvaluator? virtualTagEvaluator = null,
|
IVirtualTagEvaluator? virtualTagEvaluator = null,
|
||||||
IActorRef? virtualTagHostOverride = null)
|
IActorRef? virtualTagHostOverride = null,
|
||||||
|
ILoggerFactory? loggerFactory = null,
|
||||||
|
ScriptRootLogger? scriptRootLogger = null,
|
||||||
|
IActorRef? scriptedAlarmHostOverride = null)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_localNode = localNode;
|
_localNode = localNode;
|
||||||
@@ -153,6 +192,9 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
_healthPublisher = healthPublisher ?? NullDriverHealthPublisher.Instance;
|
_healthPublisher = healthPublisher ?? NullDriverHealthPublisher.Instance;
|
||||||
_virtualTagEvaluator = virtualTagEvaluator ?? NullVirtualTagEvaluator.Instance;
|
_virtualTagEvaluator = virtualTagEvaluator ?? NullVirtualTagEvaluator.Instance;
|
||||||
_virtualTagHostOverride = virtualTagHostOverride;
|
_virtualTagHostOverride = virtualTagHostOverride;
|
||||||
|
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
|
_scriptRootLogger = scriptRootLogger;
|
||||||
|
_scriptedAlarmHostOverride = scriptedAlarmHostOverride;
|
||||||
|
|
||||||
// Default behavior is Steady — PreStart may flip to Stale or replay an orphan apply.
|
// Default behavior is Steady — PreStart may flip to Stale or replay an orphan apply.
|
||||||
Become(Steady);
|
Become(Steady);
|
||||||
@@ -168,6 +210,9 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
// Spawn the VirtualTag host BEFORE Bootstrap so the bootstrap-restore path (which routes
|
// Spawn the VirtualTag host BEFORE Bootstrap so the bootstrap-restore path (which routes
|
||||||
// through PushDesiredSubscriptions and Tells ApplyVirtualTags) has a live host to target.
|
// through PushDesiredSubscriptions and Tells ApplyVirtualTags) has a live host to target.
|
||||||
SpawnVirtualTagHost();
|
SpawnVirtualTagHost();
|
||||||
|
// Same rationale for the ScriptedAlarm host — the bootstrap-restore path Tells it
|
||||||
|
// ApplyScriptedAlarms through the same PushDesiredSubscriptions pass.
|
||||||
|
SpawnScriptedAlarmHost();
|
||||||
Bootstrap();
|
Bootstrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +244,46 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
"virtual-tag-host");
|
"virtual-tag-host");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawns the single <see cref="ScriptedAlarmHostActor"/> child that owns the per-node
|
||||||
|
/// <see cref="ScriptedAlarmEngine"/>, feeds it live tag values from the dependency mux, and
|
||||||
|
/// bridges its emissions onto the OPC UA publish actor + the cluster <c>alerts</c> topic. A
|
||||||
|
/// test-supplied <c>scriptedAlarmHostOverride</c> short-circuits the spawn so a probe can
|
||||||
|
/// intercept <see cref="ScriptedAlarmHostActor.ApplyScriptedAlarms"/>. The real host needs
|
||||||
|
/// both a non-null <see cref="_opcUaPublishActor"/> (the emission sink) and a non-null
|
||||||
|
/// <see cref="_scriptRootLogger"/> (the engine + its script logging hang off it); when either
|
||||||
|
/// is missing (legacy ControlPlane test harnesses, dev/None deployments) the host is left
|
||||||
|
/// null and ApplyScriptedAlarms becomes a no-op. The engine is built around a fresh
|
||||||
|
/// <see cref="DependencyMuxTagUpstreamSource"/> + an <see cref="EfAlarmConditionStateStore"/>;
|
||||||
|
/// the host (spawned as a child) owns + disposes the engine in its PostStop, so it stops with
|
||||||
|
/// the driver host.
|
||||||
|
/// </summary>
|
||||||
|
private void SpawnScriptedAlarmHost()
|
||||||
|
{
|
||||||
|
if (_scriptedAlarmHostOverride is not null)
|
||||||
|
{
|
||||||
|
_scriptedAlarmHost = _scriptedAlarmHostOverride;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_opcUaPublishActor is null || _scriptRootLogger is null)
|
||||||
|
{
|
||||||
|
_log.Debug(
|
||||||
|
"DriverHost {Node}: skipping ScriptedAlarm host spawn (no publish actor / root logger)",
|
||||||
|
_localNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var upstream = new DependencyMuxTagUpstreamSource();
|
||||||
|
var store = new EfAlarmConditionStateStore(
|
||||||
|
_dbFactory, _loggerFactory.CreateLogger<EfAlarmConditionStateStore>());
|
||||||
|
var engine = new ScriptedAlarmEngine(
|
||||||
|
upstream, store, new ScriptLoggerFactory(_scriptRootLogger.Logger), _scriptRootLogger.Logger);
|
||||||
|
_scriptedAlarmHost = Context.ActorOf(
|
||||||
|
ScriptedAlarmHostActor.Props(_opcUaPublishActor, _dependencyMux, upstream, engine),
|
||||||
|
"scripted-alarm-host");
|
||||||
|
}
|
||||||
|
|
||||||
private void Bootstrap()
|
private void Bootstrap()
|
||||||
{
|
{
|
||||||
// Read the most-recent NodeDeploymentState for this node; if it's Applied, jump
|
// Read the most-recent NodeDeploymentState for this node; if it's Applied, jump
|
||||||
@@ -535,6 +620,17 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
|||||||
_log.Info("DriverHost {Node}: applied {Count} Equipment VirtualTag(s) to the VirtualTag host",
|
_log.Info("DriverHost {Node}: applied {Count} Equipment VirtualTag(s) to the VirtualTag host",
|
||||||
_localNode, composition.EquipmentVirtualTags.Count);
|
_localNode, composition.EquipmentVirtualTags.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same pass for Equipment-namespace ScriptedAlarms: hand the plans to the host so it
|
||||||
|
// (re)loads its engine + re-registers mux interest for the union of dependency refs. Covers
|
||||||
|
// both the fresh-apply and bootstrap-restore paths (both call this method); the Stale-recovery
|
||||||
|
// path deliberately does not, matching driver + VirtualTag recovery.
|
||||||
|
_scriptedAlarmHost?.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(composition.EquipmentScriptedAlarms));
|
||||||
|
if (composition.EquipmentScriptedAlarms.Count > 0)
|
||||||
|
{
|
||||||
|
_log.Info("DriverHost {Node}: applied {Count} Equipment ScriptedAlarm(s) to the ScriptedAlarm host",
|
||||||
|
_localNode, composition.EquipmentScriptedAlarms.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpawnChild(DriverInstanceSpec spec)
|
private void SpawnChild(DriverInstanceSpec spec)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|||||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
|
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
||||||
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Health;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Health;
|
||||||
@@ -90,6 +91,10 @@ public static class ServiceCollectionExtensions
|
|||||||
var serviceLevel = resolver.GetService<IServiceLevelPublisher>() ?? NullServiceLevelPublisher.Instance;
|
var serviceLevel = resolver.GetService<IServiceLevelPublisher>() ?? NullServiceLevelPublisher.Instance;
|
||||||
var loggerFactory = resolver.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
var loggerFactory = resolver.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||||
var healthPublisher = resolver.GetService<IDriverHealthPublisher>() ?? NullDriverHealthPublisher.Instance;
|
var healthPublisher = resolver.GetService<IDriverHealthPublisher>() ?? NullDriverHealthPublisher.Instance;
|
||||||
|
// Root script logger backs the ScriptedAlarm host's engine + script logging. Registered in
|
||||||
|
// Host DI inside the hasDriver block; may be absent in some role configs / test harnesses,
|
||||||
|
// in which case the DriverHostActor gracefully skips spawning the ScriptedAlarm host.
|
||||||
|
var scriptRootLogger = resolver.GetService<ScriptRootLogger>();
|
||||||
// Production evaluator is the Host's RoslynVirtualTagEvaluator (registered as
|
// Production evaluator is the Host's RoslynVirtualTagEvaluator (registered as
|
||||||
// IVirtualTagEvaluator); fall back to the null evaluator for test harnesses that don't
|
// IVirtualTagEvaluator); fall back to the null evaluator for test harnesses that don't
|
||||||
// register one (VirtualTagActor children then evaluate to nothing).
|
// register one (VirtualTagActor children then evaluate to nothing).
|
||||||
@@ -131,7 +136,9 @@ public static class ServiceCollectionExtensions
|
|||||||
dependencyMux: mux,
|
dependencyMux: mux,
|
||||||
opcUaPublishActor: publishActor,
|
opcUaPublishActor: publishActor,
|
||||||
healthPublisher: healthPublisher,
|
healthPublisher: healthPublisher,
|
||||||
virtualTagEvaluator: virtualTagEvaluator),
|
virtualTagEvaluator: virtualTagEvaluator,
|
||||||
|
loggerFactory: loggerFactory,
|
||||||
|
scriptRootLogger: scriptRootLogger),
|
||||||
DriverHostActorName);
|
DriverHostActorName);
|
||||||
registry.Register<DriverHostActorKey>(driverHost);
|
registry.Register<DriverHostActorKey>(driverHost);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@@ -7,6 +8,7 @@ using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
|||||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Runtime.ScriptedAlarms;
|
||||||
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
||||||
@@ -126,6 +128,84 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase
|
|||||||
.Status.ShouldBe(NodeDeploymentStatus.Applied);
|
.Status.ShouldBe(NodeDeploymentStatus.Applied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Fresh apply: dispatching a deployment whose artifact carries one Equipment
|
||||||
|
/// ScriptedAlarm forwards a <see cref="ScriptedAlarmHostActor.ApplyScriptedAlarms"/> carrying that
|
||||||
|
/// plan to the injected ScriptedAlarm host (via the <c>scriptedAlarmHostOverride</c> seam, mirroring
|
||||||
|
/// the VirtualTag-host wiring).</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Apply_forwards_EquipmentScriptedAlarms_to_scripted_alarm_host()
|
||||||
|
{
|
||||||
|
var db = NewInMemoryDbFactory();
|
||||||
|
var deploymentId = SeedDeploymentWithScriptedAlarm(db, RevA);
|
||||||
|
|
||||||
|
var coordinator = CreateTestProbe();
|
||||||
|
var alarmHost = CreateTestProbe();
|
||||||
|
var actor = Sys.ActorOf(DriverHostActor.Props(
|
||||||
|
db, TestNode, coordinator.Ref,
|
||||||
|
localRoles: new HashSet<string> { "driver" },
|
||||||
|
scriptedAlarmHostOverride: alarmHost.Ref));
|
||||||
|
|
||||||
|
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||||
|
|
||||||
|
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||||
|
|
||||||
|
var apply = alarmHost.ExpectMsg<ScriptedAlarmHostActor.ApplyScriptedAlarms>(TimeSpan.FromSeconds(5));
|
||||||
|
var plan = apply.Plans.ShouldHaveSingleItem();
|
||||||
|
plan.ScriptedAlarmId.ShouldBe("al-1");
|
||||||
|
plan.EquipmentId.ShouldBe("eq-1");
|
||||||
|
plan.Name.ShouldBe("Overheat");
|
||||||
|
plan.PredicateSource.ShouldContain("ctx.GetTag");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DeploymentId SeedDeploymentWithScriptedAlarm(
|
||||||
|
IDbContextFactory<OtOpcUaConfigDbContext> db, RevisionHash rev)
|
||||||
|
{
|
||||||
|
// Artifact carries a ScriptedAlarm joined (by PredicateScriptId) to its predicate Script —
|
||||||
|
// the same shape ConfigComposer emits and DeploymentArtifact.ParseComposition decodes into
|
||||||
|
// composition.EquipmentScriptedAlarms.
|
||||||
|
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
|
||||||
|
{
|
||||||
|
Scripts = new[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ScriptId = "scr-1",
|
||||||
|
SourceCode = "return System.Convert.ToDouble(ctx.GetTag(\"Mach1.Temp\").Value) > 80;",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ScriptedAlarms = new[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ScriptedAlarmId = "al-1",
|
||||||
|
EquipmentId = "eq-1",
|
||||||
|
Name = "Overheat",
|
||||||
|
AlarmType = "LimitAlarm",
|
||||||
|
Severity = 700,
|
||||||
|
MessageTemplate = "Machine 1 hot",
|
||||||
|
PredicateScriptId = "scr-1",
|
||||||
|
HistorizeToAveva = true,
|
||||||
|
Retain = true,
|
||||||
|
Enabled = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var id = DeploymentId.NewId();
|
||||||
|
using var ctx = db.CreateDbContext();
|
||||||
|
ctx.Deployments.Add(new Configuration.Entities.Deployment
|
||||||
|
{
|
||||||
|
DeploymentId = id.Value,
|
||||||
|
RevisionHash = rev.Value,
|
||||||
|
Status = DeploymentStatus.Sealed,
|
||||||
|
CreatedBy = "test",
|
||||||
|
SealedAtUtc = DateTime.UtcNow,
|
||||||
|
ArtifactBlob = artifact,
|
||||||
|
});
|
||||||
|
ctx.SaveChanges();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
private static DeploymentId SeedDeployment(
|
private static DeploymentId SeedDeployment(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
IDbContextFactory<OtOpcUaConfigDbContext> db,
|
||||||
RevisionHash rev,
|
RevisionHash rev,
|
||||||
|
|||||||
Reference in New Issue
Block a user