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  })