github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/cluster_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  	"encoding/json"
    24  	"fmt"
    25  	"reflect"
    26  	"strings"
    27  	"time"
    28  
    29  	. "github.com/onsi/ginkgo/v2"
    30  	. "github.com/onsi/gomega"
    31  
    32  	"github.com/sethvargo/go-password/password"
    33  	"golang.org/x/exp/slices"
    34  	appsv1 "k8s.io/api/apps/v1"
    35  	corev1 "k8s.io/api/core/v1"
    36  	rbacv1 "k8s.io/api/rbac/v1"
    37  	"k8s.io/apimachinery/pkg/api/meta"
    38  	"k8s.io/apimachinery/pkg/api/resource"
    39  	"k8s.io/apimachinery/pkg/types"
    40  	"k8s.io/apimachinery/pkg/util/intstr"
    41  	"sigs.k8s.io/controller-runtime/pkg/client"
    42  
    43  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    44  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    45  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    46  	"github.com/1aal/kubeblocks/controllers/apps/components"
    47  	"github.com/1aal/kubeblocks/pkg/constant"
    48  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    49  	"github.com/1aal/kubeblocks/pkg/generics"
    50  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    51  	testdp "github.com/1aal/kubeblocks/pkg/testutil/dataprotection"
    52  	testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    53  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    54  )
    55  
    56  var _ = Describe("Cluster Controller", func() {
    57  	const (
    58  		clusterDefName     = "test-clusterdef"
    59  		clusterVersionName = "test-clusterversion"
    60  		clusterName        = "test-cluster" // this become cluster prefix name if used with testapps.NewClusterFactory().WithRandomName()
    61  		// REVIEW:
    62  		// - setup componentName and componentDefName as map entry pair
    63  		statelessCompName      = "stateless"
    64  		statelessCompDefName   = "stateless"
    65  		statefulCompName       = "stateful"
    66  		statefulCompDefName    = "stateful"
    67  		consensusCompName      = "consensus"
    68  		consensusCompDefName   = "consensus"
    69  		replicationCompName    = "replication"
    70  		replicationCompDefName = "replication"
    71  	)
    72  
    73  	var (
    74  		clusterNameRand        string
    75  		clusterDefNameRand     string
    76  		clusterVersionNameRand string
    77  		clusterDefObj          *appsv1alpha1.ClusterDefinition
    78  		clusterVersionObj      *appsv1alpha1.ClusterVersion
    79  		clusterObj             *appsv1alpha1.Cluster
    80  		clusterKey             types.NamespacedName
    81  		allSettings            map[string]interface{}
    82  	)
    83  
    84  	resetViperCfg := func() {
    85  		if allSettings != nil {
    86  			Expect(viper.MergeConfigMap(allSettings)).ShouldNot(HaveOccurred())
    87  			allSettings = nil
    88  		}
    89  	}
    90  
    91  	resetTestContext := func() {
    92  		clusterDefObj = nil
    93  		clusterVersionObj = nil
    94  		clusterObj = nil
    95  		randomStr := testCtx.GetRandomStr()
    96  		clusterNameRand = "mysql-" + randomStr
    97  		clusterDefNameRand = "mysql-definition-" + randomStr
    98  		clusterVersionNameRand = "mysql-cluster-version-" + randomStr
    99  		resetViperCfg()
   100  	}
   101  
   102  	// Cleanups
   103  	cleanEnv := func() {
   104  		// must wait till resources deleted and no longer existed before the testcases start,
   105  		// otherwise if later it needs to create some new resource objects with the same name,
   106  		// in race conditions, it will find the existence of old objects, resulting failure to
   107  		// create the new objects.
   108  		By("clean resources")
   109  
   110  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
   111  		testapps.ClearClusterResourcesWithRemoveFinalizerOption(&testCtx)
   112  
   113  		// delete rest mocked objects
   114  		inNS := client.InNamespace(testCtx.DefaultNamespace)
   115  		ml := client.HasLabels{testCtx.TestObjLabelKey}
   116  		// namespaced
   117  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PersistentVolumeClaimSignature, true, inNS, ml)
   118  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.PodSignature, true, inNS, ml)
   119  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupSignature, true, inNS, ml)
   120  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.BackupPolicySignature, true, inNS, ml)
   121  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.VolumeSnapshotSignature, true, inNS)
   122  		// non-namespaced
   123  		testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml)
   124  		testapps.ClearResources(&testCtx, generics.ActionSetSignature, ml)
   125  		testapps.ClearResources(&testCtx, generics.StorageClassSignature, ml)
   126  		resetTestContext()
   127  	}
   128  
   129  	BeforeEach(func() {
   130  		cleanEnv()
   131  		allSettings = viper.AllSettings()
   132  	})
   133  
   134  	AfterEach(func() {
   135  		cleanEnv()
   136  	})
   137  
   138  	// test function helpers
   139  	createAllWorkloadTypesClusterDef := func(noCreateAssociateCV ...bool) {
   140  		By("Create a clusterDefinition obj")
   141  		clusterDefObj = testapps.NewClusterDefFactory(clusterDefName).
   142  			AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName).
   143  			AddComponentDef(testapps.ConsensusMySQLComponent, consensusCompDefName).
   144  			AddComponentDef(testapps.ReplicationRedisComponent, replicationCompDefName).
   145  			AddComponentDef(testapps.StatelessNginxComponent, statelessCompDefName).
   146  			Create(&testCtx).GetObject()
   147  
   148  		if len(noCreateAssociateCV) > 0 && noCreateAssociateCV[0] {
   149  			return
   150  		}
   151  		By("Create a clusterVersion obj")
   152  		clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()).
   153  			AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   154  			AddComponentVersion(consensusCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   155  			AddComponentVersion(replicationCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   156  			AddComponentVersion(statelessCompDefName).AddContainerShort("nginx", testapps.NginxImage).
   157  			Create(&testCtx).GetObject()
   158  	}
   159  
   160  	waitForCreatingResourceCompletely := func(clusterKey client.ObjectKey, compNames ...string) {
   161  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   162  		cluster := &appsv1alpha1.Cluster{}
   163  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, cluster, true)).Should(Succeed())
   164  		for _, compName := range compNames {
   165  			compPhase := appsv1alpha1.CreatingClusterCompPhase
   166  			for _, spec := range cluster.Spec.ComponentSpecs {
   167  				if spec.Name == compName && spec.Replicas == 0 {
   168  					compPhase = appsv1alpha1.StoppedClusterCompPhase
   169  				}
   170  			}
   171  			Eventually(testapps.GetClusterComponentPhase(&testCtx, clusterKey, compName)).Should(Equal(compPhase))
   172  		}
   173  	}
   174  
   175  	type ExpectService struct {
   176  		headless bool
   177  		svcType  corev1.ServiceType
   178  	}
   179  
   180  	// getHeadlessSvcPorts returns the component's headless service ports by gathering all container's ports in the
   181  	// ClusterComponentDefinition.PodSpec, it's a subset of the real ports as some containers can be dynamically
   182  	// injected into the pod by the lifecycle controller, such as the probe container.
   183  	getHeadlessSvcPorts := func(g Gomega, compDefName string) []corev1.ServicePort {
   184  		comp, err := appsv1alpha1.GetComponentDefByCluster(testCtx.Ctx, k8sClient, *clusterObj, compDefName)
   185  		g.Expect(err).ShouldNot(HaveOccurred())
   186  		var headlessSvcPorts []corev1.ServicePort
   187  		for _, container := range comp.PodSpec.Containers {
   188  			for _, port := range container.Ports {
   189  				// be consistent with headless_service_template.cue
   190  				headlessSvcPorts = append(headlessSvcPorts, corev1.ServicePort{
   191  					Name:       port.Name,
   192  					Protocol:   port.Protocol,
   193  					Port:       port.ContainerPort,
   194  					TargetPort: intstr.FromString(port.Name),
   195  				})
   196  			}
   197  		}
   198  		return headlessSvcPorts
   199  	}
   200  
   201  	validateCompSvcList := func(g Gomega, compName string, compDefName string, expectServices map[string]ExpectService) {
   202  		if intctrlutil.IsRSMEnabled() {
   203  			return
   204  		}
   205  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   206  
   207  		svcList := &corev1.ServiceList{}
   208  		g.Expect(k8sClient.List(testCtx.Ctx, svcList, client.MatchingLabels{
   209  			constant.AppInstanceLabelKey:    clusterKey.Name,
   210  			constant.KBAppComponentLabelKey: compName,
   211  		}, client.InNamespace(clusterKey.Namespace))).Should(Succeed())
   212  
   213  		for svcName, svcSpec := range expectServices {
   214  			idx := slices.IndexFunc(svcList.Items, func(e corev1.Service) bool {
   215  				parts := []string{clusterKey.Name, compName}
   216  				if svcName != "" {
   217  					parts = append(parts, svcName)
   218  				}
   219  				return strings.Join(parts, "-") == e.Name
   220  			})
   221  			g.Expect(idx >= 0).To(BeTrue())
   222  			svc := svcList.Items[idx]
   223  			g.Expect(svc.Spec.Type).Should(Equal(svcSpec.svcType))
   224  			switch {
   225  			case svc.Spec.Type == corev1.ServiceTypeLoadBalancer:
   226  				g.Expect(svc.Spec.ExternalTrafficPolicy).Should(Equal(corev1.ServiceExternalTrafficPolicyTypeLocal))
   227  			case svc.Spec.Type == corev1.ServiceTypeClusterIP && !svcSpec.headless:
   228  				g.Expect(svc.Spec.ClusterIP).ShouldNot(Equal(corev1.ClusterIPNone))
   229  			case svc.Spec.Type == corev1.ServiceTypeClusterIP && svcSpec.headless:
   230  				g.Expect(svc.Spec.ClusterIP).Should(Equal(corev1.ClusterIPNone))
   231  				for _, port := range getHeadlessSvcPorts(g, compDefName) {
   232  					g.Expect(slices.Index(svc.Spec.Ports, port) >= 0).Should(BeTrue())
   233  				}
   234  			}
   235  		}
   236  		g.Expect(len(expectServices)).Should(Equal(len(svcList.Items)))
   237  	}
   238  
   239  	testServiceAddAndDelete := func(compName, compDefName string) {
   240  		By("Creating a cluster with two LoadBalancer services")
   241  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   242  			clusterDefObj.Name, clusterVersionObj.Name).
   243  			AddComponent(compName, compDefName).SetReplicas(1).
   244  			AddService(testapps.ServiceVPCName, corev1.ServiceTypeLoadBalancer).
   245  			AddService(testapps.ServiceInternetName, corev1.ServiceTypeLoadBalancer).
   246  			WithRandomName().Create(&testCtx).GetObject()
   247  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   248  
   249  		By("Waiting for the cluster controller to create resources completely")
   250  		waitForCreatingResourceCompletely(clusterKey, compName)
   251  
   252  		expectServices := map[string]ExpectService{
   253  			testapps.ServiceHeadlessName: {svcType: corev1.ServiceTypeClusterIP, headless: true},
   254  			testapps.ServiceDefaultName:  {svcType: corev1.ServiceTypeClusterIP, headless: false},
   255  			testapps.ServiceVPCName:      {svcType: corev1.ServiceTypeLoadBalancer, headless: false},
   256  			testapps.ServiceInternetName: {svcType: corev1.ServiceTypeLoadBalancer, headless: false},
   257  		}
   258  		Eventually(func(g Gomega) { validateCompSvcList(g, compName, compDefName, expectServices) }).Should(Succeed())
   259  
   260  		By("Delete a LoadBalancer service")
   261  		deleteService := testapps.ServiceVPCName
   262  		delete(expectServices, deleteService)
   263  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   264  			for idx, comp := range cluster.Spec.ComponentSpecs {
   265  				if comp.ComponentDefRef != compDefName || comp.Name != compName {
   266  					continue
   267  				}
   268  				var services []appsv1alpha1.ClusterComponentService
   269  				for _, item := range comp.Services {
   270  					if item.Name == deleteService {
   271  						continue
   272  					}
   273  					services = append(services, item)
   274  				}
   275  				cluster.Spec.ComponentSpecs[idx].Services = services
   276  				return
   277  			}
   278  		})()).ShouldNot(HaveOccurred())
   279  		Eventually(func(g Gomega) { validateCompSvcList(g, compName, compDefName, expectServices) }).Should(Succeed())
   280  
   281  		By("Add the deleted LoadBalancer service back")
   282  		expectServices[deleteService] = ExpectService{svcType: corev1.ServiceTypeLoadBalancer, headless: false}
   283  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   284  			for idx, comp := range cluster.Spec.ComponentSpecs {
   285  				if comp.ComponentDefRef != compDefName || comp.Name != compName {
   286  					continue
   287  				}
   288  				comp.Services = append(comp.Services, appsv1alpha1.ClusterComponentService{
   289  					Name:        deleteService,
   290  					ServiceType: corev1.ServiceTypeLoadBalancer,
   291  				})
   292  				cluster.Spec.ComponentSpecs[idx] = comp
   293  				return
   294  			}
   295  		})()).ShouldNot(HaveOccurred())
   296  		Eventually(func(g Gomega) { validateCompSvcList(g, compName, compDefName, expectServices) }).Should(Succeed())
   297  	}
   298  
   299  	createClusterObj := func(compName, compDefName string) {
   300  		By("Creating a cluster")
   301  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   302  			clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   303  			AddComponent(compName, compDefName).
   304  			SetReplicas(1).
   305  			Create(&testCtx).GetObject()
   306  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   307  
   308  		By("Waiting for the cluster enter running phase")
   309  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   310  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   311  	}
   312  
   313  	testWipeOut := func(compName, compDefName string) {
   314  		createClusterObj(compName, compDefName)
   315  
   316  		By("Waiting for the cluster enter running phase")
   317  		Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   318  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   319  
   320  		By("Mocking a retained backup")
   321  		backupPolicyName := "test-backup-policy"
   322  		backupName := "test-backup"
   323  		backupMethod := "test-backup-method"
   324  		backup := testdp.NewBackupFactory(testCtx.DefaultNamespace, backupName).
   325  			SetBackupPolicyName(backupPolicyName).
   326  			SetBackupMethod(backupMethod).
   327  			SetLabels(map[string]string{
   328  				constant.AppInstanceLabelKey:      clusterKey.Name,
   329  				constant.BackupProtectionLabelKey: constant.BackupRetain,
   330  			}).
   331  			WithRandomName().
   332  			Create(&testCtx).GetObject()
   333  		backupKey := client.ObjectKeyFromObject(backup)
   334  
   335  		// REVIEW: this test flow
   336  
   337  		By("Delete the cluster")
   338  		testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   339  
   340  		By("Wait for the cluster to terminate")
   341  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   342  
   343  		By("Checking backup should exist")
   344  		Eventually(testapps.CheckObjExists(&testCtx, backupKey, &dpv1alpha1.Backup{}, true)).Should(Succeed())
   345  	}
   346  
   347  	testDoNotTerminate := func(compName, compDefName string) {
   348  		createClusterObj(compName, compDefName)
   349  
   350  		// REVIEW: this test flow
   351  
   352  		// REVIEW: why not set termination upon creation?
   353  		By("Update the cluster's termination policy to DoNotTerminate")
   354  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   355  			cluster.Spec.TerminationPolicy = appsv1alpha1.DoNotTerminate
   356  		})()).ShouldNot(HaveOccurred())
   357  
   358  		By("Delete the cluster")
   359  		testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   360  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, true)).Should(Succeed())
   361  
   362  		By("Update the cluster's termination policy to WipeOut")
   363  		Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   364  			cluster.Spec.TerminationPolicy = appsv1alpha1.WipeOut
   365  		})()).ShouldNot(HaveOccurred())
   366  
   367  		By("Wait for the cluster to terminate")
   368  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   369  	}
   370  
   371  	getPodSpec := func(sts *appsv1.StatefulSet, deploy *appsv1.Deployment) *corev1.PodSpec {
   372  		if sts != nil {
   373  			return &sts.Spec.Template.Spec
   374  		} else if deploy != nil {
   375  			return &deploy.Spec.Template.Spec
   376  		}
   377  		panic("unreachable")
   378  	}
   379  
   380  	checkSingleWorkload := func(compDefName string, expects func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment)) {
   381  		Eventually(func(g Gomega) {
   382  			l := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   383  			sts := components.ConvertRSMToSTS(&l.Items[0])
   384  			expects(g, sts, nil)
   385  		}).Should(Succeed())
   386  	}
   387  
   388  	getPVCName := func(vctName, compName string, i int) string {
   389  		return fmt.Sprintf("%s-%s-%s-%d", vctName, clusterKey.Name, compName, i)
   390  	}
   391  
   392  	createPVC := func(clusterName, pvcName, compName, storageSize, storageClassName string) {
   393  		if storageSize == "" {
   394  			storageSize = "1Gi"
   395  		}
   396  		clusterBytes, _ := json.Marshal(clusterObj)
   397  		testapps.NewPersistentVolumeClaimFactory(testCtx.DefaultNamespace, pvcName, clusterName,
   398  			compName, testapps.DataVolumeName).
   399  			AddLabelsInMap(map[string]string{
   400  				constant.AppInstanceLabelKey:    clusterName,
   401  				constant.KBAppComponentLabelKey: compName,
   402  				constant.AppManagedByLabelKey:   constant.AppName,
   403  			}).AddAnnotations(constant.LastAppliedClusterAnnotationKey, string(clusterBytes)).
   404  			SetStorage(storageSize).
   405  			SetStorageClass(storageClassName).
   406  			CheckedCreate(&testCtx)
   407  	}
   408  
   409  	checkClusterRBACResourcesExistence := func(cluster *appsv1alpha1.Cluster, serviceAccountName string, volumeProtectionEnabled, expectExisted bool) {
   410  		saObjKey := types.NamespacedName{
   411  			Namespace: cluster.Namespace,
   412  			Name:      serviceAccountName,
   413  		}
   414  		rbObjKey := types.NamespacedName{
   415  			Namespace: cluster.Namespace,
   416  			Name:      fmt.Sprintf("kb-%s", cluster.Name),
   417  		}
   418  		Eventually(testapps.CheckObjExists(&testCtx, saObjKey, &corev1.ServiceAccount{}, expectExisted)).Should(Succeed())
   419  		Eventually(testapps.CheckObjExists(&testCtx, rbObjKey, &rbacv1.RoleBinding{}, expectExisted)).Should(Succeed())
   420  		if volumeProtectionEnabled {
   421  			Eventually(testapps.CheckObjExists(&testCtx, rbObjKey, &rbacv1.ClusterRoleBinding{}, expectExisted)).Should(Succeed())
   422  		}
   423  	}
   424  
   425  	testClusterRBAC := func(compName, compDefName string, volumeProtectionEnabled bool) {
   426  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
   427  
   428  		By("Creating a cluster with target service account name")
   429  		serviceAccountName := "test-service-account"
   430  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   431  			clusterDefObj.Name, clusterVersionObj.Name).
   432  			AddComponent(compName, compDefName).SetReplicas(3).
   433  			SetServiceAccountName(serviceAccountName).
   434  			WithRandomName().
   435  			Create(&testCtx).GetObject()
   436  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   437  
   438  		By("Waiting for the cluster controller to create resources completely")
   439  		waitForCreatingResourceCompletely(clusterKey, compName)
   440  
   441  		By("Checking the podSpec.serviceAccountName")
   442  		checkSingleWorkload(compDefName, func(g Gomega, sts *appsv1.StatefulSet, deploy *appsv1.Deployment) {
   443  			podSpec := getPodSpec(sts, deploy)
   444  			g.Expect(podSpec.ServiceAccountName).To(Equal(serviceAccountName))
   445  		})
   446  
   447  		By("check the RBAC resources created exist")
   448  		checkClusterRBACResourcesExistence(clusterObj, serviceAccountName, volumeProtectionEnabled, true)
   449  	}
   450  
   451  	testClusterRBACForBackup := func(compName, compDefName string) {
   452  		// set probes and volumeProtections to nil
   453  		Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj), func(clusterDef *appsv1alpha1.ClusterDefinition) {
   454  			for i := range clusterDef.Spec.ComponentDefs {
   455  				compDef := clusterDef.Spec.ComponentDefs[i]
   456  				if compDef.Name == compDefName {
   457  					compDef.Probes = nil
   458  					compDef.VolumeProtectionSpec = nil
   459  					clusterDef.Spec.ComponentDefs[i] = compDef
   460  					break
   461  				}
   462  			}
   463  		})()).Should(Succeed())
   464  		testClusterRBAC(compName, compDefName, false)
   465  	}
   466  
   467  	testReCreateClusterWithRBAC := func(compName, compDefName string) {
   468  		Expect(compDefName).Should(BeElementOf(statelessCompDefName, statefulCompDefName, replicationCompDefName, consensusCompDefName))
   469  
   470  		randomStr, _ := password.Generate(6, 0, 0, true, false)
   471  		serviceAccountName := "test-sa-" + randomStr
   472  
   473  		By(fmt.Sprintf("Creating a cluster with random service account %s", serviceAccountName))
   474  		clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   475  			clusterDefObj.Name, clusterVersionObj.Name).
   476  			AddComponent(compName, compDefName).SetReplicas(3).
   477  			SetServiceAccountName(serviceAccountName).
   478  			WithRandomName().
   479  			Create(&testCtx).GetObject()
   480  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   481  
   482  		By("Waiting for the cluster controller to create resources completely")
   483  		waitForCreatingResourceCompletely(clusterKey, compName)
   484  
   485  		By("check the RBAC resources created exist")
   486  		checkClusterRBACResourcesExistence(clusterObj, serviceAccountName, true, true)
   487  
   488  		By("Delete the cluster")
   489  		testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   490  
   491  		By("Wait for the cluster to terminate")
   492  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   493  
   494  		By("check the RBAC resources deleted")
   495  		checkClusterRBACResourcesExistence(clusterObj, serviceAccountName, true, false)
   496  
   497  		By("re-create cluster with same name")
   498  		clusterObj = testapps.NewClusterFactory(clusterKey.Namespace, clusterKey.Name,
   499  			clusterDefObj.Name, clusterVersionObj.Name).
   500  			AddComponent(compName, compDefName).SetReplicas(3).
   501  			SetServiceAccountName(serviceAccountName).
   502  			Create(&testCtx).GetObject()
   503  		waitForCreatingResourceCompletely(clusterKey, compName)
   504  
   505  		By("check the RBAC resources re-created exist")
   506  		checkClusterRBACResourcesExistence(clusterObj, serviceAccountName, true, true)
   507  
   508  		By("Delete the cluster")
   509  		testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   510  
   511  		By("Wait for the cluster to terminate")
   512  		Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   513  	}
   514  
   515  	updateClusterAnnotation := func(cluster *appsv1alpha1.Cluster) {
   516  		Expect(testapps.ChangeObj(&testCtx, cluster, func(lcluster *appsv1alpha1.Cluster) {
   517  			lcluster.Annotations = map[string]string{
   518  				"time": time.Now().Format(time.RFC3339),
   519  			}
   520  		})).ShouldNot(HaveOccurred())
   521  	}
   522  
   523  	// TODO: add case: empty image in cd, should report applyResourceFailed condition
   524  	Context("when creating cluster without clusterversion", func() {
   525  		BeforeEach(func() {
   526  			createAllWorkloadTypesClusterDef(true)
   527  		})
   528  
   529  		It("should reconcile to create cluster with no error", func() {
   530  			By("Creating a cluster")
   531  			clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   532  				clusterDefObj.Name, "").
   533  				AddComponent(statelessCompName, statelessCompDefName).SetReplicas(3).
   534  				AddComponent(statefulCompName, statefulCompDefName).SetReplicas(3).
   535  				AddComponent(consensusCompName, consensusCompDefName).SetReplicas(3).
   536  				AddComponent(replicationCompName, replicationCompDefName).SetReplicas(3).
   537  				WithRandomName().Create(&testCtx).GetObject()
   538  			clusterKey = client.ObjectKeyFromObject(clusterObj)
   539  
   540  			By("Waiting for the cluster controller to create resources completely")
   541  			waitForCreatingResourceCompletely(clusterKey, statelessCompName, statefulCompName, consensusCompName, replicationCompName)
   542  		})
   543  	})
   544  
   545  	Context("when creating cluster with multiple kinds of components", func() {
   546  		BeforeEach(func() {
   547  			cleanEnv()
   548  			createAllWorkloadTypesClusterDef()
   549  		})
   550  
   551  		createNWaitClusterObj := func(components map[string]string,
   552  			addedComponentProcessor func(compName string, factory *testapps.MockClusterFactory),
   553  			withFixedName ...bool) {
   554  			Expect(components).ShouldNot(BeEmpty())
   555  
   556  			By("Creating a cluster")
   557  			clusterBuilder := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   558  				clusterDefObj.Name, clusterVersionObj.Name)
   559  
   560  			compNames := make([]string, 0, len(components))
   561  			for compName, compDefName := range components {
   562  				clusterBuilder = clusterBuilder.AddComponent(compName, compDefName)
   563  				if addedComponentProcessor != nil {
   564  					addedComponentProcessor(compName, clusterBuilder)
   565  				}
   566  				compNames = append(compNames, compName)
   567  			}
   568  			if len(withFixedName) == 0 || !withFixedName[0] {
   569  				clusterBuilder.WithRandomName()
   570  			}
   571  			clusterObj = clusterBuilder.Create(&testCtx).GetObject()
   572  			clusterKey = client.ObjectKeyFromObject(clusterObj)
   573  
   574  			By("Waiting for the cluster controller to create resources completely")
   575  			waitForCreatingResourceCompletely(clusterKey, compNames...)
   576  		}
   577  
   578  		checkAllResourcesCreated := func(compNameNDef map[string]string) {
   579  			createNWaitClusterObj(compNameNDef, func(compName string, factory *testapps.MockClusterFactory) {
   580  				factory.SetReplicas(3)
   581  			}, true)
   582  
   583  			for compName := range compNameNDef {
   584  				By(fmt.Sprintf("Check %s workload has been created", compName))
   585  				Eventually(testapps.List(&testCtx, generics.RSMSignature,
   586  					client.MatchingLabels{
   587  						constant.AppInstanceLabelKey:    clusterKey.Name,
   588  						constant.KBAppComponentLabelKey: compName,
   589  					}, client.InNamespace(clusterKey.Namespace))).ShouldNot(HaveLen(0))
   590  			}
   591  			rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   592  
   593  			By("Check stateful pod's volumes")
   594  			for _, sts := range rsmList.Items {
   595  				podSpec := sts.Spec.Template
   596  				volumeNames := map[string]struct{}{}
   597  				for _, v := range podSpec.Spec.Volumes {
   598  					volumeNames[v.Name] = struct{}{}
   599  				}
   600  
   601  				for _, cc := range [][]corev1.Container{
   602  					podSpec.Spec.Containers,
   603  					podSpec.Spec.InitContainers,
   604  				} {
   605  					for _, c := range cc {
   606  						for _, vm := range c.VolumeMounts {
   607  							_, ok := volumeNames[vm.Name]
   608  							Expect(ok).Should(BeTrue())
   609  						}
   610  					}
   611  				}
   612  			}
   613  
   614  			By("Check associated Secret has been created")
   615  			Eventually(testapps.List(&testCtx, generics.SecretSignature,
   616  				client.MatchingLabels{
   617  					constant.AppInstanceLabelKey: clusterKey.Name,
   618  				})).ShouldNot(BeEmpty())
   619  
   620  			By("Check associated CM has been created")
   621  			Eventually(testapps.List(&testCtx, generics.ConfigMapSignature,
   622  				client.MatchingLabels{
   623  					constant.AppInstanceLabelKey: clusterKey.Name,
   624  				})).ShouldNot(BeEmpty())
   625  
   626  			By("Check associated PDB has been created")
   627  			Eventually(testapps.List(&testCtx, generics.PodDisruptionBudgetSignature,
   628  				client.MatchingLabels{
   629  					constant.AppInstanceLabelKey: clusterKey.Name,
   630  				}, client.InNamespace(clusterKey.Namespace))).ShouldNot(BeEmpty())
   631  
   632  			podSpec := rsmList.Items[0].Spec.Template.Spec
   633  			By("Checking created rsm pods template with built-in toleration")
   634  			Expect(podSpec.Tolerations).Should(HaveLen(1))
   635  			Expect(podSpec.Tolerations[0].Key).To(Equal(testDataPlaneTolerationKey))
   636  
   637  			By("Checking created rsm pods template with built-in Affinity")
   638  			Expect(podSpec.Affinity.PodAntiAffinity == nil && podSpec.Affinity.PodAffinity == nil).Should(BeTrue())
   639  			Expect(podSpec.Affinity.NodeAffinity).ShouldNot(BeNil())
   640  			Expect(podSpec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions[0].Key).To(
   641  				Equal(testDataPlaneNodeAffinityKey))
   642  
   643  			By("Checking created rsm pods template without TopologySpreadConstraints")
   644  			Expect(podSpec.TopologySpreadConstraints).Should(BeEmpty())
   645  
   646  			By("Checking stateless services")
   647  			statelessExpectServices := map[string]ExpectService{
   648  				// TODO: fix me later, proxy should not have internal headless service
   649  				testapps.ServiceHeadlessName: {svcType: corev1.ServiceTypeClusterIP, headless: true},
   650  				testapps.ServiceDefaultName:  {svcType: corev1.ServiceTypeClusterIP, headless: false},
   651  			}
   652  			Eventually(func(g Gomega) {
   653  				validateCompSvcList(g, statelessCompName, statelessCompDefName, statelessExpectServices)
   654  			}).Should(Succeed())
   655  
   656  			By("Checking stateful types services")
   657  			for compName, compNameNDef := range compNameNDef {
   658  				if compName == statelessCompName {
   659  					continue
   660  				}
   661  				consensusExpectServices := map[string]ExpectService{
   662  					testapps.ServiceHeadlessName: {svcType: corev1.ServiceTypeClusterIP, headless: true},
   663  					testapps.ServiceDefaultName:  {svcType: corev1.ServiceTypeClusterIP, headless: false},
   664  				}
   665  				Eventually(func(g Gomega) {
   666  					validateCompSvcList(g, compName, compNameNDef, consensusExpectServices)
   667  				}).Should(Succeed())
   668  			}
   669  		}
   670  
   671  		It("should create all sub-resources successfully, with terminationPolicy=Halt lifecycle", func() {
   672  			compNameNDef := map[string]string{
   673  				statelessCompName:   statelessCompDefName,
   674  				consensusCompName:   consensusCompDefName,
   675  				statefulCompName:    statefulCompDefName,
   676  				replicationCompName: replicationCompDefName,
   677  			}
   678  			checkAllResourcesCreated(compNameNDef)
   679  
   680  			By("Mocking components' PVCs to bound")
   681  			var items []client.Object
   682  			rsmList := testk8s.ListAndCheckRSM(&testCtx, clusterKey)
   683  			for i := range rsmList.Items {
   684  				items = append(items, &rsmList.Items[i])
   685  			}
   686  			for _, item := range items {
   687  				compName, ok := item.GetLabels()[constant.KBAppComponentLabelKey]
   688  				Expect(ok).Should(BeTrue())
   689  				replicas := reflect.ValueOf(item).Elem().FieldByName("Spec").FieldByName("Replicas").Elem().Int()
   690  				for i := int(replicas); i >= 0; i-- {
   691  					pvcKey := types.NamespacedName{
   692  						Namespace: clusterKey.Namespace,
   693  						Name:      getPVCName(testapps.DataVolumeName, compName, i),
   694  					}
   695  					createPVC(clusterKey.Name, pvcKey.Name, compName, "", "")
   696  					Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
   697  					Expect(testapps.GetAndChangeObjStatus(&testCtx, pvcKey, func(pvc *corev1.PersistentVolumeClaim) {
   698  						pvc.Status.Phase = corev1.ClaimBound
   699  					})()).ShouldNot(HaveOccurred())
   700  				}
   701  			}
   702  
   703  			By("delete the cluster and should be preserved PVC,Secret,CM resources")
   704  			deleteCluster := func(termPolicy appsv1alpha1.TerminationPolicyType) {
   705  				// TODO: would be better that cluster is created with terminationPolicy=Halt instead of
   706  				// reassign the value after created
   707  				Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) {
   708  					cluster.Spec.TerminationPolicy = termPolicy
   709  				})()).ShouldNot(HaveOccurred())
   710  				testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   711  				Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   712  			}
   713  			deleteCluster(appsv1alpha1.Halt)
   714  
   715  			By("check should preserved PVC,Secret,CM resources")
   716  
   717  			checkPreservedObjects := func(uid types.UID) (*corev1.PersistentVolumeClaimList, *corev1.SecretList, *corev1.ConfigMapList) {
   718  				checkObject := func(obj client.Object) {
   719  					clusterJSON, ok := obj.GetAnnotations()[constant.LastAppliedClusterAnnotationKey]
   720  					Expect(ok).Should(BeTrue())
   721  					Expect(clusterJSON).ShouldNot(BeEmpty())
   722  					lastAppliedCluster := &appsv1alpha1.Cluster{}
   723  					Expect(json.Unmarshal([]byte(clusterJSON), lastAppliedCluster)).ShouldNot(HaveOccurred())
   724  					Expect(lastAppliedCluster.UID).Should(BeEquivalentTo(uid))
   725  				}
   726  				listOptions := []client.ListOption{
   727  					client.InNamespace(clusterKey.Namespace),
   728  					client.MatchingLabels{
   729  						constant.AppInstanceLabelKey: clusterKey.Name,
   730  					},
   731  				}
   732  				pvcList := &corev1.PersistentVolumeClaimList{}
   733  				Expect(k8sClient.List(testCtx.Ctx, pvcList, listOptions...)).Should(Succeed())
   734  
   735  				cmList := &corev1.ConfigMapList{}
   736  				Expect(k8sClient.List(testCtx.Ctx, cmList, listOptions...)).Should(Succeed())
   737  
   738  				secretList := &corev1.SecretList{}
   739  				Expect(k8sClient.List(testCtx.Ctx, secretList, listOptions...)).Should(Succeed())
   740  				if uid != "" {
   741  					By("check pvc resources preserved")
   742  					Expect(pvcList.Items).ShouldNot(BeEmpty())
   743  
   744  					for _, pvc := range pvcList.Items {
   745  						checkObject(&pvc)
   746  					}
   747  					By("check secret resources preserved")
   748  					Expect(secretList.Items).ShouldNot(BeEmpty())
   749  					for _, secret := range secretList.Items {
   750  						checkObject(&secret)
   751  					}
   752  				}
   753  				return pvcList, secretList, cmList
   754  			}
   755  			initPVCList, initSecretList, _ := checkPreservedObjects(clusterObj.UID)
   756  
   757  			By("create recovering cluster")
   758  			lastClusterUID := clusterObj.UID
   759  			checkAllResourcesCreated(compNameNDef)
   760  
   761  			Expect(clusterObj.UID).ShouldNot(Equal(lastClusterUID))
   762  			lastPVCList, lastSecretList, _ := checkPreservedObjects("")
   763  
   764  			Expect(outOfOrderEqualFunc(initPVCList.Items, lastPVCList.Items, func(i corev1.PersistentVolumeClaim, j corev1.PersistentVolumeClaim) bool {
   765  				return i.UID == j.UID
   766  			})).Should(BeTrue())
   767  			Expect(outOfOrderEqualFunc(initSecretList.Items, lastSecretList.Items, func(i corev1.Secret, j corev1.Secret) bool {
   768  				return i.UID == j.UID
   769  			})).Should(BeTrue())
   770  
   771  			By("delete the cluster and should be preserved PVC,Secret,CM resources but result updated the new last applied cluster UID")
   772  			deleteCluster(appsv1alpha1.Halt)
   773  			checkPreservedObjects(clusterObj.UID)
   774  		})
   775  	})
   776  
   777  	When("creating cluster with all workloadTypes (being Stateless|Stateful|Consensus|Replication) component", func() {
   778  		compNameNDef := map[string]string{
   779  			statelessCompName:   statelessCompDefName,
   780  			statefulCompName:    statefulCompDefName,
   781  			consensusCompName:   consensusCompDefName,
   782  			replicationCompName: replicationCompDefName,
   783  		}
   784  
   785  		BeforeEach(func() {
   786  			createAllWorkloadTypesClusterDef()
   787  		})
   788  		AfterEach(func() {
   789  			cleanEnv()
   790  		})
   791  
   792  		for compName, compDefName := range compNameNDef {
   793  			It(fmt.Sprintf("[comp: %s] should delete cluster resources immediately if deleting cluster with terminationPolicy=WipeOut", compName), func() {
   794  				testWipeOut(compName, compDefName)
   795  			})
   796  
   797  			It(fmt.Sprintf("[comp: %s] should not terminate immediately if deleting cluster with terminationPolicy=DoNotTerminate", compName), func() {
   798  				testDoNotTerminate(compName, compDefName)
   799  			})
   800  
   801  			It(fmt.Sprintf("[comp: %s] should add and delete service correctly", compName), func() {
   802  				testServiceAddAndDelete(compName, compDefName)
   803  			})
   804  
   805  			It(fmt.Sprintf("[comp: %s] should create RBAC resources correctly", compName), func() {
   806  				testClusterRBAC(compName, compDefName, true)
   807  			})
   808  
   809  			It(fmt.Sprintf("[comp: %s] should create RBAC resources correctly if only supports backup", compName), func() {
   810  				testClusterRBACForBackup(compName, compDefName)
   811  			})
   812  
   813  			It(fmt.Sprintf("[comp: %s] should re-create cluster and RBAC resources correctly", compName), func() {
   814  				testReCreateClusterWithRBAC(compName, compDefName)
   815  			})
   816  		}
   817  	})
   818  
   819  	Context("test cluster Failed/Abnormal phase", func() {
   820  		It("test cluster conditions", func() {
   821  			By("init cluster")
   822  			cluster := testapps.CreateConsensusMysqlCluster(&testCtx, clusterDefNameRand,
   823  				clusterVersionNameRand, clusterNameRand, consensusCompDefName, consensusCompName,
   824  				"2Gi")
   825  			clusterKey := client.ObjectKeyFromObject(cluster)
   826  
   827  			By("test when clusterDefinition not found")
   828  			Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, tmpCluster *appsv1alpha1.Cluster) {
   829  				g.Expect(tmpCluster.Status.ObservedGeneration).Should(BeZero())
   830  				condition := meta.FindStatusCondition(tmpCluster.Status.Conditions, appsv1alpha1.ConditionTypeProvisioningStarted)
   831  				g.Expect(condition).ShouldNot(BeNil())
   832  				g.Expect(condition.Reason).Should(BeEquivalentTo(ReasonPreCheckFailed))
   833  			})).Should(Succeed())
   834  
   835  			// TODO: removed conditionsError phase need to review correct-ness of following commented off block:
   836  			// By("test conditionsError phase")
   837  			// Expect(testapps.GetAndChangeObjStatus(&testCtx, clusterKey, func(tmpCluster *appsv1alpha1.Cluster) {
   838  			// 	condition := meta.FindStatusCondition(tmpCluster.Status.Conditions, ConditionTypeProvisioningStarted)
   839  			// 	condition.LastTransitionTime = metav1.Time{Time: time.Now().Add(-(time.Millisecond*time.Duration(viper.GetInt(constant.CfgKeyCtrlrReconcileRetryDurationMS)) + time.Second))}
   840  			// 	meta.SetStatusCondition(&tmpCluster.Status.Conditions, *condition)
   841  			// })()).ShouldNot(HaveOccurred())
   842  
   843  			// Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, tmpCluster *appsv1alpha1.Cluster) {
   844  			// 	g.Expect(tmpCluster.Status.Phase == appsv1alpha1.ConditionsErrorPhase).Should(BeTrue())
   845  			// })).Should(Succeed())
   846  
   847  			By("test when clusterVersion not Available")
   848  			clusterVersion := testapps.CreateConsensusMysqlClusterVersion(&testCtx, clusterDefNameRand, clusterVersionNameRand, consensusCompDefName)
   849  			clusterVersionKey := client.ObjectKeyFromObject(clusterVersion)
   850  			// mock clusterVersion unavailable
   851  			Expect(testapps.GetAndChangeObj(&testCtx, clusterVersionKey, func(clusterVersion *appsv1alpha1.ClusterVersion) {
   852  				clusterVersion.Spec.ComponentVersions[0].ComponentDefRef = "test-n"
   853  			})()).ShouldNot(HaveOccurred())
   854  			_ = testapps.CreateConsensusMysqlClusterDef(&testCtx, clusterDefNameRand, consensusCompDefName)
   855  
   856  			Eventually(testapps.CheckObj(&testCtx, clusterVersionKey, func(g Gomega, clusterVersion *appsv1alpha1.ClusterVersion) {
   857  				g.Expect(clusterVersion.Status.Phase).Should(Equal(appsv1alpha1.UnavailablePhase))
   858  			})).Should(Succeed())
   859  
   860  			// trigger reconcile
   861  			Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(tmpCluster *appsv1alpha1.Cluster) {
   862  				tmpCluster.Spec.ComponentSpecs[0].EnabledLogs = []string{"error1"}
   863  			})()).ShouldNot(HaveOccurred())
   864  
   865  			Eventually(func(g Gomega) {
   866  				updateClusterAnnotation(cluster)
   867  				g.Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
   868  					g.Expect(cluster.Status.ObservedGeneration).Should(BeZero())
   869  					condition := meta.FindStatusCondition(cluster.Status.Conditions, appsv1alpha1.ConditionTypeProvisioningStarted)
   870  					g.Expect(condition).ShouldNot(BeNil())
   871  					g.Expect(condition.Reason).Should(BeEquivalentTo(ReasonPreCheckFailed))
   872  				})).Should(Succeed())
   873  			}).Should(Succeed())
   874  
   875  			By("reset clusterVersion to Available")
   876  			Expect(testapps.GetAndChangeObj(&testCtx, clusterVersionKey, func(clusterVersion *appsv1alpha1.ClusterVersion) {
   877  				clusterVersion.Spec.ComponentVersions[0].ComponentDefRef = "consensus"
   878  			})()).ShouldNot(HaveOccurred())
   879  
   880  			Eventually(testapps.CheckObj(&testCtx, clusterVersionKey, func(g Gomega, clusterVersion *appsv1alpha1.ClusterVersion) {
   881  				g.Expect(clusterVersion.Status.Phase).Should(Equal(appsv1alpha1.AvailablePhase))
   882  			})).Should(Succeed())
   883  
   884  			// trigger reconcile
   885  			updateClusterAnnotation(cluster)
   886  			By("test preCheckFailed")
   887  			Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
   888  				g.Expect(cluster.Status.ObservedGeneration).Should(BeZero())
   889  				condition := meta.FindStatusCondition(cluster.Status.Conditions, appsv1alpha1.ConditionTypeProvisioningStarted)
   890  				g.Expect(condition).ShouldNot(BeNil())
   891  				g.Expect(condition.Reason).Should(Equal(ReasonPreCheckFailed))
   892  			})).Should(Succeed())
   893  
   894  			By("reset and waiting cluster to Creating")
   895  			Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(tmpCluster *appsv1alpha1.Cluster) {
   896  				tmpCluster.Spec.ComponentSpecs[0].EnabledLogs = []string{"error"}
   897  			})()).ShouldNot(HaveOccurred())
   898  
   899  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(cluster), func(g Gomega, tmpCluster *appsv1alpha1.Cluster) {
   900  				g.Expect(tmpCluster.Status.Phase).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   901  				g.Expect(tmpCluster.Status.ObservedGeneration).Should(BeNumerically(">", 1))
   902  			})).Should(Succeed())
   903  
   904  			By("mock pvc of component to create")
   905  			for i := 0; i < testapps.ConsensusReplicas; i++ {
   906  				pvcName := fmt.Sprintf("%s-%s-%s-%d", testapps.DataVolumeName, clusterKey.Name, consensusCompName, i)
   907  				pvc := testapps.NewPersistentVolumeClaimFactory(testCtx.DefaultNamespace, pvcName, clusterKey.Name,
   908  					consensusCompName, "data").SetStorage("2Gi").Create(&testCtx).GetObject()
   909  				// mock pvc bound
   910  				Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(pvc), func(pvc *corev1.PersistentVolumeClaim) {
   911  					pvc.Status.Phase = corev1.ClaimBound
   912  					pvc.Status.Capacity = corev1.ResourceList{
   913  						corev1.ResourceStorage: resource.MustParse("2Gi"),
   914  					}
   915  				})()).ShouldNot(HaveOccurred())
   916  			}
   917  
   918  			By("apply smaller PVC size will should failed")
   919  			Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(cluster), func(tmpCluster *appsv1alpha1.Cluster) {
   920  				tmpCluster.Spec.ComponentSpecs[0].VolumeClaimTemplates[0].Spec.Resources.Requests[corev1.ResourceStorage] = resource.MustParse("1Gi")
   921  			})()).ShouldNot(HaveOccurred())
   922  
   923  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(cluster),
   924  				func(g Gomega, tmpCluster *appsv1alpha1.Cluster) {
   925  					// REVIEW/TODO: (wangyelei) following expects causing inconsistent behavior
   926  					condition := meta.FindStatusCondition(tmpCluster.Status.Conditions, appsv1alpha1.ConditionTypeApplyResources)
   927  					g.Expect(condition).ShouldNot(BeNil())
   928  					g.Expect(condition.Reason).Should(Equal(ReasonApplyResourcesFailed))
   929  				})).Should(Succeed())
   930  		})
   931  	})
   932  
   933  	Context("cluster deletion", func() {
   934  		BeforeEach(func() {
   935  			createAllWorkloadTypesClusterDef()
   936  		})
   937  		It("should deleted after all the sub-resources", func() {
   938  			createClusterObj(consensusCompName, consensusCompDefName)
   939  
   940  			By("Waiting for the cluster enter running phase")
   941  			Eventually(testapps.GetClusterObservedGeneration(&testCtx, clusterKey)).Should(BeEquivalentTo(1))
   942  			Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.CreatingClusterPhase))
   943  
   944  			workloadKey := types.NamespacedName{
   945  				Namespace: clusterKey.Namespace,
   946  				Name:      clusterKey.Name + "-" + consensusCompName,
   947  			}
   948  
   949  			By("checking workload exists")
   950  			Eventually(testapps.CheckObjExists(&testCtx, workloadKey, &workloads.ReplicatedStateMachine{}, true)).Should(Succeed())
   951  
   952  			finalizerName := "test/finalizer"
   953  			By("set finalizer for workload to prevent it from deletion")
   954  			Expect(testapps.GetAndChangeObj(&testCtx, workloadKey, func(wl *workloads.ReplicatedStateMachine) {
   955  				wl.ObjectMeta.Finalizers = append(wl.ObjectMeta.Finalizers, finalizerName)
   956  			})()).ShouldNot(HaveOccurred())
   957  
   958  			By("Delete the cluster")
   959  			testapps.DeleteObject(&testCtx, clusterKey, &appsv1alpha1.Cluster{})
   960  
   961  			By("checking cluster keep existing")
   962  			Consistently(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, true)).Should(Succeed())
   963  
   964  			By("remove finalizer of sts to get it deleted")
   965  			Expect(testapps.GetAndChangeObj(&testCtx, workloadKey, func(wl *workloads.ReplicatedStateMachine) {
   966  				wl.ObjectMeta.Finalizers = nil
   967  			})()).ShouldNot(HaveOccurred())
   968  
   969  			By("Wait for the cluster to terminate")
   970  			Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &appsv1alpha1.Cluster{}, false)).Should(Succeed())
   971  		})
   972  	})
   973  })
   974  
   975  func outOfOrderEqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
   976  	if l := len(s1); l != len(s2) {
   977  		return false
   978  	}
   979  
   980  	for _, v1 := range s1 {
   981  		isEq := false
   982  		for _, v2 := range s2 {
   983  			if isEq = eq(v1, v2); isEq {
   984  				break
   985  			}
   986  		}
   987  		if !isEq {
   988  			return false
   989  		}
   990  	}
   991  	return true
   992  }