Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/DeploymentManagerOptions.cs
T
Joseph Doherty fc48db480c feat(deploy): PendingUpdatesEvaluator — one fleet snapshot of stale instances and stale-artifact sites
One scoped read that answers "is anything waiting to be deployed?" for the whole
fleet: each site's stored artifact revision hash compared against what a deploy
would send right now (NeverDeployed / Current / OutOfDate), plus per-site
deployed/stale/unknown instance tallies from the batched staleness evaluation.
NeverDeployed is deliberately NOT a pending update — the operator has not deployed
yet — and exceptions propagate so the caller's single-flight cache can render
"unavailable" rather than invent an "up to date". Adds the validated
PendingUpdatesCacheTtl (60 s) that TTL will key off.

Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
2026-09-14 13:49:54 -04:00

94 lines
4.7 KiB
C#

namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
/// <summary>
/// Configuration options for the central-side Deployment Manager.
/// </summary>
public class DeploymentManagerOptions
{
/// <summary>
/// Timeout for a lifecycle command round-trip (disable, enable, delete).
/// Applied as a linked-CTS deadline in <c>DeploymentService</c> so a hung or
/// unreachable site does not hold the per-instance operation lock indefinitely.
/// </summary>
public TimeSpan LifecycleCommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>Timeout per site for system-wide artifact deployment.</summary>
public TimeSpan ArtifactDeploymentTimeoutPerSite { get; set; } = TimeSpan.FromSeconds(120);
/// <summary>Timeout for acquiring an operation lock on an instance.</summary>
public TimeSpan OperationLockTimeout { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// Maximum number of instance deployments whose SITE ROUND-TRIP runs
/// concurrently during a bulk <c>DeploySiteAsync</c>. The flatten/validate and
/// persistence phases stay serial regardless (they share one non-thread-safe
/// <c>DbContext</c>); only the network wait is fanned out.
///
/// <para>
/// Bounded rather than unbounded so a large site cannot open one in-flight
/// gRPC command per instance against a two-node site pair. Four mirrors the
/// conservative end of the site's own apply concurrency; raise it only with
/// evidence from the target-scale load test.
/// </para>
/// </summary>
public int SiteDeploymentMaxParallelism { get; set; } = 4;
/// <summary>
/// Per-instance deadline applied to the site round-trip inside a bulk
/// <c>DeploySiteAsync</c>. One unreachable or wedged instance is recorded as a
/// failed row and the rest of the batch continues — the bulk operation never
/// inherits a single instance's hang.
/// </summary>
public TimeSpan SiteDeploymentTimeoutPerInstance { get; set; } = TimeSpan.FromSeconds(120);
/// <summary>
/// Deadline applied to each query-before-redeploy reconciliation round-trip
/// inside a bulk <c>DeploySiteAsync</c> (the single-instance path keeps the
/// communication layer's own <c>QueryTimeout</c>).
///
/// <para>
/// Phase 1 is strictly serial, so an unreachable site otherwise costs
/// <c>QueryTimeout</c> (30 s) PER INSTANCE before a single deploy is sent — for
/// a 500-instance site roughly four hours, far past the CLI's 5-minute bulk
/// deploy timeout, which then cancels and re-arms the identical cost on the next
/// attempt. Deliberately short (seconds): a reconcile that does not answer
/// promptly falls through to a normal deploy anyway, which is exactly what its
/// own timeout produces. Works together with the once-per-batch reachability
/// circuit-breaker, which skips the remaining reconciles after the first
/// timeout.
/// </para>
/// </summary>
public TimeSpan SiteDeploymentReconcileTimeout { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// Retention window for TERMINAL deployment records (<c>Success</c>,
/// <c>Failed</c>). Deployments are insert-only — one row per attempt, forever —
/// so without a window the table grows without bound and every unfiltered
/// deployment query gets slower with the age of the system.
///
/// <para>
/// Deliberately generous (one year) because deployment history is
/// operator-facing forensics, and deliberately terminal-only: an
/// <c>InProgress</c> or <c>Pending</c> row is never purged regardless of age,
/// because it is exactly the row the query-before-redeploy reconciliation
/// path needs to find. Set to <see cref="TimeSpan.Zero"/> to disable purging.
/// </para>
/// </summary>
public TimeSpan TerminalDeploymentRecordRetention { get; set; } = TimeSpan.FromDays(365);
/// <summary>
/// How long the Central UI pending-updates snapshot stays fresh; one fleet
/// flatten per TTL while any Deployer/Administrator circuit is open.
///
/// <para>
/// The snapshot is a whole-fleet read (every site's artifact hash plus every
/// deployed instance's staleness), so it is held behind a single-flight TTL
/// cache: however many circuits poll, and however often, the fleet is evaluated
/// at most once per this window — and not at all when nobody is looking. Must be
/// positive; zero would make every poll a cache miss and re-flatten the fleet per
/// circuit, which is precisely the cost the cache exists to avoid.
/// </para>
/// </summary>
public TimeSpan PendingUpdatesCacheTtl { get; set; } = TimeSpan.FromSeconds(60);
}