diff --git a/internal/hook/item_hook_handler.go b/internal/hook/item_hook_handler.go index 83c756bd53..062922a056 100644 --- a/internal/hook/item_hook_handler.go +++ b/internal/hook/item_hook_handler.go @@ -36,7 +36,7 @@ import ( velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/podexec" - "github.com/vmware-tanzu/velero/pkg/restic" + "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/util/collections" "github.com/vmware-tanzu/velero/pkg/util/kube" ) @@ -126,7 +126,7 @@ func (i *InitContainerRestoreHookHandler) HandleRestoreHooks( // restored data to be consumed by the application container(s). // So if there is a "restic-wait" init container already on the pod at index 0, we'll preserve that and run // it before running any other init container. - if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == restic.InitContainer { + if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == podvolume.InitContainer { initContainers = append(initContainers, pod.Spec.InitContainers[0]) pod.Spec.InitContainers = pod.Spec.InitContainers[1:] } diff --git a/pkg/cmd/cli/restore/describe.go b/pkg/cmd/cli/restore/describe.go index 3a8c797a61..76ef3a43b0 100644 --- a/pkg/cmd/cli/restore/describe.go +++ b/pkg/cmd/cli/restore/describe.go @@ -28,7 +28,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" - "github.com/vmware-tanzu/velero/pkg/restic" + "github.com/vmware-tanzu/velero/pkg/label" ) func NewDescribeCommand(f client.Factory, use string) *cobra.Command { @@ -69,7 +69,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { first := true for _, restore := range restores.Items { - opts := restic.NewPodVolumeRestoreListOptions(restore.Name) + opts := newPodVolumeRestoreListOptions(restore.Name) podvolumeRestoreList, err := veleroClient.VeleroV1().PodVolumeRestores(f.Namespace()).List(context.TODO(), opts) if err != nil { fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err) @@ -94,3 +94,11 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { return c } + +// newPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to +// find PodVolumeRestores for the restore identified by name. +func newPodVolumeRestoreListOptions(name string) metav1.ListOptions { + return metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", velerov1api.RestoreNameLabel, label.GetValidName(name)), + } +} diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index c05dc75ef5..03111bcdf7 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -112,6 +112,9 @@ const ( // defaultCredentialsDirectory is the path on disk where credential // files will be written to defaultCredentialsDirectory = "/tmp/credentials" + + // daemonSet is the name of the Velero restic daemonset. + daemonSet = "restic" ) type serverConfig struct { @@ -529,7 +532,7 @@ var defaultRestorePriorities = []string{ func (s *server) initRestic() error { // warn if restic daemonset does not exist - if _, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get(s.ctx, restic.DaemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) { + if _, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get(s.ctx, daemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) { s.logger.Warn("Velero restic daemonset not found; restic backups/restores will not work until it's created") } else if err != nil { s.logger.WithError(errors.WithStack(err)).Warn("Error checking for existence of velero restic daemonset") diff --git a/pkg/controller/backup_deletion_controller.go b/pkg/controller/backup_deletion_controller.go index a616dcb711..a2193cd927 100644 --- a/pkg/controller/backup_deletion_controller.go +++ b/pkg/controller/backup_deletion_controller.go @@ -41,7 +41,6 @@ import ( "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt" "github.com/vmware-tanzu/velero/pkg/plugin/velero" "github.com/vmware-tanzu/velero/pkg/repository" - "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/util/filesystem" "github.com/vmware-tanzu/velero/pkg/util/kube" @@ -440,7 +439,7 @@ func (r *backupDeletionReconciler) deleteResticSnapshots(ctx context.Context, ba return nil } - snapshots, err := restic.GetSnapshotsInBackup(ctx, backup, r.Client) + snapshots, err := getSnapshotsInBackup(ctx, backup, r.Client) if err != nil { return []error{err} } @@ -491,3 +490,33 @@ func (r *backupDeletionReconciler) patchBackup(ctx context.Context, backup *vele } return backup, nil } + +// getSnapshotsInBackup returns a list of all restic snapshot ids associated with +// a given Velero backup. +func getSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) ([]repository.SnapshotIdentifier, error) { + podVolumeBackups := &velerov1api.PodVolumeBackupList{} + options := &client.ListOptions{ + LabelSelector: labels.Set(map[string]string{ + velerov1api.BackupNameLabel: label.GetValidName(backup.Name), + }).AsSelector(), + } + + err := kbClient.List(ctx, podVolumeBackups, options) + if err != nil { + return nil, errors.WithStack(err) + } + + var res []repository.SnapshotIdentifier + for _, item := range podVolumeBackups.Items { + if item.Status.SnapshotID == "" { + continue + } + res = append(res, repository.SnapshotIdentifier{ + VolumeNamespace: item.Spec.Pod.Namespace, + BackupStorageLocation: backup.Spec.StorageLocation, + SnapshotID: item.Status.SnapshotID, + }) + } + + return res, nil +} diff --git a/pkg/controller/backup_deletion_controller_test.go b/pkg/controller/backup_deletion_controller_test.go index d2de589b24..79833958f5 100644 --- a/pkg/controller/backup_deletion_controller_test.go +++ b/pkg/controller/backup_deletion_controller_test.go @@ -19,6 +19,7 @@ package controller import ( "bytes" "fmt" + "sort" "time" "context" @@ -32,6 +33,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + corev1api "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -52,6 +54,7 @@ import ( persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks" "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt" pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks" + "github.com/vmware-tanzu/velero/pkg/repository" velerotest "github.com/vmware-tanzu/velero/pkg/test" ) @@ -692,3 +695,172 @@ func TestBackupDeletionControllerReconcile(t *testing.T) { }) } + +func TestGetSnapshotsInBackup(t *testing.T) { + tests := []struct { + name string + podVolumeBackups []velerov1api.PodVolumeBackup + expected []repository.SnapshotIdentifier + longBackupNameEnabled bool + }{ + { + name: "no pod volume backups", + podVolumeBackups: nil, + expected: nil, + }, + { + name: "no pod volume backups with matching label", + podVolumeBackups: []velerov1api.PodVolumeBackup{ + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, + }, + }, + expected: nil, + }, + { + name: "some pod volume backups with matching label", + podVolumeBackups: []velerov1api.PodVolumeBackup{ + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""}, + }, + }, + expected: []repository.SnapshotIdentifier{ + { + VolumeNamespace: "ns-1", + SnapshotID: "snap-3", + }, + { + VolumeNamespace: "ns-1", + SnapshotID: "snap-4", + }, + }, + }, + { + name: "some pod volume backups with matching label and backup name greater than 63 chars", + longBackupNameEnabled: true, + podVolumeBackups: []velerov1api.PodVolumeBackup{ + { + ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "the-really-long-backup-name-that-is-much-more-than-63-cha6ca4bc"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, + Spec: velerov1api.PodVolumeBackupSpec{ + Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"}, + }, + Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""}, + }, + }, + expected: []repository.SnapshotIdentifier{ + { + VolumeNamespace: "ns-1", + SnapshotID: "snap-3", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + clientBuilder = velerotest.NewFakeControllerRuntimeClientBuilder(t) + veleroBackup = &velerov1api.Backup{} + ) + + veleroBackup.Name = "backup-1" + + if test.longBackupNameEnabled { + veleroBackup.Name = "the-really-long-backup-name-that-is-much-more-than-63-characters" + } + clientBuilder.WithLists(&velerov1api.PodVolumeBackupList{ + Items: test.podVolumeBackups, + }) + + res, err := getSnapshotsInBackup(context.TODO(), veleroBackup, clientBuilder.Build()) + assert.NoError(t, err) + + // sort to ensure good compare of slices + less := func(snapshots []repository.SnapshotIdentifier) func(i, j int) bool { + return func(i, j int) bool { + if snapshots[i].VolumeNamespace == snapshots[j].VolumeNamespace { + return snapshots[i].SnapshotID < snapshots[j].SnapshotID + } + return snapshots[i].VolumeNamespace < snapshots[j].VolumeNamespace + } + + } + + sort.Slice(test.expected, less(test.expected)) + sort.Slice(res, less(res)) + + assert.Equal(t, test.expected, res) + }) + } +} diff --git a/pkg/controller/pod_volume_restore_controller.go b/pkg/controller/pod_volume_restore_controller.go index 00853710fc..c8f0913db7 100644 --- a/pkg/controller/pod_volume_restore_controller.go +++ b/pkg/controller/pod_volume_restore_controller.go @@ -39,6 +39,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/podvolume" repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/uploader" @@ -106,7 +107,7 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req resticInitContainerIndex := getResticInitContainerIndex(pod) if resticInitContainerIndex > 0 { log.Warnf(`Init containers before the %s container may cause issues - if they interfere with volumes being restored: %s index %d`, restic.InitContainer, restic.InitContainer, resticInitContainerIndex) + if they interfere with volumes being restored: %s index %d`, podvolume.InitContainer, podvolume.InitContainer, resticInitContainerIndex) } log.Info("Restore starting") @@ -216,7 +217,7 @@ func isResticInitContainerRunning(pod *corev1api.Pod) bool { func getResticInitContainerIndex(pod *corev1api.Pod) int { // Restic wait container can be anywhere in the list of init containers so locate it. for i, initContainer := range pod.Spec.InitContainers { - if initContainer.Name == restic.InitContainer { + if initContainer.Name == podvolume.InitContainer { return i } } diff --git a/pkg/controller/pod_volume_restore_controller_test.go b/pkg/controller/pod_volume_restore_controller_test.go index 69bc313a34..d9cdc5d264 100644 --- a/pkg/controller/pod_volume_restore_controller_test.go +++ b/pkg/controller/pod_volume_restore_controller_test.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "github.com/vmware-tanzu/velero/pkg/restic" + "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/test" ) @@ -120,7 +120,7 @@ func TestShouldProcess(t *testing.T) { NodeName: controllerNode, InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, }, }, @@ -160,7 +160,7 @@ func TestShouldProcess(t *testing.T) { NodeName: controllerNode, InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, }, }, @@ -260,7 +260,7 @@ func TestIsResticContainerRunning(t *testing.T) { Name: "non-restic-init", }, { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, }, }, @@ -291,7 +291,7 @@ func TestIsResticContainerRunning(t *testing.T) { Spec: corev1api.PodSpec{ InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, { Name: "non-restic-init", @@ -323,7 +323,7 @@ func TestIsResticContainerRunning(t *testing.T) { Spec: corev1api.PodSpec{ InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, { Name: "non-restic-init", @@ -357,7 +357,7 @@ func TestIsResticContainerRunning(t *testing.T) { Spec: corev1api.PodSpec{ InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, }, }, @@ -422,7 +422,7 @@ func TestGetResticInitContainerIndex(t *testing.T) { Name: "non-restic-init", }, { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, }, }, @@ -439,7 +439,7 @@ func TestGetResticInitContainerIndex(t *testing.T) { Spec: corev1api.PodSpec{ InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, { Name: "non-restic-init", @@ -459,7 +459,7 @@ func TestGetResticInitContainerIndex(t *testing.T) { Spec: corev1api.PodSpec{ InitContainers: []corev1api.Container{ { - Name: restic.InitContainer, + Name: podvolume.InitContainer, }, { Name: "non-restic-init", diff --git a/pkg/podvolume/util.go b/pkg/podvolume/util.go index 57baacc106..959f05b7ef 100644 --- a/pkg/podvolume/util.go +++ b/pkg/podvolume/util.go @@ -42,6 +42,10 @@ const ( // VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes // should be excluded from restic backup. VolumesToExcludeAnnotation = "backup.velero.io/backup-volumes-excludes" + + // InitContainer is the name of the init container added + // to workload pods to help with restores. + InitContainer = "restic-wait" ) // GetVolumeBackupsForPod returns a map, of volume name -> snapshot id, diff --git a/pkg/repository/manager.go b/pkg/repository/manager.go index eb700d1062..ff97931827 100644 --- a/pkg/repository/manager.go +++ b/pkg/repository/manager.go @@ -27,10 +27,24 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/repository/provider" - "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/util/filesystem" ) +// SnapshotIdentifier uniquely identifies a restic snapshot +// taken by Velero. +type SnapshotIdentifier struct { + // VolumeNamespace is the namespace of the pod/volume that + // the restic snapshot is for. + VolumeNamespace string + + // BackupStorageLocation is the backup's storage location + // name. + BackupStorageLocation string + + // SnapshotID is the short ID of the restic snapshot. + SnapshotID string +} + // Manager manages backup repositories. type Manager interface { // InitRepo initializes a repo with the specified name and identifier. @@ -50,7 +64,7 @@ type Manager interface { // Forget removes a snapshot from the list of // available snapshots in a repo. - Forget(context.Context, restic.SnapshotIdentifier) error + Forget(context.Context, SnapshotIdentifier) error } type manager struct { @@ -147,7 +161,7 @@ func (m *manager) UnlockRepo(repo *velerov1api.BackupRepository) error { return prd.EnsureUnlockRepo(context.Background(), param) } -func (m *manager) Forget(ctx context.Context, snapshot restic.SnapshotIdentifier) error { +func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error { repo, err := m.repoEnsurer.EnsureRepo(ctx, m.namespace, snapshot.VolumeNamespace, snapshot.BackupStorageLocation) if err != nil { return err diff --git a/pkg/repository/mocks/repository_manager.go b/pkg/repository/mocks/repository_manager.go index 5533706741..a0ec81db75 100644 --- a/pkg/repository/mocks/repository_manager.go +++ b/pkg/repository/mocks/repository_manager.go @@ -20,10 +20,10 @@ import ( context "context" mock "github.com/stretchr/testify/mock" - restic "github.com/vmware-tanzu/velero/pkg/restic" v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/podvolume" + "github.com/vmware-tanzu/velero/pkg/repository" ) // RepositoryManager is an autogenerated mock type for the RepositoryManager type @@ -46,11 +46,11 @@ func (_m *RepositoryManager) ConnectToRepo(repo *v1.BackupRepository) error { } // Forget provides a mock function with given fields: _a0, _a1 -func (_m *RepositoryManager) Forget(_a0 context.Context, _a1 restic.SnapshotIdentifier) error { +func (_m *RepositoryManager) Forget(_a0 context.Context, _a1 repository.SnapshotIdentifier) error { ret := _m.Called(_a0, _a1) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, restic.SnapshotIdentifier) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, repository.SnapshotIdentifier) error); ok { r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) diff --git a/pkg/restic/common.go b/pkg/restic/common.go index 860f983f72..90a38f58be 100644 --- a/pkg/restic/common.go +++ b/pkg/restic/common.go @@ -17,7 +17,6 @@ limitations under the License. package restic import ( - "context" "fmt" "os" "strconv" @@ -25,24 +24,14 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "github.com/vmware-tanzu/velero/pkg/label" repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config" "github.com/vmware-tanzu/velero/pkg/util/filesystem" ) const ( - // DaemonSet is the name of the Velero restic daemonset. - DaemonSet = "restic" - - // InitContainer is the name of the init container added - // to workload pods to help with restores. - InitContainer = "restic-wait" // DefaultMaintenanceFrequency is the default time interval // at which restic prune is run. @@ -61,51 +50,6 @@ const ( resticInsecureTLSFlag = "--insecure-tls" ) -// SnapshotIdentifier uniquely identifies a restic snapshot -// taken by Velero. -type SnapshotIdentifier struct { - // VolumeNamespace is the namespace of the pod/volume that - // the restic snapshot is for. - VolumeNamespace string - - // BackupStorageLocation is the backup's storage location - // name. - BackupStorageLocation string - - // SnapshotID is the short ID of the restic snapshot. - SnapshotID string -} - -// GetSnapshotsInBackup returns a list of all restic snapshot ids associated with -// a given Velero backup. -func GetSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) ([]SnapshotIdentifier, error) { - podVolumeBackups := &velerov1api.PodVolumeBackupList{} - options := &client.ListOptions{ - LabelSelector: labels.Set(map[string]string{ - velerov1api.BackupNameLabel: label.GetValidName(backup.Name), - }).AsSelector(), - } - - err := kbClient.List(ctx, podVolumeBackups, options) - if err != nil { - return nil, errors.WithStack(err) - } - - var res []SnapshotIdentifier - for _, item := range podVolumeBackups.Items { - if item.Status.SnapshotID == "" { - continue - } - res = append(res, SnapshotIdentifier{ - VolumeNamespace: item.Spec.Pod.Namespace, - BackupStorageLocation: backup.Spec.StorageLocation, - SnapshotID: item.Status.SnapshotID, - }) - } - - return res, nil -} - // TempCACertFile creates a temp file containing a CA bundle // and returns its path. The caller should generally call os.Remove() // to remove the file when done with it. @@ -131,14 +75,6 @@ func TempCACertFile(caCert []byte, bsl string, fs filesystem.Interface) (string, return name, nil } -// NewPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to -// find PodVolumeRestores for the restore identified by name. -func NewPodVolumeRestoreListOptions(name string) metav1.ListOptions { - return metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", velerov1api.RestoreNameLabel, label.GetValidName(name)), - } -} - // CmdEnv returns a list of environment variables (in the format var=val) that // should be used when running a restic command for a particular backend provider. // This list is the current environment, plus any provider-specific variables restic needs. diff --git a/pkg/restic/common_test.go b/pkg/restic/common_test.go index b2acee773f..97363340c0 100644 --- a/pkg/restic/common_test.go +++ b/pkg/restic/common_test.go @@ -17,190 +17,17 @@ limitations under the License. package restic import ( - "context" "os" - "sort" "testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - corev1api "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerotest "github.com/vmware-tanzu/velero/pkg/test" ) -func TestGetSnapshotsInBackup(t *testing.T) { - tests := []struct { - name string - podVolumeBackups []velerov1api.PodVolumeBackup - expected []SnapshotIdentifier - longBackupNameEnabled bool - }{ - { - name: "no pod volume backups", - podVolumeBackups: nil, - expected: nil, - }, - { - name: "no pod volume backups with matching label", - podVolumeBackups: []velerov1api.PodVolumeBackup{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, - }, - }, - expected: nil, - }, - { - name: "some pod volume backups with matching label", - podVolumeBackups: []velerov1api.PodVolumeBackup{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""}, - }, - }, - expected: []SnapshotIdentifier{ - { - VolumeNamespace: "ns-1", - SnapshotID: "snap-3", - }, - { - VolumeNamespace: "ns-1", - SnapshotID: "snap-4", - }, - }, - }, - { - name: "some pod volume backups with matching label and backup name greater than 63 chars", - longBackupNameEnabled: true, - podVolumeBackups: []velerov1api.PodVolumeBackup{ - { - ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "the-really-long-backup-name-that-is-much-more-than-63-cha6ca4bc"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"}, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}}, - Spec: velerov1api.PodVolumeBackupSpec{ - Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"}, - }, - Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""}, - }, - }, - expected: []SnapshotIdentifier{ - { - VolumeNamespace: "ns-1", - SnapshotID: "snap-3", - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var ( - clientBuilder = velerotest.NewFakeControllerRuntimeClientBuilder(t) - veleroBackup = &velerov1api.Backup{} - ) - - veleroBackup.Name = "backup-1" - - if test.longBackupNameEnabled { - veleroBackup.Name = "the-really-long-backup-name-that-is-much-more-than-63-characters" - } - clientBuilder.WithLists(&velerov1api.PodVolumeBackupList{ - Items: test.podVolumeBackups, - }) - - res, err := GetSnapshotsInBackup(context.TODO(), veleroBackup, clientBuilder.Build()) - assert.NoError(t, err) - - // sort to ensure good compare of slices - less := func(snapshots []SnapshotIdentifier) func(i, j int) bool { - return func(i, j int) bool { - if snapshots[i].VolumeNamespace == snapshots[j].VolumeNamespace { - return snapshots[i].SnapshotID < snapshots[j].SnapshotID - } - return snapshots[i].VolumeNamespace < snapshots[j].VolumeNamespace - } - - } - - sort.Slice(test.expected, less(test.expected)) - sort.Slice(res, less(res)) - - assert.Equal(t, test.expected, res) - }) - } -} - func TestTempCACertFile(t *testing.T) { var ( fs = velerotest.NewFakeFileSystem() diff --git a/pkg/restore/restic_restore_action.go b/pkg/restore/restic_restore_action.go index ba9f9cb0a3..586dbf7852 100644 --- a/pkg/restore/restic_restore_action.go +++ b/pkg/restore/restic_restore_action.go @@ -37,7 +37,6 @@ import ( "github.com/vmware-tanzu/velero/pkg/plugin/framework" "github.com/vmware-tanzu/velero/pkg/plugin/velero" "github.com/vmware-tanzu/velero/pkg/podvolume" - "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/util/kube" ) @@ -161,7 +160,7 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu initContainerBuilder.Command(getCommand(log, config)) initContainer := *initContainerBuilder.Result() - if len(pod.Spec.InitContainers) == 0 || pod.Spec.InitContainers[0].Name != restic.InitContainer { + if len(pod.Spec.InitContainers) == 0 || pod.Spec.InitContainers[0].Name != podvolume.InitContainer { pod.Spec.InitContainers = append([]corev1.Container{initContainer}, pod.Spec.InitContainers...) } else { pod.Spec.InitContainers[0] = initContainer @@ -290,7 +289,7 @@ func getPluginConfig(kind framework.PluginKind, name string, client corev1client } func newResticInitContainerBuilder(image, restoreUID string) *builder.ContainerBuilder { - return builder.ForContainer(restic.InitContainer, image). + return builder.ForContainer(podvolume.InitContainer, image). Args(restoreUID). Env([]*corev1.EnvVar{ {