From 278f23d0dae0d6c41f425635103841357fe3cae1 Mon Sep 17 00:00:00 2001 From: Archit Sharma Date: Fri, 3 Nov 2023 14:52:44 +0530 Subject: [PATCH] we can afford an extra (now) deprecated Restic repository type ..better than using unstructured and be the deer in headlights Signed-off-by: Archit Sharma --- pkg/analyze/types/restic_types.go | 62 ++++++++++++++++++++++++++ pkg/analyze/velero.go | 67 +++++++++++++++++++++------- pkg/analyze/velero_test.go | 74 ++++++++++++++++++++++++++++--- pkg/types/types.go | 4 +- 4 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 pkg/analyze/types/restic_types.go diff --git a/pkg/analyze/types/restic_types.go b/pkg/analyze/types/restic_types.go new file mode 100644 index 000000000..2161e04e4 --- /dev/null +++ b/pkg/analyze/types/restic_types.go @@ -0,0 +1,62 @@ +package analyzer + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ResticRepositorySpec is the specification for a ResticRepository. +type ResticRepositorySpec struct { + // VolumeNamespace is the namespace this restic repository contains + // pod volume backups for. + VolumeNamespace string `json:"volumeNamespace"` + + // BackupStorageLocation is the name of the BackupStorageLocation + // that should contain this repository. + BackupStorageLocation string `json:"backupStorageLocation"` + + // ResticIdentifier is the full restic-compatible string for identifying + // this repository. + ResticIdentifier string `json:"resticIdentifier"` + + // MaintenanceFrequency is how often maintenance should be run. + MaintenanceFrequency metav1.Duration `json:"maintenanceFrequency"` +} + +// ResticRepositoryPhase represents the lifecycle phase of a ResticRepository. +// +kubebuilder:validation:Enum=New;Ready;NotReady +type ResticRepositoryPhase string + +const ( + ResticRepositoryPhaseNew ResticRepositoryPhase = "New" + ResticRepositoryPhaseReady ResticRepositoryPhase = "Ready" + ResticRepositoryPhaseNotReady ResticRepositoryPhase = "NotReady" +) + +// ResticRepositoryStatus is the current status of a ResticRepository. +type ResticRepositoryStatus struct { + // Phase is the current state of the ResticRepository. + // +optional + Phase ResticRepositoryPhase `json:"phase,omitempty"` + + // Message is a message about the current status of the ResticRepository. + // +optional + Message string `json:"message,omitempty"` + + // LastMaintenanceTime is the last time maintenance was run. + // +optional + // +nullable + LastMaintenanceTime *metav1.Time `json:"lastMaintenanceTime,omitempty"` +} + +type ResticRepository struct { + metav1.TypeMeta `json:",inline"` + + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +optional + Spec ResticRepositorySpec `json:"spec,omitempty"` + + // +optional + Status ResticRepositoryStatus `json:"status,omitempty"` +} diff --git a/pkg/analyze/velero.go b/pkg/analyze/velero.go index 3653eae64..df96f920d 100644 --- a/pkg/analyze/velero.go +++ b/pkg/analyze/velero.go @@ -6,14 +6,11 @@ import ( "path/filepath" "strings" + restic_types "github.com/replicatedhq/troubleshoot/pkg/analyze/types" + "github.com/pkg/errors" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -const ( - DefaultVeleroNamespace = "velero" ) type AnalyzeVelero struct { @@ -45,11 +42,6 @@ func (a *AnalyzeVelero) Analyze(getFile getCollectedFileContents, findFiles getC } func (a *AnalyzeVelero) veleroStatus(analyzer *troubleshootv1beta2.VeleroAnalyze, getFileContents getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) { - ns := DefaultVeleroNamespace - if analyzer.Namespace != "" { - ns = analyzer.Namespace - } - excludeFiles := []string{} // get backuprepositories.velero.io @@ -77,12 +69,14 @@ func (a *AnalyzeVelero) veleroStatus(analyzer *troubleshootv1beta2.VeleroAnalyze if err != nil { return nil, errors.Wrapf(err, "failed to find velero restic repositories files under %s", resticRepositoriesDir) } - resticRepositories := []unstructured.Unstructured{} + resticRepositories := []*restic_types.ResticRepository{} for key, resticRepositoryJson := range resticRepositoriesJson { + var resticRepositoryArray []*restic_types.ResticRepository err := json.Unmarshal(resticRepositoryJson, &resticRepositories) if err != nil { return nil, errors.Wrapf(err, "failed to unmarshal restic repository json from %s", key) } + resticRepositories = append(resticRepositories, resticRepositoryArray...) } // get backups.velero.io @@ -241,7 +235,7 @@ func (a *AnalyzeVelero) veleroStatus(analyzer *troubleshootv1beta2.VeleroAnalyze volumeSnapshotLocations = append(volumeSnapshotLocations, volumeSnapshotLocationArray...) } - logsDir := GetVeleroLogsDirectory(ns) + logsDir := GetVeleroLogsDirectory() logsGlob := filepath.Join(logsDir, "node-agent*", "*.log") logs, err := findFiles(logsGlob, excludeFiles) @@ -252,6 +246,7 @@ func (a *AnalyzeVelero) veleroStatus(analyzer *troubleshootv1beta2.VeleroAnalyze results := []*AnalyzeResult{} results = append(results, analyzeLogs(logs)...) results = append(results, analyzeBackupRepositories(backupRepositories)...) + results = append(results, analyzeResticRepositories(resticRepositories)...) results = append(results, analyzeBackups(backups)...) results = append(results, analyzeBackupStorageLocations(backupStorageLocations)...) results = append(results, analyzeDeleteBackupRequests(deleteBackupRequests)...) @@ -268,7 +263,7 @@ func analyzeBackupRepositories(backupRepositories []*velerov1.BackupRepository) results := []*AnalyzeResult{} readyCount := 0 backupRepositoriesResult := &AnalyzeResult{ - Title: "At least 1 Velero Backup Repository configured", + Title: "At least 1 Backup Repository configured", } if len(backupRepositories) == 0 { backupRepositoriesResult.IsFail = true @@ -295,11 +290,49 @@ func analyzeBackupRepositories(backupRepositories []*velerov1.BackupRepository) } } results = append(results, backupRepositoriesResult) - return results } +func analyzeResticRepositories(resticRepositories []*restic_types.ResticRepository) []*AnalyzeResult { + results := []*AnalyzeResult{} + readyCount := 0 + resticRepositoriesResult := &AnalyzeResult{ + Title: "At least 1 Restic Repository configured", + } + if len(resticRepositories) == 0 { + resticRepositoriesResult.IsFail = true + resticRepositoriesResult.Message = "No restic repositories configured" + } else { + for _, resticRepository := range resticRepositories { + // phase, _, err := unstructured.NestedString(resticRepository.Object, "status", "phase") + // if err != nil { + // klog.V(2).Infof("Failed to get phase for restic repository %s: %v", resticRepository.GetName(), err) + // } + // if phase != "Ready" { + if resticRepository.Status.Phase != restic_types.ResticRepositoryPhaseReady { + result := &AnalyzeResult{ + Title: fmt.Sprintf("Restic Repository %s", resticRepository.GetName()), + } + result.Message = fmt.Sprintf("Restic Repository [%s] is in phase %s", resticRepository.Name, resticRepository.Status.Phase) + result.IsWarn = true + results = append(results, result) + } else { + readyCount++ + } + } + if readyCount > 0 { + resticRepositoriesResult.IsPass = true + resticRepositoriesResult.Message = fmt.Sprintf("Found %d restic repositories configured and %d Ready", len(resticRepositories), readyCount) + } else { + resticRepositoriesResult.IsWarn = true + resticRepositoriesResult.Message = fmt.Sprintf("Found %d configured restic repositories, but none are ready", len(resticRepositories)) + } + } + results = append(results, resticRepositoriesResult) + return results +} + func analyzeBackups(backups []*velerov1.Backup) []*AnalyzeResult { results := []*AnalyzeResult{} @@ -337,7 +370,7 @@ func analyzeBackupStorageLocations(backupStorageLocations []*velerov1.BackupStor results := []*AnalyzeResult{} availableCount := 0 bslResult := &AnalyzeResult{ - Title: "At least 1 Velero Backup Storage Location configured", + Title: "At least 1 Backup Storage Location configured", } if len(backupStorageLocations) == 0 { @@ -615,8 +648,8 @@ func GetVeleroDownloadRequestsDirectory() string { return "cluster-resources/custom-resources/downloadrequests.velero.io" } -func GetVeleroLogsDirectory(namespace string) string { - return fmt.Sprint("%s/logs", namespace) +func GetVeleroLogsDirectory() string { + return "velero/logs" } func GetVeleroPodVolumeBackupsDirectory() string { diff --git a/pkg/analyze/velero_test.go b/pkg/analyze/velero_test.go index 92d6487cf..337ec649b 100644 --- a/pkg/analyze/velero_test.go +++ b/pkg/analyze/velero_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + restic_types "github.com/replicatedhq/troubleshoot/pkg/analyze/types" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,7 +28,7 @@ func TestAnalyzeVelero_BackupRepositories(t *testing.T) { }, want: []*AnalyzeResult{ { - Title: "At least 1 Velero Backup Repository configured", + Title: "At least 1 Backup Repository configured", Message: "No backup repositories configured", IsFail: true, }, @@ -50,7 +51,7 @@ func TestAnalyzeVelero_BackupRepositories(t *testing.T) { }, want: []*AnalyzeResult{ { - Title: "At least 1 Velero Backup Repository configured", + Title: "At least 1 Backup Repository configured", Message: "Found 1 backup repositories configured and 1 Ready", IsPass: true, }, @@ -95,7 +96,7 @@ func TestAnalyzeVelero_BackupRepositories(t *testing.T) { IsWarn: true, }, { - Title: "At least 1 Velero Backup Repository configured", + Title: "At least 1 Backup Repository configured", Message: "Found 2 backup repositories configured and 1 Ready", IsPass: true, }, @@ -127,7 +128,7 @@ func TestAnalyzeVelero_BackupRepositories(t *testing.T) { IsWarn: true, }, { - Title: "At least 1 Velero Backup Repository configured", + Title: "At least 1 Backup Repository configured", Message: "Found 1 configured backup repositories, but none are ready", IsWarn: true, }, @@ -143,6 +144,65 @@ func TestAnalyzeVelero_BackupRepositories(t *testing.T) { } } +func TestAnalyzeVelero_ResticRepositories(t *testing.T) { + type args struct { + resticRepositories []*restic_types.ResticRepository + } + tests := []struct { + name string + args args + want []*AnalyzeResult + }{ + { + name: "no restic repositories", + args: args{ + resticRepositories: []*restic_types.ResticRepository{}, + }, + want: []*AnalyzeResult{ + { + Title: "At least 1 Restic Repository configured", + Message: "No restic repositories configured", + IsFail: true, + }, + }, + }, + { + name: "1 restic repository and 1 Ready", + args: args{ + resticRepositories: []*restic_types.ResticRepository{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "default-default-restic-245sd", + Namespace: "velero", + }, + Spec: restic_types.ResticRepositorySpec{ + BackupStorageLocation: "default", + VolumeNamespace: "velero", + }, + Status: restic_types.ResticRepositoryStatus{ + Phase: restic_types.ResticRepositoryPhaseReady, + }, + }, + }, + }, + want: []*AnalyzeResult{ + { + Title: "At least 1 Restic Repository configured", + Message: "Found 1 restic repositories configured and 1 Ready", + IsPass: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := analyzeResticRepositories(tt.args.resticRepositories); !reflect.DeepEqual(got, tt.want) { + t.Errorf("analyzeResticRepositories() = %v, want %v", got, tt.want) + } + }) + } +} + func TestAnalyzeVelero_Backups(t *testing.T) { type args struct { backups []*velerov1.Backup @@ -242,7 +302,7 @@ func TestAnalyzeVelero_BackupStorageLocations(t *testing.T) { }, want: []*AnalyzeResult{ { - Title: "At least 1 Velero Backup Storage Location configured", + Title: "At least 1 Backup Storage Location configured", Message: "No backup storage locations configured", IsFail: true, }, @@ -268,7 +328,7 @@ func TestAnalyzeVelero_BackupStorageLocations(t *testing.T) { }, want: []*AnalyzeResult{ { - Title: "At least 1 Velero Backup Storage Location configured", + Title: "At least 1 Backup Storage Location configured", Message: "Found 1 backup storage locations configured and 1 Available", IsPass: true, }, @@ -299,7 +359,7 @@ func TestAnalyzeVelero_BackupStorageLocations(t *testing.T) { IsWarn: true, }, { - Title: "At least 1 Velero Backup Storage Location configured", + Title: "At least 1 Backup Storage Location configured", Message: "Found 1 configured backup storage locations, but none are available", IsWarn: true, }, diff --git a/pkg/types/types.go b/pkg/types/types.go index 3809912c4..d710362a7 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,6 +1,8 @@ package types -import "fmt" +import ( + "fmt" +) type NotFoundError struct { Name string