github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/builtin_env_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  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/resource"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	coreclient "sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    34  	ctrlcomp "github.com/1aal/kubeblocks/pkg/controller/component"
    35  	testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    36  )
    37  
    38  var _ = Describe("tpl env template", func() {
    39  
    40  	patroniTemplate := `
    41  bootstrap:
    42    initdb:
    43      - auth-host: md5
    44      - auth-local: trust
    45  `
    46  	const (
    47  		cmTemplateName   = "patroni-template-config"
    48  		cmConfigFileName = "postgresql.yaml"
    49  	)
    50  
    51  	var (
    52  		podSpec     *corev1.PodSpec
    53  		cfgTemplate []appsv1alpha1.ComponentConfigSpec
    54  		component   *ctrlcomp.SynthesizedComponent
    55  		cluster     *appsv1alpha1.Cluster
    56  
    57  		mockClient *testutil.K8sClientMockHelper
    58  	)
    59  
    60  	BeforeEach(func() {
    61  		mockClient = testutil.NewK8sMockClient()
    62  
    63  		mockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]coreclient.Object{
    64  			&corev1.ConfigMap{
    65  				ObjectMeta: metav1.ObjectMeta{
    66  					Name:      cmTemplateName,
    67  					Namespace: "default",
    68  				},
    69  				Data: map[string]string{
    70  					cmConfigFileName: patroniTemplate,
    71  				}},
    72  			&corev1.ConfigMap{
    73  				ObjectMeta: metav1.ObjectMeta{
    74  					Name:      "my-config-env",
    75  					Namespace: "default",
    76  				},
    77  				Data: map[string]string{
    78  					"KB_0_HOSTNAME":    "my-mysql-0.my-mysql-headless",
    79  					"KB_FOLLOWERS":     "",
    80  					"KB_LEADER":        "my-mysql-0",
    81  					"KB_REPLICA_COUNT": "1",
    82  					"LOOP_REFERENCE_A": "$(LOOP_REFERENCE_B)",
    83  					"LOOP_REFERENCE_B": "$(LOOP_REFERENCE_C)",
    84  					"LOOP_REFERENCE_C": "$(LOOP_REFERENCE_A)",
    85  				}},
    86  			&corev1.Secret{
    87  				ObjectMeta: metav1.ObjectMeta{
    88  					Name:      "my-conn-credential",
    89  					Namespace: "default",
    90  				},
    91  				Data: map[string][]byte{
    92  					"password": []byte("4zrqfl2r"),
    93  					"username": []byte("root"),
    94  				}},
    95  		}), testutil.WithAnyTimes()))
    96  
    97  		// 2 configmap and 2 secret
    98  		// Add any setup steps that needs to be executed before each test
    99  		podSpec = &corev1.PodSpec{
   100  			Containers: []corev1.Container{
   101  				{
   102  					Name: "mytest",
   103  					Env: []corev1.EnvVar{
   104  						{
   105  							Name:  "KB_CLUSTER_NAME",
   106  							Value: "my",
   107  						},
   108  						{
   109  							Name:  "KB_COMP_NAME",
   110  							Value: "mysql",
   111  						},
   112  						{
   113  							Name: "MEMORY_SIZE",
   114  							ValueFrom: &corev1.EnvVarSource{
   115  								ResourceFieldRef: &corev1.ResourceFieldSelector{
   116  									ContainerName: "mytest",
   117  									Resource:      "limits.memory",
   118  								},
   119  							},
   120  						},
   121  						{
   122  							Name: "CPU",
   123  							ValueFrom: &corev1.EnvVarSource{
   124  								ResourceFieldRef: &corev1.ResourceFieldSelector{
   125  									Resource: "limits.cpu",
   126  								},
   127  							},
   128  						},
   129  						{
   130  							Name: "CPU2",
   131  							ValueFrom: &corev1.EnvVarSource{
   132  								ResourceFieldRef: &corev1.ResourceFieldSelector{
   133  									ContainerName: "not_exist_container",
   134  									Resource:      "limits.memory",
   135  								},
   136  							},
   137  						},
   138  						{
   139  							Name: "MYSQL_USER",
   140  							ValueFrom: &corev1.EnvVarSource{
   141  								SecretKeyRef: &corev1.SecretKeySelector{
   142  									LocalObjectReference: corev1.LocalObjectReference{
   143  										Name: "my-conn-credential",
   144  									},
   145  									Key: "username",
   146  								},
   147  							},
   148  						},
   149  						{
   150  							Name: "MYSQL_PASSWORD",
   151  							ValueFrom: &corev1.EnvVarSource{
   152  								SecretKeyRef: &corev1.SecretKeySelector{
   153  									LocalObjectReference: corev1.LocalObjectReference{
   154  										Name: "$(CONN_CREDENTIAL_SECRET_NAME)",
   155  									},
   156  									Key: "password",
   157  								},
   158  							},
   159  						},
   160  						{
   161  							Name: "SPILO_CONFIGURATION",
   162  							ValueFrom: &corev1.EnvVarSource{
   163  								ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
   164  									LocalObjectReference: corev1.LocalObjectReference{
   165  										Name: cmTemplateName,
   166  									},
   167  									Key: cmConfigFileName,
   168  								},
   169  							},
   170  						},
   171  					},
   172  					EnvFrom: []corev1.EnvFromSource{
   173  						{
   174  							ConfigMapRef: &corev1.ConfigMapEnvSource{
   175  								LocalObjectReference: corev1.LocalObjectReference{
   176  									Name: "my-config-env",
   177  								},
   178  							},
   179  						},
   180  						{
   181  							SecretRef: &corev1.SecretEnvSource{
   182  								LocalObjectReference: corev1.LocalObjectReference{
   183  									Name: "my-secret-env",
   184  								},
   185  							},
   186  						},
   187  					},
   188  					Resources: corev1.ResourceRequirements{
   189  						Limits: map[corev1.ResourceName]resource.Quantity{
   190  							corev1.ResourceMemory: resource.MustParse("8Gi"),
   191  							corev1.ResourceCPU:    resource.MustParse("4"),
   192  						},
   193  					},
   194  				},
   195  				{
   196  					Name: "invalid_container",
   197  				},
   198  			},
   199  		}
   200  		cluster = &appsv1alpha1.Cluster{
   201  			ObjectMeta: metav1.ObjectMeta{
   202  				Name: "my",
   203  				UID:  "b006a20c-fb03-441c-bffa-2605cad7e297",
   204  			},
   205  		}
   206  		component = &ctrlcomp.SynthesizedComponent{
   207  			Name:        "mysql",
   208  			ClusterName: cluster.Name,
   209  		}
   210  		cfgTemplate = []appsv1alpha1.ComponentConfigSpec{{
   211  			ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{
   212  				Name:        "mysql-config-8.0.2",
   213  				TemplateRef: "mysql-config",
   214  				VolumeName:  "config1",
   215  			},
   216  			ConfigConstraintRef: "mysql-config-8.0.2-constraint",
   217  		}}
   218  	})
   219  
   220  	AfterEach(func() {
   221  		mockClient.Finish()
   222  	})
   223  
   224  	// for test GetContainerWithVolumeMount
   225  	Context("ConfigTemplateBuilder built-in env test", func() {
   226  		It("test built-in function", func() {
   227  			cfgBuilder := newTemplateBuilder(
   228  				"my_test",
   229  				"default",
   230  				&appsv1alpha1.Cluster{
   231  					ObjectMeta: metav1.ObjectMeta{
   232  						Name:      "my_test",
   233  						Namespace: "default",
   234  					},
   235  				},
   236  				nil, ctx, mockClient.Client(),
   237  			)
   238  
   239  			localObjs := []coreclient.Object{
   240  				&corev1.ConfigMap{
   241  					ObjectMeta: metav1.ObjectMeta{
   242  						Name:      cmTemplateName,
   243  						Namespace: "default",
   244  					},
   245  					Data: map[string]string{
   246  						cmConfigFileName: patroniTemplate,
   247  					}},
   248  			}
   249  			Expect(cfgBuilder.injectBuiltInObjectsAndFunctions(podSpec, cfgTemplate, component, localObjs)).Should(BeNil())
   250  
   251  			rendered, err := cfgBuilder.render(map[string]string{
   252  				// KB_CLUSTER_NAME, KB_COMP_NAME from env
   253  				// MYSQL_USER,MYSQL_PASSWORD from valueFrom secret key
   254  				// SPILO_CONFIGURATION from valueFrom configmap key
   255  				// KB_LEADER from envFrom configmap
   256  				// MEMORY_SIZE, CPU from resourceFieldRef
   257  				"my":            "{{ getEnvByName ( index $.podSpec.containers 0 ) \"KB_CLUSTER_NAME\" }}",
   258  				"mysql":         "{{ getEnvByName ( index $.podSpec.containers 0 ) \"KB_COMP_NAME\" }}",
   259  				"root":          "{{ getEnvByName ( index $.podSpec.containers 0 ) \"MYSQL_USER\" }}",
   260  				"4zrqfl2r":      "{{ getEnvByName ( index $.podSpec.containers 0 ) \"MYSQL_PASSWORD\" }}",
   261  				patroniTemplate: "{{ getEnvByName ( index $.podSpec.containers 0 ) \"SPILO_CONFIGURATION\" }}",
   262  				"my-mysql-0":    "{{ getEnvByName ( index $.podSpec.containers 0 ) \"KB_LEADER\" }}",
   263  
   264  				strconv.Itoa(4):                      "{{ getEnvByName ( index $.podSpec.containers 0 ) \"CPU\" }}",
   265  				strconv.Itoa(8 * 1024 * 1024 * 1024): "{{ getEnvByName ( index $.podSpec.containers 0 ) \"MEMORY_SIZE\" }}",
   266  			})
   267  
   268  			Expect(err).Should(Succeed())
   269  			for key, value := range rendered {
   270  				Expect(key).Should(BeEquivalentTo(value))
   271  			}
   272  
   273  			_, err = cfgBuilder.render(map[string]string{
   274  				"error": "{{ getEnvByName ( index $.podSpec.containers 0 ) \"CPU2\" }}",
   275  			})
   276  			Expect(err).ShouldNot(Succeed())
   277  			Expect(err.Error()).Should(ContainSubstring("not found named[not_exist_container] container"))
   278  
   279  			_, err = cfgBuilder.render(map[string]string{
   280  				"error_loop_reference": "{{ getEnvByName ( index $.podSpec.containers 0 ) \"LOOP_REFERENCE_A\" }}",
   281  			})
   282  			Expect(err).ShouldNot(Succeed())
   283  			Expect(err.Error()).Should(ContainSubstring("too many reference count, maybe there is a cycled reference"))
   284  		})
   285  	})
   286  })