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 })