using System.Collections.Immutable; using Akka.Actor; using Akka.Cluster.Tools.Client; using Akka.Configuration; using ScadaLink.Commons.Messages.Management; namespace ScadaLink.CLI; public class ClusterConnection : IAsyncDisposable { private ActorSystem? _system; private IActorRef? _clusterClient; public async Task ConnectAsync(IReadOnlyList contactPoints, TimeSpan timeout) { var seedNodes = string.Join(",", contactPoints.Select(cp => $"\"{cp}\"")); var config = ConfigurationFactory.ParseString($@" akka {{ actor.provider = remote remote.dot-netty.tcp {{ hostname = ""127.0.0.1"" port = 0 }} }} "); _system = ActorSystem.Create("scadalink-cli", config); var initialContacts = contactPoints .Select(cp => $"{cp}/system/receptionist") .Select(path => ActorPath.Parse(path)) .ToImmutableHashSet(); var clientSettings = ClusterClientSettings.Create(_system) .WithInitialContacts(initialContacts); _clusterClient = _system.ActorOf(ClusterClient.Props(clientSettings), "cluster-client"); // Wait for connection by sending a ping // ClusterClient doesn't have a direct "connected" signal, so we rely on the first Ask succeeding await Task.CompletedTask; } public async Task AskManagementAsync(ManagementEnvelope envelope, TimeSpan timeout) { if (_clusterClient == null) throw new InvalidOperationException("Not connected"); var response = await _clusterClient.Ask( new ClusterClient.Send("/user/management", envelope), timeout); return response; } public async ValueTask DisposeAsync() { if (_system != null) { await CoordinatedShutdown.Get(_system).Run(CoordinatedShutdown.ClrExitReason.Instance); _system = null; } } }