Skip to content

H5: Remove Linux upgrade strategy's cold-cache historical default

Summary

H5 of the 1.8.0 Upgrade Hardening initiative (H1-H4 already merged).

Closes the last gap in the 1.7.x operator failure chain by making LinuxTentacleUpgradeStrategy.CanHandle strict-Linux-only AND adding a symmetric cold-cache guard to MachineUpgradeService.UpgradeAsync so direct API callers can't bypass the FE's H1 upgrade-info gate.

The bug class H5 closes

Pre-H5 LinuxTentacleUpgradeStrategy.CanHandle had two backward-compat "historical default" branches:

if (capabilities == null) return true;                        // ← null = claim
return capabilities.IsLinux || capabilities.IsUnknown;        // ← Unknown = claim

The IsUnknown claim was the root cause of the operator's 1.7.x failure chain:

  1. Cold-cache Windows machine triggers GetUpgradeInfoAsync
  2. ResolveStrategy ran with capabilities.Os = "" (cold cache)
  3. Linux strategy claimed it via IsUnknown=true historical default
  4. Version registry queried Docker Hub for a Linux tarball that doesn't exist for win-x64
  5. Operator saw "Docker Hub unreachable, set SQUID_TARGET_LINUX_TENTACLE_VERSION" — and went down the wrong remediation path because they were on Windows

H1 already short-circuits cold-cache tentacle styles at the eligibility evaluator with NoOsDetected, so the FE's GetUpgradeInfo call no longer reaches strategy resolution in the cold case. H5 closes the remaining gap (direct API callers / curl) AND removes the now-dead historical-default code path.

Behaviour changes

Scenario Pre-H5 Post-H5
LinuxTentacleUpgradeStrategy.CanHandle("TentaclePolling", null) true false
LinuxTentacleUpgradeStrategy.CanHandle("TentaclePolling", {Os=""}) true false
LinuxTentacleUpgradeStrategy.CanHandle("TentaclePolling", {Os="Unknown"}) true false
LinuxTentacleUpgradeStrategy.CanHandle("TentaclePolling", {Os="Linux"}) true true (unchanged)
Direct POST /upgrade with cold-cache tentacle machine Dispatched via Linux historical default Rejected with OS not yet detected, run health check (mirrors H1)

Cross-OS uniformity

The Windows and Linux strategies are now structurally symmetric:

// Windows:
if (capabilities == null) return false;
return capabilities.IsWindows;

// Linux (post-H5):
if (capabilities == null) return false;
return capabilities.IsLinux;

A future MacOSTentacleUpgradeStrategy / BSDTentacleUpgradeStrategy plugs in via Autofac DI without modifying either existing strategy. Open-closed property.

Test plan

  • +2 invariant pins on Linux strategy (empty Os rejected, explicit "Unknown" rejected)
  • +1 invariant on UpgradeAsync cold-cache guard (rejected at entry with health-check guidance)
  • Repurposed UpgradeAsync_ColdCacheNoOs_RoutesToLinuxAsHistoricalDefault → *_RejectedAtEntry_PointsToHealthCheck with inverted expectations
  • ArrangeMachine helper auto-seeds os=Linux so tests that don't care about OS routing continue to exercise the happy path
  • 5563/5563 unit tests green (+2 net new)

Backward compatibility

  • Operator-facing break: cold-cache + direct POST /upgrade previously succeeded (via Linux historical default). It now fails with OS not yet detected. This is the intentional security/correctness improvement — operators running curl -X POST against a freshly-registered machine should hit the same guidance the UI gives.
  • All existing tests adjusted to reflect the new strict-OS contract.

What's NOT in this PR

  • H6: Agent rollback + abandonment recovery (next)
  • H7: Role/feature capability slots
  • H8: Comprehensive E2E test matrix

Merge request reports

Loading