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