alarms-over-gateway: full pipeline (wnwrap consumer + dispatcher + IPC + auto-subscribe + ack-by-name + live smoke) #118

Merged
dohertj2 merged 16 commits from docs/alarm-client-wm-app-finding into main 2026-05-01 12:31:29 -04:00
12 changed files with 1390 additions and 421 deletions
Showing only changes of commit 4e02927f01 - Show all commits
File diff suppressed because it is too large Load Diff
@@ -92,6 +92,7 @@ message MxCommand {
UnsubscribeAlarmsCommand unsubscribe_alarms = 35;
AcknowledgeAlarmCommand acknowledge_alarm_command = 36;
QueryActiveAlarmsCommand query_active_alarms_command = 37;
AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38;
PingCommand ping = 100;
GetSessionStateCommand get_session_state = 101;
GetWorkerInfoCommand get_worker_info = 102;
@@ -130,6 +131,7 @@ enum MxCommandKind {
MX_COMMAND_KIND_UNSUBSCRIBE_ALARMS = 26;
MX_COMMAND_KIND_ACKNOWLEDGE_ALARM = 27;
MX_COMMAND_KIND_QUERY_ACTIVE_ALARMS = 28;
MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29;
MX_COMMAND_KIND_PING = 100;
MX_COMMAND_KIND_GET_SESSION_STATE = 101;
MX_COMMAND_KIND_GET_WORKER_INFO = 102;
@@ -307,6 +309,27 @@ message QueryActiveAlarmsCommand {
string alarm_filter_prefix = 1;
}
// Acknowledge a single alarm by its (name, provider, group) tuple. Used
// when the public RPC's AlarmFullReference (Provider!Group.Tag) cannot
// be resolved to a GUID directly. The worker invokes
// wwAlarmConsumerClass.AlarmAckByName which reaches the same alarm
// history path as AlarmAckByGUID.
message AcknowledgeAlarmByNameCommand {
// Tag/alarm name (e.g. "TestMachine_001.TestAlarm001"). Tag itself
// may contain dots; the gateway-side parser splits on the first dot
// after the '!' separator.
string alarm_name = 1;
// AVEVA alarm-provider name (literal "Galaxy" for ArchestrA Galaxies).
string provider_name = 2;
// Area/group name (e.g. "TestArea").
string group_name = 3;
string comment = 4;
string operator_user = 5;
string operator_node = 6;
string operator_domain = 7;
string operator_full_name = 8;
}
message UnsubscribeBulkCommand {
int32 server_handle = 1;
repeated int32 item_handles = 2;
@@ -45,6 +45,39 @@ public sealed class WorkerAlarmRpcDispatcher : IAlarmRpcDispatcher
this.timeProvider = timeProvider ?? TimeProvider.System;
}
/// <summary>
/// Parse a full alarm reference of the form <c>Provider!Group.Tag</c>
/// into its components. Convention: the first <c>!</c> separates
/// provider from <c>Group.Tag</c>; the first <c>.</c> after the
/// <c>!</c> separates group from tag (the tag itself may contain
/// more dots — e.g. <c>TestMachine_001.TestAlarm001</c>).
/// </summary>
/// <returns>true on a well-formed reference; false otherwise.</returns>
public static bool TryParseAlarmReference(
string? reference,
out string providerName,
out string groupName,
out string alarmName)
{
providerName = string.Empty;
groupName = string.Empty;
alarmName = string.Empty;
if (string.IsNullOrWhiteSpace(reference)) return false;
int bang = reference!.IndexOf('!');
if (bang <= 0 || bang == reference.Length - 1) return false;
string left = reference[..bang];
string right = reference[(bang + 1)..];
int dot = right.IndexOf('.');
if (dot <= 0 || dot == right.Length - 1) return false;
providerName = left;
groupName = right[..dot];
alarmName = right[(dot + 1)..];
return true;
}
/// <inheritdoc />
public async Task<AcknowledgeAlarmReply> AcknowledgeAsync(
AcknowledgeAlarmRequest request,
@@ -64,11 +97,58 @@ public sealed class WorkerAlarmRpcDispatcher : IAlarmRpcDispatcher
};
}
if (!System.Guid.TryParse(request.AlarmFullReference, out System.Guid guid))
WorkerCommand workerCommand;
if (System.Guid.TryParse(request.AlarmFullReference, out System.Guid guid))
{
workerCommand = new WorkerCommand
{
Command = new MxCommand
{
Kind = MxCommandKind.AcknowledgeAlarm,
AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
{
AlarmGuid = guid.ToString(),
Comment = request.Comment ?? string.Empty,
OperatorUser = request.OperatorUser ?? string.Empty,
// Operator node/domain/full-name are not on the public
// RPC surface today; pass empty strings so the worker
// honours the existing AcknowledgeAlarmCommand schema.
OperatorNode = string.Empty,
OperatorDomain = string.Empty,
OperatorFullName = string.Empty,
},
},
EnqueueTimestamp = Timestamp.FromDateTimeOffset(timeProvider.GetUtcNow()),
};
}
else if (TryParseAlarmReference(
request.AlarmFullReference,
out string providerName,
out string groupName,
out string alarmName))
{
workerCommand = new WorkerCommand
{
Command = new MxCommand
{
Kind = MxCommandKind.AcknowledgeAlarmByName,
AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
{
AlarmName = alarmName,
ProviderName = providerName,
GroupName = groupName,
Comment = request.Comment ?? string.Empty,
OperatorUser = request.OperatorUser ?? string.Empty,
OperatorNode = string.Empty,
OperatorDomain = string.Empty,
OperatorFullName = string.Empty,
},
},
EnqueueTimestamp = Timestamp.FromDateTimeOffset(timeProvider.GetUtcNow()),
};
}
else
{
// Reference→GUID lookup not yet implemented. Surface a clear
// diagnostic so client teams can plumb the reference parser
// when the worker AlarmAckByName command lands.
return new AcknowledgeAlarmReply
{
SessionId = request.SessionId,
@@ -76,33 +156,12 @@ public sealed class WorkerAlarmRpcDispatcher : IAlarmRpcDispatcher
ProtocolStatus = new ProtocolStatus
{
Code = ProtocolStatusCode.InvalidRequest,
Message = "AlarmFullReference must currently be a canonical GUID; reference→GUID lookup is pending the AlarmAckByName worker command.",
Message = "AlarmFullReference must be a canonical GUID or 'Provider!Group.Tag' format.",
},
DiagnosticMessage = $"AcknowledgeAlarm received non-GUID reference '{request.AlarmFullReference}'.",
DiagnosticMessage = $"AcknowledgeAlarm received unrecognized reference '{request.AlarmFullReference}'.",
};
}
WorkerCommand workerCommand = new WorkerCommand
{
Command = new MxCommand
{
Kind = MxCommandKind.AcknowledgeAlarm,
AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
{
AlarmGuid = guid.ToString(),
Comment = request.Comment ?? string.Empty,
OperatorUser = request.OperatorUser ?? string.Empty,
// Operator node/domain/full-name are not on the public
// RPC surface today; pass empty strings so the worker
// honours the existing AcknowledgeAlarmCommand schema.
OperatorNode = string.Empty,
OperatorDomain = string.Empty,
OperatorFullName = string.Empty,
},
},
EnqueueTimestamp = Timestamp.FromDateTimeOffset(timeProvider.GetUtcNow()),
};
WorkerCommandReply workerReply = await session.InvokeAsync(workerCommand, cancellationToken)
.ConfigureAwait(false);
@@ -36,33 +36,6 @@ public sealed class WorkerAlarmRpcDispatcherTests
Assert.Equal(ProtocolStatusCode.SessionNotFound, reply.ProtocolStatus.Code);
}
[Fact]
public async Task AcknowledgeAsync_returns_invalid_request_when_reference_is_not_a_guid()
{
SessionRegistry registry = new SessionRegistry();
FakeAlarmWorkerClient worker = new FakeAlarmWorkerClient();
GatewaySession session = NewSession("s1");
session.AttachWorkerClient(worker);
session.MarkReady();
registry.TryAdd(session);
WorkerAlarmRpcDispatcher dispatcher = new WorkerAlarmRpcDispatcher(registry);
AcknowledgeAlarmReply reply = await dispatcher.AcknowledgeAsync(
new AcknowledgeAlarmRequest
{
SessionId = "s1",
ClientCorrelationId = "c1",
AlarmFullReference = "Galaxy!Area.Tag", // not a GUID
Comment = "x",
OperatorUser = "u",
},
CancellationToken.None);
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
Assert.Equal(0, worker.InvokeCount); // dispatcher short-circuits before the IPC.
}
[Fact]
public async Task AcknowledgeAsync_forwards_guid_and_returns_native_status()
{
@@ -148,6 +121,107 @@ public sealed class WorkerAlarmRpcDispatcherTests
Assert.Contains("-123", reply.DiagnosticMessage);
}
[Theory]
[InlineData("Galaxy!TestArea.TestMachine_001.TestAlarm001", "Galaxy", "TestArea", "TestMachine_001.TestAlarm001")]
[InlineData("Galaxy!Area.Tag", "Galaxy", "Area", "Tag")]
[InlineData("Provider!Group.Tag.With.Dots", "Provider", "Group", "Tag.With.Dots")]
public void TryParseAlarmReference_decomposes_provider_group_tag(
string reference, string expectedProvider, string expectedGroup, string expectedName)
{
Assert.True(WorkerAlarmRpcDispatcher.TryParseAlarmReference(
reference, out string provider, out string group, out string name));
Assert.Equal(expectedProvider, provider);
Assert.Equal(expectedGroup, group);
Assert.Equal(expectedName, name);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
[InlineData("no-bang-here")]
[InlineData("!Group.Tag")] // empty provider
[InlineData("Galaxy!")] // bang at end
[InlineData("Galaxy!Group")] // missing dot
[InlineData("Galaxy!.Tag")] // empty group
[InlineData("Galaxy!Group.")] // empty tag
public void TryParseAlarmReference_rejects_malformed_references(string? reference)
{
Assert.False(WorkerAlarmRpcDispatcher.TryParseAlarmReference(
reference, out _, out _, out _));
}
[Fact]
public async Task AcknowledgeAsync_routes_provider_group_tag_via_AckByName()
{
SessionRegistry registry = new SessionRegistry();
AcknowledgeAlarmByNameCommand? observed = null;
FakeAlarmWorkerClient worker = new FakeAlarmWorkerClient
{
ReplyFactory = command =>
{
Assert.Equal(MxCommandKind.AcknowledgeAlarmByName, command.Command.Kind);
observed = command.Command.AcknowledgeAlarmByNameCommand;
return new MxCommandReply
{
Kind = MxCommandKind.AcknowledgeAlarmByName,
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok, Message = "OK" },
Hresult = 0,
AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload { NativeStatus = 0 },
};
},
};
GatewaySession session = NewSession("s1");
session.AttachWorkerClient(worker);
session.MarkReady();
registry.TryAdd(session);
WorkerAlarmRpcDispatcher dispatcher = new WorkerAlarmRpcDispatcher(registry);
AcknowledgeAlarmReply reply = await dispatcher.AcknowledgeAsync(
new AcknowledgeAlarmRequest
{
SessionId = "s1",
ClientCorrelationId = "c1",
AlarmFullReference = "Galaxy!TestArea.TestMachine_001.TestAlarm001",
Comment = "ack-by-name",
OperatorUser = "bob",
},
CancellationToken.None);
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.NotNull(observed);
Assert.Equal("TestMachine_001.TestAlarm001", observed!.AlarmName);
Assert.Equal("Galaxy", observed.ProviderName);
Assert.Equal("TestArea", observed.GroupName);
Assert.Equal("bob", observed.OperatorUser);
Assert.Equal("ack-by-name", observed.Comment);
}
[Fact]
public async Task AcknowledgeAsync_returns_invalid_request_for_unparseable_reference()
{
SessionRegistry registry = new SessionRegistry();
FakeAlarmWorkerClient worker = new FakeAlarmWorkerClient();
GatewaySession session = NewSession("s1");
session.AttachWorkerClient(worker);
session.MarkReady();
registry.TryAdd(session);
WorkerAlarmRpcDispatcher dispatcher = new WorkerAlarmRpcDispatcher(registry);
AcknowledgeAlarmReply reply = await dispatcher.AcknowledgeAsync(
new AcknowledgeAlarmRequest
{
SessionId = "s1",
AlarmFullReference = "no-bang-no-dot",
},
CancellationToken.None);
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
Assert.Equal(0, worker.InvokeCount);
}
[Fact]
public async Task QueryActiveAlarmsAsync_yields_each_snapshot_from_payload()
{
@@ -164,6 +164,62 @@ public sealed class AlarmCommandExecutorTests
Assert.Contains("-123", reply.DiagnosticMessage);
}
[Fact]
public void AcknowledgeAlarmByName_routes_tuple_to_handler()
{
FakeAlarmHandler handler = new FakeAlarmHandler { AcknowledgeReturn = 0 };
MxAccessCommandExecutor executor = NewExecutor(handler);
StaCommand command = new StaCommand(
SessionId, CorrelationId,
new MxCommand
{
Kind = MxCommandKind.AcknowledgeAlarmByName,
AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
{
AlarmName = "TestMachine_001.TestAlarm001",
ProviderName = "Galaxy",
GroupName = "TestArea",
Comment = "ack",
OperatorUser = "alice",
},
});
MxCommandReply reply = executor.Execute(command);
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.NotNull(reply.AcknowledgeAlarm);
Assert.Equal(0, reply.AcknowledgeAlarm.NativeStatus);
Assert.NotNull(handler.LastAckByNameTuple);
Assert.Equal("TestMachine_001.TestAlarm001", handler.LastAckByNameTuple!.Value.Name);
Assert.Equal("Galaxy", handler.LastAckByNameTuple!.Value.Provider);
Assert.Equal("TestArea", handler.LastAckByNameTuple!.Value.Group);
Assert.Equal("alice", handler.LastAckOperatorName);
}
[Fact]
public void AcknowledgeAlarmByName_with_empty_name_returns_invalid_request()
{
MxAccessCommandExecutor executor = NewExecutor(new FakeAlarmHandler());
StaCommand command = new StaCommand(
SessionId, CorrelationId,
new MxCommand
{
Kind = MxCommandKind.AcknowledgeAlarmByName,
AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
{
AlarmName = " ",
ProviderName = "Galaxy",
GroupName = "TestArea",
},
});
MxCommandReply reply = executor.Execute(command);
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
}
[Fact]
public void QueryActiveAlarms_returns_payload_with_snapshots()
{
@@ -373,6 +429,18 @@ public sealed class AlarmCommandExecutorTests
return AcknowledgeReturn;
}
public int AcknowledgeByName(
string alarmName, string providerName, string groupName,
string comment, string operatorUser, string operatorNode,
string operatorDomain, string operatorFullName)
{
LastAckByNameTuple = (alarmName, providerName, groupName);
LastAckOperatorName = operatorUser;
return AcknowledgeReturn;
}
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
public IReadOnlyList<ActiveAlarmSnapshot> QueryActive(string? alarmFilterPrefix)
{
LastFilterPrefix = alarmFilterPrefix;
@@ -222,6 +222,18 @@ public sealed class AlarmCommandHandlerTests
return AcknowledgeReturn;
}
public int AcknowledgeByName(
string alarmName, string providerName, string groupName,
string ackComment, string ackOperatorName, string ackOperatorNode,
string ackOperatorDomain, string ackOperatorFullName)
{
LastAckByNameTuple = (alarmName, providerName, groupName);
LastAckOperatorName = ackOperatorName;
return AcknowledgeReturn;
}
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms() => SnapshotResult;
public void Dispose()
@@ -158,6 +158,32 @@ public sealed class AlarmDispatcherTests
Assert.Equal("Alice Smith", consumer.LastAckOperatorFullName);
}
[Fact]
public void AcknowledgeByName_forwards_to_consumer_with_full_tuple()
{
FakeAlarmConsumer consumer = new FakeAlarmConsumer { AcknowledgeReturn = 0 };
using AlarmDispatcher dispatcher = new AlarmDispatcher(
consumer,
new MxAccessAlarmEventSink(new MxAccessEventQueue(), new MxAccessEventMapper()),
SessionId);
int rc = dispatcher.AcknowledgeByName(
alarmName: "TestMachine_001.TestAlarm001",
providerName: "Galaxy",
groupName: "TestArea",
ackComment: "ack",
ackOperatorName: "alice",
ackOperatorNode: "WS",
ackOperatorDomain: "CORP",
ackOperatorFullName: "Alice Smith");
Assert.Equal(0, rc);
Assert.NotNull(consumer.LastAckByNameTuple);
Assert.Equal("TestMachine_001.TestAlarm001", consumer.LastAckByNameTuple!.Value.Name);
Assert.Equal("Galaxy", consumer.LastAckByNameTuple!.Value.Provider);
Assert.Equal("TestArea", consumer.LastAckByNameTuple!.Value.Group);
}
[Fact]
public void SnapshotActiveAlarms_maps_records_to_protos()
{
@@ -275,6 +301,18 @@ public sealed class AlarmDispatcherTests
return AcknowledgeReturn;
}
public int AcknowledgeByName(
string alarmName, string providerName, string groupName,
string ackComment, string ackOperatorName, string ackOperatorNode,
string ackOperatorDomain, string ackOperatorFullName)
{
LastAckByNameTuple = (alarmName, providerName, groupName);
LastAckOperatorName = ackOperatorName;
return AcknowledgeReturn;
}
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms()
{
return SnapshotResult;
@@ -120,6 +120,29 @@ public sealed class AlarmCommandHandler : IAlarmCommandHandler
operatorFullName ?? string.Empty);
}
/// <inheritdoc />
public int AcknowledgeByName(
string alarmName,
string providerName,
string groupName,
string comment,
string operatorUser,
string operatorNode,
string operatorDomain,
string operatorFullName)
{
AlarmDispatcher? d = GetDispatcherOrThrow();
return d.AcknowledgeByName(
alarmName ?? string.Empty,
providerName ?? string.Empty,
groupName ?? string.Empty,
comment ?? string.Empty,
operatorUser ?? string.Empty,
operatorNode ?? string.Empty,
operatorDomain ?? string.Empty,
operatorFullName ?? string.Empty);
}
/// <inheritdoc />
public IReadOnlyList<ActiveAlarmSnapshot> QueryActive(string? alarmFilterPrefix)
{
@@ -184,6 +207,20 @@ public interface IAlarmCommandHandler : IDisposable
string operatorDomain,
string operatorFullName);
/// <summary>
/// Acknowledge a single alarm by (name, provider, group) — used when
/// the caller has the human-readable reference but not the GUID.
/// </summary>
int AcknowledgeByName(
string alarmName,
string providerName,
string groupName,
string comment,
string operatorUser,
string operatorNode,
string operatorDomain,
string operatorFullName);
/// <summary>
/// Snapshot the currently-active alarm set, optionally scoped to a
/// prefix matched against <c>AlarmFullReference</c>.
@@ -92,6 +92,33 @@ public sealed class AlarmDispatcher : IDisposable
ackOperatorFullName);
}
/// <summary>
/// Acknowledge an alarm by its (name, provider, group) tuple.
/// Routes to the consumer's <c>AcknowledgeByName</c> path which
/// maps to <c>wwAlarmConsumerClass.AlarmAckByName</c>.
/// </summary>
public int AcknowledgeByName(
string alarmName,
string providerName,
string groupName,
string ackComment,
string ackOperatorName,
string ackOperatorNode,
string ackOperatorDomain,
string ackOperatorFullName)
{
if (disposed) throw new ObjectDisposedException(nameof(AlarmDispatcher));
return consumer.AcknowledgeByName(
alarmName,
providerName,
groupName,
ackComment,
ackOperatorName,
ackOperatorNode,
ackOperatorDomain,
ackOperatorFullName);
}
/// <summary>
/// Snapshot the currently-active alarm set as
/// <see cref="ActiveAlarmSnapshot"/> protos for the
@@ -61,6 +61,23 @@ public interface IMxAccessAlarmConsumer : IDisposable
string ackOperatorDomain,
string ackOperatorFullName);
/// <summary>
/// Acknowledge a single alarm by its (name, provider, group) tuple.
/// Reaches AVEVA's <c>AlarmAckByName</c> on
/// <c>wwAlarmConsumerClass</c>; same alarm-history outcome as
/// <see cref="AcknowledgeByGuid"/>, used when the caller has the
/// human-readable reference but not the canonical GUID.
/// </summary>
int AcknowledgeByName(
string alarmName,
string providerName,
string groupName,
string ackComment,
string ackOperatorName,
string ackOperatorNode,
string ackOperatorDomain,
string ackOperatorFullName);
/// <summary>
/// Returns the consumer's most recently parsed snapshot of currently
/// active alarms. Used by the gateway's QueryActiveAlarms (PR A.7)
@@ -83,6 +83,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
MxCommandKind.SubscribeAlarms => ExecuteSubscribeAlarms(command),
MxCommandKind.UnsubscribeAlarms => ExecuteUnsubscribeAlarms(command),
MxCommandKind.AcknowledgeAlarm => ExecuteAcknowledgeAlarm(command),
MxCommandKind.AcknowledgeAlarmByName => ExecuteAcknowledgeAlarmByName(command),
MxCommandKind.QueryActiveAlarms => ExecuteQueryActiveAlarms(command),
_ => CreateInvalidRequestReply(command, $"Unsupported MXAccess command kind {command.Kind}."),
};
@@ -402,6 +403,54 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
}
}
private MxCommandReply ExecuteAcknowledgeAlarmByName(StaCommand command)
{
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AcknowledgeAlarmByNameCommand)
{
return CreateInvalidRequestReply(command, "AcknowledgeAlarmByName command payload is required.");
}
if (alarmCommandHandler is null)
{
return CreateInvalidRequestReply(
command,
"AcknowledgeAlarmByName requires an alarm command handler; the worker was constructed without one.");
}
AcknowledgeAlarmByNameCommand payload = command.Command.AcknowledgeAlarmByNameCommand;
if (string.IsNullOrWhiteSpace(payload.AlarmName))
{
return CreateInvalidRequestReply(command, "AcknowledgeAlarmByName.alarm_name is required.");
}
try
{
int rc = alarmCommandHandler.AcknowledgeByName(
payload.AlarmName,
payload.ProviderName,
payload.GroupName,
payload.Comment,
payload.OperatorUser,
payload.OperatorNode,
payload.OperatorDomain,
payload.OperatorFullName);
MxCommandReply reply = CreateOkReply(command);
reply.Hresult = rc;
reply.AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload
{
NativeStatus = rc,
};
if (rc != 0)
{
reply.DiagnosticMessage = $"AVEVA AlarmAckByName returned non-zero status {rc}.";
}
return reply;
}
catch (Exception ex)
{
return CreateAlarmFailureReply(command, ex);
}
}
private MxCommandReply ExecuteQueryActiveAlarms(StaCommand command)
{
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.QueryActiveAlarmsCommand)
@@ -172,6 +172,33 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
szOprFullName: ackOperatorFullName ?? string.Empty);
}
/// <inheritdoc />
public int AcknowledgeByName(
string alarmName,
string providerName,
string groupName,
string ackComment,
string ackOperatorName,
string ackOperatorNode,
string ackOperatorDomain,
string ackOperatorFullName)
{
if (disposed) throw new ObjectDisposedException(nameof(WnWrapAlarmConsumer));
wwAlarmConsumerClass com = client
?? throw new ObjectDisposedException(nameof(WnWrapAlarmConsumer));
return com.AlarmAckByName(
szAlarmName: alarmName ?? string.Empty,
szProviderName: providerName ?? string.Empty,
szGroupName: groupName ?? string.Empty,
szComment: ackComment ?? string.Empty,
szOprName: ackOperatorName ?? string.Empty,
szNode: ackOperatorNode ?? string.Empty,
szDomainName: ackOperatorDomain ?? string.Empty,
szOprFullName: ackOperatorFullName ?? string.Empty);
}
/// <inheritdoc />
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms()
{