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:
Joseph Doherty
2026-03-28 13:32:17 -04:00
parent a3c2d9b243
commit a55153d7d5
27 changed files with 1475 additions and 248 deletions

View File

@@ -104,6 +104,47 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration
Log.Warning("Only the 'None' security profile is configured — transport security is disabled");
}
// Redundancy
if (config.OpcUa.ApplicationUri != null)
Log.Information("OpcUa.ApplicationUri={ApplicationUri}", config.OpcUa.ApplicationUri);
Log.Information("Redundancy.Enabled={Enabled}, Mode={Mode}, Role={Role}, ServiceLevelBase={ServiceLevelBase}",
config.Redundancy.Enabled, config.Redundancy.Mode, config.Redundancy.Role, config.Redundancy.ServiceLevelBase);
if (config.Redundancy.ServerUris.Count > 0)
Log.Information("Redundancy.ServerUris=[{ServerUris}]", string.Join(", ", config.Redundancy.ServerUris));
if (config.Redundancy.Enabled)
{
if (string.IsNullOrWhiteSpace(config.OpcUa.ApplicationUri))
{
Log.Error("OpcUa.ApplicationUri must be set when redundancy is enabled — each instance needs a unique identity");
valid = false;
}
if (config.Redundancy.ServerUris.Count < 2)
{
Log.Warning("Redundancy.ServerUris contains fewer than 2 entries — a redundant set typically has at least 2 servers");
}
if (config.OpcUa.ApplicationUri != null && !config.Redundancy.ServerUris.Contains(config.OpcUa.ApplicationUri))
{
Log.Warning("Local OpcUa.ApplicationUri '{ApplicationUri}' is not listed in Redundancy.ServerUris", config.OpcUa.ApplicationUri);
}
var mode = RedundancyModeResolver.Resolve(config.Redundancy.Mode, true);
if (mode == Opc.Ua.RedundancySupport.None)
{
Log.Warning("Redundancy is enabled but Mode '{Mode}' is not recognized — will fall back to None", config.Redundancy.Mode);
}
}
if (config.Redundancy.ServiceLevelBase < 1 || config.Redundancy.ServiceLevelBase > 255)
{
Log.Error("Redundancy.ServiceLevelBase must be between 1 and 255");
valid = false;
}
Log.Information("=== Configuration {Status} ===", valid ? "Valid" : "INVALID");
return valid;
}