GL-5: DR restore passes the FULL archive layout (live finding #2)
The live scratch DR exposed the second half of PVE's all-or-nothing explicit-params restore: mountpoints NOT named in the params are silently DROPPED - the DR guest came up without its mp0/mp1 data volumes (boot passed; the customer's world did not ride along). drRestoreOverrides now derives the COMPLETE param set from the archive's extracted config: explicit rootfs, every storage-backed mpN passed through (size + in-guest path + backup flag preserved so vzrestore extracts its content), the two structural binds replaced by 4d-swapped throwaways; unknown bind mpN or unparseable size refuses loudly. Snapshot sections never shadow the current config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,3 +1,43 @@
|
||||
## v0.75.0 — DR bring-up structural bind overrides + real-bind swap (GL-5 / go-live G8) (2026-07-08)
|
||||
|
||||
Implements the verdict of `felhom.eu/documentation/audits/SPIKE-dr-bindmount-source-2026-07-07.md`:
|
||||
`bring-up -mode dr` of a customer archive FAILED outright because the archive carries the two
|
||||
structural host-bind mountpoints (mp8 parent bind, mp9 bootstrap bind) that a `pct restore` under
|
||||
the privsep token cannot recreate ("restoring 'mp8' to bind mount is only possible for root") —
|
||||
bring-up passed no `MountOverrides`. Provision was never affected (the golden has no mp8/mp9; the
|
||||
back-half adds them) — that asymmetry was the bug.
|
||||
|
||||
- **DR restore overrides** (`internal/reconcile/bringup.go`): ModeDRGuestLoss synthesizes throwaway
|
||||
1G-volume overrides for the two PLATFORM-CONSTANT mpN (mirroring backhalf.go's values; the spike's
|
||||
whole point — no archive parse for the LAYOUT) via the shared `throwawayVolumeOverride` format
|
||||
helper (extracted from `bindMountOverrides`; the restore-test's is-a-bind FILTER reads live
|
||||
configs, which DR by definition has none of). ModeProvision passes nil — regression-contract test.
|
||||
- **LIVE-DISCOVERED PVE constraint (not in the spike — it never ran an override restore):** mpN
|
||||
restore params without an explicit `rootfs` → HTTP 500 "mount points configured, but 'rootfs' not
|
||||
set" (same rule restoretest.go:211 documents for the live-config path). Fixed with NEW
|
||||
`Client.ExtractArchiveConfig` (GET `/nodes/{node}/vzdump/extractconfig` — **answers 200 under the
|
||||
scoped agent token**, verified live; PBS keys stay server-side, the spike's candidate-1 rejection
|
||||
holds): the archive's OWN embedded config supplies the rootfs SIZE only; unreadable/unparseable →
|
||||
clean refusal before any restore.
|
||||
- **Step 4d — real-bind swap** (DR only, pre-start): mp9 bootstrap host dir created (idempotent —
|
||||
a same-host guest-loss still has bootstrap.json there, untouched), then mp8/mp9 set to the REAL
|
||||
binds via the host runner (`pct set` — bind mounts are root@pam-only, hence NOT the API; sudoers
|
||||
already allowlists both shapes), one slot per call so a failure names the exact mpN and rolls
|
||||
back per the committed/launched envelope (C2 — never a silent half-wired success). The displaced
|
||||
throwaway volumes (PVE parks them as `unusedN`) are deleted via one config PUT (`delete=`);
|
||||
a scoped-token refusal logs the residue LOUDLY + warns in the result instead of widening
|
||||
privileges. NEW `proxmox.GuestConfig.Unused()`.
|
||||
- **Engine seam:** `EngineOptions.HostRunner` + `StateDir` (DR refuses up front on an API-only
|
||||
engine); the bring-up selftest wires the same ExecRunner shape as the back-half and removes the
|
||||
scratch vmid's mp9 host dir at teardown (never a real drive's bind source).
|
||||
- Tests (non-hollow): Scenario A exact-override + exact-swap-command + unusedN-delete asserts;
|
||||
provision-nil regression; C2 mid-swap rollback; C3 older-archive-without-mp9; DR-without-runner
|
||||
refusal; extract-failure refusal; 403-residue warn; archive rootfs parse (snapshot sections never
|
||||
shadow). Red-proofs: override synthesis reverted → A FAILS; unconditional overrides → B FAILS
|
||||
(both run→fail→revert).
|
||||
- Live validation (campaign-2-precedent scratch DR, §REPORT): see REPORT.md — the exact op that
|
||||
failed on v0.74.0 now round-trips with correctly-wired binds and no residue.
|
||||
|
||||
## v0.74.0 — pool membership re-asserted after restore-over-existing (campaign-2 R2) (2026-07-07)
|
||||
|
||||
Closes campaign-2 finding **R2** (`felhom.eu/documentation/tests/CAMPAIGN-2-2026-07-07.md`). Pool
|
||||
|
||||
Reference in New Issue
Block a user