github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/configuration/config_manager/builder_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 configmanager 21 22 import ( 23 "context" 24 "reflect" 25 "testing" 26 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 30 "github.com/stretchr/testify/assert" 31 corev1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/util/yaml" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s" 38 ) 39 40 var _ = Describe("Config Builder Test", func() { 41 42 const ( 43 scriptsName = "script_cm" 44 scriptsNS = "default" 45 46 lazyRenderedTemplateName = "lazy-rendered-template" 47 ) 48 49 var mockK8sCli *testutil.K8sClientMockHelper 50 51 BeforeEach(func() { 52 // Add any setup steps that needs to be executed before each test 53 mockK8sCli = testutil.NewK8sMockClient() 54 }) 55 56 AfterEach(func() { 57 DeferCleanup(mockK8sCli.Finish) 58 }) 59 60 syncFn := func(sync bool) *bool { r := sync; return &r } 61 62 newVolumeMounts := func() []corev1.VolumeMount { 63 return []corev1.VolumeMount{ 64 { 65 MountPath: "/postgresql/conf", 66 Name: "pg_config", 67 }} 68 } 69 newVolumeMounts2 := func() []corev1.VolumeMount { 70 return []corev1.VolumeMount{ 71 { 72 MountPath: "/postgresql/conf", 73 Name: "pg_config", 74 }, 75 { 76 MountPath: "/postgresql/conf2", 77 Name: "pg_config", 78 }} 79 } 80 newReloadOptions := func(t appsv1alpha1.CfgReloadType, sync *bool) *appsv1alpha1.ReloadOptions { 81 signalHandle := &appsv1alpha1.UnixSignalTrigger{ 82 ProcessName: "postgres", 83 Signal: appsv1alpha1.SIGHUP, 84 } 85 shellHandle := &appsv1alpha1.ShellTrigger{ 86 Command: []string{"pwd"}, 87 } 88 scriptHandle := &appsv1alpha1.TPLScriptTrigger{ 89 Sync: sync, 90 ScriptConfig: appsv1alpha1.ScriptConfig{ 91 ScriptConfigMapRef: "reload-script", 92 Namespace: scriptsNS, 93 }, 94 } 95 96 switch t { 97 default: 98 return nil 99 case appsv1alpha1.UnixSignalType: 100 return &appsv1alpha1.ReloadOptions{ 101 UnixSignalTrigger: signalHandle} 102 case appsv1alpha1.ShellType: 103 return &appsv1alpha1.ReloadOptions{ 104 ShellTrigger: shellHandle} 105 case appsv1alpha1.TPLScriptType: 106 return &appsv1alpha1.ReloadOptions{ 107 TPLScriptTrigger: scriptHandle} 108 } 109 } 110 newConfigSpecMeta := func() []ConfigSpecMeta { 111 return []ConfigSpecMeta{ 112 { 113 ConfigSpecInfo: ConfigSpecInfo{ 114 ConfigSpec: appsv1alpha1.ComponentConfigSpec{ 115 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 116 Name: "pg_config", 117 VolumeName: "pg_config", 118 }, 119 }, 120 }, 121 }, 122 } 123 } 124 125 newCMBuildParams := func(hasScripts bool) *CfgManagerBuildParams { 126 param := &CfgManagerBuildParams{ 127 Cluster: &appsv1alpha1.Cluster{ 128 ObjectMeta: metav1.ObjectMeta{ 129 Name: "abcd", 130 Namespace: "default", 131 }, 132 }, 133 ComponentName: "test", 134 Volumes: newVolumeMounts(), 135 ConfigSpecsBuildParams: newConfigSpecMeta(), 136 ConfigLazyRenderedVolumes: make(map[string]corev1.VolumeMount), 137 DownwardAPIVolumes: make([]corev1.VolumeMount, 0), 138 } 139 if hasScripts { 140 param.ConfigSpecsBuildParams[0].ScriptConfig = []appsv1alpha1.ScriptConfig{ 141 { 142 Namespace: scriptsNS, 143 ScriptConfigMapRef: scriptsName, 144 }, 145 } 146 } 147 return param 148 } 149 150 mockTplScriptCM := func() { 151 mockK8sCli.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ 152 &corev1.ConfigMap{ 153 ObjectMeta: metav1.ObjectMeta{ 154 Name: "reload-script", 155 Namespace: scriptsNS, 156 }, 157 Data: map[string]string{ 158 "reload.yaml": ` 159 scripts: reload.tpl 160 fileRegex: my.cnf 161 formatterConfig: 162 format: ini 163 `, 164 }}, 165 &corev1.ConfigMap{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Name: lazyRenderedTemplateName, 168 Namespace: scriptsNS, 169 }, 170 Data: map[string]string{ 171 "my.cnf": "", 172 }}, 173 }), testutil.WithAnyTimes())) 174 mockK8sCli.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes())) 175 } 176 177 newDownwardAPIVolumes := func() []appsv1alpha1.DownwardAPIOption { 178 return []appsv1alpha1.DownwardAPIOption{ 179 { 180 Name: "downward-api", 181 MountPoint: "/etc/podinfo", 182 Command: []string{"/bin/true"}, 183 Items: []corev1.DownwardAPIVolumeFile{ 184 { 185 Path: "labels/role", 186 FieldRef: &corev1.ObjectFieldSelector{ 187 FieldPath: `metadata.labels['kubeblocks.io/role']`, 188 }, 189 }, 190 }, 191 }, 192 } 193 } 194 195 Context("TestBuildConfigManagerContainer", func() { 196 It("builds unixSignal reloader correctly", func() { 197 param := newCMBuildParams(false) 198 mockTplScriptCM() 199 reloadOptions := newReloadOptions(appsv1alpha1.UnixSignalType, nil) 200 for i := range param.ConfigSpecsBuildParams { 201 buildParam := ¶m.ConfigSpecsBuildParams[i] 202 buildParam.ReloadOptions = reloadOptions 203 buildParam.ReloadType = appsv1alpha1.UnixSignalType 204 } 205 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), ctx, param, newVolumeMounts2())).Should(Succeed()) 206 for _, arg := range []string{`--volume-dir`, `/postgresql/conf`, `--volume-dir`, `/postgresql/conf2`} { 207 Expect(param.Args).Should(ContainElement(arg)) 208 } 209 }) 210 211 It("builds shellTrigger reloader correctly", func() { 212 mockK8sCli.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ 213 &corev1.ConfigMap{ 214 ObjectMeta: metav1.ObjectMeta{ 215 Name: scriptsName, 216 Namespace: scriptsNS, 217 }, 218 }, 219 }), testutil.WithTimes(3))) 220 mockK8sCli.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithTimes(2))) 221 222 param := newCMBuildParams(true) 223 reloadOptions := newReloadOptions(appsv1alpha1.ShellType, nil) 224 for i := range param.ConfigSpecsBuildParams { 225 buildParam := ¶m.ConfigSpecsBuildParams[i] 226 buildParam.ReloadOptions = reloadOptions 227 buildParam.ReloadType = appsv1alpha1.ShellType 228 } 229 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) 230 for _, arg := range []string{`--volume-dir`, `/postgresql/conf`} { 231 Expect(param.Args).Should(ContainElement(arg)) 232 } 233 }) 234 235 It("builds tplScriptsTrigger reloader correctly", func() { 236 mockTplScriptCM() 237 param := newCMBuildParams(false) 238 reloadOptions := newReloadOptions(appsv1alpha1.TPLScriptType, syncFn(true)) 239 for i := range param.ConfigSpecsBuildParams { 240 buildParam := ¶m.ConfigSpecsBuildParams[i] 241 buildParam.ReloadOptions = reloadOptions 242 buildParam.ReloadType = appsv1alpha1.TPLScriptType 243 } 244 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) 245 for _, arg := range []string{`--operator-update-enable`} { 246 Expect(param.Args).Should(ContainElement(arg)) 247 } 248 }) 249 250 It("builds tplScriptsTrigger reloader correctly with sync", func() { 251 mockTplScriptCM() 252 param := newCMBuildParams(false) 253 reloadOptions := newReloadOptions(appsv1alpha1.TPLScriptType, syncFn(false)) 254 for i := range param.ConfigSpecsBuildParams { 255 buildParam := ¶m.ConfigSpecsBuildParams[i] 256 buildParam.ReloadOptions = reloadOptions 257 buildParam.ReloadType = appsv1alpha1.TPLScriptType 258 } 259 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) 260 for _, arg := range []string{`--volume-dir`, `/postgresql/conf`} { 261 Expect(param.Args).Should(ContainElement(arg)) 262 } 263 }) 264 265 It("builds secondary render correctly", func() { 266 mockTplScriptCM() 267 param := newCMBuildParams(false) 268 reloadOptions := newReloadOptions(appsv1alpha1.TPLScriptType, syncFn(false)) 269 for i := range param.ConfigSpecsBuildParams { 270 buildParam := ¶m.ConfigSpecsBuildParams[i] 271 buildParam.ReloadOptions = reloadOptions 272 buildParam.ReloadType = appsv1alpha1.TPLScriptType 273 buildParam.ConfigSpec.LegacyRenderedConfigSpec = &appsv1alpha1.LegacyRenderedTemplateSpec{ 274 ConfigTemplateExtension: appsv1alpha1.ConfigTemplateExtension{ 275 Namespace: scriptsNS, 276 TemplateRef: lazyRenderedTemplateName, 277 }, 278 } 279 } 280 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) 281 for _, buildParam := range param.ConfigSpecsBuildParams { 282 Expect(FindVolumeMount(param.Volumes, GetConfigVolumeName(buildParam.ConfigSpec))).ShouldNot(BeNil()) 283 } 284 }) 285 286 It("builds downwardAPI correctly", func() { 287 mockTplScriptCM() 288 param := newCMBuildParams(false) 289 buildParam := ¶m.ConfigSpecsBuildParams[0] 290 buildParam.DownwardAPIOptions = newDownwardAPIVolumes() 291 buildParam.ReloadOptions = newReloadOptions(appsv1alpha1.TPLScriptType, syncFn(true)) 292 Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) 293 Expect(FindVolumeMount(param.DownwardAPIVolumes, buildParam.DownwardAPIOptions[0].Name)).ShouldNot(BeNil()) 294 }) 295 }) 296 297 }) 298 299 func TestCheckAndUpdateReloadYaml(t *testing.T) { 300 customEqual := func(l, r map[string]string) bool { 301 if len(l) != len(r) { 302 return false 303 } 304 var err error 305 for k, v := range l { 306 var lv any 307 var rv any 308 err = yaml.Unmarshal([]byte(v), &lv) 309 assert.Nil(t, err) 310 err = yaml.Unmarshal([]byte(r[k]), &rv) 311 assert.Nil(t, err) 312 if !reflect.DeepEqual(lv, rv) { 313 return false 314 } 315 } 316 return true 317 } 318 319 type args struct { 320 data map[string]string 321 reloadConfig string 322 formatterConfig *appsv1alpha1.FormatterConfig 323 } 324 tests := []struct { 325 name string 326 args args 327 want map[string]string 328 wantErr bool 329 }{{ 330 name: "testCheckAndUpdateReloadYaml", 331 args: args{ 332 data: map[string]string{"reload.yaml": ` 333 fileRegex: my.cnf 334 scripts: reload.tpl 335 `}, 336 reloadConfig: "reload.yaml", 337 formatterConfig: &appsv1alpha1.FormatterConfig{ 338 Format: appsv1alpha1.Ini, 339 }, 340 }, 341 wantErr: false, 342 want: map[string]string{"reload.yaml": ` 343 scripts: reload.tpl 344 fileRegex: my.cnf 345 formatterConfig: 346 format: ini 347 `, 348 }, 349 }, { 350 name: "testCheckAndUpdateReloadYaml", 351 args: args{ 352 data: map[string]string{}, 353 reloadConfig: "reload.yaml", 354 formatterConfig: &appsv1alpha1.FormatterConfig{Format: appsv1alpha1.Ini}, 355 }, 356 wantErr: true, 357 want: map[string]string{}, 358 }} 359 for _, tt := range tests { 360 t.Run(tt.name, func(t *testing.T) { 361 got, err := checkAndUpdateReloadYaml(tt.args.data, tt.args.reloadConfig, *tt.args.formatterConfig) 362 if (err != nil) != tt.wantErr { 363 t.Errorf("checkAndUpdateReloadYaml() error = %v, wantErr %v", err, tt.wantErr) 364 return 365 } 366 if !customEqual(got, tt.want) { 367 t.Errorf("checkAndUpdateReloadYaml() got = %v, want %v", got, tt.want) 368 } 369 }) 370 } 371 }