R-106 + R-109: the DR recipe records the resolved namespace and names the backup target (v0.118.0)
Both defects were live on both demo boxes: the recipe said namespace "root" while storage.cfg said demo-felhom/demo-hp, and it never named which of two content=backup dir storages holds the local archives. R-106: the namespace came from the listed snapshot, but PBS omits `ns` per item once the list is namespace-scoped, so it was always empty and normalised to "root". It now resolves from the pbs STORAGE (storage.cfg's `namespace`) — the same field vzdump makes PVE read, so the recipe cannot disagree with the backup. R-109: backup_target resolves from the primary tier of cfg.Backup.BackupTiers(), the function the scheduler consults, and carries the mountpoint that separates /mnt/hdd_1 from /var/lib/vz. The resolver reports the tier IN EFFECT (daemon-start config), not agent.json on disk — a target move rewrites the file and deliberately does not restart. Unresolvable is recorded as unresolvable: resolved|unknown plus a distinct reason, never a default, an empty string, or a placeholder. Needs hub v0.83.0 — AssembleDRRecipe allow-lists top-level keys, so backup_target would otherwise be stored intact and dropped before any operator saw it. 9 tests, 4 red-proofs (each mutation asserted to have landed). Suite rc=0, 29 ok.
This commit is contained in:
+26
-1
@@ -89,6 +89,7 @@ type Collector struct {
|
||||
selfUpdate SelfUpdateReporter // D1: agent self-update pending status (nil → false)
|
||||
mgmtPlane MgmtPlaneReporter // G1: management-plane health (nil → stanza omitted)
|
||||
oob OOBReporter // H1: operator-access health (nil → stanza omitted)
|
||||
backupTarget func() ConfiguredBackupTarget // R-109: primary backup tier id (nil → recipe records unknown)
|
||||
hostID string
|
||||
agentVersion string
|
||||
logger *slog.Logger
|
||||
@@ -123,6 +124,30 @@ func (c *Collector) SetTempReader(t TempReader) *Collector {
|
||||
return c
|
||||
}
|
||||
|
||||
// SetBackupTargetResolver wires the DR recipe to the agent's own backup config (R-109), so the recipe
|
||||
// can name WHICH storage holds the local whole-guest archives. Returns the collector for chaining.
|
||||
//
|
||||
// The resolver MUST report the tier that is IN EFFECT, which is the daemon-start snapshot — NOT the
|
||||
// current contents of agent.json. A backup-target move rewrites that file and deliberately does not
|
||||
// restart the agent (the E-1 lesson: restarting mid-backup records a spurious failure for a run that
|
||||
// succeeded), so between the write and the restart the file names a target no backup is writing to yet.
|
||||
// Re-reading the file here — the live-reload shape used for escrow.pbs_storage_id — would make the
|
||||
// recipe point at the new storage while every archive still landed on the old one. One state, one
|
||||
// owner: the recipe follows what performs the backup.
|
||||
func (c *Collector) SetBackupTargetResolver(f func() ConfiguredBackupTarget) *Collector {
|
||||
c.backupTarget = f
|
||||
return c
|
||||
}
|
||||
|
||||
// configuredBackupTarget consults the resolver. An unwired seam is reported as NOT KNOWN — never as a
|
||||
// guess — so the recipe records an explicit unknown instead of a target the agent never verified.
|
||||
func (c *Collector) configuredBackupTarget() ConfiguredBackupTarget {
|
||||
if c.backupTarget == nil {
|
||||
return ConfiguredBackupTarget{}
|
||||
}
|
||||
return c.backupTarget()
|
||||
}
|
||||
|
||||
// SetCapabilityProber wires the privileged-capability self-check (v0.44.0): each collect runs it
|
||||
// and attaches the snapshot. nil → the report carries an empty []. Returns the collector for chaining.
|
||||
func (c *Collector) SetCapabilityProber(probe func(ctx context.Context) []capability.Status) *Collector {
|
||||
@@ -231,7 +256,7 @@ func (c *Collector) Collect(ctx context.Context) (*HostReport, error) {
|
||||
}
|
||||
// DR recipe host-half — derived from the just-collected guest/storage/PBS facts (no new reads).
|
||||
// Secret-free by construction (identifiers/intents/sizes/coordinates only).
|
||||
report.DRRecipe = BuildDRRecipeHostHalf(report.Guests, report.StorageTargets, report.PBSSnapshots)
|
||||
report.DRRecipe = BuildDRRecipeHostHalf(report.Guests, report.StorageTargets, report.PBSSnapshots, c.configuredBackupTarget())
|
||||
// S3: offsite-tunnel status stanza (nil reporter = feature disabled → omitted; the pubkey in
|
||||
// it is the operator's revocation-recovery handle).
|
||||
if c.wg != nil {
|
||||
|
||||
Reference in New Issue
Block a user