sidecar: wire IAlarmEventWriter into Program.cs (PR C.2) #411
@@ -87,6 +87,10 @@ if ($InstallWonderwareHistorian) {
|
|||||||
"OTOPCUA_ALLOWED_SID=$sid"
|
"OTOPCUA_ALLOWED_SID=$sid"
|
||||||
"OTOPCUA_HISTORIAN_SECRET=$HistorianSharedSecret"
|
"OTOPCUA_HISTORIAN_SECRET=$HistorianSharedSecret"
|
||||||
"OTOPCUA_HISTORIAN_ENABLED=true"
|
"OTOPCUA_HISTORIAN_ENABLED=true"
|
||||||
|
# Default-on when the historian sidecar is installed; flip to false for a
|
||||||
|
# read-only deployment that still loads aahClientManaged for reads but
|
||||||
|
# rejects WriteAlarmEvents frames.
|
||||||
|
"OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true"
|
||||||
"OTOPCUA_HISTORIAN_SERVER=$HistorianServer"
|
"OTOPCUA_HISTORIAN_SERVER=$HistorianServer"
|
||||||
"OTOPCUA_HISTORIAN_PORT=$HistorianPort"
|
"OTOPCUA_HISTORIAN_PORT=$HistorianPort"
|
||||||
) -join "`0"
|
) -join "`0"
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ public static class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var historian = BuildHistorian();
|
using var historian = BuildHistorian();
|
||||||
var handler = new HistorianFrameHandler(historian, Log.Logger);
|
var alarmWriter = BuildAlarmWriter();
|
||||||
|
var handler = new HistorianFrameHandler(historian, Log.Logger, alarmWriter);
|
||||||
using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger);
|
using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger);
|
||||||
|
|
||||||
Log.Information("Wonderware historian sidecar serving — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);
|
Log.Information("Wonderware historian sidecar serving — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);
|
||||||
@@ -107,4 +108,46 @@ public static class Program
|
|||||||
var raw = Environment.GetEnvironmentVariable(envName);
|
var raw = Environment.GetEnvironmentVariable(envName);
|
||||||
return int.TryParse(raw, out var parsed) ? parsed : defaultValue;
|
return int.TryParse(raw, out var parsed) ? parsed : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the alarm-event writer when the alarm-write toggle is on, otherwise
|
||||||
|
/// returns <c>null</c> so <see cref="HistorianFrameHandler"/> falls back to the
|
||||||
|
/// "not configured" reply for any incoming <c>WriteAlarmEvents</c> frame.
|
||||||
|
/// Default is <c>true</c> when <c>OTOPCUA_HISTORIAN_ENABLED=true</c>; explicitly
|
||||||
|
/// set <c>OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=false</c> to keep a read-only
|
||||||
|
/// deployment that still loads the SDK for reads.
|
||||||
|
/// </summary>
|
||||||
|
internal static IAlarmEventWriter? BuildAlarmWriter()
|
||||||
|
{
|
||||||
|
var raw = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED");
|
||||||
|
var enabled = string.IsNullOrWhiteSpace(raw)
|
||||||
|
? true
|
||||||
|
: !string.Equals(raw, "false", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
Log.Information("Alarm-event writer disabled (OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=false); historian sidecar will reject WriteAlarmEvents frames.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg = BuildAlarmWriterConfig();
|
||||||
|
var backend = new SdkAlarmHistorianWriteBackend(cfg);
|
||||||
|
Log.Information("Alarm-event writer enabled — backend=SdkAlarmHistorianWriteBackend server={Server}", cfg.ServerName);
|
||||||
|
return new AahClientManagedAlarmEventWriter(backend);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HistorianConfiguration BuildAlarmWriterConfig()
|
||||||
|
{
|
||||||
|
return new HistorianConfiguration
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
ServerName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVER") ?? "localhost",
|
||||||
|
Port = TryParseInt("OTOPCUA_HISTORIAN_PORT", 32568),
|
||||||
|
IntegratedSecurity = !string.Equals(Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_INTEGRATED"), "false", StringComparison.OrdinalIgnoreCase),
|
||||||
|
UserName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_USER"),
|
||||||
|
Password = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_PASS"),
|
||||||
|
CommandTimeoutSeconds = TryParseInt("OTOPCUA_HISTORIAN_TIMEOUT_SEC", 30),
|
||||||
|
FailureCooldownSeconds = TryParseInt("OTOPCUA_HISTORIAN_COOLDOWN_SEC", 60),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PR C.2 — pins the env-var contract that gates whether the sidecar boots an
|
||||||
|
/// alarm-event writer. Default-on (when the historian itself is enabled) so a
|
||||||
|
/// fresh deploy picks up the writer without a service-config edit; explicit
|
||||||
|
/// <c>false</c> opts a read-only deployment out.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class ProgramAlarmWriterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void BuildAlarmWriter_returns_writer_when_env_unset()
|
||||||
|
{
|
||||||
|
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", null);
|
||||||
|
|
||||||
|
var writer = Program.BuildAlarmWriter();
|
||||||
|
|
||||||
|
writer.ShouldNotBeNull();
|
||||||
|
writer.ShouldBeOfType<AahClientManagedAlarmEventWriter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("true")]
|
||||||
|
[InlineData("True")]
|
||||||
|
[InlineData("TRUE")]
|
||||||
|
public void BuildAlarmWriter_returns_writer_when_env_explicitly_true(string value)
|
||||||
|
{
|
||||||
|
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value);
|
||||||
|
|
||||||
|
var writer = Program.BuildAlarmWriter();
|
||||||
|
|
||||||
|
writer.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("false")]
|
||||||
|
[InlineData("False")]
|
||||||
|
[InlineData("FALSE")]
|
||||||
|
public void BuildAlarmWriter_returns_null_when_env_false(string value)
|
||||||
|
{
|
||||||
|
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", value);
|
||||||
|
|
||||||
|
var writer = Program.BuildAlarmWriter();
|
||||||
|
|
||||||
|
writer.ShouldBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildAlarmWriter_treats_unrecognized_value_as_enabled()
|
||||||
|
{
|
||||||
|
// Anything other than the literal "false" (case-insensitive) keeps the writer
|
||||||
|
// wired — fail-open under accidental misconfiguration so an alarm-write deploy
|
||||||
|
// doesn't silently lose alarms because of a typo.
|
||||||
|
using var _ = ScopedEnv("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED", "yes");
|
||||||
|
|
||||||
|
var writer = Program.BuildAlarmWriter();
|
||||||
|
|
||||||
|
writer.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IDisposable ScopedEnv(string name, string? value)
|
||||||
|
{
|
||||||
|
var prior = Environment.GetEnvironmentVariable(name);
|
||||||
|
Environment.SetEnvironmentVariable(name, value);
|
||||||
|
return new DisposableAction(() => Environment.SetEnvironmentVariable(name, prior));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DisposableAction : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Action _action;
|
||||||
|
public DisposableAction(Action action) { _action = action; }
|
||||||
|
public void Dispose() => _action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user