github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/backuprepo_controller_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package dataprotection
    21  
    22  import (
    23  	"time"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  	batchv1 "k8s.io/api/batch/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	storagev1 "k8s.io/api/storage/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	"k8s.io/apimachinery/pkg/api/resource"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/types"
    35  	"k8s.io/client-go/kubernetes"
    36  	"k8s.io/utils/clock/testing"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    39  
    40  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    41  	storagev1alpha1 "github.com/1aal/kubeblocks/apis/storage/v1alpha1"
    42  	"github.com/1aal/kubeblocks/pkg/constant"
    43  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    44  	"github.com/1aal/kubeblocks/pkg/generics"
    45  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    46  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    47  )
    48  
    49  var _ = Describe("BackupRepo controller", func() {
    50  	const namespace2 = "namespace2"
    51  	const pvcProtectionFinalizer = "kubernetes.io/pvc-protection"
    52  
    53  	cleanEnv := func() {
    54  		// must wait till resources deleted and no longer existed before the testcases start,
    55  		// otherwise if later it needs to create some new resource objects with the same name,
    56  		// in race conditions, it will find the existence of old objects, resulting failure to
    57  		// create the new objects.
    58  		By("clean resources")
    59  		// non-namespaced
    60  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    61  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupRepoSignature, true, ml)
    62  		testapps.ClearResources(&testCtx, generics.StorageProviderSignature, ml)
    63  		testapps.ClearResources(&testCtx, generics.CSIDriverSignature, ml)
    64  		testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
    65  
    66  		// namespaced
    67  		inNS := client.InNamespace(viper.GetString(constant.CfgKeyCtrlrMgrNS))
    68  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS, ml)
    69  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS, ml)
    70  		testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml)
    71  		testapps.ClearResources(&testCtx, generics.JobSignature, inNS, ml)
    72  
    73  		// namespace2
    74  		inNS2 := client.InNamespace(namespace2)
    75  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS2, ml)
    76  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS2, ml)
    77  		testapps.ClearResources(&testCtx, generics.SecretSignature, inNS2, ml)
    78  		testapps.ClearResources(&testCtx, generics.JobSignature, inNS2, ml)
    79  
    80  		// delete namespace2
    81  		Eventually(func(g Gomega) {
    82  			// from https://github.com/kubernetes-sigs/controller-runtime/issues/880#issuecomment-749742403
    83  			namespaceObj := &corev1.Namespace{}
    84  			err := testCtx.Cli.Get(testCtx.Ctx, types.NamespacedName{Name: namespace2}, namespaceObj)
    85  			if apierrors.IsNotFound(err) {
    86  				return
    87  			}
    88  			namespaceObj.Spec.Finalizers = []corev1.FinalizerName{}
    89  			// We have to use the k8s.io/client-go library here to expose
    90  			// ability to patch the /finalize subresource on the namespace
    91  			clientGo, err := kubernetes.NewForConfig(testEnv.Config)
    92  			Expect(err).Should(Succeed())
    93  			_, err = clientGo.CoreV1().Namespaces().Finalize(testCtx.Ctx, namespaceObj, metav1.UpdateOptions{})
    94  			Expect(err).Should(Succeed())
    95  		}).Should(Succeed())
    96  
    97  		// By("deleting the Namespace to perform the tests")
    98  		// Eventually(func(g Gomega) {
    99  		// 	namespace := testCtx.GetNamespaceObj()
   100  		// 	err := testCtx.Cli.Delete(testCtx.Ctx, &namespace)
   101  		// 	g.Expect(client.IgnoreNotFound(err)).To(Not(HaveOccurred()))
   102  		// 	g.Expect(client.IgnoreNotFound(testCtx.Cli.Get(
   103  		// 		testCtx.Ctx, testCtx.GetNamespaceKey(), &namespace))).To(Not(HaveOccurred()))
   104  		// }).Should(Succeed())
   105  	}
   106  
   107  	ensureNamespace := func(name string) {
   108  		Eventually(func(g Gomega) {
   109  			obj := &corev1.Namespace{}
   110  			obj.Name = name
   111  			err := testCtx.Cli.Get(testCtx.Ctx, client.ObjectKeyFromObject(obj), &corev1.Namespace{})
   112  			if err == nil {
   113  				return
   114  			}
   115  			g.Expect(client.IgnoreNotFound(err)).Should(Succeed())
   116  			err = testCtx.Cli.Create(testCtx.Ctx, obj)
   117  			g.Expect(err).Should(Succeed())
   118  		}).Should(Succeed())
   119  	}
   120  
   121  	BeforeEach(func() {
   122  		cleanEnv()
   123  		ensureNamespace(namespace2)
   124  	})
   125  
   126  	AfterEach(func() {
   127  		cleanEnv()
   128  	})
   129  
   130  	Context("BackupRepo controller test", func() {
   131  		const defaultCSIDriverName = "default.csi.driver"
   132  		var credentialSecretKey types.NamespacedName
   133  		var repoKey types.NamespacedName
   134  		var providerKey types.NamespacedName
   135  		var repo *dpv1alpha1.BackupRepo
   136  
   137  		createCredentialSecretSpec := func() {
   138  			obj := &corev1.Secret{}
   139  			obj.GenerateName = "credential-"
   140  			obj.Namespace = testCtx.DefaultNamespace
   141  			obj.StringData = map[string]string{
   142  				"cred-key1": "cred-val1",
   143  				"cred-key2": "cred-val2",
   144  			}
   145  			secret := testapps.CreateK8sResource(&testCtx, obj)
   146  			credentialSecretKey = client.ObjectKeyFromObject(secret)
   147  		}
   148  
   149  		createBackupRepoSpec := func(mutateFunc func(repo *dpv1alpha1.BackupRepo)) *dpv1alpha1.BackupRepo {
   150  			obj := &dpv1alpha1.BackupRepo{}
   151  			obj.GenerateName = "backuprepo-"
   152  			obj.Spec = dpv1alpha1.BackupRepoSpec{
   153  				StorageProviderRef: providerKey.Name,
   154  				VolumeCapacity:     resource.MustParse("100Gi"),
   155  				PVReclaimPolicy:    corev1.PersistentVolumeReclaimRetain,
   156  				Config: map[string]string{
   157  					"key1": "val1",
   158  					"key2": "val2",
   159  				},
   160  				Credential: &corev1.SecretReference{
   161  					Name:      credentialSecretKey.Name,
   162  					Namespace: credentialSecretKey.Namespace,
   163  				},
   164  			}
   165  			if mutateFunc != nil {
   166  				mutateFunc(obj)
   167  			}
   168  			repo = testapps.CreateK8sResource(&testCtx, obj).(*dpv1alpha1.BackupRepo)
   169  			repoKey = client.ObjectKeyFromObject(repo)
   170  			return repo
   171  		}
   172  
   173  		createStorageProviderSpec := func(mutateFunc func(provider *storagev1alpha1.StorageProvider)) {
   174  			obj := &storagev1alpha1.StorageProvider{}
   175  			obj.GenerateName = "storageprovider-"
   176  			obj.Spec.CSIDriverName = defaultCSIDriverName
   177  			obj.Spec.CSIDriverSecretTemplate = `
   178  value-of-key1: {{ index .Parameters "key1" }}
   179  value-of-key2: {{ index .Parameters "key2" }}
   180  value-of-cred-key1: {{ index .Parameters "cred-key1" }}
   181  value-of-cred-key2: {{ index .Parameters "cred-key2" }}
   182  `
   183  			obj.Spec.StorageClassTemplate = `
   184  provisioner: default.csi.driver
   185  parameters:
   186      value-of-key1: {{ index .Parameters "key1" }}
   187      value-of-key2: {{ index .Parameters "key2" }}
   188      value-of-cred-key1: {{ index .Parameters "cred-key1" }}
   189      value-of-cred-key2: {{ index .Parameters "cred-key2" }}
   190      secret-name: {{ .CSIDriverSecretRef.Name }}
   191      secret-namespace: {{ .CSIDriverSecretRef.Namespace }}
   192  `
   193  			obj.Status.Phase = storagev1alpha1.StorageProviderReady
   194  			meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{
   195  				Type:   storagev1alpha1.ConditionTypeCSIDriverInstalled,
   196  				Status: metav1.ConditionTrue,
   197  				Reason: "CSIDriverInstalled",
   198  			})
   199  			if mutateFunc != nil {
   200  				mutateFunc(obj)
   201  			}
   202  			provider := testapps.CreateK8sResource(&testCtx, obj.DeepCopy())
   203  			providerKey = client.ObjectKeyFromObject(provider)
   204  			// update status
   205  			newObj := provider.(*storagev1alpha1.StorageProvider)
   206  			patch := client.MergeFrom(newObj.DeepCopy())
   207  			newObj.Status = obj.Status
   208  			Expect(testCtx.Cli.Status().Patch(testCtx.Ctx, newObj, patch)).NotTo(HaveOccurred())
   209  		}
   210  
   211  		createCSIDriverObjectSpec := func(driverName string) {
   212  			obj := &storagev1.CSIDriver{}
   213  			obj.Name = driverName
   214  			testapps.CreateK8sResource(&testCtx, obj)
   215  		}
   216  
   217  		createBackupSpec := func(mutateFunc func(backup *dpv1alpha1.Backup)) *dpv1alpha1.Backup {
   218  			obj := &dpv1alpha1.Backup{}
   219  			obj.GenerateName = "backup-"
   220  			obj.Namespace = testCtx.DefaultNamespace
   221  			obj.Labels = map[string]string{
   222  				dataProtectionBackupRepoKey:          repoKey.Name,
   223  				dataProtectionWaitRepoPreparationKey: trueVal,
   224  			}
   225  			obj.Spec.BackupMethod = "test-backup-method"
   226  			obj.Spec.BackupPolicyName = "default"
   227  			if mutateFunc != nil {
   228  				mutateFunc(obj)
   229  			}
   230  			backup := testapps.CreateK8sResource(&testCtx, obj).(*dpv1alpha1.Backup)
   231  			// updating the status of the Backup to COMPLETED, backup repo controller only
   232  			// handles for non-failed backups.
   233  			Eventually(func(g Gomega) {
   234  				obj := &dpv1alpha1.Backup{}
   235  				err := testCtx.Cli.Get(testCtx.Ctx, client.ObjectKeyFromObject(backup), obj)
   236  				g.Expect(err).ShouldNot(HaveOccurred())
   237  				if obj.Status.Phase == dpv1alpha1.BackupPhaseFailed {
   238  					// the controller will set the status to failed because
   239  					// essential objects (e.g. backup policy) are missed.
   240  					// we set the status to completed after that, to avoid conflict.
   241  					obj.Status.Phase = dpv1alpha1.BackupPhaseCompleted
   242  					err = testCtx.Cli.Status().Update(testCtx.Ctx, obj)
   243  					g.Expect(err).ShouldNot(HaveOccurred())
   244  				} else {
   245  					// check again
   246  					g.Expect(false).Should(BeTrue())
   247  				}
   248  			}).Should(Succeed())
   249  			return backup
   250  		}
   251  
   252  		getBackupRepo := func(g Gomega, key types.NamespacedName) *dpv1alpha1.BackupRepo {
   253  			repo := &dpv1alpha1.BackupRepo{}
   254  			err := testCtx.Cli.Get(testCtx.Ctx, key, repo)
   255  			g.Expect(err).ShouldNot(HaveOccurred())
   256  			return repo
   257  		}
   258  
   259  		deleteBackup := func(g Gomega, key types.NamespacedName) {
   260  			backupObj := &dpv1alpha1.Backup{}
   261  			err := testCtx.Cli.Get(testCtx.Ctx, key, backupObj)
   262  			if apierrors.IsNotFound(err) {
   263  				return
   264  			}
   265  			g.Expect(err).ShouldNot(HaveOccurred())
   266  			// remove finalizers
   267  			backupObj.Finalizers = nil
   268  			err = testCtx.Cli.Update(testCtx.Ctx, backupObj)
   269  			g.Expect(err).ShouldNot(HaveOccurred())
   270  			// delete the backup
   271  			err = testCtx.Cli.Delete(testCtx.Ctx, backupObj)
   272  			g.Expect(err).ShouldNot(HaveOccurred())
   273  		}
   274  
   275  		preCheckResouceName := func(repo *dpv1alpha1.BackupRepo) string {
   276  			reconCtx := reconcileContext{repo: repo}
   277  			return reconCtx.preCheckResourceName()
   278  		}
   279  
   280  		completePreCheckJob := func(repo *dpv1alpha1.BackupRepo) {
   281  			jobName := preCheckResouceName(repo)
   282  			namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
   283  			Eventually(testapps.GetAndChangeObjStatus(&testCtx, types.NamespacedName{Name: jobName, Namespace: namespace}, func(job *batchv1.Job) {
   284  				job.Status.Conditions = append(job.Status.Conditions, batchv1.JobCondition{
   285  					Type:   batchv1.JobComplete,
   286  					Status: corev1.ConditionTrue,
   287  				})
   288  			})).Should(Succeed())
   289  		}
   290  
   291  		completePreCheckJobWithError := func(repo *dpv1alpha1.BackupRepo, message string) {
   292  			jobName := preCheckResouceName(repo)
   293  			namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
   294  			Eventually(testapps.GetAndChangeObjStatus(&testCtx, types.NamespacedName{Name: jobName, Namespace: namespace}, func(job *batchv1.Job) {
   295  				job.Status.Conditions = append(job.Status.Conditions, batchv1.JobCondition{
   296  					Type:    batchv1.JobFailed,
   297  					Status:  corev1.ConditionTrue,
   298  					Reason:  "Failed",
   299  					Message: message,
   300  				})
   301  			})).Should(Succeed())
   302  		}
   303  
   304  		removePVCProtectionFinalizer := func(pvcKey types.NamespacedName) {
   305  			Eventually(testapps.GetAndChangeObjStatus(&testCtx, pvcKey, func(pvc *corev1.PersistentVolumeClaim) {
   306  				controllerutil.RemoveFinalizer(pvc, pvcProtectionFinalizer)
   307  			})).Should(Succeed())
   308  		}
   309  
   310  		BeforeEach(func() {
   311  			cleanEnv()
   312  			Expect(client.IgnoreAlreadyExists(testCtx.CreateNamespace())).To(Not(HaveOccurred()))
   313  			createCredentialSecretSpec()
   314  			createCSIDriverObjectSpec(defaultCSIDriverName)
   315  			createStorageProviderSpec(nil)
   316  			createBackupRepoSpec(nil)
   317  			completePreCheckJob(repo)
   318  		})
   319  
   320  		AfterEach(func() {
   321  			cleanEnv()
   322  		})
   323  
   324  		It("should monitor the status of the storage provider", func() {
   325  			By("creating a BackupRepo which is referencing a non-existent storage provider")
   326  			createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   327  				repo.Spec.StorageProviderRef = "myprovider" // not exist for now
   328  			})
   329  			By("checking the status of the BackupRepo, should be not ready")
   330  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   331  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   332  				g.Expect(cond).ToNot(BeNil())
   333  				g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionFalse))
   334  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonStorageProviderNotFound))
   335  				g.Expect(repo.Status.Phase).To(Equal(dpv1alpha1.BackupRepoFailed))
   336  			})).Should(Succeed())
   337  
   338  			By("creating the required storage provider")
   339  			createStorageProviderSpec(func(provider *storagev1alpha1.StorageProvider) {
   340  				provider.GenerateName = ""
   341  				provider.Name = "myprovider"
   342  			})
   343  
   344  			By("checking the status of the BackupRepo")
   345  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   346  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   347  				g.Expect(cond).ToNot(BeNil())
   348  				g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue))
   349  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonStorageProviderReady))
   350  				g.Expect(repo.Status.Phase).To(Equal(dpv1alpha1.BackupRepoPreChecking))
   351  			})).Should(Succeed())
   352  
   353  			By("updating the status of the storage provider to not ready")
   354  			Eventually(testapps.GetAndChangeObjStatus(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   355  				provider.Status.Phase = storagev1alpha1.StorageProviderNotReady
   356  				meta.SetStatusCondition(&provider.Status.Conditions, metav1.Condition{
   357  					Type:   storagev1alpha1.ConditionTypeCSIDriverInstalled,
   358  					Status: metav1.ConditionFalse,
   359  					Reason: "CSINotInstalled",
   360  				})
   361  			})).Should(Succeed())
   362  			By("checking the status of the BackupRepo, should become failed")
   363  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   364  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   365  				g.Expect(cond).ToNot(BeNil())
   366  				g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionFalse))
   367  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonStorageProviderNotReady))
   368  				g.Expect(repo.Status.Phase).To(Equal(dpv1alpha1.BackupRepoFailed))
   369  			})).Should(Succeed())
   370  
   371  			By("deleting the storage provider")
   372  			testapps.DeleteObject(&testCtx, providerKey, &storagev1alpha1.StorageProvider{})
   373  			By("checking the status of the BackupRepo, condition should become NotFound")
   374  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   375  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   376  				g.Expect(cond).ToNot(BeNil())
   377  				g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionFalse))
   378  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonStorageProviderNotFound))
   379  				g.Expect(repo.Status.Phase).To(Equal(dpv1alpha1.BackupRepoFailed))
   380  			})).Should(Succeed())
   381  		})
   382  
   383  		It("should create StorageClass and Secret for the CSI driver", func() {
   384  			var secretRef corev1.SecretReference
   385  			var storageClassName string
   386  			By("checking the BackupRepo, should be ready")
   387  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   388  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   389  				g.Expect(repo.Status.GeneratedCSIDriverSecret).NotTo(BeNil())
   390  				g.Expect(repo.Status.GeneratedStorageClassName).NotTo(BeEmpty())
   391  				g.Expect(repo.Status.BackupPVCName).NotTo(BeEmpty())
   392  				secretRef = *repo.Status.GeneratedCSIDriverSecret
   393  				storageClassName = repo.Status.GeneratedStorageClassName
   394  			})).Should(Succeed())
   395  
   396  			By("checking the Secret")
   397  			secretKey := types.NamespacedName{Name: secretRef.Name, Namespace: secretRef.Namespace}
   398  			Eventually(testapps.CheckObj(&testCtx, secretKey, func(g Gomega, secret *corev1.Secret) {
   399  				g.Expect(secret.Data).To(Equal(map[string][]byte{
   400  					"value-of-key1":      []byte("val1"),
   401  					"value-of-key2":      []byte("val2"),
   402  					"value-of-cred-key1": []byte("cred-val1"),
   403  					"value-of-cred-key2": []byte("cred-val2"),
   404  				}))
   405  				g.Expect(isOwned(repo, secret)).To(BeTrue())
   406  				g.Expect(secret.Labels[dataProtectionBackupRepoKey]).To(Equal(repoKey.Name))
   407  			})).Should(Succeed())
   408  
   409  			By("checking the StorageClass")
   410  			storageClassNameKey := types.NamespacedName{Name: storageClassName}
   411  			Eventually(testapps.CheckObj(&testCtx, storageClassNameKey, func(g Gomega, storageClass *storagev1.StorageClass) {
   412  				g.Expect(storageClass.Parameters).To(Equal(map[string]string{
   413  					"value-of-key1":      "val1",
   414  					"value-of-key2":      "val2",
   415  					"value-of-cred-key1": "cred-val1",
   416  					"value-of-cred-key2": "cred-val2",
   417  					"secret-name":        secretKey.Name,
   418  					"secret-namespace":   secretKey.Namespace,
   419  				}))
   420  				g.Expect(isOwned(repo, storageClass)).To(BeTrue())
   421  				g.Expect(storageClass.Labels[dataProtectionBackupRepoKey]).To(Equal(repoKey.Name))
   422  				g.Expect(storageClass.Provisioner).To(Equal(defaultCSIDriverName))
   423  				g.Expect(*storageClass.ReclaimPolicy).To(Equal(corev1.PersistentVolumeReclaimRetain))
   424  				g.Expect(*storageClass.VolumeBindingMode).To(Equal(storagev1.VolumeBindingImmediate))
   425  			})).Should(Succeed())
   426  		})
   427  
   428  		It("should update the Secret object if the template or values got changed", func() {
   429  			By("checking the Secret")
   430  			var secretKey types.NamespacedName
   431  			var reversion string
   432  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   433  				g.Expect(repo.Status.GeneratedCSIDriverSecret).NotTo(BeNil())
   434  				secretKey = types.NamespacedName{
   435  					Name:      repo.Status.GeneratedCSIDriverSecret.Name,
   436  					Namespace: repo.Status.GeneratedCSIDriverSecret.Namespace,
   437  				}
   438  			})).Should(Succeed())
   439  			Eventually(testapps.CheckObj(&testCtx, secretKey, func(g Gomega, secret *corev1.Secret) {
   440  				reversion = secret.ResourceVersion
   441  			})).Should(Succeed())
   442  
   443  			By("updating the template")
   444  			Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   445  				provider.Spec.CSIDriverSecretTemplate += "\nnew-item: new-value"
   446  			})).Should(Succeed())
   447  			By("checking the Secret again, should have new generation and new content")
   448  			Eventually(testapps.CheckObj(&testCtx, secretKey, func(g Gomega, secret *corev1.Secret) {
   449  				g.Expect(secret.Data).To(Equal(map[string][]byte{
   450  					"value-of-key1":      []byte("val1"),
   451  					"value-of-key2":      []byte("val2"),
   452  					"value-of-cred-key1": []byte("cred-val1"),
   453  					"value-of-cred-key2": []byte("cred-val2"),
   454  					"new-item":           []byte("new-value"),
   455  				}))
   456  				g.Expect(secret.ResourceVersion).ToNot(Equal(reversion))
   457  				reversion = secret.ResourceVersion
   458  			})).Should(Succeed())
   459  
   460  			By("updating the config")
   461  			Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
   462  				repo.Spec.Config["key1"] = "changed-val1"
   463  			})).Should(Succeed())
   464  			By("checking the Secret again, should have new generation and new content")
   465  			Eventually(testapps.CheckObj(&testCtx, secretKey, func(g Gomega, secret *corev1.Secret) {
   466  				g.Expect(secret.Data).To(Equal(map[string][]byte{
   467  					"value-of-key1":      []byte("changed-val1"),
   468  					"value-of-key2":      []byte("val2"),
   469  					"value-of-cred-key1": []byte("cred-val1"),
   470  					"value-of-cred-key2": []byte("cred-val2"),
   471  					"new-item":           []byte("new-value"),
   472  				}))
   473  				g.Expect(secret.ResourceVersion).ToNot(Equal(reversion))
   474  				reversion = secret.ResourceVersion
   475  			})).Should(Succeed())
   476  
   477  			By("updating the credential")
   478  			Eventually(testapps.GetAndChangeObj(&testCtx, credentialSecretKey, func(secret *corev1.Secret) {
   479  				secret.Data["cred-key1"] = []byte("changed-cred-val1")
   480  			})).Should(Succeed())
   481  			By("checking the Secret again, should have new generation and new content")
   482  			Eventually(testapps.CheckObj(&testCtx, secretKey, func(g Gomega, secret *corev1.Secret) {
   483  				g.Expect(secret.Data).To(Equal(map[string][]byte{
   484  					"value-of-key1":      []byte("changed-val1"),
   485  					"value-of-key2":      []byte("val2"),
   486  					"value-of-cred-key1": []byte("changed-cred-val1"),
   487  					"value-of-cred-key2": []byte("cred-val2"),
   488  					"new-item":           []byte("new-value"),
   489  				}))
   490  				g.Expect(secret.ResourceVersion).ToNot(Equal(reversion))
   491  				reversion = secret.ResourceVersion
   492  			})).Should(Succeed())
   493  		})
   494  
   495  		It("should fail if the secret referenced by the credential secret not found", func() {
   496  			By("checking the repo object to make sure it's ready")
   497  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   498  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   499  			})).Should(Succeed())
   500  			By("updating to a non-existing credential")
   501  			Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
   502  				repo.Spec.Credential.Name += "non-existing"
   503  			})).Should(Succeed())
   504  			By("checking the repo object again, it should be failed")
   505  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   506  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   507  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeParametersChecked)
   508  				g.Expect(cond).NotTo(BeNil())
   509  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   510  				g.Expect(cond.Reason).Should(Equal(ReasonCredentialSecretNotFound))
   511  			})).Should(Succeed())
   512  		})
   513  
   514  		It("should fail if the secret template is invalid", func() {
   515  			By("setting a invalid template")
   516  			Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   517  				provider.Spec.CSIDriverSecretTemplate = "{{ bad template }"
   518  			})).Should(Succeed())
   519  			By("checking the repo status")
   520  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   521  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   522  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageClassCreated)
   523  				g.Expect(cond).NotTo(BeNil())
   524  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   525  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPrepareCSISecretFailed))
   526  				g.Expect(cond.Message).Should(ContainSubstring(`function "bad" not defined`))
   527  			})).Should(Succeed())
   528  		})
   529  
   530  		It("should fail if the render result of the secret template is not a yaml", func() {
   531  			By("setting a invalid template")
   532  			Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   533  				provider.Spec.CSIDriverSecretTemplate = "bad yaml"
   534  			})).Should(Succeed())
   535  			By("checking the repo status")
   536  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   537  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   538  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageClassCreated)
   539  				g.Expect(cond).NotTo(BeNil())
   540  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   541  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPrepareCSISecretFailed))
   542  				g.Expect(cond.Message).Should(ContainSubstring(`cannot unmarshal string into Go value of type map[string]string`))
   543  			})).Should(Succeed())
   544  		})
   545  
   546  		It("should fail if the storage class template is invalid", func() {
   547  			By("setting a invalid template")
   548  			Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   549  				provider.Spec.StorageClassTemplate = "{{ bad template }"
   550  			})).Should(Succeed())
   551  			By("creating a new repo to reference the provider")
   552  			createBackupRepoSpec(nil)
   553  			By("checking the repo status")
   554  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   555  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   556  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageClassCreated)
   557  				g.Expect(cond).NotTo(BeNil())
   558  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   559  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPrepareStorageClassFailed))
   560  				g.Expect(cond.Message).Should(ContainSubstring(`function "bad" not defined`))
   561  			})).Should(Succeed())
   562  		})
   563  
   564  		It("should fail if the render result of the storage class template is not a yaml", func() {
   565  			By("setting a invalid template")
   566  			Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   567  				provider.Spec.StorageClassTemplate = "bad yaml"
   568  			})).Should(Succeed())
   569  			By("creating a new repo to reference the provider")
   570  			createBackupRepoSpec(nil)
   571  			By("checking the repo status")
   572  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   573  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   574  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageClassCreated)
   575  				g.Expect(cond).NotTo(BeNil())
   576  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   577  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPrepareStorageClassFailed))
   578  				g.Expect(cond.Message).Should(ContainSubstring(`cannot unmarshal string into Go value of type v1.StorageClass`))
   579  			})).Should(Succeed())
   580  		})
   581  
   582  		It("should run a pre-check job", func() {
   583  			By("creating a backup repo")
   584  			createBackupRepoSpec(nil)
   585  
   586  			By("checking the pre-check job resources")
   587  			pvcName := preCheckResouceName(repo)
   588  			jobName := preCheckResouceName(repo)
   589  			namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
   590  			checkResources := func(exists bool) {
   591  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: jobName, Namespace: namespace},
   592  					&batchv1.Job{}, exists)).WithOffset(1).Should(Succeed())
   593  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: pvcName, Namespace: namespace},
   594  					&corev1.PersistentVolumeClaim{}, exists)).WithOffset(1).Should(Succeed())
   595  			}
   596  			checkResources(true)
   597  
   598  			By("checking repo's status, it should fail if the pre-check job has failed")
   599  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   600  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoPreChecking))
   601  			})).Should(Succeed())
   602  			completePreCheckJobWithError(repo, "connect to endpoint failed")
   603  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   604  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   605  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypePreCheckPassed)
   606  				g.Expect(cond).NotTo(BeNil())
   607  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   608  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPreCheckFailed))
   609  				g.Expect(cond.Message).Should(ContainSubstring(`connect to endpoint failed`))
   610  			})).Should(Succeed())
   611  
   612  			By("checking the resources, they should be deleted")
   613  			removePVCProtectionFinalizer(types.NamespacedName{Name: pvcName, Namespace: namespace})
   614  			checkResources(false)
   615  
   616  			By("updating the repo, it should run the pre-check job again")
   617  			Eventually(testapps.GetAndChangeObj(&testCtx, credentialSecretKey, func(cred *corev1.Secret) {
   618  				cred.Data["new-key"] = []byte("new-value")
   619  			})).Should(Succeed())
   620  			checkResources(true)
   621  			completePreCheckJob(repo)
   622  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   623  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   624  			})).Should(Succeed())
   625  		})
   626  
   627  		It("should remove the stale pre-check job if the repo spec has changed too quickly", func() {
   628  			resourceName := preCheckResouceName(repo)
   629  			namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
   630  			updateRepoAndCheckResources := func(content string, lastJobUID types.UID) (string, types.UID) {
   631  				Eventually(testapps.GetAndChangeObj(&testCtx, credentialSecretKey, func(cred *corev1.Secret) {
   632  					cred.Data["new-key"] = []byte(content)
   633  				})).WithOffset(1).Should(Succeed())
   634  				var digest string
   635  				var uid types.UID
   636  				Eventually(func(g Gomega) {
   637  					pvc := &corev1.PersistentVolumeClaim{}
   638  					err := testCtx.Cli.Get(testCtx.Ctx, client.ObjectKey{Name: resourceName, Namespace: namespace}, pvc)
   639  					g.Expect(err).ToNot(HaveOccurred())
   640  					job := &batchv1.Job{}
   641  					err = testCtx.Cli.Get(testCtx.Ctx, client.ObjectKey{Name: resourceName, Namespace: namespace}, job)
   642  					g.Expect(err).ToNot(HaveOccurred())
   643  					g.Expect(pvc.Annotations[dataProtectionBackupRepoDigestAnnotationKey]).To(
   644  						BeEquivalentTo(job.Annotations[dataProtectionBackupRepoDigestAnnotationKey]))
   645  					digest = job.Annotations[dataProtectionBackupRepoDigestAnnotationKey]
   646  					uid = job.UID
   647  					g.Expect(digest).ToNot(BeEmpty())
   648  					g.Expect(uid).ToNot(Equal(lastJobUID))
   649  				}).WithOffset(1).Should(Succeed())
   650  				return digest, uid
   651  			}
   652  
   653  			By("updating the repo, and then get the digest from the pre-check resources")
   654  			digest1, uid1 := updateRepoAndCheckResources("value1", "")
   655  
   656  			By("updating the repo again, and then get the digest")
   657  			removePVCProtectionFinalizer(types.NamespacedName{Name: resourceName, Namespace: namespace})
   658  			digest2, uid2 := updateRepoAndCheckResources("value2", uid1)
   659  
   660  			By("checking the digests are different")
   661  			Expect(digest1).ToNot(Equal(digest2))
   662  			Expect(uid1).ToNot(Equal(uid2))
   663  			completePreCheckJob(repo)
   664  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   665  				g.Expect(repo.Annotations[dataProtectionBackupRepoDigestAnnotationKey]).To(Equal(digest2))
   666  			})).Should(Succeed())
   667  		})
   668  
   669  		It("should timeout if the pre-check job runs too long", func() {
   670  			fakeClock := testing.NewFakeClock(time.Now())
   671  			original := wallClock
   672  			wallClock = fakeClock
   673  			defer func() {
   674  				wallClock = original
   675  			}()
   676  			// create a new repo
   677  			createBackupRepoSpec(nil)
   678  			// make the job timed out
   679  			fakeClock.Step(defaultPreCheckTimeout * 2)
   680  			// trigger reconciliation
   681  			Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
   682  				if repo.Annotations == nil {
   683  					repo.Annotations = make(map[string]string)
   684  				}
   685  				repo.Annotations["touch"] = "whatever"
   686  			})).Should(Succeed())
   687  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   688  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   689  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypePreCheckPassed)
   690  				g.Expect(cond).ToNot(BeNil())
   691  				g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionFalse))
   692  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPreCheckFailed))
   693  				g.Expect(cond.Message).Should(ContainSubstring("timeout"))
   694  			})).Should(Succeed())
   695  		})
   696  
   697  		createBackupAndCheckPVC := func(namespace string) (backup *dpv1alpha1.Backup, pvcName string) {
   698  			By("making sure the repo is ready")
   699  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   700  				g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady), "%+v", repo)
   701  				g.Expect(repo.Status.BackupPVCName).ShouldNot(BeEmpty())
   702  				pvcName = repo.Status.BackupPVCName
   703  			})).Should(Succeed())
   704  			By("creating a Backup object in the namespace")
   705  			backup = createBackupSpec(func(backup *dpv1alpha1.Backup) {
   706  				backup.Namespace = namespace
   707  			})
   708  			By("checking the PVC has been created in the namespace")
   709  			pvcKey := types.NamespacedName{
   710  				Name:      pvcName,
   711  				Namespace: namespace,
   712  			}
   713  			Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
   714  			return backup, pvcName
   715  		}
   716  
   717  		It("should create a PVC in Backup's namespace (in default namespace)", func() {
   718  			createBackupAndCheckPVC(testCtx.DefaultNamespace)
   719  		})
   720  
   721  		It("should create a PVC in Backup's namespace (in namespace2)", func() {
   722  			createBackupAndCheckPVC(namespace2)
   723  		})
   724  
   725  		Context("storage provider with PersistentVolumeClaimTemplate", func() {
   726  			It("should create a PVC in Backup's namespace (in default namespace)", func() {
   727  				By("setting the PersistentVolumeClaimTemplate")
   728  				createStorageProviderSpec(func(provider *storagev1alpha1.StorageProvider) {
   729  					provider.Spec.PersistentVolumeClaimTemplate = `
   730  apiVersion: v1
   731  kind: PersistentVolumeClaim
   732  metadata:
   733    labels:
   734      byPVCTemplate: "true"
   735  spec:
   736    storageClassName: {{ .GeneratedStorageClassName }}
   737    accessModes:
   738      - ReadWriteOnce
   739    resources:
   740      volumeMode: Filesystem
   741  `
   742  				})
   743  				createBackupRepoSpec(nil)
   744  				completePreCheckJob(repo)
   745  				_, pvcName := createBackupAndCheckPVC(testCtx.DefaultNamespace)
   746  
   747  				Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: pvcName, Namespace: testCtx.DefaultNamespace},
   748  					func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
   749  						repo := getBackupRepo(g, repoKey)
   750  						g.Expect(pvc.Spec.StorageClassName).ShouldNot(BeNil())
   751  						g.Expect(*pvc.Spec.StorageClassName).Should(Equal(repo.Status.GeneratedStorageClassName))
   752  						g.Expect(pvc.Spec.Resources.Requests.Storage()).ShouldNot(BeNil())
   753  						g.Expect(pvc.Spec.Resources.Requests.Storage().String()).Should(Equal(repo.Spec.VolumeCapacity.String()))
   754  						g.Expect(pvc.Spec.AccessModes).Should(Equal([]corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}))
   755  						g.Expect(pvc.Spec.VolumeMode).ShouldNot(BeNil())
   756  						g.Expect(*pvc.Spec.VolumeMode).Should(BeEquivalentTo(corev1.PersistentVolumeFilesystem))
   757  						g.Expect(pvc.Labels["byPVCTemplate"]).Should(Equal("true"))
   758  					})).Should(Succeed())
   759  			})
   760  
   761  			It("should fail if the PVC template is invalid", func() {
   762  				By("setting a invalid PersistentVolumeClaimTemplate")
   763  				Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   764  					provider.Spec.PersistentVolumeClaimTemplate = `bad spec`
   765  				})).Should(Succeed())
   766  
   767  				By("checking repo's status")
   768  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   769  					g.Expect(repo.Status.Phase, dpv1alpha1.BackupRepoFailed)
   770  					cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypePVCTemplateChecked)
   771  					g.Expect(cond).NotTo(BeNil())
   772  					g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   773  					g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonBadPVCTemplate))
   774  				})).Should(Succeed())
   775  			})
   776  		})
   777  
   778  		Context("storage provider contains only PersistentVolumeClaimTemplate", func() {
   779  			BeforeEach(func() {
   780  				createStorageProviderSpec(func(provider *storagev1alpha1.StorageProvider) {
   781  					provider.Spec.CSIDriverName = ""
   782  					provider.Spec.CSIDriverSecretTemplate = ""
   783  					provider.Spec.StorageClassTemplate = ""
   784  					provider.Spec.PersistentVolumeClaimTemplate = `
   785  spec:
   786    storageClassName: some.storage.class
   787    accessModes:
   788      - ReadWriteOnce
   789  `
   790  				})
   791  				createBackupRepoSpec(nil)
   792  				completePreCheckJob(repo)
   793  			})
   794  			It("should create the PVC based on the PersistentVolumeClaimTemplate", func() {
   795  				_, pvcName := createBackupAndCheckPVC(namespace2)
   796  				Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: pvcName, Namespace: namespace2},
   797  					func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
   798  						g.Expect(pvc.Spec.StorageClassName).ShouldNot(BeNil())
   799  						g.Expect(*pvc.Spec.StorageClassName).Should(Equal("some.storage.class"))
   800  						g.Expect(pvc.Spec.AccessModes).Should(Equal([]corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}))
   801  						g.Expect(pvc.Spec.VolumeMode).ShouldNot(BeNil())
   802  						g.Expect(*pvc.Spec.VolumeMode).Should(BeEquivalentTo(corev1.PersistentVolumeFilesystem))
   803  						g.Expect(pvc.Spec.Resources.Requests.Storage()).ShouldNot(BeNil())
   804  						g.Expect(pvc.Spec.Resources.Requests.Storage().String()).Should(Equal(repo.Spec.VolumeCapacity.String()))
   805  					})).Should(Succeed())
   806  			})
   807  		})
   808  
   809  		It("should fail if both StorageClassTemplate and PersistentVolumeClaimTemplate are empty", func() {
   810  			By("creating a storage provider with empty PersistentVolumeClaimTemplate and StorageClassTemplate")
   811  			createStorageProviderSpec(func(provider *storagev1alpha1.StorageProvider) {
   812  				provider.Spec.CSIDriverName = ""
   813  				provider.Spec.CSIDriverSecretTemplate = ""
   814  				provider.Spec.StorageClassTemplate = ""
   815  				provider.Spec.PersistentVolumeClaimTemplate = ""
   816  			})
   817  			By("creating a backup repo with the storage provider")
   818  			createBackupRepoSpec(nil)
   819  			By("checking repo's status")
   820  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   821  				g.Expect(repo.Status.Phase).Should(BeEquivalentTo(dpv1alpha1.BackupRepoFailed))
   822  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   823  				g.Expect(cond).NotTo(BeNil())
   824  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   825  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonInvalidStorageProvider))
   826  			})).Should(Succeed())
   827  		})
   828  
   829  		Context("with AccessMethodTool", func() {
   830  			var backup *dpv1alpha1.Backup
   831  			var toolConfigSecretKey types.NamespacedName
   832  
   833  			createStorageProviderSpecForToolAccessMethod := func(mutateFunc func(provider *storagev1alpha1.StorageProvider)) {
   834  				createStorageProviderSpec(func(provider *storagev1alpha1.StorageProvider) {
   835  					provider.Spec.DatasafedConfigTemplate = `
   836  [storage]
   837  type=local
   838  key1={{ index .Parameters "key1" }}
   839  key2={{ index .Parameters "key2" }}
   840  cred-key1={{ index .Parameters "cred-key1" }}
   841  cred-key2={{ index .Parameters "cred-key2" }}
   842  `
   843  					if mutateFunc != nil {
   844  						mutateFunc(provider)
   845  					}
   846  				})
   847  			}
   848  
   849  			BeforeEach(func() {
   850  				By("preparing")
   851  				createStorageProviderSpecForToolAccessMethod(nil)
   852  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   853  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
   854  				})
   855  				completePreCheckJob(repo)
   856  
   857  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, obj *dpv1alpha1.BackupRepo) {
   858  					g.Expect(obj.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   859  					repo = obj
   860  				})).Should(Succeed())
   861  
   862  				backup = createBackupSpec(nil)
   863  				toolConfigSecretKey = types.NamespacedName{
   864  					Name:      repo.Status.ToolConfigSecretName,
   865  					Namespace: backup.Namespace,
   866  				}
   867  				Eventually(testapps.CheckObjExists(&testCtx, toolConfigSecretKey, &corev1.Secret{}, true)).Should(Succeed())
   868  			})
   869  
   870  			It("should check that the storage provider has a non-empty datasafedConfigTemplate", func() {
   871  				By("preparing")
   872  				createStorageProviderSpecForToolAccessMethod(func(provider *storagev1alpha1.StorageProvider) {
   873  					provider.Spec.DatasafedConfigTemplate = ""
   874  				})
   875  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   876  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
   877  				})
   878  				By("checking")
   879  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   880  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
   881  					cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeStorageProviderReady)
   882  					g.Expect(cond).NotTo(BeNil())
   883  					g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
   884  					g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonInvalidStorageProvider))
   885  					g.Expect(cond.Message).Should(ContainSubstring("DatasafedConfigTemplate is empty"))
   886  				})).Should(Succeed())
   887  			})
   888  
   889  			It("should fail if the datasafedConfigTemplate is invalid", func() {
   890  				By("preparing")
   891  				createStorageProviderSpecForToolAccessMethod(func(provider *storagev1alpha1.StorageProvider) {
   892  					provider.Spec.DatasafedConfigTemplate = "bad template {{"
   893  				})
   894  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   895  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
   896  				})
   897  				By("checking")
   898  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   899  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoPreChecking))
   900  					cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypePreCheckPassed)
   901  					g.Expect(cond).NotTo(BeNil())
   902  					g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionUnknown))
   903  					g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonUnknownError))
   904  					g.Expect(cond.Message).Should(ContainSubstring("failed to render tool config template"))
   905  				})).Should(Succeed())
   906  			})
   907  
   908  			It("should work even if the CSI driver required by the storage provider is not installed", func() {
   909  				By("preparing")
   910  				createStorageProviderSpecForToolAccessMethod(func(provider *storagev1alpha1.StorageProvider) {
   911  					provider.Status.Phase = storagev1alpha1.StorageProviderNotReady
   912  					meta.SetStatusCondition(&provider.Status.Conditions, metav1.Condition{
   913  						Type:   storagev1alpha1.ConditionTypeCSIDriverInstalled,
   914  						Status: metav1.ConditionFalse,
   915  						Reason: "NotInstalled",
   916  					})
   917  				})
   918  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   919  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
   920  				})
   921  				completePreCheckJob(repo)
   922  				By("checking")
   923  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   924  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   925  				})).Should(Succeed())
   926  			})
   927  
   928  			It("should create the secret containing the tool config", func() {
   929  				Eventually(testapps.CheckObj(&testCtx, toolConfigSecretKey, func(g Gomega, secret *corev1.Secret) {
   930  					g.Expect(secret.Data).Should(HaveKeyWithValue("datasafed.conf", []byte(`
   931  [storage]
   932  type=local
   933  key1=val1
   934  key2=val2
   935  cred-key1=cred-val1
   936  cred-key2=cred-val2
   937  `)))
   938  				})).Should(Succeed())
   939  
   940  				By("creating a backup in namespace2")
   941  				createBackupSpec(func(backup *dpv1alpha1.Backup) {
   942  					backup.Namespace = namespace2
   943  				})
   944  				secretKey := types.NamespacedName{
   945  					Name:      repo.Status.ToolConfigSecretName,
   946  					Namespace: namespace2,
   947  				}
   948  				Eventually(testapps.CheckObjExists(&testCtx, secretKey, &corev1.Secret{}, true)).Should(Succeed())
   949  			})
   950  
   951  			It("should update the content of the secret when the template or the value changes", func() {
   952  				By("changing the template")
   953  				Eventually(testapps.GetAndChangeObj(&testCtx, providerKey, func(provider *storagev1alpha1.StorageProvider) {
   954  					provider.Spec.DatasafedConfigTemplate += "new-item=new-value\n"
   955  				})).Should(Succeed())
   956  				completePreCheckJob(repo)
   957  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   958  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
   959  				})).Should(Succeed())
   960  				Eventually(testapps.CheckObj(&testCtx, toolConfigSecretKey, func(g Gomega, secret *corev1.Secret) {
   961  					g.Expect(secret.Data).Should(HaveKeyWithValue("datasafed.conf", []byte(`
   962  [storage]
   963  type=local
   964  key1=val1
   965  key2=val2
   966  cred-key1=cred-val1
   967  cred-key2=cred-val2
   968  new-item=new-value
   969  `)))
   970  				})).Should(Succeed())
   971  
   972  				By("changing the value")
   973  				Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
   974  					repo.Spec.Config["key1"] = "changed-val1"
   975  				})).Should(Succeed())
   976  				completePreCheckJob(repo)
   977  				Eventually(testapps.CheckObj(&testCtx, toolConfigSecretKey, func(g Gomega, secret *corev1.Secret) {
   978  					g.Expect(secret.Data).Should(HaveKeyWithValue("datasafed.conf", []byte(`
   979  [storage]
   980  type=local
   981  key1=changed-val1
   982  key2=val2
   983  cred-key1=cred-val1
   984  cred-key2=cred-val2
   985  new-item=new-value
   986  `)))
   987  				})).Should(Succeed())
   988  			})
   989  
   990  			It("should run a pre-check job", func() {
   991  				By("creating a backup repo")
   992  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
   993  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
   994  				})
   995  
   996  				By("checking the pre-check job resources")
   997  				secretName := preCheckResouceName(repo)
   998  				jobName := preCheckResouceName(repo)
   999  				namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
  1000  				checkResources := func(exists bool) {
  1001  					Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: jobName, Namespace: namespace},
  1002  						&batchv1.Job{}, exists)).WithOffset(1).Should(Succeed())
  1003  					Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: secretName, Namespace: namespace},
  1004  						&corev1.Secret{}, exists)).WithOffset(1).Should(Succeed())
  1005  				}
  1006  				checkResources(true)
  1007  
  1008  				By("checking repo's status, it should fail if the pre-check job has failed")
  1009  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1010  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoPreChecking))
  1011  				})).Should(Succeed())
  1012  				completePreCheckJobWithError(repo, "connect to endpoint failed")
  1013  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1014  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoFailed))
  1015  					cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypePreCheckPassed)
  1016  					g.Expect(cond).NotTo(BeNil())
  1017  					g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
  1018  					g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonPreCheckFailed))
  1019  					g.Expect(cond.Message).Should(ContainSubstring(`connect to endpoint failed`))
  1020  				})).Should(Succeed())
  1021  
  1022  				By("checking the resources, they should be deleted")
  1023  				checkResources(false)
  1024  
  1025  				By("updating the repo, it should run the pre-check job again")
  1026  				Eventually(testapps.GetAndChangeObj(&testCtx, credentialSecretKey, func(cred *corev1.Secret) {
  1027  					cred.Data["new-key"] = []byte("new-value")
  1028  				})).Should(Succeed())
  1029  				checkResources(true)
  1030  				completePreCheckJob(repo)
  1031  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1032  					g.Expect(repo.Status.Phase).Should(Equal(dpv1alpha1.BackupRepoReady))
  1033  				})).Should(Succeed())
  1034  			})
  1035  
  1036  			It("should remove the stale pre-check job if the repo spec has changed too quickly", func() {
  1037  				resourceName := preCheckResouceName(repo)
  1038  				namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
  1039  				updateRepoAndCheckResources := func(content string, lastJobUID types.UID) (string, types.UID) {
  1040  					Eventually(testapps.GetAndChangeObj(&testCtx, credentialSecretKey, func(cred *corev1.Secret) {
  1041  						cred.Data["new-key"] = []byte(content)
  1042  					})).WithOffset(1).Should(Succeed())
  1043  					var digest string
  1044  					var uid types.UID
  1045  					Eventually(func(g Gomega) {
  1046  						secret := &corev1.Secret{}
  1047  						err := testCtx.Cli.Get(testCtx.Ctx, client.ObjectKey{Name: resourceName, Namespace: namespace}, secret)
  1048  						g.Expect(err).ToNot(HaveOccurred())
  1049  						job := &batchv1.Job{}
  1050  						err = testCtx.Cli.Get(testCtx.Ctx, client.ObjectKey{Name: resourceName, Namespace: namespace}, job)
  1051  						g.Expect(err).ToNot(HaveOccurred())
  1052  						g.Expect(secret.Annotations[dataProtectionBackupRepoDigestAnnotationKey]).To(
  1053  							BeEquivalentTo(job.Annotations[dataProtectionBackupRepoDigestAnnotationKey]))
  1054  						digest = job.Annotations[dataProtectionBackupRepoDigestAnnotationKey]
  1055  						uid = job.UID
  1056  						g.Expect(digest).ToNot(BeEmpty())
  1057  						g.Expect(uid).ToNot(Equal(lastJobUID))
  1058  					}).WithOffset(1).Should(Succeed())
  1059  					return digest, uid
  1060  				}
  1061  
  1062  				By("updating the repo, and then get the digest from the pre-check resources")
  1063  				digest1, uid1 := updateRepoAndCheckResources("value1", "")
  1064  
  1065  				By("updating the repo again, and then get the digest")
  1066  				digest2, uid2 := updateRepoAndCheckResources("value2", uid1)
  1067  
  1068  				By("checking the digests are different")
  1069  				Expect(digest1).ToNot(Equal(digest2))
  1070  				Expect(uid1).ToNot(Equal(uid2))
  1071  				completePreCheckJob(repo)
  1072  				Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1073  					g.Expect(repo.Annotations[dataProtectionBackupRepoDigestAnnotationKey]).To(Equal(digest2))
  1074  				})).Should(Succeed())
  1075  			})
  1076  
  1077  			It("should delete resources for pre-checking when deleting the repo", func() {
  1078  				By("preparing")
  1079  				createBackupRepoSpec(func(repo *dpv1alpha1.BackupRepo) {
  1080  					repo.Spec.AccessMethod = dpv1alpha1.AccessMethodTool
  1081  				})
  1082  				resourceName := preCheckResouceName(repo)
  1083  				namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
  1084  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1085  					&batchv1.Job{}, true)).Should(Succeed())
  1086  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1087  					&corev1.Secret{}, true)).Should(Succeed())
  1088  
  1089  				By("deleting the repo")
  1090  				testapps.DeleteObject(&testCtx, repoKey, &dpv1alpha1.BackupRepo{})
  1091  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1092  					&batchv1.Job{}, false)).Should(Succeed())
  1093  				Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1094  					&corev1.Secret{}, false)).Should(Succeed())
  1095  			})
  1096  
  1097  			It("should delete the secret when the repo is deleted", func() {
  1098  				By("deleting the Backup and BackupRepo")
  1099  				testapps.DeleteObject(&testCtx, client.ObjectKeyFromObject(backup), &dpv1alpha1.Backup{})
  1100  				testapps.DeleteObject(&testCtx, repoKey, &dpv1alpha1.BackupRepo{})
  1101  				By("checking the secret is deleted")
  1102  				Eventually(testapps.CheckObjExists(&testCtx, toolConfigSecretKey, &corev1.Secret{}, false)).Should(Succeed())
  1103  			})
  1104  		})
  1105  
  1106  		It("should block the deletion of the BackupRepo if derived objects are not deleted", func() {
  1107  			backup, pvcName := createBackupAndCheckPVC(namespace2)
  1108  
  1109  			By("deleting the BackupRepo")
  1110  			testapps.DeleteObject(&testCtx, repoKey, &dpv1alpha1.BackupRepo{})
  1111  
  1112  			By("checking the BackupRepo, the deletion should be blocked because there are associated backups")
  1113  			Eventually(func(g Gomega) {
  1114  				repo := &dpv1alpha1.BackupRepo{}
  1115  				err := testCtx.Cli.Get(testCtx.Ctx, repoKey, repo)
  1116  				g.Expect(err).ShouldNot(HaveOccurred())
  1117  				g.Expect(repo.DeletionTimestamp).ShouldNot(BeNil())
  1118  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeDerivedObjectsDeleted)
  1119  				g.Expect(cond).NotTo(BeNil())
  1120  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
  1121  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonHaveAssociatedBackups))
  1122  			}).Should(Succeed())
  1123  
  1124  			By("deleting the Backup")
  1125  			Eventually(func(g Gomega) {
  1126  				deleteBackup(g, client.ObjectKeyFromObject(backup))
  1127  			}).Should(Succeed())
  1128  
  1129  			By("checking the BackupRepo, the deletion should be blocked because the PVC is still present")
  1130  			Eventually(func(g Gomega) {
  1131  				repo := &dpv1alpha1.BackupRepo{}
  1132  				err := testCtx.Cli.Get(testCtx.Ctx, repoKey, repo)
  1133  				g.Expect(err).ShouldNot(HaveOccurred())
  1134  				g.Expect(repo.DeletionTimestamp).ShouldNot(BeNil())
  1135  				cond := meta.FindStatusCondition(repo.Status.Conditions, ConditionTypeDerivedObjectsDeleted)
  1136  				g.Expect(cond).NotTo(BeNil())
  1137  				g.Expect(cond.Status).Should(BeEquivalentTo(corev1.ConditionFalse))
  1138  				g.Expect(cond.Reason).Should(BeEquivalentTo(ReasonHaveResidualPVCs))
  1139  			}).Should(Succeed())
  1140  
  1141  			By("releasing the PVC for pre-checking")
  1142  			pvcKey := types.NamespacedName{
  1143  				Name:      (&reconcileContext{repo: repo}).preCheckResourceName(),
  1144  				Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS),
  1145  			}
  1146  			removePVCProtectionFinalizer(pvcKey)
  1147  
  1148  			By("releasing the PVC")
  1149  			pvcKey = types.NamespacedName{
  1150  				Namespace: namespace2,
  1151  				Name:      pvcName,
  1152  			}
  1153  			removePVCProtectionFinalizer(pvcKey)
  1154  
  1155  			By("checking the BackupRepo, it should have been deleted")
  1156  			Eventually(func(g Gomega) {
  1157  				repo := &dpv1alpha1.BackupRepo{}
  1158  				err := testCtx.Cli.Get(testCtx.Ctx, repoKey, repo)
  1159  				g.Expect(apierrors.IsNotFound(err)).Should(BeTrue())
  1160  			}).Should(Succeed())
  1161  
  1162  			By("checking derived objects should be all deleted")
  1163  			Eventually(func(g Gomega) {
  1164  				// get the newest repo object
  1165  				repo := &dpv1alpha1.BackupRepo{}
  1166  				err := testCtx.Cli.Get(testCtx.Ctx, repoKey, repo)
  1167  				if apierrors.IsNotFound(err) {
  1168  					return
  1169  				}
  1170  				g.Expect(err).ShouldNot(HaveOccurred())
  1171  
  1172  				// check the secret for the CSI driver
  1173  				err = testCtx.Cli.Get(testCtx.Ctx, types.NamespacedName{
  1174  					Name:      repo.Status.GeneratedCSIDriverSecret.Name,
  1175  					Namespace: repo.Status.GeneratedCSIDriverSecret.Namespace,
  1176  				}, &corev1.Secret{})
  1177  				g.Expect(apierrors.IsNotFound(err)).Should(BeTrue())
  1178  
  1179  				// check the storage class
  1180  				err = testCtx.Cli.Get(testCtx.Ctx, types.NamespacedName{
  1181  					Name: repo.Status.GeneratedStorageClassName,
  1182  				}, &storagev1.StorageClass{})
  1183  				g.Expect(apierrors.IsNotFound(err)).Should(BeTrue())
  1184  
  1185  				// check the PVC
  1186  				pvc := &corev1.PersistentVolumeClaim{}
  1187  				err = testCtx.Cli.Get(testCtx.Ctx, pvcKey, pvc)
  1188  				g.Expect(apierrors.IsNotFound(err)).Should(BeTrue())
  1189  			}).Should(Succeed())
  1190  		})
  1191  
  1192  		It("should delete resources for pre-checking when deleting the repo", func() {
  1193  			By("preparing")
  1194  			createBackupRepoSpec(nil)
  1195  			resourceName := preCheckResouceName(repo)
  1196  			namespace := viper.GetString(constant.CfgKeyCtrlrMgrNS)
  1197  			Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1198  				&batchv1.Job{}, true)).Should(Succeed())
  1199  			Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1200  				&corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
  1201  			removePVCProtectionFinalizer(types.NamespacedName{Name: resourceName, Namespace: namespace})
  1202  
  1203  			By("deleting the repo")
  1204  			testapps.DeleteObject(&testCtx, repoKey, &dpv1alpha1.BackupRepo{})
  1205  			Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1206  				&batchv1.Job{}, false)).Should(Succeed())
  1207  			Eventually(testapps.CheckObjExists(&testCtx, types.NamespacedName{Name: resourceName, Namespace: namespace},
  1208  				&corev1.PersistentVolumeClaim{}, false)).Should(Succeed())
  1209  		})
  1210  
  1211  		It("should update backupRepo.status.isDefault", func() {
  1212  			By("making the repo default")
  1213  			Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
  1214  				repo.Annotations = map[string]string{
  1215  					dptypes.DefaultBackupRepoAnnotationKey: trueVal,
  1216  				}
  1217  			})).Should(Succeed())
  1218  			By("checking the repo is default")
  1219  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1220  				g.Expect(repo.Status.IsDefault).Should(BeTrue())
  1221  			})).Should(Succeed())
  1222  
  1223  			By("making the repo non default")
  1224  			Eventually(testapps.GetAndChangeObj(&testCtx, repoKey, func(repo *dpv1alpha1.BackupRepo) {
  1225  				repo.Annotations = nil
  1226  			})).Should(Succeed())
  1227  			By("checking the repo is not default")
  1228  			Eventually(testapps.CheckObj(&testCtx, repoKey, func(g Gomega, repo *dpv1alpha1.BackupRepo) {
  1229  				g.Expect(repo.Status.IsDefault).Should(BeFalse())
  1230  			})).Should(Succeed())
  1231  		})
  1232  	})
  1233  })