github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/component_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 apps
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  
    35  	snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
    36  	"golang.org/x/exp/slices"
    37  	appsv1 "k8s.io/api/apps/v1"
    38  	corev1 "k8s.io/api/core/v1"
    39  	storagev1 "k8s.io/api/storage/v1"
    40  	"k8s.io/apimachinery/pkg/api/resource"
    41  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    42  	"k8s.io/apimachinery/pkg/types"
    43  	"k8s.io/apimachinery/pkg/util/rand"
    44  	"k8s.io/client-go/kubernetes/scheme"
    45  	controllerruntime "sigs.k8s.io/controller-runtime"
    46  	"sigs.k8s.io/controller-runtime/pkg/client"
    47  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    48  
    49  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    50  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    51  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    52  	"github.com/1aal/kubeblocks/controllers/apps/components"
    53  	"github.com/1aal/kubeblocks/pkg/common"
    54  	"github.com/1aal/kubeblocks/pkg/constant"
    55  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    56  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    57  	"github.com/1aal/kubeblocks/pkg/generics"
    58  	lorry "github.com/1aal/kubeblocks/pkg/lorry/client"
    59  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    60  	testdp "github.com/1aal/kubeblocks/pkg/testutil/dataprotection"
    61  	testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    62  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    63  )
    64  
    65  const (
    66  	backupPolicyTPLName = "test-backup-policy-template-mysql"
    67  	backupMethodName    = "test-backup-method"
    68  	vsBackupMethodName  = "test-vs-backup-method"
    69  	actionSetName       = "test-action-set"
    70  	vsActionSetName     = "test-vs-action-set"
    71  )
    72  
    73  var (
    74  	podAnnotationKey4Test = fmt.Sprintf("%s-test", constant.ComponentReplicasAnnotationKey)
    75  )
    76  
    77  var newMockLorryClient = func(clusterKey types.NamespacedName, compName string, replicas int) {
    78  	ctrl := gomock.NewController(GinkgoT())
    79  	mockLorryClient := lorry.NewMockClient(ctrl)
    80  	lorry.SetMockClient(mockLorryClient, nil)
    81  	mockLorryClient.EXPECT().JoinMember(gomock.Any()).Return(nil).AnyTimes()
    82  	mockLorryClient.EXPECT().LeaveMember(gomock.Any()).DoAndReturn(func(ctx context.Context) error {
    83  		var podList corev1.PodList
    84  		labels := client.MatchingLabels{
    85  			constant.AppInstanceLabelKey:    clusterKey.Name,
    86  			constant.KBAppComponentLabelKey: compName,
    87  		}
    88  		if err := testCtx.Cli.List(ctx, &podList, labels, client.InNamespace(clusterKey.Namespace)); err != nil {
    89  			return err
    90  		}
    91  		for _, pod := range podList.Items {
    92  			if pod.Annotations == nil {
    93  				panic(fmt.Sprintf("pod annotaions is nil: %s", pod.Name))
    94  			}
    95  			if pod.Annotations[podAnnotationKey4Test] == fmt.Sprintf("%d", replicas) {
    96  				continue
    97  			}
    98  			pod.Annotations[podAnnotationKey4Test] = fmt.Sprintf("%d", replicas)
    99  			if err := testCtx.Cli.Update(ctx, &pod); err != nil {
   100  				return err
   101  			}
   102  		}
   103  		return nil
   104  	}).AnyTimes()
   105  }
   106  
   107  var _ = Describe("Cluster Controller", func() {
   108  	const (
   109  		clusterDefName     = "test-clusterdef"
   110  		clusterVersionName = "test-clusterversion"
   111  		clusterName        = "test-cluster" // this become cluster prefix name if used with testapps.NewClusterFactory().WithRandomName()
   112  		leader             = "leader"
   113  		follower           = "follower"
   114  		// REVIEW:
   115  		// - setup componentName and componentDefName as map entry pair
   116  		statelessCompName      = "stateless"
   117  		statelessCompDefName   = "stateless"
   118  		statefulCompName       = "stateful"
   119  		statefulCompDefName    = "stateful"
   120  		consensusCompName      = "consensus"
   121  		consensusCompDefName   = "consensus"
   122  		replicationCompName    = "replication"
   123  		replicationCompDefName = "replication"
   124  		actionSetName          = "test-actionset"
   125  	)
   126  
   127  	var (
   128  		clusterDefObj     *appsv1alpha1.ClusterDefinition
   129  		clusterVersionObj *appsv1alpha1.ClusterVersion
   130  		clusterObj        *appsv1alpha1.Cluster
   131  		clusterKey        types.NamespacedName
   132  		allSettings       map[string]interface{}
   133  	)
   134  
   135  	resetViperCfg := func() {
   136  		if allSettings != nil {
   137  			Expect(viper.MergeConfigMap(allSettings)).ShouldNot(HaveOccurred())
   138  			allSettings = nil
   139  		}
   140  	}
   141  
   142  	resetTestContext := func() {
   143  		clusterDefObj = nil
   144  		clusterVersionObj = nil
   145  		clusterObj = nil
   146  		resetViperCfg()
   147  	}
   148  
   149  	// Cleanups
   150  	cleanEnv := func() {
   151  		// must wait till resources deleted and no longer existed before the testcases start,
   152  		// otherwise if later it needs to create some new resource objects with the same name,
   153  		// in race conditions, it will find the existence of old objects, resulting failure to
   154  		// create the new objects.
   155  		By("clean resources")
   156  
   157  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
   158  		testapps.ClearClusterResourcesWithRemoveFinalizerOption(&testCtx)
   159  
   160  		// delete rest mocked objects
   161  		inNS := client.InNamespace(testCtx.DefaultNamespace)
   162  		ml := client.HasLabels{testCtx.TestObjLabelKey}
   163  		// namespaced
   164  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS, ml)
   165  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PodSignature, true, inNS, ml)
   166  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS, ml)
   167  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS, ml)
   168  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.VolumeSnapshotSignature, true, inNS)
   169  		// non-namespaced
   170  		testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml)
   171  		testapps.ClearResources(&testCtx, generics.ActionSetSignature, ml)
   172  		testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
   173  		resetTestContext()
   174  	}
   175  
   176  	BeforeEach(func() {
   177  		cleanEnv()
   178  		allSettings = viper.AllSettings()
   179  	})
   180  
   181  	AfterEach(func() {
   182  		cleanEnv()
   183  	})
   184  
   185  	// test function helpers
   186  	createAllWorkloadTypesClusterDef := func(noCreateAssociateCV ...bool) {
   187  		By("Create a clusterDefinition obj")
   188  		clusterDefObj = testapps.NewClusterDefFactory(clusterDefName).
   189  			AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName).
   190  			AddComponentDef(testapps.ConsensusMySQLComponent, consensusCompDefName).
   191  			AddComponentDef(testapps.ReplicationRedisComponent, replicationCompDefName).
   192  			AddComponentDef(testapps.StatelessNginxComponent, statelessCompDefName).
   193  			Create(&testCtx).GetObject()
   194  
   195  		if len(noCreateAssociateCV) > 0 && noCreateAssociateCV[0] {
   196  			return
   197  		}
   198  		By("Create a clusterVersion obj")
   199  		clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()).
   200  			AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   201  			AddComponentVersion(consensusCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   202  			AddComponentVersion(replicationCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   203  			AddComponentVersion(statelessCompDefName).AddContainerShort("nginx", testapps.NginxImage).
   204  			Create(&testCtx).GetObject()
   205  	}
   206  
   207  	waitForCreatingResourceCompletely := func(clusterKey client.ObjectKey, compNames ...string) {
   208  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   209  		cluster := &appsv1alpha1.Cluster{}
   210  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, cluster, true)).Should(Succeed())
   211  		for _, compName := range compNames {
   212  			compPhase := appsv1alpha1.CreatingClusterCompPhase
   213  			for _, spec := range cluster.Spec.ComponentSpecs {
   214  				if spec.Name == compName && spec.Replicas == 0 {
   215  					compPhase = appsv1alpha1.StoppedClusterCompPhase
   216  				}
   217  			}
   218  			Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(compPhase))
   219  		}
   220  	}
   221  
   222  	createClusterObj := func(compName, compDefName string) {
   223  		By("Creating a cluster")
   224  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   225  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   226  			AddComponent(compName, compDefName).
   227  			SetReplicas(1).
   228  			Create(&testCtx).GetObject()
   229  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   230  
   231  		By("Waiting for the cluster enter running phase")
   232  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   233  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   234  	}
   235  
   236  	changeCompReplicas := func(clusterName types.NamespacedName, replicas int32, comp *appsv1alpha1.ClusterComponentSpec) {
   237  		Expect(testapps.GetAndChangeObj(&testCtx, clusterName, func(cluster *appsv1alpha1.Cluster) {
   238  			for i, clusterComp := range cluster.Spec.ComponentSpecs {
   239  				if clusterComp.Name == comp.Name {
   240  					cluster.Spec.ComponentSpecs[i].Replicas = replicas
   241  				}
   242  			}
   243  		})()).ShouldNot(HaveOccurred())
   244  	}
   245  
   246  	changeComponentReplicas := func(clusterName types.NamespacedName, replicas int32) {
   247  		Expect(testapps.GetAndChangeObj(&testCtx, clusterName, func(cluster *appsv1alpha1.Cluster) {
   248  			Expect(cluster.Spec.ComponentSpecs).Should(HaveLen(1))
   249  			cluster.Spec.ComponentSpecs[0].Replicas = replicas
   250  		})()).ShouldNot(HaveOccurred())
   251  	}
   252  
   253  	getPodSpec := func(sts *appsv1.StatefulSet, deploy *appsv1.Deployment) *corev1.PodSpec {
   254  		if sts != nil {
   255  			return &sts.Spec.Template.Spec
   256  		} else if deploy != nil {
   257  			return &deploy.Spec.Template.Spec
   258  		}
   259  		panic("unreachable")
   260  	}
   261  
   262  	checkSingleWorkload := func(compDefName string, expects func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment)) {
   263  		Eventually(func(g Gomega) {
   264  			l := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   265  			sts := components.ConvertRSMToSTS(&l.Items[0])
   266  			expects(g, sts, nil)
   267  		}).Should(Succeed())
   268  	}
   269  
   270  	testChangeReplicas := func(compName, compDefName string) {
   271  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
   272  		createClusterObj(compName, compDefName)
   273  		replicasSeq := []int32{5, 3, 1, 0, 2, 4}
   274  		expectedOG := int64(1)
   275  		for _, replicas := range replicasSeq {
   276  			By(fmt.Sprintf("Change replicas to %d", replicas))
   277  			changeComponentReplicas(clusterKey, replicas)
   278  			expectedOG++
   279  			By("Checking cluster status and the number of replicas changed")
   280  			Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, fetched *appsv1alpha1.Cluster) {
   281  				g.Expect(fetched.Status.ObservedGeneration).To(BeEquivalentTo(expectedOG))
   282  				g.Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(BeElementOf(appsv1alpha1.CreatingClusterPhase, appsv1alpha1.UpdatingClusterPhase))
   283  			})).Should(Succeed())
   284  
   285  			checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
   286  				if sts != nil {
   287  					g.Expect(int(*sts.Spec.Replicas)).To(BeEquivalentTo(replicas))
   288  				} else {
   289  					g.Expect(int(*deploy.Spec.Replicas)).To(BeEquivalentTo(replicas))
   290  				}
   291  			})
   292  		}
   293  	}
   294  
   295  	getPVCName := func(vctName, compName string, i int) string {
   296  		return fmt.Sprintf("%s-%s-%s-%d", vctName, clusterKey.Name, compName, i)
   297  	}
   298  
   299  	createPVC := func(clusterName, pvcName, compName, storageSize, storageClassName string) {
   300  		if storageSize == "" {
   301  			storageSize = "1Gi"
   302  		}
   303  		clusterBytes, _ := json.Marshal(clusterObj)
   304  		testapps.NewPersistentVolumeClaimFactory(testCtx.DefaultNamespace, pvcName, clusterName,
   305  			compName, testapps.DataVolumeName).
   306  			AddLabelsInMap(map[string]string{
   307  				constant.AppInstanceLabelKey:    clusterName,
   308  				constant.KBAppComponentLabelKey: compName,
   309  				constant.AppManagedByLabelKey:   constant.AppName,
   310  			}).AddAnnotations(constant.LastAppliedClusterAnnotationKey, string(clusterBytes)).
   311  			SetStorage(storageSize).
   312  			SetStorageClass(storageClassName).
   313  			CheckedCreate(&testCtx)
   314  	}
   315  
   316  	mockComponentPVCsAndBound := func(comp *appsv1alpha1.ClusterComponentSpec, replicas int, create bool, storageClassName string) {
   317  		for i := 0; i < replicas; i++ {
   318  			for _, vct := range comp.VolumeClaimTemplates {
   319  				pvcKey := types.NamespacedName{
   320  					Namespace: clusterKey.Namespace,
   321  					Name:      getPVCName(vct.Name, comp.Name, i),
   322  				}
   323  				if create {
   324  					createPVC(clusterKey.Name, pvcKey.Name, comp.Name, vct.Spec.Resources.Requests.Storage().String(), storageClassName)
   325  				}
   326  				Eventually(testapps.CheckObjExists(&testCtx, pvcKey,
   327  					&corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
   328  				Eventually(testapps.GetAndChangeObjStatus(&testCtx, pvcKey, func(pvc *corev1.PersistentVolumeClaim) {
   329  					pvc.Status.Phase = corev1.ClaimBound
   330  					if pvc.Status.Capacity == nil {
   331  						pvc.Status.Capacity = corev1.ResourceList{}
   332  					}
   333  					pvc.Status.Capacity[corev1.ResourceStorage] = pvc.Spec.Resources.Requests[corev1.ResourceStorage]
   334  				})).Should(Succeed())
   335  			}
   336  		}
   337  	}
   338  
   339  	mockPodsForTest := func(cluster *appsv1alpha1.Cluster, number int) []corev1.Pod {
   340  		clusterDefName := cluster.Spec.ClusterDefRef
   341  		componentName := cluster.Spec.ComponentSpecs[0].Name
   342  		clusterName := cluster.Name
   343  		stsName := cluster.Name + "-" + componentName
   344  		pods := make([]corev1.Pod, 0)
   345  		for i := 0; i < number; i++ {
   346  			pod := &corev1.Pod{
   347  				ObjectMeta: metav1.ObjectMeta{
   348  					Name:      stsName + "-" + strconv.Itoa(i),
   349  					Namespace: testCtx.DefaultNamespace,
   350  					Labels: map[string]string{
   351  						constant.AppManagedByLabelKey:         constant.AppName,
   352  						constant.AppNameLabelKey:              clusterDefName,
   353  						constant.AppInstanceLabelKey:          clusterName,
   354  						constant.KBAppComponentLabelKey:       componentName,
   355  						appsv1.ControllerRevisionHashLabelKey: "mock-version",
   356  					},
   357  					Annotations: map[string]string{
   358  						podAnnotationKey4Test: fmt.Sprintf("%d", number),
   359  					},
   360  				},
   361  				Spec: corev1.PodSpec{
   362  					Containers: []corev1.Container{{
   363  						Name:  "mock-container",
   364  						Image: "mock-container",
   365  					}},
   366  				},
   367  			}
   368  			pods = append(pods, *pod)
   369  		}
   370  		return pods
   371  	}
   372  
   373  	horizontalScaleComp := func(updatedReplicas int, comp *appsv1alpha1.ClusterComponentSpec,
   374  		storageClassName string, policy *appsv1alpha1.HorizontalScalePolicy) {
   375  		By("Mocking component PVCs to bound")
   376  		mockComponentPVCsAndBound(comp, int(comp.Replicas), true, storageClassName)
   377  
   378  		By("Checking rsm replicas right")
   379  		rsmList := testk8s.ListAndCheckRSMWithComponent(&testCtx, clusterKey, comp.Name)
   380  		Expect(int(*rsmList.Items[0].Spec.Replicas)).To(BeEquivalentTo(comp.Replicas))
   381  
   382  		By("Creating mock pods in StatefulSet")
   383  		pods := mockPodsForTest(clusterObj, int(comp.Replicas))
   384  		for i, pod := range pods {
   385  			if comp.ComponentDefRef == replicationCompDefName && i == 0 {
   386  				By("mocking primary for replication to pass check")
   387  				pods[0].ObjectMeta.Labels[constant.RoleLabelKey] = "primary"
   388  			}
   389  			Expect(testCtx.CheckedCreateObj(testCtx.Ctx, &pod)).Should(Succeed())
   390  			// mock the status to pass the isReady(pod) check in consensus_set
   391  			pod.Status.Conditions = []corev1.PodCondition{{
   392  				Type:   corev1.PodReady,
   393  				Status: corev1.ConditionTrue,
   394  			}}
   395  			Expect(k8sClient.Status().Update(ctx, &pod)).Should(Succeed())
   396  		}
   397  
   398  		By(fmt.Sprintf("Changing replicas to %d", updatedReplicas))
   399  		changeCompReplicas(clusterKey, int32(updatedReplicas), comp)
   400  
   401  		checkUpdatedStsReplicas := func() {
   402  			By("Checking updated sts replicas")
   403  			Eventually(func() int32 {
   404  				rsmList := testk8s.ListAndCheckRSMWithComponent(&testCtx, clusterKey, comp.Name)
   405  				return *rsmList.Items[0].Spec.Replicas
   406  			}).Should(BeEquivalentTo(updatedReplicas))
   407  		}
   408  
   409  		scaleOutCheck := func() {
   410  			if comp.Replicas == 0 {
   411  				return
   412  			}
   413  
   414  			ml := client.MatchingLabels{
   415  				constant.AppInstanceLabelKey:    clusterKey.Name,
   416  				constant.KBAppComponentLabelKey: comp.Name,
   417  				constant.KBManagedByKey:         "cluster",
   418  			}
   419  			if policy != nil {
   420  				By(fmt.Sprintf("Checking backup of component %s created", comp.Name))
   421  				Eventually(testapps.List(&testCtx, generics.BackupSignature,
   422  					ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(1))
   423  
   424  				backupKey := types.NamespacedName{Name: fmt.Sprintf("%s-%s-scaling",
   425  					clusterKey.Name, comp.Name),
   426  					Namespace: testCtx.DefaultNamespace}
   427  				By("Mocking backup status to completed")
   428  				Expect(testapps.GetAndChangeObjStatus(&testCtx, backupKey, func(backup *dpv1alpha1.Backup) {
   429  					backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
   430  					backup.Status.PersistentVolumeClaimName = "backup-data"
   431  					testdp.MockBackupStatusMethod(backup, testdp.BackupMethodName, testapps.DataVolumeName, testdp.ActionSetName)
   432  				})()).Should(Succeed())
   433  
   434  				if testk8s.IsMockVolumeSnapshotEnabled(&testCtx, storageClassName) {
   435  					By("Mocking VolumeSnapshot and set it as ReadyToUse")
   436  					pvcName := getPVCName(testapps.DataVolumeName, comp.Name, 0)
   437  					volumeSnapshot := &snapshotv1.VolumeSnapshot{
   438  						ObjectMeta: metav1.ObjectMeta{
   439  							Name:      backupKey.Name,
   440  							Namespace: backupKey.Namespace,
   441  							Labels: map[string]string{
   442  								dptypes.BackupNameLabelKey: backupKey.Name,
   443  							}},
   444  						Spec: snapshotv1.VolumeSnapshotSpec{
   445  							Source: snapshotv1.VolumeSnapshotSource{
   446  								PersistentVolumeClaimName: &pvcName,
   447  							},
   448  						},
   449  					}
   450  					scheme, _ := appsv1alpha1.SchemeBuilder.Build()
   451  					Expect(controllerruntime.SetControllerReference(clusterObj, volumeSnapshot, scheme)).Should(Succeed())
   452  					Expect(testCtx.CreateObj(testCtx.Ctx, volumeSnapshot)).Should(Succeed())
   453  					readyToUse := true
   454  					volumeSnapshotStatus := snapshotv1.VolumeSnapshotStatus{ReadyToUse: &readyToUse}
   455  					volumeSnapshot.Status = &volumeSnapshotStatus
   456  					Expect(k8sClient.Status().Update(testCtx.Ctx, volumeSnapshot)).Should(Succeed())
   457  				}
   458  			}
   459  
   460  			By("Mock PVCs and set status to bound")
   461  			mockComponentPVCsAndBound(comp, updatedReplicas, true, storageClassName)
   462  
   463  			if policy != nil {
   464  				checkRestoreAndSetCompleted(clusterKey, comp.Name, updatedReplicas-int(comp.Replicas))
   465  			}
   466  
   467  			if policy != nil {
   468  				By("Checking Backup and Restore cleanup")
   469  				Eventually(testapps.List(&testCtx, generics.BackupSignature, ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(0))
   470  				Eventually(testapps.List(&testCtx, generics.RestoreSignature, ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(0))
   471  			}
   472  
   473  			checkUpdatedStsReplicas()
   474  
   475  			By("Checking updated sts replicas' PVC and size")
   476  			for _, vct := range comp.VolumeClaimTemplates {
   477  				var volumeQuantity resource.Quantity
   478  				for i := 0; i < updatedReplicas; i++ {
   479  					pvcKey := types.NamespacedName{
   480  						Namespace: clusterKey.Namespace,
   481  						Name:      getPVCName(vct.Name, comp.Name, i),
   482  					}
   483  					Eventually(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
   484  						if volumeQuantity.IsZero() {
   485  							volumeQuantity = pvc.Spec.Resources.Requests[corev1.ResourceStorage]
   486  						}
   487  						Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(volumeQuantity))
   488  						Expect(pvc.Status.Capacity[corev1.ResourceStorage]).To(Equal(volumeQuantity))
   489  					})).Should(Succeed())
   490  				}
   491  			}
   492  		}
   493  
   494  		scaleInCheck := func() {
   495  			if updatedReplicas == 0 {
   496  				Consistently(func(g Gomega) {
   497  					pvcList := corev1.PersistentVolumeClaimList{}
   498  					g.Expect(testCtx.Cli.List(testCtx.Ctx, &pvcList, client.MatchingLabels{
   499  						constant.AppInstanceLabelKey:    clusterKey.Name,
   500  						constant.KBAppComponentLabelKey: comp.Name,
   501  					})).Should(Succeed())
   502  					for _, pvc := range pvcList.Items {
   503  						ss := strings.Split(pvc.Name, "-")
   504  						idx, _ := strconv.Atoi(ss[len(ss)-1])
   505  						if idx >= updatedReplicas && idx < int(comp.Replicas) {
   506  							g.Expect(pvc.DeletionTimestamp).Should(BeNil())
   507  						}
   508  					}
   509  				}).Should(Succeed())
   510  				return
   511  			}
   512  
   513  			checkUpdatedStsReplicas()
   514  
   515  			By("Checking pvcs deleting")
   516  			Eventually(func(g Gomega) {
   517  				pvcList := corev1.PersistentVolumeClaimList{}
   518  				g.Expect(testCtx.Cli.List(testCtx.Ctx, &pvcList, client.MatchingLabels{
   519  					constant.AppInstanceLabelKey:    clusterKey.Name,
   520  					constant.KBAppComponentLabelKey: comp.Name,
   521  				})).Should(Succeed())
   522  				for _, pvc := range pvcList.Items {
   523  					ss := strings.Split(pvc.Name, "-")
   524  					idx, _ := strconv.Atoi(ss[len(ss)-1])
   525  					if idx >= updatedReplicas && idx < int(comp.Replicas) {
   526  						g.Expect(pvc.DeletionTimestamp).ShouldNot(BeNil())
   527  					}
   528  				}
   529  			}).Should(Succeed())
   530  
   531  			By("Checking pod's annotation should be updated consistently")
   532  			Eventually(func(g Gomega) {
   533  				podList := corev1.PodList{}
   534  				g.Expect(testCtx.Cli.List(testCtx.Ctx, &podList, client.MatchingLabels{
   535  					constant.AppInstanceLabelKey:    clusterKey.Name,
   536  					constant.KBAppComponentLabelKey: comp.Name,
   537  				})).Should(Succeed())
   538  				for _, pod := range podList.Items {
   539  					ss := strings.Split(pod.Name, "-")
   540  					ordinal, _ := strconv.Atoi(ss[len(ss)-1])
   541  					if ordinal >= updatedReplicas {
   542  						continue
   543  					}
   544  					g.Expect(pod.Annotations[podAnnotationKey4Test]).Should(Equal(fmt.Sprintf("%d", updatedReplicas)))
   545  				}
   546  			}).Should(Succeed())
   547  		}
   548  
   549  		if int(comp.Replicas) < updatedReplicas {
   550  			scaleOutCheck()
   551  		}
   552  		if int(comp.Replicas) > updatedReplicas {
   553  			scaleInCheck()
   554  		}
   555  	}
   556  
   557  	setHorizontalScalePolicy := func(policyType appsv1alpha1.HScaleDataClonePolicyType, componentDefsWithHScalePolicy ...string) {
   558  		By(fmt.Sprintf("Set HorizontalScalePolicy, policyType is %s", policyType))
   559  		Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
   560  			func(clusterDef *appsv1alpha1.ClusterDefinition) {
   561  				// assign 1st component
   562  				if len(componentDefsWithHScalePolicy) == 0 && len(clusterDef.Spec.ComponentDefs) > 0 {
   563  					componentDefsWithHScalePolicy = []string{
   564  						clusterDef.Spec.ComponentDefs[0].Name,
   565  					}
   566  				}
   567  				for i, compDef := range clusterDef.Spec.ComponentDefs {
   568  					if !slices.Contains(componentDefsWithHScalePolicy, compDef.Name) {
   569  						continue
   570  					}
   571  
   572  					if len(policyType) == 0 {
   573  						clusterDef.Spec.ComponentDefs[i].HorizontalScalePolicy = nil
   574  						continue
   575  					}
   576  
   577  					By("Checking backup policy created from backup policy template")
   578  					policyName := generateBackupPolicyName(clusterKey.Name, compDef.Name, "")
   579  					clusterDef.Spec.ComponentDefs[i].HorizontalScalePolicy = &appsv1alpha1.HorizontalScalePolicy{
   580  						Type:                     policyType,
   581  						BackupPolicyTemplateName: backupPolicyTPLName,
   582  					}
   583  
   584  					Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKey{Name: policyName, Namespace: clusterKey.Namespace},
   585  						&dpv1alpha1.BackupPolicy{}, true)).Should(Succeed())
   586  
   587  					if policyType == appsv1alpha1.HScaleDataClonePolicyCloneVolume {
   588  						By("creating actionSet if backup policy is backup")
   589  						actionSet := &dpv1alpha1.ActionSet{
   590  							ObjectMeta: metav1.ObjectMeta{
   591  								Name:      actionSetName,
   592  								Namespace: clusterKey.Namespace,
   593  								Labels: map[string]string{
   594  									constant.ClusterDefLabelKey: clusterDef.Name,
   595  								},
   596  							},
   597  							Spec: dpv1alpha1.ActionSetSpec{
   598  								Env: []corev1.EnvVar{
   599  									{
   600  										Name:  "test-name",
   601  										Value: "test-value",
   602  									},
   603  								},
   604  								BackupType: dpv1alpha1.BackupTypeFull,
   605  								Backup: &dpv1alpha1.BackupActionSpec{
   606  									BackupData: &dpv1alpha1.BackupDataActionSpec{
   607  										JobActionSpec: dpv1alpha1.JobActionSpec{
   608  											Image:   "xtrabackup",
   609  											Command: []string{""},
   610  										},
   611  									},
   612  								},
   613  								Restore: &dpv1alpha1.RestoreActionSpec{
   614  									PrepareData: &dpv1alpha1.JobActionSpec{
   615  										Image: "xtrabackup",
   616  										Command: []string{
   617  											"sh",
   618  											"-c",
   619  											"/backup_scripts.sh",
   620  										},
   621  									},
   622  								},
   623  							},
   624  						}
   625  						testapps.CheckedCreateK8sResource(&testCtx, actionSet)
   626  					}
   627  				}
   628  			})()).ShouldNot(HaveOccurred())
   629  	}
   630  
   631  	// @argument componentDefsWithHScalePolicy assign ClusterDefinition.spec.componentDefs[].horizontalScalePolicy for
   632  	// the matching names. If not provided, will set 1st ClusterDefinition.spec.componentDefs[0].horizontalScalePolicy.
   633  	horizontalScale := func(updatedReplicas int, storageClassName string,
   634  		policyType appsv1alpha1.HScaleDataClonePolicyType, componentDefsWithHScalePolicy ...string) {
   635  		defer lorry.UnsetMockClient()
   636  
   637  		cluster := &appsv1alpha1.Cluster{}
   638  		Expect(testCtx.Cli.Get(testCtx.Ctx, clusterKey, cluster)).Should(Succeed())
   639  		initialGeneration := int(cluster.Status.ObservedGeneration)
   640  
   641  		setHorizontalScalePolicy(policyType, componentDefsWithHScalePolicy...)
   642  
   643  		By("Mocking all components' PVCs to bound")
   644  		for _, comp := range cluster.Spec.ComponentSpecs {
   645  			mockComponentPVCsAndBound(&comp, int(comp.Replicas), true, storageClassName)
   646  		}
   647  
   648  		hscalePolicy := func(comp appsv1alpha1.ClusterComponentSpec) *appsv1alpha1.HorizontalScalePolicy {
   649  			for _, componentDef := range clusterDefObj.Spec.ComponentDefs {
   650  				if componentDef.Name == comp.ComponentDefRef {
   651  					return componentDef.HorizontalScalePolicy
   652  				}
   653  			}
   654  			return nil
   655  		}
   656  
   657  		By("Get the latest cluster def")
   658  		Expect(k8sClient.Get(testCtx.Ctx, client.ObjectKeyFromObject(clusterDefObj), clusterDefObj)).Should(Succeed())
   659  		for i, comp := range cluster.Spec.ComponentSpecs {
   660  			newMockLorryClient(clusterKey, comp.Name, updatedReplicas)
   661  
   662  			By(fmt.Sprintf("H-scale component %s with policy %s", comp.Name, hscalePolicy(comp)))
   663  			horizontalScaleComp(updatedReplicas, &cluster.Spec.ComponentSpecs[i], storageClassName, hscalePolicy(comp))
   664  		}
   665  
   666  		By("Checking cluster status and the number of replicas changed")
   667  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).
   668  			Should(BeEquivalentTo(initialGeneration + len(cluster.Spec.ComponentSpecs)))
   669  	}
   670  
   671  	testHorizontalScale := func(compName, compDefName string, initialReplicas, updatedReplicas int32,
   672  		dataClonePolicy appsv1alpha1.HScaleDataClonePolicyType) {
   673  		By("Creating a single component cluster with VolumeClaimTemplate")
   674  		pvcSpec := testapps.NewPVCSpec("1Gi")
   675  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   676  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   677  			AddComponent(compName, compDefName).
   678  			AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   679  			AddVolumeClaimTemplate(testapps.LogVolumeName, pvcSpec).
   680  			SetReplicas(initialReplicas).
   681  			Create(&testCtx).GetObject()
   682  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   683  
   684  		By("Waiting for the cluster controller to create resources completely")
   685  		waitForCreatingResourceCompletely(clusterKey, compName)
   686  
   687  		// REVIEW: this test flow, wait for running phase?
   688  		testk8s.MockEnableVolumeSnapshot(&testCtx, testk8s.DefaultStorageClassName)
   689  
   690  		horizontalScale(int(updatedReplicas), testk8s.DefaultStorageClassName, dataClonePolicy, compDefName)
   691  	}
   692  
   693  	testVolumeExpansion := func(compName, compDefName string, storageClass *storagev1.StorageClass) {
   694  		var (
   695  			replicas          = 3
   696  			volumeSize        = "1Gi"
   697  			newVolumeSize     = "2Gi"
   698  			volumeQuantity    = resource.MustParse(volumeSize)
   699  			newVolumeQuantity = resource.MustParse(newVolumeSize)
   700  		)
   701  
   702  		By("Mock a StorageClass which allows resize")
   703  		Expect(*storageClass.AllowVolumeExpansion).Should(BeTrue())
   704  
   705  		By("Creating a cluster with VolumeClaimTemplate")
   706  		pvcSpec := testapps.NewPVCSpec(volumeSize)
   707  		pvcSpec.StorageClassName = &storageClass.Name
   708  
   709  		By("Create cluster and waiting for the cluster initialized")
   710  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   711  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   712  			AddComponent(compName, compDefName).
   713  			AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   714  			AddVolumeClaimTemplate(testapps.LogVolumeName, pvcSpec).
   715  			SetReplicas(int32(replicas)).
   716  			Create(&testCtx).GetObject()
   717  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   718  
   719  		By("Waiting for the cluster controller to create resources completely")
   720  		waitForCreatingResourceCompletely(clusterKey, compName)
   721  
   722  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   723  
   724  		By("Checking the replicas")
   725  		rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   726  		rsm := &rsmList.Items[0]
   727  		sts := testapps.NewStatefulSetFactory(rsm.Namespace, rsm.Name, clusterObj.Name, compName).
   728  			SetReplicas(*rsm.Spec.Replicas).
   729  			Create(&testCtx).GetObject()
   730  
   731  		Expect(*sts.Spec.Replicas).Should(BeEquivalentTo(replicas))
   732  
   733  		By("Mock PVCs in Bound Status")
   734  		for i := 0; i < replicas; i++ {
   735  			for _, vctName := range []string{testapps.DataVolumeName, testapps.LogVolumeName} {
   736  				pvc := &corev1.PersistentVolumeClaim{
   737  					ObjectMeta: metav1.ObjectMeta{
   738  						Name:      getPVCName(vctName, compName, i),
   739  						Namespace: clusterKey.Namespace,
   740  						Labels: map[string]string{
   741  							constant.AppManagedByLabelKey:   constant.AppName,
   742  							constant.AppInstanceLabelKey:    clusterKey.Name,
   743  							constant.KBAppComponentLabelKey: compName,
   744  						}},
   745  					Spec: pvcSpec.ToV1PersistentVolumeClaimSpec(),
   746  				}
   747  				Expect(testCtx.CreateObj(testCtx.Ctx, pvc)).Should(Succeed())
   748  				pvc.Status.Phase = corev1.ClaimBound // only bound pvc allows resize
   749  				if pvc.Status.Capacity == nil {
   750  					pvc.Status.Capacity = corev1.ResourceList{}
   751  				}
   752  				pvc.Status.Capacity[corev1.ResourceStorage] = volumeQuantity
   753  				Expect(k8sClient.Status().Update(testCtx.Ctx, pvc)).Should(Succeed())
   754  			}
   755  		}
   756  
   757  		By("mock pods/sts of component are available")
   758  		var mockPods []*corev1.Pod
   759  		switch compDefName {
   760  		case statelessCompDefName:
   761  			// ignore
   762  		case replicationCompDefName:
   763  			mockPods = testapps.MockReplicationComponentPods(nil, testCtx, sts, clusterObj.Name, compDefName, nil)
   764  		case statefulCompDefName, consensusCompDefName:
   765  			mockPods = testapps.MockConsensusComponentPods(&testCtx, sts, clusterObj.Name, compName)
   766  		}
   767  		Expect(testapps.ChangeObjStatus(&testCtx, rsm, func() {
   768  			testk8s.MockRSMReady(rsm, mockPods...)
   769  		})).ShouldNot(HaveOccurred())
   770  		Expect(testapps.ChangeObjStatus(&testCtx, sts, func() {
   771  			testk8s.MockStatefulSetReady(sts)
   772  		})).ShouldNot(HaveOccurred())
   773  
   774  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   775  		Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(appsv1alpha1.RunningClusterCompPhase))
   776  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
   777  
   778  		By("Updating data PVC storage size")
   779  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   780  			comp := &cluster.Spec.ComponentSpecs[0]
   781  			for i, vct := range comp.VolumeClaimTemplates {
   782  				if vct.Name == testapps.DataVolumeName {
   783  					comp.VolumeClaimTemplates[i].Spec.Resources.Requests[corev1.ResourceStorage] = newVolumeQuantity
   784  				}
   785  			}
   786  		})()).ShouldNot(HaveOccurred())
   787  
   788  		By("Checking the resize operation in progress for data volume")
   789  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(2))
   790  		Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(appsv1alpha1.UpdatingClusterCompPhase))
   791  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.UpdatingClusterPhase))
   792  		for i := 0; i < replicas; i++ {
   793  			pvc := &corev1.PersistentVolumeClaim{}
   794  			pvcKey := types.NamespacedName{
   795  				Namespace: clusterKey.Namespace,
   796  				Name:      getPVCName(testapps.DataVolumeName, compName, i),
   797  			}
   798  			Expect(k8sClient.Get(testCtx.Ctx, pvcKey, pvc)).Should(Succeed())
   799  			Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(newVolumeQuantity))
   800  			Expect(pvc.Status.Capacity[corev1.ResourceStorage]).To(Equal(volumeQuantity))
   801  		}
   802  
   803  		By("Mock resizing of data volumes finished")
   804  		for i := 0; i < replicas; i++ {
   805  			pvcKey := types.NamespacedName{
   806  				Namespace: clusterKey.Namespace,
   807  				Name:      getPVCName(testapps.DataVolumeName, compName, i),
   808  			}
   809  			Expect(testapps.GetAndChangeObjStatus(&testCtx, pvcKey, func(pvc *corev1.PersistentVolumeClaim) {
   810  				pvc.Status.Capacity[corev1.ResourceStorage] = newVolumeQuantity
   811  			})()).ShouldNot(HaveOccurred())
   812  		}
   813  
   814  		By("Checking the resize operation finished")
   815  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(2))
   816  		Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(appsv1alpha1.RunningClusterCompPhase))
   817  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
   818  
   819  		By("Checking data volumes are resized")
   820  		for i := 0; i < replicas; i++ {
   821  			pvcKey := types.NamespacedName{
   822  				Namespace: clusterKey.Namespace,
   823  				Name:      getPVCName(testapps.DataVolumeName, compName, i),
   824  			}
   825  			Eventually(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
   826  				g.Expect(pvc.Status.Capacity[corev1.ResourceStorage]).To(Equal(newVolumeQuantity))
   827  			})).Should(Succeed())
   828  		}
   829  
   830  		By("Checking log volumes stay unchanged")
   831  		for i := 0; i < replicas; i++ {
   832  			pvc := &corev1.PersistentVolumeClaim{}
   833  			pvcKey := types.NamespacedName{
   834  				Namespace: clusterKey.Namespace,
   835  				Name:      getPVCName(testapps.LogVolumeName, compName, i),
   836  			}
   837  			Expect(k8sClient.Get(testCtx.Ctx, pvcKey, pvc)).Should(Succeed())
   838  			Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(volumeQuantity))
   839  			Expect(pvc.Status.Capacity[corev1.ResourceStorage]).To(Equal(volumeQuantity))
   840  		}
   841  	}
   842  
   843  	testVolumeExpansionFailedAndRecover := func(compName, compDefName string) {
   844  
   845  		const storageClassName = "test-sc"
   846  		const replicas = 3
   847  
   848  		By("Mock a StorageClass which allows resize")
   849  		sc := testapps.CreateStorageClass(&testCtx, storageClassName, true)
   850  
   851  		By("Creating a cluster with VolumeClaimTemplate")
   852  		pvcSpec := testapps.NewPVCSpec("1Gi")
   853  		pvcSpec.StorageClassName = &sc.Name
   854  
   855  		By("Create cluster and waiting for the cluster initialized")
   856  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   857  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   858  			AddComponent(compName, compDefName).
   859  			AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
   860  			SetReplicas(replicas).
   861  			Create(&testCtx).GetObject()
   862  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   863  
   864  		By("Waiting for the cluster controller to create resources completely")
   865  		waitForCreatingResourceCompletely(clusterKey, compName)
   866  
   867  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   868  
   869  		By("Checking the replicas")
   870  		rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   871  		numbers := *rsmList.Items[0].Spec.Replicas
   872  
   873  		Expect(numbers).Should(BeEquivalentTo(replicas))
   874  
   875  		By("Mock PVCs in Bound Status")
   876  		for i := 0; i < replicas; i++ {
   877  			tmpSpec := pvcSpec.ToV1PersistentVolumeClaimSpec()
   878  			tmpSpec.VolumeName = getPVCName(testapps.DataVolumeName, compName, i)
   879  			pvc := &corev1.PersistentVolumeClaim{
   880  				ObjectMeta: metav1.ObjectMeta{
   881  					Name:      getPVCName(testapps.DataVolumeName, compName, i),
   882  					Namespace: clusterKey.Namespace,
   883  					Labels: map[string]string{
   884  						constant.AppInstanceLabelKey: clusterKey.Name,
   885  					}},
   886  				Spec: tmpSpec,
   887  			}
   888  			Expect(testCtx.CreateObj(testCtx.Ctx, pvc)).Should(Succeed())
   889  			pvc.Status.Phase = corev1.ClaimBound // only bound pvc allows resize
   890  			Expect(k8sClient.Status().Update(testCtx.Ctx, pvc)).Should(Succeed())
   891  		}
   892  
   893  		By("mocking PVs")
   894  		for i := 0; i < replicas; i++ {
   895  			pv := &corev1.PersistentVolume{
   896  				ObjectMeta: metav1.ObjectMeta{
   897  					Name:      getPVCName(testapps.DataVolumeName, compName, i), // use same name as pvc
   898  					Namespace: clusterKey.Namespace,
   899  					Labels: map[string]string{
   900  						constant.AppInstanceLabelKey: clusterKey.Name,
   901  					}},
   902  				Spec: corev1.PersistentVolumeSpec{
   903  					Capacity: corev1.ResourceList{
   904  						"storage": resource.MustParse("1Gi"),
   905  					},
   906  					AccessModes: []corev1.PersistentVolumeAccessMode{
   907  						"ReadWriteOnce",
   908  					},
   909  					PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete,
   910  					StorageClassName:              storageClassName,
   911  					PersistentVolumeSource: corev1.PersistentVolumeSource{
   912  						HostPath: &corev1.HostPathVolumeSource{
   913  							Path: "/opt/volume/nginx",
   914  							Type: nil,
   915  						},
   916  					},
   917  					ClaimRef: &corev1.ObjectReference{
   918  						Name: getPVCName(testapps.DataVolumeName, compName, i),
   919  					},
   920  				},
   921  			}
   922  			Expect(testCtx.CreateObj(testCtx.Ctx, pv)).Should(Succeed())
   923  		}
   924  
   925  		By("Updating the PVC storage size")
   926  		newStorageValue := resource.MustParse("2Gi")
   927  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   928  			comp := &cluster.Spec.ComponentSpecs[0]
   929  			comp.VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = newStorageValue
   930  		})()).ShouldNot(HaveOccurred())
   931  
   932  		By("Checking the resize operation finished")
   933  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(2))
   934  
   935  		By("Checking PVCs are resized")
   936  		rsmList = testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   937  		numbers = *rsmList.Items[0].Spec.Replicas
   938  		for i := numbers - 1; i >= 0; i-- {
   939  			pvc := &corev1.PersistentVolumeClaim{}
   940  			pvcKey := types.NamespacedName{
   941  				Namespace: clusterKey.Namespace,
   942  				Name:      getPVCName(testapps.DataVolumeName, compName, int(i)),
   943  			}
   944  			Expect(k8sClient.Get(testCtx.Ctx, pvcKey, pvc)).Should(Succeed())
   945  			Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(newStorageValue))
   946  		}
   947  
   948  		By("Updating the PVC storage size back")
   949  		originStorageValue := resource.MustParse("1Gi")
   950  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   951  			comp := &cluster.Spec.ComponentSpecs[0]
   952  			comp.VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = originStorageValue
   953  		})()).ShouldNot(HaveOccurred())
   954  
   955  		By("Checking the resize operation finished")
   956  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(3))
   957  
   958  		By("Checking PVCs are resized")
   959  		Eventually(func(g Gomega) {
   960  			rsmList = testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   961  			numbers = *rsmList.Items[0].Spec.Replicas
   962  			for i := numbers - 1; i >= 0; i-- {
   963  				pvc := &corev1.PersistentVolumeClaim{}
   964  				pvcKey := types.NamespacedName{
   965  					Namespace: clusterKey.Namespace,
   966  					Name:      getPVCName(testapps.DataVolumeName, compName, int(i)),
   967  				}
   968  				g.Expect(k8sClient.Get(testCtx.Ctx, pvcKey, pvc)).Should(Succeed())
   969  				g.Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(originStorageValue))
   970  			}
   971  		}).Should(Succeed())
   972  	}
   973  
   974  	testClusterAffinity := func(compName, compDefName string) {
   975  		const topologyKey = "testTopologyKey"
   976  		const labelKey = "testNodeLabelKey"
   977  		const labelValue = "testLabelValue"
   978  
   979  		By("Creating a cluster with Affinity")
   980  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
   981  
   982  		affinity := &appsv1alpha1.Affinity{
   983  			PodAntiAffinity: appsv1alpha1.Required,
   984  			TopologyKeys:    []string{topologyKey},
   985  			NodeLabels: map[string]string{
   986  				labelKey: labelValue,
   987  			},
   988  			Tenancy: appsv1alpha1.SharedNode,
   989  		}
   990  
   991  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   992  			clusterDefObj.Name, clusterVersionObj.Name).
   993  			AddComponent(compName, compDefName).SetReplicas(3).
   994  			WithRandomName().SetClusterAffinity(affinity).
   995  			Create(&testCtx).GetObject()
   996  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   997  
   998  		By("Waiting for the cluster controller to create resources completely")
   999  		waitForCreatingResourceCompletely(clusterKey, compName)
  1000  
  1001  		By("Checking the Affinity and TopologySpreadConstraints")
  1002  		checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
  1003  			podSpec := getPodSpec(sts, deploy)
  1004  			g.Expect(podSpec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Key).To(Equal(labelKey))
  1005  			g.Expect(podSpec.TopologySpreadConstraints[0].WhenUnsatisfiable).To(Equal(corev1.DoNotSchedule))
  1006  			g.Expect(podSpec.TopologySpreadConstraints[0].TopologyKey).To(Equal(topologyKey))
  1007  			g.Expect(podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution).Should(HaveLen(1))
  1008  			g.Expect(podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey).To(Equal(topologyKey))
  1009  		})
  1010  	}
  1011  
  1012  	testComponentAffinity := func(compName, compDefName string) {
  1013  		const clusterTopologyKey = "testClusterTopologyKey"
  1014  		const compTopologyKey = "testComponentTopologyKey"
  1015  
  1016  		By("Creating a cluster with Affinity")
  1017  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
  1018  		affinity := &appsv1alpha1.Affinity{
  1019  			PodAntiAffinity: appsv1alpha1.Required,
  1020  			TopologyKeys:    []string{clusterTopologyKey},
  1021  			Tenancy:         appsv1alpha1.SharedNode,
  1022  		}
  1023  		compAffinity := &appsv1alpha1.Affinity{
  1024  			PodAntiAffinity: appsv1alpha1.Preferred,
  1025  			TopologyKeys:    []string{compTopologyKey},
  1026  			Tenancy:         appsv1alpha1.DedicatedNode,
  1027  		}
  1028  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1029  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().SetClusterAffinity(affinity).
  1030  			AddComponent(compName, compDefName).SetReplicas(1).SetComponentAffinity(compAffinity).
  1031  			Create(&testCtx).GetObject()
  1032  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1033  
  1034  		By("Waiting for the cluster controller to create resources completely")
  1035  		waitForCreatingResourceCompletely(clusterKey, compName)
  1036  
  1037  		By("Checking the Affinity and the TopologySpreadConstraints")
  1038  		checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
  1039  			podSpec := getPodSpec(sts, deploy)
  1040  			g.Expect(podSpec.TopologySpreadConstraints[0].WhenUnsatisfiable).To(Equal(corev1.ScheduleAnyway))
  1041  			g.Expect(podSpec.TopologySpreadConstraints[0].TopologyKey).To(Equal(compTopologyKey))
  1042  			g.Expect(podSpec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Weight).ShouldNot(BeNil())
  1043  			g.Expect(podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution).Should(HaveLen(1))
  1044  			g.Expect(podSpec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey).To(Equal(corev1.LabelHostname))
  1045  		})
  1046  	}
  1047  
  1048  	testClusterToleration := func(compName, compDefName string) {
  1049  		const tolerationKey = "testClusterTolerationKey"
  1050  		const tolerationValue = "testClusterTolerationValue"
  1051  		By("Creating a cluster with Toleration")
  1052  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
  1053  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1054  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1055  			AddComponent(compName, compDefName).SetReplicas(1).
  1056  			AddClusterToleration(corev1.Toleration{
  1057  				Key:      tolerationKey,
  1058  				Value:    tolerationValue,
  1059  				Operator: corev1.TolerationOpEqual,
  1060  				Effect:   corev1.TaintEffectNoSchedule,
  1061  			}).
  1062  			Create(&testCtx).GetObject()
  1063  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1064  
  1065  		By("Waiting for the cluster controller to create resources completely")
  1066  		waitForCreatingResourceCompletely(clusterKey, compName)
  1067  
  1068  		By("Checking the tolerations")
  1069  		checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
  1070  			podSpec := getPodSpec(sts, deploy)
  1071  			g.Expect(podSpec.Tolerations).Should(HaveLen(2))
  1072  			t := podSpec.Tolerations[0]
  1073  			g.Expect(t.Key).Should(BeEquivalentTo(tolerationKey))
  1074  			g.Expect(t.Value).Should(BeEquivalentTo(tolerationValue))
  1075  			g.Expect(t.Operator).Should(BeEquivalentTo(corev1.TolerationOpEqual))
  1076  			g.Expect(t.Effect).Should(BeEquivalentTo(corev1.TaintEffectNoSchedule))
  1077  		})
  1078  	}
  1079  
  1080  	testStsWorkloadComponentToleration := func(compName, compDefName string) {
  1081  		clusterTolerationKey := "testClusterTolerationKey"
  1082  		compTolerationKey := "testcompTolerationKey"
  1083  		compTolerationValue := "testcompTolerationValue"
  1084  
  1085  		By("Creating a cluster with Toleration")
  1086  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
  1087  		compToleration := corev1.Toleration{
  1088  			Key:      compTolerationKey,
  1089  			Value:    compTolerationValue,
  1090  			Operator: corev1.TolerationOpEqual,
  1091  			Effect:   corev1.TaintEffectNoSchedule,
  1092  		}
  1093  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1094  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1095  			AddClusterToleration(corev1.Toleration{
  1096  				Key:      clusterTolerationKey,
  1097  				Operator: corev1.TolerationOpExists,
  1098  				Effect:   corev1.TaintEffectNoExecute,
  1099  			}).
  1100  			AddComponent(compName, compDefName).SetReplicas(1).AddComponentToleration(compToleration).
  1101  			Create(&testCtx).GetObject()
  1102  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1103  
  1104  		By("Waiting for the cluster controller to create resources completely")
  1105  		waitForCreatingResourceCompletely(clusterKey, compName)
  1106  
  1107  		By("Checking the tolerations")
  1108  		checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
  1109  			podSpec := getPodSpec(sts, deploy)
  1110  			Expect(podSpec.Tolerations).Should(HaveLen(2))
  1111  			t := podSpec.Tolerations[0]
  1112  			g.Expect(t.Key).Should(BeEquivalentTo(compTolerationKey))
  1113  			g.Expect(t.Value).Should(BeEquivalentTo(compTolerationValue))
  1114  			g.Expect(t.Operator).Should(BeEquivalentTo(corev1.TolerationOpEqual))
  1115  			g.Expect(t.Effect).Should(BeEquivalentTo(corev1.TaintEffectNoSchedule))
  1116  		})
  1117  	}
  1118  
  1119  	getStsPodsName := func(sts *appsv1.StatefulSet) []string {
  1120  		pods, err := common.GetPodListByStatefulSet(ctx, k8sClient, sts)
  1121  		Expect(err).To(Succeed())
  1122  
  1123  		names := make([]string, 0)
  1124  		for _, pod := range pods {
  1125  			names = append(names, pod.Name)
  1126  		}
  1127  		return names
  1128  	}
  1129  
  1130  	testThreeReplicas := func(compName, compDefName string) {
  1131  		const replicas = 3
  1132  
  1133  		By("Mock a cluster obj")
  1134  		pvcSpec := testapps.NewPVCSpec("1Gi")
  1135  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1136  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1137  			AddComponent(compName, compDefName).
  1138  			SetReplicas(replicas).AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
  1139  			Create(&testCtx).GetObject()
  1140  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1141  
  1142  		By("Waiting for the cluster controller to create resources completely")
  1143  		waitForCreatingResourceCompletely(clusterKey, compName)
  1144  
  1145  		var rsm *workloads.ReplicatedStateMachine
  1146  		Eventually(func(g Gomega) {
  1147  			rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
  1148  			g.Expect(rsmList.Items).ShouldNot(BeEmpty())
  1149  			rsm = &rsmList.Items[0]
  1150  		}).Should(Succeed())
  1151  		sts := testapps.NewStatefulSetFactory(rsm.Namespace, rsm.Name, clusterKey.Name, compName).
  1152  			AddAppComponentLabel(rsm.Labels[constant.KBAppComponentLabelKey]).
  1153  			AddAppInstanceLabel(rsm.Labels[constant.AppInstanceLabelKey]).
  1154  			SetReplicas(*rsm.Spec.Replicas).Create(&testCtx).GetObject()
  1155  
  1156  		By("Creating mock pods in StatefulSet, and set controller reference")
  1157  		pods := mockPodsForTest(clusterObj, replicas)
  1158  		for i, pod := range pods {
  1159  			Expect(controllerutil.SetControllerReference(sts, &pod, scheme.Scheme)).Should(Succeed())
  1160  			Expect(testCtx.CreateObj(testCtx.Ctx, &pod)).Should(Succeed())
  1161  			patch := client.MergeFrom(pod.DeepCopy())
  1162  			// mock the status to pass the isReady(pod) check in consensus_set
  1163  			pod.Status.Conditions = []corev1.PodCondition{{
  1164  				Type:   corev1.PodReady,
  1165  				Status: corev1.ConditionTrue,
  1166  			}}
  1167  			Eventually(k8sClient.Status().Patch(ctx, &pod, patch)).Should(Succeed())
  1168  			role := "follower"
  1169  			if i == 0 {
  1170  				role = "leader"
  1171  			}
  1172  			patch = client.MergeFrom(pod.DeepCopy())
  1173  			pod.Labels[constant.RoleLabelKey] = role
  1174  			Eventually(k8sClient.Patch(ctx, &pod, patch)).Should(Succeed())
  1175  		}
  1176  
  1177  		By("Checking pods' role are changed accordingly")
  1178  		Eventually(func(g Gomega) {
  1179  			pods, err := common.GetPodListByStatefulSet(ctx, k8sClient, sts)
  1180  			g.Expect(err).ShouldNot(HaveOccurred())
  1181  			// should have 3 pods
  1182  			g.Expect(pods).Should(HaveLen(3))
  1183  			// 1 leader
  1184  			// 2 followers
  1185  			leaderCount, followerCount := 0, 0
  1186  			for _, pod := range pods {
  1187  				switch pod.Labels[constant.RoleLabelKey] {
  1188  				case leader:
  1189  					leaderCount++
  1190  				case follower:
  1191  					followerCount++
  1192  				}
  1193  			}
  1194  			g.Expect(leaderCount).Should(Equal(1))
  1195  			g.Expect(followerCount).Should(Equal(2))
  1196  		}).Should(Succeed())
  1197  
  1198  		// trigger rsm to reconcile as the underlying sts is not created
  1199  		Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(sts), func(rsm *workloads.ReplicatedStateMachine) {
  1200  			rsm.Annotations["time"] = time.Now().Format(time.RFC3339)
  1201  		})()).Should(Succeed())
  1202  		By("Checking pods' annotations")
  1203  		Eventually(func(g Gomega) {
  1204  			pods, err := common.GetPodListByStatefulSet(ctx, k8sClient, sts)
  1205  			g.Expect(err).ShouldNot(HaveOccurred())
  1206  			g.Expect(pods).Should(HaveLen(int(*sts.Spec.Replicas)))
  1207  			for _, pod := range pods {
  1208  				g.Expect(pod.Annotations).ShouldNot(BeNil())
  1209  				g.Expect(pod.Annotations[constant.ComponentReplicasAnnotationKey]).Should(Equal(strconv.Itoa(int(*sts.Spec.Replicas))))
  1210  			}
  1211  		}).Should(Succeed())
  1212  		rsmPatch := client.MergeFrom(rsm.DeepCopy())
  1213  		By("Updating RSM's status")
  1214  		rsm.Status.UpdateRevision = "mock-version"
  1215  		pods, err := common.GetPodListByStatefulSet(ctx, k8sClient, sts)
  1216  		Expect(err).Should(BeNil())
  1217  		var podList []*corev1.Pod
  1218  		for i := range pods {
  1219  			podList = append(podList, &pods[i])
  1220  		}
  1221  		testk8s.MockRSMReady(rsm, podList...)
  1222  		Expect(k8sClient.Status().Patch(ctx, rsm, rsmPatch)).Should(Succeed())
  1223  
  1224  		stsPatch := client.MergeFrom(sts.DeepCopy())
  1225  		By("Updating StatefulSet's status")
  1226  		sts.Status.UpdateRevision = "mock-version"
  1227  		sts.Status.Replicas = int32(replicas)
  1228  		sts.Status.AvailableReplicas = int32(replicas)
  1229  		sts.Status.CurrentReplicas = int32(replicas)
  1230  		sts.Status.ReadyReplicas = int32(replicas)
  1231  		sts.Status.ObservedGeneration = sts.Generation
  1232  		Expect(k8sClient.Status().Patch(ctx, sts, stsPatch)).Should(Succeed())
  1233  
  1234  		By("Checking consensus set pods' role are updated in cluster status")
  1235  		Eventually(func(g Gomega) {
  1236  			fetched := &appsv1alpha1.Cluster{}
  1237  			g.Expect(k8sClient.Get(ctx, clusterKey, fetched)).To(Succeed())
  1238  			compName := fetched.Spec.ComponentSpecs[0].Name
  1239  			g.Expect(fetched.Status.Components != nil).To(BeTrue())
  1240  			g.Expect(fetched.Status.Components).To(HaveKey(compName))
  1241  			compStatus, ok := fetched.Status.Components[compName]
  1242  			g.Expect(ok).Should(BeTrue())
  1243  			consensusStatus := compStatus.ConsensusSetStatus
  1244  			g.Expect(consensusStatus != nil).To(BeTrue())
  1245  			g.Expect(consensusStatus.Leader.Pod).To(BeElementOf(getStsPodsName(sts)))
  1246  			g.Expect(consensusStatus.Followers).Should(HaveLen(2))
  1247  			g.Expect(consensusStatus.Followers[0].Pod).To(BeElementOf(getStsPodsName(sts)))
  1248  			g.Expect(consensusStatus.Followers[1].Pod).To(BeElementOf(getStsPodsName(sts)))
  1249  		}).Should(Succeed())
  1250  
  1251  		By("Waiting the component be running")
  1252  		Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).
  1253  			Should(Equal(appsv1alpha1.RunningClusterCompPhase))
  1254  	}
  1255  
  1256  	testBackupError := func(compName, compDefName string) {
  1257  		initialReplicas := int32(1)
  1258  		updatedReplicas := int32(3)
  1259  		testk8s.MockEnableVolumeSnapshot(&testCtx, testk8s.DefaultStorageClassName)
  1260  
  1261  		By("Set HorizontalScalePolicy")
  1262  		Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
  1263  			func(clusterDef *appsv1alpha1.ClusterDefinition) {
  1264  				for i, def := range clusterDef.Spec.ComponentDefs {
  1265  					if def.Name != compDefName {
  1266  						continue
  1267  					}
  1268  					clusterDef.Spec.ComponentDefs[i].HorizontalScalePolicy =
  1269  						&appsv1alpha1.HorizontalScalePolicy{Type: appsv1alpha1.HScaleDataClonePolicyCloneVolume,
  1270  							BackupPolicyTemplateName: backupPolicyTPLName}
  1271  				}
  1272  			})()).ShouldNot(HaveOccurred())
  1273  
  1274  		By("Creating a cluster with VolumeClaimTemplate")
  1275  		pvcSpec := testapps.NewPVCSpec("1Gi")
  1276  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1277  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1278  			AddComponent(compName, compDefName).
  1279  			AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
  1280  			SetReplicas(initialReplicas).
  1281  			Create(&testCtx).GetObject()
  1282  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1283  
  1284  		By("Waiting for the cluster controller to create resources completely")
  1285  		waitForCreatingResourceCompletely(clusterKey, compName)
  1286  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
  1287  
  1288  		By("Create and Mock PVCs status to bound")
  1289  		for _, comp := range clusterObj.Spec.ComponentSpecs {
  1290  			mockComponentPVCsAndBound(&comp, int(comp.Replicas), true, testk8s.DefaultStorageClassName)
  1291  		}
  1292  
  1293  		By(fmt.Sprintf("Changing replicas to %d", updatedReplicas))
  1294  		changeCompReplicas(clusterKey, updatedReplicas, &clusterObj.Spec.ComponentSpecs[0])
  1295  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(2))
  1296  
  1297  		By("Waiting for the backup object been created")
  1298  		ml := client.MatchingLabels{
  1299  			constant.AppInstanceLabelKey:    clusterKey.Name,
  1300  			constant.KBAppComponentLabelKey: compName,
  1301  		}
  1302  		Eventually(testapps.List(&testCtx, generics.BackupSignature,
  1303  			ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(1))
  1304  
  1305  		By("Mocking backup status to failed")
  1306  		backupList := dpv1alpha1.BackupList{}
  1307  		Expect(testCtx.Cli.List(testCtx.Ctx, &backupList, ml)).Should(Succeed())
  1308  		backupKey := types.NamespacedName{
  1309  			Namespace: backupList.Items[0].Namespace,
  1310  			Name:      backupList.Items[0].Name,
  1311  		}
  1312  		Expect(testapps.GetAndChangeObjStatus(&testCtx, backupKey, func(backup *dpv1alpha1.Backup) {
  1313  			backup.Status.Phase = dpv1alpha1.BackupPhaseFailed
  1314  		})()).Should(Succeed())
  1315  
  1316  		By("Checking cluster status failed with backup error")
  1317  		Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
  1318  			g.Expect(testk8s.IsMockVolumeSnapshotEnabled(&testCtx, testk8s.DefaultStorageClassName)).Should(BeTrue())
  1319  			g.Expect(cluster.Status.Conditions).ShouldNot(BeEmpty())
  1320  			var err error
  1321  			for _, cond := range cluster.Status.Conditions {
  1322  				if strings.Contains(cond.Message, "backup for horizontalScaling failed") {
  1323  					err = errors.New("has backup error")
  1324  					break
  1325  				}
  1326  			}
  1327  			g.Expect(err).Should(HaveOccurred())
  1328  		})).Should(Succeed())
  1329  
  1330  		By("Expect for backup error event")
  1331  		Eventually(func(g Gomega) {
  1332  			eventList := corev1.EventList{}
  1333  			Expect(k8sClient.List(ctx, &eventList, client.InNamespace(testCtx.DefaultNamespace))).Should(Succeed())
  1334  			hasBackupErrorEvent := false
  1335  			for _, v := range eventList.Items {
  1336  				if v.Reason == string(intctrlutil.ErrorTypeBackupFailed) {
  1337  					hasBackupErrorEvent = true
  1338  					break
  1339  				}
  1340  			}
  1341  			g.Expect(hasBackupErrorEvent).Should(BeTrue())
  1342  		}).Should(Succeed())
  1343  	}
  1344  
  1345  	testUpdateKubeBlocksToolsImage := func(compName, compDefName string) {
  1346  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1347  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1348  			AddComponent(compName, compDefName).SetReplicas(1).
  1349  			Create(&testCtx).GetObject()
  1350  		clusterKey = client.ObjectKeyFromObject(clusterObj)
  1351  
  1352  		By("Waiting for the cluster controller to create resources completely")
  1353  		waitForCreatingResourceCompletely(clusterKey, compName)
  1354  
  1355  		oldToolsImage := viper.GetString(constant.KBToolsImage)
  1356  		newToolsImage := fmt.Sprintf("%s-%s", oldToolsImage, rand.String(4))
  1357  		defer func() {
  1358  			viper.Set(constant.KBToolsImage, oldToolsImage)
  1359  		}()
  1360  
  1361  		checkWorkloadGenerationAndToolsImage := func(workloadGenerationExpected int64, oldImageCntExpected, newImageCntExpected int) {
  1362  			checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
  1363  				if sts != nil {
  1364  					g.Expect(sts.Generation).Should(Equal(workloadGenerationExpected))
  1365  				}
  1366  				if deploy != nil {
  1367  					g.Expect(deploy.Generation).Should(Equal(workloadGenerationExpected))
  1368  				}
  1369  				oldImageCnt := 0
  1370  				newImageCnt := 0
  1371  				for _, c := range getPodSpec(sts, deploy).Containers {
  1372  					if c.Image == oldToolsImage {
  1373  						oldImageCnt += 1
  1374  					}
  1375  					if c.Image == newToolsImage {
  1376  						newImageCnt += 1
  1377  					}
  1378  				}
  1379  				g.Expect(oldImageCnt).Should(Equal(oldImageCntExpected))
  1380  				g.Expect(newImageCnt).Should(Equal(newImageCntExpected))
  1381  			})
  1382  		}
  1383  
  1384  		By("check the workload generation as 1")
  1385  		checkWorkloadGenerationAndToolsImage(int64(1), 1, 0)
  1386  
  1387  		By("update kubeblocks tools image")
  1388  		viper.Set(constant.KBToolsImage, newToolsImage)
  1389  
  1390  		By("update cluster annotation to trigger cluster status reconcile")
  1391  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
  1392  			cluster.Annotations = map[string]string{"time": time.Now().Format(time.RFC3339)}
  1393  		})()).Should(Succeed())
  1394  		Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
  1395  			g.Expect(cluster.Status.ObservedGeneration).Should(Equal(int64(1)))
  1396  		})).Should(Succeed())
  1397  		checkWorkloadGenerationAndToolsImage(int64(1), 1, 0)
  1398  
  1399  		By("update termination policy to trigger cluster spec reconcile, but workload not changed")
  1400  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
  1401  			cluster.Spec.TerminationPolicy = appsv1alpha1.DoNotTerminate
  1402  		})()).Should(Succeed())
  1403  		Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
  1404  			g.Expect(cluster.Status.ObservedGeneration).Should(Equal(int64(2)))
  1405  		})).Should(Succeed())
  1406  		checkWorkloadGenerationAndToolsImage(int64(1), 1, 0)
  1407  
  1408  		By("update replicas to trigger cluster spec and workload reconcile")
  1409  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
  1410  			replicas := cluster.Spec.ComponentSpecs[0].Replicas
  1411  			cluster.Spec.ComponentSpecs[0].Replicas = replicas + 1
  1412  		})()).Should(Succeed())
  1413  		Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
  1414  			g.Expect(cluster.Status.ObservedGeneration).Should(Equal(int64(3)))
  1415  		})).Should(Succeed())
  1416  		checkWorkloadGenerationAndToolsImage(int64(2), 0, 1)
  1417  	}
  1418  
  1419  	Context("when creating cluster with multiple kinds of components", func() {
  1420  		BeforeEach(func() {
  1421  			cleanEnv()
  1422  			createAllWorkloadTypesClusterDef()
  1423  			createBackupPolicyTpl(clusterDefObj)
  1424  		})
  1425  
  1426  		createNWaitClusterObj := func(components map[string]string,
  1427  			addedComponentProcessor func(compName string, factory *testapps.MockClusterFactory),
  1428  			withFixedName ...bool) {
  1429  			Expect(components).ShouldNot(BeEmpty())
  1430  
  1431  			By("Creating a cluster")
  1432  			clusterBuilder := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1433  				clusterDefObj.Name, clusterVersionObj.Name)
  1434  
  1435  			compNames := make([]string, 0, len(components))
  1436  			for compName, compDefName := range components {
  1437  				clusterBuilder = clusterBuilder.AddComponent(compName, compDefName)
  1438  				if addedComponentProcessor != nil {
  1439  					addedComponentProcessor(compName, clusterBuilder)
  1440  				}
  1441  				compNames = append(compNames, compName)
  1442  			}
  1443  			if len(withFixedName) == 0 || !withFixedName[0] {
  1444  				clusterBuilder.WithRandomName()
  1445  			}
  1446  			clusterObj = clusterBuilder.Create(&testCtx).GetObject()
  1447  			clusterKey = client.ObjectKeyFromObject(clusterObj)
  1448  
  1449  			By("Waiting for the cluster controller to create resources completely")
  1450  			waitForCreatingResourceCompletely(clusterKey, compNames...)
  1451  		}
  1452  
  1453  		testMultiCompHScale := func(policyType appsv1alpha1.HScaleDataClonePolicyType) {
  1454  			compNameNDef := map[string]string{
  1455  				statefulCompName:    statefulCompDefName,
  1456  				consensusCompName:   consensusCompDefName,
  1457  				replicationCompName: replicationCompDefName,
  1458  			}
  1459  			initialReplicas := int32(1)
  1460  			updatedReplicas := int32(3)
  1461  
  1462  			By("Creating a multi components cluster with VolumeClaimTemplate")
  1463  			pvcSpec := testapps.NewPVCSpec("1Gi")
  1464  
  1465  			createNWaitClusterObj(compNameNDef, func(compName string, factory *testapps.MockClusterFactory) {
  1466  				factory.AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).SetReplicas(initialReplicas)
  1467  			}, false)
  1468  
  1469  			By("Waiting for the cluster controller to create resources completely")
  1470  			waitForCreatingResourceCompletely(clusterKey, statefulCompName, consensusCompName, replicationCompName)
  1471  
  1472  			// statefulCompDefName not in componentDefsWithHScalePolicy, for nil backup policy test
  1473  			// REVIEW:
  1474  			//  1. this test flow, wait for running phase?
  1475  			horizontalScale(int(updatedReplicas), testk8s.DefaultStorageClassName, policyType, consensusCompDefName, replicationCompDefName)
  1476  		}
  1477  
  1478  		It("should successfully h-scale with multiple components", func() {
  1479  			testk8s.MockEnableVolumeSnapshot(&testCtx, testk8s.DefaultStorageClassName)
  1480  			testMultiCompHScale(appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1481  		})
  1482  
  1483  		It("should successfully h-scale with multiple components by backup tool", func() {
  1484  			testk8s.MockDisableVolumeSnapshot(&testCtx, testk8s.DefaultStorageClassName)
  1485  			testMultiCompHScale(appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1486  		})
  1487  	})
  1488  
  1489  	When("creating cluster with backup configuration", func() {
  1490  		const (
  1491  			compName                       = statefulCompName
  1492  			compDefName                    = statefulCompDefName
  1493  			backupRepoName                 = "test-backup-repo"
  1494  			backupMethodName               = "test-backup-method"
  1495  			volumeSnapshotBackupMethodName = "test-vs-backup-method"
  1496  		)
  1497  		BeforeEach(func() {
  1498  			cleanEnv()
  1499  			createAllWorkloadTypesClusterDef()
  1500  			createBackupPolicyTpl(clusterDefObj, clusterVersionName)
  1501  		})
  1502  
  1503  		createClusterWithBackup := func(backup *appsv1alpha1.ClusterBackup) {
  1504  			By("Creating a cluster")
  1505  			clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1506  				clusterDefObj.Name, clusterVersionObj.Name).
  1507  				AddComponent(compName, compDefName).WithRandomName().SetBackup(backup).
  1508  				Create(&testCtx).GetObject()
  1509  			clusterKey = client.ObjectKeyFromObject(clusterObj)
  1510  
  1511  			By("Waiting for the cluster controller to create resources completely")
  1512  			waitForCreatingResourceCompletely(clusterKey)
  1513  		}
  1514  
  1515  		It("Creating cluster without backup", func() {
  1516  			createClusterWithBackup(nil)
  1517  			Eventually(testapps.List(&testCtx, generics.BackupPolicySignature,
  1518  				client.MatchingLabels{
  1519  					constant.AppInstanceLabelKey: clusterKey.Name,
  1520  				}, client.InNamespace(clusterKey.Namespace))).ShouldNot(BeEmpty())
  1521  		})
  1522  
  1523  		It("Creating cluster with backup", func() {
  1524  			var (
  1525  				boolTrue  = true
  1526  				boolFalse = false
  1527  				int64Ptr  = func(in int64) *int64 {
  1528  					return &in
  1529  				}
  1530  				retention = func(s string) dpv1alpha1.RetentionPeriod {
  1531  					return dpv1alpha1.RetentionPeriod(s)
  1532  				}
  1533  			)
  1534  
  1535  			var testCases = []struct {
  1536  				desc   string
  1537  				backup *appsv1alpha1.ClusterBackup
  1538  			}{
  1539  				{
  1540  					desc: "backup with snapshot method",
  1541  					backup: &appsv1alpha1.ClusterBackup{
  1542  						Enabled:                 &boolTrue,
  1543  						RetentionPeriod:         retention("1d"),
  1544  						Method:                  vsBackupMethodName,
  1545  						CronExpression:          "*/1 * * * *",
  1546  						StartingDeadlineMinutes: int64Ptr(int64(10)),
  1547  						PITREnabled:             &boolTrue,
  1548  						RepoName:                backupRepoName,
  1549  					},
  1550  				},
  1551  				{
  1552  					desc: "disable backup",
  1553  					backup: &appsv1alpha1.ClusterBackup{
  1554  						Enabled:                 &boolFalse,
  1555  						RetentionPeriod:         retention("1d"),
  1556  						Method:                  vsBackupMethodName,
  1557  						CronExpression:          "*/1 * * * *",
  1558  						StartingDeadlineMinutes: int64Ptr(int64(10)),
  1559  						PITREnabled:             &boolTrue,
  1560  						RepoName:                backupRepoName,
  1561  					},
  1562  				},
  1563  				{
  1564  					desc: "backup with backup tool",
  1565  					backup: &appsv1alpha1.ClusterBackup{
  1566  						Enabled:                 &boolTrue,
  1567  						RetentionPeriod:         retention("2d"),
  1568  						Method:                  backupMethodName,
  1569  						CronExpression:          "*/1 * * * *",
  1570  						StartingDeadlineMinutes: int64Ptr(int64(10)),
  1571  						RepoName:                backupRepoName,
  1572  						PITREnabled:             &boolFalse,
  1573  					},
  1574  				},
  1575  				{
  1576  					desc:   "backup is nil",
  1577  					backup: nil,
  1578  				},
  1579  			}
  1580  
  1581  			for _, t := range testCases {
  1582  				By(t.desc)
  1583  				backup := t.backup
  1584  				createClusterWithBackup(backup)
  1585  
  1586  				checkSchedule := func(g Gomega, schedule *dpv1alpha1.BackupSchedule) {
  1587  					var policy *dpv1alpha1.SchedulePolicy
  1588  					for i, s := range schedule.Spec.Schedules {
  1589  						if s.BackupMethod == backup.Method {
  1590  							Expect(*s.Enabled).Should(BeEquivalentTo(*backup.Enabled))
  1591  							policy = &schedule.Spec.Schedules[i]
  1592  						}
  1593  					}
  1594  					if backup.Enabled != nil && *backup.Enabled {
  1595  						Expect(policy).ShouldNot(BeNil())
  1596  						Expect(policy.RetentionPeriod).Should(BeEquivalentTo(backup.RetentionPeriod))
  1597  						Expect(policy.CronExpression).Should(BeEquivalentTo(backup.CronExpression))
  1598  					}
  1599  				}
  1600  
  1601  				checkPolicy := func(g Gomega, policy *dpv1alpha1.BackupPolicy) {
  1602  					if backup != nil && backup.RepoName != "" {
  1603  						g.Expect(*policy.Spec.BackupRepoName).Should(BeEquivalentTo(backup.RepoName))
  1604  					}
  1605  					g.Expect(policy.Spec.BackupMethods).ShouldNot(BeEmpty())
  1606  					// expect for image tage env in backupMethod
  1607  					var existImageTagEnv bool
  1608  					for _, v := range policy.Spec.BackupMethods {
  1609  						for _, e := range v.Env {
  1610  							if e.Name == testapps.EnvKeyImageTag && e.Value == testapps.DefaultImageTag {
  1611  								existImageTagEnv = true
  1612  								break
  1613  							}
  1614  						}
  1615  					}
  1616  					g.Expect(existImageTagEnv).Should(BeTrue())
  1617  				}
  1618  
  1619  				By("checking backup policy")
  1620  				backupPolicyName := generateBackupPolicyName(clusterKey.Name, compDefName, "")
  1621  				backupPolicyKey := client.ObjectKey{Name: backupPolicyName, Namespace: clusterKey.Namespace}
  1622  				backupPolicy := &dpv1alpha1.BackupPolicy{}
  1623  				Eventually(testapps.CheckObjExists(&testCtx, backupPolicyKey, backupPolicy, true)).Should(Succeed())
  1624  				Eventually(testapps.CheckObj(&testCtx, backupPolicyKey, checkPolicy)).Should(Succeed())
  1625  
  1626  				By("checking backup schedule")
  1627  				backupScheduleName := generateBackupScheduleName(clusterKey.Name, compDefName, "")
  1628  				backupScheduleKey := client.ObjectKey{Name: backupScheduleName, Namespace: clusterKey.Namespace}
  1629  				if backup == nil {
  1630  					Eventually(testapps.CheckObjExists(&testCtx, backupScheduleKey,
  1631  						&dpv1alpha1.BackupSchedule{}, true)).Should(Succeed())
  1632  					continue
  1633  				}
  1634  				Eventually(testapps.CheckObj(&testCtx, backupScheduleKey, checkSchedule)).Should(Succeed())
  1635  			}
  1636  		})
  1637  	})
  1638  
  1639  	When("creating cluster with all workloadTypes (being Stateless|Stateful|Consensus|Replication) component", func() {
  1640  		compNameNDef := map[string]string{
  1641  			statelessCompName:   statelessCompDefName,
  1642  			statefulCompName:    statefulCompDefName,
  1643  			consensusCompName:   consensusCompDefName,
  1644  			replicationCompName: replicationCompDefName,
  1645  		}
  1646  
  1647  		BeforeEach(func() {
  1648  			createAllWorkloadTypesClusterDef()
  1649  			createBackupPolicyTpl(clusterDefObj)
  1650  		})
  1651  		AfterEach(func() {
  1652  			cleanEnv()
  1653  		})
  1654  
  1655  		for compName, compDefName := range compNameNDef {
  1656  			It(fmt.Sprintf("[comp: %s] should create/delete pods to match the desired replica number if updating cluster's replica number to a valid value", compName), func() {
  1657  				testChangeReplicas(compName, compDefName)
  1658  			})
  1659  
  1660  			Context(fmt.Sprintf("[comp: %s] and with cluster affinity set", compName), func() {
  1661  				It("should create pod with cluster affinity", func() {
  1662  					testClusterAffinity(compName, compDefName)
  1663  				})
  1664  			})
  1665  
  1666  			Context(fmt.Sprintf("[comp: %s] and with both cluster affinity and component affinity set", compName), func() {
  1667  				It("Should observe the component affinity will override the cluster affinity", func() {
  1668  					testComponentAffinity(compName, compDefName)
  1669  				})
  1670  			})
  1671  
  1672  			Context(fmt.Sprintf("[comp: %s] and with cluster tolerations set", compName), func() {
  1673  				It("Should create pods with cluster tolerations", func() {
  1674  					testClusterToleration(compName, compDefName)
  1675  				})
  1676  			})
  1677  
  1678  			Context(fmt.Sprintf("[comp: %s] and with both cluster tolerations and component tolerations set", compName), func() {
  1679  				It("Should observe the component tolerations will override the cluster tolerations", func() {
  1680  					testStsWorkloadComponentToleration(compName, compDefName)
  1681  				})
  1682  			})
  1683  
  1684  			It(fmt.Sprintf("[comp: %s] update kubeblocks-tools image", compName), func() {
  1685  				testUpdateKubeBlocksToolsImage(compName, compDefName)
  1686  			})
  1687  		}
  1688  	})
  1689  
  1690  	When("creating cluster with stateful workloadTypes (being Stateful|Consensus|Replication) component", func() {
  1691  		var (
  1692  			mockStorageClass *storagev1.StorageClass
  1693  		)
  1694  
  1695  		compNameNDef := map[string]string{
  1696  			statefulCompName:    statefulCompDefName,
  1697  			consensusCompName:   consensusCompDefName,
  1698  			replicationCompName: replicationCompDefName,
  1699  		}
  1700  
  1701  		BeforeEach(func() {
  1702  			createAllWorkloadTypesClusterDef()
  1703  			createBackupPolicyTpl(clusterDefObj)
  1704  			mockStorageClass = testk8s.CreateMockStorageClass(&testCtx, testk8s.DefaultStorageClassName)
  1705  		})
  1706  
  1707  		for compName, compDefName := range compNameNDef {
  1708  			Context(fmt.Sprintf("[comp: %s] volume expansion", compName), func() {
  1709  				It("should update PVC request storage size accordingly", func() {
  1710  					testVolumeExpansion(compName, compDefName, mockStorageClass)
  1711  				})
  1712  
  1713  				It("should be able to recover if volume expansion fails", func() {
  1714  					testVolumeExpansionFailedAndRecover(compName, compDefName)
  1715  				})
  1716  			})
  1717  
  1718  			Context(fmt.Sprintf("[comp: %s] horizontal scale", compName), func() {
  1719  				It("scale-out from 1 to 3 with backup(snapshot) policy normally", func() {
  1720  					testHorizontalScale(compName, compDefName, 1, 3, appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1721  				})
  1722  
  1723  				It("backup error at scale-out", func() {
  1724  					testBackupError(compName, compDefName)
  1725  				})
  1726  
  1727  				It("scale-out without data clone policy", func() {
  1728  					testHorizontalScale(compName, compDefName, 1, 3, "")
  1729  				})
  1730  
  1731  				It("scale-in from 3 to 1", func() {
  1732  					testHorizontalScale(compName, compDefName, 3, 1, appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1733  				})
  1734  
  1735  				It("scale-in to 0 and PVCs should not been deleted", func() {
  1736  					testHorizontalScale(compName, compDefName, 3, 0, appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1737  				})
  1738  
  1739  				It("scale-out from 0 and should work well", func() {
  1740  					testHorizontalScale(compName, compDefName, 0, 3, appsv1alpha1.HScaleDataClonePolicyCloneVolume)
  1741  				})
  1742  			})
  1743  
  1744  			Context(fmt.Sprintf("[comp: %s] scale-out after volume expansion", compName), func() {
  1745  				It("scale-out with data clone policy", func() {
  1746  					testVolumeExpansion(compName, compDefName, mockStorageClass)
  1747  					testk8s.MockEnableVolumeSnapshot(&testCtx, mockStorageClass.Name)
  1748  					horizontalScale(5, mockStorageClass.Name, appsv1alpha1.HScaleDataClonePolicyCloneVolume, compDefName)
  1749  				})
  1750  
  1751  				It("scale-out without data clone policy", func() {
  1752  					testVolumeExpansion(compName, compDefName, mockStorageClass)
  1753  					horizontalScale(5, mockStorageClass.Name, "", compDefName)
  1754  				})
  1755  			})
  1756  		}
  1757  	})
  1758  
  1759  	When("creating cluster with workloadType=consensus component", func() {
  1760  		const (
  1761  			compName    = consensusCompName
  1762  			compDefName = consensusCompDefName
  1763  		)
  1764  
  1765  		BeforeEach(func() {
  1766  			createAllWorkloadTypesClusterDef()
  1767  			createBackupPolicyTpl(clusterDefObj)
  1768  		})
  1769  
  1770  		It("Should success with one leader pod and two follower pods", func() {
  1771  			testThreeReplicas(compName, compDefName)
  1772  		})
  1773  
  1774  		It("test restore cluster from backup", func() {
  1775  			By("mock backuptool object")
  1776  			backupPolicyName := "test-backup-policy"
  1777  			backupName := "test-backup"
  1778  			_ = testapps.CreateCustomizedObj(&testCtx, "backup/actionset.yaml",
  1779  				&dpv1alpha1.ActionSet{}, testapps.RandomizedObjName())
  1780  
  1781  			By("creating backup")
  1782  			backup := testdp.NewBackupFactory(testCtx.DefaultNamespace, backupName).
  1783  				SetBackupPolicyName(backupPolicyName).
  1784  				SetBackupMethod(testdp.BackupMethodName).
  1785  				Create(&testCtx).GetObject()
  1786  
  1787  			By("mocking backup status completed, we don't need backup reconcile here")
  1788  			Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(backup), func(backup *dpv1alpha1.Backup) {
  1789  				backup.Status.PersistentVolumeClaimName = "backup-pvc"
  1790  				backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted
  1791  				testdp.MockBackupStatusMethod(backup, testdp.BackupMethodName, testapps.DataVolumeName, testdp.ActionSetName)
  1792  			})).Should(Succeed())
  1793  
  1794  			By("creating cluster with backup")
  1795  			restoreFromBackup := fmt.Sprintf(`{"%s":{"name":"%s"}}`, compName, backupName)
  1796  			pvcSpec := testapps.NewPVCSpec("1Gi")
  1797  			replicas := 3
  1798  			clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1799  				clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1800  				AddComponent(compName, compDefName).
  1801  				SetReplicas(int32(replicas)).
  1802  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
  1803  				AddAnnotations(constant.RestoreFromBackupAnnotationKey, restoreFromBackup).Create(&testCtx).GetObject()
  1804  			clusterKey = client.ObjectKeyFromObject(clusterObj)
  1805  
  1806  			// mock pvcs have restored
  1807  			mockComponentPVCsAndBound(clusterObj.Spec.GetComponentByName(compName), replicas, true, testk8s.DefaultStorageClassName)
  1808  			By("wait for restore created")
  1809  			ml := client.MatchingLabels{
  1810  				constant.AppInstanceLabelKey:    clusterKey.Name,
  1811  				constant.KBAppComponentLabelKey: compName,
  1812  			}
  1813  			Eventually(testapps.List(&testCtx, generics.RestoreSignature,
  1814  				ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(1))
  1815  
  1816  			By("Mocking restore phase to Completed")
  1817  			// mock prepareData restore completed
  1818  			mockRestoreCompleted(ml)
  1819  
  1820  			By("Waiting for the cluster controller to create resources completely")
  1821  			waitForCreatingResourceCompletely(clusterKey, compName)
  1822  
  1823  			rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
  1824  			rsm := rsmList.Items[0]
  1825  			sts := testapps.NewStatefulSetFactory(rsm.Namespace, rsm.Name, clusterKey.Name, compName).
  1826  				SetReplicas(*rsm.Spec.Replicas).
  1827  				Create(&testCtx).GetObject()
  1828  			By("mock pod/sts are available and wait for component enter running phase")
  1829  			mockPods := testapps.MockConsensusComponentPods(&testCtx, sts, clusterObj.Name, compName)
  1830  			Expect(testapps.ChangeObjStatus(&testCtx, sts, func() {
  1831  				testk8s.MockStatefulSetReady(sts)
  1832  			})).ShouldNot(HaveOccurred())
  1833  			Expect(testapps.ChangeObjStatus(&testCtx, &rsm, func() {
  1834  				testk8s.MockRSMReady(&rsm, mockPods...)
  1835  			})).ShouldNot(HaveOccurred())
  1836  			Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(appsv1alpha1.RunningClusterCompPhase))
  1837  
  1838  			By("the restore container has been removed from init containers")
  1839  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(&rsm), func(g Gomega, tmpRSM *workloads.ReplicatedStateMachine) {
  1840  				g.Expect(tmpRSM.Spec.Template.Spec.InitContainers).Should(BeEmpty())
  1841  			})).Should(Succeed())
  1842  
  1843  			By("clean up annotations after cluster running")
  1844  			Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, tmpCluster *appsv1alpha1.Cluster) {
  1845  				g.Expect(tmpCluster.Status.Phase).Should(Equal(appsv1alpha1.RunningClusterPhase))
  1846  				// mock postReady restore completed
  1847  				mockRestoreCompleted(ml)
  1848  				g.Expect(tmpCluster.Annotations[constant.RestoreFromBackupAnnotationKey]).Should(BeEmpty())
  1849  			})).Should(Succeed())
  1850  		})
  1851  	})
  1852  
  1853  	When("creating cluster with workloadType=replication component", func() {
  1854  		const (
  1855  			compName    = replicationCompName
  1856  			compDefName = replicationCompDefName
  1857  		)
  1858  		BeforeEach(func() {
  1859  			createAllWorkloadTypesClusterDef()
  1860  			createBackupPolicyTpl(clusterDefObj)
  1861  		})
  1862  
  1863  		// REVIEW/TODO: following test always failed at cluster.phase.observerGeneration=1
  1864  		//     with cluster.phase.phase=creating
  1865  		It("Should success with primary pod and secondary pod", func() {
  1866  			By("Mock a cluster obj with replication componentDefRef.")
  1867  			pvcSpec := testapps.NewPVCSpec("1Gi")
  1868  			clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
  1869  				clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
  1870  				AddComponent(compName, compDefName).
  1871  				SetReplicas(testapps.DefaultReplicationReplicas).
  1872  				AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec).
  1873  				Create(&testCtx).GetObject()
  1874  			clusterKey = client.ObjectKeyFromObject(clusterObj)
  1875  
  1876  			By("Waiting for the cluster controller to create resources completely")
  1877  			waitForCreatingResourceCompletely(clusterKey, compDefName)
  1878  
  1879  			By("Checking statefulSet number")
  1880  			rsmList := testk8s.ListAndCheckRSMItemsCount(&testCtx, clusterKey, 1)
  1881  			rsm := &rsmList.Items[0]
  1882  			sts := testapps.NewStatefulSetFactory(rsm.Namespace, rsm.Name, clusterKey.Name, compName).
  1883  				SetReplicas(*rsm.Spec.Replicas).Create(&testCtx).GetObject()
  1884  			mockPods := testapps.MockReplicationComponentPods(nil, testCtx, sts, clusterObj.Name, compDefName, nil)
  1885  			Expect(testapps.ChangeObjStatus(&testCtx, sts, func() {
  1886  				testk8s.MockStatefulSetReady(sts)
  1887  			})).ShouldNot(HaveOccurred())
  1888  			Expect(testapps.ChangeObjStatus(&testCtx, rsm, func() {
  1889  				testk8s.MockRSMReady(rsm, mockPods...)
  1890  			})).ShouldNot(HaveOccurred())
  1891  			Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
  1892  		})
  1893  	})
  1894  })
  1895  
  1896  func createBackupPolicyTpl(clusterDefObj *appsv1alpha1.ClusterDefinition, mappingClusterVersions ...string) {
  1897  	By("Creating a BackupPolicyTemplate")
  1898  	bpt := testapps.NewBackupPolicyTemplateFactory(backupPolicyTPLName).
  1899  		AddLabels(constant.ClusterDefLabelKey, clusterDefObj.Name).
  1900  		SetClusterDefRef(clusterDefObj.Name)
  1901  	for _, v := range clusterDefObj.Spec.ComponentDefs {
  1902  		bpt = bpt.AddBackupPolicy(v.Name).
  1903  			AddBackupMethod(backupMethodName, false, actionSetName, mappingClusterVersions...).
  1904  			SetBackupMethodVolumeMounts("data", "/data").
  1905  			AddBackupMethod(vsBackupMethodName, true, vsActionSetName).
  1906  			SetBackupMethodVolumes([]string{"data"}).
  1907  			AddSchedule(backupMethodName, "0 0 * * *", true).
  1908  			AddSchedule(vsBackupMethodName, "0 0 * * *", true)
  1909  		switch v.WorkloadType {
  1910  		case appsv1alpha1.Consensus:
  1911  			bpt.SetTargetRole("leader")
  1912  		case appsv1alpha1.Replication:
  1913  			bpt.SetTargetRole("primary")
  1914  		}
  1915  	}
  1916  	bpt.Create(&testCtx)
  1917  }
  1918  
  1919  func mockRestoreCompleted(ml client.MatchingLabels) {
  1920  	restoreList := dpv1alpha1.RestoreList{}
  1921  	Expect(testCtx.Cli.List(testCtx.Ctx, &restoreList, ml)).Should(Succeed())
  1922  	for _, rs := range restoreList.Items {
  1923  		err := testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(&rs), func(res *dpv1alpha1.Restore) {
  1924  			res.Status.Phase = dpv1alpha1.RestorePhaseCompleted
  1925  		})()
  1926  		Expect(client.IgnoreNotFound(err)).ShouldNot(HaveOccurred())
  1927  	}
  1928  }
  1929  
  1930  func checkRestoreAndSetCompleted(clusterKey types.NamespacedName, compName string, scaleOutReplicas int) {
  1931  	By("Checking restore CR created")
  1932  	ml := client.MatchingLabels{
  1933  		constant.AppInstanceLabelKey:    clusterKey.Name,
  1934  		constant.KBAppComponentLabelKey: compName,
  1935  		constant.KBManagedByKey:         "cluster",
  1936  	}
  1937  	Eventually(testapps.List(&testCtx, generics.RestoreSignature,
  1938  		ml, client.InNamespace(clusterKey.Namespace))).Should(HaveLen(scaleOutReplicas))
  1939  
  1940  	By("Mocking restore phase to succeeded")
  1941  	mockRestoreCompleted(ml)
  1942  }