feat(mesh): CentralCommandService on the ConfigServe listener
This commit is contained in:
@@ -20,9 +20,18 @@ namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests;
|
||||
[Collection(RoleEnvironmentCollection.Name)]
|
||||
public class MeshTransportOptionsValidatorTests
|
||||
{
|
||||
private static ValidateOptionsResult Validate(MeshTransportOptions o, params string[] roles)
|
||||
private static ValidateOptionsResult Validate(MeshTransportOptions o, params string[] roles) =>
|
||||
Validate(o, new Dictionary<string, string?>(), roles);
|
||||
|
||||
/// <summary>
|
||||
/// As <see cref="Validate(MeshTransportOptions, string[])"/>, plus arbitrary configuration
|
||||
/// keys — the admin-side rule cross-reads <c>ConfigServe:GrpcListenPort</c>, which lives
|
||||
/// outside this options class entirely.
|
||||
/// </summary>
|
||||
private static ValidateOptionsResult Validate(
|
||||
MeshTransportOptions o, Dictionary<string, string?> extra, params string[] roles)
|
||||
{
|
||||
var pairs = new Dictionary<string, string?>();
|
||||
var pairs = new Dictionary<string, string?>(extra);
|
||||
for (var i = 0; i < roles.Length; i++)
|
||||
{
|
||||
pairs[$"Cluster:Roles:{i}"] = roles[i];
|
||||
@@ -34,6 +43,9 @@ public class MeshTransportOptionsValidatorTests
|
||||
.Validate(MeshTransportOptions.SectionName, o);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string?> ConfigServePort(string? value) =>
|
||||
new() { ["ConfigServe:GrpcListenPort"] = value };
|
||||
|
||||
private static MeshTransportOptions WellFormedGrpc() => new()
|
||||
{
|
||||
Mode = MeshTransportOptions.ModeGrpc,
|
||||
@@ -298,11 +310,105 @@ public class MeshTransportOptionsValidatorTests
|
||||
// The node-side surface is what a DRIVER uses to dial central and post its ack. An admin-only
|
||||
// central node dials nodes instead and resolves per-cluster keys at runtime, so requiring
|
||||
// these here would make an admin node carry a PSK and an endpoint list it never reads.
|
||||
// (The ConfigServe port IS required of an admin node under Grpc — supplied here so this test
|
||||
// stays about the node-side keys and nothing else.)
|
||||
Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc },
|
||||
ConfigServePort("4055"),
|
||||
"admin", "cluster-MAIN").Succeeded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_admin_node_without_a_config_serve_listener_fails()
|
||||
{
|
||||
// Central hosts CentralCommandService on the ConfigServe h2c listener, so an unset port means
|
||||
// the service is never mapped and every node's ack has nowhere to land. Nothing errors at
|
||||
// runtime — the deployment simply times out naming nodes that applied perfectly, which is the
|
||||
// exact absence this validator exists to convert into a refusal to start.
|
||||
var result = Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc },
|
||||
"admin", "cluster-MAIN");
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
result.FailureMessage.ShouldContain("ConfigServe:GrpcListenPort");
|
||||
result.FailureMessage.ShouldContain("ConfigServe__GrpcListenPort");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_admin_node_with_a_zero_config_serve_port_fails()
|
||||
{
|
||||
// 0 is the documented "disabled" value, not merely a missing key — it must fail exactly as an
|
||||
// absent one does, because it binds nothing just as surely.
|
||||
Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc },
|
||||
ConfigServePort("0"),
|
||||
"admin", "cluster-MAIN").Failed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_admin_node_with_an_unparseable_config_serve_port_fails()
|
||||
{
|
||||
// A typo'd port binds nothing, so it belongs on the same side of the line as an absent one.
|
||||
Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc },
|
||||
ConfigServePort("40o55"),
|
||||
"admin", "cluster-MAIN").Failed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_admin_node_with_a_config_serve_listener_passes()
|
||||
{
|
||||
Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc },
|
||||
ConfigServePort("4055"),
|
||||
"admin", "cluster-MAIN").Succeeded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_driver_only_node_needs_no_config_serve_listener()
|
||||
{
|
||||
// A driver node dials central; it serves no central command endpoint at all, and requiring an
|
||||
// admin-side listener port of it would demand configuration it never reads.
|
||||
Validate(WellFormedGrpc(), "driver", "cluster-SITE-A").Succeeded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dps_admin_node_needs_no_config_serve_listener()
|
||||
{
|
||||
// The no-new-obligations invariant, admin side: a central node still on the default transport
|
||||
// publishes on DistributedPubSub and hosts no command endpoint.
|
||||
Validate(
|
||||
new MeshTransportOptions { Mode = MeshTransportOptions.ModeDps },
|
||||
"admin", "cluster-MAIN").Succeeded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClusterClient_admin_node_needs_no_config_serve_listener()
|
||||
{
|
||||
// Contact points are required by the mode itself, independently of role — supplied so this
|
||||
// test fails only if the ConfigServe rule wrongly fires outside Grpc mode.
|
||||
Validate(
|
||||
new MeshTransportOptions
|
||||
{
|
||||
Mode = MeshTransportOptions.ModeClusterClient,
|
||||
CentralContactPoints = ["akka.tcp://otopcua@central-1:4053"],
|
||||
},
|
||||
"admin", "cluster-MAIN").Succeeded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_admin_role_is_also_read_from_OTOPCUA_ROLES()
|
||||
{
|
||||
// Same roles-source divergence as the driver case (Gitea #532), on the side that hosts the
|
||||
// endpoint: a central node configured only through the env var is still the node whose
|
||||
// listener every ack depends on.
|
||||
using (new EnvVarScope("OTOPCUA_ROLES", "admin,cluster-MAIN"))
|
||||
{
|
||||
Validate(new MeshTransportOptions { Mode = MeshTransportOptions.ModeGrpc })
|
||||
.Failed.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grpc_role_less_node_needs_none_of_the_node_side_keys()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user