github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/dataprotection_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 cluster
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"fmt"
    26  	"strings"
    27  	"time"
    28  
    29  	. "github.com/onsi/ginkgo/v2"
    30  	. "github.com/onsi/gomega"
    31  
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	k8sapitypes "k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  	"k8s.io/client-go/dynamic"
    38  	clientfake "k8s.io/client-go/rest/fake"
    39  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    40  
    41  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    42  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    43  	"github.com/1aal/kubeblocks/pkg/cli/cluster"
    44  	"github.com/1aal/kubeblocks/pkg/cli/create"
    45  	"github.com/1aal/kubeblocks/pkg/cli/delete"
    46  	"github.com/1aal/kubeblocks/pkg/cli/list"
    47  	"github.com/1aal/kubeblocks/pkg/cli/testing"
    48  	"github.com/1aal/kubeblocks/pkg/cli/types"
    49  	"github.com/1aal/kubeblocks/pkg/cli/util"
    50  	"github.com/1aal/kubeblocks/pkg/constant"
    51  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    52  )
    53  
    54  var _ = Describe("DataProtection", func() {
    55  	const policyName = "policy"
    56  	const repoName = "repo"
    57  	var streams genericiooptions.IOStreams
    58  	var tf *cmdtesting.TestFactory
    59  	var out *bytes.Buffer
    60  	BeforeEach(func() {
    61  		streams, _, out, _ = genericiooptions.NewTestIOStreams()
    62  		tf = cmdtesting.NewTestFactory().WithNamespace(testing.Namespace)
    63  		tf.Client = &clientfake.RESTClient{}
    64  	})
    65  
    66  	AfterEach(func() {
    67  		tf.Cleanup()
    68  	})
    69  
    70  	Context("backup", func() {
    71  		initClient := func(policies ...*dpv1alpha1.BackupPolicy) {
    72  			clusterDef := testing.FakeClusterDef()
    73  			cluster := testing.FakeCluster(testing.ClusterName, testing.Namespace)
    74  			clusterDefLabel := map[string]string{
    75  				constant.ClusterDefLabelKey: clusterDef.Name,
    76  			}
    77  			cluster.SetLabels(clusterDefLabel)
    78  			pods := testing.FakePods(1, testing.Namespace, testing.ClusterName)
    79  			objects := []runtime.Object{
    80  				cluster, clusterDef, &pods.Items[0],
    81  			}
    82  			for _, v := range policies {
    83  				objects = append(objects, v)
    84  			}
    85  			tf.FakeDynamicClient = testing.FakeDynamicClient(objects...)
    86  		}
    87  
    88  		It("list-backup-policy", func() {
    89  			By("fake client")
    90  			defaultBackupPolicy := testing.FakeBackupPolicy(policyName, testing.ClusterName)
    91  			policy2 := testing.FakeBackupPolicy("policy1", testing.ClusterName)
    92  			policy3 := testing.FakeBackupPolicy("policy2", testing.ClusterName)
    93  			policy3.Namespace = "policy"
    94  			initClient(defaultBackupPolicy, policy2, policy3)
    95  
    96  			By("test list-backup-policy cmd")
    97  			cmd := NewListBackupPolicyCmd(tf, streams)
    98  			Expect(cmd).ShouldNot(BeNil())
    99  			cmd.Run(cmd, nil)
   100  			Expect(out.String()).Should(ContainSubstring(defaultBackupPolicy.Name))
   101  			Expect(out.String()).Should(ContainSubstring("true"))
   102  			Expect(len(strings.Split(strings.Trim(out.String(), "\n"), "\n"))).Should(Equal(3))
   103  
   104  			By("test list all namespace")
   105  			out.Reset()
   106  			_ = cmd.Flags().Set("all-namespaces", "true")
   107  			cmd.Run(cmd, nil)
   108  			fmt.Println(out.String())
   109  			Expect(out.String()).Should(ContainSubstring(policy2.Name))
   110  			Expect(len(strings.Split(strings.Trim(out.String(), "\n"), "\n"))).Should(Equal(4))
   111  		})
   112  
   113  		It("edit-backup-policy", func() {
   114  			By("fake client")
   115  			defaultBackupPolicy := testing.FakeBackupPolicy(policyName, testing.ClusterName)
   116  			repo := testing.FakeBackupRepo(repoName, false)
   117  			tf.FakeDynamicClient = testing.FakeDynamicClient(defaultBackupPolicy, repo)
   118  
   119  			By("test edit backup policy function")
   120  			o := editBackupPolicyOptions{Factory: tf, IOStreams: streams, GVR: types.BackupPolicyGVR()}
   121  			Expect(o.complete([]string{policyName})).Should(Succeed())
   122  			o.values = []string{"backupRepoName=repo"}
   123  			Expect(o.runEditBackupPolicy()).Should(Succeed())
   124  
   125  			By("test backup repo not exists")
   126  			o.values = []string{"backupRepoName=repo1"}
   127  			Expect(o.runEditBackupPolicy()).Should(MatchError(ContainSubstring(`"repo1" not found`)))
   128  
   129  			By("test with vim editor")
   130  			o.values = []string{}
   131  			o.isTest = true
   132  			Expect(o.runEditBackupPolicy()).Should(Succeed())
   133  		})
   134  
   135  		It("validate create backup", func() {
   136  			By("without cluster name")
   137  			o := &CreateBackupOptions{
   138  				CreateOptions: create.CreateOptions{
   139  					Dynamic:   testing.FakeDynamicClient(),
   140  					IOStreams: streams,
   141  					Factory:   tf,
   142  				},
   143  			}
   144  			Expect(o.Validate()).To(MatchError("missing cluster name"))
   145  
   146  			By("test without default backupPolicy")
   147  			o.Name = testing.ClusterName
   148  			o.Namespace = testing.Namespace
   149  			initClient()
   150  			o.Dynamic = tf.FakeDynamicClient
   151  			Expect(o.Validate()).Should(MatchError(fmt.Errorf(`not found any backup policy for cluster "%s"`, testing.ClusterName)))
   152  
   153  			By("test with two default backupPolicy")
   154  			defaultBackupPolicy := testing.FakeBackupPolicy(policyName, testing.ClusterName)
   155  			initClient(defaultBackupPolicy, testing.FakeBackupPolicy("policy2", testing.ClusterName))
   156  			o.Dynamic = tf.FakeDynamicClient
   157  			Expect(o.Validate()).Should(MatchError(fmt.Errorf(`cluster "%s" has multiple default backup policies`, o.Name)))
   158  
   159  			By("test without method")
   160  			initClient(defaultBackupPolicy)
   161  			o.Dynamic = tf.FakeDynamicClient
   162  			Expect(o.Validate().Error()).Should(ContainSubstring("backup method can not be empty, you can specify it by --method"))
   163  
   164  			By("test with one default backupPolicy")
   165  			initClient(defaultBackupPolicy)
   166  			o.Dynamic = tf.FakeDynamicClient
   167  			o.BackupMethod = testing.BackupMethodName
   168  			Expect(o.Validate()).Should(Succeed())
   169  		})
   170  
   171  		It("run backup command", func() {
   172  			defaultBackupPolicy := testing.FakeBackupPolicy(policyName, testing.ClusterName)
   173  			otherBackupPolicy := testing.FakeBackupPolicy("otherPolicy", testing.ClusterName)
   174  			otherBackupPolicy.Annotations = map[string]string{}
   175  			initClient(defaultBackupPolicy, otherBackupPolicy)
   176  			By("test backup with default backupPolicy")
   177  			cmd := NewCreateBackupCmd(tf, streams)
   178  			Expect(cmd).ShouldNot(BeNil())
   179  			_ = cmd.Flags().Set("method", testing.BackupMethodName)
   180  			cmd.Run(cmd, []string{testing.ClusterName})
   181  
   182  			By("test with specified backupMethod and backupPolicy")
   183  			o := &CreateBackupOptions{
   184  				CreateOptions: create.CreateOptions{
   185  					IOStreams:       streams,
   186  					Factory:         tf,
   187  					GVR:             types.BackupGVR(),
   188  					CueTemplateName: "backup_template.cue",
   189  					Name:            testing.ClusterName,
   190  				},
   191  				BackupPolicy: otherBackupPolicy.Name,
   192  				BackupMethod: testing.BackupMethodName,
   193  			}
   194  			Expect(o.CompleteBackup()).Should(Succeed())
   195  			err := o.Validate()
   196  			Expect(err).Should(Succeed())
   197  		})
   198  	})
   199  
   200  	It("delete-backup", func() {
   201  		By("test delete-backup cmd")
   202  		cmd := NewDeleteBackupCmd(tf, streams)
   203  		Expect(cmd).ShouldNot(BeNil())
   204  
   205  		args := []string{"test1"}
   206  		clusterLabel := util.BuildLabelSelectorByNames("", args)
   207  
   208  		By("test delete-backup with cluster")
   209  		o := delete.NewDeleteOptions(tf, streams, types.BackupGVR())
   210  		Expect(completeForDeleteBackup(o, args)).Should(HaveOccurred())
   211  
   212  		By("test delete-backup with cluster and force")
   213  		o.Force = true
   214  		Expect(completeForDeleteBackup(o, args)).Should(Succeed())
   215  		Expect(o.LabelSelector == clusterLabel).Should(BeTrue())
   216  
   217  		By("test delete-backup with cluster and force and labels")
   218  		o.Force = true
   219  		customLabel := "test=test"
   220  		o.LabelSelector = customLabel
   221  		Expect(completeForDeleteBackup(o, args)).Should(Succeed())
   222  		Expect(o.LabelSelector == customLabel+","+clusterLabel).Should(BeTrue())
   223  	})
   224  
   225  	It("list-backup", func() {
   226  		cmd := NewListBackupCmd(tf, streams)
   227  		Expect(cmd).ShouldNot(BeNil())
   228  		By("test list-backup cmd with no backup")
   229  		tf.FakeDynamicClient = testing.FakeDynamicClient()
   230  		o := ListBackupOptions{ListOptions: list.NewListOptions(tf, streams, types.BackupGVR())}
   231  		Expect(PrintBackupList(o)).Should(Succeed())
   232  		Expect(o.ErrOut.(*bytes.Buffer).String()).Should(ContainSubstring("No backups found"))
   233  
   234  		By("test list-backup")
   235  		backup1 := testing.FakeBackup("test1")
   236  		backup1.Labels = map[string]string{
   237  			constant.AppInstanceLabelKey: "apecloud-mysql",
   238  		}
   239  		backup1.Status.Phase = dpv1alpha1.BackupPhaseRunning
   240  		backup2 := testing.FakeBackup("test1")
   241  		backup2.Namespace = "backup"
   242  		tf.FakeDynamicClient = testing.FakeDynamicClient(backup1, backup2)
   243  		Expect(PrintBackupList(o)).Should(Succeed())
   244  		Expect(o.Out.(*bytes.Buffer).String()).Should(ContainSubstring("test1"))
   245  		Expect(o.Out.(*bytes.Buffer).String()).Should(ContainSubstring("apecloud-mysql"))
   246  
   247  		By("test list all namespace")
   248  		o.Out.(*bytes.Buffer).Reset()
   249  		o.AllNamespaces = true
   250  		Expect(PrintBackupList(o)).Should(Succeed())
   251  		Expect(len(strings.Split(strings.Trim(o.Out.(*bytes.Buffer).String(), "\n"), "\n"))).Should(Equal(3))
   252  	})
   253  
   254  	It("restore", func() {
   255  		timestamp := time.Now().Format("20060102150405")
   256  		backupName := "backup-test-" + timestamp
   257  		clusterName := "source-cluster-" + timestamp
   258  		newClusterName := "new-cluster-" + timestamp
   259  		secrets := testing.FakeSecrets(testing.Namespace, clusterName)
   260  		clusterDef := testing.FakeClusterDef()
   261  		clusterObj := testing.FakeCluster(clusterName, testing.Namespace)
   262  		clusterDefLabel := map[string]string{
   263  			constant.ClusterDefLabelKey: clusterDef.Name,
   264  		}
   265  		clusterObj.SetLabels(clusterDefLabel)
   266  		backupPolicy := testing.FakeBackupPolicy("backPolicy", clusterObj.Name)
   267  
   268  		pods := testing.FakePods(1, testing.Namespace, clusterName)
   269  		tf.FakeDynamicClient = testing.FakeDynamicClient(&secrets.Items[0],
   270  			&pods.Items[0], clusterDef, clusterObj, backupPolicy)
   271  		tf.Client = &clientfake.RESTClient{}
   272  		// create backup
   273  		cmd := NewCreateBackupCmd(tf, streams)
   274  		Expect(cmd).ShouldNot(BeNil())
   275  		_ = cmd.Flags().Set("method", testing.BackupMethodName)
   276  		_ = cmd.Flags().Set("name", backupName)
   277  		cmd.Run(nil, []string{clusterName})
   278  
   279  		By("restore new cluster from source cluster which is not deleted")
   280  		// mock backup is ok
   281  		mockBackupInfo(tf.FakeDynamicClient, backupName, clusterName, nil, "")
   282  		cmdRestore := NewCreateRestoreCmd(tf, streams)
   283  		Expect(cmdRestore != nil).To(BeTrue())
   284  		_ = cmdRestore.Flags().Set("backup", backupName)
   285  		cmdRestore.Run(nil, []string{newClusterName})
   286  		newClusterObj := &appsv1alpha1.Cluster{}
   287  		Expect(cluster.GetK8SClientObject(tf.FakeDynamicClient, newClusterObj, types.ClusterGVR(), testing.Namespace, newClusterName)).Should(Succeed())
   288  		Expect(clusterObj.Spec.ComponentSpecs[0].Replicas).Should(Equal(int32(1)))
   289  		// check if cluster contains the annotation for restoring
   290  		Expect(newClusterObj.Annotations[constant.RestoreFromBackupAnnotationKey]).Should(ContainSubstring(constant.ConnectionPassword))
   291  		Expect(newClusterObj.Annotations[constant.RestoreFromBackupAnnotationKey]).Should(ContainSubstring(constant.BackupNamespaceKeyForRestore))
   292  		By("restore new cluster from source cluster which is deleted")
   293  		// mock cluster is not lived in kubernetes
   294  		mockBackupInfo(tf.FakeDynamicClient, backupName, "deleted-cluster", nil, "")
   295  		cmdRestore.Run(nil, []string{newClusterName + "1"})
   296  
   297  		By("run restore cmd with cluster spec.affinity=nil")
   298  		patchCluster := []byte(`{"spec":{"affinity":null}}`)
   299  		_, _ = tf.FakeDynamicClient.Resource(types.ClusterGVR()).Namespace(testing.Namespace).Patch(context.TODO(), clusterName,
   300  			k8sapitypes.MergePatchType, patchCluster, metav1.PatchOptions{})
   301  		cmdRestore.Run(nil, []string{newClusterName + "-with-nil-affinity"})
   302  	})
   303  
   304  	// It("restore-to-time", func() {
   305  	//	timestamp := time.Now().Format("20060102150405")
   306  	//	backupName := "backup-test-" + timestamp
   307  	//	backupName1 := backupName + "1"
   308  	//	clusterName := "source-cluster-" + timestamp
   309  	//	secrets := testing.FakeSecrets(testing.Namespace, clusterName)
   310  	//	clusterDef := testing.FakeClusterDef()
   311  	//	cluster := testing.FakeCluster(clusterName, testing.Namespace)
   312  	//	clusterDefLabel := map[string]string{
   313  	//		constant.ClusterDefLabelKey: clusterDef.Name,
   314  	//	}
   315  	//	cluster.SetLabels(clusterDefLabel)
   316  	//	backupPolicy := testing.FakeBackupPolicy("backPolicy", cluster.Name)
   317  	//	backupTypeMeta := testing.FakeBackup("backup-none").TypeMeta
   318  	//	backupLabels := map[string]string{
   319  	//		constant.AppInstanceLabelKey:             clusterName,
   320  	//		constant.KBAppComponentLabelKey:          "test",
   321  	//		dptypes.DataProtectionLabelClusterUIDKey: string(cluster.UID),
   322  	//	}
   323  	//	now := metav1.Now()
   324  	//	baseBackup := testapps.NewBackupFactory(testing.Namespace, "backup-base").
   325  	//		SetBackupMethod(dpv1alpha1.BackupTypeSnapshot).
   326  	//		SetBackupTimeRange(now.Add(-time.Minute), now.Add(-time.Second)).
   327  	//		SetLabels(backupLabels).GetObject()
   328  	//	baseBackup.TypeMeta = backupTypeMeta
   329  	//	baseBackup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
   330  	//	logfileBackup := testapps.NewBackupFactory(testing.Namespace, backupName).
   331  	//		SetBackupMethod(dpv1alpha1.BackupTypeLogFile).
   332  	//		SetBackupTimeRange(now.Add(-time.Minute), now.Add(time.Minute)).
   333  	//		SetLabels(backupLabels).GetObject()
   334  	//	logfileBackup.TypeMeta = backupTypeMeta
   335  	//
   336  	//	logfileBackup1 := testapps.NewBackupFactory(testing.Namespace, backupName1).
   337  	//		SetBackupMethod(dpv1alpha1.BackupTypeLogFile).
   338  	//		SetBackupTimeRange(now.Add(-time.Minute), now.Add(2*time.Minute)).GetObject()
   339  	//	uid := string(cluster.UID)
   340  	//	logfileBackup1.Labels = map[string]string{
   341  	//		constant.AppInstanceLabelKey:              clusterName,
   342  	//		constant.KBAppComponentLabelKey:           "test",
   343  	//		constant.DataProtectionLabelClusterUIDKey: uid[:30] + "00",
   344  	//	}
   345  	//	logfileBackup1.TypeMeta = backupTypeMeta
   346  	//
   347  	//	pods := testing.FakePods(1, testing.Namespace, clusterName)
   348  	//	tf.FakeDynamicClient = fake.NewSimpleDynamicClient(
   349  	//		scheme.Scheme, &secrets.Items[0], &pods.Items[0], cluster, backupPolicy, baseBackup, logfileBackup, logfileBackup1)
   350  	//	tf.Client = &clientfake.RESTClient{}
   351  	//
   352  	//	By("restore new cluster from source cluster which is not deleted")
   353  	//	cmdRestore := NewCreateRestoreCmd(tf, streams)
   354  	//	Expect(cmdRestore != nil).To(BeTrue())
   355  	//	_ = cmdRestore.Flags().Set("restore-to-time", util.TimeFormatWithDuration(&now, time.Second))
   356  	//	_ = cmdRestore.Flags().Set("source-cluster", clusterName)
   357  	//	cmdRestore.Run(nil, []string{})
   358  	//
   359  	//	// test with RFC3339 format
   360  	//	_ = cmdRestore.Flags().Set("restore-to-time", now.Format(time.RFC3339))
   361  	//	_ = cmdRestore.Flags().Set("source-cluster", clusterName)
   362  	//	cmdRestore.Run(nil, []string{"new-cluster"})
   363  	//
   364  	//	By("restore should be failed when backups belong to different source clusters")
   365  	//	o := &CreateRestoreOptions{CreateOptions: create.CreateOptions{
   366  	//		IOStreams: streams,
   367  	//		Factory:   tf,
   368  	//	}}
   369  	//	restoreTime := time.Now().Add(90 * time.Second)
   370  	//	o.RestoreTimeStr = util.TimeFormatWithDuration(&metav1.Time{Time: restoreTime}, time.Second)
   371  	//	o.SourceCluster = clusterName
   372  	//	Expect(o.Complete()).Should(Succeed())
   373  	//	Expect(o.validateRestoreTime().Error()).Should(ContainSubstring("restore-to-time is out of time range"))
   374  	// })
   375  
   376  	It("describe-backup", func() {
   377  		cmd := NewDescribeBackupCmd(tf, streams)
   378  		Expect(cmd).ShouldNot(BeNil())
   379  		By("test describe-backup cmd with no backup")
   380  		tf.FakeDynamicClient = testing.FakeDynamicClient()
   381  		o := DescribeBackupOptions{
   382  			Factory:   tf,
   383  			IOStreams: streams,
   384  			Gvr:       types.BackupGVR(),
   385  		}
   386  		args := []string{}
   387  		Expect(o.Complete(args)).Should(HaveOccurred())
   388  
   389  		By("test describe-backup")
   390  		backupName := "test1"
   391  		backup1 := testing.FakeBackup(backupName)
   392  		args = append(args, backupName)
   393  		backup1.Status.Phase = dpv1alpha1.BackupPhaseCompleted
   394  		logNow := metav1.Now()
   395  		backup1.Status.StartTimestamp = &logNow
   396  		backup1.Status.CompletionTimestamp = &logNow
   397  		backup1.Status.Expiration = &logNow
   398  		backup1.Status.Duration = &metav1.Duration{Duration: logNow.Sub(logNow.Time)}
   399  		tf.FakeDynamicClient = testing.FakeDynamicClient(backup1)
   400  		Expect(o.Complete(args)).Should(Succeed())
   401  		o.client = testing.FakeClientSet()
   402  		Expect(o.Run()).Should(Succeed())
   403  	})
   404  
   405  	It("describe-backup-policy", func() {
   406  		cmd := NewDescribeBackupPolicyCmd(tf, streams)
   407  		Expect(cmd).ShouldNot(BeNil())
   408  		By("test describe-backup-policy cmd with cluster and backupPolicy")
   409  		tf.FakeDynamicClient = testing.FakeDynamicClient()
   410  		o := DescribeBackupPolicyOptions{
   411  			Factory:   tf,
   412  			IOStreams: streams,
   413  		}
   414  		Expect(o.Complete()).Should(Succeed())
   415  		Expect(o.Validate()).Should(HaveOccurred())
   416  
   417  		By("test describe-backup-policy with cluster")
   418  		policyName := "test1"
   419  		policy1 := testing.FakeBackupPolicy(policyName, testing.ClusterName)
   420  		tf.FakeDynamicClient = testing.FakeDynamicClient(policy1)
   421  		o.client = testing.FakeClientSet()
   422  		o.ClusterNames = []string{testing.ClusterName}
   423  		Expect(o.Complete()).Should(Succeed())
   424  		Expect(o.Validate()).Should(Succeed())
   425  		Expect(o.Run()).Should(Succeed())
   426  
   427  		By("test describe-backup-policy with backupPolicy")
   428  		o = DescribeBackupPolicyOptions{
   429  			Factory:   tf,
   430  			IOStreams: streams,
   431  		}
   432  		o.Names = []string{policyName}
   433  		o.client = testing.FakeClientSet()
   434  		Expect(o.Complete()).Should(Succeed())
   435  		Expect(o.Validate()).Should(Succeed())
   436  		Expect(o.Run()).Should(Succeed())
   437  	})
   438  
   439  })
   440  
   441  func mockBackupInfo(dynamic dynamic.Interface, backupName, clusterName string, timeRange map[string]any, backupMethod string) {
   442  	clusterString := fmt.Sprintf(`{"metadata":{"name":"deleted-cluster","namespace":"%s"},"spec":{"clusterDefinitionRef":"apecloud-mysql","clusterVersionRef":"ac-mysql-8.0.30","componentSpecs":[{"name":"mysql","componentDefRef":"mysql","replicas":1}]}}`, testing.Namespace)
   443  	backupStatus := &unstructured.Unstructured{
   444  		Object: map[string]any{
   445  			"status": map[string]any{
   446  				"phase":     "Completed",
   447  				"timeRange": timeRange,
   448  			},
   449  			"metadata": map[string]any{
   450  				"name": backupName,
   451  				"annotations": map[string]any{
   452  					constant.ClusterSnapshotAnnotationKey: clusterString,
   453  					dptypes.ConnectionPasswordKey:         "test-password",
   454  				},
   455  				"labels": map[string]any{
   456  					constant.AppInstanceLabelKey:    clusterName,
   457  					constant.KBAppComponentLabelKey: "test",
   458  				},
   459  			},
   460  			"spec": map[string]any{
   461  				"backupMethod": backupMethod,
   462  			},
   463  		},
   464  	}
   465  	_, err := dynamic.Resource(types.BackupGVR()).Namespace(testing.Namespace).UpdateStatus(context.TODO(),
   466  		backupStatus, metav1.UpdateOptions{})
   467  	Expect(err).Should(Succeed())
   468  }