github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/backup_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  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  
    26  	"time"
    27  
    28  	vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
    29  	batchv1 "k8s.io/api/batch/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    36  	storagev1alpha1 "github.com/1aal/kubeblocks/apis/storage/v1alpha1"
    37  	"github.com/1aal/kubeblocks/pkg/constant"
    38  	dpbackup "github.com/1aal/kubeblocks/pkg/dataprotection/backup"
    39  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    40  	dputils "github.com/1aal/kubeblocks/pkg/dataprotection/utils"
    41  	"github.com/1aal/kubeblocks/pkg/generics"
    42  	"github.com/1aal/kubeblocks/pkg/testutil"
    43  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    44  	testdp "github.com/1aal/kubeblocks/pkg/testutil/dataprotection"
    45  	testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    46  )
    47  
    48  var _ = Describe("Backup Controller test", func() {
    49  	cleanEnv := func() {
    50  		// must wait till resources deleted and no longer existed before the testcases start,
    51  		// otherwise if later it needs to create some new resource objects with the same name,
    52  		// in race conditions, it will find the existence of old objects, resulting failure to
    53  		// create the new objects.
    54  		By("clean resources")
    55  
    56  		// delete rest mocked objects
    57  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    58  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    59  
    60  		// namespaced
    61  		testapps.ClearResources(&testCtx, generics.ClusterSignature, inNS, ml)
    62  		testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml)
    63  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS)
    64  
    65  		// wait all backup to be deleted, otherwise the controller maybe create
    66  		// job to delete the backup between the ClearResources function delete
    67  		// the job and get the job list, resulting the ClearResources panic.
    68  		Eventually(testapps.List(&testCtx, generics.BackupSignature, inNS)).Should(HaveLen(0))
    69  		testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml)
    70  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS)
    71  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS)
    72  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS)
    73  
    74  		// non-namespaced
    75  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ActionSetSignature, true, ml)
    76  		testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
    77  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeSignature, true, ml)
    78  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupRepoSignature, true, ml)
    79  		testapps.ClearResources(&testCtx, generics.StorageProviderSignature, ml)
    80  		testapps.ClearResources(&testCtx, generics.VolumeSnapshotClassSignature, ml)
    81  	}
    82  
    83  	var clusterInfo *testdp.BackupClusterInfo
    84  
    85  	BeforeEach(func() {
    86  		cleanEnv()
    87  		clusterInfo = testdp.NewFakeCluster(&testCtx)
    88  	})
    89  
    90  	AfterEach(func() {
    91  		cleanEnv()
    92  	})
    93  
    94  	When("with default settings", func() {
    95  		var (
    96  			backupPolicy *dpv1alpha1.BackupPolicy
    97  			repoPVCName  string
    98  			cluster      *appsv1alpha1.Cluster
    99  			pvcName      string
   100  			targetPod    *corev1.Pod
   101  		)
   102  
   103  		BeforeEach(func() {
   104  			By("creating an actionSet")
   105  			actionSet := testdp.NewFakeActionSet(&testCtx)
   106  
   107  			By("creating storage provider")
   108  			_ = testdp.NewFakeStorageProvider(&testCtx, nil)
   109  
   110  			By("creating backup repo")
   111  			_, repoPVCName = testdp.NewFakeBackupRepo(&testCtx, nil)
   112  
   113  			By("creating a backupPolicy from actionSet: " + actionSet.Name)
   114  			backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, nil)
   115  
   116  			cluster = clusterInfo.Cluster
   117  			pvcName = clusterInfo.TargetPVC
   118  			targetPod = clusterInfo.TargetPod
   119  		})
   120  
   121  		Context("creates a backup", func() {
   122  			var (
   123  				backupKey types.NamespacedName
   124  				backup    *dpv1alpha1.Backup
   125  			)
   126  
   127  			getJobKey := func() client.ObjectKey {
   128  				return client.ObjectKey{
   129  					Name:      dpbackup.GenerateBackupJobName(backup, dpbackup.BackupDataJobNamePrefix),
   130  					Namespace: backup.Namespace,
   131  				}
   132  			}
   133  
   134  			BeforeEach(func() {
   135  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   136  				backup = testdp.NewFakeBackup(&testCtx, nil)
   137  				backupKey = client.ObjectKeyFromObject(backup)
   138  			})
   139  
   140  			It("should succeed after job completes", func() {
   141  				By("check backup status")
   142  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   143  					g.Expect(fetched.Status.PersistentVolumeClaimName).Should(Equal(repoPVCName))
   144  					g.Expect(fetched.Status.Path).Should(Equal(dpbackup.BuildBackupPath(fetched, backupPolicy.Spec.PathPrefix)))
   145  					g.Expect(fetched.Status.Phase).Should(Equal(dpv1alpha1.BackupPhaseRunning))
   146  					g.Expect(fetched.Annotations[dptypes.ConnectionPasswordKey]).ShouldNot(BeEmpty())
   147  				})).Should(Succeed())
   148  
   149  				By("check backup job's nodeName equals pod's nodeName")
   150  				Eventually(testapps.CheckObj(&testCtx, getJobKey(), func(g Gomega, fetched *batchv1.Job) {
   151  					g.Expect(fetched.Spec.Template.Spec.NodeSelector[corev1.LabelHostname]).To(Equal(targetPod.Spec.NodeName))
   152  					// image should be expanded by env
   153  					g.Expect(fetched.Spec.Template.Spec.Containers[0].Image).Should(ContainSubstring(testdp.ImageTag))
   154  				})).Should(Succeed())
   155  
   156  				testdp.PatchK8sJobStatus(&testCtx, getJobKey(), batchv1.JobComplete)
   157  
   158  				By("backup job should have completed")
   159  				Eventually(testapps.CheckObj(&testCtx, getJobKey(), func(g Gomega, fetched *batchv1.Job) {
   160  					_, finishedType, _ := dputils.IsJobFinished(fetched)
   161  					g.Expect(finishedType).To(Equal(batchv1.JobComplete))
   162  				})).Should(Succeed())
   163  
   164  				By("backup should have completed")
   165  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   166  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseCompleted))
   167  					g.Expect(fetched.Labels[dptypes.ClusterUIDLabelKey]).Should(Equal(string(cluster.UID)))
   168  					g.Expect(fetched.Labels[constant.AppInstanceLabelKey]).Should(Equal(testdp.ClusterName))
   169  					g.Expect(fetched.Labels[constant.KBAppComponentLabelKey]).Should(Equal(testdp.ComponentName))
   170  					g.Expect(fetched.Annotations[constant.ClusterSnapshotAnnotationKey]).ShouldNot(BeEmpty())
   171  				})).Should(Succeed())
   172  
   173  				By("backup job should be deleted after backup completed")
   174  				Eventually(testapps.CheckObjExists(&testCtx, getJobKey(), &batchv1.Job{}, false)).Should(Succeed())
   175  			})
   176  
   177  			It("should fail after job fails", func() {
   178  				testdp.PatchK8sJobStatus(&testCtx, getJobKey(), batchv1.JobFailed)
   179  
   180  				By("check backup job failed")
   181  				Eventually(testapps.CheckObj(&testCtx, getJobKey(), func(g Gomega, fetched *batchv1.Job) {
   182  					_, finishedType, _ := dputils.IsJobFinished(fetched)
   183  					g.Expect(finishedType).To(Equal(batchv1.JobFailed))
   184  				})).Should(Succeed())
   185  
   186  				By("check backup failed")
   187  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   188  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   189  				})).Should(Succeed())
   190  			})
   191  		})
   192  
   193  		Context("create an invalid backup", func() {
   194  			It("should fail if backupPolicy is not found", func() {
   195  				By("creating a backup using a not found backupPolicy")
   196  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   197  					backup.Spec.BackupPolicyName = "not-found"
   198  				})
   199  				backupKey := client.ObjectKeyFromObject(backup)
   200  
   201  				By("check backup failed and its expiration when retentionPeriod is not set")
   202  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   203  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   204  					g.Expect(fetched.Status.Expiration).Should(BeNil())
   205  				})).Should(Succeed())
   206  			})
   207  		})
   208  
   209  		Context("creates a backup with retentionPeriod", func() {
   210  			It("create a valid backup", func() {
   211  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   212  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   213  					backup.Spec.RetentionPeriod = "1h"
   214  				})
   215  				backupKey := client.ObjectKeyFromObject(backup)
   216  
   217  				getJobKey := func() client.ObjectKey {
   218  					return client.ObjectKey{
   219  						Name:      dpbackup.GenerateBackupJobName(backup, dpbackup.BackupDataJobNamePrefix),
   220  						Namespace: backup.Namespace,
   221  					}
   222  				}
   223  
   224  				By("check backup expiration is set by start time when backup is running")
   225  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   226  					g.Expect(fetched.Status.Phase).Should(Equal(dpv1alpha1.BackupPhaseRunning))
   227  					g.Expect(fetched.Status.Expiration.Second()).Should(Equal(fetched.Status.StartTimestamp.Add(time.Hour).Second()))
   228  				})).Should(Succeed())
   229  
   230  				testdp.PatchK8sJobStatus(&testCtx, getJobKey(), batchv1.JobComplete)
   231  
   232  				By("check backup expiration is updated by completion time when backup is completed")
   233  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   234  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseCompleted))
   235  					g.Expect(fetched.Status.CompletionTimestamp).ShouldNot(BeNil())
   236  					g.Expect(fetched.Status.Expiration.Second()).Should(Equal(fetched.Status.CompletionTimestamp.Add(time.Hour).Second()))
   237  				})).Should(Succeed())
   238  			})
   239  
   240  			It("create an invalid backup", func() {
   241  				By("creating a backup using a not found backupPolicy")
   242  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   243  					backup.Spec.BackupPolicyName = "not-found"
   244  					backup.Spec.RetentionPeriod = "1h"
   245  				})
   246  				backupKey := client.ObjectKeyFromObject(backup)
   247  
   248  				By("check backup failed and its expiration is set")
   249  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   250  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   251  					g.Expect(fetched.Status.Expiration).ShouldNot(BeNil())
   252  				})).Should(Succeed())
   253  			})
   254  		})
   255  
   256  		Context("deletes a backup", func() {
   257  			var (
   258  				backupKey types.NamespacedName
   259  				backup    *dpv1alpha1.Backup
   260  			)
   261  			BeforeEach(func() {
   262  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   263  				backup = testdp.NewFakeBackup(&testCtx, nil)
   264  				backupKey = client.ObjectKeyFromObject(backup)
   265  
   266  				By("waiting for backup status to be running")
   267  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   268  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseRunning))
   269  				})).Should(Succeed())
   270  			})
   271  
   272  			It("should create a Job for deleting backup files", func() {
   273  				By("deleting a backup object")
   274  				testapps.DeleteObject(&testCtx, backupKey, &dpv1alpha1.Backup{})
   275  
   276  				By("checking new created Job")
   277  				jobKey := dpbackup.BuildDeleteBackupFilesJobKey(backup)
   278  				job := &batchv1.Job{}
   279  				Eventually(testapps.CheckObjExists(&testCtx, jobKey, job, true)).Should(Succeed())
   280  				volumeName := "dp-backup-data"
   281  				Eventually(testapps.CheckObj(&testCtx, jobKey, func(g Gomega, job *batchv1.Job) {
   282  					Expect(job.Spec.Template.Spec.Volumes).
   283  						Should(ContainElement(corev1.Volume{
   284  							Name: volumeName,
   285  							VolumeSource: corev1.VolumeSource{
   286  								PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   287  									ClaimName: repoPVCName,
   288  								},
   289  							},
   290  						}))
   291  					Expect(job.Spec.Template.Spec.Containers[0].VolumeMounts).
   292  						Should(ContainElement(corev1.VolumeMount{
   293  							Name:      volumeName,
   294  							MountPath: dpbackup.RepoVolumeMountPath,
   295  						}))
   296  				})).Should(Succeed())
   297  
   298  				By("checking backup object, it should not be deleted")
   299  				Eventually(testapps.CheckObjExists(&testCtx, backupKey,
   300  					&dpv1alpha1.Backup{}, true)).Should(Succeed())
   301  
   302  				By("mock job for deletion to failed, backup should not be deleted")
   303  				testdp.ReplaceK8sJobStatus(&testCtx, jobKey, batchv1.JobFailed)
   304  				Eventually(testapps.CheckObjExists(&testCtx, backupKey,
   305  					&dpv1alpha1.Backup{}, true)).Should(Succeed())
   306  
   307  				By("mock job for deletion to completed, backup should be deleted")
   308  				testdp.ReplaceK8sJobStatus(&testCtx, jobKey, batchv1.JobComplete)
   309  
   310  				By("check deletion backup file job completed")
   311  				Eventually(testapps.CheckObj(&testCtx, jobKey, func(g Gomega, fetched *batchv1.Job) {
   312  					_, finishedType, _ := dputils.IsJobFinished(fetched)
   313  					g.Expect(finishedType).To(Equal(batchv1.JobComplete))
   314  				})).Should(Succeed())
   315  
   316  				By("check backup deleted")
   317  				Eventually(testapps.CheckObjExists(&testCtx, backupKey,
   318  					&dpv1alpha1.Backup{}, false)).Should(Succeed())
   319  
   320  				// TODO: add delete backup test case with the pvc not exists
   321  			})
   322  		})
   323  
   324  		Context("creates a snapshot backup", func() {
   325  			var (
   326  				backupKey types.NamespacedName
   327  				backup    *dpv1alpha1.Backup
   328  				vsKey     client.ObjectKey
   329  			)
   330  
   331  			BeforeEach(func() {
   332  				// mock VolumeSnapshotClass for volume snapshot
   333  				testk8s.CreateVolumeSnapshotClass(&testCtx, testutil.DefaultCSIDriver)
   334  
   335  				By("create a backup from backupPolicy " + testdp.BackupPolicyName)
   336  				backup = testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   337  					backup.Spec.BackupMethod = testdp.VSBackupMethodName
   338  				})
   339  				backupKey = client.ObjectKeyFromObject(backup)
   340  				vsKey = client.ObjectKey{
   341  					Name:      dputils.GetBackupVolumeSnapshotName(backup.Name, "data"),
   342  					Namespace: backup.Namespace,
   343  				}
   344  			})
   345  
   346  			It("should success after all volume snapshot ready", func() {
   347  				By("patching volumesnapshot status to ready")
   348  				testdp.PatchVolumeSnapshotStatus(&testCtx, vsKey, true)
   349  
   350  				By("checking volume snapshot source is equal to pvc")
   351  				Eventually(testapps.CheckObj(&testCtx, vsKey, func(g Gomega, fetched *vsv1.VolumeSnapshot) {
   352  					g.Expect(*fetched.Spec.Source.PersistentVolumeClaimName).To(Equal(pvcName))
   353  				})).Should(Succeed())
   354  			})
   355  
   356  			It("should fail if volumesnapshot reports error", func() {
   357  				By("patching volumesnapshot status with error")
   358  				Eventually(testapps.GetAndChangeObjStatus(&testCtx, vsKey, func(tmpVS *vsv1.VolumeSnapshot) {
   359  					msg := "Failed to set default snapshot class with error: some error"
   360  					vsError := vsv1.VolumeSnapshotError{
   361  						Message: &msg,
   362  					}
   363  					snapStatus := vsv1.VolumeSnapshotStatus{Error: &vsError}
   364  					tmpVS.Status = &snapStatus
   365  				})).Should(Succeed())
   366  
   367  				By("checking backup failed")
   368  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   369  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   370  				})).Should(Succeed())
   371  			})
   372  		})
   373  
   374  		Context("creates a snapshot backup on error", func() {
   375  			var backupKey types.NamespacedName
   376  
   377  			BeforeEach(func() {
   378  				By("By remove persistent pvc")
   379  				// delete rest mocked objects
   380  				inNS := client.InNamespace(testCtx.DefaultNamespace)
   381  				ml := client.HasLabels{testCtx.TestObjLabelKey}
   382  				testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx,
   383  					generics.PersistentVolumeClaimSignature, true, inNS, ml)
   384  			})
   385  
   386  			It("should fail when disable volumesnapshot", func() {
   387  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   388  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   389  					backup.Spec.BackupMethod = testdp.VSBackupMethodName
   390  				})
   391  				backupKey = client.ObjectKeyFromObject(backup)
   392  
   393  				By("check backup failed")
   394  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   395  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   396  				})).Should(Succeed())
   397  			})
   398  
   399  			It("should fail without pvc", func() {
   400  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   401  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   402  					backup.Spec.BackupMethod = testdp.VSBackupMethodName
   403  				})
   404  				backupKey = client.ObjectKeyFromObject(backup)
   405  
   406  				By("check backup failed")
   407  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   408  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   409  				})).Should(Succeed())
   410  			})
   411  		})
   412  	})
   413  
   414  	When("with exceptional settings", func() {
   415  		var (
   416  			backupPolicy *dpv1alpha1.BackupPolicy
   417  		)
   418  
   419  		Context("creates a backup with non-existent backup policy", func() {
   420  			var backupKey types.NamespacedName
   421  			BeforeEach(func() {
   422  				By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
   423  				backup := testdp.NewFakeBackup(&testCtx, nil)
   424  				backupKey = client.ObjectKeyFromObject(backup)
   425  			})
   426  			It("should fail", func() {
   427  				By("check backup status failed")
   428  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   429  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   430  				})).Should(Succeed())
   431  			})
   432  		})
   433  
   434  		Context("creates a backup using non-existent backup method", func() {
   435  			BeforeEach(func() {
   436  				By("creating a backupPolicy without backup method")
   437  				backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, nil)
   438  			})
   439  
   440  			It("should fail because of no-existent backup method", func() {
   441  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   442  					backup.Spec.BackupPolicyName = backupPolicy.Name
   443  					backup.Spec.BackupMethod = "non-existent"
   444  				})
   445  				backupKey := client.ObjectKeyFromObject(backup)
   446  
   447  				By("check backup status failed")
   448  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   449  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   450  				})).Should(Succeed())
   451  			})
   452  		})
   453  
   454  		Context("creates a backup with invalid backup method", func() {
   455  			BeforeEach(func() {
   456  				backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   457  					backupPolicy.Spec.BackupMethods = append(backupPolicy.Spec.BackupMethods, dpv1alpha1.BackupMethod{
   458  						Name:          "invalid",
   459  						ActionSetName: "",
   460  					})
   461  				})
   462  			})
   463  
   464  			It("should fail because backup method doesn't specify snapshotVolumes with empty actionSet", func() {
   465  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   466  					backup.Spec.BackupPolicyName = backupPolicy.Name
   467  					backup.Spec.BackupMethod = "invalid"
   468  				})
   469  				backupKey := client.ObjectKeyFromObject(backup)
   470  
   471  				By("check backup status failed")
   472  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   473  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   474  				})).Should(Succeed())
   475  			})
   476  
   477  			It("should fail because of no-existing actionSet", func() {
   478  				backup := testdp.NewFakeBackup(&testCtx, nil)
   479  				backupKey := client.ObjectKeyFromObject(backup)
   480  
   481  				By("check backup status failed")
   482  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   483  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   484  				})).Should(Succeed())
   485  			})
   486  
   487  			It("should fail because actionSet's backup type isn't Full", func() {
   488  				actionSet := testdp.NewFakeActionSet(&testCtx)
   489  				actionSetKey := client.ObjectKeyFromObject(actionSet)
   490  				Eventually(testapps.GetAndChangeObj(&testCtx, actionSetKey, func(fetched *dpv1alpha1.ActionSet) {
   491  					fetched.Spec.BackupType = dpv1alpha1.BackupTypeIncremental
   492  				}))
   493  
   494  				backup := testdp.NewFakeBackup(&testCtx, nil)
   495  				backupKey := client.ObjectKeyFromObject(backup)
   496  
   497  				By("check backup status failed")
   498  				Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
   499  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
   500  				})).Should(Succeed())
   501  			})
   502  		})
   503  	})
   504  
   505  	When("with backup repo", func() {
   506  		var (
   507  			repoPVCName string
   508  			sp          *storagev1alpha1.StorageProvider
   509  			repo        *dpv1alpha1.BackupRepo
   510  		)
   511  
   512  		BeforeEach(func() {
   513  			By("creating backup repo")
   514  			sp = testdp.NewFakeStorageProvider(&testCtx, nil)
   515  			repo, repoPVCName = testdp.NewFakeBackupRepo(&testCtx, nil)
   516  
   517  			By("creating actionSet")
   518  			_ = testdp.NewFakeActionSet(&testCtx)
   519  		})
   520  
   521  		Context("explicitly specify backup repo", func() {
   522  			It("should use the backup repo specified in the policy", func() {
   523  				By("creating backup policy and backup")
   524  				_ = testdp.NewFakeBackupPolicy(&testCtx, nil)
   525  				backup := testdp.NewFakeBackup(&testCtx, nil)
   526  				By("checking backup, it should use the PVC from the backup repo")
   527  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   528  					g.Expect(backup.Status.PersistentVolumeClaimName).Should(BeEquivalentTo(repoPVCName))
   529  				})).Should(Succeed())
   530  			})
   531  
   532  			It("should use the backup repo specified in the backup object", func() {
   533  				By("creating a second backup repo")
   534  				repo2, repoPVCName2 := testdp.NewFakeBackupRepo(&testCtx, func(repo *dpv1alpha1.BackupRepo) {
   535  					repo.Name += "2"
   536  				})
   537  				By("creating backup policy and backup")
   538  				_ = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   539  					backupPolicy.Spec.BackupRepoName = &repo.Name
   540  				})
   541  				backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   542  					if backup.Labels == nil {
   543  						backup.Labels = map[string]string{}
   544  					}
   545  					backup.Labels[dataProtectionBackupRepoKey] = repo2.Name
   546  				})
   547  				By("checking backup, it should use the PVC from repo2")
   548  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   549  					g.Expect(backup.Status.PersistentVolumeClaimName).Should(BeEquivalentTo(repoPVCName2))
   550  				})).Should(Succeed())
   551  			})
   552  		})
   553  
   554  		Context("default backup repo", func() {
   555  			It("should use the default backup repo if it's not specified", func() {
   556  				By("creating backup policy and backup")
   557  				_ = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   558  					backupPolicy.Spec.BackupRepoName = nil
   559  				})
   560  				backup := testdp.NewFakeBackup(&testCtx, nil)
   561  				By("checking backup, it should use the PVC from the backup repo")
   562  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   563  					g.Expect(backup.Status.PersistentVolumeClaimName).Should(BeEquivalentTo(repoPVCName))
   564  				})).Should(Succeed())
   565  			})
   566  
   567  			It("should associate the default backup repo with the backup object", func() {
   568  				By("creating backup policy and backup")
   569  				_ = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   570  					backupPolicy.Spec.BackupRepoName = nil
   571  				})
   572  				backup := testdp.NewFakeBackup(&testCtx, nil)
   573  				By("checking backup labels")
   574  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   575  					g.Expect(backup.Labels[dataProtectionBackupRepoKey]).Should(BeEquivalentTo(repo.Name))
   576  				})).Should(Succeed())
   577  
   578  				By("creating backup2")
   579  				backup2 := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   580  					backup.Name += "2"
   581  				})
   582  				By("checking backup2 labels")
   583  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup2), func(g Gomega, backup *dpv1alpha1.Backup) {
   584  					g.Expect(backup.Status.PersistentVolumeClaimName).Should(BeEquivalentTo(repoPVCName))
   585  					g.Expect(backup.Labels[dataProtectionBackupRepoKey]).Should(BeEquivalentTo(repo.Name))
   586  				})).Should(Succeed())
   587  			})
   588  
   589  			Context("multiple default backup repos", func() {
   590  				var repoPVCName2 string
   591  				BeforeEach(func() {
   592  					By("creating a second backup repo")
   593  					sp2 := testdp.NewFakeStorageProvider(&testCtx, func(sp *storagev1alpha1.StorageProvider) {
   594  						sp.Name += "2"
   595  					})
   596  					_, repoPVCName2 = testdp.NewFakeBackupRepo(&testCtx, func(repo *dpv1alpha1.BackupRepo) {
   597  						repo.Name += "2"
   598  						repo.Spec.StorageProviderRef = sp2.Name
   599  					})
   600  					By("creating backup policy")
   601  					_ = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   602  						// set backupRepoName in backupPolicy to nil to make it use the default backup repo
   603  						backupPolicy.Spec.BackupRepoName = nil
   604  					})
   605  				})
   606  
   607  				It("should fail if there are multiple default backup repos", func() {
   608  					By("creating backup")
   609  					backup := testdp.NewFakeBackup(&testCtx, nil)
   610  					By("checking backup, it should fail because there are multiple default backup repos")
   611  					Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   612  						g.Expect(backup.Status.Phase).Should(BeEquivalentTo(dpv1alpha1.BackupPhaseFailed))
   613  						g.Expect(backup.Status.FailureReason).Should(ContainSubstring("multiple default BackupRepo found"))
   614  					})).Should(Succeed())
   615  				})
   616  
   617  				It("should only repos in ready status can be selected as the default backup repo", func() {
   618  					By("making repo to failed status")
   619  					Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(sp),
   620  						func(fetched *storagev1alpha1.StorageProvider) {
   621  							fetched.Status.Phase = storagev1alpha1.StorageProviderNotReady
   622  							fetched.Status.Conditions = nil
   623  						})).ShouldNot(HaveOccurred())
   624  					Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(repo),
   625  						func(g Gomega, repo *dpv1alpha1.BackupRepo) {
   626  							g.Expect(repo.Status.Phase).Should(BeEquivalentTo(dpv1alpha1.BackupRepoFailed))
   627  						})).Should(Succeed())
   628  					By("creating backup")
   629  					backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
   630  						backup.Name = "second-backup"
   631  					})
   632  					By("checking backup, it should use the PVC from repo2")
   633  					Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   634  						g.Expect(backup.Status.PersistentVolumeClaimName).Should(BeEquivalentTo(repoPVCName2))
   635  					})).Should(Succeed())
   636  				})
   637  			})
   638  		})
   639  
   640  		Context("no backup repo available", func() {
   641  			It("should throw error", func() {
   642  				By("making the backup repo as non-default")
   643  				Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(repo), func(repo *dpv1alpha1.BackupRepo) {
   644  					delete(repo.Annotations, dptypes.DefaultBackupRepoAnnotationKey)
   645  				})).Should(Succeed())
   646  				By("creating backup")
   647  				_ = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
   648  					backupPolicy.Spec.BackupRepoName = nil
   649  				})
   650  				backup := testdp.NewFakeBackup(&testCtx, nil)
   651  				By("checking backup, it should fail because the backup repo are not available")
   652  				Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(backup), func(g Gomega, backup *dpv1alpha1.Backup) {
   653  					g.Expect(backup.Status.Phase).Should(BeEquivalentTo(dpv1alpha1.BackupPhaseFailed))
   654  					g.Expect(backup.Status.FailureReason).Should(ContainSubstring("no default BackupRepo found"))
   655  				})).Should(Succeed())
   656  			})
   657  		})
   658  	})
   659  })