github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/systemaccount_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 "fmt" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 29 batchv1 "k8s.io/api/batch/v1" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/types" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 "github.com/1aal/kubeblocks/pkg/constant" 38 "github.com/1aal/kubeblocks/pkg/controller/component" 39 "github.com/1aal/kubeblocks/pkg/generics" 40 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 41 testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s" 42 ) 43 44 var _ = Describe("SystemAccount Controller", func() { 45 46 const ( 47 clusterDefName = "test-clusterdef" 48 clusterVersionName = "test-clusterversion" 49 clusterNamePrefix = "test-cluster" 50 mysqlCompDefName = "replicasets" 51 mysqlCompTypeWOSysAcctDefName = "wo-sysacct" 52 mysqlCompName = "mysql" 53 mysqlCompNameWOSysAcct = "wo-sysacct" 54 clusterEndPointsSize = 3 55 ) 56 57 /** 58 * To test the behavior of system accounts controller, we conduct following tests: 59 * 1. construct two components, one with all accounts set, and one with none. 60 * 2. create two clusters, one cluster for each component, and verify 61 * a) the number of secrets, jobs are as expected 62 * b) secret will be created, once corresponding job succeeds. 63 * c) secrets, deleted accidentally, will be re-created during next cluster reconciliation round. 64 * 65 * Each test case, used in following IT(integration test), consists of two parts: 66 * a) how to build the test cluster, and 67 * b) what does this cluster expect 68 **/ 69 70 // sysAcctResourceInfo defines the number of jobs and secrets to be created per account. 71 type sysAcctResourceInfo struct { 72 jobNum int 73 secretNum int 74 } 75 // sysAcctTestCase defines the info to setup test env, cluster and their expected result to verify against. 76 type sysAcctTestCase struct { 77 componentName string 78 componentDefRef string 79 resourceMap map[appsv1alpha1.AccountName]sysAcctResourceInfo 80 accounts []appsv1alpha1.AccountName // accounts this cluster should have 81 } 82 83 var ( 84 ctx = context.Background() 85 clusterDefObj *appsv1alpha1.ClusterDefinition 86 clusterVersionObj *appsv1alpha1.ClusterVersion 87 ) 88 89 cleanEnv := func() { 90 // must wait till resources deleted and no longer existed before the testcases start, 91 // otherwise if later it needs to create some new resource objects with the same name, 92 // in race conditions, it will find the existence of old objects, resulting failure to 93 // create the new objects. 94 By("clean resources") 95 96 testapps.ClearClusterResourcesWithRemoveFinalizerOption(&testCtx) 97 98 // namespaced resources 99 inNS := client.InNamespace(testCtx.DefaultNamespace) 100 ml := client.HasLabels{testCtx.TestObjLabelKey} 101 testapps.ClearResources(&testCtx, generics.EndpointsSignature, inNS, ml) 102 testapps.ClearResources(&testCtx, generics.JobSignature, inNS, ml) 103 testapps.ClearResources(&testCtx, generics.SecretSignature, inNS, ml) 104 } 105 106 /** 107 * Start of mock functions. 108 **/ 109 mockEndpoint := func(namespace, endpointName string, ips []string) *corev1.Endpoints { 110 mockAddresses := func(ip, podName string) corev1.EndpointAddress { 111 return corev1.EndpointAddress{ 112 IP: ip, 113 NodeName: nil, 114 TargetRef: &corev1.ObjectReference{ 115 Kind: "Pod", 116 Namespace: testCtx.DefaultNamespace, 117 Name: podName, 118 }, 119 } 120 } 121 122 addresses := make([]corev1.EndpointAddress, 0) 123 for i := 0; i < len(ips); i++ { 124 podName := "pod-" + testCtx.GetRandomStr() 125 addresses = append(addresses, mockAddresses(ips[i], podName)) 126 } 127 128 ep := &corev1.Endpoints{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Namespace: namespace, 131 Name: endpointName, 132 }, 133 } 134 ep.Subsets = []corev1.EndpointSubset{ 135 { 136 Addresses: addresses, 137 }, 138 } 139 return ep 140 } 141 142 assureEndpoint := func(namespace, epname string, ips []string) *corev1.Endpoints { 143 ep := mockEndpoint(namespace, epname, ips) 144 Expect(testCtx.CheckedCreateObj(ctx, ep)).Should(Succeed()) 145 // assure cluster def is ready 146 createdEP := &corev1.Endpoints{} 147 Eventually(func() error { 148 return k8sClient.Get(ctx, client.ObjectKey{Name: epname, Namespace: namespace}, createdEP) 149 }).Should(Succeed()) 150 return createdEP 151 } 152 /* 153 * end of mock functions to be refined 154 */ 155 156 /* 157 * Start of helper functions 158 */ 159 getAccounts := func(g Gomega, cluster *appsv1alpha1.Cluster, ml client.MatchingLabels) appsv1alpha1.KBAccountType { 160 secrets := &corev1.SecretList{} 161 g.Expect(k8sClient.List(ctx, secrets, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 162 jobs := &batchv1.JobList{} 163 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 164 return getAcctFromSecretAndJobs(secrets, jobs) 165 } 166 167 checkOwnerReferenceToObj := func(ref metav1.OwnerReference, obj client.Object) bool { 168 return ref.Name == obj.GetName() && ref.UID == obj.GetUID() 169 } 170 171 patchClusterToRunning := func(objectKey types.NamespacedName, compName string) { 172 // services of type ClusterIP should have been created. 173 ips := []string{"10.0.0.0", "10.0.0.1", "10.0.0.2"} 174 serviceName := objectKey.Name + "-" + compName 175 headlessServiceName := serviceName + "-headless" 176 _ = assureEndpoint(objectKey.Namespace, serviceName, ips[0:1]) 177 _ = assureEndpoint(objectKey.Namespace, headlessServiceName, ips[0:clusterEndPointsSize]) 178 179 By("Mock the underlying workloads to ready") 180 rsmList := testk8s.ListAndCheckRSMWithComponent(&testCtx, objectKey, compName) 181 rsm := &rsmList.Items[0] 182 podName := fmt.Sprintf("%s-%s-0", objectKey.Name, compName) 183 pod := testapps.MockConsensusComponentStsPod(&testCtx, nil, objectKey.Name, compName, 184 podName, "leader", "ReadWrite") 185 sts := testapps.NewStatefulSetFactory(rsm.Namespace, rsm.Name, objectKey.Name, compName). 186 SetReplicas(*rsm.Spec.Replicas).Create(&testCtx).GetObject() 187 Expect(testapps.ChangeObjStatus(&testCtx, sts, func() { 188 testk8s.MockStatefulSetReady(sts) 189 })).ShouldNot(HaveOccurred()) 190 Expect(testapps.ChangeObjStatus(&testCtx, rsm, func() { 191 testk8s.MockRSMReady(rsm, pod) 192 })).ShouldNot(HaveOccurred()) 193 194 By("Wait cluster phase to be Running") 195 Eventually(testapps.GetClusterPhase(&testCtx, objectKey)).Should(Equal(appsv1alpha1.RunningClusterPhase)) 196 } 197 198 initSysAccountTestsAndCluster := func(testCases map[string]*sysAcctTestCase) (clustersMap map[string]types.NamespacedName) { 199 // create clusterdef and cluster versions, but not clusters 200 By("Create a clusterDefinition obj") 201 systemAccount := mockSystemAccountsSpec() 202 clusterDefObj = testapps.NewClusterDefFactory(clusterDefName). 203 AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompDefName). 204 AddSystemAccountSpec(systemAccount). 205 AddComponentDef(testapps.StatefulMySQLComponent, mysqlCompTypeWOSysAcctDefName). 206 Create(&testCtx).GetObject() 207 208 By("Create a clusterVersion obj") 209 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 210 AddComponentVersion(mysqlCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage). 211 AddComponentVersion(mysqlCompNameWOSysAcct).AddContainerShort("mysql", testapps.ApeCloudMySQLImage). 212 Create(&testCtx).GetObject() 213 214 Expect(clusterDefObj).NotTo(BeNil()) 215 216 Expect(len(testCases)).To(BeNumerically(">", 0)) 217 // fill the number of secrets, jobs 218 for _, testCase := range testCases { 219 compDef := clusterDefObj.GetComponentDefByName(testCase.componentDefRef) 220 Expect(compDef).NotTo(BeNil()) 221 if compDef.SystemAccounts == nil { 222 continue 223 } 224 if testCase.resourceMap == nil { 225 testCase.resourceMap = make(map[appsv1alpha1.AccountName]sysAcctResourceInfo) 226 } 227 var jobNum, secretNum int 228 for _, account := range compDef.SystemAccounts.Accounts { 229 name := account.Name 230 policy := account.ProvisionPolicy 231 switch policy.Type { 232 case appsv1alpha1.CreateByStmt: 233 secretNum = 1 234 if policy.Scope == appsv1alpha1.AnyPods { 235 jobNum = 1 236 } else { 237 jobNum = clusterEndPointsSize 238 } 239 case appsv1alpha1.ReferToExisting: 240 jobNum = 0 241 secretNum = 1 242 } 243 testCase.resourceMap[name] = sysAcctResourceInfo{ 244 jobNum: jobNum, 245 secretNum: secretNum, 246 } 247 } 248 } 249 250 clustersMap = make(map[string]types.NamespacedName) 251 252 // create cluster defined in each testcase 253 for testName, testCase := range testCases { 254 clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterNamePrefix, 255 clusterDefObj.Name, clusterVersionObj.Name).WithRandomName(). 256 AddComponent(testCase.componentName, testCase.componentDefRef). 257 SetReplicas(1). 258 Create(&testCtx).GetObject() 259 clusterKey := client.ObjectKeyFromObject(clusterObj) 260 clustersMap[testName] = clusterKey 261 262 By("Make sure cluster root conn credential is ready.") 263 Eventually(func(g Gomega) { 264 rootSecretName := component.GenerateConnCredential(clusterKey.Name) 265 rootSecret := &corev1.Secret{} 266 g.Expect(k8sClient.Get(ctx, types.NamespacedName{ 267 Namespace: clusterKey.Namespace, 268 Name: rootSecretName}, rootSecret)).To(Succeed()) 269 }).Should(Succeed()) 270 } 271 return clustersMap 272 } 273 /* 274 * end of helper functions 275 */ 276 277 // scenario 1: create cluster and check secrets and jobs are created 278 Context("When Creating Cluster", func() { 279 var ( 280 clustersMap map[string]types.NamespacedName 281 mysqlTestCases map[string]*sysAcctTestCase 282 ) 283 284 BeforeEach(func() { 285 cleanEnv() 286 DeferCleanup(cleanEnv) 287 288 // setup testcase 289 mysqlTestCases = map[string]*sysAcctTestCase{ 290 "wesql-no-accts": { 291 componentName: mysqlCompNameWOSysAcct, 292 componentDefRef: mysqlCompTypeWOSysAcctDefName, 293 accounts: []appsv1alpha1.AccountName{}, 294 }, 295 "wesql-with-accts": { 296 componentName: mysqlCompName, 297 componentDefRef: mysqlCompDefName, 298 accounts: getAllSysAccounts(), 299 }, 300 } 301 clustersMap = initSysAccountTestsAndCluster(mysqlTestCases) 302 }) 303 304 It("Should create jobs and secrets as expected for each test case", func() { 305 for testName, testCase := range mysqlTestCases { 306 var ( 307 acctList appsv1alpha1.KBAccountType 308 jobsNum int 309 secretsNum int 310 ) 311 312 for _, acc := range testCase.accounts { 313 resource := testCase.resourceMap[acc] 314 acctList |= acc.GetAccountID() 315 jobsNum += resource.jobNum 316 secretsNum += resource.secretNum 317 } 318 319 clusterKey, ok := clustersMap[testName] 320 Expect(ok).To(BeTrue()) 321 322 // get latest cluster object 323 cluster := &appsv1alpha1.Cluster{} 324 Expect(k8sClient.Get(ctx, clusterKey, cluster)).Should(Succeed()) 325 // patch cluster to running 326 patchClusterToRunning(clusterKey, testCase.componentName) 327 328 ml := getLabelsForSecretsAndJobs(componentUniqueKey{namespace: cluster.Namespace, clusterName: cluster.Name, componentName: testCase.componentName}) 329 330 if secretsNum == 0 && jobsNum == 0 { 331 By("No accouts should be create for test case: " + testName) 332 // verify nothing will be created till timeout 333 Consistently(func(g Gomega) { 334 accounts := getAccounts(g, cluster, ml) 335 g.Expect(accounts).To(BeEquivalentTo(acctList)) 336 }).Should(Succeed()) 337 continue 338 } 339 340 By("Verify accounts to be created are correct") 341 Eventually(func(g Gomega) { 342 accounts := getAccounts(g, cluster, ml) 343 g.Expect(accounts).To(BeEquivalentTo(acctList)) 344 }).Should(Succeed()) 345 346 By("Verify all jobs created have their labels set correctly") 347 // get all jobs 348 Eventually(func(g Gomega) { 349 // all jobs matching filter `ml` should be a job for sys account. 350 jobs := &batchv1.JobList{} 351 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 352 for _, job := range jobs.Items { 353 _, ok := job.Labels[constant.ClusterAccountLabelKey] 354 g.Expect(ok).To(BeTrue()) 355 g.Expect(len(job.ObjectMeta.OwnerReferences)).To(BeEquivalentTo(1)) 356 g.Expect(checkOwnerReferenceToObj(job.OwnerReferences[0], cluster)).To(BeTrue()) 357 } 358 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 359 }).Should(Succeed()) 360 } 361 }) 362 363 It("Secrets should be created when jobs succeeds", func() { 364 for testName, testCase := range mysqlTestCases { 365 var ( 366 acctList appsv1alpha1.KBAccountType 367 jobsNum int 368 secretsNum int 369 ) 370 371 for _, acc := range testCase.accounts { 372 resource := testCase.resourceMap[acc] 373 acctList |= acc.GetAccountID() 374 jobsNum += resource.jobNum 375 secretsNum += resource.secretNum 376 } 377 378 if secretsNum == 0 && jobsNum == 0 { 379 continue 380 } 381 // get a cluster instance from map, created during preparation 382 clusterKey, ok := clustersMap[testName] 383 Expect(ok).To(BeTrue()) 384 // patch cluster to running 385 patchClusterToRunning(clusterKey, testCase.componentName) 386 387 // get cluster object 388 cluster := &appsv1alpha1.Cluster{} 389 Expect(k8sClient.Get(ctx, clusterKey, cluster)).Should(Succeed()) 390 391 ml := getLabelsForSecretsAndJobs(componentUniqueKey{ 392 namespace: cluster.Namespace, 393 clusterName: cluster.Name, 394 componentName: testCase.componentName}) 395 396 By("Verify accounts to be created are correct") 397 Eventually(func(g Gomega) { 398 accounts := getAccounts(g, cluster, ml) 399 g.Expect(accounts).To(BeEquivalentTo(acctList)) 400 }).Should(Succeed()) 401 402 // wait for a while till all jobs are created 403 By("Check Jobs are created") 404 Eventually(func(g Gomega) { 405 jobs := &batchv1.JobList{} 406 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 407 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 408 }).Should(Succeed()) 409 410 By("Mock all jobs are completed") 411 Eventually(func(g Gomega) { 412 jobs := &batchv1.JobList{} 413 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 414 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 415 for _, job := range jobs.Items { 416 g.Expect(testapps.ChangeObjStatus(&testCtx, &job, func() { 417 job.Status.Conditions = []batchv1.JobCondition{{ 418 Type: batchv1.JobComplete, 419 Status: corev1.ConditionTrue, 420 }} 421 })).To(Succeed()) 422 } 423 }).Should(Succeed()) 424 425 By("Check secrets created") 426 Eventually(func(g Gomega) { 427 secrets := &corev1.SecretList{} 428 g.Expect(k8sClient.List(ctx, secrets, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 429 g.Expect(len(secrets.Items)).To(BeEquivalentTo(secretsNum)) 430 }).Should(Succeed()) 431 432 By("Verify all secrets created have their finalizer and labels set correctly") 433 // get all secrets, and check their labels and finalizer 434 Eventually(func(g Gomega) { 435 // get secrets matching filter 436 secretsForAcct := &corev1.SecretList{} 437 g.Expect(k8sClient.List(ctx, secretsForAcct, ml)).To(Succeed()) 438 for _, secret := range secretsForAcct.Items { 439 // each secret has finalizer 440 g.Expect(controllerutil.ContainsFinalizer(&secret, constant.DBClusterFinalizerName)).To(BeTrue()) 441 g.Expect(len(secret.ObjectMeta.OwnerReferences)).To(BeEquivalentTo(1)) 442 g.Expect(checkOwnerReferenceToObj(secret.OwnerReferences[0], cluster)).To(BeTrue()) 443 } 444 }).Should(Succeed()) 445 } 446 }) 447 }) // end of context 448 449 Context("When Delete Cluster", func() { 450 var ( 451 clustersMap map[string]types.NamespacedName 452 mysqlTestCases map[string]*sysAcctTestCase 453 ) 454 455 BeforeEach(func() { 456 cleanEnv() 457 DeferCleanup(cleanEnv) 458 459 // setup testcase 460 mysqlTestCases = map[string]*sysAcctTestCase{ 461 "wesql-with-accts": { 462 componentName: mysqlCompName, 463 componentDefRef: mysqlCompDefName, 464 accounts: getAllSysAccounts(), 465 }, 466 "wesql-with-accts-dup": { 467 componentName: mysqlCompName, 468 componentDefRef: mysqlCompDefName, 469 accounts: getAllSysAccounts(), 470 }, 471 } 472 473 clustersMap = initSysAccountTestsAndCluster(mysqlTestCases) 474 }) 475 476 It("Should clear relevant expectations and secrets after cluster deletion", func() { 477 var totalJobs, totalSecrets int 478 for testName, testCase := range mysqlTestCases { 479 var ( 480 acctList appsv1alpha1.KBAccountType 481 jobsNum int 482 secretsNum int 483 ) 484 485 for _, acc := range testCase.accounts { 486 resource := testCase.resourceMap[acc] 487 acctList |= acc.GetAccountID() 488 jobsNum += resource.jobNum 489 secretsNum += resource.secretNum 490 } 491 totalJobs += jobsNum 492 totalSecrets += secretsNum 493 494 // get a cluster instance from map, created during preparation 495 clusterKey, ok := clustersMap[testName] 496 Expect(ok).To(BeTrue()) 497 498 // patch cluster to running 499 patchClusterToRunning(clusterKey, testCase.componentName) 500 501 // get latest cluster object 502 cluster := &appsv1alpha1.Cluster{} 503 Expect(k8sClient.Get(ctx, clusterKey, cluster)).Should(Succeed()) 504 ml := getLabelsForSecretsAndJobs(componentUniqueKey{namespace: cluster.Namespace, clusterName: cluster.Name, componentName: testCase.componentName}) 505 506 By("Verify accounts to be created") 507 Eventually(func(g Gomega) { 508 accounts := getAccounts(g, cluster, ml) 509 g.Expect(accounts).To(BeEquivalentTo(acctList)) 510 }).Should(Succeed()) 511 512 Eventually(func(g Gomega) { 513 jobs := &batchv1.JobList{} 514 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 515 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 516 }).Should(Succeed()) 517 } 518 519 clusterKeys := make([]types.NamespacedName, 0, len(clustersMap)) 520 for _, v := range clustersMap { 521 clusterKeys = append(clusterKeys, v) 522 } 523 524 By("Delete 0-th cluster from list, there should be no change in secrets size") 525 cluster := &appsv1alpha1.Cluster{} 526 Expect(k8sClient.Get(ctx, clusterKeys[0], cluster)).To(Succeed()) 527 Expect(k8sClient.Delete(ctx, cluster)).To(Succeed()) 528 529 By("Delete remaining cluster before jobs are done, all secrets should be removed") 530 for i := 1; i < len(clusterKeys); i++ { 531 cluster = &appsv1alpha1.Cluster{} 532 Expect(k8sClient.Get(ctx, clusterKeys[i], cluster)).To(Succeed()) 533 Expect(k8sClient.Delete(ctx, cluster)).To(Succeed()) 534 } 535 }) 536 537 It("Should remove jobs neither completed nor failed on cluster deletion", func() { 538 var totalJobs int 539 for testName, testCase := range mysqlTestCases { 540 var ( 541 acctList appsv1alpha1.KBAccountType 542 jobsNum int 543 secretsNum int 544 ) 545 546 for _, acc := range testCase.accounts { 547 resource := testCase.resourceMap[acc] 548 acctList |= acc.GetAccountID() 549 jobsNum += resource.jobNum 550 secretsNum += resource.secretNum 551 } 552 totalJobs += jobsNum 553 554 // get a cluster instance from map, created during preparation 555 clusterKey, ok := clustersMap[testName] 556 Expect(ok).To(BeTrue()) 557 558 // patch cluster to running 559 patchClusterToRunning(clusterKey, testCase.componentName) 560 561 // get latest cluster object 562 cluster := &appsv1alpha1.Cluster{} 563 Expect(k8sClient.Get(ctx, clusterKey, cluster)).Should(Succeed()) 564 ml := getLabelsForSecretsAndJobs(componentUniqueKey{namespace: cluster.Namespace, clusterName: cluster.Name, componentName: testCase.componentName}) 565 566 By("Verify accounts to be created") 567 Eventually(func(g Gomega) { 568 accounts := getAccounts(g, cluster, ml) 569 g.Expect(accounts).To(BeEquivalentTo(acctList)) 570 }).Should(Succeed()) 571 572 Eventually(func(g Gomega) { 573 jobs := &batchv1.JobList{} 574 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 575 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 576 }).Should(Succeed()) 577 578 By("Delete cluster before jobs are done, all jobs should be removed") 579 Expect(k8sClient.Delete(ctx, cluster)).To(Succeed()) 580 Eventually(func(g Gomega) { 581 jobs := &batchv1.JobList{} 582 g.Expect(k8sClient.List(ctx, jobs, ml)).To(Succeed()) 583 g.Expect(len(jobs.Items)).To(BeEquivalentTo(0)) 584 }).Should(Succeed()) 585 } 586 }) 587 }) // end of context 588 589 Context("When Update Cluster", func() { 590 var ( 591 clustersMap map[string]types.NamespacedName 592 mysqlTestCases map[string]*sysAcctTestCase 593 ) 594 595 BeforeEach(func() { 596 cleanEnv() 597 DeferCleanup(cleanEnv) 598 599 // setup testcase 600 mysqlTestCases = map[string]*sysAcctTestCase{ 601 "wesql-with-accts": { 602 componentName: mysqlCompName, 603 componentDefRef: mysqlCompDefName, 604 accounts: getAllSysAccounts(), 605 }, 606 "wesql-with-accts-dup": { 607 componentName: mysqlCompName, 608 componentDefRef: mysqlCompDefName, 609 accounts: getAllSysAccounts(), 610 }, 611 } 612 clustersMap = initSysAccountTestsAndCluster(mysqlTestCases) 613 }) 614 615 It("Patch Cluster after running", func() { 616 for testName, testCase := range mysqlTestCases { 617 var ( 618 acctList appsv1alpha1.KBAccountType 619 jobsNum int 620 secretsNum int 621 ) 622 623 for _, acc := range testCase.accounts { 624 resource := testCase.resourceMap[acc] 625 acctList |= acc.GetAccountID() 626 jobsNum += resource.jobNum 627 secretsNum += resource.secretNum 628 } 629 630 // get a cluster instance from map, created during preparation 631 clusterKey, ok := clustersMap[testName] 632 Expect(ok).To(BeTrue()) 633 // patch cluster to running 634 patchClusterToRunning(clusterKey, testCase.componentName) 635 636 // get latest cluster object 637 cluster := &appsv1alpha1.Cluster{} 638 Expect(k8sClient.Get(ctx, clusterKey, cluster)).Should(Succeed()) 639 640 ml := getLabelsForSecretsAndJobs(componentUniqueKey{namespace: cluster.Namespace, clusterName: cluster.Name, componentName: testCase.componentName}) 641 642 // wait for a while till all jobs are created 643 Eventually(func(g Gomega) { 644 jobs := &batchv1.JobList{} 645 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 646 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 647 }).Should(Succeed()) 648 649 By("Enable monitor, no more jobs or secrets should be created") 650 // patch cluster, flip comp.Monitor 651 Eventually(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1alpha1.Cluster) { 652 for _, comp := range cluster.Spec.ComponentSpecs { 653 comp.Monitor = !comp.Monitor 654 } 655 })).Should(Succeed()) 656 657 jobs := &batchv1.JobList{} 658 Eventually(func(g Gomega) { 659 g.Expect(k8sClient.List(ctx, jobs, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 660 g.Expect(len(jobs.Items)).To(BeEquivalentTo(jobsNum)) 661 }).Should(Succeed()) 662 663 if len(jobs.Items) == 0 { 664 continue 665 } 666 // delete one job, but the job IS NOT completed. 667 By("Delete one job directly, the system should not create new secrets.") 668 jobToDelete := jobs.Items[0] 669 jobKey := client.ObjectKeyFromObject(&jobToDelete) 670 671 tmpJob := &batchv1.Job{} 672 Eventually(func(g Gomega) { 673 g.Expect(k8sClient.Get(ctx, jobKey, tmpJob)).To(Succeed()) 674 g.Expect(len(tmpJob.ObjectMeta.Finalizers)).To(BeEquivalentTo(1)) 675 }).Should(Succeed()) 676 677 Expect(testapps.ChangeObjStatus(&testCtx, tmpJob, func() { 678 tmpJob.Status.Conditions = []batchv1.JobCondition{{ 679 Type: batchv1.JobFailed, 680 Status: corev1.ConditionTrue, 681 }} 682 })).To(Succeed()) 683 684 By("Verify secrets size does not increase") 685 var secretsLen int 686 Consistently(func(g Gomega) { 687 secrets := &corev1.SecretList{} 688 g.Expect(k8sClient.List(ctx, secrets, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 689 secretsLen = len(secrets.Items) 690 }).Should(Succeed()) 691 692 if len(jobs.Items) < 1 { 693 continue 694 } 695 // delete one job directly, but the job is completed. 696 By("Delete one job and mark it as JobComplete, the system should create new secrets.") 697 jobKey = client.ObjectKeyFromObject(&jobs.Items[1]) 698 tmpJob = &batchv1.Job{} 699 Eventually(func(g Gomega) { 700 g.Expect(k8sClient.Get(ctx, jobKey, tmpJob)).To(Succeed()) 701 g.Expect(len(tmpJob.ObjectMeta.Finalizers)).To(BeEquivalentTo(1)) 702 }).Should(Succeed()) 703 Expect(testapps.ChangeObjStatus(&testCtx, tmpJob, func() { 704 tmpJob.Status.Conditions = []batchv1.JobCondition{{ 705 Type: batchv1.JobComplete, 706 Status: corev1.ConditionTrue, 707 }} 708 })).To(Succeed()) 709 710 By("Verify jobs size decreased and secrets size increased") 711 Eventually(func(g Gomega) { 712 secrets := &corev1.SecretList{} 713 g.Expect(k8sClient.List(ctx, secrets, client.InNamespace(cluster.Namespace), ml)).To(Succeed()) 714 secretsSize2 := len(secrets.Items) 715 g.Expect(secretsSize2).To(BeNumerically(">", secretsLen)) 716 }).Should(Succeed()) 717 } 718 }) 719 }) 720 })