Completes the Inbound API → site script call chain by adding RouteToCallRequest handlers in SiteCommunicationActor and DeploymentManagerActor. Also replaces the placeholder dispatch table in InboundScriptExecutor with Roslyn compilation of API method scripts at startup, enabling user-defined inbound API methods to call instance scripts across the cluster.
221 lines
7.6 KiB
C#
221 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
|
|
}
|