github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/apis/apps/v1alpha1/opsrequest_webhook_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1alpha1 18 19 import ( 20 "context" 21 "fmt" 22 23 . "github.com/onsi/ginkgo/v2" 24 . "github.com/onsi/gomega" 25 26 "github.com/sethvargo/go-password/password" 27 corev1 "k8s.io/api/core/v1" 28 storagev1 "k8s.io/api/storage/v1" 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/kubectl/pkg/util/storage" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 "github.com/1aal/kubeblocks/pkg/constant" 35 // testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 36 ) 37 38 var _ = Describe("OpsRequest webhook", func() { 39 const ( 40 componentName = "replicasets" 41 proxyComponentName = "proxy" 42 ) 43 var ( 44 randomStr = testCtx.GetRandomStr() 45 clusterDefinitionName = "opswebhook-mysql-definition-" + randomStr 46 clusterVersionName = "opswebhook-mysql-clusterversion-" + randomStr 47 clusterVersionNameForUpgrade = "opswebhook-mysql-upgrade-" + randomStr 48 clusterName = "opswebhook-mysql-" + randomStr 49 opsRequestName = "opswebhook-mysql-ops-" + randomStr 50 ) 51 52 int32Ptr := func(i int32) *int32 { 53 return &i 54 } 55 56 cleanupObjects := func() { 57 // Add any setup steps that needs to be executed before each test 58 err := k8sClient.DeleteAllOf(ctx, &OpsRequest{}, client.InNamespace(testCtx.DefaultNamespace), client.HasLabels{testCtx.TestObjLabelKey}) 59 Expect(err).NotTo(HaveOccurred()) 60 err = k8sClient.DeleteAllOf(ctx, &Cluster{}, client.InNamespace(testCtx.DefaultNamespace), client.HasLabels{testCtx.TestObjLabelKey}) 61 Expect(err).NotTo(HaveOccurred()) 62 err = k8sClient.DeleteAllOf(ctx, &ClusterVersion{}, client.HasLabels{testCtx.TestObjLabelKey}) 63 Expect(err).NotTo(HaveOccurred()) 64 err = k8sClient.DeleteAllOf(ctx, &ClusterDefinition{}, client.HasLabels{testCtx.TestObjLabelKey}) 65 Expect(err).NotTo(HaveOccurred()) 66 err = k8sClient.DeleteAllOf(ctx, &storagev1.StorageClass{}) 67 Expect(err).NotTo(HaveOccurred()) 68 } 69 BeforeEach(func() { 70 // Add any setup steps that needs to be executed before each test 71 cleanupObjects() 72 }) 73 74 AfterEach(func() { 75 // Add any teardown steps that needs to be executed after each test 76 cleanupObjects() 77 }) 78 79 addClusterRequestAnnotation := func(cluster *Cluster, opsName string, toClusterPhase ClusterPhase) { 80 clusterPatch := client.MergeFrom(cluster.DeepCopy()) 81 cluster.Annotations = map[string]string{ 82 opsRequestAnnotationKey: fmt.Sprintf(`[{"name":"%s","clusterPhase":"%s"}]`, opsName, toClusterPhase), 83 } 84 Expect(k8sClient.Patch(ctx, cluster, clusterPatch)).Should(Succeed()) 85 } 86 87 createStorageClass := func(ctx context.Context, storageClassName string, isDefault string, allowVolumeExpansion bool) *storagev1.StorageClass { 88 storageClass := &storagev1.StorageClass{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: storageClassName, 91 Annotations: map[string]string{ 92 storage.IsDefaultStorageClassAnnotation: isDefault, 93 }, 94 }, 95 Provisioner: "kubernetes.io/no-provisioner", 96 AllowVolumeExpansion: &allowVolumeExpansion, 97 } 98 err := testCtx.CheckedCreateObj(ctx, storageClass) 99 Expect(err).Should(BeNil()) 100 return storageClass 101 } 102 103 createPVC := func(clusterName, compName, storageClassName, vctName string, index int) *corev1.PersistentVolumeClaim { 104 pvc := &corev1.PersistentVolumeClaim{ 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: fmt.Sprintf("%s-%s-%s-%d", vctName, clusterName, compName, index), 107 Namespace: testCtx.DefaultNamespace, 108 Labels: map[string]string{ 109 constant.AppInstanceLabelKey: clusterName, 110 constant.VolumeClaimTemplateNameLabelKey: vctName, 111 constant.KBAppComponentLabelKey: compName, 112 }, 113 }, 114 Spec: corev1.PersistentVolumeClaimSpec{ 115 AccessModes: []corev1.PersistentVolumeAccessMode{ 116 corev1.ReadWriteOnce, 117 }, 118 Resources: corev1.ResourceRequirements{ 119 Requests: corev1.ResourceList{ 120 "storage": resource.MustParse("1Gi"), 121 }, 122 }, 123 StorageClassName: &storageClassName, 124 }, 125 } 126 Expect(testCtx.CheckedCreateObj(ctx, pvc)).ShouldNot(HaveOccurred()) 127 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(pvc), pvc)).ShouldNot(HaveOccurred()) 128 patch := client.MergeFrom(pvc.DeepCopy()) 129 pvc.Status.Capacity = corev1.ResourceList{ 130 "storage": resource.MustParse("1Gi"), 131 } 132 Expect(k8sClient.Status().Patch(ctx, pvc, patch)).ShouldNot(HaveOccurred()) 133 return pvc 134 } 135 136 notFoundComponentsString := func(notFoundComponents string) string { 137 return fmt.Sprintf("components: [%s] not found", notFoundComponents) 138 } 139 140 testUpgrade := func(cluster *Cluster) { 141 opsRequest := createTestOpsRequest(clusterName, opsRequestName+"-upgrade", UpgradeType) 142 143 By("By testing when spec.upgrade is null") 144 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("spec.upgrade")) 145 146 By("By creating a new clusterVersion for upgrade") 147 newClusterVersion := createTestClusterVersionObj(clusterDefinitionName, clusterVersionNameForUpgrade) 148 Expect(testCtx.CreateObj(ctx, newClusterVersion)).Should(Succeed()) 149 150 By("By testing when target cluster version not exist") 151 opsRequest.Spec.Upgrade = &Upgrade{ClusterVersionRef: clusterVersionName + "-not-exist"} 152 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("not found")) 153 154 By("Test Cluster Phase") 155 opsRequest.Name = opsRequestName + "-upgrade-cluster-phase" 156 opsRequest.Spec.Upgrade = &Upgrade{ClusterVersionRef: clusterVersionName} 157 OpsRequestBehaviourMapper[UpgradeType] = OpsRequestBehaviour{ 158 FromClusterPhases: []ClusterPhase{RunningClusterPhase}, 159 ToClusterPhase: UpdatingClusterPhase, // original VersionUpgradingPhase, 160 } 161 // TODO: do VersionUpgradingPhase condition value check 162 163 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("Upgrade is forbidden")) 164 // update cluster phase to Running 165 clusterPatch := client.MergeFrom(cluster.DeepCopy()) 166 cluster.Status.Phase = RunningClusterPhase 167 Expect(k8sClient.Status().Patch(ctx, cluster, clusterPatch)).Should(Succeed()) 168 169 By("Test existing other operations in cluster") 170 // update cluster existing operations 171 addClusterRequestAnnotation(cluster, "testOpsName", UpdatingClusterPhase) 172 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).Should(ContainSubstring("existing OpsRequest: testOpsName")) 173 // test opsRequest reentry 174 addClusterRequestAnnotation(cluster, opsRequest.Name, UpdatingClusterPhase) 175 176 By("By creating a upgrade opsRequest, it should be succeed") 177 opsRequest.Spec.Upgrade.ClusterVersionRef = newClusterVersion.Name 178 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 179 Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: opsRequest.Name, 180 Namespace: opsRequest.Namespace}, opsRequest)).Should(Succeed()) 181 182 By("Expect an error for cancelling this opsRequest") 183 opsRequest.Spec.Cancel = true 184 Expect(k8sClient.Update(context.Background(), opsRequest).Error()).Should(ContainSubstring("forbidden to cancel the opsRequest which type not in ['VerticalScaling','HorizontalScaling']")) 185 } 186 187 testVerticalScaling := func(cluster *Cluster) { 188 verticalScalingList := []VerticalScaling{ 189 { 190 ComponentOps: ComponentOps{ComponentName: "vs-not-exist"}, 191 ResourceRequirements: corev1.ResourceRequirements{}, 192 }, 193 { 194 ComponentOps: ComponentOps{ComponentName: proxyComponentName}, 195 ResourceRequirements: corev1.ResourceRequirements{ 196 Requests: corev1.ResourceList{ 197 "cpu": resource.MustParse("100m"), 198 "memory": resource.MustParse("100Mi"), 199 }, 200 }, 201 }, 202 { 203 ComponentOps: ComponentOps{ComponentName: componentName}, 204 ResourceRequirements: corev1.ResourceRequirements{ 205 Requests: corev1.ResourceList{ 206 "cpu": resource.MustParse("200m"), 207 "memory": resource.MustParse("100Mi"), 208 }, 209 Limits: corev1.ResourceList{ 210 "cpu": resource.MustParse("100m"), 211 "memory": resource.MustParse("100Mi"), 212 }, 213 }, 214 }, 215 } 216 217 By("By testing verticalScaling opsRequest components is not exist") 218 opsRequest := createTestOpsRequest(clusterName, opsRequestName, VerticalScalingType) 219 opsRequest.Spec.VerticalScalingList = []VerticalScaling{verticalScalingList[0]} 220 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("vs-not-exist"))) 221 222 By("By testing verticalScaling opsRequest components is not consistent") 223 opsRequest = createTestOpsRequest(clusterName, opsRequestName, VerticalScalingType) 224 // [0] is not exist, and [1] is valid. 225 opsRequest.Spec.VerticalScalingList = []VerticalScaling{verticalScalingList[0], verticalScalingList[1]} 226 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("not found")) 227 228 By("By testing verticalScaling opsRequest components partly") 229 opsRequest = createTestOpsRequest(clusterName, opsRequestName, VerticalScalingType) 230 opsRequest.Spec.VerticalScalingList = []VerticalScaling{verticalScalingList[1]} 231 Expect(testCtx.CreateObj(ctx, opsRequest) == nil).Should(BeTrue()) 232 233 By("By testing requests cpu less than limits cpu") 234 opsRequest = createTestOpsRequest(clusterName, opsRequestName, VerticalScalingType) 235 opsRequest.Spec.VerticalScalingList = []VerticalScaling{verticalScalingList[2]} 236 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("must be less than or equal to cpu limit")) 237 238 By("expect successful") 239 opsRequest.Spec.VerticalScalingList[0].Requests[corev1.ResourceCPU] = resource.MustParse("100m") 240 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 241 242 By("test spec immutable") 243 newClusterName := clusterName + "1" 244 newCluster, _ := createTestCluster(clusterDefinitionName, clusterVersionName, newClusterName) 245 Expect(testCtx.CheckedCreateObj(ctx, newCluster)).Should(Succeed()) 246 247 testSpecImmutable := func(phase OpsPhase) { 248 By(fmt.Sprintf("spec is immutable when status.phase in %s", phase)) 249 patch := client.MergeFrom(opsRequest.DeepCopy()) 250 opsRequest.Status.Phase = phase 251 Expect(k8sClient.Status().Patch(ctx, opsRequest, patch)).Should(Succeed()) 252 253 patch = client.MergeFrom(opsRequest.DeepCopy()) 254 opsRequest.Spec.Cancel = true 255 Expect(k8sClient.Patch(ctx, opsRequest, patch).Error()).To(ContainSubstring(fmt.Sprintf("is forbidden when status.Phase is %s", phase))) 256 } 257 phaseList := []OpsPhase{OpsSucceedPhase, OpsFailedPhase, OpsCancelledPhase} 258 for _, phase := range phaseList { 259 testSpecImmutable(phase) 260 } 261 262 By("test spec immutable except for cancel") 263 testSpecImmutableExpectForCancel := func(phase OpsPhase) { 264 patch := client.MergeFrom(opsRequest.DeepCopy()) 265 opsRequest.Status.Phase = phase 266 Expect(k8sClient.Status().Patch(ctx, opsRequest, patch)).Should(Succeed()) 267 268 patch = client.MergeFrom(opsRequest.DeepCopy()) 269 By(fmt.Sprintf("cancel opsRequest when ops phase is %s", phase)) 270 opsRequest.Spec.Cancel = !opsRequest.Spec.Cancel 271 Expect(k8sClient.Patch(ctx, opsRequest, patch)).ShouldNot(HaveOccurred()) 272 273 By(fmt.Sprintf("expect an error for updating spec.ClusterRef when ops phase is %s", phase)) 274 opsRequest.Spec.ClusterRef = newClusterName 275 Expect(k8sClient.Patch(ctx, opsRequest, patch).Error()).To(ContainSubstring("forbidden to update spec.clusterRef")) 276 } 277 278 phaseList = []OpsPhase{OpsCreatingPhase, OpsRunningPhase, OpsCancellingPhase} 279 for _, phase := range phaseList { 280 testSpecImmutableExpectForCancel(phase) 281 } 282 } 283 284 testVolumeExpansion := func(cluster *Cluster) { 285 getSingleVolumeExpansionList := func(compName, vctName, storage string) []VolumeExpansion { 286 return []VolumeExpansion{ 287 { 288 ComponentOps: ComponentOps{ComponentName: compName}, 289 VolumeClaimTemplates: []OpsRequestVolumeClaimTemplate{ 290 { 291 Name: vctName, 292 Storage: resource.MustParse(storage), 293 }, 294 }, 295 }, 296 } 297 } 298 defaultVCTName := "data" 299 logVCTName := "log" 300 targetStorage := "2Gi" 301 By("By testing volumeExpansion - target component not exist") 302 opsRequest := createTestOpsRequest(clusterName, opsRequestName, VolumeExpansionType) 303 opsRequest.Spec.VolumeExpansionList = getSingleVolumeExpansionList("ve-not-exist", defaultVCTName, targetStorage) 304 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("ve-not-exist"))) 305 306 By("By testing volumeExpansion - target volume not exist") 307 volumeExpansionList := []VolumeExpansion{{ 308 ComponentOps: ComponentOps{ComponentName: componentName}, 309 VolumeClaimTemplates: []OpsRequestVolumeClaimTemplate{ 310 { 311 Name: logVCTName, 312 Storage: resource.MustParse(targetStorage), 313 }, 314 { 315 Name: defaultVCTName, 316 Storage: resource.MustParse(targetStorage), 317 }, 318 }, 319 }, 320 } 321 opsRequest.Spec.VolumeExpansionList = volumeExpansionList 322 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("volumeClaimTemplates: [log] not found in component: " + componentName)) 323 324 By("By testing volumeExpansion - storageClass do not support volume expansion") 325 volumeExpansionList = getSingleVolumeExpansionList(componentName, defaultVCTName, targetStorage) 326 opsRequest.Spec.VolumeExpansionList = volumeExpansionList 327 notSupportMsg := fmt.Sprintf("volumeClaimTemplate: [data] not support volume expansion in component: %s, you can view infos by command: kubectl get sc", componentName) 328 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notSupportMsg)) 329 330 By("testing volumeExpansion - storageClass supports volume expansion") 331 storageClassName := "standard" 332 storageClass := createStorageClass(testCtx.Ctx, storageClassName, "true", true) 333 Expect(storageClass).ShouldNot(BeNil()) 334 // mock to create pvc 335 createPVC(clusterName, componentName, storageClassName, defaultVCTName, 0) 336 337 By("create a pvc and storageClass does not support volume expansion") 338 storageClassName1 := "standard1" 339 storageClass1 := createStorageClass(testCtx.Ctx, storageClassName1, "false", false) 340 Expect(storageClass1).ShouldNot(BeNil()) 341 createPVC(clusterName, componentName, storageClassName1, logVCTName, 0) 342 343 By("testing volumeExpansion with smaller storage, expect an error occurs") 344 opsRequest.Spec.VolumeExpansionList = getSingleVolumeExpansionList(componentName, defaultVCTName, "500Mi") 345 Expect(testCtx.CreateObj(ctx, opsRequest)).Should(HaveOccurred()) 346 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(`requested storage size of volumeClaimTemplate "data" can not less than status.capacity.storage "1Gi"`)) 347 348 By("testing other volumeExpansion opsRequest exists") 349 opsRequest.Spec.VolumeExpansionList = getSingleVolumeExpansionList(componentName, defaultVCTName, targetStorage) 350 Expect(testCtx.CreateObj(ctx, opsRequest)).ShouldNot(HaveOccurred()) 351 // mock ops to running 352 patch := client.MergeFrom(opsRequest.DeepCopy()) 353 opsRequest.Status.Phase = OpsRunningPhase 354 Expect(k8sClient.Status().Patch(ctx, opsRequest, patch)).ShouldNot(HaveOccurred()) 355 // create another ops 356 opsRequest1 := createTestOpsRequest(clusterName, opsRequestName+"1", VolumeExpansionType) 357 opsRequest1.Spec.VolumeExpansionList = getSingleVolumeExpansionList(componentName, defaultVCTName, "3Gi") 358 Expect(testCtx.CreateObj(ctx, opsRequest1).Error()).Should(ContainSubstring("existing other VolumeExpansion OpsRequest")) 359 } 360 361 testHorizontalScaling := func(clusterDef *ClusterDefinition, cluster *Cluster) { 362 hScalingList := []HorizontalScaling{ 363 { 364 ComponentOps: ComponentOps{ComponentName: "hs-not-exist"}, 365 Replicas: 2, 366 }, 367 { 368 ComponentOps: ComponentOps{ComponentName: proxyComponentName}, 369 Replicas: 2, 370 }, 371 { 372 ComponentOps: ComponentOps{ComponentName: componentName}, 373 Replicas: 2, 374 }, 375 } 376 377 By("By testing horizontalScaling - delete component proxy from cluster definition which is exist in cluster") 378 patch := client.MergeFrom(clusterDef.DeepCopy()) 379 // delete component proxy from cluster definition 380 if clusterDef.Spec.ComponentDefs[0].Name == proxyComponentName { 381 clusterDef.Spec.ComponentDefs = clusterDef.Spec.ComponentDefs[1:] 382 } else { 383 clusterDef.Spec.ComponentDefs = clusterDef.Spec.ComponentDefs[:1] 384 } 385 Expect(k8sClient.Patch(ctx, clusterDef, patch)).Should(Succeed()) 386 tmp := &ClusterDefinition{} 387 _ = k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterDef.Name, Namespace: clusterDef.Namespace}, tmp) 388 Expect(len(tmp.Spec.ComponentDefs)).Should(Equal(1)) 389 390 By("By testing horizontalScaling - target component not exist") 391 opsRequest := createTestOpsRequest(clusterName, opsRequestName, HorizontalScalingType) 392 opsRequest.Spec.HorizontalScalingList = []HorizontalScaling{hScalingList[0]} 393 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("hs-not-exist"))) 394 395 By("By testing horizontalScaling - target component not exist partly") 396 opsRequest = createTestOpsRequest(clusterName, opsRequestName, HorizontalScalingType) 397 opsRequest.Spec.HorizontalScalingList = []HorizontalScaling{hScalingList[0], hScalingList[2]} 398 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("hs-not-exist"))) 399 400 By("By testing horizontalScaling. if api is legal, it will create successfully") 401 opsRequest = createTestOpsRequest(clusterName, opsRequestName, HorizontalScalingType) 402 opsRequest.Spec.HorizontalScalingList = []HorizontalScaling{hScalingList[2]} 403 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 404 405 By("test min, max is zero") 406 opsRequest = createTestOpsRequest(clusterName, opsRequestName, HorizontalScalingType) 407 opsRequest.Spec.HorizontalScalingList = []HorizontalScaling{hScalingList[2]} 408 opsRequest.Spec.HorizontalScalingList[0].Replicas = 5 409 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 410 } 411 412 testSwitchover := func(clusterDef *ClusterDefinition, cluster *Cluster) { 413 switchoverList := []Switchover{ 414 { 415 ComponentOps: ComponentOps{ComponentName: "switchover-component-not-exist"}, 416 InstanceName: "*", 417 }, 418 { 419 ComponentOps: ComponentOps{ComponentName: componentName}, 420 InstanceName: "", 421 }, 422 { 423 ComponentOps: ComponentOps{ComponentName: componentName}, 424 InstanceName: "switchover-instance-name-not-exist", 425 }, 426 { 427 ComponentOps: ComponentOps{ComponentName: componentName}, 428 InstanceName: "*", 429 }, 430 { 431 ComponentOps: ComponentOps{ComponentName: componentName}, 432 InstanceName: fmt.Sprintf("%s-%s-0", cluster.Name, componentName), 433 }, 434 } 435 436 By("By testing horizontalScaling - delete component proxy from cluster definition which is exist in cluster") 437 patch := client.MergeFrom(clusterDef.DeepCopy()) 438 // delete component proxy from cluster definition 439 if clusterDef.Spec.ComponentDefs[0].Name == proxyComponentName { 440 clusterDef.Spec.ComponentDefs = clusterDef.Spec.ComponentDefs[1:] 441 } else { 442 clusterDef.Spec.ComponentDefs = clusterDef.Spec.ComponentDefs[:1] 443 } 444 Expect(k8sClient.Patch(ctx, clusterDef, patch)).Should(Succeed()) 445 tmp := &ClusterDefinition{} 446 _ = k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterDef.Name, Namespace: clusterDef.Namespace}, tmp) 447 Expect(len(tmp.Spec.ComponentDefs)).Should(Equal(1)) 448 449 By("By testing switchover - target component not exist") 450 opsRequest := createTestOpsRequest(clusterName, opsRequestName, SwitchoverType) 451 opsRequest.Spec.SwitchoverList = []Switchover{switchoverList[0]} 452 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("switchover-component-not-exist"))) 453 454 By("By testing switchover - target switchover.Instance cannot be empty") 455 opsRequest = createTestOpsRequest(clusterName, opsRequestName, SwitchoverType) 456 opsRequest.Spec.SwitchoverList = []Switchover{switchoverList[1]} 457 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("switchover.instanceName")) 458 459 By("By testing switchover - clusterDefinition has no switchoverSpec and do not support switchover") 460 opsRequest = createTestOpsRequest(clusterName, opsRequestName, SwitchoverType) 461 opsRequest.Spec.SwitchoverList = []Switchover{switchoverList[3]} 462 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("does not support switchover")) 463 464 By("By testing switchover - target switchover.Instance cannot be empty") 465 patch = client.MergeFrom(clusterDef.DeepCopy()) 466 commandExecutorEnvItem := CommandExecutorEnvItem{ 467 Image: "", 468 } 469 commandExecutorItem := CommandExecutorItem{ 470 Command: []string{"echo", "hello"}, 471 Args: []string{}, 472 } 473 switchoverSpec := &SwitchoverSpec{ 474 WithCandidate: &SwitchoverAction{ 475 CmdExecutorConfig: &CmdExecutorConfig{ 476 CommandExecutorEnvItem: commandExecutorEnvItem, 477 CommandExecutorItem: commandExecutorItem, 478 }, 479 }, 480 WithoutCandidate: &SwitchoverAction{ 481 CmdExecutorConfig: &CmdExecutorConfig{ 482 CommandExecutorEnvItem: commandExecutorEnvItem, 483 CommandExecutorItem: commandExecutorItem, 484 }, 485 }, 486 } 487 clusterDef.Spec.ComponentDefs[0].SwitchoverSpec = switchoverSpec 488 Expect(k8sClient.Patch(ctx, clusterDef, patch)).Should(Succeed()) 489 tmp = &ClusterDefinition{} 490 _ = k8sClient.Get(context.Background(), client.ObjectKey{Name: clusterDef.Name, Namespace: clusterDef.Namespace}, tmp) 491 Expect(len(tmp.Spec.ComponentDefs)).Should(Equal(1)) 492 493 By("By testing switchover - switchover.InstanceName is * and should succeed ") 494 opsRequest = createTestOpsRequest(clusterName, opsRequestName, SwitchoverType) 495 opsRequest.Spec.SwitchoverList = []Switchover{switchoverList[3]} 496 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 497 } 498 499 testWhenClusterDeleted := func(cluster *Cluster, opsRequest *OpsRequest) { 500 By("delete cluster") 501 newCluster := &Cluster{} 502 Expect(k8sClient.Get(ctx, client.ObjectKey{Name: clusterName, Namespace: cluster.Namespace}, newCluster)).Should(Succeed()) 503 Expect(k8sClient.Delete(ctx, newCluster)).Should(Succeed()) 504 505 By("test path labels") 506 Eventually(k8sClient.Get(ctx, client.ObjectKey{Name: clusterName, Namespace: cluster.Namespace}, &Cluster{})).Should(HaveOccurred()) 507 508 patch := client.MergeFrom(opsRequest.DeepCopy()) 509 opsRequest.Labels["test"] = "test-ops" 510 Expect(k8sClient.Patch(ctx, opsRequest, patch)).Should(Succeed()) 511 } 512 513 testRestart := func(cluster *Cluster) *OpsRequest { 514 By("By testing restart when componentNames is not correct") 515 opsRequest := createTestOpsRequest(clusterName, opsRequestName, RestartType) 516 opsRequest.Spec.RestartList = []ComponentOps{ 517 {ComponentName: "replicasets1"}, 518 } 519 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring(notFoundComponentsString("replicasets1"))) 520 521 By("By testing restart. if api is legal, it will create successfully") 522 opsRequest.Spec.RestartList[0].ComponentName = componentName 523 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 524 return opsRequest 525 } 526 527 testReconfiguring := func(cluster *Cluster, clusterDef *ClusterDefinition) { 528 opsRequest := createTestOpsRequest(clusterName, opsRequestName+"-reconfiguring", ReconfiguringType) 529 530 createReconfigureObj := func(compName string) *Reconfigure { 531 return &Reconfigure{ 532 ComponentOps: ComponentOps{ComponentName: compName}, 533 Configurations: []ConfigurationItem{{Name: "for-test", 534 Keys: []ParameterConfig{{ 535 Key: "test", 536 Parameters: []ParameterPair{{ 537 Key: "test", 538 Value: func(t string) *string { return &t }("test")}}, 539 }}, 540 }}} 541 } 542 543 By("By testing when spec.reconfiguring is null") 544 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("spec.reconfigure")) 545 546 By("By testing when target cluster definition not exist") 547 opsRequest.Spec.Reconfigure = createReconfigureObj(componentName + "-not-exist") 548 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("not found")) 549 opsRequest.Spec.Reconfigure = createReconfigureObj(componentName) 550 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("not found")) 551 552 By("By creating a configmap") 553 Expect(testCtx.CheckedCreateObj(ctx, createTestConfigmap(fmt.Sprintf("%s-%s-%s", opsRequest.Spec.ClusterRef, componentName, "for-test")))).Should(Succeed()) 554 opsRequest.Spec.Reconfigure = createReconfigureObj(componentName) 555 Expect(testCtx.CreateObj(ctx, opsRequest).Error()).To(ContainSubstring("not found in configmap")) 556 557 By("By test reconfiguring for file content") 558 r := createReconfigureObj(componentName) 559 r.Configurations[0].Keys[0].FileContent = "new context" 560 opsRequest.Spec.Reconfigure = r 561 Expect(testCtx.CreateObj(ctx, opsRequest)).To(Succeed()) 562 } 563 564 Context("When clusterVersion create and update", func() { 565 It("Should webhook validate passed", func() { 566 By("By create a clusterDefinition") 567 568 // wait until ClusterDefinition and ClusterVersion created 569 clusterDef, _ := createTestClusterDefinitionObj(clusterDefinitionName) 570 Expect(testCtx.CheckedCreateObj(ctx, clusterDef)).Should(Succeed()) 571 By("By creating a clusterVersion") 572 clusterVersion := createTestClusterVersionObj(clusterDefinitionName, clusterVersionName) 573 Expect(testCtx.CheckedCreateObj(ctx, clusterVersion)).Should(Succeed()) 574 575 opsRequest := createTestOpsRequest(clusterName, opsRequestName, UpgradeType) 576 577 // create Cluster 578 By("By testing spec.clusterDef is legal") 579 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(HaveOccurred()) 580 By("By create a new cluster ") 581 cluster, _ := createTestCluster(clusterDefinitionName, clusterVersionName, clusterName) 582 Expect(testCtx.CheckedCreateObj(ctx, cluster)).Should(Succeed()) 583 584 testUpgrade(cluster) 585 586 testVerticalScaling(cluster) 587 588 testVolumeExpansion(cluster) 589 590 testHorizontalScaling(clusterDef, cluster) 591 592 testSwitchover(clusterDef, cluster) 593 594 testReconfiguring(cluster, clusterDef) 595 596 opsRequest = testRestart(cluster) 597 598 testWhenClusterDeleted(cluster, opsRequest) 599 }) 600 601 It("check datascript opts", func() { 602 OpsRequestBehaviourMapper[DataScriptType] = OpsRequestBehaviour{ 603 FromClusterPhases: []ClusterPhase{RunningClusterPhase}, 604 } 605 606 By("By create a clusterDefinition") 607 clusterDef, _ := createTestClusterDefinitionObj(clusterDefinitionName) 608 Expect(testCtx.CheckedCreateObj(ctx, clusterDef)).Should(Succeed()) 609 By("By creating a clusterVersion") 610 clusterVersion := createTestClusterVersionObj(clusterDefinitionName, clusterVersionName) 611 Expect(testCtx.CheckedCreateObj(ctx, clusterVersion)).Should(Succeed()) 612 613 opsRequest := createTestOpsRequest(clusterName, opsRequestName, DataScriptType) 614 opsRequest.Spec.ScriptSpec = &ScriptSpec{ 615 ComponentOps: ComponentOps{ComponentName: componentName}, 616 } 617 618 // create Cluster 619 By("By testing spec.clusterDef is legal") 620 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(HaveOccurred()) 621 By("By create a new cluster ") 622 cluster, _ := createTestCluster(clusterDefinitionName, clusterVersionName, clusterName) 623 Expect(testCtx.CheckedCreateObj(ctx, cluster)).Should(Succeed()) 624 625 By("By testing dataScript without script, should fail") 626 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(HaveOccurred()) 627 628 By("By testing dataScript, with script, no wait, should fail") 629 opsRequest.Spec.ScriptSpec.Script = []string{"create database test;"} 630 Expect(testCtx.CheckedCreateObj(ctx, opsRequest).Error()).To(ContainSubstring("DataScript is forbidden")) 631 632 By("By testing dataScript, with illegal configmap, should fail") 633 opsRequest.Spec.ScriptSpec.ScriptFrom = &ScriptFrom{ 634 ConfigMapRef: []corev1.ConfigMapKeySelector{ 635 { 636 LocalObjectReference: corev1.LocalObjectReference{ 637 Name: "test-cm", 638 }, 639 Key: "createdb", 640 }, 641 }, 642 } 643 644 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(HaveOccurred()) 645 646 By("By testing dataScript, with illegal scriptFrom, should fail") 647 opsRequest.Spec.ScriptSpec.ScriptFrom.SecretRef = nil 648 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(HaveOccurred()) 649 650 // patch cluster to running 651 By("By patching cluster to running") 652 Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(cluster), cluster)).Should(Succeed()) 653 clusterPatch := client.MergeFrom(cluster.DeepCopy()) 654 cluster.Status.Phase = RunningClusterPhase 655 Expect(k8sClient.Status().Patch(ctx, cluster, clusterPatch)).Should(Succeed()) 656 657 Eventually(func() bool { 658 _ = k8sClient.Get(ctx, client.ObjectKeyFromObject(cluster), cluster) 659 return cluster.Status.Phase == RunningClusterPhase 660 }).Should(BeTrue()) 661 662 opsRequest.Spec.ScriptSpec.Script = []string{"create database test;"} 663 opsRequest.Spec.ScriptSpec.ScriptFrom = nil 664 opsRequest.Spec.TTLSecondsBeforeAbort = int32Ptr(0) 665 Expect(testCtx.CheckedCreateObj(ctx, opsRequest)).Should(Succeed()) 666 }) 667 }) 668 }) 669 670 func createTestOpsRequest(clusterName, opsRequestName string, opsType OpsType) *OpsRequest { 671 randomStr, _ := password.Generate(6, 0, 0, true, false) 672 return &OpsRequest{ 673 ObjectMeta: metav1.ObjectMeta{ 674 Name: opsRequestName + randomStr, 675 Namespace: "default", 676 Labels: map[string]string{ 677 "app.kubernetes.io/instance": clusterName, 678 "ops.kubeblocks.io/ops-type": string(opsType), 679 }, 680 }, 681 Spec: OpsRequestSpec{ 682 ClusterRef: clusterName, 683 Type: opsType, 684 }, 685 } 686 } 687 688 func createTestConfigmap(cmName string) *corev1.ConfigMap { 689 return &corev1.ConfigMap{ 690 ObjectMeta: metav1.ObjectMeta{ 691 Name: cmName, 692 Namespace: "default", 693 }, 694 Data: map[string]string{ 695 "key1": "value1", 696 "key2": "value2", 697 }, 698 } 699 }