a38a52b831
128 KiB raw chunks base64-encoded into deployment_artifacts, with a per-cluster pointer carrying a SHA-256 over the raw bytes. Reassembly verifies chunk count AND the SHA before returning, so a partially replicated artifact reads back as a clean miss rather than a silently truncated address space. Adds GetCurrentUnkeyedAsync beyond the plan's interface: at the boot-failure seam the actor cannot know its ClusterId (it is derivable only from an artifact you already have, or from the central DB that is unreachable). Recon D-1. The prune's 'deployment_id <> @DeploymentId' clause makes 'the pointer's target is always present' structural rather than a side effect of clock resolution. Three stores inside one timestamp tick fall through to a deployment_id tiebreak, and since real ids are GUIDs that ordering is random - the row just written could lose, leaving the pointer naming chunks that no longer exist. Verified red without the clause.
69 lines
3.3 KiB
C#
69 lines
3.3 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;
|
|
|
|
/// <summary>
|
|
/// Node-local durable cache of the deployment artifact this node last applied.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Exists so a driver node can boot into its last-known-good address space when the central
|
|
/// configuration database is unreachable. Without it, a control-plane outage that outlives a
|
|
/// node restart leaves that node with no address space at all — the plant loses visibility
|
|
/// for a reason that has nothing to do with the plant.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IDeploymentArtifactCache
|
|
{
|
|
/// <summary>
|
|
/// Persists <paramref name="artifact"/> as the cluster's current deployment.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Idempotent per <paramref name="deploymentId"/>: re-storing the same deployment
|
|
/// replaces its chunks rather than accumulating orphans. Retention is bounded to the two
|
|
/// newest deployments per cluster, so a long-lived node cannot grow its local database
|
|
/// without limit.
|
|
/// </para>
|
|
/// </remarks>
|
|
Task StoreAsync(string clusterId, string deploymentId, string revisionHash,
|
|
byte[] artifact, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Reads the cached artifact for <paramref name="clusterId"/>, or <see langword="null"/>
|
|
/// when nothing is cached or the cached bytes fail their integrity check.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// A failed integrity check is deliberately indistinguishable from a miss. Booting a
|
|
/// plant from a truncated address space is strictly worse than booting from none: the
|
|
/// missing half looks like a deliberate configuration rather than an error.
|
|
/// </para>
|
|
/// </remarks>
|
|
Task<CachedDeploymentArtifact?> GetCurrentAsync(string clusterId, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Reads the single cached pointer without knowing the cluster id.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The boot seam needs this. A node's cluster id is only derivable from an artifact it
|
|
/// has already loaded, or from the central database — which is precisely what is
|
|
/// unreachable in the scenario this cache exists to survive. So the cold path reads the
|
|
/// newest pointer unkeyed.
|
|
/// </para>
|
|
/// <para>
|
|
/// More than one pointer row means the node was re-homed between clusters. The newest
|
|
/// wins, but the choice is logged as a warning naming both clusters — a node silently
|
|
/// booting a neighbouring cluster's configuration is exactly the failure that must never
|
|
/// be quiet.
|
|
/// </para>
|
|
/// </remarks>
|
|
Task<CachedDeploymentArtifact?> GetCurrentUnkeyedAsync(CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A deployment artifact recovered from the node-local cache, with the metadata needed to decide
|
|
/// whether it is still current once the control plane is reachable again.
|
|
/// </summary>
|
|
public sealed record CachedDeploymentArtifact(
|
|
string DeploymentId, string RevisionHash, byte[] Artifact, DateTimeOffset AppliedAtUtc);
|