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