dcff5ae994
v2-ci / unit-tests (push) Waiting to run
A planned stop of the Primary took ~30 s on QA: WebApplication stops the web server before Akka.Hosting's service, and Kestrel's graceful drain waited the whole ShutdownTimeout for the peer's open LocalDb sync stream (the library's LocalDbSyncService only observes the call token, which fires at the END of the drain). Only then did CoordinatedShutdown run, so the stopping node held the Primary role and the peer sat at ServiceLevel 240 for the whole window. - GracefulClusterLeaveService (IHostedLifecycleService): StoppingAsync runs before any hosted service's StopAsync; it raises ApplicationStopping, then runs and awaits CoordinatedShutdown with ClrExitReason (not Cluster.Leave, whose ClusterLeavingReason makes Akka.Hosting set ExitCode -1). The cluster is left while Kestrel is still up. - ShutdownStreamCancellationInterceptor (innermost on the shared gRPC pipeline): every streaming call gets a context whose token is linked with ApplicationStopping; streams arriving once stopping are refused Unavailable. Host-side mitigation until LocalDb 0.3.8 is published and pinned (scadaproj 3ddb083, scadaproj#57); it also covers the mesh streams. Test GracefulStopWithOpenSyncStreamTests (real WebApplication + Kestrel h2c + MapZbLocalDbSync + single-node Akka cluster + real peer initiator, 8 s drain budget): stop 895 ms, cluster left at 892 ms, drain began at 894 ms. Positive control without the fix: stop 8863 ms, cluster left at 8841 ms after the drain began at 0 ms. Mutations: no CoordinatedShutdown in StoppingAsync -> order assertion fails (drain at 1 ms, removed at 893 ms); interceptor passes the original context -> stop 8006 ms, fails. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
Two-node Akka cluster integration tests on top of TwoNodeClusterHarness.
Default mode (no infra required)
dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
Uses Microsoft.EntityFrameworkCore.InMemory for ConfigDb and a stub ILdapAuthService that
accepts any username when the password is valid-password. Each harness instance creates a
unique in-memory database scoped to its lifetime. This is the mode CI runs by default.
Real-infra mode (SQL Server + OpenLDAP)
When you need to exercise EF behaviors that diverge between providers (index uniqueness,
RowVersion concurrency, JSON columns, migration application) or a real LDAP bind, bring up
the bundled compose stack and set the env-var switches:
docker compose -f tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/docker-compose.yml up -d
export OTOPCUA_HARNESS_USE_SQL=1
export OTOPCUA_HARNESS_USE_LDAP=1
dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
docker compose -f tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/docker-compose.yml down -v
SQL Server mode (OTOPCUA_HARNESS_USE_SQL=1)
- Container:
mcr.microsoft.com/mssql/server:2022-latestonlocalhost:14331 - Each
TwoNodeClusterHarness.StartAsync()creates a unique databaseOtOpcUa_Harness_{guid}viaDatabase.EnsureCreatedAsync()and drops it onDisposeAsync()(best-effort). - Port
14331chosen to avoid colliding with thedocker-dev/fleet (which uses14330).
LDAP mode (OTOPCUA_HARNESS_USE_LDAP=1)
- Container:
bitnami/openldap:2.6onlocalhost:3894 - Users
alice/alice123andbob/bob123, all underou=FleetAdmin. - Port
3894chosen to avoid colliding with thedocker-dev/fleet (which uses3893).
Local-dev caveat
This dev VM (DESKTOP-6JL3KKO) does not run Docker locally. Real-infra mode runs on the
shared Linux Docker host (10.100.0.35) per docs/v2/dev-environment.md, or in CI on Linux.