github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/test/integration/mysql_reconfigure_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package appstest
    18  
    19  import (
    20  	"fmt"
    21  
    22  	. "github.com/onsi/ginkgo/v2"
    23  	. "github.com/onsi/gomega"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    30  	"github.com/1aal/kubeblocks/pkg/common"
    31  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    32  	"github.com/1aal/kubeblocks/pkg/generics"
    33  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    34  	testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    35  	"github.com/1aal/kubeblocks/test/testutils"
    36  )
    37  
    38  var _ = Describe("MySQL Reconfigure function", func() {
    39  	const clusterDefName = "test-clusterdef"
    40  	const clusterVersionName = "test-clusterversion"
    41  	const clusterNamePrefix = "test-cluster"
    42  
    43  	const mysqlConfigTemplatePath = "resources/mysql-consensus-config-template.yaml"
    44  	const mysqlConfigConstraintPath = "resources/mysql-consensus-config-constraint.yaml"
    45  	const mysqlScriptsPath = "resources/mysql-consensus-scripts.yaml"
    46  
    47  	const leader = "leader"
    48  	const follower = "follower"
    49  
    50  	// ctx := context.Background()
    51  
    52  	// Cleanups
    53  
    54  	cleanEnv := func() {
    55  		// must wait until resources deleted and no longer exist before the testcases start,
    56  		// otherwise if later it needs to create some new resource objects with the same name,
    57  		// in race conditions, it will find the existence of old objects, resulting failure to
    58  		// create the new objects.
    59  		By("clean resources")
    60  
    61  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
    62  		testapps.ClearClusterResources(&testCtx)
    63  
    64  		// delete rest configurations
    65  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    66  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    67  		// namespaced
    68  		testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml)
    69  		// non-namespaced
    70  		testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml)
    71  		testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml)
    72  	}
    73  
    74  	BeforeEach(cleanEnv)
    75  
    76  	AfterEach(cleanEnv)
    77  
    78  	// Testcases
    79  
    80  	var (
    81  		clusterDefObj     *appsv1alpha1.ClusterDefinition
    82  		clusterVersionObj *appsv1alpha1.ClusterVersion
    83  		clusterObj        *appsv1alpha1.Cluster
    84  		clusterKey        types.NamespacedName
    85  	)
    86  
    87  	newReconfigureRequest := func(clusterName string, componentName string,
    88  		configName string, configFile string,
    89  		parameterKey string, parameterValue *string) (opsRequest *appsv1alpha1.OpsRequest) {
    90  		randomOpsName := "reconfigure-ops-" + testCtx.GetRandomStr()
    91  		opsRequest = testapps.NewOpsRequestObj(randomOpsName, testCtx.DefaultNamespace,
    92  			clusterName, appsv1alpha1.ReconfiguringType)
    93  		opsRequest.Spec.Reconfigure = &appsv1alpha1.Reconfigure{
    94  			Configurations: []appsv1alpha1.ConfigurationItem{{
    95  				Name: configName,
    96  				Keys: []appsv1alpha1.ParameterConfig{{
    97  					Key: configFile,
    98  					Parameters: []appsv1alpha1.ParameterPair{
    99  						{
   100  							Key:   parameterKey,
   101  							Value: parameterValue,
   102  						},
   103  					},
   104  				}},
   105  			}},
   106  			ComponentOps: appsv1alpha1.ComponentOps{ComponentName: componentName},
   107  		}
   108  		return opsRequest
   109  	}
   110  
   111  	getClusterConfig := func(clusterObj *appsv1alpha1.Cluster) (
   112  		componentName string, tpl *appsv1alpha1.ComponentConfigSpec, cmObj *corev1.ConfigMap) {
   113  
   114  		By("Get configuration information from cluster")
   115  		componentName = clusterObj.Spec.ComponentSpecs[0].ComponentDefRef
   116  		tpls, err := core.GetConfigTemplatesFromComponent(clusterObj.Spec.ComponentSpecs,
   117  			clusterDefObj.Spec.ComponentDefs, clusterVersionObj.Spec.ComponentVersions, componentName)
   118  		Expect(err).Should(BeNil())
   119  		Expect(len(tpls) > 0).Should(BeTrue())
   120  
   121  		By("Should have at least one valid config")
   122  		validTpls := make([]appsv1alpha1.ComponentConfigSpec, 0, len(tpls))
   123  		for _, tpl := range tpls {
   124  			if len(tpl.ConfigConstraintRef) > 0 && len(tpl.TemplateRef) > 0 {
   125  				validTpls = append(validTpls, tpl)
   126  			}
   127  		}
   128  		Expect(len(validTpls) > 0).Should(BeTrue())
   129  
   130  		cmObj = &corev1.ConfigMap{}
   131  		cmName := core.GetComponentCfgName(clusterObj.Name, componentName, tpls[0].Name)
   132  		err = testutils.GetResourceObjectFromGVR(testutils.ConfigmapGVR(), client.ObjectKey{
   133  			Name:      cmName,
   134  			Namespace: testCtx.DefaultNamespace,
   135  		}, dynamicClient, cmObj)
   136  		Expect(err).Should(BeNil())
   137  
   138  		return componentName, &validTpls[0], cmObj
   139  	}
   140  
   141  	testReconfigureThreeReplicas := func() {
   142  		By("Create a cluster obj")
   143  		clusterName := testapps.GetRandomizedKey("", clusterNamePrefix).Name
   144  		clusterDefObj, clusterVersionObj, clusterObj = CreateSimpleConsensusMySQLClusterWithConfig(
   145  			testCtx, clusterDefName, clusterVersionName, clusterName, mysqlConfigTemplatePath, mysqlConfigConstraintPath, mysqlScriptsPath)
   146  		clusterKey = client.ObjectKeyFromObject(clusterObj)
   147  		fmt.Printf("ClusterDefinition:%s ClusterVersion:%s Cluster:%s \n", clusterDefObj.Name, clusterVersionObj.Name, clusterObj.Name)
   148  
   149  		By("Waiting the cluster is created")
   150  		Eventually(testapps.GetClusterPhase(&testCtx, clusterKey)).Should(Equal(appsv1alpha1.RunningClusterPhase))
   151  
   152  		By("Checking pods' role label")
   153  		sts := testk8s.ListAndCheckStatefulSet(&testCtx, clusterKey).Items[0]
   154  		pods, err := common.GetPodListByStatefulSet(testCtx.Ctx, k8sClient, &sts)
   155  		Expect(err).To(Succeed())
   156  		Expect(len(pods)).Should(Equal(3))
   157  
   158  		// get role->count map
   159  		By("Checking the count of leader and followers, learners are ignored")
   160  		roleCountMap := GetConsensusRoleCountMap(testCtx, k8sClient, clusterObj)
   161  		Expect(roleCountMap[leader]).Should(Equal(1))
   162  		Expect(roleCountMap[follower]).Should(Equal(2))
   163  
   164  		By("Checking the cluster config")
   165  		componentName, tpl, cmObj := getClusterConfig(clusterObj)
   166  		configFile := ""
   167  		// get first config file
   168  		for k := range cmObj.Data {
   169  			configFile = k
   170  			break
   171  		}
   172  
   173  		By("Issue a restart load reconfigure OpsRequest - max_connections")
   174  		pKey := "max_connections"
   175  		pValue := "2000"
   176  		reconfigureOpsRequest := newReconfigureRequest(clusterObj.Name, componentName,
   177  			tpl.Name, configFile, pKey, &pValue)
   178  		Expect(testCtx.CreateObj(testCtx.Ctx, reconfigureOpsRequest)).Should(Succeed())
   179  
   180  		By("Checking ReconfigureOpsRequest is running")
   181  		opsKey := types.NamespacedName{Name: reconfigureOpsRequest.Name, Namespace: testCtx.DefaultNamespace}
   182  		Eventually(testapps.GetOpsRequestPhase(&testCtx, opsKey)).Should(Equal(appsv1alpha1.OpsRunningPhase))
   183  
   184  		By("Checking Cluster and changed component phase is Reconfiguring")
   185  		Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1alpha1.Cluster) {
   186  			g.Expect(cluster.Status.Phase).To(Equal(appsv1alpha1.UpdatingClusterPhase))                               // appsv1alpha1.ReconfiguringPhase
   187  			g.Expect(cluster.Status.Components[componentName].Phase).To(Equal(appsv1alpha1.UpdatingClusterCompPhase)) // appsv1alpha1.ReconfiguringPhase
   188  			// TODO: add status condition check
   189  		})).Should(Succeed())
   190  
   191  		By("Issue another reconfigure OpsRequest that will fail - innodb_read_io_threads")
   192  		pKey = "innodb_read_io_threads"
   193  		pValue = "2"
   194  		reconfigureOpsRequest = newReconfigureRequest(clusterObj.Name, componentName,
   195  			tpl.Name, configFile, pKey, &pValue)
   196  		Expect(testCtx.CreateObj(testCtx.Ctx, reconfigureOpsRequest)).ShouldNot(Succeed())
   197  	}
   198  
   199  	// Scenarios
   200  	Context("with MySQL defined as Consensus type and three replicas", func() {
   201  		It("should update config with opsrequest in restart mode or dynamic loading mode", func() {
   202  			testReconfigureThreeReplicas()
   203  		})
   204  	})
   205  })