feat(mesh): CentralCommandService on the ConfigServe listener

This commit is contained in:
Joseph Doherty
2026-08-06 05:21:17 -04:00
parent a400b41502
commit 7e7df337cc
5 changed files with 518 additions and 5 deletions
@@ -94,9 +94,10 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions<MeshTranspo
}
}
// The Grpc surface below is node-side: the PSK a driver presents (and requires), the central
// endpoints it posts its ack to, and the two call deadlines. Scoped to the driver role for the
// same reason the ClusterClient block above is scoped to its mode — an admin-only central node
// The first Grpc block below is node-side: the PSK a driver presents (and requires), the
// central endpoints it posts its ack to, and the two call deadlines (the admin-side rule that
// follows it is a separate scope). Scoped to the driver role for the same reason the
// ClusterClient block above is scoped to its mode — an admin-only central node
// dials nodes rather than being dialled and resolves per-cluster keys at runtime through
// MeshTransportOptions.ClusterPsks / the Secrets store, so requiring a single GrpcPsk and an
// endpoint list of it would demand configuration it never reads. Roles come from
@@ -107,6 +108,7 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions<MeshTranspo
var roles = RoleParser.ResolveEffectiveRoles(
_configuration.GetSection("Cluster:Roles").Get<string[]>());
var isDriver = Array.IndexOf(roles, RoleParser.Driver) >= 0;
var isAdmin = Array.IndexOf(roles, RoleParser.Admin) >= 0;
if (isGrpc && isDriver)
{
@@ -166,6 +168,37 @@ public sealed class MeshTransportOptionsValidator : IValidateOptions<MeshTranspo
}
}
// The one admin-side obligation, and it is cross-read rather than a member of this options
// class: central hosts CentralCommandService on the SAME dedicated h2c listener as the
// config-serve endpoint (ConfigServe:GrpcListenPort) — a second cleartext HTTP/2 port would
// buy nothing but another firewall rule. With that port unset the service is never mapped, so
// every node's deployment ack has nowhere to land, and nothing errors: the deployment simply
// times out naming nodes that applied perfectly. Owned here rather than by ConfigServe's own
// validation because the requirement comes from MeshTransport:Mode, not from config-serve — a
// Dps or ClusterClient central node is legitimately portless.
if (isGrpc && isAdmin)
{
// Read as a string, the way SplitTopologyTransportValidator reads the modes:
// GetValue<int> THROWS on an unparseable value, and a validator whose whole job is to
// produce a readable failure must not be the thing that crashes on a typo. Missing,
// unparseable and non-positive all mean the same thing — nothing is bound — so they share
// one message.
var configServePort = _configuration[
$"{ConfigServeOptions.SectionName}:{nameof(ConfigServeOptions.GrpcListenPort)}"];
if (!int.TryParse(configServePort, out var port) || port <= 0)
{
errors.Add(
$"{ConfigServeOptions.SectionName}:"
+ $"{nameof(ConfigServeOptions.GrpcListenPort)} is not set to a positive port. "
+ $"Under {MeshTransportOptions.ModeGrpc} an admin node serves the central "
+ "command endpoint on that listener, and without it every node's deployment ack "
+ "has nowhere to land — the deploy would time out naming nodes that applied "
+ "perfectly. Supply it via the environment "
+ $"({ConfigServeOptions.SectionName}__"
+ $"{nameof(ConfigServeOptions.GrpcListenPort)}).");
}
}
return errors.Count == 0
? ValidateOptionsResult.Success
: ValidateOptionsResult.Fail(string.Join(" ", errors));