ee71abd1d4
Operator ruling 09 §3 decision 139. One exact sudoers rule FELHOM_FSTRIM
(`/usr/sbin/pct ^fstrim [0-9]+$`) + manifest entry guest-fstrim; new
internal/fstrim job: due Wednesday from 10:00 host-local, starts only
10:00-20:59, holds backup.InFlight (busy -> deferred to the next hourly
tick), failed trim retried at most 3x per week, bytes parsed from the
"(N bytes) trimmed" lines, last result per guest persisted in
<state_dir>/guest-disk-trim.json and reported as guest_disk_trim.
Config opt-out: "disk_trim": {"disable": true}.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
55 lines
1.9 KiB
Go
55 lines
1.9 KiB
Go
package hub
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
// R-444: the guest_disk_trim stanza must reach a report built through the PRODUCTION collect path, be absent from
|
|
// the wire when the job is not wired, and carry the keys the hub's System page reads.
|
|
|
|
type fakeDiskTrim struct{ st *GuestDiskTrimStatus }
|
|
|
|
func (f fakeDiskTrim) GuestDiskTrimStatus(context.Context) *GuestDiskTrimStatus { return f.st }
|
|
|
|
func TestCollect_GuestDiskTrim(t *testing.T) {
|
|
px := &fakePx{node: "n", ns: newTestNodeStatus()}
|
|
c := NewCollector(px, fakeProber{status: "running", detail: "connected"}, fakeObserver{}, nil, nil, nil, "h", "0.150.0", quietLogger())
|
|
r, err := c.Collect(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Collect: %v", err)
|
|
}
|
|
b, _ := json.Marshal(r)
|
|
var m map[string]any
|
|
_ = json.Unmarshal(b, &m)
|
|
if _, ok := m["guest_disk_trim"]; ok {
|
|
t.Fatalf("guest_disk_trim on the wire with no reporter wired: %s", b)
|
|
}
|
|
|
|
c.SetGuestDiskTrimReporter(fakeDiskTrim{st: &GuestDiskTrimStatus{Schedule: "weekly", Guests: []GuestDiskTrim{{
|
|
VMID: 9201, LastAttemptAt: "2026-10-07T08:30:00Z", OK: true, BytesTrimmed: 90143313920, Mounts: 2,
|
|
DurationSeconds: 24.4, LastOKAt: "2026-10-07T08:30:00Z",
|
|
}}}})
|
|
r, err = c.Collect(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Collect: %v", err)
|
|
}
|
|
b, _ = json.Marshal(r)
|
|
m = nil
|
|
_ = json.Unmarshal(b, &m)
|
|
dt, ok := m["guest_disk_trim"].(map[string]any)
|
|
if !ok || dt["schedule"] != "weekly" {
|
|
t.Fatalf("guest_disk_trim missing or wrong on the wire: %s", b)
|
|
}
|
|
g := dt["guests"].([]any)[0].(map[string]any)
|
|
for _, k := range []string{"vmid", "last_attempt_at", "ok", "bytes_trimmed", "mounts", "duration_seconds", "last_ok_at"} {
|
|
if _, ok := g[k]; !ok {
|
|
t.Fatalf("guest_disk_trim.guests[0] lacks %q: %v", k, g)
|
|
}
|
|
}
|
|
if g["bytes_trimmed"] != float64(90143313920) || g["ok"] != true {
|
|
t.Fatalf("values did not survive the round trip: %v", g)
|
|
}
|
|
}
|