a411cde7c4
sysfacts reads the agent's top-level guest_disk_trim stanza (schedule + per-guest last attempt: vmid, last_attempt_at, ok, bytes_trimmed, mounts, duration_seconds, last_ok_at, error) into a field-by-field mirror. The System page's new 'Last disk trim' column shows the last successful trim and the GiB it freed; amber when the newest attempt failed (error shown) or last_ok_at is older than 14 days (judged on the success time, never the attempt time); '—' when the agent sends no stanza. wire_contract_gate: SUBTREE_MIRRORS checks guest_disk_trim field by field BOTH ways against sysfacts.DiskTrim; decoys (ok renamed, last_ok_at dropped) in test_gate_decoys.py. Decision 139. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
73 lines
3.8 KiB
Go
73 lines
3.8 KiB
Go
package sysfacts
|
|
|
|
import "testing"
|
|
|
|
const full = `{"system":{"pve_version":"pve-manager/9.0.11/abc","kernel_version":"Linux 7.0.14-20-pve #1 SMP","vmid":9201,
|
|
"read_at":"2026-10-04T15:00:00Z","facts":{"host":{"debian":"13.7","kernel_running":"7.0.14-20-pve","kernel_next_boot":"7.0.2-6-pve",
|
|
"held":["tzdata"],"tainted":4225,"oops_this_boot":true,"kernel_panic":10,"crash_guard":{"armed":false,"tripped":true,
|
|
"tripped_at":"2026-10-04T16:00:00Z","unclean_boots":["2026-10-04T15:40:00Z"],"unclean_boots_24h":1}},
|
|
"guest":{"debian":"13.7","docker_engine":"29.7.2","containerd":"2.3.3-1","live_restore":"on"}}}}`
|
|
|
|
func TestParse_Full(t *testing.T) {
|
|
s := Parse(full)
|
|
if !s.Present || ShortPVE(s.PVEVersion) != "9.0.11" || ShortKernel(s.KernelVersion) != "7.0.14-20-pve" || s.VMID != 9201 {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
if s.Host.Debian != "13.7" || len(s.Host.Held) != 1 || !*s.Host.OopsThisBoot || !s.Host.CrashGuard.Tripped || !s.NextBootDiffers() {
|
|
t.Fatalf("host %+v", s.Host)
|
|
}
|
|
if s.Guest.DockerEngine != "29.7.2" || s.Guest.LiveRestore != "on" {
|
|
t.Fatalf("guest %+v", s.Guest)
|
|
}
|
|
}
|
|
|
|
// An agent older than v0.142.0, or a stanza whose facts failed: unknown everywhere, never empty.
|
|
// Red-proof: drop the orUnknown on the guest fields and the partial case fails.
|
|
func TestParse_AbsentAndPartialAreUnknown(t *testing.T) {
|
|
s := Parse(`{"host":{}}`)
|
|
if s.Present || s.PVEVersion != Unknown || s.Guest.DockerEngine != Unknown || s.Host.Held != nil {
|
|
t.Fatalf("absent: %+v", s)
|
|
}
|
|
s = Parse(`{"system":{"pve_version":"pve-manager/9.0.11/x","facts_error":"no running customer guest"}}`)
|
|
if !s.Present || s.Guest.DockerEngine != Unknown || s.Guest.LiveRestore != Unknown || s.Host.KernelRunning != Unknown || s.FactsError == "" {
|
|
t.Fatalf("partial: %+v", s)
|
|
}
|
|
// facts present but empty fields (a guest that answered nothing): still unknown, not ""
|
|
s = Parse(`{"system":{"pve_version":"x","facts":{"host":{},"guest":{"docker_engine":"","live_restore":"","containerd":""}}}}`)
|
|
if s.Guest.DockerEngine != Unknown || s.Guest.LiveRestore != Unknown || s.Guest.Containerd != Unknown || s.Host.Debian != Unknown {
|
|
t.Fatalf("empty facts fields: %+v", s)
|
|
}
|
|
if s.NextBootDiffers() {
|
|
t.Fatal("unknown kernels must not read as 'differs'")
|
|
}
|
|
}
|
|
|
|
// R-444: the top-level `guest_disk_trim` stanza is read on its own — with or without a `system` stanza — and an absent
|
|
// stanza stays nil (the page says "—", never a made-up trim). The fixture is the agent's TestReportJSONShape output shape.
|
|
func TestParse_DiskTrim(t *testing.T) {
|
|
s := Parse(`{"host":{},"guest_disk_trim":{"schedule":"weekly, Wednesday from 10:00","guests":[
|
|
{"vmid":9201,"last_attempt_at":"2026-10-07T08:30:00Z","ok":true,"bytes_trimmed":90143313920,"mounts":2,"duration_seconds":24.4,"last_ok_at":"2026-10-07T08:30:00Z"},
|
|
{"vmid":9202,"last_attempt_at":"2026-10-07T08:35:00Z","ok":false,"bytes_trimmed":0,"mounts":0,"duration_seconds":1.2,"error":"pct fstrim: exit 255"}]}}`)
|
|
if s.Present {
|
|
t.Error("no system stanza: Present must stay false")
|
|
}
|
|
d := s.DiskTrim
|
|
if d == nil || d.Schedule != "weekly, Wednesday from 10:00" || len(d.Guests) != 2 {
|
|
t.Fatalf("guest_disk_trim misread: %+v", d)
|
|
}
|
|
g, f := d.Guests[0], d.Guests[1]
|
|
if g.VMID != 9201 || !g.OK || g.BytesTrimmed != 90143313920 || g.Mounts != 2 || g.DurationSeconds != 24.4 ||
|
|
g.LastAttemptAt != "2026-10-07T08:30:00Z" || g.LastOKAt != "2026-10-07T08:30:00Z" {
|
|
t.Errorf("guest 9201 misread: %+v", g)
|
|
}
|
|
if f.OK || f.Error != "pct fstrim: exit 255" || f.LastOKAt != "" {
|
|
t.Errorf("guest 9202 misread: %+v", f)
|
|
}
|
|
if s = Parse(full); s.DiskTrim != nil {
|
|
t.Errorf("a report with no guest_disk_trim must read nil, got %+v", s.DiskTrim)
|
|
}
|
|
if s = Parse(`{"system":{"pve_version":"x"},"guest_disk_trim":{"schedule":"w","guests":[]}}`); !s.Present || s.DiskTrim == nil || len(s.DiskTrim.Guests) != 0 {
|
|
t.Errorf("with a system stanza the trim must still be read: %+v", s)
|
|
}
|
|
}
|