product advisories, stella router improval, tests streghthening
This commit is contained in:
@@ -35,6 +35,61 @@ public sealed class GatewayTransportOptions
|
||||
public GatewayTcpTransportOptions Tcp { get; set; } = new();
|
||||
|
||||
public GatewayTlsTransportOptions Tls { get; set; } = new();
|
||||
|
||||
public GatewayMessagingTransportOptions Messaging { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class GatewayMessagingTransportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether messaging (Valkey) transport is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Valkey connection string (e.g., "localhost:6379" or "valkey:6379,password=secret").
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; } = "localhost:6379";
|
||||
|
||||
/// <summary>
|
||||
/// Valkey database number.
|
||||
/// </summary>
|
||||
public int? Database { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue name template for incoming requests. Use {service} placeholder.
|
||||
/// </summary>
|
||||
public string RequestQueueTemplate { get; set; } = "router:requests:{service}";
|
||||
|
||||
/// <summary>
|
||||
/// Queue name for gateway responses.
|
||||
/// </summary>
|
||||
public string ResponseQueueName { get; set; } = "router:responses";
|
||||
|
||||
/// <summary>
|
||||
/// Consumer group name for request processing.
|
||||
/// </summary>
|
||||
public string ConsumerGroup { get; set; } = "router-gateway";
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for RPC requests.
|
||||
/// </summary>
|
||||
public string RequestTimeout { get; set; } = "30s";
|
||||
|
||||
/// <summary>
|
||||
/// Lease duration for message processing.
|
||||
/// </summary>
|
||||
public string LeaseDuration { get; set; } = "5m";
|
||||
|
||||
/// <summary>
|
||||
/// Batch size for leasing messages.
|
||||
/// </summary>
|
||||
public int BatchSize { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Heartbeat interval.
|
||||
/// </summary>
|
||||
public string HeartbeatInterval { get; set; } = "10s";
|
||||
}
|
||||
|
||||
public sealed class GatewayTcpTransportOptions
|
||||
|
||||
@@ -21,6 +21,10 @@ using StellaOps.Router.Gateway.RateLimit;
|
||||
using StellaOps.Router.Gateway.Routing;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using StellaOps.Router.Transport.Messaging;
|
||||
using StellaOps.Router.Transport.Messaging.Options;
|
||||
using StellaOps.Messaging.DependencyInjection;
|
||||
using StellaOps.Messaging.Transport.Valkey;
|
||||
using StellaOps.Router.AspNet;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -53,6 +57,13 @@ builder.Services.AddSingleton<GatewayMetrics>();
|
||||
builder.Services.AddTcpTransportServer();
|
||||
builder.Services.AddTlsTransportServer();
|
||||
|
||||
// Messaging transport (Valkey)
|
||||
if (bootstrapOptions.Transports.Messaging.Enabled)
|
||||
{
|
||||
builder.Services.AddMessagingTransport<ValkeyTransportPlugin>(builder.Configuration, "Gateway:Transports:Messaging");
|
||||
builder.Services.AddMessagingTransportServer();
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<GatewayTransportClient>();
|
||||
builder.Services.AddSingleton<ITransportClient>(sp => sp.GetRequiredService<GatewayTransportClient>());
|
||||
|
||||
@@ -246,4 +257,25 @@ static void ConfigureGatewayOptionsMapping(WebApplicationBuilder builder, Gatewa
|
||||
options.RequireClientCertificate = tls.RequireClientCertificate;
|
||||
options.AllowSelfSigned = tls.AllowSelfSigned;
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<MessagingTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var messaging = gateway.Value.Transports.Messaging;
|
||||
options.RequestQueueTemplate = messaging.RequestQueueTemplate;
|
||||
options.ResponseQueueName = messaging.ResponseQueueName;
|
||||
options.ConsumerGroup = messaging.ConsumerGroup;
|
||||
options.RequestTimeout = GatewayValueParser.ParseDuration(messaging.RequestTimeout, TimeSpan.FromSeconds(30));
|
||||
options.LeaseDuration = GatewayValueParser.ParseDuration(messaging.LeaseDuration, TimeSpan.FromMinutes(5));
|
||||
options.BatchSize = messaging.BatchSize;
|
||||
options.HeartbeatInterval = GatewayValueParser.ParseDuration(messaging.HeartbeatInterval, TimeSpan.FromSeconds(10));
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<ValkeyTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var messaging = gateway.Value.Transports.Messaging;
|
||||
options.ConnectionString = messaging.ConnectionString;
|
||||
options.Database = messaging.Database;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Gateway.OpenApi;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using StellaOps.Router.Transport.Messaging;
|
||||
|
||||
namespace StellaOps.Gateway.WebService.Services;
|
||||
|
||||
@@ -16,6 +17,7 @@ public sealed class GatewayHostedService : IHostedService
|
||||
{
|
||||
private readonly TcpTransportServer _tcpServer;
|
||||
private readonly TlsTransportServer _tlsServer;
|
||||
private readonly MessagingTransportServer? _messagingServer;
|
||||
private readonly IGlobalRoutingState _routingState;
|
||||
private readonly GatewayTransportClient _transportClient;
|
||||
private readonly IEffectiveClaimsStore _claimsStore;
|
||||
@@ -26,6 +28,7 @@ public sealed class GatewayHostedService : IHostedService
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private bool _tcpEnabled;
|
||||
private bool _tlsEnabled;
|
||||
private bool _messagingEnabled;
|
||||
|
||||
public GatewayHostedService(
|
||||
TcpTransportServer tcpServer,
|
||||
@@ -36,10 +39,12 @@ public sealed class GatewayHostedService : IHostedService
|
||||
IOptions<GatewayOptions> options,
|
||||
GatewayServiceStatus status,
|
||||
ILogger<GatewayHostedService> logger,
|
||||
IRouterOpenApiDocumentCache? openApiCache = null)
|
||||
IRouterOpenApiDocumentCache? openApiCache = null,
|
||||
MessagingTransportServer? messagingServer = null)
|
||||
{
|
||||
_tcpServer = tcpServer;
|
||||
_tlsServer = tlsServer;
|
||||
_messagingServer = messagingServer;
|
||||
_routingState = routingState;
|
||||
_transportClient = transportClient;
|
||||
_claimsStore = claimsStore;
|
||||
@@ -59,8 +64,9 @@ public sealed class GatewayHostedService : IHostedService
|
||||
var options = _options.Value;
|
||||
_tcpEnabled = options.Transports.Tcp.Enabled;
|
||||
_tlsEnabled = options.Transports.Tls.Enabled;
|
||||
_messagingEnabled = options.Transports.Messaging.Enabled && _messagingServer is not null;
|
||||
|
||||
if (!_tcpEnabled && !_tlsEnabled)
|
||||
if (!_tcpEnabled && !_tlsEnabled && !_messagingEnabled)
|
||||
{
|
||||
_logger.LogWarning("No transports enabled; gateway will not accept microservice connections.");
|
||||
_status.MarkStarted();
|
||||
@@ -84,6 +90,17 @@ public sealed class GatewayHostedService : IHostedService
|
||||
_logger.LogInformation("TLS transport started on port {Port}", options.Transports.Tls.Port);
|
||||
}
|
||||
|
||||
if (_messagingEnabled && _messagingServer is not null)
|
||||
{
|
||||
_messagingServer.OnHelloReceived += HandleMessagingHello;
|
||||
_messagingServer.OnHeartbeatReceived += HandleMessagingHeartbeat;
|
||||
_messagingServer.OnResponseReceived += HandleMessagingResponse;
|
||||
_messagingServer.OnConnectionClosed += HandleMessagingDisconnection;
|
||||
await _messagingServer.StartAsync(cancellationToken);
|
||||
_logger.LogInformation("Messaging transport started (Valkey connection: {Connection})",
|
||||
options.Transports.Messaging.ConnectionString);
|
||||
}
|
||||
|
||||
_status.MarkStarted();
|
||||
_status.MarkReady();
|
||||
}
|
||||
@@ -110,6 +127,15 @@ public sealed class GatewayHostedService : IHostedService
|
||||
_tlsServer.OnFrame -= HandleTlsFrame;
|
||||
_tlsServer.OnDisconnection -= HandleTlsDisconnection;
|
||||
}
|
||||
|
||||
if (_messagingEnabled && _messagingServer is not null)
|
||||
{
|
||||
await _messagingServer.StopAsync(cancellationToken);
|
||||
_messagingServer.OnHelloReceived -= HandleMessagingHello;
|
||||
_messagingServer.OnHeartbeatReceived -= HandleMessagingHeartbeat;
|
||||
_messagingServer.OnResponseReceived -= HandleMessagingResponse;
|
||||
_messagingServer.OnConnectionClosed -= HandleMessagingDisconnection;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTcpFrame(string connectionId, Frame frame)
|
||||
@@ -438,8 +464,55 @@ public sealed class GatewayHostedService : IHostedService
|
||||
{
|
||||
_tlsServer.GetConnection(connectionId)?.Close();
|
||||
}
|
||||
|
||||
// Messaging transport connections are managed by the queue system
|
||||
// and do not support explicit close operations
|
||||
}
|
||||
|
||||
#region Messaging Transport Event Handlers
|
||||
|
||||
private Task HandleMessagingHello(ConnectionState state, HelloPayload payload)
|
||||
{
|
||||
// The MessagingTransportServer already built the ConnectionState with TransportType.Messaging
|
||||
// We need to add it to the routing state and update the claims store
|
||||
_routingState.AddConnection(state);
|
||||
_claimsStore.UpdateFromMicroservice(payload.Instance.ServiceName, payload.Endpoints);
|
||||
_openApiCache?.Invalidate();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Messaging connection registered: {ConnectionId} service={ServiceName} version={Version}",
|
||||
state.ConnectionId,
|
||||
state.Instance.ServiceName,
|
||||
state.Instance.Version);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task HandleMessagingHeartbeat(ConnectionState state, HeartbeatPayload payload)
|
||||
{
|
||||
_routingState.UpdateConnection(state.ConnectionId, conn =>
|
||||
{
|
||||
conn.LastHeartbeatUtc = DateTime.UtcNow;
|
||||
conn.Status = payload.Status;
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task HandleMessagingResponse(ConnectionState state, Frame frame)
|
||||
{
|
||||
_transportClient.HandleResponseFrame(frame);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task HandleMessagingDisconnection(string connectionId)
|
||||
{
|
||||
HandleDisconnect(connectionId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class EndpointKeyComparer : IEqualityComparer<(string Method, string Path)>
|
||||
{
|
||||
public bool Equals((string Method, string Path) x, (string Method, string Path) y)
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using StellaOps.Router.Transport.Messaging;
|
||||
|
||||
namespace StellaOps.Gateway.WebService.Services;
|
||||
|
||||
@@ -13,6 +14,7 @@ public sealed class GatewayTransportClient : ITransportClient
|
||||
{
|
||||
private readonly TcpTransportServer _tcpServer;
|
||||
private readonly TlsTransportServer _tlsServer;
|
||||
private readonly MessagingTransportServer? _messagingServer;
|
||||
private readonly ILogger<GatewayTransportClient> _logger;
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<Frame>> _pendingRequests = new();
|
||||
private readonly ConcurrentDictionary<string, Channel<Frame>> _streamingResponses = new();
|
||||
@@ -20,10 +22,12 @@ public sealed class GatewayTransportClient : ITransportClient
|
||||
public GatewayTransportClient(
|
||||
TcpTransportServer tcpServer,
|
||||
TlsTransportServer tlsServer,
|
||||
ILogger<GatewayTransportClient> logger)
|
||||
ILogger<GatewayTransportClient> logger,
|
||||
MessagingTransportServer? messagingServer = null)
|
||||
{
|
||||
_tcpServer = tcpServer;
|
||||
_tlsServer = tlsServer;
|
||||
_messagingServer = messagingServer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -147,6 +151,13 @@ public sealed class GatewayTransportClient : ITransportClient
|
||||
case TransportType.Certificate:
|
||||
await _tlsServer.SendFrameAsync(connection.ConnectionId, frame, cancellationToken);
|
||||
break;
|
||||
case TransportType.Messaging:
|
||||
if (_messagingServer is null)
|
||||
{
|
||||
throw new InvalidOperationException("Messaging transport is not enabled");
|
||||
}
|
||||
await _messagingServer.SendToMicroserviceAsync(connection.ConnectionId, frame, cancellationToken);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Transport type {connection.TransportType} is not supported by the gateway.");
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Router.Gateway\StellaOps.Router.Gateway.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Router.Transport.Tcp\StellaOps.Router.Transport.Tcp.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Router.Transport.Tls\StellaOps.Router.Transport.Tls.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Router.Transport.Messaging\StellaOps.Router.Transport.Messaging.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Messaging\StellaOps.Messaging.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Messaging.Transport.Valkey\StellaOps.Messaging.Transport.Valkey.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Auth.Security\StellaOps.Auth.Security.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Configuration\StellaOps.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Authority\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user