v0.95.0: SMART coverage — union-path drives + LVM/dm root + device model
Implements SPIKE-smart-coverage-2026-07-25 fixes B+A (additive; MinAgent unchanged). Fix B: storage.SmartReader.SMARTForBacking wired into the /disks union path (localapi Smart seam) so registry/USB drives get a real SMART read (watchdog Known stays enrich-free). Fix A: smartDeviceFor resolves dm/LVM to the whole disk via /sys/block/<dm>/slaves (recursive; skips >1-disk); the builtin local dir on the LVM root gets a SMART-only device from its containing filesystem (never touches backing/durable_id). SmartSummary.ModelName captured from smartctl. Fix C (-d sat) stays rejected. Tests + red-proofs (dm multi-disk skip, enrich smartHint, union routing); Known-path-never-SMARTs asserted.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/hub"
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
|
||||
)
|
||||
|
||||
// Fix A (A2): the builtin `local` dir lives on the LVM root, so its backing is empty by design — but
|
||||
// enrich now resolves the containing filesystem (/ → /dev/mapper/pve-root) and, via the dm sysfs
|
||||
// slaves, the physical disk (/dev/sda). The system disk stops reading "Nincs adat".
|
||||
// Red-proof: drop the `smartDev = ob.smartHint` fallback in enrich → local stays UNKNOWN and SMART
|
||||
// is never called on /dev/sda.
|
||||
func TestObserve_SystemDirSMARTViaContainingFS(t *testing.T) {
|
||||
fixtureSysfs(t, map[string][]string{"dm-1": {"sda3"}}, map[string]string{"dm-1": "pve-root"})
|
||||
|
||||
api := &fakeStorageAPI{
|
||||
node: "demo-felhom",
|
||||
cluster: []proxmox.Storage{{Storage: "local", Type: "dir", Path: "/var/lib/vz"}},
|
||||
nodeSt: []proxmox.Storage{{Storage: "local", Type: "dir", Path: "/var/lib/vz", Active: 1}},
|
||||
}
|
||||
host := &fakeHostReader{
|
||||
mounts: []Mount{{Device: "/dev/mapper/pve-root", MountPoint: "/", FSType: "ext4"}},
|
||||
}
|
||||
ops := &fakeHostOps{smartByDevice: map[string]hub.SmartSummary{"/dev/sda": {Health: hub.SmartPassed, ModelName: strptr("AirDisk 512GB SSD")}}}
|
||||
|
||||
got, err := NewObserver(api, host, ops, quietLogger()).Observe(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
local := byName(got)["local"]
|
||||
if local.Smart.Health != hub.SmartPassed {
|
||||
t.Errorf("system disk SMART not enriched via the containing fs: health=%q", local.Smart.Health)
|
||||
}
|
||||
if local.Smart.ModelName == nil || *local.Smart.ModelName != "AirDisk 512GB SSD" {
|
||||
t.Errorf("model not carried: %v", local.Smart.ModelName)
|
||||
}
|
||||
// SMART must have run on the resolved PHYSICAL disk, never the dm/mapper node.
|
||||
if len(ops.smartDevices) != 1 || ops.smartDevices[0] != "/dev/sda" {
|
||||
t.Errorf("SMART should target /dev/sda, got %v", ops.smartDevices)
|
||||
}
|
||||
// The backing device / durable_id must stay untouched by the SMART-only resolution.
|
||||
if local.BackingDevice != "" {
|
||||
t.Errorf("system-dir SMART resolution leaked into BackingDevice: %q", local.BackingDevice)
|
||||
}
|
||||
}
|
||||
|
||||
// The watchdog Known() path MUST remain enrich-free (its slow root-shelling reads are the reason it
|
||||
// exists as a separate fast path). Known must never invoke SMART.
|
||||
// Red-proof: route Known through enrich → smartDevices is non-empty and this fails.
|
||||
func TestKnown_NeverInvokesSMART(t *testing.T) {
|
||||
fixtureSysfs(t, map[string][]string{"dm-1": {"sda3"}}, map[string]string{"dm-1": "pve-root"})
|
||||
api := &fakeStorageAPI{
|
||||
node: "demo-felhom",
|
||||
cluster: []proxmox.Storage{{Storage: "local", Type: "dir", Path: "/var/lib/vz"}},
|
||||
nodeSt: []proxmox.Storage{{Storage: "local", Type: "dir", Path: "/var/lib/vz", Active: 1}},
|
||||
}
|
||||
host := &fakeHostReader{mounts: []Mount{{Device: "/dev/mapper/pve-root", MountPoint: "/", FSType: "ext4"}}}
|
||||
ops := &fakeHostOps{smartByDevice: map[string]hub.SmartSummary{"/dev/sda": {Health: hub.SmartPassed}}}
|
||||
|
||||
if _, err := NewObserver(api, host, ops, quietLogger()).Known(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ops.smartDevices) != 0 {
|
||||
t.Errorf("Known() invoked SMART %d time(s) — it must stay enrich-free: %v", len(ops.smartDevices), ops.smartDevices)
|
||||
}
|
||||
}
|
||||
|
||||
func strptr(s string) *string { return &s }
|
||||
Reference in New Issue
Block a user