github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/dataprotection/restore/manager_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 restore 21 22 import ( 23 "fmt" 24 "strconv" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 batchv1 "k8s.io/api/batch/v1" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 ctrl "sigs.k8s.io/controller-runtime" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 36 "github.com/1aal/kubeblocks/pkg/constant" 37 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 38 "github.com/1aal/kubeblocks/pkg/generics" 39 "github.com/1aal/kubeblocks/pkg/testutil" 40 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 41 testdp "github.com/1aal/kubeblocks/pkg/testutil/dataprotection" 42 ) 43 44 var _ = Describe("Backup Deleter Test", func() { 45 46 cleanEnv := func() { 47 By("clean resources") 48 // delete rest mocked objects 49 inNS := client.InNamespace(testCtx.DefaultNamespace) 50 ml := client.HasLabels{testCtx.TestObjLabelKey} 51 52 // namespaced 53 testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml) 54 testapps.ClearResources(&testCtx, generics.ClusterSignature, inNS, ml) 55 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS) 56 57 // wait all backup to be deleted, otherwise the controller maybe create 58 // job to delete the backup between the ClearResources function delete 59 // the job and get the job list, resulting the ClearResources panic. 60 Eventually(testapps.List(&testCtx, generics.BackupSignature, inNS)).Should(HaveLen(0)) 61 62 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.JobSignature, true, inNS) 63 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.RestoreSignature, true, inNS) 64 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS) 65 66 // non-namespaced 67 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ActionSetSignature, true, ml) 68 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.StorageClassSignature, true, ml) 69 testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeSignature, true, ml) 70 } 71 72 BeforeEach(func() { 73 cleanEnv() 74 }) 75 76 AfterEach(func() { 77 cleanEnv() 78 }) 79 80 Context("with restore manager functions", func() { 81 var ( 82 actionSet *dpv1alpha1.ActionSet 83 nodeName = "minikube" 84 replicas = 2 85 ) 86 87 BeforeEach(func() { 88 89 By("create actionSet") 90 actionSet = testapps.CreateCustomizedObj(&testCtx, "backup/actionset.yaml", 91 &dpv1alpha1.ActionSet{}, testapps.WithName(testdp.ActionSetName)) 92 93 }) 94 95 mockBackupForRestore := func(testCtx *testutil.TestContext, actionSetName, backupPVCName string, mockBackupCompleted, useVolumeSnapshotBackup bool) *dpv1alpha1.Backup { 96 backup := testdp.NewFakeBackup(testCtx, nil) 97 if mockBackupCompleted { 98 // then mock backup to completed 99 backupMethodName := testdp.BackupMethodName 100 if useVolumeSnapshotBackup { 101 backupMethodName = testdp.VSBackupMethodName 102 } 103 Expect(testapps.ChangeObjStatus(testCtx, backup, func() { 104 backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted 105 backup.Status.PersistentVolumeClaimName = backupPVCName 106 testdp.MockBackupStatusMethod(backup, backupMethodName, testdp.DataVolumeName, actionSetName) 107 })).Should(Succeed()) 108 } 109 return backup 110 } 111 112 initResources := func(reqCtx intctrlutil.RequestCtx, startingIndex int, useVolumeSnapshotBackup bool, change func(f *testdp.MockRestoreFactory)) (*RestoreManager, *BackupActionSet) { 113 By("create a completed backup") 114 backup := mockBackupForRestore(&testCtx, actionSet.Name, testdp.BackupPVCName, true, false) 115 116 schedulingSpec := dpv1alpha1.SchedulingSpec{ 117 NodeName: nodeName, 118 } 119 120 By("create restore") 121 restoreFactory := testdp.NewRestoreactory(testCtx.DefaultNamespace, testdp.RestoreName). 122 SetBackup(backup.Name, testCtx.DefaultNamespace). 123 SetSchedulingSpec(schedulingSpec) 124 125 change(restoreFactory) 126 127 restore := restoreFactory.Create(&testCtx).Get() 128 129 By("create restore manager") 130 restoreMGR := NewRestoreManager(restore, recorder, k8sClient.Scheme()) 131 backupSet, err := restoreMGR.GetBackupActionSetByNamespaced(reqCtx, k8sClient, backup.Name, testCtx.DefaultNamespace) 132 Expect(err).ShouldNot(HaveOccurred()) 133 return restoreMGR, backupSet 134 } 135 136 checkPVC := func(startingIndex int, useVolumeSnapshot bool) { 137 By("expect for pvcs are created") 138 pvcMatchingLabels := client.MatchingLabels{constant.AppManagedByLabelKey: "restore"} 139 Eventually(testapps.List(&testCtx, generics.PersistentVolumeClaimSignature, pvcMatchingLabels, 140 client.InNamespace(testCtx.DefaultNamespace))).Should(HaveLen(replicas)) 141 142 By(fmt.Sprintf("pvc index should greater than or equal to %d and dataSource can not be nil", startingIndex)) 143 pvcList := &corev1.PersistentVolumeClaimList{} 144 Expect(k8sClient.List(ctx, pvcList, pvcMatchingLabels, 145 client.InNamespace(testCtx.DefaultNamespace))).Should(Succeed()) 146 for _, v := range pvcList.Items { 147 indexStr := string(v.Name[len(v.Name)-1]) 148 index, _ := strconv.Atoi(indexStr) 149 Expect(index >= startingIndex).Should(BeTrue()) 150 if useVolumeSnapshot { 151 Expect(v.Spec.DataSource).ShouldNot(BeNil()) 152 } 153 } 154 } 155 156 getReqCtx := func() intctrlutil.RequestCtx { 157 return intctrlutil.RequestCtx{ 158 Ctx: ctx, 159 Req: ctrl.Request{ 160 NamespacedName: types.NamespacedName{ 161 Namespace: testCtx.DefaultNamespace, 162 }, 163 }, 164 } 165 } 166 167 checkVolumes := func(job *batchv1.Job, volumeName string, exist bool) { 168 var volumeExist bool 169 for _, v := range job.Spec.Template.Spec.Volumes { 170 if v.Name == volumeName { 171 volumeExist = true 172 break 173 } 174 } 175 Expect(volumeExist).Should(Equal(exist)) 176 } 177 178 It("test with RestorePVCFromSnapshot function", func() { 179 reqCtx := getReqCtx() 180 startingIndex := 0 181 useVolumeSnapshot := true 182 restoreMGR, backupSet := initResources(reqCtx, startingIndex, useVolumeSnapshot, func(f *testdp.MockRestoreFactory) { 183 f.SetVolumeClaimsTemplate(testdp.MysqlTemplateName, testdp.DataVolumeName, 184 testdp.DataVolumeMountPath, "", int32(replicas), int32(startingIndex)) 185 }) 186 187 By("test RestorePVCFromSnapshot function") 188 Expect(restoreMGR.RestorePVCFromSnapshot(reqCtx, k8sClient, *backupSet)).Should(Succeed()) 189 190 checkPVC(startingIndex, useVolumeSnapshot) 191 }) 192 193 It("test with BuildPrepareDataJobs function and Parallel volumeRestorePolicy", func() { 194 reqCtx := getReqCtx() 195 startingIndex := 1 196 restoreMGR, backupSet := initResources(reqCtx, startingIndex, false, func(f *testdp.MockRestoreFactory) { 197 f.SetVolumeClaimsTemplate(testdp.MysqlTemplateName, testdp.DataVolumeName, 198 testdp.DataVolumeMountPath, "", int32(replicas), int32(startingIndex)) 199 }) 200 201 By(fmt.Sprintf("test BuildPrepareDataJobs function, expect for %d jobs", replicas)) 202 actionSetName := "preparedata-0" 203 jobs, err := restoreMGR.BuildPrepareDataJobs(reqCtx, k8sClient, *backupSet, actionSetName) 204 Expect(err).ShouldNot(HaveOccurred()) 205 Expect(len(jobs)).Should(Equal(replicas)) 206 // image should be expanded by env 207 Expect(jobs[0].Spec.Template.Spec.Containers[0].Image).Should(ContainSubstring(testdp.ImageTag)) 208 209 checkPVC(startingIndex, false) 210 }) 211 212 It("test with BuildPrepareDataJobs function and Serial volumeRestorePolicy", func() { 213 reqCtx := getReqCtx() 214 startingIndex := 1 215 restoreMGR, backupSet := initResources(reqCtx, startingIndex, false, func(f *testdp.MockRestoreFactory) { 216 f.SetVolumeClaimsTemplate(testdp.MysqlTemplateName, testdp.DataVolumeName, 217 testdp.DataVolumeMountPath, "", int32(replicas), int32(startingIndex)). 218 SetVolumeClaimRestorePolicy(dpv1alpha1.VolumeClaimRestorePolicySerial) 219 }) 220 221 actionSetName := "preparedata-0" 222 testSerialCreateJob := func(expectRestoreFinished bool) { 223 By("test BuildPrepareDataJobs function, expect for 1 job") 224 jobs, err := restoreMGR.BuildPrepareDataJobs(reqCtx, k8sClient, *backupSet, actionSetName) 225 Expect(err).ShouldNot(HaveOccurred()) 226 Expect(len(jobs)).Should(Equal(1)) 227 228 By("test CreateJobsIfNotExist function") 229 jobs, err = restoreMGR.CreateJobsIfNotExist(reqCtx, k8sClient, restoreMGR.Restore, jobs) 230 Expect(err).ShouldNot(HaveOccurred()) 231 232 By("test CheckJobsDone function and jobs is running") 233 allJobsFinished, existFailedJob := restoreMGR.CheckJobsDone(dpv1alpha1.PrepareData, actionSetName, *backupSet, jobs) 234 Expect(allJobsFinished).Should(BeFalse()) 235 236 By("mock jobs are completed") 237 jobCondition := batchv1.JobCondition{Type: batchv1.JobComplete, Status: corev1.ConditionTrue} 238 for i := range jobs { 239 jobs[i].Status.Conditions = append(jobs[i].Status.Conditions, jobCondition) 240 } 241 242 By("test CheckJobsDone function and jobs are finished") 243 allJobsFinished, existFailedJob = restoreMGR.CheckJobsDone(dpv1alpha1.PrepareData, actionSetName, *backupSet, jobs) 244 Expect(allJobsFinished).Should(BeTrue()) 245 246 By("test Recalculation function, allJobFinished should be false because it only restored one pvc.") 247 restoreMGR.Recalculation(backupSet.Backup.Name, actionSetName, &allJobsFinished, &existFailedJob) 248 if expectRestoreFinished { 249 Expect(allJobsFinished).Should(BeTrue()) 250 } else { 251 Expect(allJobsFinished).Should(BeFalse()) 252 } 253 } 254 255 // expect for creating and finishing the first restore job but restore is continuing. 256 testSerialCreateJob(false) 257 258 // expect for creating and finishing the last one restore job and restore should be competed. 259 testSerialCreateJob(true) 260 261 By("test AnalysisRestoreActionsWithBackup function") 262 allActionsFinished, _ := restoreMGR.AnalysisRestoreActionsWithBackup(dpv1alpha1.PrepareData, testdp.BackupName, actionSetName) 263 Expect(allActionsFinished).Should(BeTrue()) 264 265 }) 266 267 It("test with BuildVolumePopulateJob function", func() { 268 reqCtx := getReqCtx() 269 restoreMGR, backupSet := initResources(reqCtx, 0, true, func(f *testdp.MockRestoreFactory) { 270 f.SetDataSourceRef(testdp.DataVolumeName, testdp.DataVolumeMountPath) 271 }) 272 273 By("test BuildVolumePopulateJob function, expect for 1 job") 274 populatePVC := &corev1.PersistentVolumeClaim{ 275 ObjectMeta: metav1.ObjectMeta{ 276 Name: "test-populate-pvc", 277 }, 278 } 279 job, err := restoreMGR.BuildVolumePopulateJob(reqCtx, k8sClient, *backupSet, populatePVC, 0) 280 Expect(err).ShouldNot(HaveOccurred()) 281 Expect(job).ShouldNot(BeNil()) 282 }) 283 284 testPostReady := func(existVolume bool) { 285 reqCtx := getReqCtx() 286 matchLabels := map[string]string{ 287 constant.AppInstanceLabelKey: testdp.ClusterName, 288 } 289 restoreMGR, backupSet := initResources(reqCtx, 0, false, func(f *testdp.MockRestoreFactory) { 290 f.SetConnectCredential(testdp.ClusterName).SetJobActionConfig(matchLabels).SetExecActionConfig(matchLabels) 291 }) 292 293 By("create cluster to restore") 294 testdp.NewFakeCluster(&testCtx) 295 296 By("test with execAction and expect for creating 2 exec job") 297 // step 0 is the execAction in actionSet 298 jobs, err := restoreMGR.BuildPostReadyActionJobs(reqCtx, k8sClient, *backupSet, 0) 299 Expect(err).ShouldNot(HaveOccurred()) 300 // the count of exec jobs should equal to the pods count of cluster 301 Expect(len(jobs)).Should(Equal(2)) 302 303 By("test with jobAction and expect for creating 1 job") 304 // step 0 is the execAction in actionSet 305 jobs, err = restoreMGR.BuildPostReadyActionJobs(reqCtx, k8sClient, *backupSet, 1) 306 Expect(err).ShouldNot(HaveOccurred()) 307 // count of job should equal to 1 308 Expect(len(jobs)).Should(Equal(1)) 309 310 checkVolumes(jobs[0], testdp.DataVolumeName, existVolume) 311 } 312 313 It("test with BuildPostReadyActionJobs function and run target pod node", func() { 314 testPostReady(true) 315 }) 316 317 It("test with BuildPostReadyActionJobs function and no need to run target pod node", func() { 318 Expect(testapps.ChangeObj(&testCtx, actionSet, func(set *dpv1alpha1.ActionSet) { 319 runTargetPodNode := false 320 actionSet.Spec.Restore.PostReady[1].Job.RunOnTargetPodNode = &runTargetPodNode 321 })).Should(Succeed()) 322 testPostReady(false) 323 }) 324 }) 325 326 })