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