feat(central): one central cache-invalidation topic; role-mapping and external-definition caches follow peer edits (ScadaBridge#100 item 3, ScadaBridge#83)
The #96 site-psk-invalidation topic was the first cross-node invalidation broadcast. It is generalised into ONE central DistributedPubSub topic, central-cache-invalidation, carrying typed messages (Commons ICentralCacheInvalidation: SitePskInvalidated, RoleMappingsInvalidated, ExternalDefinitionsInvalidated, InboundApiMethodInvalidated) in an envelope stamped with the publisher's process-unique origin id. - Host: CentralCacheInvalidationBus (ICentralCacheInvalidationPublisher, no-op until the mediator is set, never throws) and CentralCacheInvalidationSubscriber, which skips its own node's envelopes and hands the peer's to every registered ICentralCacheInvalidationHandler (a failing handler does not stop the rest). Replaces SitePskInvalidationSubscriber. - Migration of #96: SitePskRotationInvalidator publishes through the bus and is the PSK handler; SitePskInvalidated keeps its type name and is also published bare on site-psk-invalidation, which the subscriber still listens to, so a mixed-version central pair keeps exchanging PSK invalidations. Remove after one release. - Audit D1: RoleMappingCache is a handler; ManagementActor's post-commit role-mapping invalidation also publishes RoleMappingsInvalidated, so a revocation is immediate on the peer instead of after RoleMappingCacheSeconds. - Audit D3 / #83 (peer half): ExternalDefinitionLookupCache is a handler; the 9 ManagementActor handlers and the Transport bundle importer publish ExternalDefinitionsInvalidated after commit. (#83's local import half was already in.) TTLs remain the fallback for a lost broadcast or a direct SQL edit. Tests: bus and subscriber unit tests (origin filter, legacy topic), peer-node tests through the real caches in ManagementService.Tests, importer publish, composition root, and a real two-node cluster from the production HOCON (CentralCacheInvalidationTwoNodeTests) proving every message type crosses with the production serializer. Docs: Host, Communication, Security, ManagementService, ESG, and the #85 cache audit. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
+20
@@ -13,6 +13,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Scripting;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Communication;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Scripting;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
|
||||
@@ -48,6 +49,7 @@ public sealed class BundleImporterApplyTests : IDisposable
|
||||
private readonly ServiceProvider _provider;
|
||||
private readonly RecordingScriptArtifactChangeBus _artifactBus = new();
|
||||
private readonly CountingDefinitionCacheInvalidator _definitionCache = new();
|
||||
private readonly RecordingCacheInvalidationPublisher _peerPublisher = new();
|
||||
|
||||
/// <summary>Initializes a new instance of the <c>BundleImporterApplyTests</c> class.</summary>
|
||||
public BundleImporterApplyTests()
|
||||
@@ -103,6 +105,8 @@ public sealed class BundleImporterApplyTests : IDisposable
|
||||
services.AddSingleton<IScriptArtifactChangeBus>(_artifactBus);
|
||||
// Gitea #83: observable post-commit drop of the external-definition lookup cache.
|
||||
services.AddSingleton<IExternalDefinitionCacheInvalidator>(_definitionCache);
|
||||
// ScadaBridge#100 item 3: observable post-commit broadcast to the peer central node.
|
||||
services.AddSingleton<ICentralCacheInvalidationPublisher>(_peerPublisher);
|
||||
// The stale-instance probe is implemented in DeploymentManager
|
||||
// and the flatten/hash primitives in TemplateEngine — register both so
|
||||
// BundleImporter resolves a real IStaleInstanceProbe and ApplyAsync can
|
||||
@@ -1946,6 +1950,8 @@ public sealed class BundleImporterApplyTests : IDisposable
|
||||
}
|
||||
|
||||
Assert.Equal(1, _definitionCache.Calls);
|
||||
// ScadaBridge#100 item 3 / #83: the peer central node is told to drop its copy too.
|
||||
Assert.Contains(_peerPublisher.Published, i => i is ExternalDefinitionsInvalidated);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed apply publishes no notifications.</summary>
|
||||
@@ -1998,6 +2004,20 @@ public sealed class BundleImporterApplyTests : IDisposable
|
||||
Assert.Empty(_artifactBus.Received);
|
||||
// Gitea #83: nothing committed, so the definition cache is left alone.
|
||||
Assert.Equal(0, _definitionCache.Calls);
|
||||
Assert.Empty(_peerPublisher.Published);
|
||||
}
|
||||
|
||||
/// <summary>Records peer cache invalidations (ScadaBridge#100 item 3).</summary>
|
||||
private sealed class RecordingCacheInvalidationPublisher : ICentralCacheInvalidationPublisher
|
||||
{
|
||||
/// <summary>Gets the invalidations published.</summary>
|
||||
public List<ICentralCacheInvalidation> Published { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Publish(ICentralCacheInvalidation invalidation)
|
||||
{
|
||||
lock (Published) Published.Add(invalidation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Counts post-commit external-definition cache invalidations (Gitea #83).</summary>
|
||||
|
||||
Reference in New Issue
Block a user