github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/rsm/utils_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 rsm 21 22 import ( 23 "context" 24 "fmt" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 29 "github.com/golang/mock/gomock" 30 corev1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/util/intstr" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 35 "github.com/1aal/kubeblocks/pkg/constant" 36 "github.com/1aal/kubeblocks/pkg/controller/builder" 37 ) 38 39 var _ = Describe("utils test", func() { 40 var priorityMap map[string]int 41 42 BeforeEach(func() { 43 rsm = builder.NewReplicatedStateMachineBuilder(namespace, name). 44 SetService(&corev1.Service{}). 45 SetRoles(roles). 46 GetObject() 47 priorityMap = ComposeRolePriorityMap(rsm.Spec.Roles) 48 }) 49 50 Context("ComposeRolePriorityMap function", func() { 51 It("should work well", func() { 52 priorityList := []int{ 53 leaderPriority, 54 followerReadonlyPriority, 55 followerNonePriority, 56 learnerPriority, 57 } 58 Expect(priorityMap).ShouldNot(BeZero()) 59 Expect(priorityMap).Should(HaveLen(len(roles) + 1)) 60 for i, role := range roles { 61 Expect(priorityMap[role.Name]).Should(Equal(priorityList[i])) 62 } 63 }) 64 }) 65 66 Context("SortPods function", func() { 67 It("should work well", func() { 68 pods := []corev1.Pod{ 69 *builder.NewPodBuilder(namespace, "pod-0").AddLabels(roleLabelKey, "follower").GetObject(), 70 *builder.NewPodBuilder(namespace, "pod-1").AddLabels(roleLabelKey, "logger").GetObject(), 71 *builder.NewPodBuilder(namespace, "pod-2").GetObject(), 72 *builder.NewPodBuilder(namespace, "pod-3").AddLabels(roleLabelKey, "learner").GetObject(), 73 *builder.NewPodBuilder(namespace, "pod-4").AddLabels(roleLabelKey, "candidate").GetObject(), 74 *builder.NewPodBuilder(namespace, "pod-5").AddLabels(roleLabelKey, "leader").GetObject(), 75 *builder.NewPodBuilder(namespace, "pod-6").AddLabels(roleLabelKey, "learner").GetObject(), 76 } 77 expectedOrder := []string{"pod-4", "pod-2", "pod-3", "pod-6", "pod-1", "pod-0", "pod-5"} 78 79 SortPods(pods, priorityMap, false) 80 for i, pod := range pods { 81 Expect(pod.Name).Should(Equal(expectedOrder[i])) 82 } 83 }) 84 }) 85 86 Context("sortMembersStatus function", func() { 87 It("should work well", func() { 88 // 1(learner)->2(learner)->4(logger)->0(follower)->3(leader) 89 membersStatus := []workloads.MemberStatus{ 90 { 91 PodName: "pod-0", 92 ReplicaRole: workloads.ReplicaRole{Name: "follower"}, 93 }, 94 { 95 PodName: "pod-1", 96 ReplicaRole: workloads.ReplicaRole{Name: "learner"}, 97 }, 98 { 99 PodName: "pod-2", 100 ReplicaRole: workloads.ReplicaRole{Name: "learner"}, 101 }, 102 { 103 PodName: "pod-3", 104 ReplicaRole: workloads.ReplicaRole{Name: "leader"}, 105 }, 106 { 107 PodName: "pod-4", 108 ReplicaRole: workloads.ReplicaRole{Name: "logger"}, 109 }, 110 } 111 expectedOrder := []string{"pod-3", "pod-0", "pod-4", "pod-2", "pod-1"} 112 113 sortMembersStatus(membersStatus, priorityMap) 114 for i, status := range membersStatus { 115 Expect(status.PodName).Should(Equal(expectedOrder[i])) 116 } 117 }) 118 }) 119 120 Context("setMembersStatus function", func() { 121 It("should work well", func() { 122 pods := []corev1.Pod{ 123 *builder.NewPodBuilder(namespace, "pod-0").AddLabels(roleLabelKey, "follower").GetObject(), 124 *builder.NewPodBuilder(namespace, "pod-1").AddLabels(roleLabelKey, "leader").GetObject(), 125 *builder.NewPodBuilder(namespace, "pod-2").AddLabels(roleLabelKey, "follower").GetObject(), 126 } 127 readyCondition := corev1.PodCondition{ 128 Type: corev1.PodReady, 129 Status: corev1.ConditionTrue, 130 } 131 pods[0].Status.Conditions = append(pods[0].Status.Conditions, readyCondition) 132 pods[1].Status.Conditions = append(pods[1].Status.Conditions, readyCondition) 133 oldMembersStatus := []workloads.MemberStatus{ 134 { 135 PodName: "pod-0", 136 ReplicaRole: workloads.ReplicaRole{Name: "leader"}, 137 }, 138 { 139 PodName: "pod-1", 140 ReplicaRole: workloads.ReplicaRole{Name: "follower"}, 141 }, 142 { 143 PodName: "pod-2", 144 ReplicaRole: workloads.ReplicaRole{Name: "follower"}, 145 }, 146 } 147 replicas := int32(3) 148 rsm.Spec.Replicas = &replicas 149 rsm.Status.MembersStatus = oldMembersStatus 150 setMembersStatus(rsm, pods) 151 152 Expect(rsm.Status.MembersStatus).Should(HaveLen(2)) 153 Expect(rsm.Status.MembersStatus[0].PodName).Should(Equal("pod-1")) 154 Expect(rsm.Status.MembersStatus[0].Name).Should(Equal("leader")) 155 Expect(rsm.Status.MembersStatus[1].PodName).Should(Equal("pod-0")) 156 Expect(rsm.Status.MembersStatus[1].Name).Should(Equal("follower")) 157 }) 158 }) 159 160 Context("getRoleName function", func() { 161 It("should work well", func() { 162 pod := builder.NewPodBuilder(namespace, name).AddLabels(roleLabelKey, "LEADER").GetObject() 163 role := getRoleName(*pod) 164 Expect(role).Should(Equal("leader")) 165 }) 166 }) 167 168 Context("getPodsOfStatefulSet function", func() { 169 It("should work well", func() { 170 sts := builder.NewStatefulSetBuilder(namespace, name). 171 AddMatchLabels(constant.KBManagedByKey, kindReplicatedStateMachine). 172 AddMatchLabels(constant.AppInstanceLabelKey, name). 173 GetObject() 174 pod := builder.NewPodBuilder(namespace, getPodName(name, 0)). 175 AddLabels(constant.KBManagedByKey, kindReplicatedStateMachine). 176 AddLabels(constant.AppInstanceLabelKey, name). 177 GetObject() 178 k8sMock.EXPECT(). 179 List(gomock.Any(), gomock.Any(), gomock.Any()). 180 DoAndReturn(func(_ context.Context, podList *corev1.PodList, _ ...client.ListOption) error { 181 Expect(podList).ShouldNot(BeNil()) 182 podList.Items = []corev1.Pod{*pod} 183 return nil 184 }).Times(1) 185 186 pods, err := getPodsOfStatefulSet(ctx, k8sMock, sts) 187 Expect(err).Should(BeNil()) 188 Expect(pods).Should(HaveLen(1)) 189 Expect(pods[0].Namespace).Should(Equal(pod.Namespace)) 190 Expect(pods[0].Name).Should(Equal(pod.Name)) 191 }) 192 }) 193 194 Context("getHeadlessSvcName function", func() { 195 It("should work well", func() { 196 Expect(getHeadlessSvcName(*rsm)).Should(Equal("bar-headless")) 197 }) 198 }) 199 200 Context("findSvcPort function", func() { 201 It("should work well", func() { 202 By("set port name") 203 rsm.Spec.Service.Spec.Ports = []corev1.ServicePort{ 204 { 205 Name: "svc-port", 206 Protocol: corev1.ProtocolTCP, 207 Port: 12345, 208 TargetPort: intstr.FromString("my-service"), 209 }, 210 } 211 containerPort := int32(54321) 212 container := corev1.Container{ 213 Name: name, 214 Ports: []corev1.ContainerPort{ 215 { 216 Name: "my-service", 217 Protocol: corev1.ProtocolTCP, 218 ContainerPort: containerPort, 219 }, 220 }, 221 } 222 pod := builder.NewPodBuilder(namespace, getPodName(name, 0)). 223 SetContainers([]corev1.Container{container}). 224 GetObject() 225 rsm.Spec.Template = corev1.PodTemplateSpec{ 226 ObjectMeta: pod.ObjectMeta, 227 Spec: pod.Spec, 228 } 229 Expect(findSvcPort(*rsm)).Should(BeEquivalentTo(containerPort)) 230 231 By("set port number") 232 rsm.Spec.Service.Spec.Ports = []corev1.ServicePort{ 233 { 234 Name: "svc-port", 235 Protocol: corev1.ProtocolTCP, 236 Port: 12345, 237 TargetPort: intstr.FromInt(int(containerPort)), 238 }, 239 } 240 Expect(findSvcPort(*rsm)).Should(BeEquivalentTo(containerPort)) 241 242 By("set no matched port") 243 rsm.Spec.Service.Spec.Ports = []corev1.ServicePort{ 244 { 245 Name: "svc-port", 246 Protocol: corev1.ProtocolTCP, 247 Port: 12345, 248 TargetPort: intstr.FromInt(int(containerPort - 1)), 249 }, 250 } 251 Expect(findSvcPort(*rsm)).Should(BeZero()) 252 }) 253 }) 254 255 Context("getPodName function", func() { 256 It("should work well", func() { 257 Expect(getPodName(name, 1)).Should(Equal("bar-1")) 258 }) 259 }) 260 261 Context("getActionName function", func() { 262 It("should work well", func() { 263 Expect(getActionName(name, 1, 2, jobTypeSwitchover)).Should(Equal("bar-1-2-switchover")) 264 }) 265 }) 266 267 Context("getLeaderPodName function", func() { 268 It("should work well", func() { 269 By("set leader") 270 membersStatus := []workloads.MemberStatus{ 271 { 272 PodName: "pod-0", 273 ReplicaRole: workloads.ReplicaRole{Name: "leader", IsLeader: true}, 274 }, 275 { 276 PodName: "pod-1", 277 ReplicaRole: workloads.ReplicaRole{Name: "follower"}, 278 }, 279 { 280 PodName: "pod-2", 281 ReplicaRole: workloads.ReplicaRole{Name: "follower"}, 282 }, 283 } 284 Expect(getLeaderPodName(membersStatus)).Should(Equal(membersStatus[0].PodName)) 285 286 By("set no leader") 287 membersStatus[0].IsLeader = false 288 Expect(getLeaderPodName(membersStatus)).Should(BeZero()) 289 }) 290 }) 291 292 Context("getPodOrdinal function", func() { 293 It("should work well", func() { 294 ordinal, err := getPodOrdinal("pod-5") 295 Expect(err).Should(BeNil()) 296 Expect(ordinal).Should(Equal(5)) 297 298 _, err = getPodOrdinal("foo-bar") 299 Expect(err).ShouldNot(BeNil()) 300 Expect(err.Error()).Should(ContainSubstring("wrong pod name")) 301 }) 302 }) 303 304 Context("findActionImage function", func() { 305 It("should work well", func() { 306 Expect(findActionImage(&workloads.MembershipReconfiguration{}, jobTypePromote)).Should(Equal(defaultActionImage)) 307 }) 308 }) 309 310 Context("getActionCommand function", func() { 311 It("should work well", func() { 312 reconfiguration := &workloads.MembershipReconfiguration{ 313 SwitchoverAction: &workloads.Action{Command: []string{"switchover"}}, 314 MemberJoinAction: &workloads.Action{Command: []string{"member-join"}}, 315 MemberLeaveAction: &workloads.Action{Command: []string{"member-leave"}}, 316 LogSyncAction: &workloads.Action{Command: []string{"log-sync"}}, 317 PromoteAction: &workloads.Action{Command: []string{"promote"}}, 318 } 319 320 Expect(getActionCommand(reconfiguration, jobTypeSwitchover)).Should(Equal(reconfiguration.SwitchoverAction.Command)) 321 Expect(getActionCommand(reconfiguration, jobTypeMemberJoinNotifying)).Should(Equal(reconfiguration.MemberJoinAction.Command)) 322 Expect(getActionCommand(reconfiguration, jobTypeMemberLeaveNotifying)).Should(Equal(reconfiguration.MemberLeaveAction.Command)) 323 Expect(getActionCommand(reconfiguration, jobTypeLogSync)).Should(Equal(reconfiguration.LogSyncAction.Command)) 324 Expect(getActionCommand(reconfiguration, jobTypePromote)).Should(Equal(reconfiguration.PromoteAction.Command)) 325 }) 326 }) 327 328 Context("AddAnnotationScope function", func() { 329 It("should work well", func() { 330 By("call with a nil map") 331 var annotations map[string]string 332 Expect(AddAnnotationScope(HeadlessServiceScope, annotations)).Should(BeNil()) 333 334 By("call with an empty map") 335 annotations = make(map[string]string, 0) 336 scopedAnnotations := AddAnnotationScope(HeadlessServiceScope, annotations) 337 Expect(scopedAnnotations).ShouldNot(BeNil()) 338 Expect(scopedAnnotations).Should(HaveLen(0)) 339 340 By("call with none empty map") 341 annotations["foo"] = "bar" 342 annotations["foo/bar"] = "foo.bar" 343 annotations["foo.bar/bar"] = "foo.bar.bar" 344 scopedAnnotations = AddAnnotationScope(HeadlessServiceScope, annotations) 345 Expect(scopedAnnotations).ShouldNot(BeNil()) 346 Expect(scopedAnnotations).Should(HaveLen(len(annotations))) 347 for k, v := range annotations { 348 nk := fmt.Sprintf("%s%s", k, HeadlessServiceScope) 349 nv, ok := scopedAnnotations[nk] 350 Expect(ok).Should(BeTrue()) 351 Expect(nv).Should(Equal(v)) 352 } 353 }) 354 }) 355 356 Context("ParseAnnotationsOfScope function", func() { 357 It("should work well", func() { 358 By("call with a nil map") 359 var scopedAnnotations map[string]string 360 Expect(ParseAnnotationsOfScope(HeadlessServiceScope, scopedAnnotations)).Should(BeNil()) 361 362 By("call with an empty map") 363 scopedAnnotations = make(map[string]string, 0) 364 annotations := ParseAnnotationsOfScope(HeadlessServiceScope, scopedAnnotations) 365 Expect(annotations).ShouldNot(BeNil()) 366 Expect(annotations).Should(HaveLen(0)) 367 368 By("call with RootScope") 369 scopedAnnotations["foo"] = "bar" 370 scopedAnnotations["foo.bar"] = "foo.bar" 371 headlessK := "foo.headless.rsm" 372 scopedAnnotations[headlessK] = headlessK 373 annotations = ParseAnnotationsOfScope(RootScope, scopedAnnotations) 374 Expect(annotations).ShouldNot(BeNil()) 375 Expect(annotations).Should(HaveLen(2)) 376 delete(scopedAnnotations, headlessK) 377 for k, v := range scopedAnnotations { 378 nv, ok := annotations[k] 379 Expect(ok).Should(BeTrue()) 380 Expect(nv).Should(Equal(v)) 381 } 382 383 By("call with none RootScope") 384 scopedAnnotations[headlessK] = headlessK 385 annotations = ParseAnnotationsOfScope(HeadlessServiceScope, scopedAnnotations) 386 Expect(annotations).Should(HaveLen(1)) 387 Expect(annotations["foo"]).Should(Equal(headlessK)) 388 }) 389 }) 390 })