github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/ops_progress_util_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 "time" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 32 opsutil "github.com/1aal/kubeblocks/controllers/apps/operations/util" 33 "github.com/1aal/kubeblocks/pkg/constant" 34 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 35 "github.com/1aal/kubeblocks/pkg/generics" 36 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 37 testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s" 38 ) 39 40 var _ = Describe("Ops ProgressDetails", func() { 41 42 var ( 43 randomStr = testCtx.GetRandomStr() 44 clusterDefinitionName = "cluster-definition-for-ops-" + randomStr 45 clusterVersionName = "clusterversion-for-ops-" + randomStr 46 clusterName = "cluster-for-ops-" + randomStr 47 ) 48 49 cleanEnv := func() { 50 // must wait till resources deleted and no longer existed before the testcases start, 51 // otherwise if later it needs to create some new resource objects with the same name, 52 // in race conditions, it will find the existence of old objects, resulting failure to 53 // create the new objects. 54 By("clean resources") 55 56 // delete cluster(and all dependent sub-resources), clusterversion and clusterdef 57 testapps.ClearClusterResources(&testCtx) 58 59 // delete rest resources 60 inNS := client.InNamespace(testCtx.DefaultNamespace) 61 ml := client.HasLabels{testCtx.TestObjLabelKey} 62 // namespaced 63 testapps.ClearResources(&testCtx, generics.OpsRequestSignature, inNS, ml) 64 // default GracePeriod is 30s 65 testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml, client.GracePeriodSeconds(0)) 66 } 67 68 BeforeEach(cleanEnv) 69 70 AfterEach(cleanEnv) 71 72 initClusterForOps := func(opsRes *OpsResource) { 73 Expect(opsutil.PatchClusterOpsAnnotations(ctx, k8sClient, opsRes.Cluster, nil)).Should(Succeed()) 74 opsRes.Cluster.Status.Phase = appsv1alpha1.RunningClusterPhase 75 } 76 77 testProgressDetailsWithStatefulPodUpdating := func(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource, consensusPodList []corev1.Pod) { 78 By("mock pod of statefulSet updating by deleting the pod") 79 pod := &consensusPodList[0] 80 testk8s.MockPodIsTerminating(ctx, testCtx, pod) 81 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 82 Expect(getProgressDetailStatus(opsRes, consensusComp, pod)).Should(Equal(appsv1alpha1.ProcessingProgressStatus)) 83 84 By("mock one pod of StatefulSet to update successfully") 85 testk8s.RemovePodFinalizer(ctx, testCtx, pod) 86 testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, 87 pod.Name, "leader", "ReadWrite") 88 89 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 90 Expect(getProgressDetailStatus(opsRes, consensusComp, pod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 91 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/4")) 92 } 93 94 testProgressDetailsWithStatelessPodUpdating := func(reqCtx intctrlutil.RequestCtx, opsRes *OpsResource) { 95 By("create a new pod") 96 newPodName := "busybox-" + testCtx.GetRandomStr() 97 testapps.MockStatelessPod(&testCtx, nil, clusterName, statelessComp, newPodName) 98 newPod := &corev1.Pod{} 99 Expect(k8sClient.Get(ctx, client.ObjectKey{Name: newPodName, Namespace: testCtx.DefaultNamespace}, newPod)).Should(Succeed()) 100 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 101 Expect(getProgressDetailStatus(opsRes, statelessComp, newPod)).Should(Equal(appsv1alpha1.ProcessingProgressStatus)) 102 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/4")) 103 104 By("mock new pod is ready") 105 Expect(testapps.ChangeObjStatus(&testCtx, newPod, func() { 106 lastTransTime := metav1.NewTime(time.Now().Add(-11 * time.Second)) 107 testk8s.MockPodAvailable(newPod, lastTransTime) 108 })).ShouldNot(HaveOccurred()) 109 110 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 111 Expect(getProgressDetailStatus(opsRes, statelessComp, newPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 112 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("2/4")) 113 } 114 115 Context("Test Ops ProgressDetails", func() { 116 It("Test Ops ProgressDetails for rolling update", func() { 117 By("init operations resources ") 118 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 119 opsRes, _, _ := initOperationsResources(clusterDefinitionName, clusterVersionName, clusterName) 120 121 By("create restart ops and pods of consensus component") 122 opsRes.OpsRequest = createRestartOpsObj(clusterName, "restart-"+randomStr) 123 mockComponentIsOperating(opsRes.Cluster, appsv1alpha1.UpdatingClusterCompPhase, consensusComp, statelessComp) 124 podList := initConsensusPods(ctx, k8sClient, opsRes, clusterName) 125 126 By("mock restart OpsRequest is Running") 127 _, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes) 128 Expect(err).ShouldNot(HaveOccurred()) 129 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 130 131 By("test the progressDetails when stateful pod updates during restart operation") 132 testProgressDetailsWithStatefulPodUpdating(reqCtx, opsRes, podList) 133 134 By("test the progressDetails when stateless pod updates during restart operation") 135 Expect(opsRes.OpsRequest.Status.Components[statelessComp].Phase).Should(Equal(appsv1alpha1.UpdatingClusterCompPhase)) // appsv1alpha1.RebootingPhase 136 testProgressDetailsWithStatelessPodUpdating(reqCtx, opsRes) 137 138 }) 139 140 It("Test Ops ProgressDetails with horizontally scaling replicas", func() { 141 By("init operations resources ") 142 reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx} 143 opsRes, _, _ := initOperationsResources(clusterDefinitionName, clusterVersionName, clusterName) 144 podList := initConsensusPods(ctx, k8sClient, opsRes, clusterName) 145 146 By("create horizontalScaling operation to test the progressDetails when scaling down the replicas") 147 opsRes.OpsRequest = createHorizontalScaling(clusterName, 2) 148 mockComponentIsOperating(opsRes.Cluster, appsv1alpha1.UpdatingClusterCompPhase, consensusComp) // appsv1alpha1.HorizontalScalingPhase 149 initClusterForOps(opsRes) 150 151 By("mock HorizontalScaling OpsRequest phase is running") 152 _, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes) 153 Expect(err).ShouldNot(HaveOccurred()) 154 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 155 // do h-scale action 156 _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) 157 Expect(err).ShouldNot(HaveOccurred()) 158 159 By("mock the pod is terminating, pod[0] is target pod to delete. and mock pod[1] is failed and deleted by stateful controller") 160 for i := 0; i < 2; i++ { 161 pod := &podList[i] 162 pod.Kind = constant.PodKind 163 testk8s.MockPodIsTerminating(ctx, testCtx, pod) 164 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 165 Expect(getProgressDetailStatus(opsRes, consensusComp, pod)).Should(Equal(appsv1alpha1.ProcessingProgressStatus)) 166 167 } 168 By("mock the target pod is deleted and progressDetail status should be succeed") 169 targetPod := &podList[0] 170 testk8s.RemovePodFinalizer(ctx, testCtx, targetPod) 171 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 172 Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 173 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/2")) 174 175 By("mock the pod[1] to re-create") 176 pod := &podList[1] 177 testk8s.RemovePodFinalizer(ctx, testCtx, pod) 178 testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, 179 pod.Name, "Follower", "ReadWrite") 180 // expect the progress is 2/2 181 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 182 Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 183 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("2/2")) 184 185 By("create horizontalScaling operation to test the progressDetails when scaling up the replicas ") 186 initClusterForOps(opsRes) 187 expectClusterComponentReplicas := int32(2) 188 Expect(testapps.ChangeObj(&testCtx, opsRes.Cluster, func(lcluster *appsv1alpha1.Cluster) { 189 lcluster.Spec.ComponentSpecs[1].Replicas = expectClusterComponentReplicas 190 })).ShouldNot(HaveOccurred()) 191 // ops will use the startTimestamp to make decision, start time should not equal the pod createTime during testing. 192 time.Sleep(time.Second) 193 opsRes.OpsRequest = createHorizontalScaling(clusterName, 3) 194 mockComponentIsOperating(opsRes.Cluster, appsv1alpha1.UpdatingClusterCompPhase, consensusComp, statelessComp) 195 // update ops phase to Running first 196 _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) 197 Expect(err).ShouldNot(HaveOccurred()) 198 Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase)) 199 // do h-scale cluster 200 _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) 201 Expect(err).ShouldNot(HaveOccurred()) 202 203 By("test the progressDetails when scaling up replicas") 204 testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, consensusComp, 205 targetPod.Name, "leader", "ReadWrite") 206 Expect(k8sClient.Get(ctx, client.ObjectKey{Name: targetPod.Name, Namespace: testCtx.DefaultNamespace}, targetPod)).Should(Succeed()) 207 _, _ = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) 208 Expect(getProgressDetailStatus(opsRes, consensusComp, targetPod)).Should(Equal(appsv1alpha1.SucceedProgressStatus)) 209 Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/1")) 210 }) 211 }) 212 }) 213 214 func getProgressDetailStatus(opsRes *OpsResource, componentName string, pod *corev1.Pod) appsv1alpha1.ProgressStatus { 215 objectKey := getProgressObjectKey(pod.Kind, pod.Name) 216 progressDetails := opsRes.OpsRequest.Status.Components[componentName].ProgressDetails 217 progressDetail := findStatusProgressDetail(progressDetails, objectKey) 218 var status appsv1alpha1.ProgressStatus 219 if progressDetail != nil { 220 status = progressDetail.Status 221 } 222 return status 223 }