feat(dcl): extend CreateConnectionCommand with backup config and failover retry count

Update CreateConnectionCommand to carry PrimaryConnectionDetails,
BackupConnectionDetails, and FailoverRetryCount. Update all callers:
DataConnectionManagerActor, DataConnectionActor, DeploymentManagerActor,
FlatteningService, and ConnectionConfig. The actor stores both configs
but continues using primary only — failover logic comes in Task 3.
This commit is contained in:
Joseph Doherty
2026-03-22 08:24:39 -04:00
parent 04af03980e
commit 46304678da
7 changed files with 42 additions and 10 deletions

View File

@@ -7,4 +7,6 @@ namespace ScadaLink.Commons.Messages.DataConnection;
public record CreateConnectionCommand( public record CreateConnectionCommand(
string ConnectionName, string ConnectionName,
string ProtocolType, string ProtocolType,
IDictionary<string, string> ConnectionDetails); IDictionary<string, string> PrimaryConnectionDetails,
IDictionary<string, string>? BackupConnectionDetails = null,
int FailoverRetryCount = 3);

View File

@@ -33,6 +33,8 @@ public sealed record ConnectionConfig
{ {
public string Protocol { get; init; } = string.Empty; public string Protocol { get; init; } = string.Empty;
public string? ConfigurationJson { get; init; } public string? ConfigurationJson { get; init; }
public string? BackupConfigurationJson { get; init; }
public int FailoverRetryCount { get; init; } = 3;
} }
/// <summary> /// <summary>

View File

@@ -61,6 +61,9 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
private int _resolvedTags; private int _resolvedTags;
private readonly IDictionary<string, string> _connectionDetails; private readonly IDictionary<string, string> _connectionDetails;
private readonly IDictionary<string, string> _primaryConfig;
private readonly IDictionary<string, string>? _backupConfig;
private readonly int _failoverRetryCount;
/// <summary> /// <summary>
/// Captured Self reference for use from non-actor threads (event handlers, callbacks). /// Captured Self reference for use from non-actor threads (event handlers, callbacks).
@@ -73,13 +76,18 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
IDataConnection adapter, IDataConnection adapter,
DataConnectionOptions options, DataConnectionOptions options,
ISiteHealthCollector healthCollector, ISiteHealthCollector healthCollector,
IDictionary<string, string>? connectionDetails = null) IDictionary<string, string>? primaryConfig = null,
IDictionary<string, string>? backupConfig = null,
int failoverRetryCount = 3)
{ {
_connectionName = connectionName; _connectionName = connectionName;
_adapter = adapter; _adapter = adapter;
_options = options; _options = options;
_healthCollector = healthCollector; _healthCollector = healthCollector;
_connectionDetails = connectionDetails ?? new Dictionary<string, string>(); _primaryConfig = primaryConfig ?? new Dictionary<string, string>();
_backupConfig = backupConfig;
_failoverRetryCount = failoverRetryCount;
_connectionDetails = _primaryConfig;
} }
protected override void PreStart() protected override void PreStart()

View File

@@ -45,10 +45,13 @@ public class DataConnectionManagerActor : ReceiveActor
} }
// WP-34: Factory creates the correct adapter based on protocol type // 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( 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) // Sanitize name for Akka actor path (replace spaces and invalid chars)
var actorName = new string(command.ConnectionName var actorName = new string(command.ConnectionName

View File

@@ -422,7 +422,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
if (_createdConnections.Contains(name)) if (_createdConnections.Contains(name))
continue; continue;
var connectionDetails = new Dictionary<string, string>(); var primaryDetails = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(connConfig.ConfigurationJson)) if (!string.IsNullOrEmpty(connConfig.ConfigurationJson))
{ {
try try
@@ -431,14 +431,29 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
using var doc = System.Text.Json.JsonDocument.Parse(connConfig.ConfigurationJson); using var doc = System.Text.Json.JsonDocument.Parse(connConfig.ConfigurationJson);
foreach (var prop in doc.RootElement.EnumerateObject()) foreach (var prop in doc.RootElement.EnumerateObject())
{ {
connectionDetails[prop.Name] = prop.Value.ToString(); primaryDetails[prop.Name] = prop.Value.ToString();
} }
} }
catch { /* Ignore parse errors */ } catch { /* Ignore parse errors */ }
} }
Dictionary<string, string>? backupDetails = null;
if (!string.IsNullOrEmpty(connConfig.BackupConfigurationJson))
{
try
{
backupDetails = new Dictionary<string, string>();
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( _dclManager.Tell(new Commons.Messages.DataConnection.CreateConnectionCommand(
name, connConfig.Protocol, connectionDetails)); name, connConfig.Protocol, primaryDetails, backupDetails, connConfig.FailoverRetryCount));
_createdConnections.Add(name); _createdConnections.Add(name);
_logger.LogInformation( _logger.LogInformation(

View File

@@ -90,7 +90,9 @@ public class FlatteningService
connections[attr.BoundDataConnectionName] = new ConnectionConfig connections[attr.BoundDataConnectionName] = new ConnectionConfig
{ {
Protocol = conn.Protocol, Protocol = conn.Protocol,
ConfigurationJson = conn.PrimaryConfiguration ConfigurationJson = conn.PrimaryConfiguration,
BackupConfigurationJson = conn.BackupConfiguration,
FailoverRetryCount = conn.FailoverRetryCount
}; };
} }
} }

View File

@@ -70,7 +70,7 @@ public class DataConnectionManagerActorTests : TestKit
new DataConnectionManagerActor(_mockFactory, _options, _mockHealthCollector))); new DataConnectionManagerActor(_mockFactory, _options, _mockHealthCollector)));
manager.Tell(new CreateConnectionCommand( manager.Tell(new CreateConnectionCommand(
"conn1", "OpcUa", new Dictionary<string, string>())); "conn1", "OpcUa", new Dictionary<string, string>(), null, 3));
// Factory should have been called // Factory should have been called
AwaitCondition(() => AwaitCondition(() =>