Phase 8: Production readiness — failover tests, security hardening, sandboxing, deployment docs
- WP-1-3: Central/site failover + dual-node recovery tests (17 tests) - WP-4: Performance testing framework for target scale (7 tests) - WP-5: Security hardening (LDAPS, JWT key length, no secrets in logs) (11 tests) - WP-6: Script sandboxing adversarial tests (28 tests, all forbidden APIs) - WP-7: Recovery drill test scaffolds (5 tests) - WP-8: Observability validation (structured logs, correlation IDs, metrics) (6 tests) - WP-9: Message contract compatibility (forward/backward compat) (18 tests) - WP-10: Deployment packaging (installation guide, production checklist, topology) - WP-11: Operational runbooks (failover, troubleshooting, maintenance) 92 new tests, all passing. Zero warnings.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Akka.Actor;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.Commons.Messages.Instance;
|
||||
using ScadaLink.Commons.Messages.ScriptExecution;
|
||||
|
||||
@@ -13,6 +14,13 @@ namespace ScadaLink.SiteRuntime.Scripts;
|
||||
/// Instance.CallScript("scriptName", params)
|
||||
/// Scripts.CallShared("scriptName", params)
|
||||
///
|
||||
/// WP-13 (Phase 7): Integration surface APIs:
|
||||
/// ExternalSystem.Call("systemName", "methodName", params)
|
||||
/// ExternalSystem.CachedCall("systemName", "methodName", params)
|
||||
/// Database.Connection("name")
|
||||
/// Database.CachedWrite("name", "sql", params)
|
||||
/// Notify.To("listName").Send("subject", "message")
|
||||
///
|
||||
/// WP-20: Recursion Limit — call depth tracked and enforced.
|
||||
/// </summary>
|
||||
public class ScriptRuntimeContext
|
||||
@@ -26,6 +34,21 @@ public class ScriptRuntimeContext
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _instanceName;
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: External system client for ExternalSystem.Call/CachedCall.
|
||||
/// </summary>
|
||||
private readonly IExternalSystemClient? _externalSystemClient;
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Database gateway for Database.Connection/CachedWrite.
|
||||
/// </summary>
|
||||
private readonly IDatabaseGateway? _databaseGateway;
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Notification delivery for Notify.To().Send().
|
||||
/// </summary>
|
||||
private readonly INotificationDeliveryService? _notificationService;
|
||||
|
||||
public ScriptRuntimeContext(
|
||||
IActorRef instanceActor,
|
||||
IActorRef self,
|
||||
@@ -34,7 +57,10 @@ public class ScriptRuntimeContext
|
||||
int maxCallDepth,
|
||||
TimeSpan askTimeout,
|
||||
string instanceName,
|
||||
ILogger logger)
|
||||
ILogger logger,
|
||||
IExternalSystemClient? externalSystemClient = null,
|
||||
IDatabaseGateway? databaseGateway = null,
|
||||
INotificationDeliveryService? notificationService = null)
|
||||
{
|
||||
_instanceActor = instanceActor;
|
||||
_self = self;
|
||||
@@ -44,6 +70,9 @@ public class ScriptRuntimeContext
|
||||
_askTimeout = askTimeout;
|
||||
_instanceName = instanceName;
|
||||
_logger = logger;
|
||||
_externalSystemClient = externalSystemClient;
|
||||
_databaseGateway = databaseGateway;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +152,26 @@ public class ScriptRuntimeContext
|
||||
/// </summary>
|
||||
public ScriptCallHelper Scripts => new(_sharedScriptLibrary, this, _currentCallDepth, _maxCallDepth, _logger);
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Provides access to external system calls.
|
||||
/// ExternalSystem.Call("systemName", "methodName", params)
|
||||
/// ExternalSystem.CachedCall("systemName", "methodName", params)
|
||||
/// </summary>
|
||||
public ExternalSystemHelper ExternalSystem => new(_externalSystemClient, _instanceName, _logger);
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Provides access to database operations.
|
||||
/// Database.Connection("name")
|
||||
/// Database.CachedWrite("name", "sql", params)
|
||||
/// </summary>
|
||||
public DatabaseHelper Database => new(_databaseGateway, _instanceName, _logger);
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Provides access to notification delivery.
|
||||
/// Notify.To("listName").Send("subject", "message")
|
||||
/// </summary>
|
||||
public NotifyHelper Notify => new(_notificationService, _instanceName, _logger);
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for Scripts.CallShared() syntax.
|
||||
/// </summary>
|
||||
@@ -169,4 +218,136 @@ public class ScriptRuntimeContext
|
||||
return await _library.ExecuteAsync(scriptName, _context, parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Helper for ExternalSystem.Call/CachedCall syntax.
|
||||
/// </summary>
|
||||
public class ExternalSystemHelper
|
||||
{
|
||||
private readonly IExternalSystemClient? _client;
|
||||
private readonly string _instanceName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal ExternalSystemHelper(IExternalSystemClient? client, string instanceName, ILogger logger)
|
||||
{
|
||||
_client = client;
|
||||
_instanceName = instanceName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ExternalCallResult> Call(
|
||||
string systemName,
|
||||
string methodName,
|
||||
IReadOnlyDictionary<string, object?>? parameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_client == null)
|
||||
throw new InvalidOperationException("External system client not available");
|
||||
|
||||
return await _client.CallAsync(systemName, methodName, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<ExternalCallResult> CachedCall(
|
||||
string systemName,
|
||||
string methodName,
|
||||
IReadOnlyDictionary<string, object?>? parameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_client == null)
|
||||
throw new InvalidOperationException("External system client not available");
|
||||
|
||||
return await _client.CachedCallAsync(systemName, methodName, parameters, _instanceName, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Helper for Database.Connection/CachedWrite syntax.
|
||||
/// </summary>
|
||||
public class DatabaseHelper
|
||||
{
|
||||
private readonly IDatabaseGateway? _gateway;
|
||||
private readonly string _instanceName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal DatabaseHelper(IDatabaseGateway? gateway, string instanceName, ILogger logger)
|
||||
{
|
||||
_gateway = gateway;
|
||||
_instanceName = instanceName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<System.Data.Common.DbConnection> Connection(
|
||||
string name,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_gateway == null)
|
||||
throw new InvalidOperationException("Database gateway not available");
|
||||
|
||||
return await _gateway.GetConnectionAsync(name, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task CachedWrite(
|
||||
string name,
|
||||
string sql,
|
||||
IReadOnlyDictionary<string, object?>? parameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_gateway == null)
|
||||
throw new InvalidOperationException("Database gateway not available");
|
||||
|
||||
await _gateway.CachedWriteAsync(name, sql, parameters, _instanceName, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Helper for Notify.To("listName").Send("subject", "message") syntax.
|
||||
/// </summary>
|
||||
public class NotifyHelper
|
||||
{
|
||||
private readonly INotificationDeliveryService? _service;
|
||||
private readonly string _instanceName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal NotifyHelper(INotificationDeliveryService? service, string instanceName, ILogger logger)
|
||||
{
|
||||
_service = service;
|
||||
_instanceName = instanceName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public NotifyTarget To(string listName)
|
||||
{
|
||||
return new NotifyTarget(listName, _service, _instanceName, _logger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-13: Target for Notify.To("listName").Send("subject", "message").
|
||||
/// </summary>
|
||||
public class NotifyTarget
|
||||
{
|
||||
private readonly string _listName;
|
||||
private readonly INotificationDeliveryService? _service;
|
||||
private readonly string _instanceName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal NotifyTarget(string listName, INotificationDeliveryService? service, string instanceName, ILogger logger)
|
||||
{
|
||||
_listName = listName;
|
||||
_service = service;
|
||||
_instanceName = instanceName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<NotificationResult> Send(
|
||||
string subject,
|
||||
string message,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_service == null)
|
||||
throw new InvalidOperationException("Notification service not available");
|
||||
|
||||
return await _service.SendAsync(_listName, subject, message, _instanceName, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user