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