github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/switchover_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  	"fmt"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  
    28  	batchv1 "k8s.io/api/batch/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    34  	"github.com/1aal/kubeblocks/pkg/constant"
    35  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    36  	"github.com/1aal/kubeblocks/pkg/generics"
    37  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    38  )
    39  
    40  var (
    41  	clusterDefObj     *appsv1alpha1.ClusterDefinition
    42  	clusterVersionObj *appsv1alpha1.ClusterVersion
    43  	clusterObj        *appsv1alpha1.Cluster
    44  )
    45  
    46  var _ = Describe("", func() {
    47  	var (
    48  		randomStr          = testCtx.GetRandomStr()
    49  		clusterVersionName = "cluster-version-for-ops-" + randomStr
    50  		clusterName        = "cluster-for-ops-" + randomStr
    51  	)
    52  
    53  	defaultRole := func(index int32) string {
    54  		role := constant.Follower
    55  		if index == 0 {
    56  			role = constant.Leader
    57  		}
    58  		return role
    59  	}
    60  
    61  	patchK8sJobStatus := func(jobStatus batchv1.JobConditionType, key types.NamespacedName) {
    62  		Eventually(testapps.GetAndChangeObjStatus(&testCtx, key, func(job *batchv1.Job) {
    63  			found := false
    64  			for _, cond := range job.Status.Conditions {
    65  				if cond.Type == jobStatus {
    66  					found = true
    67  				}
    68  			}
    69  			if !found {
    70  				jobCondition := batchv1.JobCondition{Type: jobStatus}
    71  				job.Status.Conditions = append(job.Status.Conditions, jobCondition)
    72  			}
    73  		})).Should(Succeed())
    74  	}
    75  
    76  	cleanEnv := func() {
    77  		// must wait till resources deleted and no longer existed before the testcases start,
    78  		// otherwise if later it needs to create some new resource objects with the same name,
    79  		// in race conditions, it will find the existence of old objects, resulting failure to
    80  		// create the new objects.
    81  		By("clean resources")
    82  
    83  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
    84  		testapps.ClearClusterResources(&testCtx)
    85  
    86  		// delete rest resources
    87  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    88  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    89  		// namespaced
    90  		testapps.ClearResources(&testCtx, generics.OpsRequestSignature, inNS, ml)
    91  	}
    92  
    93  	BeforeEach(cleanEnv)
    94  
    95  	AfterEach(cleanEnv)
    96  
    97  	Context("Test OpsRequest", func() {
    98  		BeforeEach(func() {
    99  			By("Create a clusterDefinition obj with switchoverSpec.")
   100  			commandExecutorEnvItem := &appsv1alpha1.CommandExecutorEnvItem{
   101  				Image: testapps.ApeCloudMySQLImage,
   102  			}
   103  			commandExecutorItem := &appsv1alpha1.CommandExecutorItem{
   104  				Command: []string{"echo", "hello"},
   105  				Args:    []string{},
   106  			}
   107  			switchoverSpec := &appsv1alpha1.SwitchoverSpec{
   108  				WithCandidate: &appsv1alpha1.SwitchoverAction{
   109  					CmdExecutorConfig: &appsv1alpha1.CmdExecutorConfig{
   110  						CommandExecutorEnvItem: *commandExecutorEnvItem,
   111  						CommandExecutorItem:    *commandExecutorItem,
   112  					},
   113  				},
   114  				WithoutCandidate: &appsv1alpha1.SwitchoverAction{
   115  					CmdExecutorConfig: &appsv1alpha1.CmdExecutorConfig{
   116  						CommandExecutorEnvItem: *commandExecutorEnvItem,
   117  						CommandExecutorItem:    *commandExecutorItem,
   118  					},
   119  				},
   120  			}
   121  			clusterDefObj = testapps.NewClusterDefFactory(consensusComp).
   122  				AddComponentDef(testapps.ConsensusMySQLComponent, consensusComp).
   123  				AddSwitchoverSpec(switchoverSpec).
   124  				Create(&testCtx).GetObject()
   125  
   126  			By("Create a clusterVersion obj with replication workloadType.")
   127  			clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()).
   128  				AddComponentVersion(consensusComp).AddContainerShort(testapps.DefaultMySQLContainerName, testapps.ApeCloudMySQLImage).
   129  				Create(&testCtx).GetObject()
   130  		})
   131  
   132  		It("Test switchover OpsRequest", func() {
   133  			reqCtx := intctrlutil.RequestCtx{
   134  				Ctx:      testCtx.Ctx,
   135  				Recorder: k8sManager.GetEventRecorderFor("opsrequest-controller"),
   136  			}
   137  			By("Creating a cluster with consensus .")
   138  			clusterObj = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName,
   139  				clusterDefObj.Name, clusterVersionObj.Name).WithRandomName().
   140  				AddComponent(consensusComp, consensusComp).
   141  				SetReplicas(2).
   142  				Create(&testCtx).GetObject()
   143  
   144  			By("Creating a statefulSet.")
   145  			container := corev1.Container{
   146  				Name:            "mock-container-name",
   147  				Image:           testapps.ApeCloudMySQLImage,
   148  				ImagePullPolicy: corev1.PullIfNotPresent,
   149  			}
   150  			sts := testapps.NewStatefulSetFactory(testCtx.DefaultNamespace,
   151  				clusterObj.Name+"-"+consensusComp, clusterObj.Name, consensusComp).
   152  				AddFinalizers([]string{constant.DBClusterFinalizerName}).
   153  				AddContainer(container).
   154  				AddAppInstanceLabel(clusterObj.Name).
   155  				AddAppComponentLabel(consensusComp).
   156  				AddAppManagedByLabel().
   157  				SetReplicas(2).
   158  				Create(&testCtx).GetObject()
   159  
   160  			By("Creating Pods of replication workloadType.")
   161  			var (
   162  				leaderPod   *corev1.Pod
   163  				followerPod *corev1.Pod
   164  			)
   165  			for i := int32(0); i < *sts.Spec.Replicas; i++ {
   166  				pod := testapps.NewPodFactory(testCtx.DefaultNamespace, fmt.Sprintf("%s-%d", sts.Name, i)).
   167  					AddContainer(container).
   168  					AddLabelsInMap(sts.Labels).
   169  					AddRoleLabel(defaultRole(i)).
   170  					Create(&testCtx).GetObject()
   171  				if pod.Labels[constant.RoleLabelKey] == constant.Leader {
   172  					leaderPod = pod
   173  				} else {
   174  					followerPod = pod
   175  				}
   176  			}
   177  
   178  			opsRes := &OpsResource{
   179  				Cluster:  clusterObj,
   180  				Recorder: k8sManager.GetEventRecorderFor("opsrequest-controller"),
   181  			}
   182  			By("mock cluster is Running and the status operations")
   183  			Expect(testapps.ChangeObjStatus(&testCtx, clusterObj, func() {
   184  				clusterObj.Status.Phase = appsv1alpha1.RunningClusterPhase
   185  				clusterObj.Status.Components = map[string]appsv1alpha1.ClusterComponentStatus{
   186  					consensusComp: {
   187  						Phase: appsv1alpha1.RunningClusterCompPhase,
   188  					},
   189  				}
   190  			})).Should(Succeed())
   191  			opsRes.Cluster = clusterObj
   192  
   193  			By("create switchover opsRequest")
   194  			ops := testapps.NewOpsRequestObj("ops-switchover-"+randomStr, testCtx.DefaultNamespace,
   195  				clusterObj.Name, appsv1alpha1.SwitchoverType)
   196  			ops.Spec.SwitchoverList = []appsv1alpha1.Switchover{
   197  				{
   198  					ComponentOps: appsv1alpha1.ComponentOps{ComponentName: consensusComp},
   199  					InstanceName: fmt.Sprintf("%s-%s-%d", clusterObj.Name, consensusComp, 1),
   200  				},
   201  			}
   202  			opsRes.OpsRequest = testapps.CreateOpsRequest(ctx, testCtx, ops)
   203  
   204  			By("mock switchover OpsRequest phase is Creating")
   205  			_, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes)
   206  			Expect(err).ShouldNot(HaveOccurred())
   207  			Eventually(testapps.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(appsv1alpha1.OpsCreatingPhase))
   208  
   209  			// do switchover action
   210  			By("do switchover action")
   211  			_, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes)
   212  			Expect(err).ShouldNot(HaveOccurred())
   213  
   214  			By("do reconcile switchoverAction failed because switchover job status failed")
   215  			_, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes)
   216  			Expect(err).Should(HaveOccurred())
   217  			Expect(err.Error()).Should(ContainSubstring("job check conditions status failed"))
   218  
   219  			By("mock job status to success.")
   220  			jobName := fmt.Sprintf("%s-%s-%s-%d", constant.KBSwitchoverJobNamePrefix, opsRes.Cluster.Name, consensusComp, opsRes.Cluster.Generation)
   221  			key := types.NamespacedName{
   222  				Name:      jobName,
   223  				Namespace: clusterObj.Namespace,
   224  			}
   225  			patchK8sJobStatus(batchv1.JobComplete, key)
   226  
   227  			By("do reconcile switchoverAction failed because pod role label is not consistency")
   228  			_, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes)
   229  			Expect(err).Should(HaveOccurred())
   230  			Expect(err.Error()).Should(ContainSubstring("requeue to waiting for pod role label consistency"))
   231  
   232  			By("mock pod role label changed.")
   233  			Expect(testapps.ChangeObj(&testCtx, leaderPod, func(pod *corev1.Pod) {
   234  				pod.Labels[constant.RoleLabelKey] = constant.Follower
   235  			})).Should(Succeed())
   236  			Expect(testapps.ChangeObj(&testCtx, followerPod, func(pod *corev1.Pod) {
   237  				pod.Labels[constant.RoleLabelKey] = constant.Leader
   238  			})).Should(Succeed())
   239  			_, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes)
   240  			Expect(err).ShouldNot(HaveOccurred())
   241  		})
   242  	})
   243  })