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:
@@ -462,6 +462,7 @@ func runDaemon(cfg config.Config, logger *slog.Logger, logRing *applog.Ring) int
|
||||
pbsTargets := pbsTargetsFromPVE(cfg, px, logger)
|
||||
pbsReporter := pbs.NewLiveSnapshotReporter(pbsTargets, pbsStore, pbs.DefaultLiveSnapshotTimeout, logger)
|
||||
collector := hub.NewCollector(px, hub.SystemctlProber{}, observer, backupStore, backupStore, pbsReporter, cfg.Hub.HostID, version, logger)
|
||||
collector.SetBackupTargetResolver(primaryBackupTargetOf(cfg)) // R-109: the recipe names the live target
|
||||
// Privileged-capability self-check (v0.44.0): probe the sudoers grants the non-root agent
|
||||
// depends on. The probe runs `sudo -n -l` LITERALLY (a policy LIST, never executing the
|
||||
// command), so it uses a DIRECT runner regardless of the agent's privileged mode. Probe once at
|
||||
@@ -1238,6 +1239,30 @@ func readTrimmed(path string) (string, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// primaryBackupTargetOf returns the resolver the DR recipe uses to name WHICH storage holds this box's
|
||||
// local whole-guest archives (R-109).
|
||||
//
|
||||
// It reads the PRIMARY tier out of cfg.Backup.BackupTiers() rather than calling BackupTarget() directly.
|
||||
// Both return the same string today — BackupTiers() builds tier 0 from BackupTarget() — but the tier
|
||||
// list is the function the scheduler itself consults, so if primary-tier derivation ever changes the
|
||||
// recipe follows it instead of quietly disagreeing with the backup. One state, one owner.
|
||||
//
|
||||
// cfg is captured BY VALUE on purpose: that is the daemon-start snapshot, which is the config actually
|
||||
// in effect. See SetBackupTargetResolver for why re-reading agent.json here would be wrong.
|
||||
func primaryBackupTargetOf(cfg config.Config) func() hub.ConfiguredBackupTarget {
|
||||
return func() hub.ConfiguredBackupTarget {
|
||||
tiers, _ := cfg.Backup.BackupTiers() // warnings are logged where the tiers are armed
|
||||
for _, t := range tiers {
|
||||
if t.Primary {
|
||||
return hub.ConfiguredBackupTarget{StorageID: t.TargetID, Known: true}
|
||||
}
|
||||
}
|
||||
// Unreachable with today's BackupTiers (tier 0 is always primary), and if that ever stops being
|
||||
// true the recipe says "I could not tell" rather than picking a tier at random.
|
||||
return hub.ConfiguredBackupTarget{}
|
||||
}
|
||||
}
|
||||
|
||||
// buildRestoreTestScheduler constructs the restore-test cadence scheduler from config. It
|
||||
// disables the cadence (returns a scheduler that just waits) when the cadence is off or the
|
||||
// scratch band / restore storage is invalid — a misconfig must not crash the daemon, and the
|
||||
@@ -1560,6 +1585,10 @@ func runSelftestHub(ctx context.Context, cfg config.Config, logger *slog.Logger)
|
||||
// the selftest reflects exactly what a freshly-restarted daemon's first collect emits.
|
||||
pbsReporter := pbs.NewLiveSnapshotReporter(pbsTargetsFromPVE(cfg, px, logger), pbs.NewSnapshotStore(), pbs.DefaultLiveSnapshotTimeout, logger)
|
||||
collector := hub.NewCollector(px, hub.SystemctlProber{}, observer, nil, nil, pbsReporter, cfg.Hub.HostID, version, logger)
|
||||
// R-109: wire the backup-target resolver here TOO. Without it selftest=hub would print a recipe whose
|
||||
// backup_target reads unknown/agent_backup_config_unavailable while the daemon's is resolved — and
|
||||
// this one-shot exists precisely so "the report it would send" can be trusted to match.
|
||||
collector.SetBackupTargetResolver(primaryBackupTargetOf(cfg))
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
Reference in New Issue
Block a user