Files
felhom-agent/internal/storage/observe_identity_test.go
T
admin c5daa9480d
gates / gates (push) Successful in 31s
Physical-disk identity of every storage place + POST /disks/same-disk (felhom.eu architecture/16 §3/§4)
The agent names the physical disk(s) under each storage place from /sys, udev and /proc/mounts only
(partition -> disk, dm/LVM thin -> slaves, loop -> backing file), keys them (eui / serial:<bus>:<model>
:<serial> / vendor WWN) and says proven or unknown (vm, network, usb_bridge, weak_wwn, no_serial).
GET /disks and the hub report's storage_targets gain kind, physical_disks, identity,
identity_reason (golden updated; the hub copy changes in the same session). POST /disks/same-disk
maps two guest paths to places the agent itself made and answers {different, proven, reason};
an absent drive is unmapped, never the root disk. Tests on fixture trees for every spike case,
red-proofed. CHANGELOG entry follows the release (release-complete gate).

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
2026-10-15 21:09:18 +02:00

74 lines
2.9 KiB
Go

package storage
import (
"context"
"reflect"
"testing"
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
)
// The report rows carry the `16` §3 identity per storage type: local + lvmthin → the system disk; a dir
// on its own partition → that disk, kind "partition"; nfs/pbs → network; an UNMOUNTED dir → not computed
// (never the root disk — the same rule as backing_device).
func TestObserve_FillsPhysicalDiskIdentity(t *testing.T) {
f := newFx(t)
oneDiskLVM(f)
f.node("sdb", "", "ID_BUS=ata", "ID_MODEL=TOSHIBA_MQ04ABF100", "ID_SERIAL_SHORT=65NOP3HDT")
f.node("sdb1", "sdb")
f.mount("/dev/sdb1", "/mnt/usb-backup", "ext4")
res := f.resolver()
api := &fakeStorageAPI{
node: "demo",
cluster: []proxmox.Storage{
{Storage: "local", Type: "dir", Path: "/var/lib/vz"},
{Storage: "local-lvm", Type: "lvmthin", VGName: "pve", ThinPool: "data"},
{Storage: "usb-backup", Type: "dir", Path: "/mnt/usb-backup"},
{Storage: "gone", Type: "dir", Path: "/mnt/gone"},
{Storage: "nfs-arch", Type: "nfs", Server: "10.0.0.5", Export: "/x"},
},
nodeSt: []proxmox.Storage{
{Storage: "local", Type: "dir", Path: "/var/lib/vz", Active: 1},
{Storage: "local-lvm", Type: "lvmthin", Active: 1},
{Storage: "usb-backup", Type: "dir", Path: "/mnt/usb-backup", Active: 1},
{Storage: "gone", Type: "dir", Path: "/mnt/gone", Active: 1},
{Storage: "nfs-arch", Type: "nfs", Active: 1},
},
}
host := &fakeHostReader{
mounts: []Mount{
{Device: "/dev/mapper/pve-root", MountPoint: "/", FSType: "ext4"},
{Device: "/dev/sdb1", MountPoint: "/mnt/usb-backup", FSType: "ext4"},
},
exists: map[string]bool{"/dev/sdb1": true, "/dev/mapper/pve-root": true},
}
ob := NewObserver(api, host, nil, quietLogger())
ob.SetIdentity(res)
got, err := ob.Observe(context.Background())
if err != nil {
t.Fatal(err)
}
m := byName(got)
ssd := []string{"serial:ata:SanDisk_X600:182195804614"}
for _, n := range []string{"local", "local-lvm"} {
if tg := m[n]; tg.Kind != "system" || tg.Identity != "proven" || !reflect.DeepEqual(tg.PhysicalDisks, ssd) {
t.Errorf("%s: want system/proven on the SSD, got kind=%q id=%q disks=%v", n, tg.Kind, tg.Identity, tg.PhysicalDisks)
}
}
if tg := m["usb-backup"]; tg.Kind != "partition" || tg.Identity != "proven" || tg.PhysicalDisks[0] != "serial:ata:TOSHIBA_MQ04ABF100:65NOP3HDT" {
t.Errorf("usb-backup: got %+v", tg)
}
if tg := m["nfs-arch"]; tg.Kind != "network" || tg.IdentityReason != "network" {
t.Errorf("nfs: got kind=%q reason=%q", tg.Kind, tg.IdentityReason)
}
if tg := m["gone"]; tg.Identity != "" || len(tg.PhysicalDisks) != 0 {
t.Errorf("an unmounted dir must not be computed (never root's disk), got %+v", tg)
}
// Without a resolver the fields stay empty (the pre-0.157 shape).
plain, _ := NewObserver(api, host, nil, quietLogger()).Observe(context.Background())
if tg := byName(plain)["local"]; tg.Identity != "" || tg.Kind != "" {
t.Errorf("no resolver: want empty identity, got %+v", tg)
}
}