mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Fix backup type-aware orphan detection
This commit is contained in:
parent
3981df57a2
commit
9fb76579cc
2 changed files with 177 additions and 4 deletions
|
|
@ -5507,6 +5507,43 @@ func BuildGuestKey(instance, node string, vmid int) string {
|
|||
return fmt.Sprintf("%s:%s:%d", instance, node, vmid)
|
||||
}
|
||||
|
||||
func canonicalBackupGuestType(kind string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(kind)) {
|
||||
case "vm", "qemu":
|
||||
return "vm"
|
||||
case "ct", "lxc", "container":
|
||||
return "ct"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func guestMatchesBackupType(guest GuestLookup, backupType string) bool {
|
||||
backupKind := canonicalBackupGuestType(backupType)
|
||||
if backupKind == "" {
|
||||
return true
|
||||
}
|
||||
guestKind := canonicalBackupGuestType(guest.Type)
|
||||
if guestKind == "" {
|
||||
return false
|
||||
}
|
||||
return guestKind == backupKind
|
||||
}
|
||||
|
||||
func filterGuestsByBackupType(guests []GuestLookup, backupType string) []GuestLookup {
|
||||
if canonicalBackupGuestType(backupType) == "" {
|
||||
return guests
|
||||
}
|
||||
|
||||
filtered := make([]GuestLookup, 0, len(guests))
|
||||
for _, guest := range guests {
|
||||
if guestMatchesBackupType(guest, backupType) {
|
||||
filtered = append(filtered, guest)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func isGuestMetricResourceType(resourceType string) bool {
|
||||
switch strings.TrimSpace(resourceType) {
|
||||
case "VM", "Container":
|
||||
|
|
@ -5961,6 +5998,9 @@ func (m *Manager) CheckBackups(
|
|||
vmid = strconv.Itoa(backup.VMID)
|
||||
}
|
||||
info := guestsByKey[key]
|
||||
if !guestMatchesBackupType(info, backup.Type) {
|
||||
info = GuestLookup{}
|
||||
}
|
||||
displayName := info.Name
|
||||
if displayName == "" {
|
||||
displayName = fmt.Sprintf("%s-%d", sanitizeAlertKey(backup.Node), backup.VMID)
|
||||
|
|
@ -5998,13 +6038,14 @@ func (m *Manager) CheckBackups(
|
|||
var node string
|
||||
|
||||
if exists && len(guests) > 0 {
|
||||
typedGuests := filterGuestsByBackupType(guests, backup.BackupType)
|
||||
// If we have exactly one match, use it directly
|
||||
// If we have multiple matches, try to disambiguate using the PBS namespace
|
||||
if len(guests) == 1 {
|
||||
info = guests[0]
|
||||
if len(typedGuests) == 1 {
|
||||
info = typedGuests[0]
|
||||
} else if backup.Namespace != "" {
|
||||
// Try to match namespace to instance name
|
||||
for _, g := range guests {
|
||||
for _, g := range typedGuests {
|
||||
if namespaceMatchesInstance(backup.Namespace, g.Instance) {
|
||||
info = g
|
||||
break
|
||||
|
|
@ -6177,6 +6218,9 @@ func (m *Manager) CheckBackups(
|
|||
if g.ResourceID == "" {
|
||||
continue
|
||||
}
|
||||
if !guestMatchesBackupType(g, record.backupType) {
|
||||
continue
|
||||
}
|
||||
if record.source == "PVE storage" && g.Instance != record.instance {
|
||||
continue
|
||||
}
|
||||
|
|
@ -6185,7 +6229,7 @@ func (m *Manager) CheckBackups(
|
|||
}
|
||||
}
|
||||
if !existsInInventory {
|
||||
if g, ok := guestsByKey[record.key]; ok && g.ResourceID != "" {
|
||||
if g, ok := guestsByKey[record.key]; ok && g.ResourceID != "" && guestMatchesBackupType(g, record.backupType) {
|
||||
existsInInventory = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1436,6 +1436,135 @@ func TestCheckBackupsVMIDCollisionNoNamespace(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCheckBackupsPbsTypeMismatchCreatesOrphanedAlert(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
m.mu.Lock()
|
||||
m.config.Enabled = true
|
||||
m.config.BackupDefaults = BackupAlertConfig{
|
||||
Enabled: true,
|
||||
WarningDays: 3,
|
||||
CriticalDays: 5,
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
pbsBackups := []models.PBSBackup{
|
||||
{
|
||||
ID: "pbs-vm-101",
|
||||
Instance: "pbs-main",
|
||||
Datastore: "backup-store",
|
||||
BackupType: "vm",
|
||||
VMID: "101",
|
||||
BackupTime: now.Add(-30 * 24 * time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
guestKey := BuildGuestKey("pve1", "node1", 101)
|
||||
guestsByKey := map[string]GuestLookup{
|
||||
guestKey: {
|
||||
ResourceID: "lxc/101",
|
||||
Name: "ct-101",
|
||||
Instance: "pve1",
|
||||
Node: "node1",
|
||||
Type: "lxc",
|
||||
VMID: 101,
|
||||
},
|
||||
}
|
||||
guestsByVMID := map[string][]GuestLookup{
|
||||
"101": {guestsByKey[guestKey]},
|
||||
}
|
||||
|
||||
m.CheckBackups(nil, pbsBackups, nil, guestsByKey, guestsByVMID, nil)
|
||||
|
||||
orphanedID := "backup-orphaned-" + sanitizeAlertKey("pbs:pbs-main:vm:101")
|
||||
ageID := "backup-age-" + sanitizeAlertKey("pbs:pbs-main:vm:101")
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
alert, exists := m.activeAlerts[orphanedID]
|
||||
if !exists {
|
||||
var keys []string
|
||||
for k := range m.activeAlerts {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
t.Fatalf("expected orphaned alert %q, found keys: %v", orphanedID, keys)
|
||||
}
|
||||
if alert.Type != "backup-orphaned" {
|
||||
t.Fatalf("expected backup-orphaned alert, got %s", alert.Type)
|
||||
}
|
||||
if _, exists := m.activeAlerts[ageID]; exists {
|
||||
t.Fatalf("expected no backup-age alert for mismatched live guest type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckBackupsStorageTypeMismatchCreatesOrphanedAlert(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
m.mu.Lock()
|
||||
m.config.Enabled = true
|
||||
m.config.BackupDefaults = BackupAlertConfig{
|
||||
Enabled: true,
|
||||
WarningDays: 3,
|
||||
CriticalDays: 5,
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
storageBackups := []models.StorageBackup{
|
||||
{
|
||||
ID: "pve1-node1-101-backup",
|
||||
Storage: "local",
|
||||
Node: "node1",
|
||||
Instance: "pve1",
|
||||
Type: "qemu",
|
||||
VMID: 101,
|
||||
Time: now.Add(-30 * 24 * time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
guestKey := BuildGuestKey("pve1", "node1", 101)
|
||||
guestsByKey := map[string]GuestLookup{
|
||||
guestKey: {
|
||||
ResourceID: "lxc/101",
|
||||
Name: "ct-101",
|
||||
Instance: "pve1",
|
||||
Node: "node1",
|
||||
Type: "lxc",
|
||||
VMID: 101,
|
||||
},
|
||||
}
|
||||
guestsByVMID := map[string][]GuestLookup{
|
||||
"101": {guestsByKey[guestKey]},
|
||||
}
|
||||
|
||||
m.CheckBackups(storageBackups, nil, nil, guestsByKey, guestsByVMID, nil)
|
||||
|
||||
orphanedID := "backup-orphaned-" + sanitizeAlertKey(guestKey)
|
||||
ageID := "backup-age-" + sanitizeAlertKey(guestKey)
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
alert, exists := m.activeAlerts[orphanedID]
|
||||
if !exists {
|
||||
var keys []string
|
||||
for k := range m.activeAlerts {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
t.Fatalf("expected orphaned alert %q, found keys: %v", orphanedID, keys)
|
||||
}
|
||||
if alert.Type != "backup-orphaned" {
|
||||
t.Fatalf("expected backup-orphaned alert, got %s", alert.Type)
|
||||
}
|
||||
if _, exists := m.activeAlerts[ageID]; exists {
|
||||
t.Fatalf("expected no backup-age alert for mismatched live guest type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckBackupsHandlesPmgBackups(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue