Add configurable non-transparent OPC UA server redundancy
Separates ApplicationUri from namespace identity so each instance in a redundant pair has a unique server URI while sharing the same Galaxy namespace. Exposes RedundancySupport, ServerUriArray, and dynamic ServiceLevel through the standard OPC UA server object. ServiceLevel is computed from role (Primary/Secondary) and runtime health (MXAccess and DB connectivity). Adds CLI redundancy command, second deployed service instance, and 31 new tests including paired-server integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
tools/opcuacli-dotnet/Commands/RedundancyCommand.cs
Normal file
70
tools/opcuacli-dotnet/Commands/RedundancyCommand.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Infrastructure;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
|
||||
namespace OpcUaCli.Commands;
|
||||
|
||||
[Command("redundancy", Description = "Read redundancy state from an OPC UA server")]
|
||||
public class RedundancyCommand : ICommand
|
||||
{
|
||||
[CommandOption("url", 'u', Description = "OPC UA server endpoint URL", IsRequired = true)]
|
||||
public string Url { get; init; } = default!;
|
||||
|
||||
[CommandOption("username", 'U', Description = "Username for authentication")]
|
||||
public string? Username { get; init; }
|
||||
|
||||
[CommandOption("password", 'P', Description = "Password for authentication")]
|
||||
public string? Password { get; init; }
|
||||
|
||||
[CommandOption("security", 'S', Description = "Transport security: none, sign, encrypt (default: none)")]
|
||||
public string Security { get; init; } = "none";
|
||||
|
||||
public async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
using var session = await OpcUaHelper.ConnectAsync(Url, Username, Password, Security);
|
||||
|
||||
// Read RedundancySupport
|
||||
var redundancySupportValue = await session.ReadValueAsync(VariableIds.Server_ServerRedundancy_RedundancySupport);
|
||||
var redundancyMode = (RedundancySupport)(int)redundancySupportValue.Value;
|
||||
await console.Output.WriteLineAsync($"Redundancy Mode: {redundancyMode}");
|
||||
|
||||
// Read ServiceLevel
|
||||
var serviceLevelValue = await session.ReadValueAsync(VariableIds.Server_ServiceLevel);
|
||||
var serviceLevel = (byte)serviceLevelValue.Value;
|
||||
await console.Output.WriteLineAsync($"Service Level: {serviceLevel}");
|
||||
|
||||
// Read ServerUriArray (only present for non-transparent redundancy)
|
||||
try
|
||||
{
|
||||
var serverUriArrayValue = await session.ReadValueAsync(VariableIds.Server_ServerRedundancy_ServerUriArray);
|
||||
if (serverUriArrayValue.Value is string[] uris && uris.Length > 0)
|
||||
{
|
||||
await console.Output.WriteLineAsync("Server URIs:");
|
||||
foreach (var uri in uris)
|
||||
{
|
||||
await console.Output.WriteLineAsync($" - {uri}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ServerUriArray may not be present when RedundancySupport is None
|
||||
}
|
||||
|
||||
// Read ServerArray for the local server's ApplicationUri
|
||||
try
|
||||
{
|
||||
var serverArrayValue = await session.ReadValueAsync(VariableIds.Server_ServerArray);
|
||||
if (serverArrayValue.Value is string[] serverArray && serverArray.Length > 0)
|
||||
{
|
||||
await console.Output.WriteLineAsync($"Application URI: {serverArray[0]}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Informational only
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user