222 lines
7.6 KiB
C#
222 lines
7.6 KiB
C#
using Akka.Actor;
|
|
using Akka.Cluster.Tools.Client;
|
|
using Akka.Event;
|
|
using ScadaLink.Commons.Messages.Artifacts;
|
|
using ScadaLink.Commons.Messages.DebugView;
|
|
using ScadaLink.Commons.Messages.Deployment;
|
|
using ScadaLink.Commons.Messages.Health;
|
|
using ScadaLink.Commons.Messages.InboundApi;
|
|
using ScadaLink.Commons.Messages.Integration;
|
|
using ScadaLink.Commons.Messages.Lifecycle;
|
|
using ScadaLink.Commons.Messages.RemoteQuery;
|
|
|
|
namespace ScadaLink.Communication.Actors;
|
|
|
|
/// <summary>
|
|
/// Site-side actor that receives messages from central via ClusterClient and routes
|
|
/// them to the appropriate local actors. Also sends heartbeats and health reports
|
|
/// to central via the registered ClusterClient.
|
|
///
|
|
/// WP-4: Routes all 8 message patterns to local handlers.
|
|
/// </summary>
|
|
public class SiteCommunicationActor : ReceiveActor, IWithTimers
|
|
{
|
|
private readonly ILoggingAdapter _log = Context.GetLogger();
|
|
private readonly string _siteId;
|
|
private readonly CommunicationOptions _options;
|
|
|
|
/// <summary>
|
|
/// Reference to the local Deployment Manager singleton proxy.
|
|
/// </summary>
|
|
private readonly IActorRef _deploymentManagerProxy;
|
|
|
|
/// <summary>
|
|
/// ClusterClient reference for sending messages to the central cluster.
|
|
/// Set via RegisterCentralClient message.
|
|
/// </summary>
|
|
private IActorRef? _centralClient;
|
|
|
|
/// <summary>
|
|
/// Local actor references for routing specific message patterns.
|
|
/// Populated via registration messages.
|
|
/// </summary>
|
|
private IActorRef? _eventLogHandler;
|
|
private IActorRef? _parkedMessageHandler;
|
|
private IActorRef? _integrationHandler;
|
|
private IActorRef? _artifactHandler;
|
|
|
|
public ITimerScheduler Timers { get; set; } = null!;
|
|
|
|
public SiteCommunicationActor(
|
|
string siteId,
|
|
CommunicationOptions options,
|
|
IActorRef deploymentManagerProxy)
|
|
{
|
|
_siteId = siteId;
|
|
_options = options;
|
|
_deploymentManagerProxy = deploymentManagerProxy;
|
|
|
|
// Registration
|
|
Receive<RegisterCentralClient>(msg =>
|
|
{
|
|
_centralClient = msg.Client;
|
|
_log.Info("Registered central ClusterClient");
|
|
});
|
|
Receive<RegisterLocalHandler>(HandleRegisterLocalHandler);
|
|
|
|
// Pattern 1: Instance Deployment — forward to Deployment Manager
|
|
Receive<DeployInstanceCommand>(msg =>
|
|
{
|
|
_log.Debug("Routing DeployInstanceCommand for {0} to DeploymentManager", msg.InstanceUniqueName);
|
|
_deploymentManagerProxy.Forward(msg);
|
|
});
|
|
|
|
// Pattern 2: Lifecycle — forward to Deployment Manager
|
|
Receive<DisableInstanceCommand>(msg => _deploymentManagerProxy.Forward(msg));
|
|
Receive<EnableInstanceCommand>(msg => _deploymentManagerProxy.Forward(msg));
|
|
Receive<DeleteInstanceCommand>(msg => _deploymentManagerProxy.Forward(msg));
|
|
|
|
// Pattern 3: Artifact Deployment — forward to artifact handler if registered
|
|
Receive<DeployArtifactsCommand>(msg =>
|
|
{
|
|
if (_artifactHandler != null)
|
|
_artifactHandler.Forward(msg);
|
|
else
|
|
{
|
|
_log.Warning("No artifact handler registered, replying with failure");
|
|
Sender.Tell(new ArtifactDeploymentResponse(
|
|
msg.DeploymentId, _siteId, false, "Artifact handler not available", DateTimeOffset.UtcNow));
|
|
}
|
|
});
|
|
|
|
// Pattern 4: Integration Routing — forward to integration handler
|
|
Receive<IntegrationCallRequest>(msg =>
|
|
{
|
|
if (_integrationHandler != null)
|
|
_integrationHandler.Forward(msg);
|
|
else
|
|
{
|
|
Sender.Tell(new IntegrationCallResponse(
|
|
msg.CorrelationId, _siteId, false, null, "Integration handler not available", DateTimeOffset.UtcNow));
|
|
}
|
|
});
|
|
|
|
// Pattern 5: Debug View — forward to Deployment Manager (which routes to Instance Actor)
|
|
Receive<SubscribeDebugViewRequest>(msg => _deploymentManagerProxy.Forward(msg));
|
|
Receive<UnsubscribeDebugViewRequest>(msg => _deploymentManagerProxy.Forward(msg));
|
|
|
|
// Pattern 6a: Debug Snapshot (one-shot) — forward to Deployment Manager
|
|
Receive<DebugSnapshotRequest>(msg => _deploymentManagerProxy.Forward(msg));
|
|
|
|
// Inbound API Route.To().Call() — forward to Deployment Manager for instance routing
|
|
Receive<RouteToCallRequest>(msg => _deploymentManagerProxy.Forward(msg));
|
|
|
|
// Pattern 7: Remote Queries
|
|
Receive<EventLogQueryRequest>(msg =>
|
|
{
|
|
if (_eventLogHandler != null)
|
|
_eventLogHandler.Forward(msg);
|
|
else
|
|
{
|
|
Sender.Tell(new EventLogQueryResponse(
|
|
msg.CorrelationId, _siteId, [], null, false, false,
|
|
"Event log handler not available", DateTimeOffset.UtcNow));
|
|
}
|
|
});
|
|
|
|
Receive<ParkedMessageQueryRequest>(msg =>
|
|
{
|
|
if (_parkedMessageHandler != null)
|
|
_parkedMessageHandler.Forward(msg);
|
|
else
|
|
{
|
|
Sender.Tell(new ParkedMessageQueryResponse(
|
|
msg.CorrelationId, _siteId, [], 0, msg.PageNumber, msg.PageSize, false,
|
|
"Parked message handler not available", DateTimeOffset.UtcNow));
|
|
}
|
|
});
|
|
|
|
// Internal: send heartbeat tick
|
|
Receive<SendHeartbeat>(_ => SendHeartbeatToCentral());
|
|
|
|
// Internal: forward health report to central
|
|
Receive<SiteHealthReport>(msg =>
|
|
{
|
|
_centralClient?.Tell(
|
|
new ClusterClient.Send("/user/central-communication", msg), Self);
|
|
});
|
|
|
|
}
|
|
|
|
protected override void PreStart()
|
|
{
|
|
_log.Info("SiteCommunicationActor started for site {0}", _siteId);
|
|
|
|
// Schedule periodic heartbeat to central
|
|
Timers.StartPeriodicTimer(
|
|
"heartbeat",
|
|
new SendHeartbeat(),
|
|
TimeSpan.FromSeconds(1), // initial delay
|
|
_options.TransportHeartbeatInterval);
|
|
}
|
|
|
|
private void HandleRegisterLocalHandler(RegisterLocalHandler msg)
|
|
{
|
|
switch (msg.HandlerType)
|
|
{
|
|
case LocalHandlerType.EventLog:
|
|
_eventLogHandler = msg.Handler;
|
|
break;
|
|
case LocalHandlerType.ParkedMessages:
|
|
_parkedMessageHandler = msg.Handler;
|
|
break;
|
|
case LocalHandlerType.Integration:
|
|
_integrationHandler = msg.Handler;
|
|
break;
|
|
case LocalHandlerType.Artifacts:
|
|
_artifactHandler = msg.Handler;
|
|
break;
|
|
}
|
|
|
|
_log.Info("Registered local handler for {0}", msg.HandlerType);
|
|
}
|
|
|
|
private void SendHeartbeatToCentral()
|
|
{
|
|
if (_centralClient == null)
|
|
return;
|
|
|
|
var hostname = Environment.MachineName;
|
|
var heartbeat = new HeartbeatMessage(
|
|
_siteId,
|
|
hostname,
|
|
IsActive: true,
|
|
DateTimeOffset.UtcNow);
|
|
|
|
_centralClient.Tell(
|
|
new ClusterClient.Send("/user/central-communication", heartbeat), Self);
|
|
}
|
|
|
|
// ── Internal messages ──
|
|
|
|
internal record SendHeartbeat;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command to register a ClusterClient for communicating with the central cluster.
|
|
/// </summary>
|
|
public record RegisterCentralClient(IActorRef Client);
|
|
|
|
/// <summary>
|
|
/// Command to register a local actor as a handler for a specific message pattern.
|
|
/// </summary>
|
|
public record RegisterLocalHandler(LocalHandlerType HandlerType, IActorRef Handler);
|
|
|
|
public enum LocalHandlerType
|
|
{
|
|
EventLog,
|
|
ParkedMessages,
|
|
Integration,
|
|
Artifacts
|
|
}
|