Guard storage backups from failed polls

This commit is contained in:
rcourtman
2025-10-11 09:58:34 +00:00
parent 74bc5d5229
commit 1ffcfbeaf6
2 changed files with 109 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package monitoring
import "testing"
func TestShouldPreserveBackups(t *testing.T) {
t.Parallel()
tests := []struct {
name string
nodeCount int
hadSuccessfulNode bool
storagesWithBackup int
contentSuccess int
want bool
}{
{
name: "no successful nodes with nodes present",
nodeCount: 2,
hadSuccessfulNode: false,
storagesWithBackup: 0,
contentSuccess: 0,
want: true,
},
{
name: "no nodes skips preservation",
nodeCount: 0,
hadSuccessfulNode: false,
storagesWithBackup: 0,
contentSuccess: 0,
want: false,
},
{
name: "storages present but no content success",
nodeCount: 3,
hadSuccessfulNode: true,
storagesWithBackup: 5,
contentSuccess: 0,
want: true,
},
{
name: "storages present with successes",
nodeCount: 3,
hadSuccessfulNode: true,
storagesWithBackup: 5,
contentSuccess: 2,
want: false,
},
{
name: "no storages and no successes but had success elsewhere",
nodeCount: 1,
hadSuccessfulNode: true,
storagesWithBackup: 0,
contentSuccess: 0,
want: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := shouldPreserveBackups(tt.nodeCount, tt.hadSuccessfulNode, tt.storagesWithBackup, tt.contentSuccess)
if got != tt.want {
t.Fatalf("shouldPreserveBackups() = %v, want %v", got, tt.want)
}
})
}
}
+41
View File
@@ -4468,6 +4468,11 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
var allBackups []models.StorageBackup
seenVolids := make(map[string]bool) // Track seen volume IDs to avoid duplicates
hadSuccessfulNode := false // Track if at least one node responded successfully
storagesWithBackup := 0 // Number of storages that should contain backups
contentSuccess := 0 // Number of successful storage content fetches
contentFailures := 0 // Number of failed storage content fetches
storageQueryErrors := 0 // Number of nodes where storage list could not be queried
// For each node, get storage and check content
for _, node := range nodes {
@@ -4504,9 +4509,12 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
if err != nil {
monErr := errors.NewMonitorError(errors.ErrorTypeAPI, "get_storage_for_backups", instanceName, err).WithNode(node.Node)
log.Warn().Err(monErr).Str("node", node.Node).Msg("Failed to get storage for backups - skipping node")
storageQueryErrors++
continue
}
hadSuccessfulNode = true
// For each storage that can contain backups or templates
for _, storage := range storages {
// Check if storage supports backup content
@@ -4514,6 +4522,8 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
continue
}
storagesWithBackup++
// Get storage content
contents, err := client.GetStorageContent(ctx, node.Node, storage.Storage)
if err != nil {
@@ -4522,9 +4532,12 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
Str("node", node.Node).
Str("storage", storage.Storage).
Msg("Failed to get storage content")
contentFailures++
continue
}
contentSuccess++
// Convert to models
for _, content := range contents {
// Skip if we've already seen this item (shared storage duplicate)
@@ -4599,6 +4612,24 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
}
}
// Decide whether to keep existing backups when every query failed
if shouldPreserveBackups(len(nodes), hadSuccessfulNode, storagesWithBackup, contentSuccess) {
if len(nodes) > 0 && !hadSuccessfulNode {
log.Warn().
Str("instance", instanceName).
Int("nodes", len(nodes)).
Int("errors", storageQueryErrors).
Msg("Failed to query storage on all nodes; keeping previous backup list")
} else if storagesWithBackup > 0 && contentSuccess == 0 {
log.Warn().
Str("instance", instanceName).
Int("storages", storagesWithBackup).
Int("failures", contentFailures).
Msg("All storage content queries failed; keeping previous backup list")
}
return
}
// Update state with storage backups for this instance
m.state.UpdateStorageBackupsForInstance(instanceName, allBackups)
@@ -4608,6 +4639,16 @@ func (m *Monitor) pollStorageBackupsWithNodes(ctx context.Context, instanceName
Msg("Storage backups polled")
}
func shouldPreserveBackups(nodeCount int, hadSuccessfulNode bool, storagesWithBackup, contentSuccess int) bool {
if nodeCount > 0 && !hadSuccessfulNode {
return true
}
if storagesWithBackup > 0 && contentSuccess == 0 {
return true
}
return false
}
func (m *Monitor) calculateBackupOperationTimeout(instanceName string) time.Duration {
const (
minTimeout = 2 * time.Minute