hub v0.7.3: ingest agent backups + restore_tests (slice 6 Phase A)
Accept + persist the now-populated host-report backups/restore_tests. Mirror structs in hostReportPayload; persisted via report_json (no schema change); a FAILED restore-test is logged prominently (loudest DR signal). Shared golden updated byte-identical with felhom-agent; bidirectional key-set tests added. Build/deploy deferred (backward-compatible). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -258,11 +258,43 @@ type hostReportPayload struct {
|
||||
ControllerVersion string `json:"controller_version"`
|
||||
} `json:"guests"`
|
||||
StorageTargets []hostStorageTarget `json:"storage_targets"`
|
||||
Backups []hostBackup `json:"backups"` // slice 6
|
||||
RestoreTests []hostRestoreTest `json:"restore_tests"` // slice 6
|
||||
Cloudflared struct {
|
||||
Status string `json:"status"`
|
||||
} `json:"cloudflared"`
|
||||
}
|
||||
|
||||
// hostBackup / hostRestoreTest mirror the agent's hub.Backup / hub.RestoreTest wire
|
||||
// contract field-for-field (slice 6, doc 03 §8). DUPLICATED contract — the golden stays
|
||||
// byte-identical with felhom-agent's copy and the key-set tests guard drift. The hub
|
||||
// persists these via report_json (no new columns this slice) and surfaces a FAILED
|
||||
// restore-test prominently (the loudest DR signal). The rich backup policy is slice 10.
|
||||
type hostBackup struct {
|
||||
TargetID string `json:"target_id"`
|
||||
VMID int `json:"vmid"`
|
||||
Archive string `json:"archive"`
|
||||
Mode string `json:"mode"`
|
||||
CrashConsistent bool `json:"crash_consistent"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
StartedAt string `json:"started_at"`
|
||||
DurationSeconds float64 `json:"duration_seconds"`
|
||||
UncoveredVolumes []string `json:"uncovered_volumes"`
|
||||
}
|
||||
|
||||
type hostRestoreTest struct {
|
||||
SourceArchive string `json:"source_archive"`
|
||||
SourceTier string `json:"source_tier"`
|
||||
ScratchVMID int `json:"scratch_vmid"`
|
||||
Pass bool `json:"pass"`
|
||||
Verified string `json:"verified"`
|
||||
Error string `json:"error,omitempty"`
|
||||
TestedAt string `json:"tested_at"`
|
||||
DurationSeconds float64 `json:"duration_seconds"`
|
||||
}
|
||||
|
||||
// hostStorageTarget mirrors the agent's hub.StorageTarget wire contract field-for-field.
|
||||
// It is a DUPLICATED contract (no shared types module yet); testdata/host-report.golden.json
|
||||
// must stay byte-identical with felhom-agent's copy and the key-set test guards drift.
|
||||
@@ -398,8 +430,23 @@ func (h *Handler) handleHostReport(w http.ResponseWriter, r *http.Request) {
|
||||
hostID, disconnected, len(rep.StorageTargets))
|
||||
}
|
||||
|
||||
h.logger.Printf("[INFO] host-report from %s (%d guests, %d storage targets, %d bytes)",
|
||||
hostID, len(rep.Guests), len(rep.StorageTargets), len(body))
|
||||
// restore_tests (slice 6): a FAILED self-restore-test is the loudest DR signal there is
|
||||
// — surface it prominently. A backup whose vzdump failed is also worth a warning.
|
||||
for _, rt := range rep.RestoreTests {
|
||||
if !rt.Pass {
|
||||
h.logger.Printf("[WARN] host %s restore-test FAILED: archive=%s tier=%s scratch=%d err=%q",
|
||||
hostID, rt.SourceArchive, rt.SourceTier, rt.ScratchVMID, rt.Error)
|
||||
}
|
||||
}
|
||||
for _, bk := range rep.Backups {
|
||||
if !bk.Success {
|
||||
h.logger.Printf("[WARN] host %s backup FAILED: target=%s vmid=%d err=%q",
|
||||
hostID, bk.TargetID, bk.VMID, bk.Error)
|
||||
}
|
||||
}
|
||||
|
||||
h.logger.Printf("[INFO] host-report from %s (%d guests, %d storage targets, %d backups, %d restore-tests, %d bytes)",
|
||||
hostID, len(rep.Guests), len(rep.StorageTargets), len(rep.Backups), len(rep.RestoreTests), len(body))
|
||||
|
||||
blocked := false
|
||||
if cc, err := h.store.GetCustomerConfig(custID); err == nil && cc != nil && cc.Status == "blocked" {
|
||||
|
||||
Reference in New Issue
Block a user