github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/backupschedule_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  	"fmt"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  
    28  	batchv1 "k8s.io/api/batch/v1"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  
    31  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    32  	dpbackup "github.com/1aal/kubeblocks/pkg/dataprotection/backup"
    33  	"github.com/1aal/kubeblocks/pkg/dataprotection/utils/boolptr"
    34  	"github.com/1aal/kubeblocks/pkg/generics"
    35  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    36  	testdp "github.com/1aal/kubeblocks/pkg/testutil/dataprotection"
    37  )
    38  
    39  var _ = Describe("Backup Schedule Controller", func() {
    40  	cleanEnv := func() {
    41  		// must wait till resources deleted and no longer existed before the testcases start,
    42  		// otherwise if later it needs to create some new resource objects with the same name,
    43  		// in race conditions, it will find the existence of old objects, resulting failure to
    44  		// create the new objects.
    45  		By("clean resources")
    46  		// delete rest mocked objects
    47  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    48  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    49  
    50  		// namespaced
    51  		testapps.ClearResources(&testCtx, generics.ClusterSignature, inNS, ml)
    52  		testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml)
    53  		testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml)
    54  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS)
    55  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupScheduleSignature, true, inNS)
    56  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS)
    57  
    58  		// wait all backup to be deleted, otherwise the controller maybe create
    59  		// job to delete the backup between the ClearResources function delete
    60  		// the job and get the job list, resulting the ClearResources panic.
    61  		Eventually(testapps.List(&testCtx, generics.BackupSignature, inNS)).Should(HaveLen(0))
    62  
    63  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS)
    64  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS)
    65  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.CronJobSignature, true, inNS)
    66  		testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml)
    67  
    68  		// non-namespaced
    69  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ActionSetSignature, true, ml)
    70  		testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
    71  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeSignature, true, ml)
    72  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupRepoSignature, true, ml)
    73  		testapps.ClearResources(&testCtx, generics.StorageProviderSignature, ml)
    74  	}
    75  
    76  	BeforeEach(func() {
    77  		cleanEnv()
    78  		_ = testdp.NewFakeCluster(&testCtx)
    79  	})
    80  
    81  	AfterEach(cleanEnv)
    82  
    83  	When("creating backup schedule with default settings", func() {
    84  		var (
    85  			backupPolicy *dpv1alpha1.BackupPolicy
    86  		)
    87  
    88  		getCronjobKey := func(backupSchedule *dpv1alpha1.BackupSchedule,
    89  			method string) client.ObjectKey {
    90  			return client.ObjectKey{
    91  				Name:      dpbackup.GenerateCRNameByBackupSchedule(backupSchedule, method),
    92  				Namespace: backupPolicy.Namespace,
    93  			}
    94  		}
    95  
    96  		BeforeEach(func() {
    97  			By("creating an actionSet")
    98  			actionSet := testdp.NewFakeActionSet(&testCtx)
    99  
   100  			By("creating storage provider")
   101  			_ = testdp.NewFakeStorageProvider(&testCtx, nil)
   102  
   103  			By("creating backup repo")
   104  			_, _ = testdp.NewFakeBackupRepo(&testCtx, nil)
   105  
   106  			By("By creating a backupPolicy from actionSet " + actionSet.Name)
   107  			backupPolicy = testdp.NewFakeBackupPolicy(&testCtx, nil)
   108  		})
   109  
   110  		AfterEach(func() {
   111  		})
   112  
   113  		Context("creates a backup schedule", func() {
   114  			var (
   115  				backupSchedule    *dpv1alpha1.BackupSchedule
   116  				backupScheduleKey client.ObjectKey
   117  			)
   118  			BeforeEach(func() {
   119  				By("creating a backupSchedule")
   120  				backupSchedule = testdp.NewFakeBackupSchedule(&testCtx, nil)
   121  				backupScheduleKey = client.ObjectKeyFromObject(backupSchedule)
   122  			})
   123  
   124  			It("should success", func() {
   125  				By("checking backupSchedule status, should be available")
   126  				Eventually(testapps.CheckObj(&testCtx, backupScheduleKey, func(g Gomega, fetched *dpv1alpha1.BackupSchedule) {
   127  					g.Expect(fetched.Status.Phase).To(Equal(dpv1alpha1.BackupSchedulePhaseAvailable))
   128  				})).Should(Succeed())
   129  
   130  				By("checking cronjob, should not exist because all schedule policies of methods are disabled")
   131  				Eventually(testapps.CheckObjExists(&testCtx, getCronjobKey(backupSchedule, testdp.BackupMethodName),
   132  					&batchv1.CronJob{}, false)).Should(Succeed())
   133  				Eventually(testapps.CheckObjExists(&testCtx, getCronjobKey(backupSchedule, testdp.VSBackupMethodName),
   134  					&batchv1.CronJob{}, false)).Should(Succeed())
   135  
   136  				By(fmt.Sprintf("enabling %s method schedule", testdp.BackupMethodName))
   137  				testdp.EnableBackupSchedule(&testCtx, backupSchedule, testdp.BackupMethodName)
   138  
   139  				By("checking cronjob, should exist one cronjob to create backup")
   140  				Eventually(testapps.CheckObj(&testCtx, getCronjobKey(backupSchedule, testdp.BackupMethodName), func(g Gomega, fetched *batchv1.CronJob) {
   141  					schedulePolicy := dpbackup.GetSchedulePolicyByMethod(backupSchedule, testdp.BackupMethodName)
   142  					g.Expect(boolptr.IsSetToTrue(schedulePolicy.Enabled)).To(BeTrue())
   143  					g.Expect(fetched.Spec.Schedule).To(Equal(schedulePolicy.CronExpression))
   144  					g.Expect(fetched.Spec.StartingDeadlineSeconds).ShouldNot(BeNil())
   145  					g.Expect(*fetched.Spec.StartingDeadlineSeconds).To(Equal(getStartingDeadlineSeconds(backupSchedule)))
   146  				})).Should(Succeed())
   147  			})
   148  		})
   149  
   150  		Context("creates a backup schedule with empty schedule", func() {
   151  			It("should fail when create a backupSchedule without nil schedule policy", func() {
   152  				backupScheduleObj := testdp.NewBackupScheduleFactory(testCtx.DefaultNamespace, testdp.BackupScheduleName).
   153  					SetBackupPolicyName(testdp.BackupPolicyName).
   154  					SetSchedules(nil).
   155  					GetObject()
   156  				Expect(testCtx.CheckedCreateObj(testCtx.Ctx, backupScheduleObj)).Should(HaveOccurred())
   157  			})
   158  
   159  			It("should fail when create a backupSchedule without empty schedule policy", func() {
   160  				backupScheduleObj := testdp.NewBackupScheduleFactory(testCtx.DefaultNamespace, testdp.BackupScheduleName).
   161  					SetBackupPolicyName(testdp.BackupPolicyName).
   162  					GetObject()
   163  				Expect(testCtx.CheckedCreateObj(testCtx.Ctx, backupScheduleObj)).Should(HaveOccurred())
   164  			})
   165  		})
   166  
   167  		Context("creates a backup schedule with invalid field", func() {
   168  			var (
   169  				backupScheduleKey client.ObjectKey
   170  				backupSchedule    *dpv1alpha1.BackupSchedule
   171  			)
   172  
   173  			BeforeEach(func() {
   174  				By("creating a backupSchedule")
   175  				backupSchedule = testdp.NewFakeBackupSchedule(&testCtx, func(schedule *dpv1alpha1.BackupSchedule) {
   176  					schedule.Spec.Schedules[0].CronExpression = "invalid"
   177  				})
   178  				backupScheduleKey = client.ObjectKeyFromObject(backupSchedule)
   179  			})
   180  
   181  			It("should fail", func() {
   182  				Eventually(testapps.CheckObj(&testCtx, backupScheduleKey, func(g Gomega, fetched *dpv1alpha1.BackupSchedule) {
   183  					g.Expect(fetched.Status.Phase).NotTo(Equal(dpv1alpha1.BackupSchedulePhaseAvailable))
   184  				})).Should(Succeed())
   185  			})
   186  		})
   187  	})
   188  })
   189  
   190  func getStartingDeadlineSeconds(backupSchedule *dpv1alpha1.BackupSchedule) int64 {
   191  	if backupSchedule.Spec.StartingDeadlineMinutes == nil {
   192  		return 0
   193  	}
   194  	return *backupSchedule.Spec.StartingDeadlineMinutes * 60
   195  }