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