diff --git a/src/ScadaLink.Commons/Messages/DataConnection/CreateConnectionCommand.cs b/src/ScadaLink.Commons/Messages/DataConnection/CreateConnectionCommand.cs index 2026ae3..f5e0b77 100644 --- a/src/ScadaLink.Commons/Messages/DataConnection/CreateConnectionCommand.cs +++ b/src/ScadaLink.Commons/Messages/DataConnection/CreateConnectionCommand.cs @@ -7,4 +7,6 @@ namespace ScadaLink.Commons.Messages.DataConnection; public record CreateConnectionCommand( string ConnectionName, string ProtocolType, - IDictionary ConnectionDetails); + IDictionary PrimaryConnectionDetails, + IDictionary? BackupConnectionDetails = null, + int FailoverRetryCount = 3); diff --git a/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs b/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs index 16f8bec..8548af8 100644 --- a/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs +++ b/src/ScadaLink.Commons/Types/Flattening/FlattenedConfiguration.cs @@ -33,6 +33,8 @@ public sealed record ConnectionConfig { public string Protocol { get; init; } = string.Empty; public string? ConfigurationJson { get; init; } + public string? BackupConfigurationJson { get; init; } + public int FailoverRetryCount { get; init; } = 3; } /// diff --git a/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionActor.cs b/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionActor.cs index 37fa73a..4ed540f 100644 --- a/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionActor.cs +++ b/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionActor.cs @@ -61,6 +61,9 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers private int _resolvedTags; private readonly IDictionary _connectionDetails; + private readonly IDictionary _primaryConfig; + private readonly IDictionary? _backupConfig; + private readonly int _failoverRetryCount; /// /// Captured Self reference for use from non-actor threads (event handlers, callbacks). @@ -73,13 +76,18 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers IDataConnection adapter, DataConnectionOptions options, ISiteHealthCollector healthCollector, - IDictionary? connectionDetails = null) + IDictionary? primaryConfig = null, + IDictionary? backupConfig = null, + int failoverRetryCount = 3) { _connectionName = connectionName; _adapter = adapter; _options = options; _healthCollector = healthCollector; - _connectionDetails = connectionDetails ?? new Dictionary(); + _primaryConfig = primaryConfig ?? new Dictionary(); + _backupConfig = backupConfig; + _failoverRetryCount = failoverRetryCount; + _connectionDetails = _primaryConfig; } protected override void PreStart() diff --git a/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionManagerActor.cs b/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionManagerActor.cs index eacb5de..b7ee2ce 100644 --- a/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionManagerActor.cs +++ b/src/ScadaLink.DataConnectionLayer/Actors/DataConnectionManagerActor.cs @@ -45,10 +45,13 @@ public class DataConnectionManagerActor : ReceiveActor } // WP-34: Factory creates the correct adapter based on protocol type - var adapter = _factory.Create(command.ProtocolType, command.ConnectionDetails); + var adapter = _factory.Create(command.ProtocolType, command.PrimaryConnectionDetails); var props = Props.Create(() => new DataConnectionActor( - command.ConnectionName, adapter, _options, _healthCollector, command.ConnectionDetails)); + command.ConnectionName, adapter, _options, _healthCollector, + command.PrimaryConnectionDetails, + command.BackupConnectionDetails, + command.FailoverRetryCount)); // Sanitize name for Akka actor path (replace spaces and invalid chars) var actorName = new string(command.ConnectionName diff --git a/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs index 40e2b1f..65c7cee 100644 --- a/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs +++ b/src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs @@ -422,7 +422,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers if (_createdConnections.Contains(name)) continue; - var connectionDetails = new Dictionary(); + var primaryDetails = new Dictionary(); if (!string.IsNullOrEmpty(connConfig.ConfigurationJson)) { try @@ -431,14 +431,29 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers using var doc = System.Text.Json.JsonDocument.Parse(connConfig.ConfigurationJson); foreach (var prop in doc.RootElement.EnumerateObject()) { - connectionDetails[prop.Name] = prop.Value.ToString(); + primaryDetails[prop.Name] = prop.Value.ToString(); } } catch { /* Ignore parse errors */ } } + Dictionary? backupDetails = null; + if (!string.IsNullOrEmpty(connConfig.BackupConfigurationJson)) + { + try + { + backupDetails = new Dictionary(); + using var doc = System.Text.Json.JsonDocument.Parse(connConfig.BackupConfigurationJson); + foreach (var prop in doc.RootElement.EnumerateObject()) + { + backupDetails[prop.Name] = prop.Value.ToString(); + } + } + catch { backupDetails = null; /* Ignore parse errors */ } + } + _dclManager.Tell(new Commons.Messages.DataConnection.CreateConnectionCommand( - name, connConfig.Protocol, connectionDetails)); + name, connConfig.Protocol, primaryDetails, backupDetails, connConfig.FailoverRetryCount)); _createdConnections.Add(name); _logger.LogInformation( diff --git a/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs b/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs index 7a2596c..56355cd 100644 --- a/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs +++ b/src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs @@ -90,7 +90,9 @@ public class FlatteningService connections[attr.BoundDataConnectionName] = new ConnectionConfig { Protocol = conn.Protocol, - ConfigurationJson = conn.PrimaryConfiguration + ConfigurationJson = conn.PrimaryConfiguration, + BackupConfigurationJson = conn.BackupConfiguration, + FailoverRetryCount = conn.FailoverRetryCount }; } } diff --git a/tests/ScadaLink.DataConnectionLayer.Tests/DataConnectionManagerActorTests.cs b/tests/ScadaLink.DataConnectionLayer.Tests/DataConnectionManagerActorTests.cs index 5621882..7cec277 100644 --- a/tests/ScadaLink.DataConnectionLayer.Tests/DataConnectionManagerActorTests.cs +++ b/tests/ScadaLink.DataConnectionLayer.Tests/DataConnectionManagerActorTests.cs @@ -70,7 +70,7 @@ public class DataConnectionManagerActorTests : TestKit new DataConnectionManagerActor(_mockFactory, _options, _mockHealthCollector))); manager.Tell(new CreateConnectionCommand( - "conn1", "OpcUa", new Dictionary())); + "conn1", "OpcUa", new Dictionary(), null, 3)); // Factory should have been called AwaitCondition(() =>