github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/config_template_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 configuration 21 22 import ( 23 "strconv" 24 "strings" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 33 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 34 "github.com/1aal/kubeblocks/pkg/constant" 35 ctrlcomp "github.com/1aal/kubeblocks/pkg/controller/component" 36 viper "github.com/1aal/kubeblocks/pkg/viperx" 37 ) 38 39 type insClassType struct { 40 memSize int64 41 cpu int64 42 // recommended buffer size 43 bufferSize string 44 45 maxBufferSize int 46 } 47 48 var _ = Describe("tpl template", func() { 49 50 var ( 51 podSpec *corev1.PodSpec 52 cfgTemplate []appsv1alpha1.ComponentConfigSpec 53 component *ctrlcomp.SynthesizedComponent 54 ) 55 56 const ( 57 mysqlDataVolume = "/data/mysql" 58 mysqlCfgName = "my.cfg" 59 mysqlCfgTmpContext = ` 60 #test 61 cluster_name = {{ $.cluster.metadata.name }} 62 cluster_namespace = {{ $.cluster.metadata.namespace }} 63 component_name = {{ $.component.name }} 64 component_replica = {{ $.component.replicas }} 65 containers = {{ (index $.podSpec.containers 0 ).name }} 66 {{- $buffer_pool_size_tmp := 2147483648 -}} 67 {{- if $.componentResource -}} 68 {{- $buffer_pool_size_tmp = $.componentResource.memorySize }} 69 {{- end }} 70 innodb_buffer_pool_size = {{ $buffer_pool_size_tmp | int64 }} 71 {{- $thread_stack := 262144 }} 72 {{- $binlog_cache_size := 32768 }} 73 {{- $single_thread_memory := add $thread_stack $binlog_cache_size }} 74 single_thread_memory = {{ $single_thread_memory }} 75 ` 76 mysqlCfgRenderedContext = ` 77 #test 78 cluster_name = my_test 79 cluster_namespace = default 80 component_name = replicasets 81 component_replica = 5 82 containers = mytest 83 innodb_buffer_pool_size = 8589934592 84 single_thread_memory = 294912 85 ` 86 ) 87 88 BeforeEach(func() { 89 // Add any steup steps that needs to be executed before each test 90 podSpec = &corev1.PodSpec{ 91 Containers: []corev1.Container{ 92 { 93 Name: "mytest", 94 VolumeMounts: []corev1.VolumeMount{ 95 { 96 Name: "data", 97 MountPath: mysqlDataVolume, 98 }, 99 { 100 Name: "log", 101 MountPath: "/log/mysql", 102 }, 103 }, 104 Env: []corev1.EnvVar{ 105 { 106 Name: "t1", 107 Value: "value1", 108 }, 109 { 110 Name: "t2", 111 Value: "value2", 112 }, 113 { 114 Name: "a", 115 Value: "b", 116 }, 117 }, 118 Args: []string{ 119 "logs", 120 "for_test", 121 }, 122 Ports: []corev1.ContainerPort{ 123 { 124 Name: "mysql", 125 ContainerPort: 3356, 126 Protocol: "TCP", 127 }, 128 { 129 Name: "paxos", 130 ContainerPort: 3356, 131 Protocol: "TCP", 132 }, 133 }, 134 Resources: corev1.ResourceRequirements{ 135 Limits: map[corev1.ResourceName]resource.Quantity{ 136 corev1.ResourceMemory: resource.MustParse("8Gi"), 137 corev1.ResourceCPU: resource.MustParse("4"), 138 }, 139 }, 140 }, 141 { 142 Name: "invalid_container", 143 }, 144 }, 145 Volumes: []corev1.Volume{ 146 { 147 Name: "config", 148 VolumeSource: corev1.VolumeSource{ 149 ConfigMap: &corev1.ConfigMapVolumeSource{ 150 LocalObjectReference: corev1.LocalObjectReference{ 151 Name: "cluster_name_for_test", 152 }, 153 }, 154 }, 155 }, 156 }, 157 } 158 component = &ctrlcomp.SynthesizedComponent{ 159 ClusterDefName: "mysql-three-node-definition", 160 Name: "replicasets", 161 CompDefName: "replicasets", 162 Replicas: 5, 163 VolumeClaimTemplates: []corev1.PersistentVolumeClaimTemplate{ 164 { 165 ObjectMeta: metav1.ObjectMeta{ 166 Name: "data", 167 }, 168 Spec: corev1.PersistentVolumeClaimSpec{ 169 Resources: corev1.ResourceRequirements{ 170 Requests: corev1.ResourceList{ 171 corev1.ResourceStorage: resource.MustParse("10Gi"), 172 }, 173 }, 174 }, 175 }, 176 }, 177 } 178 cfgTemplate = []appsv1alpha1.ComponentConfigSpec{{ 179 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 180 Name: "mysql-config-8.0.2", 181 TemplateRef: "mysql-config-8.0.2-tpl", 182 VolumeName: "config1", 183 }, 184 ConfigConstraintRef: "mysql-config-8.0.2-constraint", 185 }} 186 }) 187 188 // for test GetContainerWithVolumeMount 189 Context("ConfigTemplateBuilder sample test", func() { 190 It("test render", func() { 191 cfgBuilder := newTemplateBuilder( 192 "my_test", 193 "default", 194 &appsv1alpha1.Cluster{ 195 ObjectMeta: metav1.ObjectMeta{ 196 Name: "my_test", 197 Namespace: "default", 198 }, 199 }, 200 nil, nil, nil) 201 202 Expect(cfgBuilder.injectBuiltInObjectsAndFunctions(podSpec, cfgTemplate, component, nil)).Should(BeNil()) 203 204 cfgBuilder.componentValues.Resource = &ResourceDefinition{ 205 MemorySize: 8 * 1024 * 1024 * 1024, 206 CoreNum: 4, 207 } 208 209 cfgBuilder.setTemplateName("for_test") 210 rendered, err := cfgBuilder.render(map[string]string{ 211 mysqlCfgName: mysqlCfgTmpContext, 212 }) 213 214 Expect(err).Should(BeNil()) 215 Expect(rendered[mysqlCfgName]).Should(Equal(mysqlCfgRenderedContext)) 216 }) 217 It("test built-in function", func() { 218 cfgBuilder := newTemplateBuilder( 219 "my_test", 220 "default", 221 &appsv1alpha1.Cluster{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: "my_test", 224 Namespace: "default", 225 }, 226 }, 227 nil, nil, nil, 228 ) 229 230 viper.Set(constant.KubernetesClusterDomainEnv, "test-domain") 231 232 Expect(cfgBuilder.injectBuiltInObjectsAndFunctions(podSpec, cfgTemplate, component, nil)).Should(BeNil()) 233 234 rendered, err := cfgBuilder.render(map[string]string{ 235 "a": "{{ getVolumePathByName ( index $.podSpec.containers 0 ) \"log\" }}", 236 "b": "{{ getVolumePathByName ( index $.podSpec.containers 0 ) \"data\" }}", 237 "c": "{{ ( getPortByName ( index $.podSpec.containers 0 ) \"mysql\" ).containerPort }}", 238 "d": "{{ callBufferSizeByResource ( index $.podSpec.containers 0 ) }}", 239 "e": "{{ getArgByName ( index $.podSpec.containers 0 ) \"User\" }}", 240 "f": "{{ getVolumePathByName ( getContainerByName $.podSpec.containers \"mytest\") \"data\" }}", 241 "i": "{{ getEnvByName ( index $.podSpec.containers 0 ) \"a\" }}", 242 "j": "{{ ( getPVCByName $.podSpec.volumes \"config\" ).configMap.name }}", 243 "h": "{{ getContainerMemory ( index $.podSpec.containers 0 ) }}", 244 "invalid_volume": "{{ getVolumePathByName ( index $.podSpec.containers 0 ) \"invalid\" }}", 245 "invalid_port": "{{ getPortByName ( index $.podSpec.containers 0 ) \"invalid\" }}", 246 "invalid_container": "{{ getContainerByName $.podSpec.containers \"invalid\" }}", 247 "invalid_resource": "{{ callBufferSizeByResource ( index $.podSpec.containers 1 ) }}", 248 "invalid_env": "{{ getEnvByName ( index $.podSpec.containers 0 ) \"invalid\" }}", 249 "invalid_pvc": "{{ getPVCByName $.podSpec.volumes \"invalid\" }}", 250 "invalid_memory": "{{ getContainerMemory ( index $.podSpec.containers 1 ) }}", 251 "cluster_domain": "{{- $.clusterDomain }}", 252 "pvc_size": "{{- getComponentPVCSizeByName $.component \"data\" }}", 253 "pvc_size2": "{{- getPVCSize ( index $.component.volumeClaimTemplates 0 ) }}", 254 }) 255 256 Expect(err).Should(BeNil()) 257 // for test volumeMounts 258 Expect(rendered["a"]).Should(BeEquivalentTo("/log/mysql")) 259 // for test volumeMounts 260 Expect(rendered["b"]).Should(BeEquivalentTo(mysqlDataVolume)) 261 // for test port 262 Expect(rendered["c"]).Should(BeEquivalentTo("3356")) 263 // for test resource 264 Expect(rendered["d"]).Should(BeEquivalentTo("4096M")) 265 // for test args 266 Expect(rendered["e"]).Should(BeEquivalentTo("")) 267 // for test volumeMounts 268 Expect(rendered["f"]).Should(BeEquivalentTo(mysqlDataVolume)) 269 // for test env 270 Expect(rendered["i"]).Should(BeEquivalentTo("b")) 271 // for test volume 272 Expect(rendered["j"]).Should(BeEquivalentTo("cluster_name_for_test")) 273 Expect(rendered["h"]).Should(BeEquivalentTo(strconv.Itoa(8 * 1024 * 1024 * 1024))) 274 Expect(rendered["invalid_volume"]).Should(BeEquivalentTo("")) 275 Expect(rendered["invalid_port"]).Should(BeEquivalentTo("<no value>")) 276 Expect(rendered["invalid_container"]).Should(BeEquivalentTo("<no value>")) 277 Expect(rendered["invalid_env"]).Should(BeEquivalentTo("")) 278 Expect(rendered["invalid_pvc"]).Should(BeEquivalentTo("<no value>")) 279 Expect(rendered["invalid_resource"]).Should(BeEquivalentTo("")) 280 Expect(rendered["invalid_memory"]).Should(BeEquivalentTo("0")) 281 Expect(rendered["cluster_domain"]).Should(BeEquivalentTo("test-domain")) 282 Expect(rendered["pvc_size"]).Should(BeEquivalentTo("10737418240")) 283 Expect(rendered["pvc_size2"]).Should(BeEquivalentTo("10737418240")) 284 }) 285 286 It("test array null check", func() { 287 cfgBuilder := newTemplateBuilder( 288 "my_test", 289 "default", 290 &appsv1alpha1.Cluster{ 291 ObjectMeta: metav1.ObjectMeta{ 292 Name: "my_test", 293 Namespace: "default", 294 }, 295 }, 296 nil, nil, nil, 297 ) 298 299 Expect(cfgBuilder.injectBuiltInObjectsAndFunctions(podSpec, cfgTemplate, component, nil)).Should(BeNil()) 300 301 tests := []struct { 302 name string 303 tpl string 304 expected string 305 wantErr bool 306 }{{ 307 name: "null failed", 308 tpl: ` {{- if mustHas "logs" (index $.podSpec.containers 1 ).args -}} 309 true 310 {{- end -}} 311 `, 312 expected: "", 313 wantErr: true, 314 }, { 315 name: "null check", 316 tpl: ` 317 {{- if hasKey (index $.podSpec.containers 1 ) "args" }} 318 {{- if mustHas "logs" (index $.podSpec.containers 1 ).args -}} 319 true 320 {{- end -}} 321 {{- end -}} 322 `, 323 expected: "", 324 wantErr: false, 325 }, { 326 name: "exist_test", 327 tpl: ` 328 {{- if hasKey (index $.podSpec.containers 0 ) "args" }} 329 {{- if mustHas "logs" (index $.podSpec.containers 0 ).args -}} 330 true 331 {{- end }} 332 {{- end -}} 333 `, 334 expected: "true", 335 wantErr: false, 336 }, { 337 name: "not exist key", 338 tpl: ` 339 {{- if hasKey (index $.podSpec.containers 0 ) "args" }} 340 {{- if mustHas "abcd" (index $.podSpec.containers 0 ).args -}} 341 true 342 {{- end }} 343 {{- end -}} 344 `, 345 expected: "", 346 wantErr: false, 347 }, { 348 name: "kb component test", 349 tpl: ` 350 {{- if mustHas "error" $.component.enabledLogs }} 351 log_error=log/mysqld.err 352 {{- end }} 353 `, 354 expected: "", 355 wantErr: true, 356 }, { 357 name: "kb component test", 358 tpl: ` 359 {{- if hasKey $.component "enabledLogs" }} 360 {{- if mustHas "error" $.component.enabledLogs }} 361 log_error=log/mysqld.err 362 {{- end }} 363 {{- end -}} 364 `, 365 expected: "", 366 wantErr: false, 367 }} 368 369 for _, tt := range tests { 370 rendered, err := cfgBuilder.render(map[string]string{ 371 tt.name: tt.tpl, 372 }) 373 if tt.wantErr { 374 Expect(err).ShouldNot(Succeed()) 375 } else { 376 Expect(rendered[tt.name]).Should(BeEquivalentTo(tt.expected)) 377 } 378 } 379 }) 380 381 }) 382 383 Context("calMysqlPoolSizeByResource test", func() { 384 It("mysql test", func() { 385 Expect(calMysqlPoolSizeByResource(nil, false)).Should(Equal("128M")) 386 387 Expect(calMysqlPoolSizeByResource(nil, true)).Should(Equal("128M")) 388 389 // for small instance class 390 Expect(calMysqlPoolSizeByResource(&ResourceDefinition{ 391 MemorySize: 1024 * 1024 * 1024, 392 CoreNum: 1, 393 }, false)).Should(Equal("128M")) 394 395 Expect(calMysqlPoolSizeByResource(&ResourceDefinition{ 396 MemorySize: 2 * 1024 * 1024 * 1024, 397 CoreNum: 2, 398 }, false)).Should(Equal("256M")) 399 400 // for shard 401 Expect(calMysqlPoolSizeByResource(&ResourceDefinition{ 402 MemorySize: 2 * 1024 * 1024 * 1024, 403 CoreNum: 2, 404 }, true)).Should(Equal("1024M")) 405 406 insClassTest := []insClassType{ 407 // for 2 core 408 { 409 memSize: 4, 410 cpu: 2, 411 bufferSize: "1024M", 412 maxBufferSize: 1024, 413 }, 414 { 415 memSize: 8, 416 cpu: 2, 417 bufferSize: "4096M", 418 maxBufferSize: 4096, 419 }, 420 { 421 memSize: 16, 422 cpu: 2, 423 bufferSize: "9216M", 424 maxBufferSize: 10240, 425 }, 426 // for 4 core 427 { 428 memSize: 8, 429 cpu: 4, 430 bufferSize: "4096M", 431 maxBufferSize: 4096, 432 }, 433 { 434 memSize: 16, 435 cpu: 4, 436 bufferSize: "9216M", 437 maxBufferSize: 10240, 438 }, 439 { 440 memSize: 32, 441 cpu: 4, 442 bufferSize: "21504M", 443 maxBufferSize: 22528, 444 }, 445 // for 8 core 446 { 447 memSize: 16, 448 cpu: 8, 449 bufferSize: "9216M", 450 maxBufferSize: 10240, 451 }, 452 { 453 memSize: 32, 454 cpu: 8, 455 bufferSize: "21504M", 456 maxBufferSize: 22528, 457 }, 458 { 459 memSize: 64, 460 cpu: 8, 461 bufferSize: "45056M", 462 maxBufferSize: 48128, 463 }, 464 // for 12 core 465 { 466 memSize: 24, 467 cpu: 12, 468 bufferSize: "15360M", 469 maxBufferSize: 16384, 470 }, 471 { 472 memSize: 48, 473 cpu: 12, 474 bufferSize: "33792M", 475 maxBufferSize: 35840, 476 }, 477 { 478 memSize: 96, 479 cpu: 12, 480 bufferSize: "69632M", 481 maxBufferSize: 73728, 482 }, 483 // for 16 core 484 { 485 memSize: 32, 486 cpu: 16, 487 bufferSize: "21504M", 488 maxBufferSize: 22528, 489 }, 490 { 491 memSize: 64, 492 cpu: 16, 493 bufferSize: "45056M", 494 maxBufferSize: 48128, 495 }, 496 { 497 memSize: 128, 498 cpu: 16, 499 bufferSize: "93184M", 500 maxBufferSize: 99328, 501 }, 502 // for 24 core 503 { 504 memSize: 48, 505 cpu: 24, 506 bufferSize: "32768M", 507 maxBufferSize: 34816, 508 }, 509 { 510 memSize: 96, 511 cpu: 24, 512 bufferSize: "69632M", 513 maxBufferSize: 73728, 514 }, 515 { 516 memSize: 192, 517 cpu: 24, 518 bufferSize: "140288M", 519 maxBufferSize: 149504, 520 }, 521 // for 32 core 522 { 523 memSize: 64, 524 cpu: 32, 525 bufferSize: "45056M", 526 maxBufferSize: 47104, 527 }, 528 { 529 memSize: 128, 530 cpu: 32, 531 bufferSize: "93184M", 532 maxBufferSize: 99328, 533 }, 534 { 535 memSize: 256, 536 cpu: 32, 537 bufferSize: "188416M", 538 maxBufferSize: 200704, 539 }, 540 // for 52 core 541 { 542 memSize: 96, 543 cpu: 52, 544 bufferSize: "67584M", 545 maxBufferSize: 72704, 546 }, 547 { 548 memSize: 192, 549 cpu: 52, 550 bufferSize: "140288M", 551 maxBufferSize: 149504, 552 }, 553 { 554 memSize: 384, 555 cpu: 52, 556 bufferSize: "283648M", 557 maxBufferSize: 302080, 558 }, 559 // for 64 core 560 { 561 memSize: 256, 562 cpu: 64, 563 bufferSize: "188416M", 564 maxBufferSize: 200704, 565 }, 566 { 567 memSize: 512, 568 cpu: 64, 569 bufferSize: "378880M", 570 maxBufferSize: 403456, 571 }, 572 // for 102 573 { 574 memSize: 768, 575 cpu: 102, 576 bufferSize: "569344M", 577 maxBufferSize: 607232, 578 }, 579 // for 104 core 580 { 581 memSize: 192, 582 cpu: 104, 583 bufferSize: "138240M", 584 maxBufferSize: 147456, 585 }, 586 { 587 memSize: 384, 588 cpu: 104, 589 bufferSize: "282624M", 590 maxBufferSize: 302080, 591 }, 592 } 593 594 for _, r := range insClassTest { 595 ret := calMysqlPoolSizeByResource(&ResourceDefinition{ 596 MemorySize: r.memSize * 1024 * 1024 * 1024, // 4G 597 CoreNum: r.cpu, // 2core 598 }, false) 599 Expect(ret).Should(Equal(r.bufferSize)) 600 Expect(strconv.ParseInt(strings.Trim(ret, "M"), 10, 64)).Should(BeNumerically("<=", r.maxBufferSize)) 601 } 602 }) 603 }) 604 605 })