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  })