780bb9c369
Central now ALSO listens for the seven site→central control messages over gRPC, alongside the existing ClusterClient path. Nothing flips to gRPC yet — sites keep CentralTransport=Akka (T1A.3's job); central simply starts also accepting. - CentralControlGrpcService (Communication.Grpc): decodes each RPC onto the SAME in-process message the ClusterClient path carries, Asks the existing CentralCommunicationActor (zero handler-logic changes), encodes the reply via the T1A.1 mapper. Readiness-gated like SiteStreamGrpcServer.SetReady — Unavailable until AkkaHostedService hands the actor over. Heartbeat stays fire-and-forget (Tell, always-OK, never gated on readiness). Ingest reuses the shared SiteStreamGrpcServer.AuditIngestAskTimeout constant. Fault→status mapping is retry-aware: Unavailable (never dispatched, safe to cross-node retry) vs DeadlineExceeded/Internal (it ran, do not re-send elsewhere). - CentralControlAuthInterceptor (Host): a SEPARATE interceptor class, not a variant constructor on ControlPlaneAuthInterceptor. Central's model is per-site (verify the Bearer token against the key for the site in the required x-scadabridge-site header, via ISitePskProvider) where a site verifies its one own-key — a genuinely different model. Fail-closed on every branch: missing or blank header, unresolvable key, and mismatched token all → PermissionDenied, never pass-through. One public constructor only (the explicit-prefix ctor is internal), pinned by a reflection test — a second public ctor makes Grpc.AspNetCore's GetFactory() throw per-call and silently disables the gate. - Explicit Kestrel h2c listener on new option ScadaBridge:Node:CentralGrpcPort (default 8083, symmetric with sites), mirroring the Site branch. Additive to central's :5000 HTTP/1 surface, which is untouched — gRPC does NOT go through Traefik (HTTP/1 only). Registered by type on AddGrpc; service mapped with MapGrpcService. Port range-validated by NodeOptionsValidator. - Rig: publish the central gRPC port 9013:8083 / 9014:8083 on both central nodes so a later task can exercise it. Tests: CentralControlEndToEndTests (Host.Tests, TestServer + real interceptor + real service over a stub actor) proves auth positives/negatives are distinguishable and covers unary + the ingest bridge shapes; the interceptor is registered BY TYPE, never in DI. CentralControlAuthInterceptorTests pins the per-site gate + one-public-ctor invariant. CentralControlGrpcServiceTests (Communication.Tests, TestKit) covers the readiness gate, fire-and-forget heartbeat, and the DeadlineExceeded-vs-Unavailable status mapping. No active <Protobuf> item. Communication.Tests (356) + Host.Tests (384) green.
40 lines
2.2 KiB
C#
40 lines
2.2 KiB
C#
namespace ZB.MOM.WW.ScadaBridge.Host;
|
|
|
|
public class NodeOptions
|
|
{
|
|
/// <summary>Gets or sets the node role (e.g. Central or Site).</summary>
|
|
public string Role { get; set; } = string.Empty;
|
|
/// <summary>Gets or sets the hostname or IP address this node advertises to the Akka cluster.</summary>
|
|
public string NodeHostname { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Operator-configured semantic node name used to stamp the SourceNode
|
|
/// column on audit rows. Conventional values are <c>node-a</c>/<c>node-b</c>
|
|
/// on site nodes and <c>central-a</c>/<c>central-b</c> on central nodes,
|
|
/// but the value is a free-form label — no validation is enforced.
|
|
/// </summary>
|
|
public string NodeName { get; set; } = string.Empty;
|
|
/// <summary>Gets or sets the site identifier for site nodes; null for central nodes.</summary>
|
|
public string? SiteId { get; set; }
|
|
/// <summary>Gets or sets the Akka.NET remoting port for cluster communication.</summary>
|
|
public int RemotingPort { get; set; } = 8081;
|
|
/// <summary>Gets or sets the gRPC port for the site stream server.</summary>
|
|
public int GrpcPort { get; set; } = 8083;
|
|
/// <summary>
|
|
/// HTTP/2 (h2c) port the CENTRAL node listens on for the site→central
|
|
/// <c>CentralControlService</c> gRPC control plane. Default 8083 — deliberately symmetric
|
|
/// with the site <see cref="GrpcPort"/>, since a node is either central or a site and the
|
|
/// two never share a process. This listener is distinct from central's <c>:5000</c> HTTP/1
|
|
/// surface (Central UI, Management/Inbound API), which stays exactly as-is: gRPC does NOT go
|
|
/// through Traefik (HTTP/1 only). Ignored on site nodes.
|
|
/// </summary>
|
|
public int CentralGrpcPort { get; set; } = 8083;
|
|
/// <summary>
|
|
/// HTTP/1.1 port serving the Prometheus /metrics scrape endpoint on site nodes.
|
|
/// Defaults to 8084 — deliberately distinct from <see cref="RemotingPort"/> (8082)
|
|
/// and <see cref="GrpcPort"/> (8083) so the Kestrel metrics listener never contends
|
|
/// with the Akka remoting port a site node binds.
|
|
/// </summary>
|
|
public int MetricsPort { get; set; } = 8084;
|
|
}
|