diff --git a/controllers/dataprotection/backup_controller_test.go b/controllers/dataprotection/backup_controller_test.go
index da5b0fb2315..de4280d0d30 100644
--- a/controllers/dataprotection/backup_controller_test.go
+++ b/controllers/dataprotection/backup_controller_test.go
@@ -23,6 +23,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "time"
+
vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
@@ -194,11 +196,58 @@ var _ = Describe("Backup Controller test", func() {
})
backupKey := client.ObjectKeyFromObject(backup)
+ By("check backup failed and its expiration when retentionPeriod is not set")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
+ g.Expect(fetched.Status.Expiration).Should(BeNil())
+ })).Should(Succeed())
+ })
+ })
+
+ Context("creates a backup with retentionPeriod", func() {
+ It("create an valid backup", func() {
+ By("creating a backup from backupPolicy " + testdp.BackupPolicyName)
+ backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
+ backup.Spec.RetentionPeriod = "1h"
+ })
+ backupKey := client.ObjectKeyFromObject(backup)
+
+ getJobKey := func() client.ObjectKey {
+ return client.ObjectKey{
+ Name: dpbackup.GenerateBackupJobName(backup, dpbackup.BackupDataJobNamePrefix),
+ Namespace: backup.Namespace,
+ }
+ }
+
+ By("check backup expiration is set by start time when backup is running")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).Should(Equal(dpv1alpha1.BackupPhaseRunning))
+ g.Expect(fetched.Status.Expiration.Second()).Should(Equal(fetched.Status.StartTimestamp.Add(time.Hour).Second()))
+ })).Should(Succeed())
+
+ testdp.PatchK8sJobStatus(&testCtx, getJobKey(), batchv1.JobComplete)
+
+ By("check backup expiration is update by completion time when backup is completed")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseCompleted))
+ g.Expect(fetched.Status.CompletionTimestamp).ShouldNot(BeNil())
+ g.Expect(fetched.Status.Expiration.Second()).Should(Equal(fetched.Status.CompletionTimestamp.Add(time.Hour).Second()))
+ })).Should(Succeed())
+ })
+
+ It("create an invalid backup", func() {
+ By("creating a backup using a not found backupPolicy")
+ backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
+ backup.Spec.BackupPolicyName = "not-found"
+ backup.Spec.RetentionPeriod = "1h"
+ })
+ backupKey := client.ObjectKeyFromObject(backup)
+
By("check backup failed and its expiration is set")
Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
g.Expect(fetched.Status.Expiration).ShouldNot(BeNil())
- }))
+ })).Should(Succeed())
})
})
@@ -361,6 +410,10 @@ var _ = Describe("Backup Controller test", func() {
})
When("with exceptional settings", func() {
+ var (
+ backupPolicy *dpv1alpha1.BackupPolicy
+ )
+
Context("creates a backup with non-existent backup policy", func() {
var backupKey types.NamespacedName
BeforeEach(func() {
@@ -375,6 +428,76 @@ var _ = Describe("Backup Controller test", func() {
})).Should(Succeed())
})
})
+
+ Context("creates a backup using non-existent backup method", func() {
+ BeforeEach(func() {
+ By("creating a backupPolicy without backup method")
+ backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, nil)
+ })
+
+ It("should fail because of no-existent backup method", func() {
+ backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
+ backup.Spec.BackupPolicyName = backupPolicy.Name
+ backup.Spec.BackupMethod = "non-existent"
+ })
+ backupKey := client.ObjectKeyFromObject(backup)
+
+ By("check backup status failed")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
+ })).Should(Succeed())
+ })
+ })
+
+ Context("creates a backup with invalid backup method", func() {
+ BeforeEach(func() {
+ backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, func(backupPolicy *dpv1alpha1.BackupPolicy) {
+ backupPolicy.Spec.BackupMethods = append(backupPolicy.Spec.BackupMethods, dpv1alpha1.BackupMethod{
+ Name: "invalid",
+ ActionSetName: "",
+ })
+ })
+ })
+
+ It("should fail because backup method doesn't specify snapshotVolumes with empty actionSet", func() {
+ backup := testdp.NewFakeBackup(&testCtx, func(backup *dpv1alpha1.Backup) {
+ backup.Spec.BackupPolicyName = backupPolicy.Name
+ backup.Spec.BackupMethod = "invalid"
+ })
+ backupKey := client.ObjectKeyFromObject(backup)
+
+ By("check backup status failed")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
+ })).Should(Succeed())
+ })
+
+ It("should fail because of no-existing actionSet", func() {
+ backup := testdp.NewFakeBackup(&testCtx, nil)
+ backupKey := client.ObjectKeyFromObject(backup)
+
+ By("check backup status failed")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
+ })).Should(Succeed())
+ })
+
+ It("should fail because actionSet's backup type isn't Full", func() {
+ actionSet := testdp.NewFakeActionSet(&testCtx)
+ actionSetKey := client.ObjectKeyFromObject(actionSet)
+ Eventually(testapps.GetAndChangeObj(&testCtx, actionSetKey, func(fetched *dpv1alpha1.ActionSet) {
+ fetched.Spec.BackupType = dpv1alpha1.BackupTypeIncremental
+ }))
+
+ backup := testdp.NewFakeBackup(&testCtx, nil)
+ backupKey := client.ObjectKeyFromObject(backup)
+
+ By("check backup status failed")
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupPhaseFailed))
+ })).Should(Succeed())
+ })
+ })
})
When("with backup repo", func() {
diff --git a/pkg/dataprotection/backup/deleter_test.go b/pkg/dataprotection/backup/deleter_test.go
index 5ea0bbeea2b..72877c8d75e 100644
--- a/pkg/dataprotection/backup/deleter_test.go
+++ b/pkg/dataprotection/backup/deleter_test.go
@@ -122,6 +122,37 @@ var _ = Describe("Backup Deleter Test", func() {
job := &batchv1.Job{}
key := BuildDeleteBackupFilesJobKey(backup)
Eventually(testapps.CheckObjExists(&testCtx, key, job, true)).Should(Succeed())
+
+ By("delete backup with job running")
+ backupKey := client.ObjectKeyFromObject(backup)
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ status, err := deleter.DeleteBackupFiles(fetched)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(status).Should(Equal(DeletionStatusDeleting))
+ })).Should(Succeed())
+
+ By("delete backup with job succeed")
+ testdp.ReplaceK8sJobStatus(&testCtx, key, batchv1.JobComplete)
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ status, err := deleter.DeleteBackupFiles(fetched)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(status).Should(Equal(DeletionStatusSucceeded))
+ })).Should(Succeed())
+
+ By("delete backup with job failed")
+ testdp.ReplaceK8sJobStatus(&testCtx, key, batchv1.JobFailed)
+ Eventually(testapps.CheckObj(&testCtx, backupKey, func(g Gomega, fetched *dpv1alpha1.Backup) {
+ status, err := deleter.DeleteBackupFiles(fetched)
+ Expect(err).Should(HaveOccurred())
+ Expect(status).Should(Equal(DeletionStatusFailed))
+ })).Should(Succeed())
+ })
+
+ It("delete backup with backup repo", func() {
+ backup.Status.BackupRepoName = testdp.BackupRepoName
+ status, err := deleter.DeleteBackupFiles(backup)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(status).Should(Equal(DeletionStatusSucceeded))
})
})
diff --git a/pkg/dataprotection/backup/request_test.go b/pkg/dataprotection/backup/request_test.go
index 03c19330a9b..02213b477b5 100644
--- a/pkg/dataprotection/backup/request_test.go
+++ b/pkg/dataprotection/backup/request_test.go
@@ -18,3 +18,130 @@ along with this program. If not, see .
*/
package backup
+
+import (
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ corev1 "k8s.io/api/core/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
+ "github.com/apecloud/kubeblocks/pkg/constant"
+ ctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
+ "github.com/apecloud/kubeblocks/pkg/dataprotection/utils/boolptr"
+ "github.com/apecloud/kubeblocks/pkg/generics"
+ testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps"
+ testdp "github.com/apecloud/kubeblocks/pkg/testutil/dataprotection"
+)
+
+var _ = Describe("Request Test", func() {
+ buildRequest := func() *Request {
+ return &Request{
+ RequestCtx: ctrlutil.RequestCtx{
+ Log: logger,
+ Ctx: testCtx.Ctx,
+ Recorder: recorder,
+ },
+ Client: testCtx.Cli,
+ }
+ }
+
+ cleanEnv := func() {
+ // must wait till resources deleted and no longer existed before the testcases start,
+ // otherwise if later it needs to create some new resource objects with the same name,
+ // in race conditions, it will find the existence of old objects, resulting failure to
+ // create the new objects.
+ By("clean resources")
+
+ // delete rest mocked objects
+ inNS := client.InNamespace(testCtx.DefaultNamespace)
+ ml := client.HasLabels{testCtx.TestObjLabelKey}
+
+ // namespaced
+ testapps.ClearResources(&testCtx, generics.ClusterSignature, inNS, ml)
+ testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml)
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS)
+
+ // wait all backup to be deleted, otherwise the controller maybe create
+ // job to delete the backup between the ClearResources function delete
+ // the job and get the job list, resulting the ClearResources panic.
+ Eventually(testapps.List(&testCtx, generics.BackupSignature, inNS)).Should(HaveLen(0))
+
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS)
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS)
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS)
+
+ // non-namespaced
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ActionSetSignature, true, ml)
+ testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupRepoSignature, true, ml)
+ testapps.ClearResources(&testCtx, generics.StorageProviderSignature, ml)
+ testapps.ClearResources(&testCtx, generics.VolumeSnapshotClassSignature, ml)
+ }
+
+ var clusterInfo *testdp.BackupClusterInfo
+
+ BeforeEach(func() {
+ cleanEnv()
+ clusterInfo = testdp.NewFakeCluster(&testCtx)
+ })
+
+ AfterEach(func() {
+ cleanEnv()
+ })
+
+ When("with default settings", func() {
+ var (
+ backup *dpv1alpha1.Backup
+ actionSet *dpv1alpha1.ActionSet
+ backupPolicy *dpv1alpha1.BackupPolicy
+ backupRepo *dpv1alpha1.BackupRepo
+
+ targetPod *corev1.Pod
+ request *Request
+ )
+
+ BeforeEach(func() {
+ actionSet = testapps.CreateCustomizedObj(&testCtx, "backup/actionset.yaml",
+ &dpv1alpha1.ActionSet{}, testapps.WithName(testdp.ActionSetName))
+ backup = testdp.NewFakeBackup(&testCtx, nil)
+ backupRepo = testapps.CreateCustomizedObj(&testCtx, "backup/backuprepo.yaml", &dpv1alpha1.BackupRepo{}, nil)
+ backupPolicy = testdp.NewBackupPolicyFactory(testCtx.DefaultNamespace, testdp.BackupPolicyName).
+ SetBackupRepoName(testdp.BackupRepoName).
+ SetPathPrefix(testdp.BackupPathPrefix).
+ SetTarget(constant.AppInstanceLabelKey, testdp.ClusterName,
+ constant.KBAppComponentLabelKey, testdp.ComponentName,
+ constant.RoleLabelKey, constant.Leader).
+ AddBackupMethod(testdp.BackupMethodName, false, testdp.ActionSetName).
+ Create(&testCtx).GetObject()
+
+ targetPod = clusterInfo.TargetPod
+ request = buildRequest()
+ })
+
+ Context("build action", func() {
+ It("should build action", func() {
+ request.Backup = backup
+ request.ActionSet = actionSet
+ request.TargetPods = []*corev1.Pod{targetPod}
+ request.BackupPolicy = backupPolicy
+ request.BackupMethod = &backupPolicy.Spec.BackupMethods[0]
+ request.BackupRepo = backupRepo
+ _, err := request.BuildActions()
+ Expect(err).NotTo(HaveOccurred())
+ })
+
+ It("build create volume snapshot action", func() {
+ request.TargetPods = []*corev1.Pod{targetPod}
+ request.BackupMethod = &dpv1alpha1.BackupMethod{
+ Name: testdp.VSBackupMethodName,
+ SnapshotVolumes: boolptr.True(),
+ }
+ _, err := request.buildCreateVolumeSnapshotAction()
+ Expect(err).Should(HaveOccurred())
+ })
+
+ })
+ })
+})
diff --git a/pkg/dataprotection/backup/scheduler_test.go b/pkg/dataprotection/backup/scheduler_test.go
index 03c19330a9b..74ff4366ae6 100644
--- a/pkg/dataprotection/backup/scheduler_test.go
+++ b/pkg/dataprotection/backup/scheduler_test.go
@@ -18,3 +18,93 @@ along with this program. If not, see .
*/
package backup
+
+import (
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
+ "github.com/apecloud/kubeblocks/pkg/constant"
+ ctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
+ "github.com/apecloud/kubeblocks/pkg/generics"
+ testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps"
+ testdp "github.com/apecloud/kubeblocks/pkg/testutil/dataprotection"
+)
+
+var _ = Describe("Scheduler Test", func() {
+ buildScheduler := func() *Scheduler {
+ return &Scheduler{
+ RequestCtx: ctrlutil.RequestCtx{
+ Log: logger,
+ Ctx: testCtx.Ctx,
+ Recorder: recorder,
+ },
+ Client: testCtx.Cli,
+ }
+ }
+
+ cleanEnv := func() {
+ // must wait till resources deleted and no longer existed before the testcases start,
+ // otherwise if later it needs to create some new resource objects with the same name,
+ // in race conditions, it will find the existence of old objects, resulting failure to
+ // create the new objects.
+ By("clean resources")
+
+ // delete rest mocked objects
+ inNS := client.InNamespace(testCtx.DefaultNamespace)
+
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS)
+ testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupScheduleSignature, true, inNS)
+ }
+
+ BeforeEach(func() {
+ cleanEnv()
+ })
+
+ AfterEach(func() {
+ cleanEnv()
+ })
+
+ When("with default settings", func() {
+ var (
+ backupPolicy *dpv1alpha1.BackupPolicy
+ backupSchedule *dpv1alpha1.BackupSchedule
+
+ scheduler *Scheduler
+ )
+
+ BeforeEach(func() {
+ backupPolicy = testdp.NewBackupPolicyFactory(testCtx.DefaultNamespace, testdp.BackupPolicyName).
+ SetBackupRepoName(testdp.BackupRepoName).
+ SetPathPrefix(testdp.BackupPathPrefix).
+ SetTarget(constant.AppInstanceLabelKey, testdp.ClusterName,
+ constant.KBAppComponentLabelKey, testdp.ComponentName,
+ constant.RoleLabelKey, constant.Leader).
+ AddBackupMethod(testdp.BackupMethodName, false, testdp.ActionSetName).
+ AddBackupMethod(testdp.VSBackupMethodName, true, "").
+ Create(&testCtx).GetObject()
+ backupSchedule = testdp.NewFakeBackupSchedule(&testCtx, nil)
+
+ scheduler = buildScheduler()
+ })
+
+ Context("test Schedule", func() {
+ It("should schedule", func() {
+ scheduler.BackupSchedule = backupSchedule
+ scheduler.BackupPolicy = backupPolicy
+ Expect(scheduler.Schedule()).Should(Succeed())
+ })
+
+ It("schedule should fail if invalid backup policy", func() {
+ scheduler.BackupSchedule = backupSchedule
+ scheduler.BackupPolicy = backupPolicy
+ for i := range scheduler.BackupPolicy.Spec.BackupMethods {
+ scheduler.BackupPolicy.Spec.BackupMethods[i].Name = "not-exist"
+ }
+ Expect(scheduler.Schedule()).ShouldNot(Succeed())
+ })
+ })
+ })
+})
diff --git a/pkg/dataprotection/backup/suite_test.go b/pkg/dataprotection/backup/suite_test.go
index 8bdc4dbe763..2fae8a912cf 100644
--- a/pkg/dataprotection/backup/suite_test.go
+++ b/pkg/dataprotection/backup/suite_test.go
@@ -42,6 +42,7 @@ import (
appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1"
dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
+ storagev1alpha1 "github.com/apecloud/kubeblocks/apis/storage/v1alpha1"
ctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
"github.com/apecloud/kubeblocks/pkg/testutil"
viper "github.com/apecloud/kubeblocks/pkg/viperx"
@@ -109,6 +110,12 @@ var _ = BeforeSuite(func() {
err = dpv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
+ err = storagev1alpha1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = vsv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})