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 }