ed97232598
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.
72 lines
3.2 KiB
Go
72 lines
3.2 KiB
Go
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 }
|