test(deploy): cover cross-cluster rejection through the actor; note reservation false-positive at gate
v2-ci / build (push) Failing after 42s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 42s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
This commit is contained in:
@@ -85,6 +85,10 @@ public sealed class AdminOperationsActor : ReceiveActor
|
||||
// derivation, cross-cluster/namespace binding, driver-namespace compat, signal collisions,
|
||||
// …) gates the deploy. A green build in this repo does not prove the config is valid; this
|
||||
// is the last guard before a bad address space (or a non-derived EquipmentId) ships.
|
||||
// NOTE: a BadDuplicateExternalIdentifier rejection from the reservation pre-flight can
|
||||
// (rarely) be a false positive if an external-id reservation release has not yet been
|
||||
// committed/visible when the snapshot is read — operators seeing a spurious one should
|
||||
// check ExternalIdReservation state before re-submitting.
|
||||
var draft = await DraftSnapshotFactory.FromConfigDbAsync(db);
|
||||
var errors = DraftValidator.Validate(draft);
|
||||
if (errors.Count > 0)
|
||||
|
||||
@@ -141,6 +141,54 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
verify.Deployments.Count().ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a DriverInstance whose NamespaceId lives in a different cluster
|
||||
/// triggers <c>BadCrossClusterNamespaceBinding</c> and routes to the Rejected branch through
|
||||
/// the actor — no coordinator dispatch, no Deployment row.
|
||||
/// Seeded: Namespace in cluster "MAIN" + DriverInstance in cluster "SITE-A" referencing that
|
||||
/// namespace. NamespaceKind.Equipment + DriverType "ModbusTcp" satisfies the compat rule so
|
||||
/// only the cross-cluster rule fires.</summary>
|
||||
[Fact]
|
||||
public void StartDeployment_rejects_on_cross_cluster_namespace_binding()
|
||||
{
|
||||
const string nsId = "ns-main-equipment";
|
||||
|
||||
var dbFactory = NewInMemoryDbFactory();
|
||||
using (var db = dbFactory.CreateDbContext())
|
||||
{
|
||||
db.Namespaces.Add(new Configuration.Entities.Namespace
|
||||
{
|
||||
NamespaceId = nsId,
|
||||
ClusterId = "MAIN",
|
||||
Kind = Configuration.Enums.NamespaceKind.Equipment,
|
||||
NamespaceUri = "urn:zb:main:equipment",
|
||||
});
|
||||
db.DriverInstances.Add(new Configuration.Entities.DriverInstance
|
||||
{
|
||||
DriverInstanceId = "drv-site-a-01",
|
||||
ClusterId = "SITE-A",
|
||||
NamespaceId = nsId, // cross-cluster: drv is SITE-A, ns is MAIN
|
||||
Name = "site-a-modbus",
|
||||
DriverType = "ModbusTcp", // compatible with Equipment ns — no compat error
|
||||
DriverConfig = "{}",
|
||||
});
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var coordinator = CreateTestProbe("coord");
|
||||
var actor = Sys.ActorOf(AdminOperationsActor.Props(dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>()));
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("BadCrossClusterNamespaceBinding");
|
||||
|
||||
using var verify = dbFactory.CreateDbContext();
|
||||
verify.Deployments.Count().ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that starting a deployment is refused when another is in flight.</summary>
|
||||
[Fact]
|
||||
public void StartDeployment_refuses_when_another_is_in_flight()
|
||||
|
||||
Reference in New Issue
Block a user