fix(secrets): Akka replication was inert in 0.2.0 - registration order bug (0.2.1)
AddZbSecretsAkkaReplication called AddZbSecrets FIRST, which does TryAddSingleton<ISecretReplicator, NoOpSecretReplicator>(). The package's own TryAddSingleton<ISecretReplicator> therefore found a descriptor already present and was silently discarded. Consequence: ISecretReplicator resolved to the no-op sink, so every write published into nothing; and because SecretReplicationActor is only spawned as a side effect of constructing AkkaSecretReplicator, no actor was ever created either. No exception, no log line - a cluster that reports healthy and silently never converges. The worst available failure mode for a secrets store. Fix: register ISecretReplicator BEFORE AddZbSecrets, matching what the SQL-Server package already did. Found during OtOpcUa adoption (Task 6), which is the first code that ever built a container around this extension. Root cause of the gap: the SQL-Server package had a DI test asserting its replicator type (AddZbSecretsSqlServerTests:58) and the correct order; the Akka package had neither. Every Akka test exercised the actor, serializer and reconciler in isolation - none built a container, so nothing could see it. This is the third instance of the same defect class in this library (the inert ISecretReplicator seam, the unregistered concrete SqliteSecretStore, and now this), all of which share one cause: unit tests that never construct the DI graph. Adds AddZbSecretsAkkaReplicationTests (5 tests) asserting registration at the ServiceCollection level. Verified to discriminate: with the 0.2.0 order restored, 2 of the 5 fail; with the fix, all 5 pass. They assert descriptors rather than resolving from a provider on purpose - resolving ISecretReplicator eagerly spawns the actor, whose PreStart needs DistributedPubSub and therefore a joined cluster, which would make the test hang rather than fail. Full suite Release-green: 175 passed, 15 skipped, no new warnings.
This commit is contained in:
+8
-3
@@ -66,9 +66,6 @@ public static class AkkaSecretsServiceCollectionExtensions
|
||||
section.Bind(eager);
|
||||
eager.Validate();
|
||||
|
||||
// The local store + migrator come from AddZbSecrets (SQLite by default).
|
||||
services.AddZbSecrets(config, secretsSectionPath);
|
||||
|
||||
// The actor is created lazily on first use rather than at registration: the ActorSystem is
|
||||
// typically registered by the application AFTER this call, and a cluster node may also not be
|
||||
// ready to join at the moment the container is built.
|
||||
@@ -78,9 +75,17 @@ public static class AkkaSecretsServiceCollectionExtensions
|
||||
sp.GetService<ISecretCacheInvalidator>(),
|
||||
sp.GetRequiredService<IOptions<AkkaSecretsReplicationOptions>>().Value));
|
||||
|
||||
// MUST be registered BEFORE AddZbSecrets. That call does
|
||||
// TryAddSingleton<ISecretReplicator, NoOpSecretReplicator>(), so if it ran first it would
|
||||
// win and the TryAdd below would be silently discarded — leaving replication bound to the
|
||||
// no-op sink, publishing nothing, with no actor ever created and no error anywhere.
|
||||
// Shipped that way in 0.2.0; fixed in 0.2.1. The SqlServer package always had this order.
|
||||
services.TryAddSingleton<ISecretReplicator>(sp =>
|
||||
new AkkaSecretReplicator(sp.GetRequiredService<SecretReplicationActorProvider>().ActorRef));
|
||||
|
||||
// The local store + migrator come from AddZbSecrets (SQLite by default).
|
||||
services.AddZbSecrets(config, secretsSectionPath);
|
||||
|
||||
// Decorate the local store so every write publishes. Resolved from the concrete SQLite store,
|
||||
// not ISecretStore — asking the container for ISecretStore here would resolve this very
|
||||
// decorator and recurse forever.
|
||||
|
||||
Reference in New Issue
Block a user