mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
327 lines
9.4 KiB
Go
327 lines
9.4 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
proxmoxmapper "github.com/rcourtman/pulse-go-rewrite/internal/recovery/mapper/proxmox"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
pveapi "github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
|
)
|
|
|
|
type canonicalBackupStorageClient struct {
|
|
mockPVEClientExtra
|
|
}
|
|
|
|
func (c *canonicalBackupStorageClient) GetStorage(ctx context.Context, node string) ([]pveapi.Storage, error) {
|
|
return []pveapi.Storage{
|
|
{Storage: "local", Content: "backup", Type: "dir", Enabled: 1, Active: 1},
|
|
}, nil
|
|
}
|
|
|
|
func (c *canonicalBackupStorageClient) GetStorageContent(ctx context.Context, node, storage string) ([]pveapi.StorageContent, error) {
|
|
return []pveapi.StorageContent{
|
|
{
|
|
Volid: "backup/vzdump-qemu-100-2026_03_11-10_00_00.vma.zst",
|
|
VMID: 100,
|
|
Size: 1024,
|
|
CTime: time.Date(2026, 3, 11, 10, 0, 0, 0, time.UTC).Unix(),
|
|
Content: "backup",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func backupReadStateResourceStore(resources []unifiedresources.Resource) *resourceOnlyStore {
|
|
return &resourceOnlyStore{resources: resources}
|
|
}
|
|
|
|
func backupReadState(resources []unifiedresources.Resource) unifiedresources.ReadState {
|
|
registry := unifiedresources.NewRegistry(nil)
|
|
registry.IngestResources(resources)
|
|
return registry
|
|
}
|
|
|
|
func TestPopulateGuestNodeMapFromReadState_UsesCanonicalWorkloads(t *testing.T) {
|
|
readState := backupReadState([]unifiedresources.Resource{
|
|
{
|
|
ID: "vm-1",
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: "vm-100",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node-from-store",
|
|
VMID: 100,
|
|
},
|
|
},
|
|
{
|
|
ID: "ct-1",
|
|
Type: unifiedresources.ResourceTypeSystemContainer,
|
|
Name: "ct-200",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "ct-node-from-store",
|
|
VMID: 200,
|
|
},
|
|
},
|
|
})
|
|
|
|
guestNodeMap := map[int]string{}
|
|
populateGuestNodeMapFromReadState(readState, "pve1", guestNodeMap)
|
|
|
|
if guestNodeMap[100] != "node-from-store" {
|
|
t.Fatalf("expected VM node from canonical read-state, got %q", guestNodeMap[100])
|
|
}
|
|
if guestNodeMap[200] != "ct-node-from-store" {
|
|
t.Fatalf("expected container node from canonical read-state, got %q", guestNodeMap[200])
|
|
}
|
|
}
|
|
|
|
func TestStorageNamesForNode_UsesCanonicalStoragePools(t *testing.T) {
|
|
readState := backupReadState([]unifiedresources.Resource{
|
|
{
|
|
ID: "storage-local",
|
|
Type: unifiedresources.ResourceTypeStorage,
|
|
Name: "local",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node1",
|
|
},
|
|
Storage: &unifiedresources.StorageMeta{
|
|
Content: "images,backup",
|
|
},
|
|
},
|
|
{
|
|
ID: "storage-shared",
|
|
Type: unifiedresources.ResourceTypeStorage,
|
|
Name: "shared",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
},
|
|
Storage: &unifiedresources.StorageMeta{
|
|
Content: "backup",
|
|
Nodes: []string{"node2", "node3"},
|
|
},
|
|
},
|
|
{
|
|
ID: "storage-no-backup",
|
|
Type: unifiedresources.ResourceTypeStorage,
|
|
Name: "fast",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node2",
|
|
},
|
|
Storage: &unifiedresources.StorageMeta{
|
|
Content: "images",
|
|
},
|
|
},
|
|
})
|
|
|
|
got := storageNamesForNode(readState, "pve1", "node2")
|
|
if len(got) != 1 || got[0] != "shared" {
|
|
t.Fatalf("expected canonical backup storage names [shared], got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestMonitorCalculateBackupOperationTimeout_UsesCanonicalReadState(t *testing.T) {
|
|
resources := make([]unifiedresources.Resource, 0, 61)
|
|
for i := 0; i < 61; i++ {
|
|
resources = append(resources, unifiedresources.Resource{
|
|
ID: fmt.Sprintf("vm-%d", i),
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: fmt.Sprintf("vm-%d", i),
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node1",
|
|
VMID: 100 + i,
|
|
},
|
|
})
|
|
}
|
|
|
|
m := &Monitor{
|
|
state: models.NewState(),
|
|
resourceStore: backupReadStateResourceStore(resources),
|
|
}
|
|
|
|
timeout := m.calculateBackupOperationTimeout("pve1")
|
|
if want := 122 * time.Second; timeout != want {
|
|
t.Fatalf("expected timeout %v from canonical workload count, got %v", want, timeout)
|
|
}
|
|
}
|
|
|
|
func TestMonitorPollGuestSnapshots_UsesCanonicalReadState(t *testing.T) {
|
|
m := &Monitor{
|
|
state: models.NewState(),
|
|
resourceStore: backupReadStateResourceStore([]unifiedresources.Resource{
|
|
{
|
|
ID: "vm-store-100",
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: "vm100",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node1",
|
|
VMID: 100,
|
|
},
|
|
},
|
|
{
|
|
ID: "ct-store-200",
|
|
Type: unifiedresources.ResourceTypeSystemContainer,
|
|
Name: "ct200",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node1",
|
|
VMID: 200,
|
|
},
|
|
},
|
|
}),
|
|
}
|
|
|
|
client := &mockPVEClientSnapshots{
|
|
snapshots: []pveapi.Snapshot{{Name: "snap1", SnapTime: 1234567890, Description: "from store"}},
|
|
}
|
|
|
|
m.pollGuestSnapshots(context.Background(), "pve1", client)
|
|
|
|
snapshot := m.state.GetSnapshot()
|
|
if len(snapshot.PVEBackups.GuestSnapshots) != 2 {
|
|
t.Fatalf("expected guest snapshots from canonical workloads, got %+v", snapshot.PVEBackups.GuestSnapshots)
|
|
}
|
|
}
|
|
|
|
func TestMonitorPollStorageBackupsWithNodes_UsesCanonicalReadStateForGuestNodeLookup(t *testing.T) {
|
|
m := &Monitor{
|
|
state: models.NewState(),
|
|
resourceStore: backupReadStateResourceStore([]unifiedresources.Resource{
|
|
{
|
|
ID: "vm-store-100",
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: "vm100",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "node2",
|
|
VMID: 100,
|
|
},
|
|
},
|
|
}),
|
|
}
|
|
|
|
client := &canonicalBackupStorageClient{}
|
|
nodes := []pveapi.Node{{Node: "node1", Status: "online"}}
|
|
|
|
m.pollStorageBackupsWithNodes(context.Background(), "pve1", client, nodes, map[string]string{"node1": "online"})
|
|
|
|
backups := m.state.GetSnapshot().PVEBackups.StorageBackups
|
|
if len(backups) != 1 {
|
|
t.Fatalf("expected one storage backup, got %+v", backups)
|
|
}
|
|
if backups[0].Node != "node2" {
|
|
t.Fatalf("expected guest node from canonical read-state, got %q", backups[0].Node)
|
|
}
|
|
}
|
|
|
|
func TestBuildPBSGuestCandidates_UsesCanonicalReadState(t *testing.T) {
|
|
readState := backupReadState([]unifiedresources.Resource{
|
|
{
|
|
ID: "vm-store-100",
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: "vm100",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "nodeA",
|
|
VMID: 100,
|
|
},
|
|
},
|
|
{
|
|
ID: "ct-store-200",
|
|
Type: unifiedresources.ResourceTypeSystemContainer,
|
|
Name: "ct200",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "nodeB",
|
|
VMID: 200,
|
|
},
|
|
},
|
|
})
|
|
|
|
candidates := buildPBSGuestCandidates(readState)
|
|
|
|
assertCandidate := func(key string, resourceType unifiedresources.ResourceType, node string, vmid int) {
|
|
t.Helper()
|
|
entries := candidates[key]
|
|
if len(entries) != 1 {
|
|
t.Fatalf("expected one candidate for %s, got %+v", key, entries)
|
|
}
|
|
if entries[0] != (proxmoxmapper.GuestCandidate{
|
|
SourceID: fmt.Sprintf("%s-store-%d", map[unifiedresources.ResourceType]string{unifiedresources.ResourceTypeVM: "vm", unifiedresources.ResourceTypeSystemContainer: "ct"}[resourceType], vmid),
|
|
ResourceType: resourceType,
|
|
DisplayName: fmt.Sprintf("%s%d", map[unifiedresources.ResourceType]string{unifiedresources.ResourceTypeVM: "vm", unifiedresources.ResourceTypeSystemContainer: "ct"}[resourceType], vmid),
|
|
InstanceName: "pve1",
|
|
NodeName: node,
|
|
VMID: vmid,
|
|
}) {
|
|
t.Fatalf("unexpected candidate for %s: %+v", key, entries[0])
|
|
}
|
|
}
|
|
|
|
assertCandidate("vm:100", unifiedresources.ResourceTypeVM, "nodeA", 100)
|
|
assertCandidate("ct:200", unifiedresources.ResourceTypeSystemContainer, "nodeB", 200)
|
|
}
|
|
|
|
func TestBuildProxmoxGuestInfoIndex_UsesCanonicalReadState(t *testing.T) {
|
|
readState := backupReadState([]unifiedresources.Resource{
|
|
{
|
|
ID: "vm-store-100",
|
|
Type: unifiedresources.ResourceTypeVM,
|
|
Name: "vm100",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "nodeA",
|
|
VMID: 100,
|
|
},
|
|
},
|
|
{
|
|
ID: "ct-store-200",
|
|
Type: unifiedresources.ResourceTypeSystemContainer,
|
|
Name: "ct200",
|
|
Status: unifiedresources.StatusOnline,
|
|
Proxmox: &unifiedresources.ProxmoxData{
|
|
Instance: "pve1",
|
|
NodeName: "nodeB",
|
|
VMID: 200,
|
|
},
|
|
},
|
|
})
|
|
|
|
index := buildProxmoxGuestInfoIndex(readState)
|
|
|
|
assertInfo := func(key string, resourceType unifiedresources.ResourceType, name string) {
|
|
t.Helper()
|
|
info, ok := index[key]
|
|
if !ok {
|
|
t.Fatalf("expected info for %s, got none", key)
|
|
}
|
|
if info.ResourceType != resourceType {
|
|
t.Errorf("expected type %v, got %v", resourceType, info.ResourceType)
|
|
}
|
|
if info.Name != name {
|
|
t.Errorf("expected name %q, got %q", name, info.Name)
|
|
}
|
|
}
|
|
|
|
assertInfo("pve1|nodeA|100", unifiedresources.ResourceTypeVM, "vm100")
|
|
assertInfo("pve1|nodeB|200", unifiedresources.ResourceTypeSystemContainer, "ct200")
|
|
}
|