github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/rolling_upgrade_policy_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 configuration 21 22 import ( 23 "github.com/golang/mock/gomock" 24 . "github.com/onsi/ginkgo/v2" 25 . "github.com/onsi/gomega" 26 apps "k8s.io/api/apps/v1" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 metautil "k8s.io/apimachinery/pkg/util/intstr" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 33 cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto" 34 mock_proto "github.com/1aal/kubeblocks/pkg/configuration/proto/mocks" 35 "github.com/1aal/kubeblocks/pkg/constant" 36 testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s" 37 ) 38 39 var _ = Describe("Reconfigure RollingPolicy", func() { 40 41 var ( 42 k8sMockClient *testutil.K8sClientMockHelper 43 mockParam reconfigureParams 44 reconfigureClient *mock_proto.MockReconfigureClient 45 46 defaultReplica = 3 47 rollingPolicy = upgradePolicyMap[appsv1alpha1.RollingPolicy] 48 ) 49 50 updateLabelPatch := func(pods []corev1.Pod, patch *corev1.Pod) { 51 patchKey := client.ObjectKeyFromObject(patch) 52 for i := range pods { 53 orgPod := &pods[i] 54 if client.ObjectKeyFromObject(orgPod) == patchKey { 55 orgPod.Labels = patch.Labels 56 break 57 } 58 } 59 } 60 61 createReconfigureParam := func(compType appsv1alpha1.WorkloadType, replicas int) reconfigureParams { 62 return newMockReconfigureParams("rollingPolicy", k8sMockClient.Client(), 63 withMockStatefulSet(replicas, nil), 64 withConfigSpec("for_test", map[string]string{ 65 "key": "value", 66 }), 67 withGRPCClient(func(addr string) (cfgproto.ReconfigureClient, error) { 68 return reconfigureClient, nil 69 }), 70 withClusterComponent(replicas), 71 withCDComponent(compType, []appsv1alpha1.ComponentConfigSpec{{ 72 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 73 Name: "for_test", 74 VolumeName: "test_volume", 75 }}})) 76 } 77 78 BeforeEach(func() { 79 k8sMockClient = testutil.NewK8sMockClient() 80 reconfigureClient = mock_proto.NewMockReconfigureClient(k8sMockClient.Controller()) 81 mockParam = createReconfigureParam(appsv1alpha1.Consensus, defaultReplica) 82 }) 83 84 AfterEach(func() { 85 // Add any teardown steps that needs to be executed after each test 86 k8sMockClient.Finish() 87 }) 88 89 Context("consensus rolling reconfigure policy test", func() { 90 It("Should success without error", func() { 91 Expect(rollingPolicy.GetPolicyName()).Should(BeEquivalentTo("rolling")) 92 93 mockLeaderLabel := func(pod *corev1.Pod, i int) { 94 if pod.Labels == nil { 95 pod.Labels = make(map[string]string, 1) 96 } 97 if i == 1 { 98 pod.Labels[constant.RoleLabelKey] = "leader" 99 } else { 100 pod.Labels[constant.RoleLabelKey] = "follower" 101 } 102 } 103 104 acc := 0 105 mockPods := [][]corev1.Pod{ 106 newMockPodsWithStatefulSet(&mockParam.ComponentUnits[0], 2), 107 newMockPodsWithStatefulSet(&mockParam.ComponentUnits[0], 5, 108 mockLeaderLabel), 109 newMockPodsWithStatefulSet(&mockParam.ComponentUnits[0], 3, 110 withReadyPod(0, 0), 111 withAvailablePod(0, 3), 112 mockLeaderLabel), 113 } 114 115 k8sMockClient.MockListMethod(testutil.WithListReturned( 116 testutil.WithConstructListSequenceResult([][]runtime.Object{ 117 fromPodObjectList(mockPods[0]), 118 fromPodObjectList(mockPods[1]), 119 fromPodObjectList(mockPods[2]), 120 }, func(sequence int, r []runtime.Object) { acc = sequence }), testutil.WithAnyTimes())) 121 122 k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { 123 pod, _ := obj.(*corev1.Pod) 124 // mock patch 125 updateLabelPatch(mockPods[acc], pod) 126 return nil 127 }, testutil.WithAnyTimes())) 128 129 reconfigureClient.EXPECT().StopContainer(gomock.Any(), gomock.Any()). 130 Return(&cfgproto.StopContainerResponse{}, nil). 131 AnyTimes() 132 133 // mock wait the number of pods to target replicas 134 status, err := rollingPolicy.Upgrade(mockParam) 135 Expect(err).Should(Succeed()) 136 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 137 138 // mock wait the number of pods to ready status 139 status, err = rollingPolicy.Upgrade(mockParam) 140 Expect(err).Should(Succeed()) 141 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 142 143 // upgrade pod-0 144 status, err = rollingPolicy.Upgrade(mockParam) 145 Expect(err).Should(Succeed()) 146 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 147 Expect(mockPods[acc][0].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) 148 Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) 149 Expect(mockPods[acc][2].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) 150 151 // upgrade pod-2 152 status, err = rollingPolicy.Upgrade(mockParam) 153 Expect(err).Should(Succeed()) 154 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 155 Expect(mockPods[acc][2].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) 156 Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) 157 158 // upgrade pod-1 159 status, err = rollingPolicy.Upgrade(mockParam) 160 Expect(err).Should(Succeed()) 161 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 162 Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) 163 164 // finish check, not upgrade 165 status, err = rollingPolicy.Upgrade(mockParam) 166 Expect(err).Should(Succeed()) 167 Expect(status.Status).Should(BeEquivalentTo(ESNone)) 168 }) 169 }) 170 171 Context("statefulSet rolling reconfigure policy test", func() { 172 It("Should success without error", func() { 173 174 // for mock sts 175 var pods []corev1.Pod 176 { 177 mockParam.Component.WorkloadType = appsv1alpha1.Stateful 178 mockParam.Component.StatefulSpec = &appsv1alpha1.StatefulSetSpec{ 179 LLUpdateStrategy: &apps.StatefulSetUpdateStrategy{ 180 RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ 181 MaxUnavailable: func() *metautil.IntOrString { v := metautil.FromString("100%"); return &v }(), 182 }, 183 }, 184 } 185 pods = newMockPodsWithStatefulSet(&mockParam.ComponentUnits[0], defaultReplica) 186 } 187 188 k8sMockClient.MockListMethod(testutil.WithListReturned( 189 testutil.WithConstructListReturnedResult(fromPodObjectList(pods)), 190 testutil.WithMinTimes(3))) 191 192 k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { 193 pod, _ := obj.(*corev1.Pod) 194 updateLabelPatch(pods, pod) 195 return nil 196 }, testutil.WithTimes(defaultReplica))) 197 198 reconfigureClient.EXPECT().StopContainer(gomock.Any(), gomock.Any()). 199 Return(&cfgproto.StopContainerResponse{}, nil). 200 Times(defaultReplica) 201 202 // mock wait the number of pods to target replicas 203 status, err := rollingPolicy.Upgrade(mockParam) 204 Expect(err).Should(Succeed()) 205 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 206 207 // finish check, not finished 208 status, err = rollingPolicy.Upgrade(mockParam) 209 Expect(err).Should(Succeed()) 210 Expect(status.Status).Should(BeEquivalentTo(ESRetry)) 211 212 // mock async update state 213 go func() { 214 f := withAvailablePod(0, len(pods)) 215 for i := range pods { 216 f(&pods[i], i) 217 } 218 }() 219 220 // finish check, not finished 221 Eventually(func() bool { 222 status, err = rollingPolicy.Upgrade(mockParam) 223 Expect(err).Should(Succeed()) 224 Expect(status.Status).Should(BeElementOf(ESNone, ESRetry)) 225 return status.Status == ESNone 226 }).Should(BeTrue()) 227 228 status, err = rollingPolicy.Upgrade(mockParam) 229 Expect(status.Status).Should(BeEquivalentTo(ESNone)) 230 }) 231 }) 232 233 Context("rolling reconfigure policy test for not supported component", func() { 234 It("Should failed", func() { 235 // not supported type 236 _ = mockParam 237 k8sMockClient.MockListMethod(testutil.WithSucceed(testutil.WithTimes(0))) 238 239 status, err := rollingPolicy.Upgrade(createReconfigureParam(appsv1alpha1.Stateless, defaultReplica)) 240 Expect(err).ShouldNot(Succeed()) 241 Expect(err.Error()).Should(ContainSubstring("not supported component workload type")) 242 Expect(status.Status).Should(BeEquivalentTo(ESNotSupport)) 243 }) 244 }) 245 })