github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/horizontal_scaling_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 "time" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 33 opsutil "github.com/1aal/kubeblocks/controllers/apps/operations/util" 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 testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s" 39 ) 40 41 var _ = Describe("HorizontalScaling OpsRequest", func() { 42 43 var ( 44 randomStr = testCtx.GetRandomStr() 45 clusterDefinitionName = "cluster-definition-for-ops-" + randomStr 46 clusterVersionName = "clusterversion-for-ops-" + randomStr 47 clusterName = "cluster-for-ops-" + randomStr 48 ) 49 50 cleanEnv := func() { 51 // must wait till resources deleted and no longer existed before the testcases start, 52 // otherwise if later it needs to create some new resource objects with the same name, 53 // in race conditions, it will find the existence of old objects, resulting failure to 54 // create the new objects. 55 By("clean resources") 56 57 // delete cluster(and all dependent sub-resources), clusterversion and clusterdef 58 testapps.ClearClusterResources(&testCtx) 59 60 // delete rest resources 61 inNS := client.InNamespace(testCtx.DefaultNamespace) 62 ml := client.HasLabels{testCtx.TestObjLabelKey} 63 // namespaced 64 testapps.ClearResources(&testCtx, generics.OpsRequestSignature, inNS, ml) 65 // default GracePeriod is 30s 66 testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml, client.GracePeriodSeconds(0)) 67 } 68 69 BeforeEach(cleanEnv) 70 71 AfterEach(cleanEnv) 72 73 initClusterAnnotationAndPhaseForOps := func(opsRes *OpsResource) { 74 Expect(opsutil.PatchClusterOpsAnnotations(ctx, k8sClient, opsRes.Cluster, nil)).Should(Succeed()) 75 Expect(testapps.ChangeObjStatus(&testCtx, opsRes.Cluster, func() { 76 opsRes.Cluster.Status.Phase = appsv1alpha1.RunningClusterPhase 77 })).ShouldNot(HaveOccurred()) 78 } 79 80 Context("Test OpsRequest", func() { 81 82 commonHScaleConsensusCompTest := func(reqCtx intctrlutil.RequestCtx, replicas int) (*OpsResource, []corev1.Pod) { 83 By("init operations resources with CLusterDefinition/ClusterVersion/Hybrid components Cluster/consensus Pods") 84 opsRes, _, _ := initOperationsResources(clusterDefinitionName, clusterVersionName, clusterName) 85 podList := initConsensusPods(ctx, k8sClient, opsRes, clusterName) 86 87 By(fmt.Sprintf("create opsRequest for horizontal scaling of consensus component from 3 to %d", replicas)) 88 initClusterAnnotationAndPhaseForOps(opsRes) 89 opsRes.OpsRequest = createHorizontalScaling(clusterName, replicas) 90 mockComponentIsOperating(opsRes.Cluster, appsv1alpha1.UpdatingClusterCompPhase, consensusComp) 91 92 By("expect for opsRequest phase is Creating after doing action") 93 _, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes) 94 Expect(err).ShouldNot(HaveOccurred()) 95 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 96 97 By(fmt.Sprintf("expect for the replicas of consensus component is %d after doing action again when opsRequest phase is Creating", replicas)) 98 _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) 99 Expect(err).ShouldNot(HaveOccurred()) 100 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(opsRes.Cluster), func(g Gomega, tmpCluster *appsv1alpha1.Cluster) { 101 g.Expect(tmpCluster.Spec.GetComponentByName(consensusComp).Replicas).Should(BeEquivalentTo(replicas)) 102 })).Should(Succeed()) 103 104 By("Test OpsManager.Reconcile function when horizontal scaling OpsRequest is Running") 105 opsRes.OpsRequest.Status.Phase = appsv1alpha1.OpsRequestKind 106 _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 107 Expect(err).ShouldNot(HaveOccurred()) 108 return opsRes, podList 109 } 110 111 checkOpsRequestPhaseIsSucceed := func(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource) { 112 By("expect for opsRequest phase is Succeed after pods has been scaled and component phase is Running") 113 // mock consensus component is Running 114 mockConsensusCompToRunning(opsRes) 115 _, err := GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 116 Expect(err).ShouldNot(HaveOccurred()) 117 Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(appsv1alpha1.OpsSucceedPhase)) 118 } 119 120 checkCancelledSucceed := func(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource) { 121 _, err := GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 122 Expect(err).ShouldNot(HaveOccurred()) 123 Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(appsv1alpha1.OpsCancelledPhase)) 124 opsProgressDetails := opsRes.OpsRequest.Status.Components[consensusComp].ProgressDetails 125 Expect(len(opsProgressDetails)).Should(Equal(1)) 126 Expect(opsProgressDetails[0].Status).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 127 } 128 129 It("test scaling down replicas", func() { 130 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 131 opsRes, podList := commonHScaleConsensusCompTest(reqCtx, 1) 132 By("mock two pods are deleted") 133 for i := 0; i < 2; i++ { 134 pod := &podList[i] 135 pod.Kind = constant.PodKind 136 testk8s.MockPodIsTerminating(ctx, testCtx, pod) 137 testk8s.RemovePodFinalizer(ctx, testCtx, pod) 138 } 139 checkOpsRequestPhaseIsSucceed(reqCtx, opsRes) 140 }) 141 142 It("test scaling out replicas", func() { 143 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 144 opsRes, _ := commonHScaleConsensusCompTest(reqCtx, 5) 145 By("mock two pods are created") 146 for i := 3; i < 5; i++ { 147 podName := fmt.Sprintf("%s-%s-%d", clusterName, consensusComp, i) 148 testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, podName, "follower", "Readonly") 149 } 150 checkOpsRequestPhaseIsSucceed(reqCtx, opsRes) 151 }) 152 153 It("test canceling HScale opsRequest which scales down replicas of component", func() { 154 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 155 opsRes, podList := commonHScaleConsensusCompTest(reqCtx, 1) 156 157 By("mock one pod has been deleted") 158 pod := &podList[0] 159 pod.Kind = constant.PodKind 160 testk8s.MockPodIsTerminating(ctx, testCtx, pod) 161 testk8s.RemovePodFinalizer(ctx, testCtx, pod) 162 163 By("cancel HScale opsRequest after one pod has been deleted") 164 cancelOpsRequest(reqCtx, opsRes, time.Now().Add(-1*time.Second)) 165 166 By("re-create the deleted pod") 167 podName := fmt.Sprintf("%s-%s-%d", clusterName, consensusComp, 0) 168 testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, podName, "leader", "ReadWrite") 169 170 By("expect for opsRequest phase is Succeed after pods has been scaled and component phase is Running") 171 mockConsensusCompToRunning(opsRes) 172 checkCancelledSucceed(reqCtx, opsRes) 173 }) 174 175 It("test canceling HScale opsRequest which scales out replicas of component", func() { 176 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 177 opsRes, _ := commonHScaleConsensusCompTest(reqCtx, 5) 178 179 By("mock one pod is created") 180 podName := fmt.Sprintf("%s-%s-%d", clusterName, consensusComp, 3) 181 pod := testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, podName, "follower", "Readonly") 182 183 By("cancel HScale opsRequest after pne pod is created") 184 cancelOpsRequest(reqCtx, opsRes, time.Now().Add(-1*time.Second)) 185 186 By("delete the created pod") 187 pod.Kind = constant.PodKind 188 testk8s.MockPodIsTerminating(ctx, testCtx, pod) 189 testk8s.RemovePodFinalizer(ctx, testCtx, pod) 190 191 By("expect for opsRequest phase is Succeed after pods has been scaled and component phase is Running") 192 mockConsensusCompToRunning(opsRes) 193 checkCancelledSucceed(reqCtx, opsRes) 194 }) 195 }) 196 }) 197 198 func createHorizontalScaling(clusterName string, replicas int) *appsv1alpha1.OpsRequest { 199 horizontalOpsName := "horizontal-scaling-ops-" + testCtx.GetRandomStr() 200 ops := testapps.NewOpsRequestObj(horizontalOpsName, testCtx.DefaultNamespace, 201 clusterName, appsv1alpha1.HorizontalScalingType) 202 ops.Spec.HorizontalScalingList = []appsv1alpha1.HorizontalScaling{ 203 { 204 ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusComp}, 205 Replicas: int32(replicas), 206 }, 207 } 208 return testapps.CreateOpsRequest(ctx, testCtx, ops) 209 } 210 211 func cancelOpsRequest(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource, cancelTime time.Time) { 212 opsRequest := opsRes.OpsRequest 213 opsRequest.Spec.Cancel = true 214 opsBehaviour := GetOpsManager().OpsMap[opsRequest.Spec.Type] 215 Expect(testapps.ChangeObjStatus(&testCtx, opsRequest, func() { 216 opsRequest.Status.CancelTimestamp = metav1.Time{Time: cancelTime} 217 opsRequest.Status.Phase = appsv1alpha1.OpsCancellingPhase 218 })).Should(Succeed()) 219 Expect(opsBehaviour.CancelFunc(reqCtx, k8sClient, opsRes)).ShouldNot(HaveOccurred()) 220 } 221 222 func mockConsensusCompToRunning(opsRes *OpsResource) { 223 // mock consensus component is Running 224 compStatus := opsRes.Cluster.Status.Components[consensusComp] 225 compStatus.Phase = appsv1alpha1.RunningClusterCompPhase 226 opsRes.Cluster.Status.Components[consensusComp] = compStatus 227 }