diff --git a/apis/parameters/v1alpha1/componentparameter_types.go b/apis/parameters/v1alpha1/componentparameter_types.go index 71718c61f55..ae05de754ea 100644 --- a/apis/parameters/v1alpha1/componentparameter_types.go +++ b/apis/parameters/v1alpha1/componentparameter_types.go @@ -56,6 +56,8 @@ func init() { SchemeBuilder.Register(&ComponentParameter{}, &ComponentParameterList{}) } +// Deprecated: It is retained for API compatibility with existing ComponentParameter objects. +// // Payload holds the payload data. This field is optional and can contain any type of data. // Not included in the JSON representation of the object. type Payload map[string]json.RawMessage @@ -73,11 +75,9 @@ type ConfigTemplateItemDetail struct { // +kubebuilder:validation:Pattern:=`^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$` Name string `json:"name"` - // External controllers can trigger a configuration rerender by modifying this field. - // - // Note: Currently, the `payload` field is opaque and its content is not interpreted by the system. - // Modifying this field will cause a rerender, regardless of the specific content of this field. + // Deprecated: retained for API compatibility only. // + // +kubebuilder:deprecatedversion:warning="This field has been deprecated since 1.2.0" // +kubebuilder:validation:Schemaless // +kubebuilder:pruning:PreserveUnknownFields // +kubebuilder:validation:Type=object diff --git a/apis/parameters/v1alpha1/paramconfigrenderer_types.go b/apis/parameters/v1alpha1/paramconfigrenderer_types.go index b43df93100e..9e3c855d381 100644 --- a/apis/parameters/v1alpha1/paramconfigrenderer_types.go +++ b/apis/parameters/v1alpha1/paramconfigrenderer_types.go @@ -31,7 +31,11 @@ import ( // +kubebuilder:printcolumn:name="PHASE",type="string",JSONPath=".status.phase",description="status phase" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" -// ParamConfigRenderer is the Schema for the paramconfigrenderers API +// Deprecated: retained for API compatibility only. +// +// # ParamConfigRenderer is the Schema for the paramconfigrenderers API +// +// +kubebuilder:deprecatedversion:warning="This CRD has been deprecated since 1.2.0" type ParamConfigRenderer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/apis/parameters/v1alpha1/parametersdefinition_types.go b/apis/parameters/v1alpha1/parametersdefinition_types.go index b376d50aaab..7cb2a7dd905 100644 --- a/apis/parameters/v1alpha1/parametersdefinition_types.go +++ b/apis/parameters/v1alpha1/parametersdefinition_types.go @@ -56,11 +56,35 @@ func init() { // ParametersDefinitionSpec defines the desired state of ParametersDefinition type ParametersDefinitionSpec struct { + // Specifies the ComponentDefinition custom resource (CR) that defines the Component's characteristics and behavior. + // The value can represent an exact name, a name prefix, or a regular expression pattern. + // + // +optional + ComponentDef string `json:"componentDef,omitempty"` + + // ServiceVersion specifies the version of the Service expected to be provisioned by this Component. + // The version should follow the syntax and semantics of the "Semantic Versioning" specification (http://semver.org/). + // If no version is specified, the latest available version will be used. + // + // +optional + ServiceVersion string `json:"serviceVersion,omitempty"` + + // Specifies the name of the referenced config template. + // + // +optional + TemplateName string `json:"templateName,omitempty"` + // Specifies the config file name in the config template. // // +optional FileName string `json:"fileName,omitempty"` + // Specifies the format of the configuration file and any associated parameters that are specific to the chosen format. + // Supported formats include `ini`, `xml`, `yaml`, `json`, `hcl`, `dotenv`, `properties`, and `toml`. + // + // +optional + FileFormatConfig *FileFormatConfig `json:"fileFormatConfig,omitempty"` + // Defines a list of parameters including their names, default values, descriptions, // types, and constraints (permissible values or the range of valid values). // @@ -86,6 +110,7 @@ type ParametersDefinitionSpec struct { // Specifies the policy when parameter be removed. // + // +kubebuilder:deprecatedversion:warning="This field has been deprecated since 1.2.0" // +optional ParameterDeletedPolicy *ParameterDeletedPolicy `json:"deletedPolicy,omitempty"` @@ -137,6 +162,7 @@ type ParametersDefinitionSpec struct { ImmutableParameters []string `json:"immutableParameters,omitempty"` } +// Deprecated: It is retained for API compatibility with existing ParametersDefinition objects. type ParameterDeletedPolicy struct { // Specifies the method to handle the deletion of a parameter. @@ -180,6 +206,8 @@ type ParametersDefinitionStatus struct { // ReloadAction defines the mechanisms available for dynamically reloading a process within K8s without requiring a restart. // +// Deprecated: It is retained for API compatibility with existing ParametersDefinition objects. +// // Only one of the mechanisms can be specified at a time. type ReloadAction struct { // Allows to execute a custom shell script to reload the process. diff --git a/apis/parameters/v1alpha1/types.go b/apis/parameters/v1alpha1/types.go index 4f7c8fb9ef0..f761a65b619 100644 --- a/apis/parameters/v1alpha1/types.go +++ b/apis/parameters/v1alpha1/types.go @@ -46,6 +46,8 @@ const ( PDDeletingPhase ParametersDescPhase = "Deleting" ) +// Deprecated: It is retained for API compatibility with existing ParametersDefinition objects. +// // ParameterDeletedMethod defines how to handle parameter remove // +enum // +kubebuilder:validation:Enum={RestoreToDefault, Reset} @@ -56,6 +58,8 @@ const ( PDPReset ParameterDeletedMethod = "Reset" ) +// Deprecated: It is retained for API compatibility with existing ParamConfigRenderer objects. +// // RerenderResourceType defines the resource requirements for a component. // +enum // +kubebuilder:validation:Enum={vscale,hscale,tls,shardingHScale} diff --git a/apis/parameters/v1alpha1/zz_generated.deepcopy.go b/apis/parameters/v1alpha1/zz_generated.deepcopy.go index 21ecdde2af7..62535e70b31 100644 --- a/apis/parameters/v1alpha1/zz_generated.deepcopy.go +++ b/apis/parameters/v1alpha1/zz_generated.deepcopy.go @@ -715,6 +715,11 @@ func (in *ParametersDefinitionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ParametersDefinitionSpec) DeepCopyInto(out *ParametersDefinitionSpec) { *out = *in + if in.FileFormatConfig != nil { + in, out := &in.FileFormatConfig, &out.FileFormatConfig + *out = new(FileFormatConfig) + (*in).DeepCopyInto(*out) + } if in.ParametersSchema != nil { in, out := &in.ParametersSchema, &out.ParametersSchema *out = new(ParametersSchema) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 3a5b9d3cb76..3fd6aaec84c 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -660,6 +660,14 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ComponentParameter") os.Exit(1) } + if err = (¶meterscontrollers.LegacyParamConfigRendererReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("legacy-param-config-renderer-controller"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "LegacyParamConfigRenderer") + os.Exit(1) + } if err = (¶meterscontrollers.ComponentParameterReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -676,14 +684,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ReconfigureRequest") os.Exit(1) } - if err = (¶meterscontrollers.ParameterDrivenConfigRenderReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("component-driven-config-render-controller"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ParamConfigRenderer") - os.Exit(1) - } if err = (¶meterscontrollers.ParameterTemplateExtensionReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml b/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml index bca11468482..ebc4821496a 100644 --- a/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml +++ b/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml @@ -652,12 +652,7 @@ spec: pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string payload: - description: |- - External controllers can trigger a configuration rerender by modifying this field. - - - Note: Currently, the `payload` field is opaque and its content is not interpreted by the system. - Modifying this field will cause a rerender, regardless of the specific content of this field. + description: 'Deprecated: retained for API compatibility only.' type: object x-kubernetes-preserve-unknown-fields: true userConfigTemplates: diff --git a/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml b/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml index 6f93a3edd1f..d10d9fe015f 100644 --- a/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml +++ b/config/crd/bases/parameters.kubeblocks.io_paramconfigrenderers.yaml @@ -31,11 +31,16 @@ spec: - jsonPath: .metadata.creationTimestamp name: AGE type: date + deprecated: true + deprecationWarning: This CRD has been deprecated since 1.2.0 name: v1alpha1 schema: openAPIV3Schema: - description: ParamConfigRenderer is the Schema for the paramconfigrenderers - API + description: |- + Deprecated: retained for API compatibility only. + + + # ParamConfigRenderer is the Schema for the paramconfigrenderers API properties: apiVersion: description: |- @@ -147,8 +152,11 @@ spec: - MySQL: increase max connections after v-scale operation. - Zookeeper: update zoo.cfg with new node addresses after h-scale operation. items: - description: RerenderResourceType defines the resource requirements - for a component. + description: |- + Deprecated: It is retained for API compatibility with existing ParamConfigRenderer objects. + + + RerenderResourceType defines the resource requirements for a component. enum: - vscale - hscale diff --git a/config/crd/bases/parameters.kubeblocks.io_parametersdefinitions.yaml b/config/crd/bases/parameters.kubeblocks.io_parametersdefinitions.yaml index 92049f462bf..3ce16251ac7 100644 --- a/config/crd/bases/parameters.kubeblocks.io_parametersdefinitions.yaml +++ b/config/crd/bases/parameters.kubeblocks.io_parametersdefinitions.yaml @@ -58,6 +58,11 @@ spec: spec: description: ParametersDefinitionSpec defines the desired state of ParametersDefinition properties: + componentDef: + description: |- + Specifies the ComponentDefinition custom resource (CR) that defines the Component's characteristics and behavior. + The value can represent an exact name, a name prefix, or a regular expression pattern. + type: string deletedPolicy: description: Specifies the policy when parameter be removed. properties: @@ -88,6 +93,49 @@ spec: type: string type: array x-kubernetes-list-type: set + fileFormatConfig: + description: |- + Specifies the format of the configuration file and any associated parameters that are specific to the chosen format. + Supported formats include `ini`, `xml`, `yaml`, `json`, `hcl`, `dotenv`, `properties`, and `toml`. + properties: + format: + description: |- + The config file format. Valid values are `ini`, `xml`, `yaml`, `json`, + `hcl`, `dotenv`, `properties` and `toml`. Each format has its own characteristics and use cases. + + + - ini: is a text-based content with a structure and syntax comprising key–value pairs for properties, reference wiki: https://en.wikipedia.org/wiki/INI_file + - xml: refers to wiki: https://en.wikipedia.org/wiki/XML + - yaml: supports for complex data types and structures. + - json: refers to wiki: https://en.wikipedia.org/wiki/JSON + - hcl: The HashiCorp Configuration Language (HCL) is a configuration language authored by HashiCorp, reference url: https://www.linode.com/docs/guides/introduction-to-hcl/ + - dotenv: is a plain text file with simple key–value pairs, reference wiki: https://en.wikipedia.org/wiki/Configuration_file#MS-DOS + - properties: a file extension mainly used in Java, reference wiki: https://en.wikipedia.org/wiki/.properties + - toml: refers to wiki: https://en.wikipedia.org/wiki/TOML + - props-plus: a file extension mainly used in Java, supports CamelCase(e.g: brokerMaxConnectionsPerIp) + enum: + - xml + - ini + - yaml + - json + - hcl + - dotenv + - toml + - properties + - redis + - props-plus + - props-ultra + type: string + iniConfig: + description: Holds options specific to the 'ini' file format. + properties: + sectionName: + description: A string that describes the name of the ini section. + type: string + type: object + required: + - format + type: object fileName: description: Specifies the config file name in the config template. type: string @@ -363,6 +411,12 @@ spec: The "all" option is for certain engines that require static parameters to be set via SQL statements before they can take effect on restart. type: boolean + serviceVersion: + description: |- + ServiceVersion specifies the version of the Service expected to be provisioned by this Component. + The version should follow the syntax and semantics of the "Semantic Versioning" specification (http://semver.org/). + If no version is specified, the latest available version will be used. + type: string staticParameters: description: |- List static parameters. @@ -371,6 +425,9 @@ spec: type: string type: array x-kubernetes-list-type: set + templateName: + description: Specifies the name of the referenced config template. + type: string type: object status: description: ParametersDefinitionStatus defines the observed state of diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 7052748737f..5963d258fc2 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -855,8 +855,6 @@ rules: resources: - paramconfigrenderers verbs: - - create - - delete - get - list - patch @@ -868,14 +866,6 @@ rules: - paramconfigrenderers/finalizers verbs: - update -- apiGroups: - - parameters.kubeblocks.io - resources: - - paramconfigrenderers/status - verbs: - - get - - patch - - update - apiGroups: - parameters.kubeblocks.io resources: diff --git a/controllers/parameters/componentdrivenparameter_controller.go b/controllers/parameters/componentdrivenparameter_controller.go index 0ddab1abaf8..963def3a000 100644 --- a/controllers/parameters/componentdrivenparameter_controller.go +++ b/controllers/parameters/componentdrivenparameter_controller.go @@ -266,7 +266,7 @@ func buildComponentParameter(reqCtx intctrlutil.RequestCtx, reader client.Reader return nil, nil } - configRender, paramsDefs, err := parameters.ResolveCmpdParametersDefs(reqCtx.Ctx, reader, cmpd) + configDescs, paramsDefs, err := parameters.ResolveCmpdParametersDefs(reqCtx.Ctx, reader, cmpd) if err != nil { return nil, err } @@ -278,7 +278,7 @@ func buildComponentParameter(reqCtx intctrlutil.RequestCtx, reader client.Reader if err != nil { return nil, err } - parameterSpecs, err := parameters.ClassifyParamsFromConfigTemplate(initParameters, cmpd, paramsDefs, tpls, configRender) + parameterSpecs, err := parameters.ClassifyParamsFromConfigTemplate(initParameters, cmpd, paramsDefs, tpls, configDescs) if err != nil { return nil, err } @@ -301,14 +301,7 @@ func buildComponentParameter(reqCtx intctrlutil.RequestCtx, reader client.Reader if err = intctrlutil.SetOwnerReference(comp, parameterObj); err != nil { return nil, err } - sharding, err := parameters.ResolveShardingReference(reqCtx.Ctx, reader, comp) - if err != nil { - return nil, err - } - if configRender != nil { - err = parameters.UpdateConfigPayload(¶meterObj.Spec, &comp.Spec, &configRender.Spec, sharding) - } - return parameterObj, err + return parameterObj, nil } // resolveLegacyConfigManagerRequirement reports whether this component still depends on the @@ -471,7 +464,6 @@ func (r *ComponentDrivenParameterReconciler) mergeComponentParameter(expected *p if expected.CustomTemplates != nil { dest.CustomTemplates = expected.CustomTemplates } - dest.Payload = expected.Payload dest.ConfigSpec = expected.ConfigSpec }) } diff --git a/controllers/parameters/componentdrivenparameter_controller_test.go b/controllers/parameters/componentdrivenparameter_controller_test.go index ae91d6f6603..21ed11667c6 100644 --- a/controllers/parameters/componentdrivenparameter_controller_test.go +++ b/controllers/parameters/componentdrivenparameter_controller_test.go @@ -61,7 +61,6 @@ var _ = Describe("ComponentParameterGenerator Controller", func() { By("Create a parameters definition obj") paramsDef := testparameters.NewParametersDefinitionFactory(paramsDefName). - SetReloadAction(testparameters.WithNoneAction()). Create(&testCtx). GetObject() Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(paramsDef), func(obj *parametersv1alpha1.ParametersDefinition) { @@ -78,18 +77,16 @@ var _ = Describe("ComponentParameterGenerator Controller", func() { Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { obj.Status.Phase = appsv1.AvailablePhase })()).Should(Succeed()) - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDef.Name). - SetComponentDefinition(compDefObj.GetName()). - SetTemplateName(configSpecName). - HScaleEnabled(). - VScaleEnabled(). - TLSEnabled(). - Create(&testCtx). - GetObject() - Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(pdcr), func(obj *parametersv1alpha1.ParamConfigRenderer) { - obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + By("Bind the parameters definition directly to the component template") + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(paramsDef), func(obj *parametersv1alpha1.ParametersDefinition) { + obj.Spec.ComponentDef = compDefObj.GetName() + obj.Spec.TemplateName = configSpecName + obj.Spec.FileFormatConfig = ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{SectionName: "mysqld"}, + }, + } })()).Should(Succeed()) By("Create init parameters") @@ -157,8 +154,6 @@ var _ = Describe("ComponentParameterGenerator Controller", func() { Eventually(testapps.CheckObj(&testCtx, parameterKey, func(g Gomega, parameter *parametersv1alpha1.ComponentParameter) { item := parameters.GetConfigTemplateItem(¶meter.Spec, configSpecName) g.Expect(item).ShouldNot(BeNil()) - g.Expect(item.Payload).Should(HaveKey(constant.ReplicasPayload)) - g.Expect(item.Payload).Should(HaveKey(constant.ComponentResourcePayload)) g.Expect(item.ConfigFileParams).Should(HaveKey(testparameters.MysqlConfigFile)) g.Expect(item.ConfigFileParams[testparameters.MysqlConfigFile].Parameters).Should(HaveKeyWithValue("innodb_buffer_pool_size", pointer.String("1024M"))) g.Expect(item.ConfigFileParams[testparameters.MysqlConfigFile].Parameters).Should(HaveKeyWithValue("max_connections", pointer.String("100"))) @@ -166,11 +161,11 @@ var _ = Describe("ComponentParameterGenerator Controller", func() { }) }) - Context("No ParamConfigRenderer", func() { + Context("No matching ParametersDefinition", func() { It("NPE test", func() { initTestResource() - By("Create a component definition obj without ParamConfigRenderer") + By("Create a component definition obj without matching ParametersDefinition") key := testapps.GetRandomizedKey(testCtx.DefaultNamespace, compDefName) compDefObj := testapps.NewComponentDefinitionFactory(key.Name). WithRandomName(). @@ -217,18 +212,18 @@ func TestResolveLegacyConfigManagerRequirement(t *testing.T) { Spec: appsv1.ComponentDefinitionSpec{ServiceVersion: "8.0"}, Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, }, - ¶metersv1alpha1.ParamConfigRenderer{ - ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, - Spec: parametersv1alpha1.ParamConfigRendererSpec{ - ComponentDef: cmpdName, - ParametersDefs: []string{paramsDefName}, - }, - Status: parametersv1alpha1.ParamConfigRendererStatus{Phase: parametersv1alpha1.PDAvailablePhase}, - }, ¶metersv1alpha1.ParametersDefinition{ ObjectMeta: metav1.ObjectMeta{Name: paramsDefName}, Spec: parametersv1alpha1.ParametersDefinitionSpec{ - FileName: "my.cnf", + ComponentDef: cmpdName, + FileName: "my.cnf", + TemplateName: "mysql-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{SectionName: "mysqld"}, + }, + }, }, Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, }, @@ -244,7 +239,7 @@ func TestResolveLegacyConfigManagerRequirement(t *testing.T) { }, } if withReload { - objects[2].(*parametersv1alpha1.ParametersDefinition).Spec.ReloadAction = ¶metersv1alpha1.ReloadAction{ + objects[1].(*parametersv1alpha1.ParametersDefinition).Spec.ReloadAction = ¶metersv1alpha1.ReloadAction{ ShellTrigger: ¶metersv1alpha1.ShellTrigger{Command: []string{"bash", "-c", "reload"}}, } } @@ -351,18 +346,18 @@ func TestSyncLegacyConfigManagerRequirement(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: cmpdName}, Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, }, - ¶metersv1alpha1.ParamConfigRenderer{ - ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, - Spec: parametersv1alpha1.ParamConfigRendererSpec{ - ComponentDef: cmpdName, - ParametersDefs: []string{paramsDefName}, - }, - Status: parametersv1alpha1.ParamConfigRendererStatus{Phase: parametersv1alpha1.PDAvailablePhase}, - }, ¶metersv1alpha1.ParametersDefinition{ ObjectMeta: metav1.ObjectMeta{Name: paramsDefName}, Spec: parametersv1alpha1.ParametersDefinitionSpec{ - FileName: "my.cnf", + ComponentDef: cmpdName, + FileName: "my.cnf", + TemplateName: "mysql-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{SectionName: "mysqld"}, + }, + }, ReloadAction: ¶metersv1alpha1.ReloadAction{ ShellTrigger: ¶metersv1alpha1.ShellTrigger{Command: []string{"bash", "-c", "reload"}}, }, diff --git a/controllers/parameters/componentparameter_controller.go b/controllers/parameters/componentparameter_controller.go index 324cd443a7f..8ca243e35c3 100644 --- a/controllers/parameters/componentparameter_controller.go +++ b/controllers/parameters/componentparameter_controller.go @@ -27,15 +27,18 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/model" @@ -90,16 +93,24 @@ func (r *ComponentParameterReconciler) SetupWithManager(mgr ctrl.Manager) error MaxConcurrentReconciles: viper.GetInt(constant.CfgKBReconcileWorkers) / 4, }). Owns(&corev1.ConfigMap{}). + Watches(&appsv1.Component{}, handler.EnqueueRequestsFromMapFunc(r.enqueueByComponent)). Complete(r) } -func (r *ComponentParameterReconciler) reconcile(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) (ctrl.Result, error) { - tasks := generateReconcileTasks(reqCtx, componentParameter) - if len(tasks) == 0 { - reqCtx.Log.Info("nothing to reconcile") - return intctrlutil.Reconciled() +func (r *ComponentParameterReconciler) enqueueByComponent(_ context.Context, object client.Object) []reconcile.Request { + comp, ok := object.(*appsv1.Component) + if !ok { + return nil } + return []reconcile.Request{{ + NamespacedName: types.NamespacedName{ + Namespace: comp.Namespace, + Name: comp.Name, + }, + }} +} +func (r *ComponentParameterReconciler) reconcile(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) (ctrl.Result, error) { fetcherTask, err := prepareReconcileTask(reqCtx, r.Client, componentParameter) if err != nil { return intctrlutil.RequeueWithError(err, reqCtx.Log, errors.Wrap(err, "failed to get related object").Error()) @@ -111,6 +122,11 @@ func (r *ComponentParameterReconciler) reconcile(reqCtx intctrlutil.RequestCtx, if fetcherTask.ClusterComObj == nil || fetcherTask.ComponentObj == nil { return r.failWithInvalidComponent(componentParameter, reqCtx) } + tasks := generateReconcileTasks(reqCtx, componentParameter, fetcherTask.ComponentObj.Generation) + if len(tasks) == 0 { + reqCtx.Log.Info("nothing to reconcile") + return intctrlutil.Reconciled() + } taskCtx, err := newTaskContext(reqCtx.Ctx, r.Client, componentParameter, fetcherTask) if err != nil { diff --git a/controllers/parameters/componentparameter_controller_test.go b/controllers/parameters/componentparameter_controller_test.go index c419643f9eb..36e685febbf 100644 --- a/controllers/parameters/componentparameter_controller_test.go +++ b/controllers/parameters/componentparameter_controller_test.go @@ -20,16 +20,23 @@ along with this program. If not, see . package parameters import ( + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/parameters" "github.com/apecloud/kubeblocks/pkg/parameters/core" cfgutil "github.com/apecloud/kubeblocks/pkg/parameters/util" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("ComponentParameter Controller", func() { @@ -74,7 +81,11 @@ var _ = Describe("ComponentParameter Controller", func() { })).Should(Succeed()) By("mock the reconfigure done") - mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, configHash3) + mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, + waitRenderedConfigHash( + testCtx.DefaultNamespace, clusterName, defaultCompName, configSpecName, + "max_connections=1000", "gtid_mode=ON", + )) By("check component parameter status is updated to Finished") Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { @@ -84,5 +95,104 @@ var _ = Describe("ComponentParameter Controller", func() { g.Expect(status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) })).Should(Succeed()) }) + + It("should rerender config when the component changes", func() { + templateObj, clusterObj, compObj, _, _ := mockReconcileResource() + + cfgKey := client.ObjectKey{ + Namespace: testCtx.DefaultNamespace, + Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), + } + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { + g.Expect(cfg.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + + By("update the template to depend on the live component replicas") + Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(templateObj), func(tpl *corev1.ConfigMap) { + tpl.Data[testparameters.MysqlConfigFile] = strings.ReplaceAll( + tpl.Data[testparameters.MysqlConfigFile], + "server-id=1", + "server-id={{ $.component.replicas }}", + ) + })).Should(Succeed()) + + By("update the cluster and component replicas without touching ComponentParameter spec") + Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterObj), func(cluster *appsv1.Cluster) { + cluster.Spec.ComponentSpecs[0].Replicas = 2 + })).Should(Succeed()) + Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(compObj), func(comp *appsv1.Component) { + comp.Spec.Replicas = 2 + })).Should(Succeed()) + + configKey := client.ObjectKey{ + Namespace: testCtx.DefaultNamespace, + Name: core.GetComponentCfgName(clusterName, defaultCompName, configSpecName), + } + Eventually(testapps.CheckObj(&testCtx, configKey, func(g Gomega, cfg *corev1.ConfigMap) { + g.Expect(cfg.Data[testparameters.MysqlConfigFile]).Should(ContainSubstring("server-id=2")) + g.Expect(cfg.Annotations[constant.ParametersAppliedComponentGenerationKey]).ShouldNot(BeEmpty()) + })).Should(Succeed()) + }) + + It("should render both new PD and legacy PCR files in mixed mode", func() { + templateObj, _, compObj, _, _ := mockReconcileResource() + + cfgKey := client.ObjectKey{ + Namespace: testCtx.DefaultNamespace, + Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), + } + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { + g.Expect(cfg.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + + By("add a legacy-only file to the component template") + const legacyFile = "log.conf" + Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(templateObj), func(tpl *corev1.ConfigMap) { + tpl.Data[legacyFile] = "slow_query_log=1\n" + })).Should(Succeed()) + + By("create a legacy-only ParametersDefinition and ParamConfigRenderer binding") + legacyPD := testparameters.NewParametersDefinitionFactory("legacy-log-params"). + SetConfigFile(legacyFile). + Create(&testCtx). + GetObject() + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(legacyPD), func(obj *parametersv1alpha1.ParametersDefinition) { + obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + })()).Should(Succeed()) + + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: pdcrName + "-mixed"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: compObj.Spec.CompDef, + ServiceVersion: "8.0.30", + ParametersDefs: []string{legacyPD.Name}, + Configs: []parametersv1alpha1.ComponentConfigDescription{{ + Name: legacyFile, + TemplateName: configSpecName, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, + }, + }}, + }, + } + Expect(testCtx.CreateObj(testCtx.Ctx, pcr)).Should(Succeed()) + + By("touch the component to regenerate ComponentParameter from mixed sources") + Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(compObj), func(comp *appsv1.Component) { + if comp.Annotations == nil { + comp.Annotations = map[string]string{} + } + comp.Annotations["parameters.kubeblocks.io/mixed-mode-test"] = "true" + })).Should(Succeed()) + + configKey := client.ObjectKey{ + Namespace: testCtx.DefaultNamespace, + Name: core.GetComponentCfgName(clusterName, defaultCompName, configSpecName), + } + Eventually(testapps.CheckObj(&testCtx, configKey, func(g Gomega, cfg *corev1.ConfigMap) { + g.Expect(cfg.Data).Should(HaveKey(testparameters.MysqlConfigFile)) + g.Expect(cfg.Data).Should(HaveKeyWithValue(legacyFile, "slow_query_log=1\n")) + })).Should(Succeed()) + }) }) }) diff --git a/controllers/parameters/componentparameter_controller_utils.go b/controllers/parameters/componentparameter_controller_utils.go index 2ac1801a7fc..09bb50715a4 100644 --- a/controllers/parameters/componentparameter_controller_utils.go +++ b/controllers/parameters/componentparameter_controller_utils.go @@ -24,16 +24,13 @@ import ( "encoding/json" "fmt" "reflect" - "slices" "strconv" - "strings" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" @@ -57,7 +54,7 @@ type Task struct { type taskContext struct { componentParameter *parametersv1alpha1.ComponentParameter - configRender *parametersv1alpha1.ParamConfigRenderer + configDescs []parametersv1alpha1.ComponentConfigDescription ctx context.Context component *component.SynthesizedComponent paramsDefs []*parametersv1alpha1.ParametersDefinition @@ -74,39 +71,14 @@ func newTaskContext(ctx context.Context, cli client.Client, componentParameter * return nil, err } - configDefList := ¶metersv1alpha1.ParamConfigRendererList{} - if err := cli.List(ctx, configDefList); err != nil { + configDescs, paramsDefs, err := parameters.ResolveCmpdParametersDefs(ctx, cli, cmpd) + if err != nil { return nil, err } - slices.SortFunc(configDefList.Items, func(a, b parametersv1alpha1.ParamConfigRenderer) int { - return strings.Compare(b.Spec.ComponentDef, a.Spec.ComponentDef) - }) - - var paramsDefs []*parametersv1alpha1.ParametersDefinition - var configRender *parametersv1alpha1.ParamConfigRenderer - for i, item := range configDefList.Items { - if !component.PrefixOrRegexMatched(cmpd.Name, item.Spec.ComponentDef) { - continue - } - if item.Spec.ServiceVersion == "" || item.Spec.ServiceVersion == cmpd.Spec.ServiceVersion { - configRender = &configDefList.Items[i] - break - } - } - - if configRender != nil { - for _, paramsDef := range configRender.Spec.ParametersDefs { - var param = ¶metersv1alpha1.ParametersDefinition{} - if err := cli.Get(ctx, client.ObjectKey{Name: paramsDef}, param); err != nil { - return nil, err - } - paramsDefs = append(paramsDefs, param) - } - } return &taskContext{ctx: ctx, componentParameter: componentParameter, - configRender: configRender, + configDescs: configDescs, component: synthesizedComp, paramsDefs: paramsDefs, }, nil @@ -124,11 +96,12 @@ func buildTemplateVars(ctx context.Context, cli client.Reader, return nil } -func generateReconcileTasks(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) []Task { +func generateReconcileTasks(reqCtx intctrlutil.RequestCtx, + componentParameter *parametersv1alpha1.ComponentParameter, compGeneration int64) []Task { tasks := make([]Task, 0, len(componentParameter.Spec.ConfigItemDetails)) for _, item := range componentParameter.Spec.ConfigItemDetails { if status := fromItemStatus(reqCtx, &componentParameter.Status, item, componentParameter.GetGeneration()); status != nil { - tasks = append(tasks, newTask(item, status)) + tasks = append(tasks, newTask(item, status, compGeneration)) } } return tasks @@ -162,7 +135,8 @@ func isReconcileStatus(phase parametersv1alpha1.ParameterPhase) bool { phase != parametersv1alpha1.CDeletingPhase } -func newTask(item parametersv1alpha1.ConfigTemplateItemDetail, status *parametersv1alpha1.ConfigTemplateItemDetailStatus) Task { +func newTask(item parametersv1alpha1.ConfigTemplateItemDetail, + status *parametersv1alpha1.ConfigTemplateItemDetailStatus, compGeneration int64) Task { return Task{ Name: item.Name, Do: func(resource *Task, taskCtx *taskContext, revision string) error { @@ -177,7 +151,7 @@ func newTask(item parametersv1alpha1.ConfigTemplateItemDetail, status *parameter } // Do reconcile for config template configMap := resource.ConfigMapObj - switch parameters.GetUpdatedParametersReconciledPhase(configMap, item, status) { + switch parameters.GetUpdatedParametersReconciledPhase(configMap, item, status, compGeneration) { default: return syncStatus(configMap, status) case parametersv1alpha1.CInitPhase, @@ -198,7 +172,7 @@ func syncImpl(taskCtx *taskContext, status *parametersv1alpha1.ConfigTemplateItemDetailStatus, revision string, configMap *corev1.ConfigMap) (err error) { - if parameters.IsApplyUpdatedParameters(configMap, item) { + if parameters.IsApplyUpdatedParameters(configMap, item, fetcher.ComponentObj.Generation) { return syncStatus(configMap, status) } @@ -216,30 +190,18 @@ func syncImpl(taskCtx *taskContext, PodSpec: taskCtx.component.PodSpec, } - var baseConfig = configMap + var baseConfig *corev1.ConfigMap var updatedConfig *corev1.ConfigMap - if parameters.IsRerender(configMap, item) { - log.FromContext(taskCtx.ctx). - WithName("ParameterReconcileTask"). - WithValues("cluster", taskCtx.component.ClusterName, - "component", taskCtx.component.Name, - "parameterTpl", item.Name). - Info("rerender parameter template", - "appliedConfigMeta", resolveLastConfigMeta(configMap), - "revision", revision, - "configMeta", item, - ) - if baseConfig, err = parameters.RerenderParametersTemplate(reconcileCtx, item, taskCtx.configRender, taskCtx.paramsDefs); err != nil { - return failStatus(err) - } - updatedConfig = baseConfig + if baseConfig, err = parameters.RerenderParametersTemplate(reconcileCtx, item, taskCtx.configDescs, taskCtx.paramsDefs); err != nil { + return failStatus(err) } + updatedConfig = baseConfig if len(item.ConfigFileParams) != 0 { - if updatedConfig, err = parameters.ApplyParameters(item, baseConfig, taskCtx.configRender, taskCtx.paramsDefs); err != nil { + if updatedConfig, err = parameters.ApplyParameters(item, baseConfig, taskCtx.configDescs, taskCtx.paramsDefs); err != nil { return failStatus(err) } } - if err = mergeAndApplyConfig(fetcher.ResourceCtx, updatedConfig, configMap, fetcher.ComponentParameterObj, item, revision); err != nil { + if err = mergeAndApplyConfig(fetcher.ResourceCtx, updatedConfig, configMap, fetcher.ComponentParameterObj, item, fetcher.ComponentObj.Generation, revision); err != nil { return failStatus(err) } @@ -249,29 +211,16 @@ func syncImpl(taskCtx *taskContext, return nil } -func resolveLastConfigMeta(configMap *corev1.ConfigMap) any { - if configMap == nil || len(configMap.Annotations) == 0 { - return nil - } - return map[string]string{ - "revision": configMap.Annotations[constant.ConfigurationRevision], - "configMeta": configMap.Annotations[constant.ConfigAppliedVersionAnnotationKey], - } -} - -func mergeAndApplyConfig(resourceCtx *render.ResourceCtx, - expected *corev1.ConfigMap, - running *corev1.ConfigMap, - owner client.Object, - item parametersv1alpha1.ConfigTemplateItemDetail, - revision string) error { +func mergeAndApplyConfig(resourceCtx *render.ResourceCtx, expected, running *corev1.ConfigMap, owner client.Object, + item parametersv1alpha1.ConfigTemplateItemDetail, compGeneration int64, revision string) error { + fn := updateReconcileObject(item, owner, compGeneration, revision) switch { case expected == nil: // not update - return update(resourceCtx.Context, resourceCtx.Client, running, running, updateReconcileObject(item, owner, revision)) + return update(resourceCtx.Context, resourceCtx.Client, running, running, fn) case running == nil: // cm been deleted - return create(resourceCtx.Context, resourceCtx.Client, expected, updateReconcileObject(item, owner, revision)) + return create(resourceCtx.Context, resourceCtx.Client, expected, fn) default: - return update(resourceCtx.Context, resourceCtx.Client, running, running, mergedConfigmap(expected, updateReconcileObject(item, owner, revision))) + return update(resourceCtx.Context, resourceCtx.Client, running, running, mergedConfigmap(expected, fn)) } } @@ -307,8 +256,7 @@ func create(ctx context.Context, cli client.Client, expected *corev1.ConfigMap, } func updateReconcileObject(item parametersv1alpha1.ConfigTemplateItemDetail, - owner client.Object, - revision string) func(*corev1.ConfigMap) error { + owner client.Object, compGeneration int64, revision string) func(*corev1.ConfigMap) error { return func(cmObj *corev1.ConfigMap) error { if !controllerutil.ContainsFinalizer(cmObj, constant.ConfigFinalizerName) { controllerutil.AddFinalizer(cmObj, constant.ConfigFinalizerName) @@ -318,13 +266,12 @@ func updateReconcileObject(item parametersv1alpha1.ConfigTemplateItemDetail, return err } } - return updateConfigLabels(cmObj, item, revision) + return updateConfigLabels(cmObj, item, compGeneration, revision) } } func updateConfigLabels(obj *corev1.ConfigMap, - item parametersv1alpha1.ConfigTemplateItemDetail, - revision string) error { + item parametersv1alpha1.ConfigTemplateItemDetail, compGeneration int64, revision string) error { if obj.Annotations == nil { obj.Annotations = make(map[string]string) } @@ -333,6 +280,7 @@ func updateConfigLabels(obj *corev1.ConfigMap, return err } obj.Annotations[constant.ConfigAppliedVersionAnnotationKey] = string(b) + obj.Annotations[constant.ParametersAppliedComponentGenerationKey] = strconv.FormatInt(compGeneration, 10) obj.Annotations[constant.ConfigurationRevision] = revision if obj.Labels == nil { diff --git a/controllers/parameters/legacy_paramconfigrenderer_controller.go b/controllers/parameters/legacy_paramconfigrenderer_controller.go new file mode 100644 index 00000000000..a9dd4e7fb23 --- /dev/null +++ b/controllers/parameters/legacy_paramconfigrenderer_controller.go @@ -0,0 +1,73 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package parameters + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +// LegacyParamConfigRendererReconciler only keeps legacy ParamConfigRenderer +// finalizer lifecycle working during the compatibility window. +type LegacyParamConfigRendererReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=paramconfigrenderers,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=paramconfigrenderers/finalizers,verbs=update + +func (r *LegacyParamConfigRendererReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + reqCtx := intctrlutil.RequestCtx{ + Ctx: ctx, + Req: req, + Recorder: r.Recorder, + Log: log.FromContext(ctx). + WithName("LegacyParamConfigRendererReconciler"). + WithValues("ParamConfigRenderer", req.Name), + } + + pcr := ¶metersv1alpha1.ParamConfigRenderer{} + if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, pcr); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") + } + + res, err := intctrlutil.HandleCRDeletion(reqCtx, r, pcr, constant.ConfigFinalizerName, nil) + if res != nil { + return *res, err + } + return intctrlutil.Reconciled() +} + +func (r *LegacyParamConfigRendererReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(¶metersv1alpha1.ParamConfigRenderer{}). + Complete(r) +} diff --git a/controllers/parameters/legacy_paramconfigrenderer_controller_test.go b/controllers/parameters/legacy_paramconfigrenderer_controller_test.go new file mode 100644 index 00000000000..f245bc8615a --- /dev/null +++ b/controllers/parameters/legacy_paramconfigrenderer_controller_test.go @@ -0,0 +1,52 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package parameters + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "sigs.k8s.io/controller-runtime/pkg/client" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" +) + +var _ = Describe("Legacy ParamConfigRenderer Controller", func() { + BeforeEach(cleanEnv) + + AfterEach(cleanEnv) + + It("should register and remove the legacy finalizer on delete", func() { + pcr := ¶metersv1alpha1.ParamConfigRenderer{} + pcr.Name = pdcrName + "-finalizer" + pcr.Spec.ComponentDef = compDefName + Expect(testCtx.CreateObj(testCtx.Ctx, pcr)).Should(Succeed()) + + key := client.ObjectKeyFromObject(pcr) + Eventually(testapps.CheckObj(&testCtx, key, func(g Gomega, fetched *parametersv1alpha1.ParamConfigRenderer) { + g.Expect(fetched.Finalizers).Should(ContainElement(constant.ConfigFinalizerName)) + })).Should(Succeed()) + + Expect(testCtx.Cli.Delete(testCtx.Ctx, pcr)).Should(Succeed()) + Eventually(testapps.CheckObjExists(&testCtx, key, ¶metersv1alpha1.ParamConfigRenderer{}, false)).Should(Succeed()) + }) +}) diff --git a/controllers/parameters/paramconfigrenderer_controller.go b/controllers/parameters/paramconfigrenderer_controller.go deleted file mode 100644 index 2b3d932885e..00000000000 --- a/controllers/parameters/paramconfigrenderer_controller.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package parameters - -import ( - "context" - "fmt" - "slices" - "strings" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - "github.com/apecloud/kubeblocks/pkg/generics" - "github.com/apecloud/kubeblocks/pkg/parameters" -) - -// ParameterDrivenConfigRenderReconciler reconciles a ParamConfigRenderer object -type ParameterDrivenConfigRenderReconciler struct { - client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder -} - -// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=paramconfigrenderers,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=paramconfigrenderers/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=paramconfigrenderers/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile -func (r *ParameterDrivenConfigRenderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - reqCtx := intctrlutil.RequestCtx{ - Ctx: ctx, - Req: req, - Recorder: r.Recorder, - Log: log.FromContext(ctx). - WithName("ParameterDrivenConfigRenderReconciler"). - WithValues("ParamConfigRenderer", req.Name), - } - - parameterTemplate := ¶metersv1alpha1.ParamConfigRenderer{} - if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, parameterTemplate); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") - } - - res, err := intctrlutil.HandleCRDeletion(reqCtx, r, parameterTemplate, constant.ConfigFinalizerName, nil) - if res != nil { - return *res, err - } - return r.reconcile(reqCtx, parameterTemplate) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ParameterDrivenConfigRenderReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(¶metersv1alpha1.ParamConfigRenderer{}). - Complete(r) -} - -func (r *ParameterDrivenConfigRenderReconciler) reconcile(reqCtx intctrlutil.RequestCtx, parameterTemplate *parametersv1alpha1.ParamConfigRenderer) (ctrl.Result, error) { - if parameters.ParametersDrivenConfigRenderTerminalPhases(parameterTemplate.Status, parameterTemplate.Generation) { - return intctrlutil.Reconciled() - } - cmpd, err := r.resolveComponentDefinition(reqCtx, parameterTemplate) - if err != nil { - return intctrlutil.RequeueWithError(err, reqCtx.Log, "") - } - if err := fillParameterTemplate(reqCtx, r.Client, parameterTemplate, cmpd); err != nil { - return intctrlutil.RequeueWithError(err, reqCtx.Log, "") - } - if err := r.validate(reqCtx, r.Client, ¶meterTemplate.Spec, cmpd); err != nil { - if err2 := r.unavailable(reqCtx.Ctx, r.Client, parameterTemplate, err); err2 != nil { - return intctrlutil.RequeueWithError(err2, reqCtx.Log, "") - } - return intctrlutil.RequeueWithError(err, reqCtx.Log, "") - } - if err := r.available(reqCtx.Ctx, r.Client, parameterTemplate); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") - } - - intctrlutil.RecordCreatedEvent(r.Recorder, parameterTemplate) - return intctrlutil.Reconciled() -} - -func (r *ParameterDrivenConfigRenderReconciler) resolveComponentDefinition(reqCtx intctrlutil.RequestCtx, pcr *parametersv1alpha1.ParamConfigRenderer) (*appsv1.ComponentDefinition, error) { - componentDefPattern := pcr.Spec.ComponentDef - if err := component.ValidateDefNameRegexp(componentDefPattern); err != nil { - return nil, fmt.Errorf("invalid componentDef pattern %q: %w", componentDefPattern, err) - } - - cmpd := &appsv1.ComponentDefinition{} - if err := r.Get(reqCtx.Ctx, client.ObjectKey{Name: componentDefPattern}, cmpd); err == nil { - return cmpd, nil - } - - compDefList := &appsv1.ComponentDefinitionList{} - if err := r.List(reqCtx.Ctx, compDefList); err != nil { - return nil, err - } - slices.SortFunc(compDefList.Items, func(a, b appsv1.ComponentDefinition) int { - return strings.Compare(b.Name, a.Name) - }) - - for i, item := range compDefList.Items { - if !component.PrefixOrRegexMatched(item.Name, componentDefPattern) { - continue - } - if pcr.Spec.ServiceVersion == "" || pcr.Spec.ServiceVersion == item.Spec.ServiceVersion { - return &compDefList.Items[i], nil - } - } - return nil, fmt.Errorf("no ComponentDefinition found matching pattern %q", componentDefPattern) -} - -func (r *ParameterDrivenConfigRenderReconciler) validate(ctx intctrlutil.RequestCtx, cli client.Client, parameterTemplate *parametersv1alpha1.ParamConfigRendererSpec, cmpd *appsv1.ComponentDefinition) error { - if err := validateParametersDefs(ctx, cli, parameterTemplate.ParametersDefs); err != nil { - return err - } - if err := validateParametersConfigs(parameterTemplate.Configs, cmpd.Spec.Configs); err != nil { - return err - } - return nil -} - -func (r *ParameterDrivenConfigRenderReconciler) available(ctx context.Context, cli client.Client, parameterTemplate *parametersv1alpha1.ParamConfigRenderer) error { - return r.status(ctx, cli, parameterTemplate, parametersv1alpha1.PDAvailablePhase, nil) -} - -func (r *ParameterDrivenConfigRenderReconciler) unavailable(ctx context.Context, cli client.Client, parameterTemplate *parametersv1alpha1.ParamConfigRenderer, err error) error { - return r.status(ctx, cli, parameterTemplate, parametersv1alpha1.PDUnavailablePhase, err) -} - -func (r *ParameterDrivenConfigRenderReconciler) status(ctx context.Context, cli client.Client, parameterRender *parametersv1alpha1.ParamConfigRenderer, phase parametersv1alpha1.ParametersDescPhase, err error) error { - patch := client.MergeFrom(parameterRender.DeepCopy()) - parameterRender.Status.ObservedGeneration = parameterRender.Generation - parameterRender.Status.Phase = phase - parameterRender.Status.Message = "" - if err != nil { - parameterRender.Status.Message = err.Error() - } - return cli.Status().Patch(ctx, parameterRender, patch) -} - -func fillParameterTemplate(reqCtx intctrlutil.RequestCtx, cli client.Client, template *parametersv1alpha1.ParamConfigRenderer, cmpd *appsv1.ComponentDefinition) (err error) { - var tpls map[string]*corev1.ConfigMap - - match := func(spec parametersv1alpha1.ComponentConfigDescription) bool { - return spec.TemplateName == "" - } - resolveConfigTemplate := func(config string) string { - for name, configTemplate := range tpls { - if _, ok := configTemplate.Data[config]; ok { - return name - } - } - return "" - } - - if generics.CountFunc(template.Spec.Configs, match) == 0 { - return nil - } - if tpls, err = parameters.ResolveComponentTemplate(reqCtx.Ctx, cli, cmpd); err != nil { - return err - } - deepCopy := template.DeepCopy() - for i, config := range deepCopy.Spec.Configs { - if tplName := resolveConfigTemplate(config.Name); tplName != "" { - deepCopy.Spec.Configs[i].TemplateName = tplName - } - } - return cli.Patch(reqCtx.Ctx, deepCopy, client.MergeFrom(template)) -} - -func validateParametersConfigs(configs []parametersv1alpha1.ComponentConfigDescription, templates []appsv1.ComponentFileTemplate) error { - for _, config := range configs { - match := func(spec appsv1.ComponentFileTemplate) bool { - return config.TemplateName == spec.Name - } - if len(generics.FindFunc(templates, match)) == 0 { - return fmt.Errorf("config template[%s] not found in component definition", config.TemplateName) - } - } - return nil -} - -func validateParametersDefs(reqCtx intctrlutil.RequestCtx, cli client.Client, paramsDefs []string) error { - paramsDefObjs := make(map[string]*parametersv1alpha1.ParametersDefinition, len(paramsDefs)) - for _, paramsDef := range paramsDefs { - obj := ¶metersv1alpha1.ParametersDefinition{} - if err := cli.Get(reqCtx.Ctx, client.ObjectKey{Name: paramsDef}, obj); err != nil { - return err - } - if def, ok := paramsDefObjs[obj.Spec.FileName]; ok { - return fmt.Errorf("config file[%s] has been defined in other parametersdefinition[%s]", obj.Spec.FileName, def.Name) - } - } - return nil -} diff --git a/controllers/parameters/paramconfigrenderer_controller_test.go b/controllers/parameters/paramconfigrenderer_controller_test.go deleted file mode 100644 index a2254158572..00000000000 --- a/controllers/parameters/paramconfigrenderer_controller_test.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package parameters - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" - testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" -) - -var _ = Describe("ParamConfigRenderer Controller", func() { - - BeforeEach(cleanEnv) - - AfterEach(cleanEnv) - - initPDCRTest := func() { - By("Create a config template obj") - configmap := testparameters.NewComponentTemplateFactory(configSpecName, testCtx.DefaultNamespace). - Create(&testCtx). - GetObject() - - By("Create a parameters definition obj") - paramsDef := testparameters.NewParametersDefinitionFactory(paramsDefName). - SetReloadAction(testparameters.WithNoneAction()). - Create(&testCtx). - GetObject() - Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(paramsDef), func(obj *parametersv1alpha1.ParametersDefinition) { - obj.Status.Phase = parametersv1alpha1.PDAvailablePhase - })()).Should(Succeed()) - - By("Create a component definition obj and mock to available") - compDefObj := testapps.NewComponentDefinitionFactory(compDefName). - SetDefaultSpec(). - AddConfigTemplate(configSpecName, configmap.Name, testCtx.DefaultNamespace, configVolumeName, true). - Create(&testCtx). - GetObject() - Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { - obj.Status.Phase = appsv1.AvailablePhase - })()).Should(Succeed()) - - } - - Context("pdcr", func() { - It("normal test", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDefName). - SetComponentDefinition(compDefName). - SetTemplateName(configSpecName). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).Should(Succeed()) - - }) - - It("invalid config template", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDefName). - SetComponentDefinition(compDefName). - SetTemplateName(configSpecName). - SetConfigDescription("test", "not_exist_template", parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).ShouldNot(Succeed()) - }) - - It("invalid parametersdefinitions", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs("not_exist_pd"). - SetComponentDefinition(compDefName). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).ShouldNot(Succeed()) - }) - - It("invalid cmpd", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDefName). - SetComponentDefinition("not_exist_cmpd"). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).ShouldNot(Succeed()) - }) - - It("regex componentDef matching", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDefName). - SetComponentDefinition("^" + compDefName + ".*"). - SetTemplateName(configSpecName). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).Should(Succeed()) - }) - - It("prefix componentDef matching", func() { - initPDCRTest() - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDefName). - SetComponentDefinition(compDefName). - SetTemplateName(configSpecName). - Create(&testCtx). - GetObject() - - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pdcr), func(g Gomega, pdcr *parametersv1alpha1.ParamConfigRenderer) { - g.Expect(pdcr.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) - })).Should(Succeed()) - }) - }) -}) diff --git a/controllers/parameters/parameter_controller_test.go b/controllers/parameters/parameter_controller_test.go index b53c09af6c3..3f0139cdc74 100644 --- a/controllers/parameters/parameter_controller_test.go +++ b/controllers/parameters/parameter_controller_test.go @@ -20,11 +20,13 @@ along with this program. If not, see . package parameters import ( + "strings" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -79,7 +81,11 @@ var _ = Describe("Parameter Controller", func() { GetObject() By("mock the reconfigure done") - mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, configHash1) + mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, + waitRenderedConfigHash( + testCtx.DefaultNamespace, synthesizedComp.ClusterName, synthesizedComp.Name, configSpecName, + "innodb_buffer_pool_size=1024M", "max_connections=100", + )) By("check component parameter status") Eventually(testapps.CheckObj(&testCtx, compParamKey, func(g Gomega, compParameter *parametersv1alpha1.ComponentParameter) { @@ -109,7 +115,11 @@ var _ = Describe("Parameter Controller", func() { GetObject() By("mock the reconfigure done") - mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, configHash2) + mockReconfigureDone(itsObj.Namespace, itsObj.Name, configSpecName, + waitRenderedConfigHash( + testCtx.DefaultNamespace, synthesizedComp.ClusterName, synthesizedComp.Name, configSpecName, + "max_connections=2000", "gtid_mode=OFF", + )) By("check parameter status") Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { @@ -140,6 +150,53 @@ var _ = Describe("Parameter Controller", func() { g.Expect(parameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CMergeFailedPhase)) })).Should(Succeed()) }) + + It("rerenders the base template before applying parameter updates", func() { + prepareTestEnv() + + By("update the template to depend on the current component replicas") + templateKey := client.ObjectKey{Namespace: testCtx.DefaultNamespace, Name: configSpecName} + Eventually(testapps.GetAndChangeObj(&testCtx, templateKey, func(tpl *corev1.ConfigMap) { + tpl.Data[testparameters.MysqlConfigFile] = strings.ReplaceAll( + tpl.Data[testparameters.MysqlConfigFile], + "server-id=1", + "server-id={{ $.component.replicas }}", + ) + })).Should(Succeed()) + + By("change the component replicas without touching rerender payload fields") + componentKey := client.ObjectKey{Namespace: testCtx.DefaultNamespace, Name: synthesizedComp.FullCompName} + Eventually(testapps.GetAndChangeObj(&testCtx, componentKey, func(comp *appsv1.Component) { + comp.Spec.Replicas = 2 + })).Should(Succeed()) + + clusterKey := client.ObjectKey{Namespace: testCtx.DefaultNamespace, Name: synthesizedComp.ClusterName} + Eventually(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1.Cluster) { + cluster.Spec.ComponentSpecs[0].Replicas = 2 + })).Should(Succeed()) + + By("submit a parameter update to enter the apply flow") + key := testapps.GetRandomizedKey(synthesizedComp.Namespace, synthesizedComp.FullCompName) + parameterObj := testparameters.NewParameterFactory(key.Name, key.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name). + AddParameters("gtid_mode", "ON"). + Create(&testCtx). + GetObject() + + cfgKey := client.ObjectKey{ + Namespace: testCtx.DefaultNamespace, + Name: configcore.GetComponentCfgName(synthesizedComp.ClusterName, synthesizedComp.Name, configSpecName), + } + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *corev1.ConfigMap) { + config := cfg.Data[testparameters.MysqlConfigFile] + g.Expect(config).Should(ContainSubstring("server-id=2")) + g.Expect(config).Should(ContainSubstring("gtid_mode=ON")) + }), time.Second*10).Should(Succeed()) + + By("check parameter status eventually finishes") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { + g.Expect(parameter.Status.Phase).ShouldNot(BeEquivalentTo(parametersv1alpha1.CMergeFailedPhase)) + }), time.Second*10).Should(Succeed()) + }) }) Context("custom template update", func() { @@ -229,7 +286,15 @@ var _ = Describe("Parameter Controller", func() { for _, spec := range shardingCompSpecList { By("mock the reconfigure done: " + spec.Name) - mockReconfigureDone(synthesizedComp.Namespace, constant.GenerateWorkloadNamePattern(synthesizedComp.ClusterName, spec.Name), configSpecName, configHash1) + mockReconfigureDone( + synthesizedComp.Namespace, + constant.GenerateWorkloadNamePattern(synthesizedComp.ClusterName, spec.Name), + configSpecName, + waitRenderedConfigHash( + testCtx.DefaultNamespace, synthesizedComp.ClusterName, spec.Name, configSpecName, + "innodb_buffer_pool_size=1024M", "max_connections=100", + ), + ) By("check component parameter status: " + spec.Name) cpkey := types.NamespacedName{ diff --git a/controllers/parameters/parameter_controller_utils.go b/controllers/parameters/parameter_controller_utils.go index 7b2cfaa0db4..b15ba928ace 100644 --- a/controllers/parameters/parameter_controller_utils.go +++ b/controllers/parameters/parameter_controller_utils.go @@ -72,7 +72,7 @@ func syncReconfiguringPhase(rctx *reconcileContext, status *parametersv1alpha1.C rctx.Log.Info("component status or spec not found", "component", parameterStatus.Name, "template", parameterStatus.Name) continue } - parameterStatus.Phase = parameters.GetUpdatedParametersReconciledPhase(cm, *compSpec, compStatus) + parameterStatus.Phase = parameters.GetUpdatedParametersReconciledPhase(cm, *compSpec, compStatus, 0) if finished { finished = parameters.IsParameterFinished(parameterStatus.Phase) } @@ -154,22 +154,22 @@ func updateCustomTemplates(rctx *reconcileContext, parameter *parametersv1alpha1 func classifyParameters(updatedParameters parametersv1alpha1.ComponentParameters, configmaps map[string]*corev1.ConfigMap) func(*reconcileContext, *parametersv1alpha1.Parameter) error { return func(rctx *reconcileContext, parameter *parametersv1alpha1.Parameter) error { - if !parameters.HasValidParameterTemplate(rctx.configRender) { + if !parameters.HasValidParameterTemplate(rctx.configDescs) { return intctrlutil.NewFatalError(fmt.Sprintf("component[%s] does not support reconfigure", rctx.ComponentName)) } classParameters, err := parameters.ClassifyComponentParameters(updatedParameters, flatten(rctx.parametersDefs), rctx.ComponentDefObj.Spec.Configs, configmaps, - rctx.configRender, + rctx.configDescs, ) if err != nil { return intctrlutil.NewFatalError(err.Error()) } for tpl, m := range classParameters { - configDescs := parameters.GetComponentConfigDescriptions(&rctx.configRender.Spec, tpl) + configDescs := parameters.GetComponentConfigDescriptions(rctx.configDescs, tpl) if len(configDescs) == 0 { - return intctrlutil.NewFatalError(fmt.Sprintf("not found config description from pdcr: %s", tpl)) + return intctrlutil.NewFatalError(fmt.Sprintf("not found config description for template: %s", tpl)) } if err := validateComponentParameter(toArray(rctx.parametersDefs), configDescs, m); err != nil { return intctrlutil.NewFatalError(err.Error()) diff --git a/controllers/parameters/parametersdefinition_controller_test.go b/controllers/parameters/parametersdefinition_controller_test.go index 0bdf0daff12..58309a15bec 100644 --- a/controllers/parameters/parametersdefinition_controller_test.go +++ b/controllers/parameters/parametersdefinition_controller_test.go @@ -69,7 +69,6 @@ var _ = Describe("ConfigConstraint Controller", func() { parametersDef := testparameters.NewParametersDefinitionFactory("mysql-parameters-8.0"). StaticParameters([]string{"automatic_sp_privileges"}). DynamicParameters([]string{"innodb_autoinc_lock_mode"}). - SetReloadAction(testparameters.WithNoneAction()). Schema(` #MysqlParameter: { // [OFF|ON] default ON diff --git a/controllers/parameters/reconfigure_controller.go b/controllers/parameters/reconfigure_controller.go index 4449b332196..47611b136c2 100644 --- a/controllers/parameters/reconfigure_controller.go +++ b/controllers/parameters/reconfigure_controller.go @@ -207,7 +207,7 @@ func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *c return intctrlutil.Reconciled() } - configPatch, forceRestart, err := createConfigPatch(configMap, rctx.configRender, rctx.parametersDefs) + configPatch, forceRestart, err := createConfigPatch(configMap, rctx.configDescs, rctx.parametersDefs) if err != nil { return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) } @@ -244,7 +244,7 @@ func (r *ReconfigureReconciler) buildReconfigureTasks(templateSpec *appsv1.Compo rctx *reconcileContext, patch *core.ConfigPatchInfo, forceRestart bool) ([]reconfigure.Task, error) { // If the patch or ConfigRender is nil, return a single restart task. - if patch == nil || rctx.configRender == nil { + if patch == nil || len(rctx.configDescs) == 0 { return []reconfigure.Task{r.buildRestartTask(templateSpec, rctx)}, nil } @@ -260,7 +260,7 @@ func (r *ReconfigureReconciler) buildReconfigureTasks(templateSpec *appsv1.Compo if !ok || pd.Spec.ReloadAction == nil { continue } - configFormat := parameters.GetComponentConfigDescription(&rctx.configRender.Spec, key) + configFormat := parameters.GetComponentConfigDescription(rctx.configDescs, key) if configFormat == nil || configFormat.FileFormatConfig == nil { continue } @@ -463,8 +463,8 @@ func computeTargetConfigHash(reqCtx *intctrlutil.RequestCtx, data map[string]str return &hash } -func createConfigPatch(cfg *corev1.ConfigMap, configRender *parametersv1alpha1.ParamConfigRenderer, paramsDefs map[string]*parametersv1alpha1.ParametersDefinition) (*core.ConfigPatchInfo, bool, error) { - if configRender == nil || len(configRender.Spec.Configs) == 0 { +func createConfigPatch(cfg *corev1.ConfigMap, configDescs []parametersv1alpha1.ComponentConfigDescription, paramsDefs map[string]*parametersv1alpha1.ParametersDefinition) (*core.ConfigPatchInfo, bool, error) { + if len(configDescs) == 0 { return nil, true, nil } lastConfig, err := getLastVersionConfig(cfg) @@ -472,7 +472,7 @@ func createConfigPatch(cfg *corev1.ConfigMap, configRender *parametersv1alpha1.P return nil, false, core.WrapError(err, "failed to get last version data. config[%v]", client.ObjectKeyFromObject(cfg)) } - patch, restart, err := core.CreateConfigPatch(lastConfig, cfg.Data, configRender.Spec, true) + patch, restart, err := core.CreateConfigPatch(lastConfig, cfg.Data, configDescs, true) if err != nil { return nil, false, err } diff --git a/controllers/parameters/reconfigure_controller_test.go b/controllers/parameters/reconfigure_controller_test.go index 9d431f9770e..81a17d874c4 100644 --- a/controllers/parameters/reconfigure_controller_test.go +++ b/controllers/parameters/reconfigure_controller_test.go @@ -106,6 +106,11 @@ var _ = Describe("Reconfigure Controller", func() { Create(&testCtx). GetObject() + expectedHash := waitRenderedConfigHash( + testCtx.DefaultNamespace, synthesizedComp.ClusterName, synthesizedComp.Name, configSpecName, + "innodb_buffer_pool_size=1024M", "max_connections=100", + ) + By("verify changes submit to cluster") Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1.Cluster) { for _, comp := range cluster.Spec.ComponentSpecs { @@ -114,7 +119,7 @@ var _ = Describe("Reconfigure Controller", func() { // g.Expect(config.Variables).Should(HaveKeyWithValue("max_connections", "100")) g.Expect(config.Variables).Should(BeNil()) g.Expect(config.ConfigHash).ShouldNot(BeNil()) - g.Expect(*config.ConfigHash).Should(Equal(configHash1)) + g.Expect(*config.ConfigHash).Should(Equal(expectedHash)) g.Expect(config.Restart).ShouldNot(BeNil()) g.Expect(*config.Restart).Should(BeTrue()) g.Expect(config.Reconfigure).Should(BeNil()) @@ -141,6 +146,11 @@ var _ = Describe("Reconfigure Controller", func() { Create(&testCtx). GetObject() + expectedHash := waitRenderedConfigHash( + testCtx.DefaultNamespace, synthesizedComp.ClusterName, synthesizedComp.Name, configSpecName, + "innodb_buffer_pool_size=1024M", "max_connections=100", + ) + By("verify changes submit to cluster") Eventually(testapps.CheckObj(&testCtx, clusterKey, func(g Gomega, cluster *appsv1.Cluster) { for _, comp := range cluster.Spec.ComponentSpecs { @@ -149,7 +159,7 @@ var _ = Describe("Reconfigure Controller", func() { // g.Expect(config.Variables).Should(HaveKeyWithValue("max_connections", "100")) g.Expect(config.Variables).Should(BeNil()) g.Expect(config.ConfigHash).ShouldNot(BeNil()) - g.Expect(*config.ConfigHash).Should(Equal(configHash1)) + g.Expect(*config.ConfigHash).Should(Equal(expectedHash)) g.Expect(config.Restart).ShouldNot(BeNil()) g.Expect(*config.Restart).Should(BeTrue()) g.Expect(config.Reconfigure).Should(BeNil()) diff --git a/controllers/parameters/suite_test.go b/controllers/parameters/suite_test.go index ac9b1dddcd3..ec9bafabd73 100644 --- a/controllers/parameters/suite_test.go +++ b/controllers/parameters/suite_test.go @@ -131,13 +131,6 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - err = (&ParameterDrivenConfigRenderReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("parameter-template-controller"), - }).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - err = (&ParametersDefinitionReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), @@ -159,6 +152,13 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&LegacyParamConfigRendererReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("legacy-param-config-renderer-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + err = (&ParameterTemplateExtensionReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), diff --git a/controllers/parameters/utils.go b/controllers/parameters/utils.go index f0fcc086ebd..9b8defceac9 100644 --- a/controllers/parameters/utils.go +++ b/controllers/parameters/utils.go @@ -38,7 +38,7 @@ type reconcileContext struct { configMap *corev1.ConfigMap its *workloads.InstanceSet - configRender *parametersv1alpha1.ParamConfigRenderer + configDescs []parametersv1alpha1.ComponentConfigDescription parametersDefs map[string]*parametersv1alpha1.ParametersDefinition } @@ -79,7 +79,7 @@ func (c *reconcileContext) workload() *reconcileContext { func (c *reconcileContext) parametersDefinitions() *reconcileContext { return c.Wrap(func() (err error) { - configRender, paramsDefs, err := parameters.ResolveCmpdParametersDefs(c.Context, c.Client, c.ComponentDefObj) + configDescs, paramsDefs, err := parameters.ResolveCmpdParametersDefs(c.Context, c.Client, c.ComponentDefObj) if err != nil { return err } @@ -88,7 +88,7 @@ func (c *reconcileContext) parametersDefinitions() *reconcileContext { for _, paramsDef := range paramsDefs { paramsDefMap[paramsDef.Spec.FileName] = paramsDef } - c.configRender = configRender + c.configDescs = configDescs c.parametersDefs = paramsDefMap return nil }) diff --git a/controllers/parameters/utils_test.go b/controllers/parameters/utils_test.go index 3d13233d60a..c64fb217e71 100644 --- a/controllers/parameters/utils_test.go +++ b/controllers/parameters/utils_test.go @@ -37,6 +37,8 @@ import ( "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/generics" + "github.com/apecloud/kubeblocks/pkg/parameters" + parameterscore "github.com/apecloud/kubeblocks/pkg/parameters/core" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" "github.com/apecloud/kubeblocks/test/testdata" @@ -52,7 +54,6 @@ const ( cmName = "mysql-tree-node-template-8.0" paramsDefName = "mysql-params-def" pdcrName = "config-test-pdcr" - envTestFileKey = "env_test" ) func mockSchemaData() string { @@ -73,13 +74,11 @@ func mockConfigResource() (*corev1.ConfigMap, *parametersv1alpha1.ParametersDefi constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType, ). AddAnnotations(constant.ConfigurationRevision, "1"). - AddConfigFile(envTestFileKey, "abcde=1234"). Create(&testCtx). GetObject() By("Create a parameters definition obj") paramsdef := testparameters.NewParametersDefinitionFactory(paramsDefName). - SetReloadAction(testparameters.WithNoneAction()). Schema(mockSchemaData()). Create(&testCtx). GetObject() @@ -103,16 +102,27 @@ func mockReconcileResource() (*corev1.ConfigMap, *appsv1.Cluster, *appsv1.Compon Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { obj.Status.Phase = appsv1.AvailablePhase })()).Should(Succeed()) - - pdcr := testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDef.GetName()). - SetComponentDefinition(compDefObj.GetName()). - SetTemplateName(configSpecName). - Create(&testCtx). - GetObject() - Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(pdcr), func(obj *parametersv1alpha1.ParamConfigRenderer) { - obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(paramsDef), func(obj *parametersv1alpha1.ParametersDefinition) { + obj.Spec.ComponentDef = compDefObj.GetName() + obj.Spec.TemplateName = configSpecName + obj.Spec.FileFormatConfig = ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{SectionName: "mysqld"}, + }, + } })()).Should(Succeed()) + By("wait until the current cache can resolve parameter bindings for the component definition") + Eventually(func(g Gomega) { + cmpd := &appsv1.ComponentDefinition{} + g.Expect(testCtx.Cli.Get(testCtx.Ctx, client.ObjectKeyFromObject(compDefObj), cmpd)).Should(Succeed()) + configDescs, paramsDefs, err := parameters.ResolveCmpdParametersDefs(testCtx.Ctx, testCtx.Cli, cmpd) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(configDescs).Should(HaveLen(1)) + g.Expect(paramsDefs).Should(HaveLen(1)) + g.Expect(configDescs[0].TemplateName).Should(BeEquivalentTo(configSpecName)) + g.Expect(paramsDefs[0].Name).Should(BeEquivalentTo(paramsDef.Name)) + }).Should(Succeed()) By("Creating a cluster") clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, ""). @@ -174,12 +184,6 @@ func mockCreateITSObject(namespace, name, clusterName, compName string) *workloa return itsObj } -const ( - configHash1 = "6c5b4466f" // innodb_buffer_pool_size=1024M && max_connections=100 - configHash2 = "5b46b78c8d" // max_connections=2000 && gtid_mode=OFF - configHash3 = "8665bf6888" // max_connections=1000 && gtid_mode=ON -) - func mockReconfigureDone(namespace, itsName, configName, configHash string) { itsKey := client.ObjectKey{ Namespace: namespace, @@ -201,6 +205,25 @@ func mockReconfigureDone(namespace, itsName, configName, configHash string) { })()).Should(Succeed()) } +func waitRenderedConfigHash(namespace, clusterName, componentName, configName string, substrings ...string) string { + cfgKey := client.ObjectKey{ + Namespace: namespace, + Name: parameterscore.GetComponentCfgName(clusterName, componentName, configName), + } + var configHash string + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *corev1.ConfigMap) { + content := cfg.Data[testparameters.MysqlConfigFile] + for _, substring := range substrings { + g.Expect(content).Should(ContainSubstring(substring)) + } + hash := computeTargetConfigHash(nil, cfg.Data) + g.Expect(hash).ShouldNot(BeNil()) + g.Expect(*hash).ShouldNot(BeEmpty()) + configHash = *hash + })).Should(Succeed()) + return configHash +} + func cleanEnv() { // must wait till resources deleted and no longer existed before the testcases start, // otherwise if later it needs to create some new resource objects with the same name, @@ -216,7 +239,7 @@ func cleanEnv() { ml := client.HasLabels{testCtx.TestObjLabelKey} // non-namespaced testapps.ClearResources(&testCtx, generics.ParametersDefinitionSignature, ml) - testapps.ClearResources(&testCtx, generics.ParamConfigRendererSignature, ml) + testapps.ClearResources(&testCtx, generics.ParamConfigRendererSignature) testapps.ClearResources(&testCtx, generics.ComponentDefinitionSignature, ml) // namespaced testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ComponentSignature, true, inNS, ml) diff --git a/deploy/helm/config/rbac/role.yaml b/deploy/helm/config/rbac/role.yaml index 7052748737f..5963d258fc2 100644 --- a/deploy/helm/config/rbac/role.yaml +++ b/deploy/helm/config/rbac/role.yaml @@ -855,8 +855,6 @@ rules: resources: - paramconfigrenderers verbs: - - create - - delete - get - list - patch @@ -868,14 +866,6 @@ rules: - paramconfigrenderers/finalizers verbs: - update -- apiGroups: - - parameters.kubeblocks.io - resources: - - paramconfigrenderers/status - verbs: - - get - - patch - - update - apiGroups: - parameters.kubeblocks.io resources: diff --git a/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml b/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml index bca11468482..ebc4821496a 100644 --- a/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml +++ b/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml @@ -652,12 +652,7 @@ spec: pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string payload: - description: |- - External controllers can trigger a configuration rerender by modifying this field. - - - Note: Currently, the `payload` field is opaque and its content is not interpreted by the system. - Modifying this field will cause a rerender, regardless of the specific content of this field. + description: 'Deprecated: retained for API compatibility only.' type: object x-kubernetes-preserve-unknown-fields: true userConfigTemplates: diff --git a/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml b/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml index 6f93a3edd1f..d10d9fe015f 100644 --- a/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml +++ b/deploy/helm/crds/parameters.kubeblocks.io_paramconfigrenderers.yaml @@ -31,11 +31,16 @@ spec: - jsonPath: .metadata.creationTimestamp name: AGE type: date + deprecated: true + deprecationWarning: This CRD has been deprecated since 1.2.0 name: v1alpha1 schema: openAPIV3Schema: - description: ParamConfigRenderer is the Schema for the paramconfigrenderers - API + description: |- + Deprecated: retained for API compatibility only. + + + # ParamConfigRenderer is the Schema for the paramconfigrenderers API properties: apiVersion: description: |- @@ -147,8 +152,11 @@ spec: - MySQL: increase max connections after v-scale operation. - Zookeeper: update zoo.cfg with new node addresses after h-scale operation. items: - description: RerenderResourceType defines the resource requirements - for a component. + description: |- + Deprecated: It is retained for API compatibility with existing ParamConfigRenderer objects. + + + RerenderResourceType defines the resource requirements for a component. enum: - vscale - hscale diff --git a/deploy/helm/crds/parameters.kubeblocks.io_parametersdefinitions.yaml b/deploy/helm/crds/parameters.kubeblocks.io_parametersdefinitions.yaml index 92049f462bf..3ce16251ac7 100644 --- a/deploy/helm/crds/parameters.kubeblocks.io_parametersdefinitions.yaml +++ b/deploy/helm/crds/parameters.kubeblocks.io_parametersdefinitions.yaml @@ -58,6 +58,11 @@ spec: spec: description: ParametersDefinitionSpec defines the desired state of ParametersDefinition properties: + componentDef: + description: |- + Specifies the ComponentDefinition custom resource (CR) that defines the Component's characteristics and behavior. + The value can represent an exact name, a name prefix, or a regular expression pattern. + type: string deletedPolicy: description: Specifies the policy when parameter be removed. properties: @@ -88,6 +93,49 @@ spec: type: string type: array x-kubernetes-list-type: set + fileFormatConfig: + description: |- + Specifies the format of the configuration file and any associated parameters that are specific to the chosen format. + Supported formats include `ini`, `xml`, `yaml`, `json`, `hcl`, `dotenv`, `properties`, and `toml`. + properties: + format: + description: |- + The config file format. Valid values are `ini`, `xml`, `yaml`, `json`, + `hcl`, `dotenv`, `properties` and `toml`. Each format has its own characteristics and use cases. + + + - ini: is a text-based content with a structure and syntax comprising key–value pairs for properties, reference wiki: https://en.wikipedia.org/wiki/INI_file + - xml: refers to wiki: https://en.wikipedia.org/wiki/XML + - yaml: supports for complex data types and structures. + - json: refers to wiki: https://en.wikipedia.org/wiki/JSON + - hcl: The HashiCorp Configuration Language (HCL) is a configuration language authored by HashiCorp, reference url: https://www.linode.com/docs/guides/introduction-to-hcl/ + - dotenv: is a plain text file with simple key–value pairs, reference wiki: https://en.wikipedia.org/wiki/Configuration_file#MS-DOS + - properties: a file extension mainly used in Java, reference wiki: https://en.wikipedia.org/wiki/.properties + - toml: refers to wiki: https://en.wikipedia.org/wiki/TOML + - props-plus: a file extension mainly used in Java, supports CamelCase(e.g: brokerMaxConnectionsPerIp) + enum: + - xml + - ini + - yaml + - json + - hcl + - dotenv + - toml + - properties + - redis + - props-plus + - props-ultra + type: string + iniConfig: + description: Holds options specific to the 'ini' file format. + properties: + sectionName: + description: A string that describes the name of the ini section. + type: string + type: object + required: + - format + type: object fileName: description: Specifies the config file name in the config template. type: string @@ -363,6 +411,12 @@ spec: The "all" option is for certain engines that require static parameters to be set via SQL statements before they can take effect on restart. type: boolean + serviceVersion: + description: |- + ServiceVersion specifies the version of the Service expected to be provisioned by this Component. + The version should follow the syntax and semantics of the "Semantic Versioning" specification (http://semver.org/). + If no version is specified, the latest available version will be used. + type: string staticParameters: description: |- List static parameters. @@ -371,6 +425,9 @@ spec: type: string type: array x-kubernetes-list-type: set + templateName: + description: Specifies the name of the referenced config template. + type: string type: object status: description: ParametersDefinitionStatus defines the observed state of diff --git a/docs/developer_docs/api-reference/parameters.md b/docs/developer_docs/api-reference/parameters.md index 3b3a72e3c8e..31de7a7543f 100644 --- a/docs/developer_docs/api-reference/parameters.md +++ b/docs/developer_docs/api-reference/parameters.md @@ -146,7 +146,8 @@ ComponentParameterStatus

ParamConfigRenderer

-

ParamConfigRenderer is the Schema for the paramconfigrenderers API

+

Deprecated: retained for API compatibility only.

+

ParamConfigRenderer is the Schema for the paramconfigrenderers API

@@ -431,6 +432,45 @@ ParametersDefinitionSpec + + + + + + + + + + + + + + + + @@ -1267,7 +1320,7 @@ ReconcileDetail

FileFormatConfig

-(Appears on:ComponentConfigDescription) +(Appears on:ComponentConfigDescription, ParametersDefinitionSpec)

FileFormatConfig specifies the format of the configuration file and any associated parameters @@ -1592,6 +1645,7 @@ When set to PDAvailablePhase, the ParamsDesc can be referenced by ComponentDefin (Appears on:ParameterDeletedPolicy)

+

Deprecated: It is retained for API compatibility with existing ParametersDefinition objects.

ParameterDeletedMethod defines how to handle parameter remove

+componentDef
+ +string + +
+(Optional) +

Specifies the ComponentDefinition custom resource (CR) that defines the Component’s characteristics and behavior. +The value can represent an exact name, a name prefix, or a regular expression pattern.

+
+serviceVersion
+ +string + +
+(Optional) +

ServiceVersion specifies the version of the Service expected to be provisioned by this Component. +The version should follow the syntax and semantics of the “Semantic Versioning” specification (http://semver.org/). +If no version is specified, the latest available version will be used.

+
+templateName
+ +string + +
+(Optional) +

Specifies the name of the referenced config template.

+
fileName
string @@ -443,6 +483,21 @@ string
+fileFormatConfig
+ + +FileFormatConfig + + +
+(Optional) +

Specifies the format of the configuration file and any associated parameters that are specific to the chosen format. +Supported formats include ini, xml, yaml, json, hcl, dotenv, properties, and toml.

+
parametersSchema
@@ -1113,9 +1168,7 @@ Payload
(Optional) -

External controllers can trigger a configuration rerender by modifying this field.

-

Note: Currently, the payload field is opaque and its content is not interpreted by the system. -Modifying this field will cause a rerender, regardless of the specific content of this field.

+

Deprecated: retained for API compatibility only.

@@ -1613,6 +1667,7 @@ When set to PDAvailablePhase, the ParamsDesc can be referenced by ComponentDefin (Appears on:ParametersDefinitionSpec)

+

Deprecated: It is retained for API compatibility with existing ParametersDefinition objects.

@@ -1827,6 +1882,45 @@ updated by the API Server.

+ + + + + + + + + + + + + + + +
+componentDef
+ +string + +
+(Optional) +

Specifies the ComponentDefinition custom resource (CR) that defines the Component’s characteristics and behavior. +The value can represent an exact name, a name prefix, or a regular expression pattern.

+
+serviceVersion
+ +string + +
+(Optional) +

ServiceVersion specifies the version of the Service expected to be provisioned by this Component. +The version should follow the syntax and semantics of the “Semantic Versioning” specification (http://semver.org/). +If no version is specified, the latest available version will be used.

+
+templateName
+ +string + +
+(Optional) +

Specifies the name of the referenced config template.

+
fileName
string @@ -1839,6 +1933,21 @@ string
+fileFormatConfig
+ + +FileFormatConfig + + +
+(Optional) +

Specifies the format of the configuration file and any associated parameters that are specific to the chosen format. +Supported formats include ini, xml, yaml, json, hcl, dotenv, properties, and toml.

+
parametersSchema
@@ -2168,6 +2277,7 @@ Kubernetes api extensions v1.JSONSchemaProps (Appears on:ConfigTemplateItemDetail)

+

Deprecated: It is retained for API compatibility with existing ComponentParameter objects.

Payload holds the payload data. This field is optional and can contain any type of data. Not included in the JSON representation of the object.

@@ -2330,6 +2440,7 @@ This allows users to customize the configuration template according to their spe

ReloadAction defines the mechanisms available for dynamically reloading a process within K8s without requiring a restart.

+

Deprecated: It is retained for API compatibility with existing ParametersDefinition objects.

Only one of the mechanisms can be specified at a time.

@@ -2394,6 +2505,7 @@ reload.

(Appears on:ComponentConfigDescription)

+

Deprecated: It is retained for API compatibility with existing ParamConfigRenderer objects.

RerenderResourceType defines the resource requirements for a component.

diff --git a/pkg/constant/config.go b/pkg/constant/config.go index 13b6d401512..c53c6a05e53 100644 --- a/pkg/constant/config.go +++ b/pkg/constant/config.go @@ -42,6 +42,7 @@ const ( LastAppliedConfigAnnotationKey = "config.kubeblocks.io/last-applied-configuration" UpgradePolicyAnnotationKey = "config.kubeblocks.io/reconfigure-policy" ConfigAppliedVersionAnnotationKey = "config.kubeblocks.io/config-applied-version" + ParametersAppliedComponentGenerationKey = "parameters.kubeblocks.io/latest-component-generation" ) const ( diff --git a/pkg/constant/payload.go b/pkg/constant/payload.go deleted file mode 100644 index 195108a0e09..00000000000 --- a/pkg/constant/payload.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package constant - -const ( - TLSPayload = "tls" - ComponentResourcePayload = "componentResource" - ReplicasPayload = "replicas" - BinaryVersionPayload = "binaryVersion" - ShardingPayload = "sharding" -) diff --git a/pkg/parameters/config_metadata.go b/pkg/parameters/config_metadata.go new file mode 100644 index 00000000000..b20f5c7f338 --- /dev/null +++ b/pkg/parameters/config_metadata.go @@ -0,0 +1,47 @@ +/* +Copyright (C) 2022-2025 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package parameters + +import ( + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/generics" +) + +func GetComponentConfigDescription(configs []parametersv1alpha1.ComponentConfigDescription, name string) *parametersv1alpha1.ComponentConfigDescription { + match := func(desc parametersv1alpha1.ComponentConfigDescription) bool { + return desc.Name == name + } + + if index := generics.FindFirstFunc(configs, match); index >= 0 { + return &configs[index] + } + return nil +} + +func GetComponentConfigDescriptions(configs []parametersv1alpha1.ComponentConfigDescription, tpl string) []parametersv1alpha1.ComponentConfigDescription { + match := func(desc parametersv1alpha1.ComponentConfigDescription) bool { + return desc.TemplateName == tpl + } + return generics.FindFunc(configs, match) +} + +func HasValidParameterTemplate(configs []parametersv1alpha1.ComponentConfigDescription) bool { + return len(configs) != 0 +} diff --git a/pkg/parameters/config_util.go b/pkg/parameters/config_util.go index 364cee97f72..83cc44894ee 100644 --- a/pkg/parameters/config_util.go +++ b/pkg/parameters/config_util.go @@ -30,7 +30,6 @@ import ( "github.com/StudioSol/set" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -137,11 +136,10 @@ func fromUpdatedConfig(m map[string]string, sets *set.LinkedHashSetString) map[s } // IsApplyUpdatedParameters checks if the configuration is changed -func IsApplyUpdatedParameters(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail) bool { +func IsApplyUpdatedParameters(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail, compGeneration int64) bool { if configMap == nil { return false } - lastAppliedVersion, ok := configMap.Annotations[constant.ConfigAppliedVersionAnnotationKey] if !ok { return false @@ -150,37 +148,28 @@ func IsApplyUpdatedParameters(configMap *corev1.ConfigMap, item parametersv1alph if err := json.Unmarshal([]byte(lastAppliedVersion), &lastItem); err != nil { return false } - return reflect.DeepEqual(lastItem, item) -} - -// IsRerender checks if the configuration template is changed -func IsRerender(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail) bool { - if configMap == nil { + if !reflect.DeepEqual(lastItem, item) { + return false + } + if compGeneration == 0 { return true } - if len(item.Payload) == 0 && item.CustomTemplates == nil { + appliedGeneration, ok := configMap.Annotations[constant.ParametersAppliedComponentGenerationKey] + if !ok || appliedGeneration == "" { return false } - - var updatedVersion parametersv1alpha1.ConfigTemplateItemDetail - updatedVersionStr, ok := configMap.Annotations[constant.ConfigAppliedVersionAnnotationKey] - if ok && updatedVersionStr != "" { - if err := json.Unmarshal([]byte(updatedVersionStr), &updatedVersion); err != nil { - return false - } - } - return !reflect.DeepEqual(updatedVersion.Payload, item.Payload) || - !reflect.DeepEqual(updatedVersion.CustomTemplates, item.CustomTemplates) + return appliedGeneration == strconv.FormatInt(compGeneration, 10) } // GetUpdatedParametersReconciledPhase gets the configuration phase func GetUpdatedParametersReconciledPhase(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail, - status *parametersv1alpha1.ConfigTemplateItemDetailStatus) parametersv1alpha1.ParameterPhase { + status *parametersv1alpha1.ConfigTemplateItemDetailStatus, + compGeneration int64) parametersv1alpha1.ParameterPhase { if status == nil || status.Phase == "" { return parametersv1alpha1.CCreatingPhase } - if !IsApplyUpdatedParameters(configMap, item) { + if !IsApplyUpdatedParameters(configMap, item, compGeneration) { return parametersv1alpha1.CPendingPhase } if status.Phase == parametersv1alpha1.CFinishedPhase { @@ -193,51 +182,6 @@ func GetUpdatedParametersReconciledPhase(configMap *corev1.ConfigMap, return status.Phase } -func CheckAndPatchPayload(item *parametersv1alpha1.ConfigTemplateItemDetail, payloadID string, payload interface{}) (bool, error) { - if item == nil { - return false, nil - } - if item.Payload == nil { - item.Payload = make(map[string]json.RawMessage) - } - oldPayload, ok := item.Payload[payloadID] - if !ok && payload == nil { - return false, nil - } - if payload == nil { - delete(item.Payload, payloadID) - return true, nil - } - newPayload, err := buildPayloadAsUnstructuredObject(payload) - if err != nil { - return false, err - } - if oldPayload != nil && reflect.DeepEqual(oldPayload, newPayload) { - return false, nil - } - item.Payload[payloadID] = newPayload - return true, nil -} - -func buildPayloadAsUnstructuredObject(payload interface{}) (json.RawMessage, error) { - b, err := json.Marshal(payload) - if err != nil { - return nil, err - } - return b, nil -} - -func ResourcesPayloadForComponent(resources corev1.ResourceRequirements) any { - if len(resources.Requests) == 0 && len(resources.Limits) == 0 { - return nil - } - - return map[string]any{ - "limits": resources.Limits, - "requests": resources.Requests, - } -} - func resolveParametersDef(paramsDefs []*parametersv1alpha1.ParametersDefinition, fileName string) *parametersv1alpha1.ParametersDefinition { pos := generics.FindFirstFunc(paramsDefs, func(paramsDef *parametersv1alpha1.ParametersDefinition) bool { return paramsDef.Spec.FileName == fileName @@ -264,16 +208,89 @@ func filterImmutableParameters(parameters map[string]any, fileName string, param return validParameters } -func ResolveCmpdParametersDefs(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) (*parametersv1alpha1.ParamConfigRenderer, []*parametersv1alpha1.ParametersDefinition, error) { +func ResolveCmpdParametersDefs(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) ([]parametersv1alpha1.ComponentConfigDescription, []*parametersv1alpha1.ParametersDefinition, error) { + paramsDefList := ¶metersv1alpha1.ParametersDefinitionList{} + if err := reader.List(ctx, paramsDefList); err != nil { + return nil, nil, err + } + + slices.SortFunc(paramsDefList.Items, func(a, b parametersv1alpha1.ParametersDefinition) int { + if cmp := strings.Compare(b.Spec.ComponentDef, a.Spec.ComponentDef); cmp != 0 { + return cmp + } + if cmp := strings.Compare(a.Spec.TemplateName, b.Spec.TemplateName); cmp != 0 { + return cmp + } + if cmp := strings.Compare(a.Spec.FileName, b.Spec.FileName); cmp != 0 { + return cmp + } + return strings.Compare(a.Name, b.Name) + }) + var paramsDefs []*parametersv1alpha1.ParametersDefinition + configDescs := make([]parametersv1alpha1.ComponentConfigDescription, 0, len(paramsDefList.Items)) + coveredFiles := make(map[string]string) + for i := range paramsDefList.Items { + paramsDef := ¶msDefList.Items[i] + matched, err := matchParametersDefinition(cmpd, paramsDef) + if err != nil { + return nil, nil, err + } + if !matched { + continue + } + if paramsDef.Status.Phase != parametersv1alpha1.PDAvailablePhase { + return nil, nil, fmt.Errorf("the referenced ParametersDefinition is unavailable: %s", paramsDef.Name) + } + if err := validateMatchedParametersDefinition(paramsDef); err != nil { + return nil, nil, err + } + if existing, ok := coveredFiles[paramsDef.Spec.FileName]; ok { + return nil, nil, fmt.Errorf("config file[%s] has been defined in other parametersdefinition[%s]", paramsDef.Spec.FileName, existing) + } + coveredFiles[paramsDef.Spec.FileName] = paramsDef.Name + paramsDefs = append(paramsDefs, paramsDef) + configDescs = append(configDescs, parametersv1alpha1.ComponentConfigDescription{ + Name: paramsDef.Spec.FileName, + TemplateName: paramsDef.Spec.TemplateName, + FileFormatConfig: paramsDef.Spec.FileFormatConfig.DeepCopy(), + }) + } + + legacyConfigDescs, legacyParamsDefs, err := resolveCmpdParametersDefsByConfigRender(ctx, reader, cmpd) + if err != nil { + return nil, nil, err + } + legacyConfigFiles := make(map[string]struct{}, len(legacyConfigDescs)) + for _, legacyConfigDesc := range legacyConfigDescs { + if _, ok := coveredFiles[legacyConfigDesc.Name]; ok { + continue + } + configDescs = append(configDescs, legacyConfigDesc) + legacyConfigFiles[legacyConfigDesc.Name] = struct{}{} + } + for _, legacyParamsDef := range legacyParamsDefs { + if _, ok := legacyConfigFiles[legacyParamsDef.Spec.FileName]; !ok { + continue + } + paramsDefs = append(paramsDefs, legacyParamsDef) + } + if len(paramsDefs) == 0 { + return nil, nil, nil + } + return configDescs, paramsDefs, nil +} +func resolveCmpdParametersDefsByConfigRender(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) ([]parametersv1alpha1.ComponentConfigDescription, []*parametersv1alpha1.ParametersDefinition, error) { configRender, err := ResolveComponentConfigRender(ctx, reader, cmpd) if err != nil { return nil, nil, err } if configRender == nil || len(configRender.Spec.ParametersDefs) == 0 { - return configRender, nil, nil + return nil, nil, nil } + + paramsDefs := make([]*parametersv1alpha1.ParametersDefinition, 0, len(configRender.Spec.ParametersDefs)) for _, defName := range configRender.Spec.ParametersDefs { paramsDef := ¶metersv1alpha1.ParametersDefinition{} if err = reader.Get(ctx, client.ObjectKey{Name: defName}, paramsDef); err != nil { @@ -284,7 +301,49 @@ func ResolveCmpdParametersDefs(ctx context.Context, reader client.Reader, cmpd * } paramsDefs = append(paramsDefs, paramsDef) } - return configRender, paramsDefs, nil + return slices.Clone(configRender.Spec.Configs), paramsDefs, nil +} + +func matchParametersDefinition(cmpd *appsv1.ComponentDefinition, paramsDef *parametersv1alpha1.ParametersDefinition) (bool, error) { + if cmpd == nil || paramsDef == nil { + return false, nil + } + pattern := paramsDef.Spec.ComponentDef + if pattern == "" { + return false, nil + } + if !component.PrefixOrRegexMatched(cmpd.Name, pattern) { + return false, nil + } + return paramsDef.Spec.ServiceVersion == "" || paramsDef.Spec.ServiceVersion == cmpd.Spec.ServiceVersion, nil +} + +func validateMatchedParametersDefinition(paramsDef *parametersv1alpha1.ParametersDefinition) error { + if paramsDef.Spec.TemplateName == "" { + return fmt.Errorf("parametersdefinition[%s] misses templateName", paramsDef.Name) + } + if paramsDef.Spec.FileName == "" { + return fmt.Errorf("parametersdefinition[%s] misses fileName", paramsDef.Name) + } + if paramsDef.Spec.FileFormatConfig == nil { + return fmt.Errorf("parametersdefinition[%s] misses fileFormatConfig", paramsDef.Name) + } + return nil +} + +func BuildConfigDescriptionsFromParametersDefs(paramsDefs []*parametersv1alpha1.ParametersDefinition) []parametersv1alpha1.ComponentConfigDescription { + if len(paramsDefs) == 0 { + return nil + } + configs := make([]parametersv1alpha1.ComponentConfigDescription, 0, len(paramsDefs)) + for _, paramsDef := range paramsDefs { + configs = append(configs, parametersv1alpha1.ComponentConfigDescription{ + Name: paramsDef.Spec.FileName, + TemplateName: paramsDef.Spec.TemplateName, + FileFormatConfig: paramsDef.Spec.FileFormatConfig.DeepCopy(), + }) + } + return configs } func ResolveComponentConfigRender(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) (*parametersv1alpha1.ParamConfigRenderer, error) { @@ -296,24 +355,91 @@ func ResolveComponentConfigRender(ctx context.Context, reader client.Reader, cmp return strings.Compare(b.Spec.ComponentDef, a.Spec.ComponentDef) }) - checkAvailable := func(configDef parametersv1alpha1.ParamConfigRenderer) error { - if configDef.Status.Phase != parametersv1alpha1.PDAvailablePhase { - return fmt.Errorf("the referenced ParamConfigRenderer is unavailable: %s", configDef.Name) - } - return nil - } - for i, item := range configDefList.Items { if !component.PrefixOrRegexMatched(cmpd.Name, item.Spec.ComponentDef) { continue } if item.Spec.ServiceVersion == "" || item.Spec.ServiceVersion == cmpd.Spec.ServiceVersion { - return &configDefList.Items[i], checkAvailable(item) + resolved := configDefList.Items[i].DeepCopy() + if err := hydrateLegacyConfigRender(ctx, reader, resolved, cmpd); err != nil { + return nil, err + } + return resolved, nil } } return nil, nil } +func hydrateLegacyConfigRender(ctx context.Context, reader client.Reader, configRender *parametersv1alpha1.ParamConfigRenderer, cmpd *appsv1.ComponentDefinition) error { + if configRender == nil { + return nil + } + if err := fillLegacyConfigTemplateNames(ctx, reader, &configRender.Spec, cmpd); err != nil { + return err + } + if err := validateLegacyParametersDefs(ctx, reader, configRender.Spec.ParametersDefs); err != nil { + return err + } + return validateLegacyParametersConfigs(configRender.Spec.Configs, cmpd.Spec.Configs) +} + +func fillLegacyConfigTemplateNames(ctx context.Context, reader client.Reader, template *parametersv1alpha1.ParamConfigRendererSpec, cmpd *appsv1.ComponentDefinition) error { + match := func(spec parametersv1alpha1.ComponentConfigDescription) bool { + return spec.TemplateName == "" + } + if generics.CountFunc(template.Configs, match) == 0 { + return nil + } + tpls, err := ResolveComponentTemplate(ctx, reader, cmpd) + if err != nil { + return err + } + resolveConfigTemplate := func(config string) string { + for name, configTemplate := range tpls { + if _, ok := configTemplate.Data[config]; ok { + return name + } + } + return "" + } + for i, config := range template.Configs { + if tplName := resolveConfigTemplate(config.Name); tplName != "" { + template.Configs[i].TemplateName = tplName + } + } + return nil +} + +func validateLegacyParametersConfigs(configs []parametersv1alpha1.ComponentConfigDescription, templates []appsv1.ComponentFileTemplate) error { + for _, config := range configs { + match := func(spec appsv1.ComponentFileTemplate) bool { + return config.TemplateName == spec.Name + } + if len(generics.FindFunc(templates, match)) == 0 { + return fmt.Errorf("config template[%s] not found in component definition", config.TemplateName) + } + } + return nil +} + +func validateLegacyParametersDefs(ctx context.Context, reader client.Reader, paramsDefs []string) error { + paramsDefObjs := make(map[string]*parametersv1alpha1.ParametersDefinition, len(paramsDefs)) + for _, paramsDef := range paramsDefs { + obj := ¶metersv1alpha1.ParametersDefinition{} + if err := reader.Get(ctx, client.ObjectKey{Name: paramsDef}, obj); err != nil { + return err + } + if obj.Status.Phase != parametersv1alpha1.PDAvailablePhase { + return fmt.Errorf("the referenced ParametersDefinition is unavailable: %s", obj.Name) + } + if def, ok := paramsDefObjs[obj.Spec.FileName]; ok { + return fmt.Errorf("config file[%s] has been defined in other parametersdefinition[%s]", obj.Spec.FileName, def.Name) + } + paramsDefObjs[obj.Spec.FileName] = obj + } + return nil +} + func NeedDynamicReloadAction(pd *parametersv1alpha1.ParametersDefinitionSpec) bool { if pd.MergeReloadAndRestart != nil { return !*pd.MergeReloadAndRestart @@ -386,79 +512,6 @@ func LegacyConfigManagerRequiredForCluster(cluster *appsv1.Cluster) (bool, error return state == LegacyConfigManagerRequirementKeep, nil } -// UpdateConfigPayload updates the configuration payload -func UpdateConfigPayload(config *parametersv1alpha1.ComponentParameterSpec, component *appsv1.ComponentSpec, configRender *parametersv1alpha1.ParamConfigRendererSpec, sharding *appsv1.ClusterSharding) error { - if len(configRender.Configs) == 0 { - return nil - } - - for i, item := range config.ConfigItemDetails { - configDescs := GetComponentConfigDescriptions(configRender, item.Name) - configSpec := &config.ConfigItemDetails[i] - // check v-scale operation - if enableVScaleTrigger(configDescs) { - resourcePayload := ResourcesPayloadForComponent(component.Resources) - if _, err := CheckAndPatchPayload(configSpec, constant.ComponentResourcePayload, resourcePayload); err != nil { - return err - } - } - // check h-scale operation - if enableHScaleTrigger(configDescs) { - if _, err := CheckAndPatchPayload(configSpec, constant.ReplicasPayload, component.Replicas); err != nil { - return err - } - } - // check tls - if enableTLSTrigger(configDescs) { - if component.TLSConfig == nil { - continue - } - if _, err := CheckAndPatchPayload(configSpec, constant.TLSPayload, component.TLSConfig); err != nil { - return err - } - } - // check sharding h-scale operation - if sharding != nil && enableShardingHVScaleTrigger(configDescs) { - if _, err := CheckAndPatchPayload(configSpec, constant.ShardingPayload, resolveShardingResource(sharding)); err != nil { - return err - } - } - } - return nil -} - -func rerenderConfigEnabled(configDescs []parametersv1alpha1.ComponentConfigDescription, rerenderType parametersv1alpha1.RerenderResourceType) bool { - for _, desc := range configDescs { - if slices.Contains(desc.ReRenderResourceTypes, rerenderType) { - return true - } - } - return false -} - -func resolveShardingResource(sharding *appsv1.ClusterSharding) map[string]string { - return map[string]string{ - "shards": strconv.Itoa(int(sharding.Shards)), - "replicas": strconv.Itoa(int(sharding.Template.Replicas)), - } -} - -func enableHScaleTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { - return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentHScaleType) -} - -func enableVScaleTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { - return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentVScaleType) -} - -func enableTLSTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { - return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentTLSType) -} - -func enableShardingHVScaleTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { - return rerenderConfigEnabled(configDescs, parametersv1alpha1.ShardingComponentHScaleType) -} - func ResolveComponentTemplate(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) (map[string]*corev1.ConfigMap, error) { tpls := make(map[string]*corev1.ConfigMap, len(cmpd.Spec.Configs)) for _, config := range cmpd.Spec.Configs { @@ -470,33 +523,3 @@ func ResolveComponentTemplate(ctx context.Context, reader client.Reader, cmpd *a } return tpls, nil } - -func ResolveShardingReference(ctx context.Context, reader client.Reader, comp *appsv1.Component) (*appsv1.ClusterSharding, error) { - resolveShardingName := func(comp *appsv1.Component) string { - if len(comp.Labels) == 0 { - return "" - } - return comp.Labels[constant.KBAppShardingNameLabelKey] - } - - shardingName := resolveShardingName(comp) - if shardingName == "" { - return nil, nil - } - - cluster := appsv1.Cluster{} - clusterName, _ := component.GetClusterName(comp) - clusterKey := types.NamespacedName{ - Name: clusterName, - Namespace: comp.Namespace, - } - if err := reader.Get(ctx, clusterKey, &cluster); err != nil { - return nil, err - } - for i, sharding := range cluster.Spec.Shardings { - if sharding.Name == shardingName { - return &cluster.Spec.Shardings[i], nil - } - } - return nil, nil -} diff --git a/pkg/parameters/config_util_test.go b/pkg/parameters/config_util_test.go index 4e6d42b0071..e344b9b8d4e 100644 --- a/pkg/parameters/config_util_test.go +++ b/pkg/parameters/config_util_test.go @@ -20,6 +20,7 @@ along with this program. If not, see . package parameters import ( + "context" "encoding/json" "reflect" "testing" @@ -29,7 +30,9 @@ import ( "github.com/StudioSol/set" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" @@ -85,110 +88,6 @@ func TestFromUpdatedConfig(t *testing.T) { } } -func TestIsRerender(t *testing.T) { - type args struct { - cm *corev1.ConfigMap - item parametersv1alpha1.ConfigTemplateItemDetail - } - tests := []struct { - name string - args args - want bool - }{{ - - name: "test", - args: args{ - cm: nil, - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - }, - }, - want: true, - }, { - name: "test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test").GetObject(), - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - }, - }, - want: false, - }, { - name: "import-template-test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ""). - GetObject(), - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - CustomTemplates: ¶metersv1alpha1.ConfigTemplateExtension{ - TemplateRef: "contig-test-template", - Namespace: "default", - Policy: parametersv1alpha1.PatchPolicy, - }, - }, - }, - want: true, - }, { - name: "import-template-test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ` -{ - "userConfigTemplates": { - "templateRef": "contig-test-template", - "namespace": "default", - "policy": "patch" - } -} -`). - GetObject(), - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - CustomTemplates: ¶metersv1alpha1.ConfigTemplateExtension{ - TemplateRef: "contig-test-template", - Namespace: "default", - Policy: parametersv1alpha1.PatchPolicy, - }, - }, - }, - want: false, - }, { - name: "payload-test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ""). - GetObject(), - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - Payload: parametersv1alpha1.Payload{}, - }, - }, - want: false, - }, { - name: "payload-test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ` {"payload":{"key":"value"}} `). - GetObject(), - item: parametersv1alpha1.ConfigTemplateItemDetail{ - Name: "test", - Payload: parametersv1alpha1.Payload{ - "key": transformPayload("value"), - }, - }, - }, - want: false, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := IsRerender(tt.args.cm, tt.args.item); got != tt.want { - t.Errorf("IsRerender() = %v, want %v", got, tt.want) - } - }) - } -} - func TestGetConfigSpecReconcilePhase(t *testing.T) { type args struct { cm *corev1.ConfigMap @@ -237,7 +136,7 @@ func TestGetConfigSpecReconcilePhase(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := GetUpdatedParametersReconciledPhase(tt.args.cm, tt.args.item, tt.args.status); got != tt.want { + if got := GetUpdatedParametersReconciledPhase(tt.args.cm, tt.args.item, tt.args.status, 0); got != tt.want { t.Errorf("GetUpdatedParametersReconciledPhase() = %v, want %v", got, tt.want) } }) @@ -400,6 +299,352 @@ func TestLegacyConfigManagerRequirementStateForCluster(t *testing.T) { } } +func TestResolveCmpdParametersDefs(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{{ + Name: "mysql-config", + Template: "mysql-config", + }}, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + pd := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + ComponentDef: "mysql-8", + TemplateName: "mysql-config", + FileName: "my.cnf", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, pd).Build() + configDescs, paramsDefs, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err != nil { + t.Fatalf("ResolveCmpdParametersDefs() error = %v", err) + } + if len(paramsDefs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() paramsDefs len = %d, want 1", len(paramsDefs)) + } + if configDescs == nil { + t.Fatalf("ResolveCmpdParametersDefs() configDescs = nil") + } + if len(configDescs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() configs len = %d, want 1", len(configDescs)) + } + if configDescs[0].TemplateName != "mysql-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName = %q, want %q", configDescs[0].TemplateName, "mysql-config") + } +} + +func TestResolveCmpdParametersDefsRejectsDuplicateFiles(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{{ + Name: "mysql-config", + Template: "mysql-config", + }}, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + newPD := func(name string) *parametersv1alpha1.ParametersDefinition { + return ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + ComponentDef: "mysql-8", + TemplateName: "mysql-config", + FileName: "my.cnf", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, newPD("pd-1"), newPD("pd-2")).Build() + _, _, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err == nil { + t.Fatalf("ResolveCmpdParametersDefs() error = nil, want duplicate-file error") + } +} + +func TestResolveCmpdParametersDefsFallbacksToParamConfigRenderer(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{{ + Name: "mysql-config", + Template: "mysql-config", + }}, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + pd := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "my.cnf", + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: "mysql-8", + ServiceVersion: "8.0.30", + ParametersDefs: []string{pd.Name}, + Configs: []parametersv1alpha1.ComponentConfigDescription{{ + Name: "my.cnf", + TemplateName: "mysql-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }}, + }, + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, pd, pcr).Build() + configDescs, paramsDefs, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err != nil { + t.Fatalf("ResolveCmpdParametersDefs() error = %v", err) + } + if len(paramsDefs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() paramsDefs len = %d, want 1", len(paramsDefs)) + } + if len(configDescs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() configs len = %d, want 1", len(configDescs)) + } + if configDescs[0].TemplateName != "mysql-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName = %q, want %q", configDescs[0].TemplateName, "mysql-config") + } +} + +func TestResolveCmpdParametersDefsMergesNewParametersDefinitionsWithLegacyParamConfigRenderer(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{ + {Name: "mysql-config", Template: "mysql-config"}, + {Name: "log-config", Template: "log-config"}, + }, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + newPD := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + ComponentDef: "mysql-8", + TemplateName: "mysql-config", + FileName: "my.cnf", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + legacyPD := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-log-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "log.conf", + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: "mysql-8", + ServiceVersion: "8.0.30", + ParametersDefs: []string{legacyPD.Name}, + Configs: []parametersv1alpha1.ComponentConfigDescription{{ + Name: "log.conf", + TemplateName: "log-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, + }, + }}, + }, + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, newPD, legacyPD, pcr).Build() + configDescs, paramsDefs, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err != nil { + t.Fatalf("ResolveCmpdParametersDefs() error = %v", err) + } + if len(paramsDefs) != 2 { + t.Fatalf("ResolveCmpdParametersDefs() paramsDefs len = %d, want 2", len(paramsDefs)) + } + if len(configDescs) != 2 { + t.Fatalf("ResolveCmpdParametersDefs() configs len = %d, want 2", len(configDescs)) + } + gotTemplates := map[string]string{} + for _, configDesc := range configDescs { + gotTemplates[configDesc.Name] = configDesc.TemplateName + } + if gotTemplates["my.cnf"] != "mysql-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName for my.cnf = %q, want %q", gotTemplates["my.cnf"], "mysql-config") + } + if gotTemplates["log.conf"] != "log-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName for log.conf = %q, want %q", gotTemplates["log.conf"], "log-config") + } +} + +func TestResolveCmpdParametersDefsIgnoresMalformedUnrelatedLegacyParamConfigRenderer(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{{ + Name: "mysql-config", + Template: "mysql-config", + }}, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + pd := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "my.cnf", + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + validPCR := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: "mysql-8", + ServiceVersion: "8.0.30", + ParametersDefs: []string{pd.Name}, + Configs: []parametersv1alpha1.ComponentConfigDescription{{ + Name: "my.cnf", + TemplateName: "mysql-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }}, + }, + } + invalidPCR := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: "broken-pcr"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: "[broken", + }, + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, pd, validPCR, invalidPCR).Build() + configDescs, paramsDefs, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err != nil { + t.Fatalf("ResolveCmpdParametersDefs() error = %v", err) + } + if len(paramsDefs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() paramsDefs len = %d, want 1", len(paramsDefs)) + } + if len(configDescs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() configs len = %d, want 1", len(configDescs)) + } + if configDescs[0].TemplateName != "mysql-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName = %q, want %q", configDescs[0].TemplateName, "mysql-config") + } +} + +func TestResolveCmpdParametersDefsPrefersNewParametersDefinitionOverLegacyParamConfigRendererForSameFile(t *testing.T) { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = parametersv1alpha1.AddToScheme(scheme) + + cmpd := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-8.0.30"}, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Configs: []appsv1.ComponentFileTemplate{ + {Name: "mysql-config", Template: "mysql-config"}, + {Name: "legacy-config", Template: "legacy-config"}, + }, + }, + Status: appsv1.ComponentDefinitionStatus{Phase: appsv1.AvailablePhase}, + } + newPD := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + ComponentDef: "mysql-8", + TemplateName: "mysql-config", + FileName: "my.cnf", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + legacyPD := ¶metersv1alpha1.ParametersDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-legacy-params"}, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "my.cnf", + }, + Status: parametersv1alpha1.ParametersDefinitionStatus{Phase: parametersv1alpha1.PDAvailablePhase}, + } + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + ObjectMeta: metav1.ObjectMeta{Name: "mysql-pcr"}, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: "mysql-8", + ServiceVersion: "8.0.30", + ParametersDefs: []string{legacyPD.Name}, + Configs: []parametersv1alpha1.ComponentConfigDescription{{ + Name: "my.cnf", + TemplateName: "legacy-config", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, + }, + }}, + }, + } + + cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cmpd, newPD, legacyPD, pcr).Build() + configDescs, paramsDefs, err := ResolveCmpdParametersDefs(context.Background(), cli, cmpd) + if err != nil { + t.Fatalf("ResolveCmpdParametersDefs() error = %v", err) + } + if len(paramsDefs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() paramsDefs len = %d, want 1", len(paramsDefs)) + } + if paramsDefs[0].Name != newPD.Name { + t.Fatalf("ResolveCmpdParametersDefs() paramsDef = %q, want %q", paramsDefs[0].Name, newPD.Name) + } + if len(configDescs) != 1 { + t.Fatalf("ResolveCmpdParametersDefs() configs len = %d, want 1", len(configDescs)) + } + if configDescs[0].TemplateName != "mysql-config" { + t.Fatalf("ResolveCmpdParametersDefs() templateName = %q, want %q", configDescs[0].TemplateName, "mysql-config") + } +} + var _ = Describe("config_util", func() { var k8sMockClient *testutil.K8sClientMockHelper @@ -511,83 +756,6 @@ var _ = Describe("config_util", func() { }) -func TestCheckAndPatchPayload(t *testing.T) { - type args struct { - item *parametersv1alpha1.ConfigTemplateItemDetail - payloadID string - payload interface{} - } - tests := []struct { - name string - args args - want bool - wantErr bool - }{{ - name: "test", - args: args{ - item: ¶metersv1alpha1.ConfigTemplateItemDetail{}, - payloadID: constant.BinaryVersionPayload, - payload: "md5-12912uy1232o9y2", - }, - want: true, - }, { - name: "invalid-item-test", - args: args{ - payloadID: constant.BinaryVersionPayload, - payload: "md5-12912uy1232o9y2", - }, - want: false, - }, { - name: "test-delete-payload", - args: args{ - item: ¶metersv1alpha1.ConfigTemplateItemDetail{ - Payload: parametersv1alpha1.Payload{ - constant.BinaryVersionPayload: json.RawMessage("md5-12912uy1232o9y2"), - }, - }, - payloadID: constant.BinaryVersionPayload, - payload: nil, - }, - want: true, - }, { - name: "test-update-payload", - args: args{ - item: ¶metersv1alpha1.ConfigTemplateItemDetail{ - Payload: parametersv1alpha1.Payload{ - constant.BinaryVersionPayload: json.RawMessage("md5-12912uy1232o9y2"), - constant.ComponentResourcePayload: transformPayload(map[string]any{ - "limit": map[string]string{ - "cpu": "100m", - "memory": "100Mi", - }, - }), - }, - }, - payloadID: constant.ComponentResourcePayload, - payload: corev1.ResourceRequirements{ - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceCPU: resource.MustParse("200m"), - corev1.ResourceMemory: resource.MustParse("200m"), - }, - }, - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CheckAndPatchPayload(tt.args.item, tt.args.payloadID, tt.args.payload) - if (err != nil) != tt.wantErr { - t.Errorf("CheckAndPatchPayload() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("CheckAndPatchPayload() got = %v, want %v", got, tt.want) - } - }) - } -} - func Test_filterImmutableParameters(t *testing.T) { type args struct { parameters map[string]any @@ -644,8 +812,3 @@ func Test_filterImmutableParameters(t *testing.T) { }) } } - -func transformPayload(data interface{}) json.RawMessage { - raw, _ := buildPayloadAsUnstructuredObject(data) - return raw -} diff --git a/pkg/parameters/core/config_patch.go b/pkg/parameters/core/config_patch.go index 064a7a6e2c4..f012fb3eb50 100644 --- a/pkg/parameters/core/config_patch.go +++ b/pkg/parameters/core/config_patch.go @@ -90,7 +90,7 @@ func difference(base *cfgWrapper, target *cfgWrapper) (*ConfigPatchInfo, error) return reconfigureInfo, nil } -func TransformConfigPatchFromData(data map[string]string, configRender parametersv1alpha1.ParamConfigRendererSpec) (*ConfigPatchInfo, error) { +func TransformConfigPatchFromData(data map[string]string, configs []parametersv1alpha1.ComponentConfigDescription) (*ConfigPatchInfo, error) { emptyData := func(m map[string]string) map[string]string { r := make(map[string]string, len(m)) for key := range m { @@ -98,6 +98,6 @@ func TransformConfigPatchFromData(data map[string]string, configRender parameter } return r } - patch, _, err := CreateConfigPatch(emptyData(data), data, configRender, false) + patch, _, err := CreateConfigPatch(emptyData(data), data, configs, false) return patch, err } diff --git a/pkg/parameters/core/config_patch_test.go b/pkg/parameters/core/config_patch_test.go index cbb208fd6cf..6920f6aba83 100644 --- a/pkg/parameters/core/config_patch_test.go +++ b/pkg/parameters/core/config_patch_test.go @@ -131,11 +131,10 @@ func TestTransformConfigPatchFromData(t *testing.T) { testData := "[mysqld]\nmax_connections = 2000\ngeneral_log = OFF" t.Run("testConfigPatch", func(t *testing.T) { - got, err := TransformConfigPatchFromData(map[string]string{configFile: testData}, parametersv1alpha1.ParamConfigRendererSpec{ - Configs: []parametersv1alpha1.ComponentConfigDescription{{ - Name: "my.cnf", - FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}, - }}}) + got, err := TransformConfigPatchFromData(map[string]string{configFile: testData}, []parametersv1alpha1.ComponentConfigDescription{{ + Name: "my.cnf", + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}, + }}) require.Nil(t, err) require.True(t, got.IsModify) require.NotNil(t, got.UpdateConfig[configFile]) diff --git a/pkg/parameters/core/config_patch_util.go b/pkg/parameters/core/config_patch_util.go index b68c5e9e412..ed13a785456 100644 --- a/pkg/parameters/core/config_patch_util.go +++ b/pkg/parameters/core/config_patch_util.go @@ -32,20 +32,20 @@ import ( ) // CreateConfigPatch creates a patch for configuration files with different version. -func CreateConfigPatch(oldVersion, newVersion map[string]string, configRender parametersv1alpha1.ParamConfigRendererSpec, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { +func CreateConfigPatch(oldVersion, newVersion map[string]string, configs []parametersv1alpha1.ComponentConfigDescription, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { var hasFilesUpdated = false - var keys = ResolveConfigFiles(configRender.Configs) + var keys = ResolveConfigFiles(configs) if comparableAllFiles && len(keys) > 0 { hasFilesUpdated = checkExcludeConfigDifference(oldVersion, newVersion, keys) } - cmKeyFilter := NewConfigFileFilter(configRender.Configs) + cmKeyFilter := NewConfigFileFilter(configs) patch, err := CreateMergePatch( FromConfigData(oldVersion, cmKeyFilter), FromConfigData(newVersion, cmKeyFilter), CfgOption{ - FileFormatFn: WithConfigFileFormat(configRender.Configs), + FileFormatFn: WithConfigFileFormat(configs), Type: CfgTplType, Log: log.FromContext(context.TODO()), }) @@ -99,8 +99,8 @@ func FromConfigObject(name, config string, formatConfig *parametersv1alpha1.File // TransformConfigFileToKeyValueMap transforms a config file in appsv1alpha1.CfgFileFormat format to a map in which the key is config name and the value is config value // sectionName means the desired section of config file, such as [mysqld] section. // If config file has no section structure, sectionName should be default to get all values in this config file. -func TransformConfigFileToKeyValueMap(fileName string, configRender parametersv1alpha1.ParamConfigRendererSpec, configData []byte) (map[string]string, error) { - formatterConfig := ResolveConfigFormat(configRender.Configs, fileName) +func TransformConfigFileToKeyValueMap(fileName string, configs []parametersv1alpha1.ComponentConfigDescription, configData []byte) (map[string]string, error) { + formatterConfig := ResolveConfigFormat(configs, fileName) if formatterConfig == nil { return nil, fmt.Errorf("not found file formatter config: [%s]", fileName) } @@ -111,11 +111,11 @@ func TransformConfigFileToKeyValueMap(fileName string, configRender parametersv1 newData := map[string]string{ fileName: string(configData), } - patchInfo, _, err := CreateConfigPatch(oldData, newData, configRender, false) + patchInfo, _, err := CreateConfigPatch(oldData, newData, configs, false) if err != nil { return nil, err } - params := GenerateVisualizedParamsList(patchInfo, configRender.Configs) + params := GenerateVisualizedParamsList(patchInfo, configs) result := make(map[string]string) for _, param := range params { if param.Key != fileName { diff --git a/pkg/parameters/core/config_patch_util_test.go b/pkg/parameters/core/config_patch_util_test.go index eeb49285224..62b160979dd 100644 --- a/pkg/parameters/core/config_patch_util_test.go +++ b/pkg/parameters/core/config_patch_util_test.go @@ -302,8 +302,7 @@ max_connections=666 }) } } - configRender := parametersv1alpha1.ParamConfigRendererSpec{Configs: configs} - got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, configRender, tt.args.enableExcludeDiff) + got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, configs, tt.args.enableExcludeDiff) if (err != nil) != tt.wantErr { t.Errorf("CreateConfigPatch() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/parameters/core/reconfigure_util.go b/pkg/parameters/core/reconfigure_util.go index 229395331a7..8d5df8f9cc3 100644 --- a/pkg/parameters/core/reconfigure_util.go +++ b/pkg/parameters/core/reconfigure_util.go @@ -86,13 +86,13 @@ func trimNestedField(updatedParams any, trimField string) (any, error) { return updatedParams, nil } -// ValidateConfigPatch Verifies if the changed parameters have been removed -func ValidateConfigPatch(patch *ConfigPatchInfo, configRender parametersv1alpha1.ParamConfigRendererSpec) error { +// ValidateConfigPatch verifies if the changed parameters have been removed. +func ValidateConfigPatch(patch *ConfigPatchInfo, configs []parametersv1alpha1.ComponentConfigDescription) error { if !patch.IsModify || len(patch.UpdateConfig) == 0 { return nil } - vParams := GenerateVisualizedParamsList(patch, configRender.Configs) + vParams := GenerateVisualizedParamsList(patch, configs) for _, param := range vParams { for _, p := range param.Parameters { if p.Value == nil { diff --git a/pkg/parameters/parameter_schema.go b/pkg/parameters/parameter_schema.go index ce216d57d88..6252eb27eba 100644 --- a/pkg/parameters/parameter_schema.go +++ b/pkg/parameters/parameter_schema.go @@ -60,10 +60,6 @@ func ParametersDefinitionTerminalPhases(status parametersv1alpha1.ParametersDefi return status.ObservedGeneration == generation && status.Phase == parametersv1alpha1.PDAvailablePhase } -func ParametersDrivenConfigRenderTerminalPhases(status parametersv1alpha1.ParamConfigRendererStatus, generation int64) bool { - return status.ObservedGeneration == generation && status.Phase == parametersv1alpha1.PDAvailablePhase -} - func ParametersTerminalPhases(status parametersv1alpha1.ParameterStatus, generation int64) bool { return status.ObservedGeneration == generation && IsParameterFinished(status.Phase) } @@ -139,21 +135,3 @@ func GetConfigTemplateItem(parameterSpec *parametersv1alpha1.ComponentParameterS } return nil } - -func GetComponentConfigDescription(pdcr *parametersv1alpha1.ParamConfigRendererSpec, name string) *parametersv1alpha1.ComponentConfigDescription { - match := func(desc parametersv1alpha1.ComponentConfigDescription) bool { - return desc.Name == name - } - - if index := generics.FindFirstFunc(pdcr.Configs, match); index >= 0 { - return &pdcr.Configs[index] - } - return nil -} - -func GetComponentConfigDescriptions(pdcr *parametersv1alpha1.ParamConfigRendererSpec, tpl string) []parametersv1alpha1.ComponentConfigDescription { - match := func(desc parametersv1alpha1.ComponentConfigDescription) bool { - return desc.TemplateName == tpl - } - return generics.FindFunc(pdcr.Configs, match) -} diff --git a/pkg/parameters/parameter_utils.go b/pkg/parameters/parameter_utils.go index c37094d9240..2dd7b510606 100644 --- a/pkg/parameters/parameter_utils.go +++ b/pkg/parameters/parameter_utils.go @@ -37,26 +37,26 @@ func ClassifyParamsFromConfigTemplate(params parametersv1alpha1.ComponentParamet cmpd *appsv1.ComponentDefinition, paramsDefs []*parametersv1alpha1.ParametersDefinition, tpls map[string]*corev1.ConfigMap, - pcr *parametersv1alpha1.ParamConfigRenderer) ([]parametersv1alpha1.ConfigTemplateItemDetail, error) { + configs []parametersv1alpha1.ComponentConfigDescription) ([]parametersv1alpha1.ConfigTemplateItemDetail, error) { var itemDetails []parametersv1alpha1.ConfigTemplateItemDetail - classifyParams, err := ClassifyComponentParameters(params, paramsDefs, cmpd.Spec.Configs, tpls, pcr) + classifyParams, err := ClassifyComponentParameters(params, paramsDefs, cmpd.Spec.Configs, tpls, configs) if err != nil { return nil, err } - if !HasValidParameterTemplate(pcr) { + if !HasValidParameterTemplate(configs) { return nil, nil } - for _, template := range ResolveParameterTemplate(cmpd.Spec, pcr.Spec) { + for _, template := range ResolveParameterTemplate(cmpd.Spec, configs) { itemDetails = append(itemDetails, generateConfigTemplateItem(classifyParams, template)) } return itemDetails, nil } -func ResolveParameterTemplate(cmpd appsv1.ComponentDefinitionSpec, pcr parametersv1alpha1.ParamConfigRendererSpec) []appsv1.ComponentFileTemplate { +func ResolveParameterTemplate(cmpd appsv1.ComponentDefinitionSpec, configs []parametersv1alpha1.ComponentConfigDescription) []appsv1.ComponentFileTemplate { var templates []appsv1.ComponentFileTemplate - tpls := generics.Map(pcr.Configs, func(e parametersv1alpha1.ComponentConfigDescription) string { + tpls := generics.Map(configs, func(e parametersv1alpha1.ComponentConfigDescription) string { return e.TemplateName }) @@ -72,10 +72,6 @@ func ResolveParameterTemplate(cmpd appsv1.ComponentDefinitionSpec, pcr parameter return templates } -func HasValidParameterTemplate(pcr *parametersv1alpha1.ParamConfigRenderer) bool { - return pcr != nil && len(pcr.Spec.Configs) != 0 -} - func generateConfigTemplateItem(configParams map[string]map[string]*parametersv1alpha1.ParametersInFile, template appsv1.ComponentFileTemplate) parametersv1alpha1.ConfigTemplateItemDetail { itemDetail := parametersv1alpha1.ConfigTemplateItemDetail{ Name: template.Name, @@ -92,12 +88,12 @@ func ClassifyComponentParameters(parameters parametersv1alpha1.ComponentParamete parametersDefs []*parametersv1alpha1.ParametersDefinition, templates []appsv1.ComponentFileTemplate, tpls map[string]*corev1.ConfigMap, - pcr *parametersv1alpha1.ParamConfigRenderer) (map[string]map[string]*parametersv1alpha1.ParametersInFile, error) { - if len(parameters) == 0 || !HasValidParameterTemplate(pcr) { + configs []parametersv1alpha1.ComponentConfigDescription) (map[string]map[string]*parametersv1alpha1.ParametersInFile, error) { + if len(parameters) == 0 || !HasValidParameterTemplate(configs) { return nil, nil } if !hasValidParametersDefinition(parametersDefs) { - return transformDefaultParameters(parameters, pcr) + return transformDefaultParameters(parameters, configs) } classifyParams := make(map[string]map[string]*parametersv1alpha1.ParametersInFile, len(templates)) @@ -190,16 +186,16 @@ func hasValidParametersDefinition(defs []*parametersv1alpha1.ParametersDefinitio func transformDefaultParameters( parameters parametersv1alpha1.ComponentParameters, - pcr *parametersv1alpha1.ParamConfigRenderer) (map[string]map[string]*parametersv1alpha1.ParametersInFile, error) { + configs []parametersv1alpha1.ComponentConfigDescription) (map[string]map[string]*parametersv1alpha1.ParametersInFile, error) { match := func(config parametersv1alpha1.ComponentConfigDescription) bool { return config.TemplateName != "" && config.FileFormatConfig != nil } - configs := generics.FindFunc(pcr.Spec.Configs, match) - if len(configs) == 0 { + filteredConfigs := generics.FindFunc(configs, match) + if len(filteredConfigs) == 0 { return nil, fmt.Errorf("the component does not support parameters reconfigure") } - config := configs[0] + config := filteredConfigs[0] return map[string]map[string]*parametersv1alpha1.ParametersInFile{ config.TemplateName: { config.Name: ¶metersv1alpha1.ParametersInFile{ @@ -211,6 +207,13 @@ func transformDefaultParameters( func resolveConfigSpecFromParametersDefinition(templates []appsv1.ComponentFileTemplate, paramDef *parametersv1alpha1.ParametersDefinition, tpls map[string]*corev1.ConfigMap) *appsv1.ComponentFileTemplate { + if paramDef != nil && paramDef.Spec.TemplateName != "" { + for i, item := range templates { + if item.Name == paramDef.Spec.TemplateName { + return &templates[i] + } + } + } for i, item := range templates { tpl, ok := tpls[item.Name] if !ok { diff --git a/pkg/parameters/parameter_utils_test.go b/pkg/parameters/parameter_utils_test.go index 476588f89b8..f90b8cac8cd 100644 --- a/pkg/parameters/parameter_utils_test.go +++ b/pkg/parameters/parameter_utils_test.go @@ -35,7 +35,7 @@ import ( var _ = Describe("resource Fetcher", func() { var compDefObj *appsv1.ComponentDefinition var paramDef1, paramDef2 *configv1alpha1.ParametersDefinition - var pcr *configv1alpha1.ParamConfigRenderer + var configDescs []configv1alpha1.ComponentConfigDescription BeforeEach(func() { paramDef1 = testparameters.NewParametersDefinitionFactory("param_def1"). @@ -57,10 +57,10 @@ var _ = Describe("resource Fetcher", func() { param14: string }`).GetObject() - pcr = testparameters.NewParamConfigRendererFactory("test"). - SetConfigDescription("file1", configTemplateName, configv1alpha1.FileFormatConfig{Format: configv1alpha1.Ini}). - SetConfigDescription("file2", configTemplateName, configv1alpha1.FileFormatConfig{Format: configv1alpha1.Ini}). - GetObject() + configDescs = []configv1alpha1.ComponentConfigDescription{ + {Name: "file1", TemplateName: configTemplateName, FileFormatConfig: &configv1alpha1.FileFormatConfig{Format: configv1alpha1.Ini}}, + {Name: "file2", TemplateName: configTemplateName, FileFormatConfig: &configv1alpha1.FileFormatConfig{Format: configv1alpha1.Ini}}, + } compDefObj = allFieldsCompDefObj(false) // clusterObj, _, _ = newAllFieldsClusterObj(compDefObj, false) @@ -77,7 +77,7 @@ var _ = Describe("resource Fetcher", func() { configTemplateName: { Data: map[string]string{ "file1": "", - }}}, pcr) + }}}, configDescs) Expect(err).NotTo(HaveOccurred()) Expect(paramItems).Should(HaveLen(1)) Expect(paramItems[0].ConfigFileParams).Should(HaveLen(1)) @@ -98,7 +98,7 @@ var _ = Describe("resource Fetcher", func() { Data: map[string]string{ "file1": "", "file2": "", - }}}, pcr) + }}}, configDescs) Expect(err).NotTo(HaveOccurred()) Expect(paramItems).Should(HaveLen(1)) Expect(paramItems[0].ConfigFileParams).Should(HaveLen(2)) diff --git a/pkg/parameters/template_merger.go b/pkg/parameters/template_merger.go index 1dc5172e8bb..ac529b9a9a0 100644 --- a/pkg/parameters/template_merger.go +++ b/pkg/parameters/template_merger.go @@ -40,10 +40,10 @@ type TemplateMerger interface { type mergeContext struct { render.TemplateRender - template parametersv1alpha1.ConfigTemplateExtension - configSpec appsv1.ComponentFileTemplate - paramsDefs []*parametersv1alpha1.ParametersDefinition - configRender *parametersv1alpha1.ParamConfigRenderer + template parametersv1alpha1.ConfigTemplateExtension + configSpec appsv1.ComponentFileTemplate + paramsDefs []*parametersv1alpha1.ParametersDefinition + configs []parametersv1alpha1.ComponentConfigDescription } func (m *mergeContext) renderTemplate() (map[string]string, error) { @@ -56,7 +56,7 @@ func (m *mergeContext) renderTemplate() (map[string]string, error) { if err != nil { return nil, err } - if err := validateRenderedData(configs, m.paramsDefs, m.configRender); err != nil { + if err := validateRenderedData(configs, m.paramsDefs, m.configs); err != nil { return nil, err } return configs, nil @@ -83,10 +83,10 @@ type configOnlyAddMerger struct { } func (c *configPatcher) merge(baseData map[string]string, updatedData map[string]string, manager *valueManager) (map[string]string, error) { - if c.configRender == nil || len(c.configRender.Spec.Configs) == 0 { + if len(c.configs) == 0 { return nil, fmt.Errorf("not support patch merge policy") } - configPatch, err := core.TransformConfigPatchFromData(updatedData, c.configRender.Spec) + configPatch, err := core.TransformConfigPatchFromData(updatedData, c.configs) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func (c *configPatcher) merge(baseData map[string]string, updatedData map[string return baseData, nil } - params := core.GenerateVisualizedParamsList(configPatch, c.configRender.Spec.Configs) + params := core.GenerateVisualizedParamsList(configPatch, c.configs) mergedData := copyMap(baseData) for key, patch := range splitParameters(params) { v, ok := baseData[key] @@ -102,7 +102,7 @@ func (c *configPatcher) merge(baseData map[string]string, updatedData map[string mergedData[key] = updatedData[key] continue } - newConfig, err := core.ApplyConfigPatch([]byte(v), patch, core.ResolveConfigFormat(c.configRender.Spec.Configs, key), manager.BuildValueTransformer(key)) + newConfig, err := core.ApplyConfigPatch([]byte(v), patch, core.ResolveConfigFormat(c.configs, key), manager.BuildValueTransformer(key)) if err != nil { return nil, err } @@ -129,14 +129,14 @@ func NewTemplateMerger(template parametersv1alpha1.ConfigTemplateExtension, templateRender render.TemplateRender, configSpec appsv1.ComponentFileTemplate, paramsDefs []*parametersv1alpha1.ParametersDefinition, - configRender *parametersv1alpha1.ParamConfigRenderer, + configs []parametersv1alpha1.ComponentConfigDescription, ) (TemplateMerger, error) { templateData := &mergeContext{ configSpec: configSpec, template: template, TemplateRender: templateRender, paramsDefs: paramsDefs, - configRender: configRender, + configs: configs, } var merger TemplateMerger @@ -160,8 +160,8 @@ func mergerConfigTemplate(template parametersv1alpha1.ConfigTemplateExtension, configSpec appsv1.ComponentFileTemplate, baseData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, - configRender *parametersv1alpha1.ParamConfigRenderer) (map[string]string, error) { - templateMerger, err := NewTemplateMerger(template, templateRender, configSpec, paramsDefs, configRender) + configs []parametersv1alpha1.ComponentConfigDescription) (map[string]string, error) { + templateMerger, err := NewTemplateMerger(template, templateRender, configSpec, paramsDefs, configs) if err != nil { return nil, err } @@ -172,7 +172,7 @@ func mergerConfigTemplate(template parametersv1alpha1.ConfigTemplateExtension, if len(data) == 0 { return nil, nil } - return templateMerger.merge(baseData, data, NewValueManager(paramsDefs, configRender.Spec.Configs)) + return templateMerger.merge(baseData, data, NewValueManager(paramsDefs, configs)) } func splitParameters(params []core.VisualizedParam) map[string]map[string]*string { diff --git a/pkg/parameters/template_merger_test.go b/pkg/parameters/template_merger_test.go index e5ca890ec2d..9ff22fa03cc 100644 --- a/pkg/parameters/template_merger_test.go +++ b/pkg/parameters/template_merger_test.go @@ -27,7 +27,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -97,7 +96,7 @@ max_connections=666 templateBuilder render.TemplateRender configSpec appsv1.ComponentFileTemplate paramsDefs *parametersv1alpha1.ParametersDefinition - pdcr *parametersv1alpha1.ParamConfigRenderer + configDescs []parametersv1alpha1.ComponentConfigDescription baseCMObject *corev1.ConfigMap updatedCMObject *corev1.ConfigMap @@ -110,9 +109,11 @@ max_connections=666 paramsDefs = testparameters.NewParametersDefinitionFactory("test-pd"). SetConfigFile(testConfigName). GetObject() - pdcr = testparameters.NewParamConfigRendererFactory("test-pdcr"). - SetTemplateName(testConfigSpecName). - GetObject() + configDescs = []parametersv1alpha1.ComponentConfigDescription{{ + Name: testConfigName, + TemplateName: testConfigSpecName, + FileFormatConfig: paramsDefs.Spec.FileFormatConfig.DeepCopy(), + }} baseCMObject = &corev1.ConfigMap{ Data: map[string]string{ @@ -166,10 +167,6 @@ max_connections=666 updatedCMObject, jsonUpdatedCMObject, }), testutil.WithAnyTimes())) - mockClient.MockNListMethod(0, testutil.WithListReturned( - testutil.WithConstructListReturnedResult([]runtime.Object{pdcr}), - testutil.WithAnyTimes(), - )) }) AfterEach(func() { @@ -186,11 +183,11 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, configDescs) Expect(err).To(Succeed()) Expect(mergedData).Should(HaveLen(2)) - configReaders, err := cfgcore.LoadRawConfigObject(mergedData, pdcr.Spec.Configs[0].FileFormatConfig, []string{testConfigName}) + configReaders, err := cfgcore.LoadRawConfigObject(mergedData, configDescs[0].FileFormatConfig, []string{testConfigName}) Expect(err).Should(Succeed()) Expect(configReaders).Should(HaveLen(1)) configObject := configReaders[testConfigName] @@ -213,12 +210,12 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, configDescs) Expect(err).Should(Succeed()) Expect(mergedData).Should(HaveLen(2)) Expect(reflect.DeepEqual(mergedData, updatedCMObject.Data)).Should(BeTrue()) - configReaders, err := cfgcore.LoadRawConfigObject(mergedData, pdcr.Spec.Configs[0].FileFormatConfig, []string{testConfigName}) + configReaders, err := cfgcore.LoadRawConfigObject(mergedData, configDescs[0].FileFormatConfig, []string{testConfigName}) Expect(err).Should(Succeed()) Expect(configReaders).Should(HaveLen(1)) configObject := configReaders[testConfigName] @@ -238,7 +235,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, configDescs) Expect(err).ShouldNot(Succeed()) }) }) @@ -252,7 +249,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, configDescs) Expect(err).Should(Succeed()) Expect(reflect.DeepEqual(mergedData, updatedCMObject.Data)).Should(BeTrue()) }) @@ -267,7 +264,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, configDescs) Expect(err).ShouldNot(Succeed()) }) @@ -279,7 +276,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, ¶metersv1alpha1.ParamConfigRenderer{}) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, nil) Expect(err).Should(Succeed()) }) }) @@ -302,15 +299,18 @@ max_connections=666 mockData := map[string]string{ testConfigName: "{}", } - mockPcr := pdcr.DeepCopy() - mockPcr.Spec.Configs[0].FileFormatConfig = ¶metersv1alpha1.FileFormatConfig{ - Format: parametersv1alpha1.JSON, - } - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, mockData, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, mockPcr) + mockConfigs := []parametersv1alpha1.ComponentConfigDescription{{ + Name: testConfigName, + TemplateName: testConfigSpecName, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.JSON, + }, + }} + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, mockData, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, mockConfigs) Expect(err).To(Succeed()) Expect(mergedData).Should(HaveLen(1)) - configReaders, err := cfgcore.LoadRawConfigObject(mergedData, mockPcr.Spec.Configs[0].FileFormatConfig, []string{testConfigName}) + configReaders, err := cfgcore.LoadRawConfigObject(mergedData, mockConfigs[0].FileFormatConfig, []string{testConfigName}) Expect(err).Should(Succeed()) Expect(configReaders).Should(HaveLen(1)) configObject := configReaders[testConfigName] diff --git a/pkg/parameters/template_render.go b/pkg/parameters/template_render.go index 81afb328f85..8a07b3a49b8 100644 --- a/pkg/parameters/template_render.go +++ b/pkg/parameters/template_render.go @@ -31,10 +31,10 @@ import ( func RerenderParametersTemplate(reconcileCtx *render.ReconcileCtx, item parametersv1alpha1.ConfigTemplateItemDetail, - configRender *parametersv1alpha1.ParamConfigRenderer, + configs []parametersv1alpha1.ComponentConfigDescription, parametersDefs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { parametersValidate := func(m map[string]string) error { - return validateRenderedData(m, parametersDefs, configRender) + return validateRenderedData(m, parametersDefs, configs) } configSpec := *item.ConfigSpec @@ -56,7 +56,7 @@ func RerenderParametersTemplate(reconcileCtx *render.ReconcileCtx, configSpec, rerenderCMObj.Data, parametersDefs, - configRender) + configs) if err != nil { return nil, err } @@ -66,13 +66,13 @@ func RerenderParametersTemplate(reconcileCtx *render.ReconcileCtx, func ApplyParameters(item parametersv1alpha1.ConfigTemplateItemDetail, baseConfig *corev1.ConfigMap, - configRender *parametersv1alpha1.ParamConfigRenderer, + configs []parametersv1alpha1.ComponentConfigDescription, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { - if configRender == nil || len(configRender.Spec.Configs) == 0 { + if len(configs) == 0 { return nil, fmt.Errorf("not support parameter reconfigure") } - newData, err := DoMerge(baseConfig.Data, item.ConfigFileParams, paramsDefs, configRender.Spec.Configs) + newData, err := DoMerge(baseConfig.Data, item.ConfigFileParams, paramsDefs, configs) if err != nil { return nil, err } diff --git a/pkg/parameters/template_render_test.go b/pkg/parameters/template_render_test.go index 9117f45c34c..1451048e0fd 100644 --- a/pkg/parameters/template_render_test.go +++ b/pkg/parameters/template_render_test.go @@ -45,7 +45,7 @@ var _ = Describe("ToolsImageBuilderTest", func() { var compDefObj *appsv1.ComponentDefinition var clusterComponent *component.SynthesizedComponent var paramsDef *parametersv1alpha1.ParametersDefinition - var pdcr *parametersv1alpha1.ParamConfigRenderer + var configDescs []parametersv1alpha1.ComponentConfigDescription BeforeEach(func() { mockK8sCli = testutil.NewK8sMockClient() @@ -53,15 +53,12 @@ var _ = Describe("ToolsImageBuilderTest", func() { // Add any setup steps that needs to be executed before each test clusterObj, compDefObj, _ = newAllFieldsClusterObj(nil, false) paramsDef = testparameters.NewParametersDefinitionFactory(paramsDefName). - SetReloadAction(testparameters.WithNoneAction()). - GetObject() - clusterComponent = newAllFieldsSynthesizedComponent(compDefObj, clusterObj) - - pdcr = testparameters.NewParamConfigRendererFactory(pdcrName). - SetParametersDefs(paramsDef.Name). SetComponentDefinition(compDefObj.GetName()). SetTemplateName(configTemplateName). GetObject() + clusterComponent = newAllFieldsSynthesizedComponent(compDefObj, clusterObj) + + configDescs = BuildConfigDescriptionsFromParametersDefs([]*parametersv1alpha1.ParametersDefinition{paramsDef}) }) AfterEach(func() { @@ -100,21 +97,21 @@ var _ = Describe("ToolsImageBuilderTest", func() { CustomTemplates: customTemplate, } pds := []*parametersv1alpha1.ParametersDefinition{paramsDef} - cmObj, err := RerenderParametersTemplate(rctx, item, pdcr, pds) + cmObj, err := RerenderParametersTemplate(rctx, item, configDescs, pds) Expect(err).Should(Succeed()) - configdesc := pdcr.Spec.Configs[0] + configdesc := configDescs[0] if len(parameters) == 0 { configReaders, err := cfgcore.LoadRawConfigObject(cmObj.Data, configdesc.FileFormatConfig, []string{configdesc.Name}) Expect(err).Should(Succeed()) return configReaders[configdesc.Name] } - params, err := ClassifyComponentParameters(parameters, pds, []appsv1.ComponentFileTemplate{*item.ConfigSpec}, map[string]*corev1.ConfigMap{configTemplateName: tpl}, pdcr) + params, err := ClassifyComponentParameters(parameters, pds, []appsv1.ComponentFileTemplate{*item.ConfigSpec}, map[string]*corev1.ConfigMap{configTemplateName: tpl}, configDescs) Expect(err).Should(Succeed()) tplParams, ok := params[configTemplateName] Expect(ok).Should(BeTrue()) item.ConfigFileParams = deRef(tplParams) - result, err := ApplyParameters(item, cmObj, pdcr, pds) + result, err := ApplyParameters(item, cmObj, configDescs, pds) Expect(err).Should(Succeed()) configReaders, err := cfgcore.LoadRawConfigObject(result.Data, configdesc.FileFormatConfig, []string{configdesc.Name}) Expect(err).Should(Succeed()) diff --git a/pkg/parameters/template_validate.go b/pkg/parameters/template_validate.go index cebdf2c35a1..3e77ecf936f 100644 --- a/pkg/parameters/template_validate.go +++ b/pkg/parameters/template_validate.go @@ -26,8 +26,8 @@ import ( ) // validateRenderedData validates config file against constraint -func validateRenderedData(renderedData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, configRender *parametersv1alpha1.ParamConfigRenderer) error { - if len(paramsDefs) == 0 || configRender == nil || len(configRender.Spec.Configs) == 0 { +func validateRenderedData(renderedData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, configs []parametersv1alpha1.ComponentConfigDescription) error { + if len(paramsDefs) == 0 || len(configs) == 0 { return nil } for _, paramsDef := range paramsDefs { @@ -38,7 +38,7 @@ func validateRenderedData(renderedData map[string]string, paramsDefs []*paramete if _, ok := renderedData[fileName]; !ok { continue } - if fileConfig := resolveFileFormatConfig(configRender.Spec.Configs, fileName); fileConfig != nil { + if fileConfig := resolveFileFormatConfig(configs, fileName); fileConfig != nil { if err := validateConfigContent(renderedData[fileName], ¶msDef.Spec, fileConfig); err != nil { return err } diff --git a/pkg/testutil/parameters/paramconfigrenderer_factory.go b/pkg/testutil/parameters/paramconfigrenderer_factory.go deleted file mode 100644 index 00166f29a9e..00000000000 --- a/pkg/testutil/parameters/paramconfigrenderer_factory.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package parameters - -import ( - parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/generics" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" -) - -type MockParamConfigRendererFactory struct { - testapps.BaseFactory[parametersv1alpha1.ParamConfigRenderer, *parametersv1alpha1.ParamConfigRenderer, MockParamConfigRendererFactory] -} - -func NewParamConfigRendererFactory(name string) *MockParamConfigRendererFactory { - f := &MockParamConfigRendererFactory{} - f.Init("", name, ¶metersv1alpha1.ParamConfigRenderer{ - Spec: parametersv1alpha1.ParamConfigRendererSpec{ - Configs: []parametersv1alpha1.ComponentConfigDescription{ - { - Name: MysqlConfigFile, - FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ - Format: parametersv1alpha1.Ini, - FormatterAction: parametersv1alpha1.FormatterAction{ - IniConfig: ¶metersv1alpha1.IniConfig{ - SectionName: "mysqld", - }, - }, - }, - }, - }, - }, - }, f) - return f -} - -func (f *MockParamConfigRendererFactory) SetParametersDefs(paramsDefs ...string) *MockParamConfigRendererFactory { - f.Get().Spec.ParametersDefs = paramsDefs - return f -} - -func (f *MockParamConfigRendererFactory) SetComponentDefinition(cmpd string) *MockParamConfigRendererFactory { - f.Get().Spec.ComponentDef = cmpd - return f -} - -func (f *MockParamConfigRendererFactory) safeGetConfigDescription(key string) *parametersv1alpha1.ComponentConfigDescription { - desc := f.getComponentConfigDescription(&f.Get().Spec, key) - if desc != nil { - return desc - } - f.Get().Spec.Configs = append(f.Get().Spec.Configs, parametersv1alpha1.ComponentConfigDescription{ - Name: key, - }) - return f.getComponentConfigDescription(&f.Get().Spec, key) -} - -func (f *MockParamConfigRendererFactory) SetConfigDescription(key, tpl string, formatter parametersv1alpha1.FileFormatConfig) *MockParamConfigRendererFactory { - desc := f.safeGetConfigDescription(key) - desc.TemplateName = tpl - desc.FileFormatConfig = formatter.DeepCopy() - return f -} - -func (f *MockParamConfigRendererFactory) SetTemplateName(tpl string) *MockParamConfigRendererFactory { - desc := f.safeGetConfigDescription(MysqlConfigFile) - desc.TemplateName = tpl - return f -} - -func (f *MockParamConfigRendererFactory) HScaleEnabled() *MockParamConfigRendererFactory { - desc := f.safeGetConfigDescription(MysqlConfigFile) - desc.ReRenderResourceTypes = append(desc.ReRenderResourceTypes, parametersv1alpha1.ComponentHScaleType) - return f -} - -func (f *MockParamConfigRendererFactory) TLSEnabled() *MockParamConfigRendererFactory { - desc := f.safeGetConfigDescription(MysqlConfigFile) - desc.ReRenderResourceTypes = append(desc.ReRenderResourceTypes, parametersv1alpha1.ComponentTLSType) - return f -} - -func (f *MockParamConfigRendererFactory) VScaleEnabled() *MockParamConfigRendererFactory { - desc := f.safeGetConfigDescription(MysqlConfigFile) - desc.ReRenderResourceTypes = append(desc.ReRenderResourceTypes, parametersv1alpha1.ComponentVScaleType) - return f -} - -func (f *MockParamConfigRendererFactory) getComponentConfigDescription(pdcr *parametersv1alpha1.ParamConfigRendererSpec, name string) *parametersv1alpha1.ComponentConfigDescription { - match := func(desc parametersv1alpha1.ComponentConfigDescription) bool { - return desc.Name == name - } - if index := generics.FindFirstFunc(pdcr.Configs, match); index >= 0 { - return &pdcr.Configs[index] - } - return nil -} diff --git a/pkg/testutil/parameters/parametersdefinition_factory.go b/pkg/testutil/parameters/parametersdefinition_factory.go index 6715bc507ba..cdbfadfef37 100644 --- a/pkg/testutil/parameters/parametersdefinition_factory.go +++ b/pkg/testutil/parameters/parametersdefinition_factory.go @@ -33,13 +33,45 @@ func NewParametersDefinitionFactory(name string) *MockParametersDefinitionFactor f := &MockParametersDefinitionFactory{} f.Init("", name, ¶metersv1alpha1.ParametersDefinition{ Spec: parametersv1alpha1.ParametersDefinitionSpec{ - FileName: MysqlConfigFile, - ReloadAction: WithNoneAction(), + FileName: MysqlConfigFile, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{ + SectionName: "mysqld", + }, + }, + }, }, }, f) return f } +func (f *MockParametersDefinitionFactory) SetComponentDefinition(name string) *MockParametersDefinitionFactory { + f.Get().Spec.ComponentDef = name + return f +} + +func (f *MockParametersDefinitionFactory) SetServiceVersion(version string) *MockParametersDefinitionFactory { + f.Get().Spec.ServiceVersion = version + return f +} + +func (f *MockParametersDefinitionFactory) SetTemplateName(name string) *MockParametersDefinitionFactory { + f.Get().Spec.TemplateName = name + return f +} + +func (f *MockParametersDefinitionFactory) SetConfigFile(name string) *MockParametersDefinitionFactory { + f.Get().Spec.FileName = name + return f +} + +func (f *MockParametersDefinitionFactory) SetFileFormatConfig(cfg parametersv1alpha1.FileFormatConfig) *MockParametersDefinitionFactory { + f.Get().Spec.FileFormatConfig = &cfg + return f +} + func (f *MockParametersDefinitionFactory) Schema(cue string) *MockParametersDefinitionFactory { openAPISchema, _ := openapi.GenerateOpenAPISchema(cue, "") f.Get().Spec.ParametersSchema = ¶metersv1alpha1.ParametersSchema{ @@ -49,11 +81,6 @@ func (f *MockParametersDefinitionFactory) Schema(cue string) *MockParametersDefi return f } -func (f *MockParametersDefinitionFactory) SetConfigFile(name string) *MockParametersDefinitionFactory { - f.Get().Spec.FileName = name - return f -} - func (f *MockParametersDefinitionFactory) StaticParameters(params []string) *MockParametersDefinitionFactory { f.Get().Spec.StaticParameters = params return f @@ -68,16 +95,3 @@ func (f *MockParametersDefinitionFactory) ImmutableParameters(params []string) * f.Get().Spec.ImmutableParameters = params return f } - -func (f *MockParametersDefinitionFactory) SetReloadAction(action *parametersv1alpha1.ReloadAction) *MockParametersDefinitionFactory { - f.Get().Spec.ReloadAction = action - return f -} - -func WithNoneAction() *parametersv1alpha1.ReloadAction { - return ¶metersv1alpha1.ReloadAction{ - AutoTrigger: ¶metersv1alpha1.AutoTrigger{ - ProcessName: "", - }, - } -}