github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/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 configuration
    21  
    22  import (
    23  	"context"
    24  	"strconv"
    25  
    26  	. "github.com/onsi/ginkgo/v2"
    27  	. "github.com/onsi/gomega"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	appsv1 "k8s.io/api/apps/v1"
    31  	corev1 "k8s.io/api/core/v1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    36  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    37  	cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util"
    38  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    39  	"github.com/1aal/kubeblocks/pkg/controller/component"
    40  	"github.com/1aal/kubeblocks/pkg/controller/factory"
    41  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    42  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    43  	testutil "github.com/1aal/kubeblocks/pkg/testutil/k8s"
    44  )
    45  
    46  var _ = Describe("ConfigurationPipelineTest", func() {
    47  
    48  	const testConfigFile = "postgresql.conf"
    49  
    50  	var clusterObj *appsv1alpha1.Cluster
    51  	var clusterVersionObj *appsv1alpha1.ClusterVersion
    52  	var clusterDefObj *appsv1alpha1.ClusterDefinition
    53  	var clusterComponent *component.SynthesizedComponent
    54  	var configMapObj *corev1.ConfigMap
    55  	var configConstraint *appsv1alpha1.ConfigConstraint
    56  	var configurationObj *appsv1alpha1.Configuration
    57  	var k8sMockClient *testutil.K8sClientMockHelper
    58  
    59  	mockStatefulSet := func() *appsv1.StatefulSet {
    60  		envConfig := factory.BuildEnvConfig(clusterObj, clusterComponent)
    61  		stsObj, err := factory.BuildSts(intctrlutil.RequestCtx{
    62  			Ctx: ctx,
    63  			Log: logger,
    64  		}, clusterObj, clusterComponent, envConfig.Name)
    65  		Expect(err).Should(Succeed())
    66  		return stsObj
    67  	}
    68  
    69  	mockAPIResource := func(lazyFetcher testutil.Getter) {
    70  		k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult(
    71  			[]client.Object{
    72  				clusterDefObj,
    73  				clusterVersionObj,
    74  				clusterObj,
    75  				clusterObj,
    76  				configMapObj,
    77  				configConstraint,
    78  				configurationObj,
    79  			}, lazyFetcher), testutil.WithAnyTimes()))
    80  		k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes()))
    81  		k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error {
    82  			switch v := obj.(type) {
    83  			case *appsv1alpha1.Configuration:
    84  				if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) {
    85  					configurationObj.Spec = *v.Spec.DeepCopy()
    86  					configurationObj.Status = *v.Status.DeepCopy()
    87  				}
    88  			}
    89  			return nil
    90  		}, testutil.WithAnyTimes()))
    91  		k8sMockClient.MockStatusMethod().
    92  			EXPECT().
    93  			Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    94  			DoAndReturn(func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
    95  				switch v := obj.(type) {
    96  				case *appsv1alpha1.Configuration:
    97  					if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) {
    98  						configurationObj.Status = *v.Status.DeepCopy()
    99  					}
   100  				}
   101  				return nil
   102  			}).AnyTimes()
   103  	}
   104  
   105  	BeforeEach(func() {
   106  		// Add any setup steps that needs to be executed before each test
   107  		k8sMockClient = testutil.NewK8sMockClient()
   108  		clusterObj, clusterDefObj, clusterVersionObj, _ = newAllFieldsClusterObj(nil, nil, false)
   109  		clusterComponent = newAllFieldsComponent(clusterDefObj, clusterVersionObj)
   110  		configMapObj = testapps.NewConfigMap("default", mysqlConfigName,
   111  			testapps.SetConfigMapData(testConfigFile, `
   112  bgwriter_delay = '200ms'
   113  bgwriter_flush_after = '64'
   114  bgwriter_lru_maxpages = '1000'
   115  bgwriter_lru_multiplier = '10.0'
   116  bytea_output = 'hex'
   117  check_function_bodies = 'True'
   118  checkpoint_completion_target = '0.9'
   119  checkpoint_flush_after = '32'
   120  checkpoint_timeout = '15min'
   121  max_connections = '1000'
   122  `))
   123  		configurationObj = builder.NewConfigurationBuilder(testCtx.DefaultNamespace,
   124  			cfgcore.GenerateComponentConfigurationName(clusterName, mysqlCompName)).
   125  			ClusterRef(clusterName).
   126  			Component(mysqlCompName).
   127  			GetObject()
   128  		configConstraint = &appsv1alpha1.ConfigConstraint{
   129  			ObjectMeta: metav1.ObjectMeta{
   130  				Name: mysqlConfigConstraintName,
   131  			},
   132  			Spec: appsv1alpha1.ConfigConstraintSpec{
   133  				FormatterConfig: &appsv1alpha1.FormatterConfig{
   134  					Format: appsv1alpha1.Properties,
   135  				},
   136  			}}
   137  	})
   138  
   139  	AfterEach(func() {
   140  		k8sMockClient.Finish()
   141  	})
   142  
   143  	Context("ConfigPipelineTest", func() {
   144  		It("NormalTest", func() {
   145  			By("mock configSpec keys")
   146  			clusterComponent.ConfigTemplates[0].Keys = []string{testConfigFile}
   147  
   148  			By("create configuration resource")
   149  			createPipeline := NewCreatePipeline(ReconcileCtx{
   150  				ResourceCtx: &intctrlutil.ResourceCtx{
   151  					Client:        k8sMockClient.Client(),
   152  					Context:       ctx,
   153  					Namespace:     testCtx.DefaultNamespace,
   154  					ClusterName:   clusterName,
   155  					ComponentName: mysqlCompName,
   156  				},
   157  				Cluster:    clusterObj,
   158  				ClusterVer: clusterVersionObj,
   159  				Component:  clusterComponent,
   160  				PodSpec:    clusterComponent.PodSpec,
   161  				Object:     mockStatefulSet(),
   162  			})
   163  
   164  			By("mock api resource for configuration")
   165  			mockAPIResource(func(key client.ObjectKey, obj client.Object) (bool, error) {
   166  				switch obj.(type) {
   167  				case *corev1.ConfigMap:
   168  					for _, renderedObj := range createPipeline.renderWrapper.renderedObjs {
   169  						if client.ObjectKeyFromObject(renderedObj) == key {
   170  							testutil.SetGetReturnedObject(obj, renderedObj)
   171  							return true, nil
   172  						}
   173  					}
   174  				}
   175  				return false, nil
   176  			})
   177  
   178  			err := createPipeline.Prepare().
   179  				UpdateConfiguration(). // reconcile Configuration
   180  				Configuration().       // sync Configuration
   181  				CreateConfigTemplate().
   182  				UpdatePodVolumes().
   183  				BuildConfigManagerSidecar().
   184  				UpdateConfigRelatedObject().
   185  				UpdateConfigurationStatus().
   186  				Complete()
   187  			Expect(err).Should(Succeed())
   188  
   189  			By("update configuration resource for mocking reconfiguring")
   190  			item := configurationObj.Spec.ConfigItemDetails[0]
   191  			item.ConfigFileParams = map[string]appsv1alpha1.ConfigParams{
   192  				testConfigFile: {
   193  					Parameters: map[string]*string{
   194  						"max_connections": cfgutil.ToPointer("2000"),
   195  					},
   196  				},
   197  				"other.conf": {
   198  					Content: cfgutil.ToPointer(`for test`),
   199  				},
   200  			}
   201  			reconcileTask := NewReconcilePipeline(ReconcileCtx{
   202  				ResourceCtx: createPipeline.ResourceCtx,
   203  				Cluster:     clusterObj,
   204  				ClusterVer:  clusterVersionObj,
   205  				Component:   clusterComponent,
   206  				PodSpec:     clusterComponent.PodSpec,
   207  			}, item, &configurationObj.Status.ConfigurationItemStatus[0], nil)
   208  
   209  			By("update configuration resource")
   210  			err = reconcileTask.InitConfigSpec().
   211  				Configuration().
   212  				ConfigMap(configSpecName).
   213  				ConfigConstraints(reconcileTask.ConfigSpec().ConfigConstraintRef).
   214  				PrepareForTemplate().
   215  				RerenderTemplate().
   216  				ApplyParameters().
   217  				UpdateConfigVersion(strconv.FormatInt(reconcileTask.ConfigurationObj.GetGeneration(), 10)).
   218  				Sync().
   219  				SyncStatus().
   220  				Complete()
   221  			Expect(err).Should(Succeed())
   222  
   223  			By("rerender configuration template")
   224  			reconcileTask.item.Version = "v2"
   225  			err = reconcileTask.InitConfigSpec().
   226  				Configuration().
   227  				ConfigMap(configSpecName).
   228  				ConfigConstraints(reconcileTask.ConfigSpec().ConfigConstraintRef).
   229  				PrepareForTemplate().
   230  				RerenderTemplate().
   231  				ApplyParameters().
   232  				UpdateConfigVersion(strconv.FormatInt(reconcileTask.ConfigurationObj.GetGeneration(), 10)).
   233  				Sync().
   234  				SyncStatus().
   235  				Complete()
   236  			Expect(err).Should(Succeed())
   237  		})
   238  	})
   239  
   240  })