Files
lmxopcua/tools/opcuacli-dotnet/Commands/RedundancyCommand.cs
Joseph Doherty afd6c33d9d Add client-side failover to CLI tool for redundancy testing
All commands gain --failover-urls (-F) to specify alternate endpoints.
Short-lived commands try each URL in order on initial connect. The
subscribe command monitors KeepAlive and automatically reconnects to
the next available server, re-creating the subscription on failover.
Verified with live service start/stop: primary down triggers failover
to secondary, primary restart allows failback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:41:06 -04:00

76 lines
2.9 KiB
C#

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";
[CommandOption("failover-urls", 'F', Description = "Comma-separated failover endpoint URLs for redundancy")]
public string? FailoverUrls { get; init; }
public async ValueTask ExecuteAsync(IConsole console)
{
var urls = FailoverUrlParser.Parse(Url, FailoverUrls);
using var failover = new OpcUaFailoverHelper(urls, Username, Password, Security);
using var session = await failover.ConnectAsync();
// 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
}
}
}