github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure_pipeline_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  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  	"sigs.k8s.io/controller-runtime/pkg/log"
    29  
    30  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    31  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    32  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    33  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    34  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    35  	testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    36  )
    37  
    38  var _ = Describe("Reconfigure util test", func() {
    39  
    40  	var (
    41  		k8sMockClient *testutil.K8sClientMockHelper
    42  		tpl           appsv1alpha1.ComponentConfigSpec
    43  		tpl2          appsv1alpha1.ComponentConfigSpec
    44  		updatedCfg    appsv1alpha1.ConfigurationItem
    45  	)
    46  
    47  	const (
    48  		clusterName   = "mysql-test"
    49  		componentName = "mysql"
    50  	)
    51  
    52  	mockCfgTplObj := func(tpl appsv1alpha1.ComponentConfigSpec) (*corev1.ConfigMap, *appsv1alpha1.ConfigConstraint, *appsv1alpha1.Configuration) {
    53  		By("By assure an cm obj")
    54  
    55  		cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml",
    56  			&corev1.ConfigMap{},
    57  			testapps.WithNamespacedName(core.GetComponentCfgName(clusterName, componentName, tpl.Name), testCtx.DefaultNamespace))
    58  		cfgTpl := testapps.NewCustomizedObj("operations_config/config-constraint.yaml",
    59  			&appsv1alpha1.ConfigConstraint{},
    60  			testapps.WithNamespacedName(tpl.ConfigConstraintRef, tpl.Namespace))
    61  
    62  		configuration := builder.NewConfigurationBuilder(testCtx.DefaultNamespace,
    63  			core.GenerateComponentConfigurationName(clusterName, componentName)).
    64  			ClusterRef(clusterName).
    65  			Component(componentName).
    66  			AddConfigurationItem(tpl).
    67  			AddConfigurationItem(tpl2)
    68  		return cfgCM, cfgTpl, configuration.GetObject()
    69  	}
    70  
    71  	BeforeEach(func() {
    72  		k8sMockClient = testutil.NewK8sMockClient()
    73  		tpl = appsv1alpha1.ComponentConfigSpec{
    74  			ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{
    75  				Name:        "for_test",
    76  				TemplateRef: "cm_obj",
    77  			},
    78  			ConfigConstraintRef: "cfg_constraint_obj",
    79  			Keys:                []string{"my.cnf"},
    80  		}
    81  		tpl2 = appsv1alpha1.ComponentConfigSpec{
    82  			ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{
    83  				Name:        "for_test2",
    84  				TemplateRef: "cm_obj",
    85  			},
    86  		}
    87  		updatedCfg = appsv1alpha1.ConfigurationItem{
    88  			Name: tpl.Name,
    89  			Keys: []appsv1alpha1.ParameterConfig{{
    90  				Key: "my.cnf",
    91  				Parameters: []appsv1alpha1.ParameterPair{
    92  					{
    93  						Key:   "x1",
    94  						Value: func() *string { v := "y1"; return &v }(),
    95  					},
    96  					{
    97  						Key:   "x2",
    98  						Value: func() *string { v := "y2"; return &v }(),
    99  					},
   100  					{
   101  						Key:   "server-id",
   102  						Value: nil, // delete parameter
   103  					}},
   104  			}},
   105  		}
   106  	})
   107  
   108  	AfterEach(func() {
   109  		// Add any teardown steps that needs to be executed after each test
   110  		k8sMockClient.Finish()
   111  	})
   112  
   113  	Context("updateConfigConfigmapResource test", func() {
   114  		It("Should success without error", func() {
   115  			diffCfg := `{"mysqld":{"x1":"y1","x2":"y2"}}`
   116  
   117  			cmObj, tplObj, configObj := mockCfgTplObj(tpl)
   118  			tpl2Key := client.ObjectKey{
   119  				Namespace: cmObj.Namespace,
   120  				Name:      core.GetComponentCfgName(clusterName, componentName, tpl2.Name),
   121  			}
   122  			k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSequenceResult(map[client.ObjectKey][]testutil.MockGetReturned{
   123  				// for cm
   124  				client.ObjectKeyFromObject(cmObj): {{
   125  					Object: nil,
   126  					Err:    core.MakeError("failed to get cm object"),
   127  				}, {
   128  					Object: cmObj,
   129  					Err:    nil,
   130  				}},
   131  				tpl2Key: {{
   132  					Object: cmObj,
   133  					Err:    nil,
   134  				}},
   135  				// for tpl
   136  				client.ObjectKeyFromObject(tplObj): {{
   137  					Object: nil,
   138  					Err:    core.MakeError("failed to get tpl object"),
   139  				}, {
   140  					Object: tplObj,
   141  					Err:    nil,
   142  				}},
   143  				// for configuration
   144  				client.ObjectKeyFromObject(configObj): {{
   145  					Object: nil,
   146  					// Err:    core.MakeError("failed to get configuration object"),
   147  				}, {
   148  					Object: configObj,
   149  				}},
   150  			}), testutil.WithAnyTimes()))
   151  
   152  			k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error {
   153  				if cm, ok := obj.(*corev1.ConfigMap); ok {
   154  					cmObj.Data = cm.Data
   155  				}
   156  				return nil
   157  			}, testutil.WithAnyTimes()))
   158  
   159  			opsRes := &OpsResource{
   160  				Recorder: k8sManager.GetEventRecorderFor("Reconfiguring"),
   161  				OpsRequest: testapps.NewOpsRequestObj("reconfigure-ops-"+testCtx.GetRandomStr(), testCtx.DefaultNamespace,
   162  					clusterName, appsv1alpha1.ReconfiguringType),
   163  			}
   164  			reqCtx := intctrlutil.RequestCtx{
   165  				Ctx:      testCtx.Ctx,
   166  				Log:      log.FromContext(ctx).WithName("Reconfiguring"),
   167  				Recorder: opsRes.Recorder,
   168  			}
   169  
   170  			By("Configuration object failed.")
   171  			// mock failed
   172  			// r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate)
   173  			r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName)
   174  			Expect(r.err).ShouldNot(Succeed())
   175  			Expect(r.err.Error()).Should(ContainSubstring("failed to found configuration of component"))
   176  
   177  			By("CM object failed.")
   178  			// mock failed
   179  			// r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate)
   180  			r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName)
   181  			Expect(r.err).ShouldNot(Succeed())
   182  			Expect(r.err.Error()).Should(ContainSubstring("failed to get cm object"))
   183  
   184  			By("TPL object failed.")
   185  			// mock failed
   186  			r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName)
   187  			Expect(r.err).ShouldNot(Succeed())
   188  			Expect(r.err.Error()).Should(ContainSubstring("failed to get tpl object"))
   189  
   190  			By("update validate failed.")
   191  			r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, appsv1alpha1.ConfigurationItem{
   192  				Name: tpl.Name,
   193  				Keys: []appsv1alpha1.ParameterConfig{{
   194  					Key: "my.cnf",
   195  					Parameters: []appsv1alpha1.ParameterPair{
   196  						{
   197  							Key:   "innodb_autoinc_lock_mode",
   198  							Value: func() *string { v := "100"; return &v }(), // invalid value
   199  						},
   200  					},
   201  				}},
   202  			}, clusterName, componentName)
   203  			Expect(r.failed).Should(BeTrue())
   204  			Expect(r.err).ShouldNot(Succeed())
   205  			Expect(r.err.Error()).Should(ContainSubstring(`
   206  mysqld.innodb_autoinc_lock_mode: conflicting values 0 and 100:
   207      9:36
   208      12:18
   209  mysqld.innodb_autoinc_lock_mode: conflicting values 1 and 100:
   210      9:40
   211      12:18
   212  mysqld.innodb_autoinc_lock_mode: conflicting values 2 and 100:
   213      9:44
   214      12:18`))
   215  
   216  			By("normal params update")
   217  			{
   218  				// r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate)
   219  				r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName)
   220  				Expect(r.err).Should(Succeed())
   221  				Expect(r.noFormatFilesUpdated).Should(BeFalse())
   222  				Expect(r.configPatch).ShouldNot(BeNil())
   223  				diff := r.configPatch
   224  				Expect(diff.IsModify).Should(BeTrue())
   225  				Expect(diff.UpdateConfig["my.cnf"]).Should(BeEquivalentTo(diffCfg))
   226  			}
   227  
   228  			// normal params update
   229  			By("normal file update with configSpec keys")
   230  			{
   231  				updatedFiles := appsv1alpha1.ConfigurationItem{
   232  					Name: tpl2.Name,
   233  					Keys: []appsv1alpha1.ParameterConfig{{
   234  						Key: "my.cnf",
   235  						FileContent: `
   236  [mysqld]
   237  x1=y1
   238  z2=y2
   239  `,
   240  					}},
   241  				}
   242  
   243  				_ = updatedFiles
   244  				// r := updateConfigConfigmapResource(updatedFiles, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate)
   245  				r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName)
   246  				Expect(r.err).Should(Succeed())
   247  			}
   248  
   249  			// not params update, but file update
   250  			By("normal file update with configSpec keys")
   251  			{
   252  				oldConfig := cmObj.Data
   253  				newMyCfg := oldConfig["my.cnf"]
   254  				newMyCfg += `
   255  # for test
   256  # not valid parameter
   257  `
   258  				updatedFiles := appsv1alpha1.ConfigurationItem{
   259  					Name: tpl2.Name,
   260  					Keys: []appsv1alpha1.ParameterConfig{{
   261  						Key:         "my.cnf",
   262  						FileContent: newMyCfg,
   263  					}},
   264  				}
   265  
   266  				_ = updatedFiles
   267  				// r := updateConfigConfigmapResource(updatedFiles, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate)
   268  				r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName)
   269  				Expect(r.err).Should(Succeed())
   270  				Expect(r.configPatch).Should(BeNil())
   271  				Expect(r.noFormatFilesUpdated).Should(BeTrue())
   272  			}
   273  
   274  			By("normal file update without configSpec keys")
   275  			{
   276  				updatedFiles := appsv1alpha1.ConfigurationItem{
   277  					Name: tpl.Name,
   278  					Keys: []appsv1alpha1.ParameterConfig{{
   279  						Key:         "config2.txt",
   280  						FileContent: `# for test`,
   281  					}},
   282  				}
   283  
   284  				_ = updatedFiles
   285  				r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName)
   286  				Expect(r.err).Should(Succeed())
   287  				diff := r.configPatch
   288  				Expect(diff.IsModify).Should(BeFalse())
   289  			}
   290  		})
   291  	})
   292  
   293  })