v0.174.0 — R-82 Slice B: one quiesce window, two backup tiers
MinAgent UNCHANGED — degrades gracefully against ANY older agent.
The agent gained per-target tiers in v0.97.0. The controller owns quiescing,
so the multi-tier schedule is reconciled here: every due tier is collected up
front and run inside ONE quiesce window (one stop, N sequential backups, one
resume). Two cycles on the weekly night would mean two app outages for one
night's work.
Dedup rule: local-only -> one quiesce; PBS-only -> one quiesce; BOTH due ->
ONE window with both backups inside; neither -> no quiesce.
- quiesce.TieredBackend + BackupTier + ErrTiersUnsupported (optional extension)
- agentapi: BackupTiers/BackupDueFor/StartBackupFor/BackupStatusFor;
targetQuery("") yields an EMPTY suffix so untargeted hits the pre-R-82 route
byte-for-byte
- Loop.resolveDueTiers = the dedup rule in one place, agent order preserved
- quiesceAndPollTiers + pollTier: app stays quiesced until the LAST tier
snapshots (resuming earlier loses app-consistency on the DR tier). Consequence
stated in the docs: both-due-night downtime = first tier's full backup + last
tier's snapshot, which is why tiers run fast-first.
- Manual 'Mentes most' covers EVERY tier, due-ness ignored.
- Window-gate safety valve now uses the OLDEST due tier, so a stale DR tier
cannot be starved by a fresher local one.
Capability detection: /backup/tiers 404 = pre-R-82 agent (the documented
route-probe mechanism). Not a featureProbes row on purpose — the loop needs the
tier LIST, not a yes/no. Degrade logged exactly once per process.
Tests +11, full suite green. Red-proofs #2 and #3 observed and restored.
This commit is contained in:
@@ -1,5 +1,78 @@
|
||||
## Changelog
|
||||
|
||||
### v0.174.0 — R-82 Slice B: one quiesce window, two backup tiers (2026-07-26)
|
||||
|
||||
**MinAgent UNCHANGED — deliberately.** This release degrades gracefully against ANY older agent; it
|
||||
does not require v0.97.0. Against a pre-R-82 agent it uses the untargeted single-tier path exactly as
|
||||
before, logs the degrade once, and **still takes the backup**.
|
||||
|
||||
The agent gained per-target backup tiers in v0.97.0 ("local daily + PBS weekly"). The **controller**
|
||||
owns quiescing, so the multi-tier schedule has to be reconciled here: on the weekly night both tiers
|
||||
come due at once, and two quiesce cycles would mean **two app outages for one night's work** —
|
||||
undoing the entire argument for weekly-over-daily.
|
||||
|
||||
### The dedup rule (specified, not emergent)
|
||||
|
||||
| local due | PBS due | result |
|
||||
|---|---|---|
|
||||
| yes | no | one quiesce, local backup |
|
||||
| no | yes | one quiesce, PBS backup |
|
||||
| **yes** | **yes** | **ONE quiesce window, BOTH backups inside it — never two cycles** |
|
||||
| no | no | no quiesce |
|
||||
|
||||
### Added
|
||||
- **`quiesce.TieredBackend`** (optional extension to `Backend`) + `quiesce.BackupTier`,
|
||||
`ErrTiersUnsupported`. A backend that does not implement it — or whose `Tiers` returns
|
||||
`ErrTiersUnsupported` — drives the pre-R-82 single-tier path unchanged.
|
||||
- **`agentapi` per-tier client**: `BackupTiers`, `BackupDueFor`, `StartBackupFor`,
|
||||
`BackupStatusFor` (`internal/agentapi/backup_tiers.go`). `targetQuery("")` yields an EMPTY suffix,
|
||||
so an untargeted call hits the untargeted route byte-for-byte.
|
||||
- **`Loop.resolveDueTiers`** — the dedup rule in one place, returning due tiers in AGENT ORDER.
|
||||
- **`Loop.quiesceAndPollTiers` + `pollTier`** — one marker, one stop, N sequential backups, one
|
||||
resume, tail polled to completion.
|
||||
|
||||
### Capability detection
|
||||
`GET /backup/tiers` 404 ⇒ pre-R-82 agent. This is the project's documented ROUTE-PROBE mechanism
|
||||
(`internal/agentapi/features.go`: "a route that shipped together with the coupled semantics either
|
||||
answers (2xx ⇒ supported) or 404s"). It is **not** registered in the `featureProbes` table on
|
||||
purpose: that table answers a yes/no at a UI entry point, whereas the loop needs the tier LIST
|
||||
itself, so a table row would be a second probe of the same route for no gain. The degrade is logged
|
||||
**exactly once per process** — once because it is a steady state during a rollout, never zero times
|
||||
because a silent degrade is indistinguishable from multi-tier working.
|
||||
|
||||
### Two decisions worth stating plainly
|
||||
|
||||
**The app stays quiesced until the LAST tier snapshots.** Resuming after tier 1's snapshot would
|
||||
leave the following tier capturing a RUNNING app — losing app-consistency on exactly the DR tier we
|
||||
most want it on. **Consequence, user-visible:** on the both-due night downtime is
|
||||
*(first tier's full backup)* + *(last tier's snapshot)*, not one snapshot. Tiers must therefore run
|
||||
**fast-first**: vzdump holds a guest lock so they are necessarily sequential, and the agent
|
||||
advertises primary (local) first — local-then-PBS makes downtime ≈ local backup + PBS snapshot,
|
||||
whereas the reverse would be ≈ PBS backup + local snapshot, far worse.
|
||||
|
||||
**A manual "Mentés most" covers EVERY tier**, in one window, due-ness ignored. A manual run that
|
||||
silently skipped the DR tier would be the same applied-and-empty fault in a different costume.
|
||||
|
||||
### Resilience (unchanged guarantees, extended per tier)
|
||||
- Marker written BEFORE anything stops; unquiesce guaranteed by `defer` and fires **exactly once**
|
||||
no matter which tier fails; a crash between two backups leaves the marker and `Recover()` restarts
|
||||
the stacks at startup.
|
||||
- One tier failing to START does not prevent the other tier's backup, and the app still resumes once.
|
||||
- One tier's due-check erroring does not drop the other tier's backup.
|
||||
- An agent advertising ZERO tiers falls back to the untargeted path — never "nothing to do".
|
||||
- The window gate's safety valve now evaluates the OLDEST (most overdue) due tier, so a stale DR
|
||||
tier cannot be starved by a fresher local one (`oldestAge`; a never-backed-up tier wins outright).
|
||||
|
||||
### Tests
|
||||
+11 in `internal/quiesce/tiers_test.go`; full suite green. Red-proofs observed and restored:
|
||||
- **#3 both-due night** — a per-tier cycle instead of one window fails with
|
||||
`want EXACTLY 1 stop and 1 start, got stops=2 starts=2`. The COUNT is the assertion; asserting
|
||||
only "both backups ran" would pass against a double-quiesce implementation.
|
||||
- **#2 new controller ↔ old agent** — treating `ErrTiersUnsupported` as "nothing due" fails with
|
||||
`OLD AGENT: a backup MUST still be taken via the untargeted path; got started=[]`. The hollow
|
||||
version of this test asserts only "no error", which passes while silently skipping the backup.
|
||||
|
||||
|
||||
### v0.173.0 — R-77: endpoint-drift detection, samba protected-set gate, channel log honesty (2026-07-26)
|
||||
|
||||
Source: `felhom.eu/documentation/audits/DIAG-agent-channel-2026-07-26.md`.
|
||||
|
||||
Reference in New Issue
Block a user