github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/switchover_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 operations 21 22 import ( 23 "fmt" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 28 batchv1 "k8s.io/api/batch/v1" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/types" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 34 "github.com/1aal/kubeblocks/pkg/constant" 35 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 36 "github.com/1aal/kubeblocks/pkg/generics" 37 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 38 ) 39 40 var ( 41 clusterDefObj *appsv1alpha1.ClusterDefinition 42 clusterVersionObj *appsv1alpha1.ClusterVersion 43 clusterObj *appsv1alpha1.Cluster 44 ) 45 46 var _ = Describe("", func() { 47 var ( 48 randomStr = testCtx.GetRandomStr() 49 clusterVersionName = "cluster-version-for-ops-" + randomStr 50 clusterName = "cluster-for-ops-" + randomStr 51 ) 52 53 defaultRole := func(index int32) string { 54 role := constant.Follower 55 if index == 0 { 56 role = constant.Leader 57 } 58 return role 59 } 60 61 patchK8sJobStatus := func(jobStatus batchv1.JobConditionType, key types.NamespacedName) { 62 Eventually(testapps.GetAndChangeObjStatus(&testCtx, key, func(job *batchv1.Job) { 63 found := false 64 for _, cond := range job.Status.Conditions { 65 if cond.Type == jobStatus { 66 found = true 67 } 68 } 69 if !found { 70 jobCondition := batchv1.JobCondition{Type: jobStatus} 71 job.Status.Conditions = append(job.Status.Conditions, jobCondition) 72 } 73 })).Should(Succeed()) 74 } 75 76 cleanEnv := func() { 77 // must wait till resources deleted and no longer existed before the testcases start, 78 // otherwise if later it needs to create some new resource objects with the same name, 79 // in race conditions, it will find the existence of old objects, resulting failure to 80 // create the new objects. 81 By("clean resources") 82 83 // delete cluster(and all dependent sub-resources), clusterversion and clusterdef 84 testapps.ClearClusterResources(&testCtx) 85 86 // delete rest resources 87 inNS := client.InNamespace(testCtx.DefaultNamespace) 88 ml := client.HasLabels{testCtx.TestObjLabelKey} 89 // namespaced 90 testapps.ClearResources(&testCtx, generics.OpsRequestSignature, inNS, ml) 91 } 92 93 BeforeEach(cleanEnv) 94 95 AfterEach(cleanEnv) 96 97 Context("Test OpsRequest", func() { 98 BeforeEach(func() { 99 By("Create a clusterDefinition obj with switchoverSpec.") 100 commandExecutorEnvItem := &appsv1alpha1.CommandExecutorEnvItem{ 101 Image: testapps.ApeCloudMySQLImage, 102 } 103 commandExecutorItem := &appsv1alpha1.CommandExecutorItem{ 104 Command: []string{"echo", "hello"}, 105 Args: []string{}, 106 } 107 switchoverSpec := &appsv1alpha1.SwitchoverSpec{ 108 WithCandidate: &appsv1alpha1.SwitchoverAction{ 109 CmdExecutorConfig: &appsv1alpha1.CmdExecutorConfig{ 110 CommandExecutorEnvItem: *commandExecutorEnvItem, 111 CommandExecutorItem: *commandExecutorItem, 112 }, 113 }, 114 WithoutCandidate: &appsv1alpha1.SwitchoverAction{ 115 CmdExecutorConfig: &appsv1alpha1.CmdExecutorConfig{ 116 CommandExecutorEnvItem: *commandExecutorEnvItem, 117 CommandExecutorItem: *commandExecutorItem, 118 }, 119 }, 120 } 121 clusterDefObj = testapps.NewClusterDefFactory(consensusComp). 122 AddComponentDef(testapps.ConsensusMySQLComponent, consensusComp). 123 AddSwitchoverSpec(switchoverSpec). 124 Create(&testCtx).GetObject() 125 126 By("Create a clusterVersion obj with replication workloadType.") 127 clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()). 128 AddComponentVersion(consensusComp).AddContainerShort(testapps.DefaultMySQLContainerName, testapps.ApeCloudMySQLImage). 129 Create(&testCtx).GetObject() 130 }) 131 132 It("Test switchover OpsRequest", func() { 133 reqCtx := intctrlutil.RequestCtx{ 134 Ctx: testCtx.Ctx, 135 Recorder: k8sManager.GetEventRecorderFor("opsrequest-controller"), 136 } 137 By("Creating a cluster with consensus .") 138 clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, 139 clusterDefObj.Name, clusterVersionObj.Name).WithRandomName(). 140 AddComponent(consensusComp, consensusComp). 141 SetReplicas(2). 142 Create(&testCtx).GetObject() 143 144 By("Creating a statefulSet.") 145 container := corev1.Container{ 146 Name: "mock-container-name", 147 Image: testapps.ApeCloudMySQLImage, 148 ImagePullPolicy: corev1.PullIfNotPresent, 149 } 150 sts := testapps.NewStatefulSetFactory(testCtx.DefaultNamespace, 151 clusterObj.Name+"-"+consensusComp, clusterObj.Name, consensusComp). 152 AddFinalizers([]string{constant.DBClusterFinalizerName}). 153 AddContainer(container). 154 AddAppInstanceLabel(clusterObj.Name). 155 AddAppComponentLabel(consensusComp). 156 AddAppManagedByLabel(). 157 SetReplicas(2). 158 Create(&testCtx).GetObject() 159 160 By("Creating Pods of replication workloadType.") 161 var ( 162 leaderPod *corev1.Pod 163 followerPod *corev1.Pod 164 ) 165 for i := int32(0); i < *sts.Spec.Replicas; i++ { 166 pod := testapps.NewPodFactory(testCtx.DefaultNamespace, fmt.Sprintf("%s-%d", sts.Name, i)). 167 AddContainer(container). 168 AddLabelsInMap(sts.Labels). 169 AddRoleLabel(defaultRole(i)). 170 Create(&testCtx).GetObject() 171 if pod.Labels[constant.RoleLabelKey] == constant.Leader { 172 leaderPod = pod 173 } else { 174 followerPod = pod 175 } 176 } 177 178 opsRes := &OpsResource{ 179 Cluster: clusterObj, 180 Recorder: k8sManager.GetEventRecorderFor("opsrequest-controller"), 181 } 182 By("mock cluster is Running and the status operations") 183 Expect(testapps.ChangeObjStatus(&testCtx, clusterObj, func() { 184 clusterObj.Status.Phase = appsv1alpha1.RunningClusterPhase 185 clusterObj.Status.Components = map[string]appsv1alpha1.ClusterComponentStatus{ 186 consensusComp: { 187 Phase: appsv1alpha1.RunningClusterCompPhase, 188 }, 189 } 190 })).Should(Succeed()) 191 opsRes.Cluster = clusterObj 192 193 By("create switchover opsRequest") 194 ops := testapps.NewOpsRequestObj("ops-switchover-"+randomStr, testCtx.DefaultNamespace, 195 clusterObj.Name, appsv1alpha1.SwitchoverType) 196 ops.Spec.SwitchoverList = []appsv1alpha1.Switchover{ 197 { 198 ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusComp}, 199 InstanceName: fmt.Sprintf("%s-%s-%d", clusterObj.Name, consensusComp, 1), 200 }, 201 } 202 opsRes.OpsRequest = testapps.CreateOpsRequest(ctx, testCtx, ops) 203 204 By("mock switchover OpsRequest phase is Creating") 205 _, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes) 206 Expect(err).ShouldNot(HaveOccurred()) 207 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 208 209 // do switchover action 210 By("do switchover action") 211 _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) 212 Expect(err).ShouldNot(HaveOccurred()) 213 214 By("do reconcile switchoverAction failed because switchover job status failed") 215 _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 216 Expect(err).Should(HaveOccurred()) 217 Expect(err.Error()).Should(ContainSubstring("job check conditions status failed")) 218 219 By("mock job status to success.") 220 jobName := fmt.Sprintf("%s-%s-%s-%d", constant.KBSwitchoverJobNamePrefix, opsRes.Cluster.Name, consensusComp, opsRes.Cluster.Generation) 221 key := types.NamespacedName{ 222 Name: jobName, 223 Namespace: clusterObj.Namespace, 224 } 225 patchK8sJobStatus(batchv1.JobComplete, key) 226 227 By("do reconcile switchoverAction failed because pod role label is not consistency") 228 _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 229 Expect(err).Should(HaveOccurred()) 230 Expect(err.Error()).Should(ContainSubstring("requeue to waiting for pod role label consistency")) 231 232 By("mock pod role label changed.") 233 Expect(testapps.ChangeObj(&testCtx, leaderPod, func(pod *corev1.Pod) { 234 pod.Labels[constant.RoleLabelKey] = constant.Follower 235 })).Should(Succeed()) 236 Expect(testapps.ChangeObj(&testCtx, followerPod, func(pod *corev1.Pod) { 237 pod.Labels[constant.RoleLabelKey] = constant.Leader 238 })).Should(Succeed()) 239 _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 240 Expect(err).ShouldNot(HaveOccurred()) 241 }) 242 }) 243 })