Fix silent fingerprint loss for LXC and VMs

processFingerprint ran v.Index(i).Interface() then reflect.ValueOf(item),
dropping addressability and making reflect.Call panic on every iteration
with "Container as type *Container". The defer/recover in
collectFingerprints swallowed it, so LXC and VM fingerprints never
landed in the store — change-detection and discovery for those resource
types have been broken since v6.

Pass the slice element's address straight through (.Addr()) so the
generator's pointer receiver gets the right type. Add a regression test
that fails if anyone goes back through .Interface().
This commit is contained in:
rcourtman
2026-05-10 20:31:46 +01:00
parent 77479fb9ca
commit 9d1d24bdf1
2 changed files with 56 additions and 6 deletions
+11 -6
View File
@@ -1104,14 +1104,19 @@ func (s *Service) processFingerprint(
default:
}
item := v.Index(i).Interface()
itemVal := reflect.ValueOf(item)
// Generators take *Container / *VM. Reach through .Addr() — going via
// .Interface() drops addressability and reflect.Call panics on the type
// mismatch.
elem := v.Index(i)
if !elem.CanAddr() {
continue
}
node := itemVal.FieldByName("Node").String()
name := itemVal.FieldByName("Name").String()
vmid := itemVal.FieldByName("VMID").Int()
node := elem.FieldByName("Node").String()
name := elem.FieldByName("Name").String()
vmid := elem.FieldByName("VMID").Int()
args := []reflect.Value{reflect.ValueOf(node), reflect.ValueOf(item)}
args := []reflect.Value{reflect.ValueOf(node), elem.Addr()}
newFP := fpFuncVal.Call(args)[0].Interface().(*ContainerFingerprint)
fpKey := prefix + node + ":" + newFP.ResourceID
+45
View File
@@ -850,6 +850,51 @@ func TestService_FingerprintCollectionAndDiscoveryWrappers(t *testing.T) {
service.collectFingerprints(context.Background())
}
// Regression: collectFingerprints used to panic-and-recover for every LXC/VM
// because processFingerprint went through reflect.Value.Interface(), losing
// addressability so reflect.Call hit a Container-vs-*Container type mismatch.
// Fingerprints for LXC and VMs silently never landed in the store. This test
// fails (no rows under the expected keys) if anyone reintroduces that.
func TestService_CollectFingerprints_LXCAndVM(t *testing.T) {
store, err := NewStore(t.TempDir())
if err != nil {
t.Fatalf("NewStore error: %v", err)
}
store.crypto = nil
state := StateSnapshot{
Containers: []Container{
{VMID: 100, Name: "lxc-one", Node: "pve1", Status: "running", OSTemplate: "debian-12", CPUs: 2, MaxMemory: 2 << 30},
{VMID: 101, Name: "lxc-two", Node: "pve1", Status: "running", OSTemplate: "ubuntu-22", CPUs: 1, MaxMemory: 1 << 30},
},
VMs: []VM{
{VMID: 200, Name: "vm-one", Node: "pve1", Status: "running", OSName: "ubuntu", CPUs: 4, MaxMemory: 4 << 30},
},
}
service := NewService(store, nil, DefaultConfig())
service.SetReadState(readStateFromSnapshot(state))
service.collectFingerprints(context.Background())
for _, key := range []string{
"system-container:pve1:100",
"system-container:pve1:101",
"vm:pve1:200",
} {
fp, err := store.GetFingerprint(key)
if err != nil {
t.Fatalf("GetFingerprint(%q) error: %v", key, err)
}
if fp == nil {
t.Fatalf("expected fingerprint at %q, got nil — processFingerprint silently dropped it", key)
}
if fp.Hash == "" {
t.Fatalf("fingerprint %q has empty hash", key)
}
}
}
func TestService_PromptsAndDiscoveryLoop(t *testing.T) {
service := NewService(nil, nil, DefaultConfig())