From a73a150d98b12e99a64ac3fd0924a000a40d068f Mon Sep 17 00:00:00 2001 From: Shubham Pampattiwar Date: Wed, 11 Jun 2025 18:23:55 -0700 Subject: [PATCH] Accommodate VGS workflows in PVC CSI plugin Signed-off-by: Shubham Pampattiwar Add changelog file Signed-off-by: Shubham Pampattiwar make update Signed-off-by: Shubham Pampattiwar lint fix Signed-off-by: Shubham Pampattiwar add unit tests for getVSForPVC func Signed-off-by: Shubham Pampattiwar Use v1beta1 instead of v1 v1alpha1 Signed-off-by: Shubham Pampattiwar go mod tidy Signed-off-by: Shubham Pampattiwar update updateVGSCreatedVS func to use retry on conflict Signed-off-by: Shubham Pampattiwar make update minor fix Signed-off-by: Shubham Pampattiwar fix ut assert Signed-off-by: Shubham Pampattiwar Address PR feedback Signed-off-by: Shubham Pampattiwar minor updates Signed-off-by: Shubham Pampattiwar remove unsused func and add todo for dep upgrades Signed-off-by: Shubham Pampattiwar --- .../unreleased/9019-shubham-pampattiwar | 1 + go.mod | 2 +- go.sum | 4 +- .../csi/volumesnapshotcontent_action.go | 2 +- .../csi/volumesnapshotcontent_action_test.go | 2 +- internal/volume/volumes_information.go | 2 +- internal/volume/volumes_information_test.go | 2 +- pkg/apis/velero/v1/labels_annotations.go | 9 + pkg/backup/actions/csi/pvc_action.go | 474 ++++++- pkg/backup/actions/csi/pvc_action_test.go | 1149 ++++++++++++++++- .../actions/csi/volumesnapshot_action.go | 2 +- .../actions/csi/volumesnapshot_action_test.go | 2 +- .../actions/csi/volumesnapshotclass_action.go | 2 +- .../csi/volumesnapshotclass_action_test.go | 2 +- .../csi/volumesnapshotcontent_action.go | 2 +- .../csi/volumesnapshotcontent_action_test.go | 2 +- pkg/backup/snapshots.go | 2 +- pkg/builder/volume_snapshot_builder.go | 2 +- pkg/builder/volume_snapshot_class_builder.go | 2 +- .../volume_snapshot_content_builder.go | 2 +- pkg/client/factory.go | 10 +- pkg/cmd/cli/nodeagent/server.go | 2 +- pkg/cmd/server/server.go | 8 +- pkg/cmd/util/output/backup_describer.go | 2 +- pkg/controller/backup_controller.go | 2 +- pkg/controller/backup_controller_test.go | 2 +- pkg/controller/backup_deletion_controller.go | 2 +- .../backup_deletion_controller_test.go | 2 +- pkg/controller/backup_sync_controller.go | 2 +- pkg/controller/backup_sync_controller_test.go | 2 +- pkg/controller/data_upload_controller.go | 2 +- pkg/controller/data_upload_controller_test.go | 4 +- pkg/controller/restore_controller_test.go | 2 +- pkg/exposer/csi_snapshot.go | 4 +- pkg/exposer/csi_snapshot_test.go | 4 +- pkg/kuberesource/kuberesource.go | 2 + pkg/persistence/mocks/backup_store.go | 2 +- pkg/persistence/object_store.go | 2 +- pkg/persistence/object_store_test.go | 2 +- pkg/restore/actions/csi/pvc_action.go | 2 +- pkg/restore/actions/csi/pvc_action_test.go | 2 +- .../actions/csi/volumesnapshot_action.go | 2 +- .../actions/csi/volumesnapshot_action_test.go | 2 +- .../actions/csi/volumesnapshotclass_action.go | 2 +- .../csi/volumesnapshotclass_action_test.go | 2 +- .../csi/volumesnapshotcontent_action.go | 2 +- .../csi/volumesnapshotcontent_action_test.go | 2 +- pkg/restore/request.go | 2 +- pkg/restore/restore.go | 2 +- pkg/restore/restore_test.go | 2 +- pkg/test/fake_controller_runtime_client.go | 5 +- pkg/test/mocks.go | 4 +- pkg/test/mocks/VolumeSnapshotLister.go | 4 +- pkg/util/csi/volume_snapshot.go | 4 +- pkg/util/csi/volume_snapshot_test.go | 4 +- test/util/csi/common.go | 4 +- 56 files changed, 1704 insertions(+), 68 deletions(-) create mode 100644 changelogs/unreleased/9019-shubham-pampattiwar diff --git a/changelogs/unreleased/9019-shubham-pampattiwar b/changelogs/unreleased/9019-shubham-pampattiwar new file mode 100644 index 000000000..ba487740c --- /dev/null +++ b/changelogs/unreleased/9019-shubham-pampattiwar @@ -0,0 +1 @@ +Accommodate VGS workflows in PVC CSI plugin \ No newline at end of file diff --git a/go.mod b/go.mod index bc40325a5..0412f6a69 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/hashicorp/go-plugin v1.6.0 github.com/joho/godotenv v1.3.0 github.com/kopia/kopia v0.16.0 - github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 + github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 diff --git a/go.sum b/go.sum index 2ca2b409a..132ebdead 100644 --- a/go.sum +++ b/go.sum @@ -507,8 +507,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 h1:j3YK74myEQRxR/srciTpOrm221SAvz6J5OVWbyfeXFo= -github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0/go.mod h1:FlyYFe32mPxKEPaRXKNxfX576d1AoCzstYDoOOnyMA4= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 h1:Q3jQ1NkFqv5o+F8dMmHd8SfEmlcwNeo1immFApntEwE= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= diff --git a/internal/delete/actions/csi/volumesnapshotcontent_action.go b/internal/delete/actions/csi/volumesnapshotcontent_action.go index 8ac7d4d2a..582041533 100644 --- a/internal/delete/actions/csi/volumesnapshotcontent_action.go +++ b/internal/delete/actions/csi/volumesnapshotcontent_action.go @@ -21,7 +21,7 @@ import ( "time" "github.com/google/uuid" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/internal/delete/actions/csi/volumesnapshotcontent_action_test.go b/internal/delete/actions/csi/volumesnapshotcontent_action_test.go index 223b84a83..30ab85828 100644 --- a/internal/delete/actions/csi/volumesnapshotcontent_action_test.go +++ b/internal/delete/actions/csi/volumesnapshotcontent_action_test.go @@ -21,7 +21,7 @@ import ( "fmt" "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" diff --git a/internal/volume/volumes_information.go b/internal/volume/volumes_information.go index a080ee7b3..d02dac269 100644 --- a/internal/volume/volumes_information.go +++ b/internal/volume/volumes_information.go @@ -22,7 +22,7 @@ import ( "strings" "sync" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/internal/volume/volumes_information_test.go b/internal/volume/volumes_information_test.go index 047dc3e5f..d8e7a82be 100644 --- a/internal/volume/volumes_information_test.go +++ b/internal/volume/volumes_information_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" diff --git a/pkg/apis/velero/v1/labels_annotations.go b/pkg/apis/velero/v1/labels_annotations.go index ad24b97ba..c1431d3cc 100644 --- a/pkg/apis/velero/v1/labels_annotations.go +++ b/pkg/apis/velero/v1/labels_annotations.go @@ -160,4 +160,13 @@ const ( // DataUploadNameAnnotation is the label key for the DataUpload name DataUploadNameAnnotation = "velero.io/data-upload-name" + + // Label used on VolumeGroupSnapshotClass to mark it as Velero's default for a CSI driver + VolumeGroupSnapshotClassDefaultLabel = "velero.io/csi-volumegroupsnapshot-class" + + // Annotation on PVC to override the VGS class to use + VolumeGroupSnapshotClassAnnotationPVC = "velero.io/csi-volume-group-snapshot-class" + + // Annotation prefix on Backup to override VGS class per CSI driver + VolumeGroupSnapshotClassAnnotationBackupPrefix = "velero.io/csi-volumegroupsnapshot-class_" ) diff --git a/pkg/backup/actions/csi/pvc_action.go b/pkg/backup/actions/csi/pvc_action.go index 8b08cdebc..907e62b95 100644 --- a/pkg/backup/actions/csi/pvc_action.go +++ b/pkg/backup/actions/csi/pvc_action.go @@ -20,8 +20,12 @@ import ( "context" "fmt" "strconv" + "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + "k8s.io/client-go/util/retry" + + volumegroupsnapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" @@ -32,8 +36,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" crclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" @@ -50,6 +57,15 @@ import ( kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube" ) +// TODO: Replace hardcoded VolumeSnapshot finalizer strings with constants from +// "github.com/kubernetes-csi/external-snapshotter/v8/pkg/utils" +// once module/toolchain upgrades are done. +// Finalizer constants +const ( + VolumeSnapshotFinalizerGroupProtection = "snapshot.storage.kubernetes.io/volumesnapshot-in-group-protection" + VolumeSnapshotFinalizerSourceProtection = "snapshot.storage.kubernetes.io/volumesnapshot-as-source-protection" +) + // pvcBackupItemAction is a backup item action plugin for Velero. type pvcBackupItemAction struct { log logrus.FieldLogger @@ -220,7 +236,6 @@ func (p *pvcBackupItemAction) Execute( ); err != nil { return nil, nil, "", nil, errors.WithStack(err) } - if valid, item, err := p.validatePVCandPV( pvc, item, @@ -247,7 +262,7 @@ func (p *pvcBackupItemAction) Execute( return nil, nil, "", nil, err } - vs, err := p.createVolumeSnapshot(pvc, backup) + vs, err := p.getVolumeSnapshotReference(context.TODO(), pvc, backup) if err != nil { return nil, nil, "", nil, err } @@ -362,7 +377,6 @@ func (p *pvcBackupItemAction) Execute( if err != nil { return nil, nil, "", nil, errors.WithStack(err) } - return &unstructured.Unstructured{Object: pvcMap}, additionalItems, operationID, itemToUpdate, nil } @@ -570,3 +584,455 @@ func NewPvcBackupItemAction(f client.Factory) plugincommon.HandlerInitializer { }, nil } } + +func (p *pvcBackupItemAction) getVolumeSnapshotReference( + ctx context.Context, + pvc corev1api.PersistentVolumeClaim, + backup *velerov1api.Backup, +) (*snapshotv1api.VolumeSnapshot, error) { + vgsLabelKey := backup.Spec.VolumeGroupSnapshotLabelKey + group, hasLabel := pvc.Labels[vgsLabelKey] + + if vgsLabelKey != "" && hasLabel && group != "" { + p.log.Infof("PVC %s/%s is part of VolumeGroupSnapshot group %q via label %q", pvc.Namespace, pvc.Name, group, vgsLabelKey) + + // Try to find an existing VS created via a previous VGS in the current backup + existingVS, err := p.findExistingVSForBackup(ctx, backup.UID, backup.Name, pvc.Name, pvc.Namespace) + if err != nil { + return nil, errors.Wrapf(err, "failed to find existing VolumeSnapshot for PVC %s/%s", pvc.Namespace, pvc.Name) + } + + if existingVS != nil { + if existingVS.Status != nil && existingVS.Status.VolumeGroupSnapshotName != nil { + p.log.Infof("Reusing existing VolumeSnapshot %s for PVC %s", existingVS.Name, pvc.Name) + return existingVS, nil + } else { + return nil, errors.Errorf("found VolumeSnapshot %s for PVC %s, but it was not created via VolumeGroupSnapshot (missing volumeGroupSnapshotName)", existingVS.Name, pvc.Name) + } + } + + p.log.Infof("No existing VS found for PVC %s; creating new VGS", pvc.Name) + + // List all PVCs in the VGS group + groupedPVCs, err := p.listGroupedPVCs(ctx, pvc.Namespace, vgsLabelKey, group) + if err != nil { + return nil, errors.Wrapf(err, "failed to list PVCs in VolumeGroupSnapshot group %q in namespace %q", group, pvc.Namespace) + } + + // Determine the CSI driver for the grouped PVCs + driver, err := p.determineCSIDriver(groupedPVCs) + if err != nil { + return nil, errors.Wrapf(err, "failed to determine CSI driver for PVCs in VolumeGroupSnapshot group %q", group) + } + if driver == "" { + return nil, errors.New("no csi driver found, failing the backup") + } + + // Determine the VGSClass to be used for the VGS object to be created + vgsClass, err := p.determineVGSClass(ctx, driver, backup, &pvc) + if err != nil { + return nil, errors.Wrapf(err, "failed to determine VolumeGroupSnapshotClass for CSI driver %q", driver) + } + + // Create the VGS object + newVGS, err := p.createVolumeGroupSnapshot(ctx, backup, pvc, vgsLabelKey, group, vgsClass) + if err != nil { + return nil, errors.Wrapf(err, "failed to create VolumeGroupSnapshot for PVC %s/%s", pvc.Namespace, pvc.Name) + } + + // Wait for all the VS objects associated with the VGS to have status and VGS Name (VS readiness is checked in legacy flow) and get the PVC-to-VS map + vsMap, err := p.waitForVGSAssociatedVS(ctx, groupedPVCs, newVGS, backup.Spec.CSISnapshotTimeout.Duration) + if err != nil { + return nil, errors.Wrapf(err, "timeout waiting for VolumeSnapshots to have status created via VolumeGroupSnapshot %s", newVGS.Name) + } + + // Update the VS objects: remove VGS owner references and finalizers; add backup metadata labels. + err = p.updateVGSCreatedVS(ctx, vsMap, newVGS, backup) + if err != nil { + return nil, errors.Wrapf(err, "failed to update VolumeSnapshots created by VolumeGroupSnapshot %s", newVGS.Name) + } + + // Wait for VGSC binding in the VGS status + err = p.waitForVGSCBinding(ctx, newVGS, backup.Spec.CSISnapshotTimeout.Duration) + if err != nil { + return nil, errors.Wrapf(err, "timeout waiting for VolumeGroupSnapshotContent binding for VolumeGroupSnapshot %s", newVGS.Name) + } + + // Re-fetch latest VGS to ensure status is populated after VGSC binding + latestVGS := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{} + if err := p.crClient.Get(ctx, crclient.ObjectKeyFromObject(newVGS), latestVGS); err != nil { + return nil, errors.Wrapf(err, "failed to re-fetch VolumeGroupSnapshot %s after VGSC binding wait", newVGS.Name) + } + + // Patch the VGSC deletionPolicy to Retain. + err = p.patchVGSCDeletionPolicy(ctx, latestVGS) + if err != nil { + return nil, errors.Wrapf(err, "failed to patch VolumeGroupSnapshotContent Deletion Policy for VolumeGroupSnapshot %s", newVGS.Name) + } + + // Delete the VGS and VGSC + err = p.deleteVGSAndVGSC(ctx, latestVGS) + if err != nil { + return nil, errors.Wrapf(err, "failed to get VolumeSnapshot for PVC %s/%s created by VolumeGroupSnapshot %s", pvc.Namespace, pvc.Name, newVGS.Name) + } + + // Use the VS that was created for this PVC via VGS. + vs, found := vsMap[pvc.Name] + if !found { + return nil, errors.Wrapf(err, "failed to get VolumeSnapshot for PVC %s/%s created by VolumeGroupSnapshot %s", pvc.Namespace, pvc.Name, newVGS.Name) + } + + return vs, nil + } + + // Legacy fallback: create individual VS + return p.createVolumeSnapshot(pvc, backup) +} + +func (p *pvcBackupItemAction) findExistingVSForBackup( + ctx context.Context, + backupUID types.UID, + backupName, pvcName, namespace string, +) (*snapshotv1api.VolumeSnapshot, error) { + vsList := &snapshotv1api.VolumeSnapshotList{} + + labelSelector := labels.SelectorFromSet(map[string]string{ + velerov1api.BackupNameLabel: label.GetValidName(backupName), + velerov1api.BackupUIDLabel: string(backupUID), + }) + + if err := p.crClient.List(ctx, vsList, + crclient.InNamespace(namespace), + crclient.MatchingLabelsSelector{Selector: labelSelector}, + ); err != nil { + return nil, errors.Wrap(err, "failed to list VolumeSnapshots with backup labels") + } + + for _, vs := range vsList.Items { + if vs.Spec.Source.PersistentVolumeClaimName != nil && + *vs.Spec.Source.PersistentVolumeClaimName == pvcName { + return &vs, nil + } + } + + return nil, nil +} + +func (p *pvcBackupItemAction) listGroupedPVCs(ctx context.Context, namespace, labelKey, groupValue string) ([]corev1api.PersistentVolumeClaim, error) { + pvcList := new(corev1api.PersistentVolumeClaimList) + if err := p.crClient.List( + ctx, + pvcList, + crclient.InNamespace(namespace), + crclient.MatchingLabels{labelKey: groupValue}, + ); err != nil { + return nil, errors.Wrap(err, "failed to list grouped PVCs") + } + + return pvcList.Items, nil +} + +func (p *pvcBackupItemAction) determineCSIDriver( + pvcs []corev1api.PersistentVolumeClaim, +) (string, error) { + var driver string + + for _, pvc := range pvcs { + pv, err := kubeutil.GetPVForPVC(&pvc, p.crClient) + if err != nil { + return "", err + } + if pv.Spec.CSI == nil { + return "", errors.Errorf("PV %s for PVC %s is not CSI provisioned", pv.Name, pvc.Name) + } + current := pv.Spec.CSI.Driver + if driver == "" { + driver = current + } else if driver != current { + return "", errors.Errorf("found multiple CSI drivers: %s and %s", driver, current) + } + } + return driver, nil +} + +func (p *pvcBackupItemAction) determineVGSClass( + ctx context.Context, + driver string, + backup *velerov1api.Backup, + pvc *corev1api.PersistentVolumeClaim, +) (string, error) { + // 1. PVC-level override + if pvc != nil { + if val, ok := pvc.Annotations[velerov1api.VolumeGroupSnapshotClassAnnotationPVC]; ok && val != "" { + return val, nil + } + } + + // 2. Backup-level override + key := fmt.Sprintf(velerov1api.VolumeGroupSnapshotClassAnnotationBackupPrefix+"%s", driver) + if val, ok := backup.Annotations[key]; ok && val != "" { + return val, nil + } + + // 3. Fallback to label-based default + vgsClassList := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotClassList{} + if err := p.crClient.List(ctx, vgsClassList); err != nil { + return "", errors.Wrap(err, "failed to list VolumeGroupSnapshotClasses") + } + + var matched []string + for _, class := range vgsClassList.Items { + if class.Driver != driver { + continue + } + if val, ok := class.Labels[velerov1api.VolumeGroupSnapshotClassDefaultLabel]; ok && val == "true" { + matched = append(matched, class.Name) + } + } + + if len(matched) == 1 { + return matched[0], nil + } else if len(matched) == 0 { + return "", errors.Errorf("no VolumeGroupSnapshotClass found for driver %q for PVC %s", driver, pvc.Name) + } else { + return "", errors.Errorf("multiple VolumeGroupSnapshotClasses found for driver %q with label velero.io/csi-volumegroupsnapshot-class=true", driver) + } +} + +func (p *pvcBackupItemAction) createVolumeGroupSnapshot( + ctx context.Context, + backup *velerov1api.Backup, + pvc corev1api.PersistentVolumeClaim, + vgsLabelKey, vgsLabelValue, vgsClassName string, +) (*volumegroupsnapshotv1beta1.VolumeGroupSnapshot, error) { + vgsLabels := map[string]string{ + velerov1api.BackupNameLabel: label.GetValidName(backup.Name), + velerov1api.BackupUIDLabel: string(backup.UID), + vgsLabelKey: vgsLabelValue, + } + + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("velero-%s-", vgsLabelValue), + Namespace: pvc.Namespace, + Labels: vgsLabels, + }, + Spec: volumegroupsnapshotv1beta1.VolumeGroupSnapshotSpec{ + VolumeGroupSnapshotClassName: &vgsClassName, + Source: volumegroupsnapshotv1beta1.VolumeGroupSnapshotSource{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + vgsLabelKey: vgsLabelValue, + }, + }, + }, + }, + } + + if err := p.crClient.Create(ctx, vgs); err != nil { + return nil, errors.Wrap(err, "failed to create VolumeGroupSnapshot") + } + + refetchedVGS, err := p.getVGSByLabels(ctx, pvc.Namespace, vgsLabels) + if err != nil { + return nil, errors.Wrap(err, "failed to re-fetch VGS after creation") + } + + p.log.Infof("Re-fetched Created VolumeGroupSnapshot %s/%s for PVC group label %s=%s", + refetchedVGS.Namespace, refetchedVGS.Name, vgsLabelKey, vgsLabelValue) + + return refetchedVGS, nil +} + +func (p *pvcBackupItemAction) waitForVGSAssociatedVS( + ctx context.Context, + groupedPVCs []corev1api.PersistentVolumeClaim, + vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot, + timeout time.Duration, +) (map[string]*snapshotv1api.VolumeSnapshot, error) { + expected := len(groupedPVCs) + + vsMap := make(map[string]*snapshotv1api.VolumeSnapshot) + + err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (done bool, err error) { + vsList := &snapshotv1api.VolumeSnapshotList{} + if err := p.crClient.List(ctx, vsList, crclient.InNamespace(vgs.Namespace)); err != nil { + return false, err + } + + vsMap = make(map[string]*snapshotv1api.VolumeSnapshot) + + for _, vs := range vsList.Items { + if !hasOwnerReference(&vs, vgs) { + continue + } + if vs.Status != nil && vs.Status.VolumeGroupSnapshotName != nil && + *vs.Status.VolumeGroupSnapshotName == vgs.Name { + if vs.Spec.Source.PersistentVolumeClaimName != nil { + vsMap[*vs.Spec.Source.PersistentVolumeClaimName] = vs.DeepCopy() + } + } + } + + if expected == 0 { + return false, nil + } + if len(vsMap) == expected { + return true, nil + } + return false, nil + }) + + if err != nil { + return nil, errors.Wrapf(err, "timeout waiting for VolumeSnapshots associated with VGS %s", vgs.Name) + } + + return vsMap, nil +} + +func hasOwnerReference(obj metav1.Object, vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot) bool { + for _, ref := range obj.GetOwnerReferences() { + if ref.Kind == kuberesource.VGSKind && + ref.APIVersion == volumegroupsnapshotv1beta1.GroupName+"/"+volumegroupsnapshotv1beta1.SchemeGroupVersion.Version && + ref.UID == vgs.UID { + return true + } + } + return false +} + +func (p *pvcBackupItemAction) updateVGSCreatedVS( + ctx context.Context, + vsMap map[string]*snapshotv1api.VolumeSnapshot, + vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot, + backup *velerov1api.Backup, +) error { + for pvcName, vs := range vsMap { + if vs == nil || vs.Status == nil || vs.Status.VolumeGroupSnapshotName == nil || + *vs.Status.VolumeGroupSnapshotName != vgs.Name { + continue + } + + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Re-fetch the latest VS to avoid conflict + latestVS := &snapshotv1api.VolumeSnapshot{} + if err := p.crClient.Get(ctx, crclient.ObjectKeyFromObject(vs), latestVS); err != nil { + return errors.Wrapf(err, "failed to get latest VolumeSnapshot %s (PVC %s)", vs.Name, pvcName) + } + + // Remove VGS owner ref + if err := controllerutil.RemoveOwnerReference(vgs, latestVS, p.crClient.Scheme()); err != nil { + return errors.Wrapf(err, "failed to remove VGS owner reference from VS %s", vs.Name) + } + + // Remove known finalizers + controllerutil.RemoveFinalizer(latestVS, VolumeSnapshotFinalizerGroupProtection) + controllerutil.RemoveFinalizer(latestVS, VolumeSnapshotFinalizerSourceProtection) + + // Add Velero labels + if latestVS.Labels == nil { + latestVS.Labels = make(map[string]string) + } + latestVS.Labels[velerov1api.BackupNameLabel] = backup.Name + latestVS.Labels[velerov1api.BackupUIDLabel] = string(backup.UID) + + // Attempt to update + return p.crClient.Update(ctx, latestVS) + }) + if err != nil { + return errors.Wrapf(err, "failed to update VS %s (PVC %s) after retrying on conflict", vs.Name, pvcName) + } + } + + return nil +} + +func (p *pvcBackupItemAction) patchVGSCDeletionPolicy(ctx context.Context, vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot) error { + if vgs == nil || vgs.Status == nil || vgs.Status.BoundVolumeGroupSnapshotContentName == nil { + return errors.New("VolumeGroupSnapshotContent name not found in VGS status") + } + + vgscName := vgs.Status.BoundVolumeGroupSnapshotContentName + + return retry.RetryOnConflict(retry.DefaultBackoff, func() error { + vgsc := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{} + if err := p.crClient.Get(ctx, crclient.ObjectKey{Name: *vgscName}, vgsc); err != nil { + return errors.Wrapf(err, "failed to get VolumeGroupSnapshotContent %s for VolumeGroupSnapshot %s/%s", *vgscName, vgs.Namespace, vgs.Name) + } + + if vgsc.Spec.DeletionPolicy == snapshotv1api.VolumeSnapshotContentDelete { + p.log.Infof("Patching VGSC %s to Retain deletionPolicy", *vgscName) + vgsc.Spec.DeletionPolicy = snapshotv1api.VolumeSnapshotContentRetain + if err := p.crClient.Update(ctx, vgsc); err != nil { + return errors.Wrapf(err, "failed to update VGSC %s deletionPolicy", *vgscName) + } + } else { + p.log.Infof("VGSC %s already set to deletionPolicy=%s", *vgscName, vgsc.Spec.DeletionPolicy) + } + + return nil + }) +} + +func (p *pvcBackupItemAction) deleteVGSAndVGSC(ctx context.Context, vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot) error { + if vgs.Status != nil && vgs.Status.BoundVolumeGroupSnapshotContentName != nil { + vgsc := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: *vgs.Status.BoundVolumeGroupSnapshotContentName, + }, + } + p.log.Infof("Deleting VolumeGroupSnapshotContent %s", vgsc.Name) + if err := p.crClient.Delete(ctx, vgsc); err != nil && !apierrors.IsNotFound(err) { + p.log.Warnf("Failed to delete VolumeGroupSnapshotContent %s: %v", vgsc.Name, err) + return errors.Wrapf(err, "failed to delete VolumeGroupSnapshotContent %s", vgsc.Name) + } + } else { + p.log.Infof("No BoundVolumeGroupSnapshotContentName set in VolumeGroupSnapshot %s/%s", vgs.Namespace, vgs.Name) + } + + p.log.Infof("Deleting VolumeGroupSnapshot %s/%s", vgs.Namespace, vgs.Name) + if err := p.crClient.Delete(ctx, vgs); err != nil && !apierrors.IsNotFound(err) { + p.log.Warnf("Failed to delete VolumeGroupSnapshot %s/%s: %v", vgs.Namespace, vgs.Name, err) + return errors.Wrapf(err, "failed to delete VolumeGroupSnapshot %s/%s", vgs.Namespace, vgs.Name) + } + + return nil +} + +func (p *pvcBackupItemAction) waitForVGSCBinding( + ctx context.Context, + vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot, + timeout time.Duration, +) error { + return wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + vgsRef := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{} + if err := p.crClient.Get(ctx, crclient.ObjectKeyFromObject(vgs), vgsRef); err != nil { + return false, err + } + + if vgsRef.Status != nil && vgsRef.Status.BoundVolumeGroupSnapshotContentName != nil { + return true, nil + } + + return false, nil + }) +} + +func (p *pvcBackupItemAction) getVGSByLabels(ctx context.Context, namespace string, labels map[string]string) (*volumegroupsnapshotv1beta1.VolumeGroupSnapshot, error) { + vgsList := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotList{} + if err := p.crClient.List(ctx, vgsList, + crclient.InNamespace(namespace), + crclient.MatchingLabels(labels), + ); err != nil { + return nil, errors.Wrap(err, "failed to list VolumeGroupSnapshots by labels") + } + + if len(vgsList.Items) == 0 { + return nil, errors.New("no VolumeGroupSnapshot found matching labels") + } + if len(vgsList.Items) > 1 { + return nil, errors.New("multiple VolumeGroupSnapshots found matching labels") + } + + return &vgsList.Items[0], nil +} diff --git a/pkg/backup/actions/csi/pvc_action_test.go b/pkg/backup/actions/csi/pvc_action_test.go index 0bb9a9151..e07d724ac 100644 --- a/pkg/backup/actions/csi/pvc_action_test.go +++ b/pkg/backup/actions/csi/pvc_action_test.go @@ -19,13 +19,23 @@ package csi import ( "context" "fmt" + "strings" "testing" "time" + "github.com/vmware-tanzu/velero/pkg/kuberesource" + + volumegroupsnapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1" + "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + + "github.com/vmware-tanzu/velero/pkg/label" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - v1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" corev1api "k8s.io/api/core/v1" @@ -47,6 +57,8 @@ import ( "github.com/vmware-tanzu/velero/pkg/util/boolptr" ) +const testDriver = "csi.example.com" + func TestExecute(t *testing.T) { boolTrue := true tests := []struct { @@ -173,7 +185,7 @@ func TestExecute(t *testing.T) { if boolptr.IsSetToTrue(tc.backup.Spec.SnapshotMoveData) == true { go func() { - var vsList v1.VolumeSnapshotList + var vsList snapshotv1api.VolumeSnapshotList err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) { err = pvcBIA.crClient.List(ctx, &vsList, &crclient.ListOptions{Namespace: tc.pvc.Namespace}) @@ -188,7 +200,7 @@ func TestExecute(t *testing.T) { require.NoError(t, err) vscName := "testVSC" readyToUse := true - vsList.Items[0].Status = &v1.VolumeSnapshotStatus{ + vsList.Items[0].Status = &snapshotv1api.VolumeSnapshotStatus{ BoundVolumeSnapshotContentName: &vscName, ReadyToUse: &readyToUse, } @@ -413,3 +425,1132 @@ func TestNewPVCBackupItemAction(t *testing.T) { _, err1 := plugin1(logger) require.NoError(t, err1) } + +func TestListGroupedPVCs(t *testing.T) { + tests := []struct { + name string + namespace string + labelKey string + groupValue string + pvcs []corev1api.PersistentVolumeClaim + expectCount int + expectError bool + }{ + { + name: "Match single PVC with label", + namespace: "ns1", + labelKey: "vgs-key", + groupValue: "group-a", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns1", + Labels: map[string]string{ + "vgs-key": "group-a", + }, + }, + }, + }, + expectCount: 1, + }, + { + name: "No matching PVCs", + namespace: "ns1", + labelKey: "vgs-key", + groupValue: "group-b", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns1", + Labels: map[string]string{ + "vgs-key": "group-a", + }, + }, + }, + }, + expectCount: 0, + }, + { + name: "Match multiple PVCs", + namespace: "ns1", + labelKey: "vgs-key", + groupValue: "group-a", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns1", + Labels: map[string]string{"vgs-key": "group-a"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc2", + Namespace: "ns1", + Labels: map[string]string{"vgs-key": "group-a"}, + }, + }, + }, + expectCount: 2, + }, + { + name: "Different namespace", + namespace: "ns2", + labelKey: "vgs-key", + groupValue: "group-a", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns1", + Labels: map[string]string{"vgs-key": "group-a"}, + }, + }, + }, + expectCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var objs []runtime.Object + for i := range tt.pvcs { + objs = append(objs, &tt.pvcs[i]) + } + client := velerotest.NewFakeControllerRuntimeClient(t, objs...) + + action := &pvcBackupItemAction{ + log: logrus.New(), + crClient: client, + } + + result, err := action.listGroupedPVCs(context.TODO(), tt.namespace, tt.labelKey, tt.groupValue) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Len(t, result, tt.expectCount) + } + }) + } +} + +func TestDetermineCSIDriver(t *testing.T) { + tests := []struct { + name string + pvcs []corev1api.PersistentVolumeClaim + pvs []corev1api.PersistentVolume + expectError bool + expectedDriver string + }{ + { + name: "Single PVC with CSI PV", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-1"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + }, + pvs: []corev1api.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-1"}, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeSource: corev1api.PersistentVolumeSource{ + CSI: &corev1api.CSIPersistentVolumeSource{Driver: "csi-driver"}, + }, + }, + }, + }, + expectedDriver: "csi-driver", + }, + { + name: "Multiple PVCs with same CSI driver", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-1"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-2", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-2"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + }, + pvs: []corev1api.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-1"}, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeSource: corev1api.PersistentVolumeSource{ + CSI: &corev1api.CSIPersistentVolumeSource{Driver: "csi-driver"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-2"}, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeSource: corev1api.PersistentVolumeSource{ + CSI: &corev1api.CSIPersistentVolumeSource{Driver: "csi-driver"}, + }, + }, + }, + }, + expectedDriver: "csi-driver", + }, + { + name: "PV not CSI provisioned", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-1"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + }, + pvs: []corev1api.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-1"}, + Spec: corev1api.PersistentVolumeSpec{}, + }, + }, + expectError: true, + }, + { + name: "Multiple PVCs with different CSI drivers", + pvcs: []corev1api.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-1"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pvc-2", Namespace: "ns-1"}, + Spec: corev1api.PersistentVolumeClaimSpec{VolumeName: "pv-2"}, + Status: corev1api.PersistentVolumeClaimStatus{Phase: corev1api.ClaimBound}, + }, + }, + pvs: []corev1api.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-1"}, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeSource: corev1api.PersistentVolumeSource{ + CSI: &corev1api.CSIPersistentVolumeSource{Driver: "csi-driver-1"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pv-2"}, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeSource: corev1api.PersistentVolumeSource{ + CSI: &corev1api.CSIPersistentVolumeSource{Driver: "csi-driver-2"}, + }, + }, + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var initObjs []runtime.Object + for i := range tt.pvcs { + pvc := tt.pvcs[i] + initObjs = append(initObjs, &pvc) + } + for i := range tt.pvs { + pv := tt.pvs[i] + initObjs = append(initObjs, &pv) + } + + client := velerotest.NewFakeControllerRuntimeClient(t, initObjs...) + action := &pvcBackupItemAction{ + log: logrus.New(), + crClient: client, + } + + driver, err := action.determineCSIDriver(tt.pvcs) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedDriver, driver) + } + }) + } +} + +func TestDetermineVGSClass(t *testing.T) { + tests := []struct { + name string + backup *velerov1api.Backup + pvc *corev1api.PersistentVolumeClaim + existingVGSClass []volumegroupsnapshotv1beta1.VolumeGroupSnapshotClass + expectError bool + expectResult string + }{ + { + name: "PVC annotation override", + pvc: &corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + velerov1api.VolumeGroupSnapshotClassAnnotationPVC: "pvc-class", + }, + }, + }, + backup: &velerov1api.Backup{}, + expectResult: "pvc-class", + }, + { + name: "Backup annotation override", + pvc: &corev1api.PersistentVolumeClaim{}, + backup: &velerov1api.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + fmt.Sprintf("%s%s", velerov1api.VolumeGroupSnapshotClassAnnotationBackupPrefix, testDriver): "backup-class", + }, + }, + }, + expectResult: "backup-class", + }, + { + name: "Default label-based match", + pvc: &corev1api.PersistentVolumeClaim{}, + backup: &velerov1api.Backup{}, + existingVGSClass: []volumegroupsnapshotv1beta1.VolumeGroupSnapshotClass{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "default-class", + Labels: map[string]string{velerov1api.VolumeGroupSnapshotClassDefaultLabel: "true"}, + }, + Driver: testDriver, + }, + }, + expectResult: "default-class", + }, + { + name: "No matching VGS class", + pvc: &corev1api.PersistentVolumeClaim{}, + backup: &velerov1api.Backup{}, + expectError: true, + }, + { + name: "Multiple matching VGS classes", + pvc: &corev1api.PersistentVolumeClaim{}, + backup: &velerov1api.Backup{}, + existingVGSClass: []volumegroupsnapshotv1beta1.VolumeGroupSnapshotClass{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "class1", + Labels: map[string]string{velerov1api.VolumeGroupSnapshotClassDefaultLabel: "true"}, + }, + Driver: testDriver, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "class2", + Labels: map[string]string{velerov1api.VolumeGroupSnapshotClassDefaultLabel: "true"}, + }, + Driver: testDriver, + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var initObjs []runtime.Object + for _, vgsClass := range tt.existingVGSClass { + vgsClassCopy := vgsClass + initObjs = append(initObjs, &vgsClassCopy) + } + + client := velerotest.NewFakeControllerRuntimeClient(t, initObjs...) + logger := logrus.New() + require.NoError(t, volumegroupsnapshotv1beta1.AddToScheme(client.Scheme())) + + action := &pvcBackupItemAction{crClient: client, log: logger} + + result, err := action.determineVGSClass(context.TODO(), testDriver, tt.backup, tt.pvc) + + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectResult, result) + } + }) + } +} + +func TestCreateVolumeGroupSnapshot(t *testing.T) { + testNamespace := "test-ns" + testLabelKey := "velero.io/test-vgs-label" + testLabelValue := "group-1" + testVGSClass := "test-class" + testBackup := &velerov1api.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-backup", + UID: "test-uid", + }, + } + testPVC := corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pvc", + Namespace: testNamespace, + Labels: map[string]string{ + testLabelKey: testLabelValue, + }, + }, + } + + crClient := velerotest.NewFakeControllerRuntimeClient(t) + log := logrus.New() + action := &pvcBackupItemAction{ + log: log, + crClient: crClient, + } + + vgs, err := action.createVolumeGroupSnapshot(context.TODO(), testBackup, testPVC, testLabelKey, testLabelValue, testVGSClass) + require.NoError(t, err) + require.NotNil(t, vgs) + + // Verify VGS fields + assert.Equal(t, testNamespace, vgs.Namespace) + assert.NotEmpty(t, vgs.GenerateName) + assert.Equal(t, testVGSClass, *vgs.Spec.VolumeGroupSnapshotClassName) + assert.NotNil(t, vgs.Spec.Source.Selector) + assert.Equal(t, testLabelValue, vgs.Spec.Source.Selector.MatchLabels[testLabelKey]) + assert.Equal(t, testLabelValue, vgs.Labels[testLabelKey]) + assert.Equal(t, label.GetValidName(testBackup.Name), vgs.Labels[velerov1api.BackupNameLabel]) + assert.Equal(t, string(testBackup.UID), vgs.Labels[velerov1api.BackupUIDLabel]) + + // Check that it exists in fake client + retrieved := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{} + err = crClient.Get(context.TODO(), crclient.ObjectKey{Name: vgs.Name, Namespace: vgs.Namespace}, retrieved) + require.NoError(t, err) +} + +func TestWaitForVGSAssociatedVS(t *testing.T) { + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vgs", + Namespace: "test-ns", + UID: types.UID("1234-5678-uuid"), + }, + } + + makeVS := func(name string, hasStatus bool, hasVGSName bool, owned bool, pvcName string) *snapshotv1api.VolumeSnapshot { + var refs []metav1.OwnerReference + if owned { + refs = []metav1.OwnerReference{ + { + APIVersion: "groupsnapshot.storage.k8s.io/v1beta1", + Kind: "VolumeGroupSnapshot", + Name: vgs.Name, + UID: vgs.UID, + }, + } + } + + vs := &snapshotv1api.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: vgs.Namespace, + OwnerReferences: refs, + }, + Spec: snapshotv1api.VolumeSnapshotSpec{ + Source: snapshotv1api.VolumeSnapshotSource{ + PersistentVolumeClaimName: pointer.String(pvcName), + }, + }, + } + + if hasStatus { + vs.Status = &snapshotv1api.VolumeSnapshotStatus{} + if hasVGSName { + vs.Status.VolumeGroupSnapshotName = pointer.String(vgs.Name) + } + } + + return vs + } + + makePVC := func(name string) corev1api.PersistentVolumeClaim { + return corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: vgs.Namespace, + }, + } + } + + tests := []struct { + name string + vsList []*snapshotv1api.VolumeSnapshot + groupedPVCs []corev1api.PersistentVolumeClaim + expectErr bool + expectVSMap int + }{ + { + name: "all owned VS have VGS name", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs1", true, true, true, "pvc1"), + makeVS("vs2", true, true, true, "pvc2"), + }, + groupedPVCs: []corev1api.PersistentVolumeClaim{ + makePVC("pvc1"), + makePVC("pvc2"), + }, + expectErr: false, + expectVSMap: 2, + }, + { + name: "one owned VS missing VGS name", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs1", true, true, true, "pvc1"), + makeVS("vs2", true, false, true, "pvc2"), + }, + groupedPVCs: []corev1api.PersistentVolumeClaim{ + makePVC("pvc1"), + makePVC("pvc2"), + }, + expectErr: true, + }, + { + name: "owned VS has no status", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs1", false, false, true, "pvc1"), + }, + groupedPVCs: []corev1api.PersistentVolumeClaim{ + makePVC("pvc1"), + }, + expectErr: true, + }, + { + name: "unrelated VS ignored", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs1", true, true, false, "pvc1"), + }, + groupedPVCs: []corev1api.PersistentVolumeClaim{ + makePVC("pvc1"), + }, + expectErr: true, + }, + { + name: "no owned VS present", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs1", true, true, false, "pvc1"), + }, + groupedPVCs: []corev1api.PersistentVolumeClaim{ + makePVC("pvc1"), + }, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var objs []runtime.Object + objs = append(objs, vgs) + for _, vs := range tt.vsList { + objs = append(objs, vs) + } + for _, pvc := range tt.groupedPVCs { + objs = append(objs, &pvc) + } + + client := velerotest.NewFakeControllerRuntimeClient(t, objs...) + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + vsMap, err := action.waitForVGSAssociatedVS(context.TODO(), tt.groupedPVCs, vgs, 2*time.Second) + + if tt.expectErr { + if err == nil { + t.Errorf("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(vsMap) != tt.expectVSMap { + t.Errorf("expected vsMap length %d, got %d", tt.expectVSMap, len(vsMap)) + } + } + }) + } +} + +func TestUpdateVGSCreatedVS(t *testing.T) { + backup := &velerov1api.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backup-1", + UID: "backup-uid-123", + }, + } + + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vgs", + Namespace: "ns", + UID: "vgs-uid-123", + }, + } + + makeVS := func(name string, withVGSOwner bool, vgsNamePtr *string, pvcName string) *snapshotv1api.VolumeSnapshot { + var refs []metav1.OwnerReference + if withVGSOwner { + refs = []metav1.OwnerReference{ + { + APIVersion: "groupsnapshot.storage.k8s.io/v1beta1", + Kind: "VolumeGroupSnapshot", + Name: vgs.Name, + UID: vgs.UID, + }, + } + } + return &snapshotv1api.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: vgs.Namespace, + OwnerReferences: refs, + Finalizers: []string{ + VolumeSnapshotFinalizerGroupProtection, + VolumeSnapshotFinalizerSourceProtection, + }, + }, + Status: &snapshotv1api.VolumeSnapshotStatus{ + ReadyToUse: pointer.Bool(true), + VolumeGroupSnapshotName: vgsNamePtr, + }, + Spec: snapshotv1api.VolumeSnapshotSpec{ + Source: snapshotv1api.VolumeSnapshotSource{ + PersistentVolumeClaimName: pointer.String(pvcName), + }, + }, + } + } + + tests := []struct { + name string + vs *snapshotv1api.VolumeSnapshot + expectOwnerCleared bool + expectFinalizersCleared bool + expectLabelPatched bool + }{ + { + name: "should update owned VS", + vs: makeVS("vs-owned", true, pointer.String(vgs.Name), "pvc-1"), + expectOwnerCleared: true, + expectFinalizersCleared: true, + expectLabelPatched: true, + }, + { + name: "should skip VS not owned by VGS", + vs: makeVS("vs-unowned", false, nil, "pvc-1"), + expectOwnerCleared: false, + expectFinalizersCleared: false, + expectLabelPatched: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := velerotest.NewFakeControllerRuntimeClient(t, vgs, tt.vs) + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + // Build vsMap using the PVC name from the VS + vsMap := map[string]*snapshotv1api.VolumeSnapshot{ + *tt.vs.Spec.Source.PersistentVolumeClaimName: tt.vs, + } + + err := action.updateVGSCreatedVS(context.TODO(), vsMap, vgs, backup) + require.NoError(t, err) + + // Fetch updated VS + updated := &snapshotv1api.VolumeSnapshot{} + err = client.Get(context.TODO(), crclient.ObjectKey{Name: tt.vs.Name, Namespace: tt.vs.Namespace}, updated) + require.NoError(t, err) + + if tt.expectOwnerCleared { + assert.Empty(t, updated.OwnerReferences, "expected ownerReferences to be cleared") + } else { + assert.Equal(t, tt.vs.OwnerReferences, updated.OwnerReferences, "expected ownerReferences to remain unchanged") + } + + if tt.expectFinalizersCleared { + assert.Empty(t, updated.Finalizers, "expected finalizers to be cleared") + } else { + assert.Equal(t, tt.vs.Finalizers, updated.Finalizers, "expected finalizers to remain unchanged") + } + + if tt.expectLabelPatched { + assert.Equal(t, "backup-1", updated.Labels[velerov1api.BackupNameLabel]) + assert.Equal(t, "backup-uid-123", updated.Labels[velerov1api.BackupUIDLabel]) + } else { + assert.Nil(t, updated.Labels, "expected no labels to be patched") + } + }) + } +} + +func TestPatchVGSCDeletionPolicy(t *testing.T) { + tests := []struct { + name string + initialPolicy snapshotv1api.DeletionPolicy + expectedPolicy snapshotv1api.DeletionPolicy + expectPatch bool + expectErr bool + }{ + { + name: "patches Delete to Retain", + initialPolicy: snapshotv1api.VolumeSnapshotContentDelete, + expectedPolicy: snapshotv1api.VolumeSnapshotContentRetain, + expectPatch: true, + }, + { + name: "no patch if already Retain", + initialPolicy: snapshotv1api.VolumeSnapshotContentRetain, + expectedPolicy: snapshotv1api.VolumeSnapshotContentRetain, + expectPatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + vgsc := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{Name: "test-vgsc"}, + Spec: volumegroupsnapshotv1beta1.VolumeGroupSnapshotContentSpec{ + DeletionPolicy: tt.initialPolicy, + }, + } + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vgs", + Namespace: "ns", + }, + Status: &volumegroupsnapshotv1beta1.VolumeGroupSnapshotStatus{ + BoundVolumeGroupSnapshotContentName: pointer.String("test-vgsc"), + }, + } + + client := velerotest.NewFakeControllerRuntimeClient(t, vgs, vgsc) + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + err := action.patchVGSCDeletionPolicy(context.TODO(), vgs) + if tt.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + updated := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{} + err = client.Get(context.TODO(), crclient.ObjectKey{Name: "test-vgsc"}, updated) + require.NoError(t, err) + require.Equal(t, tt.expectedPolicy, updated.Spec.DeletionPolicy) + }) + } +} + +func TestDeleteVGSAndVGSC(t *testing.T) { + makeVGS := func(name, namespace string, boundVGSCName *string) *volumegroupsnapshotv1beta1.VolumeGroupSnapshot { + return &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Status: &volumegroupsnapshotv1beta1.VolumeGroupSnapshotStatus{ + BoundVolumeGroupSnapshotContentName: boundVGSCName, + }, + } + } + + makeVGSC := func(name string) *volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent { + return &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + } + + tests := []struct { + name string + vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot + existingVGSC *volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent + expectVGSCDelete bool + expectVGSDelete bool + }{ + { + name: "deletes both VGSC and VGS", + vgs: makeVGS("test-vgs", "ns", pointer.String("test-vgsc")), + existingVGSC: makeVGSC("test-vgsc"), + expectVGSCDelete: true, + expectVGSDelete: true, + }, + { + name: "VGSC not found, still deletes VGS", + vgs: makeVGS("test-vgs", "ns", pointer.String("missing-vgsc")), + existingVGSC: nil, + expectVGSCDelete: false, + expectVGSDelete: true, + }, + { + name: "no BoundVGSCName set, only deletes VGS", + vgs: makeVGS("test-vgs", "ns", nil), + existingVGSC: nil, + expectVGSCDelete: false, + expectVGSDelete: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var objs []runtime.Object + objs = append(objs, tt.vgs) + if tt.existingVGSC != nil { + objs = append(objs, tt.existingVGSC) + } + + client := velerotest.NewFakeControllerRuntimeClient(t, objs...) + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + err := action.deleteVGSAndVGSC(context.TODO(), tt.vgs) + require.NoError(t, err) + + // Check VGSC is deleted + if tt.expectVGSCDelete { + got := &volumegroupsnapshotv1beta1.VolumeGroupSnapshotContent{} + err = client.Get(context.TODO(), crclient.ObjectKey{Name: "test-vgsc"}, got) + assert.True(t, apierrors.IsNotFound(err), "expected VGSC to be deleted") + } + + // Check VGS is deleted + gotVGS := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{} + err = client.Get(context.TODO(), crclient.ObjectKey{Name: "test-vgs", Namespace: "ns"}, gotVGS) + assert.True(t, apierrors.IsNotFound(err), "expected VGS to be deleted") + }) + } +} + +func TestFindExistingVSForBackup(t *testing.T) { + backupUID := types.UID("backup-uid-123") + backupName := "backup-1" + pvcName := "pvc-1" + namespace := "ns" + + makeVS := func(name, pvc string, match bool) *snapshotv1api.VolumeSnapshot { + labels := map[string]string{} + if match { + labels[velerov1api.BackupNameLabel] = label.GetValidName(backupName) + labels[velerov1api.BackupUIDLabel] = string(backupUID) + } + return &snapshotv1api.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + }, + Spec: snapshotv1api.VolumeSnapshotSpec{ + Source: snapshotv1api.VolumeSnapshotSource{ + PersistentVolumeClaimName: pointer.String(pvc), + }, + }, + } + } + + tests := []struct { + name string + vsList []*snapshotv1api.VolumeSnapshot + expectName string + expectNil bool + }{ + { + name: "should find matching VS", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs-match", pvcName, true), + }, + expectName: "vs-match", + expectNil: false, + }, + { + name: "should skip VS with non-matching labels", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs-nolabel", pvcName, false), + }, + expectNil: true, + }, + { + name: "should skip VS with different PVC name", + vsList: []*snapshotv1api.VolumeSnapshot{ + makeVS("vs-other-pvc", "other-pvc", true), + }, + expectNil: true, + }, + { + name: "should return nil if VS list is empty", + vsList: []*snapshotv1api.VolumeSnapshot{}, + expectNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var objs []runtime.Object + for _, vs := range tt.vsList { + objs = append(objs, vs) + } + + client := velerotest.NewFakeControllerRuntimeClient(t, objs...) + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + vs, err := action.findExistingVSForBackup(context.TODO(), backupUID, backupName, pvcName, namespace) + require.NoError(t, err) + + if tt.expectNil { + assert.Nil(t, vs) + } else { + require.NotNil(t, vs) + assert.Equal(t, tt.expectName, vs.Name) + } + }) + } +} + +func TestWaitForVGSCBinding(t *testing.T) { + makeVGS := func(name string, withStatus bool) *volumegroupsnapshotv1beta1.VolumeGroupSnapshot { + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "ns", + }, + } + if withStatus { + contentName := "vgsc-123" + vgs.Status = &volumegroupsnapshotv1beta1.VolumeGroupSnapshotStatus{ + BoundVolumeGroupSnapshotContentName: &contentName, + } + } + return vgs + } + + tests := []struct { + name string + vgs *volumegroupsnapshotv1beta1.VolumeGroupSnapshot + expectErr bool + }{ + { + name: "status is already bound", + vgs: makeVGS("vgs1", true), + expectErr: false, + }, + { + name: "status is nil", + vgs: makeVGS("vgs2", false), + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := velerotest.NewFakeControllerRuntimeClient(t, tt.vgs.DeepCopy()) + + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + err := action.waitForVGSCBinding(context.TODO(), tt.vgs, 1*time.Second) + + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, tt.vgs.Status) + require.NotNil(t, tt.vgs.Status.BoundVolumeGroupSnapshotContentName) + require.Equal(t, "vgsc-123", *tt.vgs.Status.BoundVolumeGroupSnapshotContentName) + } + }) + } +} + +func TestGetVGSByLabels(t *testing.T) { + labelKey := "velero.io/backup-name" + labelVal := "backup-123" + testLabels := map[string]string{labelKey: labelVal} + + makeVGS := func(name string, labels map[string]string) *volumegroupsnapshotv1beta1.VolumeGroupSnapshot { + return &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "test-ns", + Labels: labels, + }, + } + } + + tests := []struct { + name string + vgsObjects []runtime.Object + expectError string + expectName string + }{ + { + name: "exactly one matching VGS", + vgsObjects: []runtime.Object{ + makeVGS("vgs1", testLabels), + }, + expectName: "vgs1", + }, + { + name: "no matching VGS", + vgsObjects: []runtime.Object{}, + expectError: "no VolumeGroupSnapshot found matching labels", + }, + { + name: "multiple matching VGS", + vgsObjects: []runtime.Object{ + makeVGS("vgs1", testLabels), + makeVGS("vgs2", testLabels), + }, + expectError: "multiple VolumeGroupSnapshots found matching labels", + }, + { + name: "client list error", + vgsObjects: []runtime.Object{}, + expectError: "failed to list VolumeGroupSnapshots by labels", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var client crclient.Client + if tt.name == "client list error" { + // Inject a client that always errors on List + client = &failingClient{} + } else { + client = velerotest.NewFakeControllerRuntimeClient(t, tt.vgsObjects...) + } + + action := &pvcBackupItemAction{ + log: velerotest.NewLogger(), + crClient: client, + } + + vgs, err := action.getVGSByLabels(context.TODO(), "test-ns", testLabels) + + if tt.expectError != "" { + if err == nil || !strings.Contains(err.Error(), tt.expectError) { + t.Errorf("expected error containing '%s', got: %v", tt.expectError, err) + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if vgs == nil || vgs.Name != tt.expectName { + t.Errorf("expected VGS name %s, got %v", tt.expectName, vgs) + } + } + }) + } +} + +// failingClient is a dummy client that fails on List +type failingClient struct { + crclient.Client +} + +func (f *failingClient) List(ctx context.Context, list crclient.ObjectList, opts ...crclient.ListOption) error { + return fmt.Errorf("simulated list error") +} + +func TestHasOwnerReference(t *testing.T) { + vgs := &volumegroupsnapshotv1beta1.VolumeGroupSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vgs", + Namespace: "test-ns", + UID: types.UID("1234-uid"), + }, + } + + tests := []struct { + name string + ownerRef metav1.OwnerReference + expect bool + }{ + { + name: "match kind, apiversion, uid", + ownerRef: metav1.OwnerReference{ + Kind: kuberesource.VGSKind, + APIVersion: volumegroupsnapshotv1beta1.GroupName + "/" + volumegroupsnapshotv1beta1.SchemeGroupVersion.Version, + UID: vgs.UID, + }, + expect: true, + }, + { + name: "mismatch kind", + ownerRef: metav1.OwnerReference{ + Kind: "other-kind", + APIVersion: volumegroupsnapshotv1beta1.GroupName + "/" + volumegroupsnapshotv1beta1.SchemeGroupVersion.Version, + UID: vgs.UID, + }, + expect: false, + }, + { + name: "mismatch apiversion", + ownerRef: metav1.OwnerReference{ + Kind: kuberesource.VGSKind, + APIVersion: "wrong.group/v1", + UID: vgs.UID, + }, + expect: false, + }, + { + name: "mismatch uid", + ownerRef: metav1.OwnerReference{ + Kind: kuberesource.VGSKind, + APIVersion: volumegroupsnapshotv1beta1.GroupName + "/" + volumegroupsnapshotv1beta1.SchemeGroupVersion.Version, + UID: "wrong-uid", + }, + expect: false, + }, + { + name: "no owner references", + ownerRef: metav1.OwnerReference{}, + expect: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + obj := &metav1.ObjectMeta{ + Name: "dummy", + Namespace: "test-ns", + } + + if tt.name != "no owner references" { + obj.OwnerReferences = []metav1.OwnerReference{tt.ownerRef} + } + + found := hasOwnerReference(obj, vgs) + assert.Equal(t, tt.expect, found) + }) + } +} diff --git a/pkg/backup/actions/csi/volumesnapshot_action.go b/pkg/backup/actions/csi/volumesnapshot_action.go index da9aa391d..14a3d0b5e 100644 --- a/pkg/backup/actions/csi/volumesnapshot_action.go +++ b/pkg/backup/actions/csi/volumesnapshot_action.go @@ -22,7 +22,7 @@ import ( "strings" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/resource" diff --git a/pkg/backup/actions/csi/volumesnapshot_action_test.go b/pkg/backup/actions/csi/volumesnapshot_action_test.go index aa1a26397..793466b99 100644 --- a/pkg/backup/actions/csi/volumesnapshot_action_test.go +++ b/pkg/backup/actions/csi/volumesnapshot_action_test.go @@ -23,7 +23,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/backup/actions/csi/volumesnapshotclass_action.go b/pkg/backup/actions/csi/volumesnapshotclass_action.go index 83b2b1c79..8200b465f 100644 --- a/pkg/backup/actions/csi/volumesnapshotclass_action.go +++ b/pkg/backup/actions/csi/volumesnapshotclass_action.go @@ -20,7 +20,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/backup/actions/csi/volumesnapshotclass_action_test.go b/pkg/backup/actions/csi/volumesnapshotclass_action_test.go index a3a6a7e25..3bd90a8aa 100644 --- a/pkg/backup/actions/csi/volumesnapshotclass_action_test.go +++ b/pkg/backup/actions/csi/volumesnapshotclass_action_test.go @@ -19,7 +19,7 @@ package csi import ( "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/backup/actions/csi/volumesnapshotcontent_action.go b/pkg/backup/actions/csi/volumesnapshotcontent_action.go index ccdcc9df0..c2ab334e0 100644 --- a/pkg/backup/actions/csi/volumesnapshotcontent_action.go +++ b/pkg/backup/actions/csi/volumesnapshotcontent_action.go @@ -19,7 +19,7 @@ package csi import ( "fmt" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/backup/actions/csi/volumesnapshotcontent_action_test.go b/pkg/backup/actions/csi/volumesnapshotcontent_action_test.go index b2e75e34a..120eebdb4 100644 --- a/pkg/backup/actions/csi/volumesnapshotcontent_action_test.go +++ b/pkg/backup/actions/csi/volumesnapshotcontent_action_test.go @@ -19,7 +19,7 @@ package csi import ( "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/backup/snapshots.go b/pkg/backup/snapshots.go index 24c3296a8..c77e1cd6e 100644 --- a/pkg/backup/snapshots.go +++ b/pkg/backup/snapshots.go @@ -19,7 +19,7 @@ package backup import ( "context" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/sets" kbclient "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/pkg/builder/volume_snapshot_builder.go b/pkg/builder/volume_snapshot_builder.go index 2f4ebe56a..d6706713c 100644 --- a/pkg/builder/volume_snapshot_builder.go +++ b/pkg/builder/volume_snapshot_builder.go @@ -17,7 +17,7 @@ limitations under the License. package builder import ( - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/builder/volume_snapshot_class_builder.go b/pkg/builder/volume_snapshot_class_builder.go index 32afc21f2..93f949f71 100644 --- a/pkg/builder/volume_snapshot_class_builder.go +++ b/pkg/builder/volume_snapshot_class_builder.go @@ -17,7 +17,7 @@ limitations under the License. package builder import ( - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/builder/volume_snapshot_content_builder.go b/pkg/builder/volume_snapshot_content_builder.go index 1e33f3f52..565aa817d 100644 --- a/pkg/builder/volume_snapshot_content_builder.go +++ b/pkg/builder/volume_snapshot_content_builder.go @@ -17,7 +17,7 @@ limitations under the License. package builder import ( - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" corev1api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" diff --git a/pkg/client/factory.go b/pkg/client/factory.go index f1524a583..4b3c8941e 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -19,13 +19,15 @@ package client import ( "os" + volumegroupsnapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/client-go/discovery" k8scheme "k8s.io/client-go/kubernetes/scheme" kbclient "sigs.k8s.io/controller-runtime/pkg/client" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" @@ -166,6 +168,9 @@ func (f *factory) KubebuilderClient() (kbclient.Client, error) { if err := snapshotv1api.AddToScheme(scheme); err != nil { return nil, err } + if err := volumegroupsnapshotv1beta1.AddToScheme(scheme); err != nil { + return nil, err + } kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{ Scheme: scheme, }) @@ -202,6 +207,9 @@ func (f *factory) KubebuilderWatchClient() (kbclient.WithWatch, error) { if err := snapshotv1api.AddToScheme(scheme); err != nil { return nil, err } + if err := volumegroupsnapshotv1beta1.AddToScheme(scheme); err != nil { + return nil, err + } kubebuilderWatchClient, err := kbclient.NewWithWatch(clientConfig, kbclient.Options{ Scheme: scheme, }) diff --git a/pkg/cmd/cli/nodeagent/server.go b/pkg/cmd/cli/nodeagent/server.go index ce4ad2090..dae91d91f 100644 --- a/pkg/cmd/cli/nodeagent/server.go +++ b/pkg/cmd/cli/nodeagent/server.go @@ -46,7 +46,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" - snapshotv1client "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned" + snapshotv1client "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index bc8171820..d89f88fdb 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -26,8 +26,10 @@ import ( "strings" "time" + volumegroupsnapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1" + logrusr "github.com/bombsimon/logrusr/v3" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" @@ -230,6 +232,10 @@ func newServer(f client.Factory, config *config.Config, logger *logrus.Logger) ( cancelFunc() return nil, err } + if err := volumegroupsnapshotv1beta1.AddToScheme(scheme); err != nil { + cancelFunc() + return nil, err + } if err := batchv1api.AddToScheme(scheme); err != nil { cancelFunc() return nil, err diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index 528117985..c8173afc6 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -28,7 +28,7 @@ import ( corev1api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/fatih/color" diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index bc296f790..18b351e20 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -24,7 +24,7 @@ import ( "slices" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 1bc3753d0..2a806243d 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -28,7 +28,7 @@ import ( "time" "github.com/google/go-cmp/cmp" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" diff --git a/pkg/controller/backup_deletion_controller.go b/pkg/controller/backup_deletion_controller.go index edd176f3a..4090accc9 100644 --- a/pkg/controller/backup_deletion_controller.go +++ b/pkg/controller/backup_deletion_controller.go @@ -23,7 +23,7 @@ import ( "time" jsonpatch "github.com/evanphx/json-patch/v5" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/controller/backup_deletion_controller_test.go b/pkg/controller/backup_deletion_controller_test.go index 3ed5184bf..a9b2c2035 100644 --- a/pkg/controller/backup_deletion_controller_test.go +++ b/pkg/controller/backup_deletion_controller_test.go @@ -25,7 +25,7 @@ import ( "reflect" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "context" diff --git a/pkg/controller/backup_sync_controller.go b/pkg/controller/backup_sync_controller.go index 00eb9dafd..69a6b2ac1 100644 --- a/pkg/controller/backup_sync_controller.go +++ b/pkg/controller/backup_sync_controller.go @@ -21,7 +21,7 @@ import ( "fmt" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/pkg/controller/backup_sync_controller_test.go b/pkg/controller/backup_sync_controller_test.go index a4db93b16..0aa7f2818 100644 --- a/pkg/controller/backup_sync_controller_test.go +++ b/pkg/controller/backup_sync_controller_test.go @@ -24,7 +24,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" diff --git a/pkg/controller/data_upload_controller.go b/pkg/controller/data_upload_controller.go index 07e0ace36..17b274db2 100644 --- a/pkg/controller/data_upload_controller.go +++ b/pkg/controller/data_upload_controller.go @@ -22,7 +22,7 @@ import ( "strings" "time" - snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/typed/volumesnapshot/v1" + snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/typed/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/controller/data_upload_controller_test.go b/pkg/controller/data_upload_controller_test.go index e1d4ad13c..cc2284f43 100644 --- a/pkg/controller/data_upload_controller_test.go +++ b/pkg/controller/data_upload_controller_test.go @@ -24,8 +24,8 @@ import ( "github.com/vmware-tanzu/velero/pkg/nodeagent" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/fake" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/fake" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" diff --git a/pkg/controller/restore_controller_test.go b/pkg/controller/restore_controller_test.go index 1d294ed63..7ef36aae2 100644 --- a/pkg/controller/restore_controller_test.go +++ b/pkg/controller/restore_controller_test.go @@ -23,7 +23,7 @@ import ( "testing" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index f88696faf..58748a105 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -21,8 +21,8 @@ import ( "fmt" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/typed/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/typed/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/exposer/csi_snapshot_test.go b/pkg/exposer/csi_snapshot_test.go index 28e4477fb..f0a989c69 100644 --- a/pkg/exposer/csi_snapshot_test.go +++ b/pkg/exposer/csi_snapshot_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/fake" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/fake" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/kuberesource/kuberesource.go b/pkg/kuberesource/kuberesource.go index c39e769db..0f1dbf8ec 100644 --- a/pkg/kuberesource/kuberesource.go +++ b/pkg/kuberesource/kuberesource.go @@ -33,7 +33,9 @@ var ( Secrets = schema.GroupResource{Group: "", Resource: "secrets"} VolumeSnapshotClasses = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotclasses"} VolumeSnapshots = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshots"} + VolumeGroupSnapshots = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumegroupsnapshots"} VolumeSnapshotContents = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotcontents"} PriorityClasses = schema.GroupResource{Group: "scheduling.k8s.io", Resource: "priorityclasses"} DataUploads = schema.GroupResource{Group: "velero.io", Resource: "datauploads"} + VGSKind = "VolumeGroupSnapshot" ) diff --git a/pkg/persistence/mocks/backup_store.go b/pkg/persistence/mocks/backup_store.go index bd1ef5dbd..b9c4184c3 100644 --- a/pkg/persistence/mocks/backup_store.go +++ b/pkg/persistence/mocks/backup_store.go @@ -31,7 +31,7 @@ import ( volume "github.com/vmware-tanzu/velero/internal/volume" - volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" ) // BackupStore is an autogenerated mock type for the BackupStore type diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index 0fd069d85..44ad96118 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -23,7 +23,7 @@ import ( "strings" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index ff28d6197..0314f78c3 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -27,7 +27,7 @@ import ( "strings" "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/restore/actions/csi/pvc_action.go b/pkg/restore/actions/csi/pvc_action.go index dd7893ff0..4e7074315 100644 --- a/pkg/restore/actions/csi/pvc_action.go +++ b/pkg/restore/actions/csi/pvc_action.go @@ -21,7 +21,7 @@ import ( "encoding/json" "fmt" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/restore/actions/csi/pvc_action_test.go b/pkg/restore/actions/csi/pvc_action_test.go index a8431505e..901f4c18c 100644 --- a/pkg/restore/actions/csi/pvc_action_test.go +++ b/pkg/restore/actions/csi/pvc_action_test.go @@ -24,7 +24,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/restore/actions/csi/volumesnapshot_action.go b/pkg/restore/actions/csi/volumesnapshot_action.go index 560ce33d7..49e346bac 100644 --- a/pkg/restore/actions/csi/volumesnapshot_action.go +++ b/pkg/restore/actions/csi/volumesnapshot_action.go @@ -19,7 +19,7 @@ package csi import ( "fmt" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/restore/actions/csi/volumesnapshot_action_test.go b/pkg/restore/actions/csi/volumesnapshot_action_test.go index e84079bee..27001858a 100644 --- a/pkg/restore/actions/csi/volumesnapshot_action_test.go +++ b/pkg/restore/actions/csi/volumesnapshot_action_test.go @@ -21,7 +21,7 @@ import ( "fmt" "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/restore/actions/csi/volumesnapshotclass_action.go b/pkg/restore/actions/csi/volumesnapshotclass_action.go index 34782efe2..c906a04b2 100644 --- a/pkg/restore/actions/csi/volumesnapshotclass_action.go +++ b/pkg/restore/actions/csi/volumesnapshotclass_action.go @@ -17,7 +17,7 @@ limitations under the License. package csi import ( - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/restore/actions/csi/volumesnapshotclass_action_test.go b/pkg/restore/actions/csi/volumesnapshotclass_action_test.go index fe222dbb3..73d86a109 100644 --- a/pkg/restore/actions/csi/volumesnapshotclass_action_test.go +++ b/pkg/restore/actions/csi/volumesnapshotclass_action_test.go @@ -19,7 +19,7 @@ package csi import ( "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/restore/actions/csi/volumesnapshotcontent_action.go b/pkg/restore/actions/csi/volumesnapshotcontent_action.go index f4b42b30a..9b7d5c878 100644 --- a/pkg/restore/actions/csi/volumesnapshotcontent_action.go +++ b/pkg/restore/actions/csi/volumesnapshotcontent_action.go @@ -17,7 +17,7 @@ limitations under the License. package csi import ( - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/restore/actions/csi/volumesnapshotcontent_action_test.go b/pkg/restore/actions/csi/volumesnapshotcontent_action_test.go index 579096a59..9ab2aa988 100644 --- a/pkg/restore/actions/csi/volumesnapshotcontent_action_test.go +++ b/pkg/restore/actions/csi/volumesnapshotcontent_action_test.go @@ -21,7 +21,7 @@ import ( "fmt" "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/pkg/restore/request.go b/pkg/restore/request.go index 0e41b10ca..452be4264 100644 --- a/pkg/restore/request.go +++ b/pkg/restore/request.go @@ -21,7 +21,7 @@ import ( "io" "sort" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 563c9741d..93e45f216 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -31,7 +31,7 @@ import ( "time" "github.com/google/uuid" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index 7d283800d..360fb8409 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -28,7 +28,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/collections" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" diff --git a/pkg/test/fake_controller_runtime_client.go b/pkg/test/fake_controller_runtime_client.go index 34b3908d1..e22220404 100644 --- a/pkg/test/fake_controller_runtime_client.go +++ b/pkg/test/fake_controller_runtime_client.go @@ -19,7 +19,9 @@ package test import ( "testing" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + volumegroupsnapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumegroupsnapshot/v1beta1" + + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/stretchr/testify/require" appsv1api "k8s.io/api/apps/v1" batchv1api "k8s.io/api/batch/v1" @@ -58,6 +60,7 @@ func NewFakeControllerRuntimeClient(t *testing.T, initObjs ...runtime.Object) cl require.NoError(t, snapshotv1api.AddToScheme(scheme)) require.NoError(t, storagev1api.AddToScheme(scheme)) require.NoError(t, batchv1api.AddToScheme(scheme)) + require.NoError(t, volumegroupsnapshotv1beta1.AddToScheme(scheme)) return k8sfake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjs...).Build() } diff --git a/pkg/test/mocks.go b/pkg/test/mocks.go index b7a7249a5..a82d07d00 100644 --- a/pkg/test/mocks.go +++ b/pkg/test/mocks.go @@ -1,8 +1,8 @@ package test import ( - snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v7/listers/volumesnapshot/v1" + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v8/listers/volumesnapshot/v1" "k8s.io/apimachinery/pkg/labels" ) diff --git a/pkg/test/mocks/VolumeSnapshotLister.go b/pkg/test/mocks/VolumeSnapshotLister.go index b1fb43637..0e8163ed7 100644 --- a/pkg/test/mocks/VolumeSnapshotLister.go +++ b/pkg/test/mocks/VolumeSnapshotLister.go @@ -6,9 +6,9 @@ import ( mock "github.com/stretchr/testify/mock" labels "k8s.io/apimachinery/pkg/labels" - v1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + v1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" - volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v7/listers/volumesnapshot/v1" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/listers/volumesnapshot/v1" ) // VolumeSnapshotLister is an autogenerated mock type for the VolumeSnapshotLister type diff --git a/pkg/util/csi/volume_snapshot.go b/pkg/util/csi/volume_snapshot.go index 3363ce632..f9be418a5 100644 --- a/pkg/util/csi/volume_snapshot.go +++ b/pkg/util/csi/volume_snapshot.go @@ -24,8 +24,8 @@ import ( "time" jsonpatch "github.com/evanphx/json-patch/v5" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/typed/volumesnapshot/v1" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/typed/volumesnapshot/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" corev1api "k8s.io/api/core/v1" diff --git a/pkg/util/csi/volume_snapshot_test.go b/pkg/util/csi/volume_snapshot_test.go index c6f10f34a..999ebf3f0 100644 --- a/pkg/util/csi/volume_snapshot_test.go +++ b/pkg/util/csi/volume_snapshot_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/fake" + snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotFake "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned/fake" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/test/util/csi/common.go b/test/util/csi/common.go index 5e58ec37d..373bc1502 100644 --- a/test/util/csi/common.go +++ b/test/util/csi/common.go @@ -21,8 +21,8 @@ import ( "fmt" "strings" - volumeSnapshotV1 "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" - snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned" + volumeSnapshotV1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes"