From ba50f1ce98d32c5f434319a0765ff33f4ece8c74 Mon Sep 17 00:00:00 2001 From: sophon Date: Fri, 6 Dec 2024 18:29:41 +0800 Subject: [PATCH 1/6] chore: parameters controller impl --- apis/apps/v1/componentdefinition_types.go | 77 +- apis/apps/v1/zz_generated.deepcopy.go | 43 +- apis/operations/v1alpha1/opsrequest_types.go | 102 +-- .../v1alpha1/opsrequest_validation.go | 57 -- .../v1alpha1/zz_generated.deepcopy.go | 74 +- .../v1alpha1/componentparameter_types.go | 4 + apis/parameters/v1alpha1/types.go | 1 + cmd/manager/main.go | 20 +- cmd/reloader/app/proxy.go | 6 +- ...ps.kubeblocks.io_componentdefinitions.yaml | 86 --- .../operations.kubeblocks.io_opsrequests.yaml | 258 +------ ...ers.kubeblocks.io_componentparameters.yaml | 7 - config/rbac/role.yaml | 104 +-- .../apps/componentdefinition_controller.go | 6 +- .../apps/sidecardefinition_controller.go | 30 +- controllers/apps/suite_test.go | 22 +- .../apps/transformer_component_parameters.go | 21 +- .../apps/transformer_component_status.go | 9 +- .../apps/transformer_component_tls_test.go | 32 +- controllers/apps/utils.go | 23 + controllers/operations/suite_test.go | 16 +- .../parameters/combine_upgrade_policy.go | 21 +- .../parameters/combine_upgrade_policy_test.go | 16 +- .../componentparameter_controller.go | 211 ++++++ .../componentparameter_controller_test.go | 141 ++++ controllers/parameters/config_annotation.go | 24 +- .../parameters/config_reconcile_wrapper.go | 14 +- controllers/parameters/config_util.go | 378 ++-------- controllers/parameters/config_util_test.go | 202 ------ .../parameters/configconstraint_controller.go | 125 ---- .../configconstraint_controller_test.go | 130 ---- .../parameters/configuration_controller.go | 243 ------- .../configuration_controller_test.go | 188 ----- controllers/parameters/configuration_test.go | 121 ++-- .../parameters/parallel_upgrade_policy.go | 29 +- .../parallel_upgrade_policy_test.go | 10 +- .../paramconfigrenderer_controller.go | 2 +- .../paramconfigrenderer_controller_test.go | 2 +- .../parameters/parameter_controller.go | 225 ++++++ .../parameters/parameter_controller_test.go | 143 ++++ controllers/parameters/parameter_util.go | 256 +++++++ controllers/parameters/policy_util.go | 174 ++++- controllers/parameters/policy_util_test.go | 79 +-- controllers/parameters/reconcile_task.go | 158 +++-- .../parameters/reconfigure_controller.go | 161 ++--- controllers/parameters/reconfigure_policy.go | 122 ++-- controllers/parameters/relatedresource.go | 40 +- .../{simple_policy.go => restart_policy.go} | 36 +- ..._policy_test.go => restart_policy_test.go} | 18 +- controllers/parameters/revision.go | 6 +- controllers/parameters/revision_test.go | 9 +- .../parameters/rolling_upgrade_policy.go | 101 ++- .../parameters/rolling_upgrade_policy_test.go | 169 +++++ controllers/parameters/suite_test.go | 44 +- controllers/parameters/sync_upgrade_policy.go | 77 +- .../parameters/sync_upgrade_policy_test.go | 30 +- controllers/parameters/types.go | 4 +- controllers/trace/reconciler_tree.go | 2 +- deploy/helm/config/rbac/role.yaml | 104 +-- ...ps.kubeblocks.io_componentdefinitions.yaml | 86 --- .../operations.kubeblocks.io_opsrequests.yaml | 258 +------ ...ers.kubeblocks.io_componentparameters.yaml | 7 - docs/developer_docs/api-reference/cluster.md | 145 +--- pkg/configuration/config_manager/builder.go | 24 +- .../config_manager/builder_test.go | 81 ++- .../config_manager/config_handler.go | 54 +- .../config_manager/config_handler_test.go | 78 +-- .../config_manager/handler_util.go | 140 ++-- .../config_manager/handler_util_test.go | 317 ++++----- pkg/configuration/config_manager/proc_util.go | 4 +- .../config_manager/reload_util.go | 17 +- .../config_manager/reload_util_test.go | 79 +-- .../config_manager/signal_darwin.go | 66 +- .../config_manager/signal_linux.go | 66 +- pkg/configuration/config_manager/type.go | 27 +- pkg/configuration/core/config.go | 55 +- pkg/configuration/core/config_patch.go | 6 +- pkg/configuration/core/config_patch_test.go | 12 +- pkg/configuration/core/config_patch_util.go | 61 +- .../core/config_patch_util_test.go | 176 ++--- pkg/configuration/core/config_query.go | 4 +- pkg/configuration/core/config_test.go | 42 +- pkg/configuration/core/config_util.go | 47 +- pkg/configuration/core/config_util_test.go | 660 +++++++++--------- pkg/configuration/core/reconfigure_util.go | 103 ++- .../core/reconfigure_util_test.go | 368 +++++----- pkg/configuration/core/suite_test.go | 2 +- pkg/configuration/core/type.go | 8 +- pkg/configuration/proto/reconfigure.pb.go | 71 +- pkg/configuration/proto/reconfigure.proto | 1 + .../proto/reconfigure_grpc.pb.go | 3 +- pkg/configuration/validate/config_validate.go | 112 +-- .../validate/config_validate_test.go | 79 +-- pkg/configuration/validate/cue_util.go | 8 +- pkg/constant/payload.go | 1 + .../builder/builder_component_definition.go | 20 +- .../builder_component_parameter_test.go | 4 +- .../builder/builder_configuration.go | 102 --- .../builder/builder_configuration_test.go | 58 -- pkg/controller/component/component_test.go | 56 -- pkg/controller/component/sidecar.go | 12 +- .../component/synthesize_component.go | 14 +- .../component/synthesize_component_test.go | 16 +- pkg/controller/component/type.go | 2 +- .../configuration/annotation_utils.go | 13 - pkg/controller/configuration/config_utils.go | 191 +++-- .../configuration/configuration_test.go | 24 +- pkg/controller/configuration/envfrom_utils.go | 234 ++----- .../configuration/envfrom_utils_test.go | 152 ++-- pkg/controller/configuration/mutation.go | 71 ++ pkg/controller/configuration/operator.go | 19 +- pkg/controller/configuration/operator_test.go | 138 +--- .../configuration/parameter_utils.go | 5 +- pkg/controller/configuration/patch_merger.go | 19 +- pkg/controller/configuration/pipeline.go | 240 +++---- pkg/controller/configuration/pipeline_test.go | 101 ++- .../configuration/resource_wrapper.go | 8 +- .../configuration/template_merger.go | 69 +- .../configuration/template_merger_test.go | 79 +-- .../configuration/template_wrapper.go | 175 ++--- .../configuration/tool_image_builder.go | 18 +- .../configuration/tool_image_builder_test.go | 11 +- pkg/controller/factory/builder.go | 4 +- pkg/controller/factory/builder_test.go | 22 +- pkg/controller/plan/prepare_test.go | 64 +- pkg/controller/plan/suite_test.go | 4 + pkg/controller/render/template_render_test.go | 7 +- pkg/controllerutil/config_util.go | 148 ++-- pkg/controllerutil/config_util_test.go | 195 +++--- pkg/dataprotection/backup/scheduler.go | 23 +- pkg/generics/slices.go | 8 + pkg/operations/ops_util.go | 30 - pkg/operations/reconfigure.go | 267 +------ pkg/operations/reconfigure_pipeline.go | 253 ------- pkg/operations/reconfigure_pipeline_test.go | 347 --------- pkg/operations/reconfigure_test.go | 301 +------- pkg/operations/reconfigure_util.go | 265 ------- pkg/operations/reconfigure_util_test.go | 51 -- pkg/operations/suite_test.go | 3 + pkg/operations/type.go | 12 - .../apps/componentdefinition_factory.go | 24 +- pkg/testutil/apps/constant.go | 11 +- pkg/testutil/k8s/k8sclient_util.go | 41 +- .../parameters/componenttemplate_factory.go | 10 - pkg/unstructured/config_object.go | 12 +- pkg/unstructured/properties.go | 4 +- pkg/unstructured/properties_test.go | 8 +- pkg/unstructured/redis_config.go | 4 +- pkg/unstructured/redis_config_test.go | 10 +- pkg/unstructured/viper_wrap.go | 23 +- pkg/unstructured/viper_wrap_test.go | 8 +- pkg/unstructured/xml_config.go | 4 +- pkg/unstructured/xml_config_test.go | 6 +- pkg/unstructured/yaml_config.go | 4 +- pkg/unstructured/yaml_config_test.go | 6 +- 155 files changed, 4672 insertions(+), 7439 deletions(-) create mode 100644 controllers/parameters/componentparameter_controller.go create mode 100644 controllers/parameters/componentparameter_controller_test.go delete mode 100644 controllers/parameters/config_util_test.go delete mode 100644 controllers/parameters/configconstraint_controller.go delete mode 100644 controllers/parameters/configconstraint_controller_test.go delete mode 100644 controllers/parameters/configuration_controller.go delete mode 100644 controllers/parameters/configuration_controller_test.go create mode 100644 controllers/parameters/parameter_controller.go create mode 100644 controllers/parameters/parameter_controller_test.go create mode 100644 controllers/parameters/parameter_util.go rename controllers/parameters/{simple_policy.go => restart_policy.go} (59%) rename controllers/parameters/{simple_policy_test.go => restart_policy_test.go} (92%) create mode 100644 controllers/parameters/rolling_upgrade_policy_test.go delete mode 100644 pkg/controller/builder/builder_configuration.go delete mode 100644 pkg/controller/builder/builder_configuration_test.go create mode 100644 pkg/controller/configuration/mutation.go delete mode 100644 pkg/operations/reconfigure_pipeline.go delete mode 100644 pkg/operations/reconfigure_pipeline_test.go delete mode 100644 pkg/operations/reconfigure_util.go delete mode 100644 pkg/operations/reconfigure_util_test.go diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go index d602b67f9c0..b111743aa51 100644 --- a/apis/apps/v1/componentdefinition_types.go +++ b/apis/apps/v1/componentdefinition_types.go @@ -318,7 +318,7 @@ type ComponentDefinitionSpec struct { // +listType=map // +listMapKey=name // +optional - Configs []ComponentConfigSpec `json:"configs,omitempty"` + Configs []ComponentTemplateSpec `json:"configs,omitempty"` // Defines the types of logs generated by instances of the Component and their corresponding file paths. // These logs can be collected for further analysis and monitoring. @@ -1056,81 +1056,6 @@ type ComponentTemplateSpec struct { DefaultMode *int32 `json:"defaultMode,omitempty" protobuf:"varint,3,opt,name=defaultMode"` } -type ComponentConfigSpec struct { - ComponentTemplateSpec `json:",inline"` - - // Specifies the configuration files within the ConfigMap that support dynamic updates. - // - // A configuration template (provided in the form of a ConfigMap) may contain templates for multiple - // configuration files. - // Each configuration file corresponds to a key in the ConfigMap. - // Some of these configuration files may support dynamic modification and reloading without requiring - // a pod restart. - // - // If empty or omitted, all configuration files in the ConfigMap are assumed to support dynamic updates, - // and ConfigConstraint applies to all keys. - // - // +listType=set - // +optional - Keys []string `json:"keys,omitempty"` - - // Specifies the name of the referenced configuration constraints object. - // - // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:Pattern:=`^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$` - // +optional - ConfigConstraintRef string `json:"constraintRef,omitempty"` - - // Specifies the containers to inject the ConfigMap parameters as environment variables. - // - // This is useful when application images accept parameters through environment variables and - // generate the final configuration file in the startup script based on these variables. - // - // This field allows users to specify a list of container names, and KubeBlocks will inject the environment - // variables converted from the ConfigMap into these designated containers. This provides a flexible way to - // pass the configuration items from the ConfigMap to the container without modifying the image. - // - // Deprecated: `asEnvFrom` has been deprecated since 0.9.0 and will be removed in 0.10.0. - // Use `injectEnvTo` instead. - // - // +kubebuilder:deprecatedversion:warning="This field has been deprecated since 0.9.0 and will be removed in 0.10.0" - // +listType=set - // +optional - AsEnvFrom []string `json:"asEnvFrom,omitempty"` - - // Specifies the containers to inject the ConfigMap parameters as environment variables. - // - // This is useful when application images accept parameters through environment variables and - // generate the final configuration file in the startup script based on these variables. - // - // This field allows users to specify a list of container names, and KubeBlocks will inject the environment - // variables converted from the ConfigMap into these designated containers. This provides a flexible way to - // pass the configuration items from the ConfigMap to the container without modifying the image. - // - // - // +listType=set - // +optional - InjectEnvTo []string `json:"injectEnvTo,omitempty"` - - // Specifies whether the configuration needs to be re-rendered after v-scale or h-scale operations to reflect changes. - // - // In some scenarios, the configuration may need to be updated to reflect the changes in resource allocation - // or cluster topology. Examples: - // - // - Redis: adjust maxmemory after v-scale operation. - // - MySQL: increase max connections after v-scale operation. - // - Zookeeper: update zoo.cfg with new node addresses after h-scale operation. - // - // +listType=set - // +optional - ReRenderResourceTypes []RerenderResourceType `json:"reRenderResourceTypes,omitempty"` - - // Whether to store the final rendered parameters as a secret. - // - // +optional - AsSecret *bool `json:"asSecret,omitempty"` -} - type ConfigTemplateExtension struct { // Specifies the name of the referenced configuration template ConfigMap object. // diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go index cbe616e24d1..538ea46bcd9 100644 --- a/apis/apps/v1/zz_generated.deepcopy.go +++ b/apis/apps/v1/zz_generated.deepcopy.go @@ -1010,47 +1010,6 @@ func (in *ComponentAvailableWithProbe) DeepCopy() *ComponentAvailableWithProbe { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentConfigSpec) DeepCopyInto(out *ComponentConfigSpec) { - *out = *in - in.ComponentTemplateSpec.DeepCopyInto(&out.ComponentTemplateSpec) - if in.Keys != nil { - in, out := &in.Keys, &out.Keys - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.AsEnvFrom != nil { - in, out := &in.AsEnvFrom, &out.AsEnvFrom - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.InjectEnvTo != nil { - in, out := &in.InjectEnvTo, &out.InjectEnvTo - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ReRenderResourceTypes != nil { - in, out := &in.ReRenderResourceTypes, &out.ReRenderResourceTypes - *out = make([]RerenderResourceType, len(*in)) - copy(*out, *in) - } - if in.AsSecret != nil { - in, out := &in.AsSecret, &out.AsSecret - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigSpec. -func (in *ComponentConfigSpec) DeepCopy() *ComponentConfigSpec { - if in == nil { - return nil - } - out := new(ComponentConfigSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ComponentDefinition) DeepCopyInto(out *ComponentDefinition) { *out = *in @@ -1154,7 +1113,7 @@ func (in *ComponentDefinitionSpec) DeepCopyInto(out *ComponentDefinitionSpec) { } if in.Configs != nil { in, out := &in.Configs, &out.Configs - *out = make([]ComponentConfigSpec, len(*in)) + *out = make([]ComponentTemplateSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/apis/operations/v1alpha1/opsrequest_types.go b/apis/operations/v1alpha1/opsrequest_types.go index 8a1d32005b1..a4252fa9a66 100644 --- a/apis/operations/v1alpha1/opsrequest_types.go +++ b/apis/operations/v1alpha1/opsrequest_types.go @@ -524,31 +524,12 @@ type Reconfigure struct { // Specifies the name of the Component. ComponentOps `json:",inline"` - // Contains a list of ConfigurationItem objects, specifying the Component's configuration template name, - // upgrade policy, and parameter key-value pairs to be updated. + // Specifies a list of key-value pairs representing parameters and their corresponding values + // within a single configuration file. + // This field is used to override or set the values of parameters without modifying the entire configuration file. // - // +kubebuilder:validation:Required - // +kubebuilder:validation:MinItems=1 - // +patchMergeKey=name - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=name - Configurations []ConfigurationItem `json:"configurations" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` - - // Indicates the duration for which the parameter changes are valid. - // +optional - // TTL *int64 `json:"ttl,omitempty"` - - // Specifies the time when the parameter changes should be applied. - // +kubebuilder:validation:MaxLength=19 - // +kubebuilder:validation:MinLength=19 - // +kubebuilder:validation:Pattern:=`^([0-9]{2})/([0-9]{2})/([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$` - // +optional - // TriggeringTime *string `json:"triggeringTime,omitempty"` - - // Identifies the component to be reconfigured. // +optional - // Selector *metav1.LabelSelector `json:"selector,omitempty"` + Parameters []appsv1.ComponentParameter `json:"parameters,omitempty"` } type ConfigurationItem struct { @@ -1016,10 +997,6 @@ type OpsRequestStatus struct { // +optional CancelTimestamp metav1.Time `json:"cancelTimestamp,omitempty"` - // Records the status of a reconfiguring operation if `opsRequest.spec.type` equals to "Reconfiguring". - // +optional - ReconfiguringStatusAsComponent map[string]*ReconfiguringStatus `json:"reconfiguringStatusAsComponent,omitempty"` - // Describes the detailed status of the OpsRequest. // Possible condition types include "Cancelled", "WaitForProgressing", "Validated", "Succeed", "Failed", "Restarting", // "VerticalScaling", "HorizontalScaling", "VolumeExpanding", "Reconfigure", "Switchover", "Stopping", "Starting", @@ -1177,77 +1154,6 @@ type PreCheckResult struct { Message string `json:"message,omitempty"` } -type ReconfiguringStatus struct { - // Describes the reconfiguring detail status. - // Possible condition types include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - // "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty"` - - // Describes the status of the component reconfiguring. - // +kubebuilder:validation:Required - // +patchMergeKey=name - // +patchStrategy=merge,retainKeys - // +listType=map - // +listMapKey=name - ConfigurationStatus []ConfigurationItemStatus `json:"configurationStatus"` -} - -type ConfigurationItemStatus struct { - // Indicates the name of the configuration template (as ConfigMap). - // +kubebuilder:validation:Required - // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:Pattern:=`^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$` - Name string `json:"name"` - - // Records the UpgradePolicy of the configuration change operation. - // +optional - UpdatePolicy appsv1alpha1.UpgradePolicy `json:"updatePolicy,omitempty"` - - // Represents the current state of the reconfiguration state machine. - // Possible values include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - // "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - // +optional - Status string `json:"status,omitempty"` - - // Provides details about the operation. - // +optional - Message string `json:"message,omitempty"` - - // Records the number of pods successfully updated following a configuration change. - // +kubebuilder:default=0 - // +optional - SucceedCount int32 `json:"succeedCount"` - - // Represents the total count of pods intended to be updated by a configuration change. - // +kubebuilder:default=-1 - // +optional - ExpectedCount int32 `json:"expectedCount"` - - // Records the last state of the reconfiguration finite state machine. - // Possible values include "None", "Retry", "Failed", "NotSupport", "FailedAndRetry". - // - // - "None" describes fsm has finished and quit. - // - "Retry" describes fsm is running. - // - "Failed" describes fsm is failed and exited. - // - "NotSupport" describes fsm does not support the feature. - // - "FailedAndRetry" describes fsm is failed in current state, but can be retried. - // +optional - LastAppliedStatus string `json:"lastStatus,omitempty"` - - // Stores the last applied configuration. - // +optional - LastAppliedConfiguration map[string]string `json:"lastAppliedConfiguration,omitempty"` - - // Contains the updated parameters. - // +optional - UpdatedParameters UpdatedParameters `json:"updatedParameters"` -} - // UpdatedParameters holds details about the modifications made to configuration parameters. // Example: // diff --git a/apis/operations/v1alpha1/opsrequest_validation.go b/apis/operations/v1alpha1/opsrequest_validation.go index 57ce7cb3411..c178d466052 100644 --- a/apis/operations/v1alpha1/opsrequest_validation.go +++ b/apis/operations/v1alpha1/opsrequest_validation.go @@ -139,8 +139,6 @@ func (r *OpsRequest) ValidateOps(ctx context.Context, return r.validateVolumeExpansion(ctx, k8sClient, cluster) case RestartType: return r.validateRestart(cluster) - case ReconfiguringType: - return r.validateReconfigure(ctx, k8sClient, cluster) case SwitchoverType: return r.validateSwitchover(cluster) case ExposeType: @@ -245,61 +243,6 @@ func (r *OpsRequest) validateVerticalScaling(cluster *appsv1.Cluster) error { return r.checkComponentExistence(cluster, compOpsList) } -// validateVerticalScaling validate api is legal when spec.type is VerticalScaling -func (r *OpsRequest) validateReconfigure(ctx context.Context, - k8sClient client.Client, - cluster *appsv1.Cluster) error { - if len(r.Spec.Reconfigures) == 0 { - return notEmptyError("spec.reconfigures") - } - for _, reconfigure := range r.Spec.Reconfigures { - if err := r.validateReconfigureParams(ctx, k8sClient, cluster, &reconfigure); err != nil { - return err - } - } - return nil -} - -func (r *OpsRequest) validateReconfigureParams(ctx context.Context, - k8sClient client.Client, - cluster *appsv1.Cluster, - reconfigure *Reconfigure) error { - if cluster.Spec.GetComponentByName(reconfigure.ComponentName) == nil { - return fmt.Errorf("component %s not found", reconfigure.ComponentName) - } - for _, configuration := range reconfigure.Configurations { - cmObj, err := r.getConfigMap(ctx, k8sClient, fmt.Sprintf("%s-%s-%s", r.Spec.GetClusterName(), reconfigure.ComponentName, configuration.Name)) - if err != nil { - return err - } - for _, key := range configuration.Keys { - // check add file - if _, ok := cmObj.Data[key.Key]; !ok && key.FileContent == "" { - return errors.Errorf("key %s not found in configmap %s", key.Key, configuration.Name) - } - if key.FileContent == "" && len(key.Parameters) == 0 { - return errors.New("key.fileContent and key.parameters cannot be empty at the same time") - } - } - } - return nil -} - -func (r *OpsRequest) getConfigMap(ctx context.Context, - k8sClient client.Client, - cmName string) (*corev1.ConfigMap, error) { - cmObj := &corev1.ConfigMap{} - cmKey := client.ObjectKey{ - Namespace: r.Namespace, - Name: cmName, - } - - if err := k8sClient.Get(ctx, cmKey, cmObj); err != nil { - return nil, err - } - return cmObj, nil -} - // compareRequestsAndLimits compares the resource requests and limits func compareRequestsAndLimits(resources corev1.ResourceRequirements) (string, error) { requests := resources.Requests diff --git a/apis/operations/v1alpha1/zz_generated.deepcopy.go b/apis/operations/v1alpha1/zz_generated.deepcopy.go index a6e0f7bb3c6..a4cce6162a7 100644 --- a/apis/operations/v1alpha1/zz_generated.deepcopy.go +++ b/apis/operations/v1alpha1/zz_generated.deepcopy.go @@ -156,29 +156,6 @@ func (in *ConfigurationItem) DeepCopy() *ConfigurationItem { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigurationItemStatus) DeepCopyInto(out *ConfigurationItemStatus) { - *out = *in - if in.LastAppliedConfiguration != nil { - in, out := &in.LastAppliedConfiguration, &out.LastAppliedConfiguration - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - in.UpdatedParameters.DeepCopyInto(&out.UpdatedParameters) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationItemStatus. -func (in *ConfigurationItemStatus) DeepCopy() *ConfigurationItemStatus { - if in == nil { - return nil - } - out := new(ConfigurationItemStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomOps) DeepCopyInto(out *CustomOps) { *out = *in @@ -840,22 +817,6 @@ func (in *OpsRequestStatus) DeepCopyInto(out *OpsRequestStatus) { in.StartTimestamp.DeepCopyInto(&out.StartTimestamp) in.CompletionTimestamp.DeepCopyInto(&out.CompletionTimestamp) in.CancelTimestamp.DeepCopyInto(&out.CancelTimestamp) - if in.ReconfiguringStatusAsComponent != nil { - in, out := &in.ReconfiguringStatusAsComponent, &out.ReconfiguringStatusAsComponent - *out = make(map[string]*ReconfiguringStatus, len(*in)) - for key, val := range *in { - var outVal *ReconfiguringStatus - if val == nil { - (*out)[key] = nil - } else { - inVal := (*in)[key] - in, out := &inVal, &outVal - *out = new(ReconfiguringStatus) - (*in).DeepCopyInto(*out) - } - (*out)[key] = outVal - } - } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]metav1.Condition, len(*in)) @@ -1260,9 +1221,9 @@ func (in *RebuildInstance) DeepCopy() *RebuildInstance { func (in *Reconfigure) DeepCopyInto(out *Reconfigure) { *out = *in out.ComponentOps = in.ComponentOps - if in.Configurations != nil { - in, out := &in.Configurations, &out.Configurations - *out = make([]ConfigurationItem, len(*in)) + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]appsv1.ComponentParameter, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -1279,35 +1240,6 @@ func (in *Reconfigure) DeepCopy() *Reconfigure { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReconfiguringStatus) DeepCopyInto(out *ReconfiguringStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ConfigurationStatus != nil { - in, out := &in.ConfigurationStatus, &out.ConfigurationStatus - *out = make([]ConfigurationItemStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReconfiguringStatus. -func (in *ReconfiguringStatus) DeepCopy() *ReconfiguringStatus { - if in == nil { - return nil - } - out := new(ReconfiguringStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RefNamespaceName) DeepCopyInto(out *RefNamespaceName) { *out = *in diff --git a/apis/parameters/v1alpha1/componentparameter_types.go b/apis/parameters/v1alpha1/componentparameter_types.go index 1412e96511f..7f9a2efabac 100644 --- a/apis/parameters/v1alpha1/componentparameter_types.go +++ b/apis/parameters/v1alpha1/componentparameter_types.go @@ -59,6 +59,8 @@ func init() { SchemeBuilder.Register(&ComponentParameter{}, &ComponentParameterList{}) } +// 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 // ConfigTemplateItemDetail corresponds to settings of a configuration template (a ConfigMap). @@ -79,7 +81,9 @@ type ConfigTemplateItemDetail struct { // 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. // + // +kubebuilder:validation:Schemaless // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object // +optional Payload Payload `json:"payload,omitempty"` diff --git a/apis/parameters/v1alpha1/types.go b/apis/parameters/v1alpha1/types.go index 3334a0337c4..9f635bb254c 100644 --- a/apis/parameters/v1alpha1/types.go +++ b/apis/parameters/v1alpha1/types.go @@ -171,6 +171,7 @@ type ReloadPolicy string const ( NonePolicy ReloadPolicy = "none" RestartPolicy ReloadPolicy = "restart" + RestartContainerPolicy ReloadPolicy = "restartContainer" RollingPolicy ReloadPolicy = "rolling" AsyncDynamicReloadPolicy ReloadPolicy = "asyncReload" SyncDynamicReloadPolicy ReloadPolicy = "syncReload" diff --git a/cmd/manager/main.go b/cmd/manager/main.go index f262a73565f..86fdba9887e 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -590,20 +590,28 @@ func main() { } if viper.GetBool(parametersFlagKey.viperName()) { - if err = (¶meterscontrollers.ConfigConstraintReconciler{ + if err = (¶meterscontrollers.ParametersDefinitionReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("config-constraint-controller"), + Recorder: mgr.GetEventRecorderFor("parameters-definition-controller"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ParametersDefinition") + os.Exit(1) + } + if err = (¶meterscontrollers.ParameterReconciler{ + Client: client, + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("parameter-controller"), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ConfigConstraint") + setupLog.Error(err, "unable to create controller", "controller", "Parameter") os.Exit(1) } - if err = (¶meterscontrollers.ConfigurationReconciler{ + if err = (¶meterscontrollers.ComponentParameterReconciler{ Client: client, Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("configuration-controller"), + Recorder: mgr.GetEventRecorderFor("component-parameter-controller"), }).SetupWithManager(mgr, multiClusterMgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Configuration") + setupLog.Error(err, "unable to create controller", "controller", "ComponentParameter") os.Exit(1) } if err = (¶meterscontrollers.ReconfigureReconciler{ diff --git a/cmd/reloader/app/proxy.go b/cmd/reloader/app/proxy.go index 36ed6c3bccf..1f25d0da8e2 100644 --- a/cmd/reloader/app/proxy.go +++ b/cmd/reloader/app/proxy.go @@ -95,7 +95,11 @@ func (r *reconfigureProxy) OnlineUpgradeParams(ctx context.Context, request *cfg if len(params) == 0 { return nil, cfgcore.MakeError("update params is empty.") } - if err := r.updater(ctx, request.ConfigSpec, params); err != nil { + key := request.ConfigSpec + if request.ConfigFile != nil && *request.ConfigFile != "" { + key = key + "/" + *request.ConfigFile + } + if err := r.updater(ctx, key, params); err != nil { return nil, err } return &cfgproto.OnlineUpgradeParamsResponse{}, nil diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..a4a5b67da5c 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -4233,36 +4233,6 @@ spec: This field is immutable. items: properties: - asEnvFrom: - description: |- - Specifies the containers to inject the ConfigMap parameters as environment variables. - - - This is useful when application images accept parameters through environment variables and - generate the final configuration file in the startup script based on these variables. - - - This field allows users to specify a list of container names, and KubeBlocks will inject the environment - variables converted from the ConfigMap into these designated containers. This provides a flexible way to - pass the configuration items from the ConfigMap to the container without modifying the image. - - - Deprecated: `asEnvFrom` has been deprecated since 0.9.0 and will be removed in 0.10.0. - Use `injectEnvTo` instead. - items: - type: string - type: array - x-kubernetes-list-type: set - asSecret: - description: Whether to store the final rendered parameters - as a secret. - type: boolean - constraintRef: - description: Specifies the name of the referenced configuration - constraints object. - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ - type: string defaultMode: description: |- The operator attempts to set default file permissions for scripts (0555) and configurations (0444). @@ -4283,40 +4253,6 @@ spec: Refers to documents of k8s.ConfigMapVolumeSource.defaultMode for more information. format: int32 type: integer - injectEnvTo: - description: |- - Specifies the containers to inject the ConfigMap parameters as environment variables. - - - This is useful when application images accept parameters through environment variables and - generate the final configuration file in the startup script based on these variables. - - - This field allows users to specify a list of container names, and KubeBlocks will inject the environment - variables converted from the ConfigMap into these designated containers. This provides a flexible way to - pass the configuration items from the ConfigMap to the container without modifying the image. - items: - type: string - type: array - x-kubernetes-list-type: set - keys: - description: |- - Specifies the configuration files within the ConfigMap that support dynamic updates. - - - A configuration template (provided in the form of a ConfigMap) may contain templates for multiple - configuration files. - Each configuration file corresponds to a key in the ConfigMap. - Some of these configuration files may support dynamic modification and reloading without requiring - a pod restart. - - - If empty or omitted, all configuration files in the ConfigMap are assumed to support dynamic updates, - and ConfigConstraint applies to all keys. - items: - type: string - type: array - x-kubernetes-list-type: set name: description: Specifies the name of the configuration template. maxLength: 63 @@ -4330,28 +4266,6 @@ spec: maxLength: 63 pattern: ^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$ type: string - reRenderResourceTypes: - description: |- - Specifies whether the configuration needs to be re-rendered after v-scale or h-scale operations to reflect changes. - - - In some scenarios, the configuration may need to be updated to reflect the changes in resource allocation - or cluster topology. Examples: - - - - Redis: adjust maxmemory after v-scale operation. - - 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. - enum: - - vscale - - hscale - - tls - type: string - type: array - x-kubernetes-list-type: set templateRef: description: Specifies the name of the referenced configuration template ConfigMap object. diff --git a/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml b/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml index 4dd1102ce19..09366c612c2 100644 --- a/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml +++ b/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml @@ -4187,89 +4187,28 @@ spec: componentName: description: Specifies the name of the Component. type: string - configurations: + parameters: description: |- - Contains a list of ConfigurationItem objects, specifying the Component's configuration template name, - upgrade policy, and parameter key-value pairs to be updated. + Specifies a list of key-value pairs representing parameters and their corresponding values + within a single configuration file. + This field is used to override or set the values of parameters without modifying the entire configuration file. items: properties: - keys: - description: |- - Sets the configuration files and their associated parameters that need to be updated. - It should contain at least one item. - items: - properties: - fileContent: - description: |- - Specifies the content of the entire configuration file. - This field is used to update the complete configuration file. - - - Either the `parameters` field or the `fileContent` field must be set, but not both. - type: string - key: - description: |- - Represents a key in the configuration template(as ConfigMap). - Each key in the ConfigMap corresponds to a specific configuration file. - type: string - parameters: - description: |- - Specifies a list of key-value pairs representing parameters and their corresponding values - within a single configuration file. - This field is used to override or set the values of parameters without modifying the entire configuration file. - - - Either the `parameters` field or the `fileContent` field must be set, but not both. - items: - properties: - key: - description: Represents the name of the parameter - that is to be updated. - type: string - value: - description: |- - Represents the parameter values that are to be updated. - If set to nil, the parameter defined by the Key field will be removed from the configuration file. - type: string - required: - - key - type: object - type: array - required: - - key - type: object - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - key - x-kubernetes-list-type: map name: - description: Specifies the name of the configuration template. - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + description: Specifies the name of the parameter that + is to be updated. type: string - policy: - description: Defines the upgrade policy for the configuration. - enum: - - simple - - parallel - - rolling - - autoReload - - operatorSyncUpdate - - dynamicReloadBeginRestart + value: + description: |- + Specifies the parameter values that are to be updated. + If set to nil, the parameter defined by the Key field will be removed from the configuration file. type: string required: - - keys - name type: object - minItems: 1 type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map required: - componentName - - configurations type: object type: array x-kubernetes-list-map-keys: @@ -8617,183 +8556,6 @@ spec: description: Represents the progress of the OpsRequest. pattern: ^(\d+|\-)/(\d+|\-)$ type: string - reconfiguringStatusAsComponent: - additionalProperties: - properties: - conditions: - description: |- - Describes the reconfiguring detail status. - Possible condition types include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - items: - description: "Condition contains details for one aspect of - the current state of this API Resource.\n---\nThis struct - is intended for direct use as an array at the field path - .status.conditions. For example,\n\n\n\ttype FooStatus - struct{\n\t // Represents the observations of a foo's - current state.\n\t // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // - +listType=map\n\t // +listMapKey=type\n\t Conditions - []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" - patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - configurationStatus: - description: Describes the status of the component reconfiguring. - items: - properties: - expectedCount: - default: -1 - description: Represents the total count of pods intended - to be updated by a configuration change. - format: int32 - type: integer - lastAppliedConfiguration: - additionalProperties: - type: string - description: Stores the last applied configuration. - type: object - lastStatus: - description: |- - Records the last state of the reconfiguration finite state machine. - Possible values include "None", "Retry", "Failed", "NotSupport", "FailedAndRetry". - - - - "None" describes fsm has finished and quit. - - "Retry" describes fsm is running. - - "Failed" describes fsm is failed and exited. - - "NotSupport" describes fsm does not support the feature. - - "FailedAndRetry" describes fsm is failed in current state, but can be retried. - type: string - message: - description: Provides details about the operation. - type: string - name: - description: Indicates the name of the configuration template - (as ConfigMap). - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ - type: string - status: - description: |- - Represents the current state of the reconfiguration state machine. - Possible values include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - type: string - succeedCount: - default: 0 - description: Records the number of pods successfully updated - following a configuration change. - format: int32 - type: integer - updatePolicy: - description: Records the UpgradePolicy of the configuration - change operation. - enum: - - simple - - parallel - - rolling - - autoReload - - operatorSyncUpdate - - dynamicReloadBeginRestart - type: string - updatedParameters: - description: Contains the updated parameters. - properties: - addedKeys: - additionalProperties: - type: string - description: Maps newly added configuration files - to their content. - type: object - deletedKeys: - additionalProperties: - type: string - description: Lists the name of configuration files - that have been deleted. - type: object - updatedKeys: - additionalProperties: - type: string - description: Maps the name of configuration files - to their updated content, detailing the changes - made. - type: object - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - configurationStatus - type: object - description: Records the status of a reconfiguring operation if `opsRequest.spec.type` - equals to "Reconfiguring". - type: object startTimestamp: description: Records the time when the OpsRequest started processing. format: date-time diff --git a/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml b/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml index 276f009e40d..a36f8a49a3b 100644 --- a/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml +++ b/config/crd/bases/parameters.kubeblocks.io_componentparameters.yaml @@ -192,13 +192,6 @@ spec: pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string payload: - additionalProperties: - description: |- - RawMessage is a raw encoded JSON value. - It implements Marshaler and Unmarshaler and can - be used to delay JSON decoding or precompute a JSON encoding. - format: byte - type: string description: |- External controllers can trigger a configuration rerender by modifying this field. diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 9ef7735f3b9..5a4f001216d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -175,58 +175,6 @@ rules: - get - patch - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints/finalizers - verbs: - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints/status - verbs: - - get - - patch - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations/finalizers - verbs: - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations/status - verbs: - - get - - patch - - update - apiGroups: - apps.kubeblocks.io resources: @@ -863,6 +811,32 @@ rules: - get - patch - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters/finalizers + verbs: + - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters/status + verbs: + - get + - patch + - update - apiGroups: - parameters.kubeblocks.io resources: @@ -889,6 +863,32 @@ rules: - get - patch - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters/finalizers + verbs: + - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters/status + verbs: + - get + - patch + - update - apiGroups: - parameters.kubeblocks.io resources: diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go index 735bd5a6054..9f84c23556e 100644 --- a/controllers/apps/componentdefinition_controller.go +++ b/controllers/apps/componentdefinition_controller.go @@ -40,7 +40,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsconfig "github.com/apecloud/kubeblocks/controllers/parameters" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" @@ -138,9 +137,6 @@ func (r *ComponentDefinitionReconciler) deletionHandler(rctx intctrlutil.Request recordEvent, &appsv1.ComponentList{}); res != nil || err != nil { return res, err } - if err := appsconfig.DeleteConfigMapFinalizer(r.Client, rctx, cmpd); err != nil { - return &ctrl.Result{}, err - } return nil, nil } } @@ -337,7 +333,7 @@ func (r *ComponentDefinitionReconciler) validateServices(cli client.Client, rctx func (r *ComponentDefinitionReconciler) validateConfigs(cli client.Client, rctx intctrlutil.RequestCtx, compDef *appsv1.ComponentDefinition) error { - return appsconfig.ReconcileConfigSpecsForReferencedCR(cli, rctx, compDef) + return validateComponentTemplate(cli, rctx, compDef) } func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client, rctx intctrlutil.RequestCtx, diff --git a/controllers/apps/sidecardefinition_controller.go b/controllers/apps/sidecardefinition_controller.go index bcd068885d5..ac7f522153d 100644 --- a/controllers/apps/sidecardefinition_controller.go +++ b/controllers/apps/sidecardefinition_controller.go @@ -40,7 +40,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsconfig "github.com/apecloud/kubeblocks/controllers/parameters" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" @@ -228,25 +227,13 @@ func (r *SidecarDefinitionReconciler) validateConfigNScript(cli client.Client, r return fmt.Errorf("duplicate names of configs/scripts are not allowed") } - configs := func() []appsv1.ComponentConfigSpec { - if len(sidecarDef.Spec.Configs) == 0 { - return nil - } - configs := make([]appsv1.ComponentConfigSpec, 0) - for i := range sidecarDef.Spec.Configs { - configs = append(configs, appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: sidecarDef.Spec.Configs[i], - }) - } - return configs - } compDef := &appsv1.ComponentDefinition{ Spec: appsv1.ComponentDefinitionSpec{ - Configs: configs(), + Configs: sidecarDef.Spec.Configs, Scripts: sidecarDef.Spec.Scripts, }, } - return appsconfig.ReconcileConfigSpecsForReferencedCR(cli, rctx, compDef) + return validateComponentTemplate(cli, rctx, compDef) } func (r *SidecarDefinitionReconciler) validateNResolveOwner(_ client.Client, _ intctrlutil.RequestCtx, @@ -339,18 +326,7 @@ func (r *SidecarDefinitionReconciler) validateMatchedCompDef(sidecarDef *appsv1. return nil } - templateOfConfig := func(configs []appsv1.ComponentConfigSpec) []appsv1.ComponentTemplateSpec { - if len(configs) == 0 { - return nil - } - l := make([]appsv1.ComponentTemplateSpec, 0) - for i := range configs { - l = append(l, configs[i].ComponentTemplateSpec) - } - return l - } - - if err := validate("config", sidecarDef.Spec.Configs, templateOfConfig(compDef.Spec.Configs)); err != nil { + if err := validate("config", sidecarDef.Spec.Configs, compDef.Spec.Configs); err != nil { return err } return validate("script", sidecarDef.Spec.Scripts, compDef.Spec.Scripts) diff --git a/controllers/apps/suite_test.go b/controllers/apps/suite_test.go index b4753809829..21a4145781b 100644 --- a/controllers/apps/suite_test.go +++ b/controllers/apps/suite_test.go @@ -47,6 +47,7 @@ import ( appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/controllers/dataprotection" "github.com/apecloud/kubeblocks/controllers/k8score" @@ -150,6 +151,10 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) model.AddScheme(workloadsv1.AddToScheme) + err = parametersv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(parametersv1alpha1.AddToScheme) + // +kubebuilder:scaffold:rscheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -243,18 +248,25 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager, nil) Expect(err).ToNot(HaveOccurred()) - err = (¶meters.ConfigConstraintReconciler{ + err = (¶meters.ComponentParameterReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-template-controller"), + Recorder: k8sManager.GetEventRecorderFor("component-parameter-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (¶meters.ParametersDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("parameters-definition-controller"), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - err = (¶meters.ConfigurationReconciler{ + err = (¶meters.ParameterDrivenConfigRenderReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), - }).SetupWithManager(k8sManager, nil) + Recorder: k8sManager.GetEventRecorderFor("parameter-driven-config-render-controller"), + }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) err = (&dataprotection.BackupPolicyTemplateReconciler{ diff --git a/controllers/apps/transformer_component_parameters.go b/controllers/apps/transformer_component_parameters.go index b1e2996788e..d34ebbd7f8b 100644 --- a/controllers/apps/transformer_component_parameters.go +++ b/controllers/apps/transformer_component_parameters.go @@ -22,10 +22,11 @@ package apps import ( "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/graph" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) type componentRelatedParametersTransformer struct { @@ -38,20 +39,24 @@ func (c *componentRelatedParametersTransformer) Transform(ctx graph.TransformCon transCtx, _ := ctx.(*componentTransformContext) synthesizedComp := transCtx.SynthesizeComponent - config := appsv1alpha1.Configuration{} + componentParameter := ¶metersv1alpha1.ComponentParameter{} configKey := client.ObjectKey{Namespace: synthesizedComp.Namespace, Name: cfgcore.GenerateComponentConfigurationName(synthesizedComp.ClusterName, synthesizedComp.Name)} - if err := c.Get(ctx.GetContext(), configKey, &config); err != nil { + if err := c.Get(ctx.GetContext(), configKey, componentParameter); err != nil { return client.IgnoreNotFound(err) } - configNew := config.DeepCopy() - updated, err := configuration.UpdateConfigPayload(&configNew.Spec, synthesizedComp) + configRender, err := intctrlutil.ResolveComponentConfigRender(ctx.GetContext(), c, transCtx.CompDef) if err != nil { - return err + return client.IgnoreNotFound(err) } - if !updated { + if configRender == nil { return nil } - return c.Patch(ctx.GetContext(), configNew, client.MergeFrom(config.DeepCopy())) + + configNew := componentParameter.DeepCopy() + if err = configuration.UpdateConfigPayload(&configNew.Spec, &transCtx.Component.Spec, &configRender.Spec); err != nil { + return err + } + return c.Patch(ctx.GetContext(), configNew, client.MergeFrom(componentParameter.DeepCopy())) } diff --git a/controllers/apps/transformer_component_status.go b/controllers/apps/transformer_component_status.go index b799498a214..5fc3e5c65b3 100644 --- a/controllers/apps/transformer_component_status.go +++ b/controllers/apps/transformer_component_status.go @@ -34,6 +34,7 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" @@ -248,13 +249,13 @@ func (t *componentStatusTransformer) isAllConfigSynced(transCtx *componentTransf Namespace: t.cluster.Namespace, Name: cfgcore.GenerateComponentConfigurationName(t.cluster.Name, t.synthesizeComp.Name), } - configuration := &appsv1alpha1.Configuration{} + configuration := ¶metersv1alpha1.ComponentParameter{} if err := t.Client.Get(transCtx.Context, configurationKey, configuration); err != nil { return false, err } for _, configSpec := range t.synthesizeComp.ConfigTemplates { - item := configuration.Spec.GetConfigurationItem(configSpec.Name) - status := configuration.Status.GetItemStatus(configSpec.Name) + item := intctrlutil.GetConfigTemplateItem(&configuration.Spec, configSpec.Name) + status := intctrlutil.GetItemStatus(&configuration.Status, configSpec.Name) // for creating phase if item == nil || status == nil { return false, nil @@ -266,7 +267,7 @@ func (t *componentStatusTransformer) isAllConfigSynced(transCtx *componentTransf if err := t.Client.Get(transCtx.Context, cmKey, cmObj, inDataContext4C()); err != nil { return false, err } - if intctrlutil.GetConfigSpecReconcilePhase(cmObj, *item, status) != appsv1alpha1.CFinishedPhase { + if intctrlutil.GetConfigSpecReconcilePhase(cmObj, *item, status) != parametersv1alpha1.CFinishedPhase { return false, nil } } diff --git a/controllers/apps/transformer_component_tls_test.go b/controllers/apps/transformer_component_tls_test.go index bdc75b4b587..3a673d32486 100644 --- a/controllers/apps/transformer_component_tls_test.go +++ b/controllers/apps/transformer_component_tls_test.go @@ -32,13 +32,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/graph" "github.com/apecloud/kubeblocks/pkg/controller/plan" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("TLS self-signed cert function", func() { @@ -51,6 +51,8 @@ var _ = Describe("TLS self-signed cert function", func() { caFile = "ca.pem" certFile = "cert.pem" keyFile = "key.pem" + paramsDef = "mysql-pd" + pdcrName = "mysql-pd" ) var ( @@ -73,8 +75,10 @@ var _ = Describe("TLS self-signed cert function", func() { // delete rest configurations ml := client.HasLabels{testCtx.TestObjLabelKey} + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigMapSignature, true, client.InNamespace(testCtx.DefaultNamespace)) // non-namespaced - testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) + testapps.ClearResources(&testCtx, generics.ParametersDefinitionSignature, ml) + testapps.ClearResources(&testCtx, generics.ParameterDrivenConfigRenderSignature, ml) testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml) } @@ -84,15 +88,14 @@ var _ = Describe("TLS self-signed cert function", func() { Context("tls is enabled/disabled", func() { BeforeEach(func() { - configMapObj := testapps.CheckedCreateCustomizedObj(&testCtx, - "resources/mysql-tls-config-template.yaml", - &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace(), - testapps.WithAnnotations(constant.CMInsEnableRerenderTemplateKey, "true")) + configMapObj := testparameters.NewComponentTemplateFactory(configTemplateName, testCtx.DefaultNamespace). + Create(&testCtx). + GetObject() - configConstraintObj := testapps.CheckedCreateCustomizedObj(&testCtx, - "resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) + paramsdef := testparameters.NewParametersDefinitionFactory(paramsDef). + SetReloadAction(testparameters.WithNoneAction()). + Create(&testCtx). + GetObject() By("Create a componentDefinition obj") compDefObj = testapps.NewComponentDefinitionFactory(compDefName). @@ -100,10 +103,17 @@ var _ = Describe("TLS self-signed cert function", func() { AddAnnotations(constant.SkipImmutableCheckAnnotationKey, "true"). SetDefaultSpec(). SetServiceKind(serviceKind). - AddConfigTemplate(configTemplateName, configMapObj.Name, configConstraintObj.Name, testCtx.DefaultNamespace, testapps.ConfVolumeName). + AddConfigTemplate(configTemplateName, configMapObj.Name, testCtx.DefaultNamespace, testapps.ConfVolumeName). AddEnv(testapps.DefaultMySQLContainerName, corev1.EnvVar{Name: "MYSQL_ALLOW_EMPTY_PASSWORD", Value: "yes"}). Create(&testCtx). GetObject() + + testparameters.NewParametersDrivenConfigFactory(pdcrName). + SetParametersDefs(paramsdef.Name). + SetComponentDefinition(compDefObj.GetName()). + SetTemplateName(configTemplateName). + TLSEnabled(). + Create(&testCtx) }) Context("when issuer is UserProvided", func() { diff --git a/controllers/apps/utils.go b/controllers/apps/utils.go index 72beb7f3756..7daae2a2345 100644 --- a/controllers/apps/utils.go +++ b/controllers/apps/utils.go @@ -28,9 +28,11 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/model" "github.com/apecloud/kubeblocks/pkg/controller/multicluster" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) // default reconcile requeue after duration @@ -149,3 +151,24 @@ func shouldAllocateNodePorts(svc *corev1.ServiceSpec) bool { } return false } + +func validateComponentTemplate(cli client.Client, rctx intctrlutil.RequestCtx, compd *appsv1.ComponentDefinition) error { + validateObject := func(objectKey client.ObjectKey) error { + configObj := &corev1.ConfigMap{} + return cli.Get(rctx.Ctx, objectKey, configObj) + } + validateTemplate := func(tpl appsv1.ComponentTemplateSpec) error { + if tpl.TemplateRef != "" { + return validateObject(client.ObjectKey{Namespace: tpl.Namespace, Name: tpl.TemplateRef}) + } + return nil + } + for _, tpls := range [][]appsv1.ComponentTemplateSpec{compd.Spec.Configs, compd.Spec.Scripts} { + for _, tpl := range tpls { + if err := validateTemplate(tpl); err != nil { + return err + } + } + } + return nil +} diff --git a/controllers/operations/suite_test.go b/controllers/operations/suite_test.go index 1a1d9e2b695..278894c86e5 100644 --- a/controllers/operations/suite_test.go +++ b/controllers/operations/suite_test.go @@ -47,6 +47,7 @@ import ( appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/controllers/apps" "github.com/apecloud/kubeblocks/controllers/dataprotection" @@ -151,6 +152,10 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) model.AddScheme(workloadsv1.AddToScheme) + err = parametersv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(parametersv1alpha1.AddToScheme) + // +kubebuilder:scaffold:rscheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -238,17 +243,10 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager, nil) Expect(err).ToNot(HaveOccurred()) - err = (¶meters.ConfigConstraintReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-template-controller"), - }).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - - err = (¶meters.ConfigurationReconciler{ + err = (¶meters.ComponentParameterReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), + Recorder: k8sManager.GetEventRecorderFor("component-parameter"), }).SetupWithManager(k8sManager, nil) Expect(err).ToNot(HaveOccurred()) diff --git a/controllers/parameters/combine_upgrade_policy.go b/controllers/parameters/combine_upgrade_policy.go index 7f243f1d4d4..5b4a10df245 100644 --- a/controllers/parameters/combine_upgrade_policy.go +++ b/controllers/parameters/combine_upgrade_policy.go @@ -19,26 +19,33 @@ along with this program. If not, see . package parameters -import appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" +import ( + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" +) + +var combineUpgradePolicyInstance = &combineUpgradePolicy{ + policyExecutors: []reconfigurePolicy{ + syncPolicyInstance, + restartPolicyInstance, + }, +} type combineUpgradePolicy struct { policyExecutors []reconfigurePolicy } func init() { - RegisterPolicy(appsv1alpha1.DynamicReloadAndRestartPolicy, &combineUpgradePolicy{ - policyExecutors: []reconfigurePolicy{&syncPolicy{}, &simplePolicy{}}, - }) + registerPolicy(parametersv1alpha1.DynamicReloadAndRestartPolicy, combineUpgradePolicyInstance) } func (h *combineUpgradePolicy) GetPolicyName() string { - return string(appsv1alpha1.DynamicReloadAndRestartPolicy) + return string(parametersv1alpha1.DynamicReloadAndRestartPolicy) } -func (h *combineUpgradePolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { +func (h *combineUpgradePolicy) Upgrade(rctx reconfigureContext) (ReturnedStatus, error) { var ret ReturnedStatus for _, executor := range h.policyExecutors { - retStatus, err := executor.Upgrade(params) + retStatus, err := executor.Upgrade(rctx) if err != nil { return retStatus, err } diff --git a/controllers/parameters/combine_upgrade_policy_test.go b/controllers/parameters/combine_upgrade_policy_test.go index c3cfabef749..1daa6e41ac2 100644 --- a/controllers/parameters/combine_upgrade_policy_test.go +++ b/controllers/parameters/combine_upgrade_policy_test.go @@ -25,7 +25,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -50,16 +50,16 @@ var _ = Describe("Reconfigure CombineSyncPolicy", func() { policyExecutors: []reconfigurePolicy{&testPolicy{}}, } - Expect(upgradePolicyMap[appsv1alpha1.DynamicReloadAndRestartPolicy]).ShouldNot(BeNil()) + Expect(upgradePolicyMap[parametersv1alpha1.DynamicReloadAndRestartPolicy]).ShouldNot(BeNil()) - mockParam := newMockReconfigureParams("simplePolicy", k8sMockClient.Client(), + mockParam := newMockReconfigureParams("restartPolicy", k8sMockClient.Client(), withMockInstanceSet(2, nil), withConfigSpec("for_test", map[string]string{ "key": "value", }), withClusterComponent(2)) - Expect(testPolicyExecs.GetPolicyName()).Should(BeEquivalentTo(appsv1alpha1.DynamicReloadAndRestartPolicy)) + Expect(testPolicyExecs.GetPolicyName()).Should(BeEquivalentTo(parametersv1alpha1.DynamicReloadAndRestartPolicy)) status, err := testPolicyExecs.Upgrade(mockParam) Expect(err).Should(Succeed()) Expect(status.Status).Should(BeEquivalentTo(ESNone)) @@ -71,14 +71,14 @@ var _ = Describe("Reconfigure CombineSyncPolicy", func() { policyExecutors: []reconfigurePolicy{&testErrorPolicy{}}, } - mockParam := newMockReconfigureParams("simplePolicy", k8sMockClient.Client(), + mockParam := newMockReconfigureParams("restartPolicy", k8sMockClient.Client(), withMockInstanceSet(2, nil), withConfigSpec("for_test", map[string]string{ "key": "value", }), withClusterComponent(2)) - Expect(testPolicyExecs.GetPolicyName()).Should(BeEquivalentTo(appsv1alpha1.DynamicReloadAndRestartPolicy)) + Expect(testPolicyExecs.GetPolicyName()).Should(BeEquivalentTo(parametersv1alpha1.DynamicReloadAndRestartPolicy)) status, err := testPolicyExecs.Upgrade(mockParam) Expect(err).ShouldNot(Succeed()) Expect(status.Status).Should(BeEquivalentTo(ESFailedAndRetry)) @@ -92,7 +92,7 @@ type testPolicy struct { type testErrorPolicy struct { } -func (t testErrorPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { +func (t testErrorPolicy) Upgrade(params reconfigureContext) (ReturnedStatus, error) { return makeReturnedStatus(ESFailedAndRetry), fmt.Errorf("testErrorPolicy failed") } @@ -100,7 +100,7 @@ func (t testErrorPolicy) GetPolicyName() string { return "testErrorPolicy" } -func (t testPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { +func (t testPolicy) Upgrade(params reconfigureContext) (ReturnedStatus, error) { return makeReturnedStatus(ESNone), nil } diff --git a/controllers/parameters/componentparameter_controller.go b/controllers/parameters/componentparameter_controller.go new file mode 100644 index 00000000000..9afa70ba4c4 --- /dev/null +++ b/controllers/parameters/componentparameter_controller.go @@ -0,0 +1,211 @@ +/* +Copyright (C) 2022-2024 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" + "math" + "strconv" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + 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/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/multicluster" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + viper "github.com/apecloud/kubeblocks/pkg/viperx" +) + +// ComponentParameterReconciler reconciles a ComponentParameter object +type ComponentParameterReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=componentparameters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=componentparameters/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=componentparameters/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 *ComponentParameterReconciler) 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("ComponentParameterReconciler"). + WithValues("Namespace", req.Namespace, "ComponentParameter", req.Name), + } + + componentParam := ¶metersv1alpha1.ComponentParameter{} + if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, componentParam); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") + } + + res, err := intctrlutil.HandleCRDeletion(reqCtx, r, componentParam, constant.ConfigFinalizerName, r.deletionHandler(reqCtx, componentParam)) + if res != nil { + return *res, err + } + return r.reconcile(reqCtx, componentParam) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ComponentParameterReconciler) SetupWithManager(mgr ctrl.Manager, multiClusterMgr multicluster.Manager) error { + builder := intctrlutil.NewControllerManagedBy(mgr). + For(¶metersv1alpha1.ComponentParameter{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: int(math.Ceil(viper.GetFloat64(constant.CfgKBReconcileWorkers) / 2)), + }). + Owns(&corev1.ConfigMap{}) + if multiClusterMgr != nil { + multiClusterMgr.Own(builder, &corev1.ConfigMap{}, ¶metersv1alpha1.ComponentParameter{}) + } + return builder.Complete(r) +} + +func (r *ComponentParameterReconciler) reconcile(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) (ctrl.Result, error) { + tasks := generateReconcileTasks(reqCtx, componentParameter) + if len(tasks) == 0 { + return intctrlutil.Reconciled() + } + + fetcherTask, err := prepareReconcileTask(reqCtx, r.Client, componentParameter) + if err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to get related object.") + } + if !fetcherTask.ClusterObj.GetDeletionTimestamp().IsZero() { + reqCtx.Log.Info("cluster is deleting, skip reconcile") + return intctrlutil.Reconciled() + } + if fetcherTask.ClusterComObj == nil || fetcherTask.ComponentObj == nil { + return r.failWithInvalidComponent(componentParameter, reqCtx) + } + + taskCtx, err := NewTaskContext(reqCtx.Ctx, r.Client, componentParameter, fetcherTask) + if err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to create task context.") + } + if err := r.runTasks(taskCtx, tasks, fetcherTask); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to run configuration reconcile task.") + } + return intctrlutil.Reconciled() +} + +func (r *ComponentParameterReconciler) failWithInvalidComponent(componentParam *parametersv1alpha1.ComponentParameter, reqCtx intctrlutil.RequestCtx) (ctrl.Result, error) { + msg := fmt.Sprintf("not found cluster component: [%s]", componentParam.Spec.ComponentName) + + reqCtx.Log.Error(fmt.Errorf("%s", msg), "") + patch := client.MergeFrom(componentParam.DeepCopy()) + componentParam.Status.Message = msg + if err := r.Client.Status().Patch(reqCtx.Ctx, componentParam, patch); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to update configuration status.") + } + return intctrlutil.Reconciled() +} + +func (r *ComponentParameterReconciler) runTasks(taskCtx *TaskContext, tasks []Task, resource *Task) error { + var ( + errs []error + compParameter = taskCtx.componentParameter + ) + + patch := client.MergeFrom(compParameter.DeepCopy()) + revision := strconv.FormatInt(compParameter.GetGeneration(), 10) + for _, task := range tasks { + if err := task.Do(resource, taskCtx, revision); err != nil { + errs = append(errs, err) + continue + } + } + + updateCompParamStatus(&compParameter.Status, errs, compParameter.Generation) + if err := r.Client.Status().Patch(taskCtx.ctx, compParameter, patch); err != nil { + errs = append(errs, err) + } + if len(errs) == 0 { + return nil + } + return utilerrors.NewAggregate(errs) +} + +func updateCompParamStatus(status *parametersv1alpha1.ComponentParameterStatus, errs []error, generation int64) { + aggregatePhase := func(ss []parametersv1alpha1.ConfigTemplateItemDetailStatus) parametersv1alpha1.ParameterPhase { + var phase = parametersv1alpha1.CFinishedPhase + for _, s := range ss { + switch { + case intctrlutil.IsFailedPhase(s.Phase): + return s.Phase + case !intctrlutil.IsParameterFinished(s.Phase): + phase = parametersv1alpha1.CRunningPhase + } + } + return phase + } + + status.ObservedGeneration = generation + status.Message = "" + status.Phase = aggregatePhase(status.ConfigurationItemStatus) + if len(errs) > 0 { + status.Message = utilerrors.NewAggregate(errs).Error() + } +} + +func (r *ComponentParameterReconciler) deletionHandler(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) func() (*ctrl.Result, error) { + return func() (*ctrl.Result, error) { + var cms = &corev1.ConfigMapList{} + matchLabels := client.MatchingLabels(constant.GetCompLabels(componentParameter.Spec.ClusterName, componentParameter.Spec.ComponentName)) + if err := r.Client.List(reqCtx.Ctx, cms, client.InNamespace(componentParameter.Name), matchLabels); err != nil { + return &reconcile.Result{}, err + } + if err := removeConfigRelatedFinalizer(reqCtx.Ctx, r.Client, cms.Items); err != nil { + return &reconcile.Result{}, err + } + return nil, nil + } +} + +func removeConfigRelatedFinalizer(ctx context.Context, cli client.Client, objs []corev1.ConfigMap) error { + for _, obj := range objs { + if !controllerutil.ContainsFinalizer(&obj, constant.ConfigFinalizerName) { + continue + } + patch := client.MergeFrom(obj.DeepCopy()) + controllerutil.RemoveFinalizer(&obj, constant.ConfigFinalizerName) + if err := cli.Patch(ctx, &obj, patch); err != nil { + return err + } + } + return nil +} diff --git a/controllers/parameters/componentparameter_controller_test.go b/controllers/parameters/componentparameter_controller_test.go new file mode 100644 index 00000000000..e4e44d87680 --- /dev/null +++ b/controllers/parameters/componentparameter_controller_test.go @@ -0,0 +1,141 @@ +/* +Copyright (C) 2022-2024 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" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/controller-runtime/pkg/client" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/configuration/core" + cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" +) + +var _ = Describe("ComponentParameter Controller", func() { + + BeforeEach(cleanEnv) + + AfterEach(cleanEnv) + + updatePDCRForInjectEnv := func() { + Eventually(testapps.GetAndChangeObj(&testCtx, types.NamespacedName{Name: pdcrName}, func(pdcr *parametersv1alpha1.ParameterDrivenConfigRender) { + pdcr.Spec.Configs = append(pdcr.Spec.Configs, parametersv1alpha1.ComponentConfigDescription{ + Name: envTestFileKey, + TemplateName: configSpecName, + InjectEnvTo: []string{testapps.DefaultMySQLContainerName}, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, + }, + }) + })).Should(Succeed()) + + Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: pdcrName}, func(g Gomega, pdcr *parametersv1alpha1.ParameterDrivenConfigRender) { + g.Expect(pdcr.Spec.Configs).Should(HaveLen(2)) + g.Expect(pdcr.Spec.Configs[1].FileFormatConfig.Format).Should(BeEquivalentTo(parametersv1alpha1.Properties)) + })).Should(Succeed()) + } + + Context("When updating configuration", func() { + It("Should reconcile success", func() { + mockReconcileResource() + + cfgKey := client.ObjectKey{ + Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), + Namespace: testCtx.DefaultNamespace, + } + + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, componentParameter *parametersv1alpha1.ComponentParameter) { + g.Expect(componentParameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + itemStatus := intctrlutil.GetItemStatus(&componentParameter.Status, configSpecName) + g.Expect(itemStatus.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + + By("reconfiguring parameters.") + Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *parametersv1alpha1.ComponentParameter) { + item := intctrlutil.GetConfigTemplateItem(&cfg.Spec, configSpecName) + item.ConfigFileParams = map[string]parametersv1alpha1.ParametersInFile{ + "my.cnf": { + Parameters: map[string]*string{ + "max_connections": cfgutil.ToPointer("1000"), + "gtid_mode": cfgutil.ToPointer("ON"), + }, + }, + } + })).Should(Succeed()) + + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { + itemStatus := intctrlutil.GetItemStatus(&cfg.Status, configSpecName) + g.Expect(itemStatus).ShouldNot(BeNil()) + g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) + g.Expect(itemStatus.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + }) + + }) + + Context("When updating configuration with injectEnvTo", func() { + It("Should reconcile success", func() { + mockReconcileResource() + updatePDCRForInjectEnv() + + cfgKey := client.ObjectKey{ + Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), + Namespace: testCtx.DefaultNamespace, + } + envKey := client.ObjectKey{ + Name: core.GenerateEnvFromName(core.GetComponentCfgName(clusterName, defaultCompName, configSpecName)), + Namespace: testCtx.DefaultNamespace, + } + + By("reconfiguring parameters.") + Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *parametersv1alpha1.ComponentParameter) { + item := intctrlutil.GetConfigTemplateItem(&cfg.Spec, configSpecName) + item.ConfigFileParams = map[string]parametersv1alpha1.ParametersInFile{ + envTestFileKey: { + Parameters: map[string]*string{ + "max_connections": cfgutil.ToPointer("1000"), + "gtid_mode": cfgutil.ToPointer("ON"), + }, + }, + } + })).Should(Succeed()) + + Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { + itemStatus := intctrlutil.GetItemStatus(&cfg.Status, configSpecName) + g.Expect(itemStatus).ShouldNot(BeNil()) + g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) + g.Expect(itemStatus.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + + Eventually(testapps.CheckObj(&testCtx, envKey, func(g Gomega, envObj *corev1.ConfigMap) { + g.Expect(envObj.Data).Should(HaveKeyWithValue("max_connections", "1000")) + g.Expect(envObj.Data).Should(HaveKeyWithValue("gtid_mode", "ON")) + })).Should(Succeed()) + }) + + }) +}) diff --git a/controllers/parameters/config_annotation.go b/controllers/parameters/config_annotation.go index 61e3594d434..7e827cc3291 100644 --- a/controllers/parameters/config_annotation.go +++ b/controllers/parameters/config_annotation.go @@ -28,7 +28,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" @@ -37,7 +37,7 @@ import ( type options = func(*intctrlutil.Result) -func reconciled(status ReturnedStatus, policy string, phase appsv1alpha1.ConfigurationPhase, options ...options) intctrlutil.Result { +func reconciled(status ReturnedStatus, policy string, phase parametersv1alpha1.ParameterPhase, options ...options) intctrlutil.Result { result := intctrlutil.Result{ Policy: policy, Phase: phase, @@ -52,7 +52,7 @@ func reconciled(status ReturnedStatus, policy string, phase appsv1alpha1.Configu return result } -func unReconciled(phase appsv1alpha1.ConfigurationPhase, revision string, message string) intctrlutil.Result { +func unReconciled(phase parametersv1alpha1.ParameterPhase, revision string, message string) intctrlutil.Result { return intctrlutil.Result{ Phase: phase, Revision: revision, @@ -95,7 +95,7 @@ func checkEnableCfgUpgrade(object client.Object) bool { return true } -func updateConfigPhase(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, phase appsv1alpha1.ConfigurationPhase, message string) (ctrl.Result, error) { +func updateConfigPhase(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, phase parametersv1alpha1.ParameterPhase, message string) (ctrl.Result, error) { return updateConfigPhaseWithResult(cli, ctx, config, unReconciled(phase, "", message)) } @@ -159,7 +159,7 @@ func updateAppliedConfigs(cli client.Client, ctx intctrlutil.RequestCtx, config GcConfigRevision(config) if revision, ok := config.ObjectMeta.Annotations[constant.ConfigurationRevision]; ok && revision != "" { if result == nil { - result = util.ToPointer(unReconciled(appsv1alpha1.CFinishedPhase, "", fmt.Sprintf("phase: %s", reconfigurePhase))) + result = util.ToPointer(unReconciled(parametersv1alpha1.CFinishedPhase, "", fmt.Sprintf("phase: %s", reconfigurePhase))) } result.Revision = revision b, _ := json.Marshal(result) @@ -203,17 +203,3 @@ func getLastVersionConfig(cm *corev1.ConfigMap) (map[string]string, error) { return data, nil } - -func getUpgradePolicy(cm *corev1.ConfigMap) appsv1alpha1.UpgradePolicy { - const ( - DefaultUpgradePolicy = appsv1alpha1.NonePolicy - ) - - annotations := cm.GetAnnotations() - value, ok := annotations[constant.UpgradePolicyAnnotationKey] - if !ok { - return DefaultUpgradePolicy - } - - return appsv1alpha1.UpgradePolicy(value) -} diff --git a/controllers/parameters/config_reconcile_wrapper.go b/controllers/parameters/config_reconcile_wrapper.go index 5a777906102..649c8fce91e 100644 --- a/controllers/parameters/config_reconcile_wrapper.go +++ b/controllers/parameters/config_reconcile_wrapper.go @@ -20,8 +20,6 @@ along with this program. If not, see . package parameters import ( - "context" - corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -38,7 +36,6 @@ type ReconcileContext struct { intctrlutil.RequestCtx configctrl.ResourceFetcher[ReconcileContext] - ctx context.Context Name string MatchingLabels client.MatchingLabels ConfigMap *corev1.ConfigMap @@ -51,13 +48,13 @@ type ReconcileContext struct { ParametersDefs map[string]*parametersv1alpha1.ParametersDefinition } -func newConfigReconcileContext(ctx context.Context, +func newParameterReconcileContext(reqCtx intctrlutil.RequestCtx, resourceCtx *render.ResourceCtx, cm *corev1.ConfigMap, configSpecName string, matchingLabels client.MatchingLabels) *ReconcileContext { configContext := ReconcileContext{ - ctx: ctx, + RequestCtx: reqCtx, ConfigMap: cm, Name: configSpecName, MatchingLabels: matchingLabels, @@ -71,11 +68,12 @@ func (c *ReconcileContext) GetRelatedObjects() error { ComponentSpec(). Workload(). SynthesizedComponent(). + ParametersDefinitions(). Complete() } func (c *ReconcileContext) Workload() *ReconcileContext { - stsFn := func() (err error) { + instanceSetFn := func() (err error) { c.InstanceSetList, c.Containers, err = retrieveRelatedComponentsByConfigmap( c.Client, c.Context, @@ -86,13 +84,13 @@ func (c *ReconcileContext) Workload() *ReconcileContext { c.MatchingLabels) return } - return c.Wrap(stsFn) + return c.Wrap(instanceSetFn) } func (c *ReconcileContext) SynthesizedComponent() *ReconcileContext { return c.Wrap(func() (err error) { // build synthesized component for the component - c.BuiltinComponent, err = component.BuildSynthesizedComponent(c.ctx, c.Client, c.ComponentDefObj, c.ComponentObj, c.ClusterObj) + c.BuiltinComponent, err = component.BuildSynthesizedComponent(c.Ctx, c.Client, c.ComponentDefObj, c.ComponentObj, c.ClusterObj) return err }) } diff --git a/controllers/parameters/config_util.go b/controllers/parameters/config_util.go index c17f8823f6d..b82f6edc135 100644 --- a/controllers/parameters/config_util.go +++ b/controllers/parameters/config_util.go @@ -22,29 +22,22 @@ package parameters import ( "context" "fmt" - "reflect" + "strconv" - "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/openapi" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/configuration" + "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - "github.com/apecloud/kubeblocks/pkg/generics" ) -type ValidateConfigMap func(configTpl, ns string) (*corev1.ConfigMap, error) -type ValidateConfigSchema func(tpl *appsv1beta1.ParametersSchema) (bool, error) +// type ValidateConfigMap func(configTpl, ns string) (*corev1.ConfigMap, error) +// type ValidateConfigSchema func(tpl *appsv1beta1.ParametersSchema) (bool, error) func checkConfigLabels(object client.Object, requiredLabs []string) bool { labels := object.GetLabels() @@ -66,344 +59,71 @@ func checkConfigLabels(object client.Object, requiredLabs []string) bool { return checkEnableCfgUpgrade(object) } -func getConfigMapByTemplateName(cli client.Client, ctx intctrlutil.RequestCtx, templateName, ns string) (*corev1.ConfigMap, error) { - if len(templateName) == 0 { - return nil, fmt.Errorf("required configmap reference name is empty! [%v]", templateName) - } - - configObj := &corev1.ConfigMap{} - if err := cli.Get(ctx.Ctx, client.ObjectKey{ - Namespace: ns, - Name: templateName, - }, configObj); err != nil { - ctx.Log.Error(err, "failed to get config template cm object!", "configMapName", templateName) - return nil, err - } - - return configObj, nil -} - -func checkConfigConstraint(ctx intctrlutil.RequestCtx, configConstraint *appsv1beta1.ConfigConstraint) (bool, error) { - // validate configuration template - validateConfigSchema := func(ccSchema *appsv1beta1.ParametersSchema) (bool, error) { - if ccSchema == nil || len(ccSchema.CUE) == 0 { - return true, nil - } - - err := validate.CueValidate(ccSchema.CUE) - return err == nil, err - } - - // validate schema - if ok, err := validateConfigSchema(configConstraint.Spec.ParametersSchema); !ok || err != nil { - ctx.Log.Error(err, "failed to validate template schema!", "configMapName", fmt.Sprintf("%v", configConstraint.Spec.ParametersSchema)) - return ok, err - } - return true, nil -} - -func ReconcileConfigSpecsForReferencedCR[T generics.Object, PT generics.PObject[T]](client client.Client, ctx intctrlutil.RequestCtx, obj PT) error { - if ok, err := checkConfigTemplate(client, ctx, obj); !ok || err != nil { - return fmt.Errorf("failed to check config template: %v", err) - } - if ok, err := updateLabelsByConfigSpec(client, ctx, obj); !ok || err != nil { - return fmt.Errorf("failed to update using config template info: %v", err) - } - if _, err := updateConfigMapFinalizer(client, ctx, obj); err != nil { - return fmt.Errorf("failed to update config map finalizer: %v", err) - } - return nil -} - -func DeleteConfigMapFinalizer(cli client.Client, ctx intctrlutil.RequestCtx, obj client.Object) error { - handler := func(configSpecs []appsv1.ComponentConfigSpec) (bool, error) { - return true, batchDeleteConfigMapFinalizer(cli, ctx, configSpecs, obj) - } - _, err := handleConfigTemplate(obj, handler) - return err -} - -func validateConfigMapOwners(cli client.Client, ctx intctrlutil.RequestCtx, labels client.MatchingLabels, check func(obj client.Object) bool, objLists ...client.ObjectList) (bool, error) { - for _, objList := range objLists { - if err := cli.List(ctx.Ctx, objList, labels, inDataContextUnspecified()); err != nil { - return false, err - } - v, err := conversion.EnforcePtr(objList) - if err != nil { - return false, err - } - items := v.FieldByName("Items") - if !items.IsValid() || items.Kind() != reflect.Slice { - return false, nil - } - if items.Len() == 0 { - continue - } - if !checkEnabledDelete(items, check) { - return false, nil - } +func createConfigPatch(cfg *corev1.ConfigMap, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs map[string]*parametersv1alpha1.ParametersDefinition) (*core.ConfigPatchInfo, bool, error) { + if configRender == nil || len(configRender.Spec.Configs) == 0 { + return nil, true, nil } - return true, nil -} - -func checkEnabledDelete(items reflect.Value, check func(obj client.Object) bool) bool { - for i := 0; i < items.Len(); i++ { - val := items.Index(i) - // fetch object pointer - if val.CanAddr() { - val = val.Addr() - } - if !val.CanInterface() { - return false - } - obj, ok := val.Interface().(client.Object) - if !ok { - return false - } - if obj.GetDeletionTimestamp() != nil { - continue - } - if !check(obj) { - return false - } + lastConfig, err := getLastVersionConfig(cfg) + if err != nil { + return nil, false, core.WrapError(err, "failed to get last version data. config[%v]", client.ObjectKeyFromObject(cfg)) } - return true -} -func batchDeleteConfigMapFinalizer(cli client.Client, ctx intctrlutil.RequestCtx, configSpecs []appsv1.ComponentConfigSpec, cr client.Object) error { - validator := func(obj client.Object) bool { - return obj.GetName() == cr.GetName() && obj.GetNamespace() == cr.GetNamespace() - } - for _, configSpec := range configSpecs { - labels := client.MatchingLabels{ - core.GenerateTPLUniqLabelKeyWithConfig(configSpec.Name): configSpec.TemplateRef, - } - if ok, err := validateConfigMapOwners(cli, ctx, labels, validator, &appsv1.ClusterDefinitionList{}, &appsv1.ComponentDefinitionList{}); err != nil { - return err - } else if !ok { - continue - } - if err := deleteConfigMapFinalizer(cli, ctx, configSpec); err != nil { - return err - } + patch, restart, err := core.CreateConfigPatch(lastConfig, cfg.Data, configRender.Spec, true) + if err != nil { + return nil, false, err } - return nil -} - -func updateConfigMapFinalizer(client client.Client, ctx intctrlutil.RequestCtx, obj client.Object) (bool, error) { - handler := func(configSpecs []appsv1.ComponentConfigSpec) (bool, error) { - return true, batchUpdateConfigMapFinalizer(client, ctx, configSpecs) + if !restart { + restart = cfgcm.NeedRestart(paramsDefs, patch) } - return handleConfigTemplate(obj, handler) + return patch, restart, nil } -func batchUpdateConfigMapFinalizer(cli client.Client, ctx intctrlutil.RequestCtx, configSpecs []appsv1.ComponentConfigSpec) error { - for _, configSpec := range configSpecs { - if err := updateConfigMapFinalizerImpl(cli, ctx, configSpec); err != nil { - return err +func generateReconcileTasks(reqCtx intctrlutil.RequestCtx, componentParameter *parametersv1alpha1.ComponentParameter) []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)) } } - return nil -} - -func updateConfigMapFinalizerImpl(cli client.Client, ctx intctrlutil.RequestCtx, configSpec appsv1.ComponentConfigSpec) error { - // step1: add finalizer - // step2: add labels: CMConfigurationTypeLabelKey - // step3: update immutable - - cmObj, err := getConfigMapByTemplateName(cli, ctx, configSpec.TemplateRef, configSpec.Namespace) - if err != nil { - ctx.Log.Error(err, "failed to get template cm object!", "configMapName", cmObj.Name) - return err - } - - if controllerutil.ContainsFinalizer(cmObj, constant.ConfigFinalizerName) { - return nil - } - - patch := client.MergeFrom(cmObj.DeepCopy()) - - if cmObj.Labels == nil { - cmObj.Labels = map[string]string{} - } - cmObj.Labels[constant.CMConfigurationTypeLabelKey] = constant.ConfigTemplateType - controllerutil.AddFinalizer(cmObj, constant.ConfigFinalizerName) - - // cmObj.Immutable = &tpl.Spec.Immutable - return cli.Patch(ctx.Ctx, cmObj, patch) + return tasks } -func deleteConfigMapFinalizer(cli client.Client, ctx intctrlutil.RequestCtx, configSpec appsv1.ComponentConfigSpec) error { - cmObj, err := getConfigMapByTemplateName(cli, ctx, configSpec.TemplateRef, configSpec.Namespace) - if err != nil && apierrors.IsNotFound(err) { - return nil - } else if err != nil { - ctx.Log.Error(err, "failed to get config template cm object!", "configMapName", configSpec.TemplateRef) - return err - } - - if !controllerutil.ContainsFinalizer(cmObj, constant.ConfigFinalizerName) { +func fromItemStatus(ctx intctrlutil.RequestCtx, status *parametersv1alpha1.ComponentParameterStatus, item parametersv1alpha1.ConfigTemplateItemDetail, generation int64) *parametersv1alpha1.ConfigTemplateItemDetailStatus { + if item.ConfigSpec == nil { + ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration is creating and pass: %s", item.Name)) return nil } - - patch := client.MergeFrom(cmObj.DeepCopy()) - controllerutil.RemoveFinalizer(cmObj, constant.ConfigFinalizerName) - return cli.Patch(ctx.Ctx, cmObj, patch) -} - -type ConfigTemplateHandler func([]appsv1.ComponentConfigSpec) (bool, error) -type componentValidateHandler func(*appsv1.ComponentDefinition) error - -func handleConfigTemplate(object client.Object, handler ConfigTemplateHandler, handler2 ...componentValidateHandler) (bool, error) { - var ( - err error - configTemplates []appsv1.ComponentConfigSpec - ) - switch cr := object.(type) { - case *appsv1.ComponentDefinition: - configTemplates, err = getConfigTemplateFromComponentDef(cr, handler2...) - default: - return false, core.MakeError("not support CR type: %v", cr) - } - - switch { - case err != nil: - return false, err - case len(configTemplates) > 0: - return handler(configTemplates) - default: - return true, nil - } -} - -func getConfigTemplateFromComponentDef(componentDef *appsv1.ComponentDefinition, - validators ...componentValidateHandler) ([]appsv1.ComponentConfigSpec, error) { - configTemplates := make([]appsv1.ComponentConfigSpec, 0) - // For compatibility with the previous lifecycle management of configurationSpec.TemplateRef, - // it is necessary to convert ScriptSpecs to ConfigSpecs, - // ensuring that the script-related configmap is not allowed to be deleted. - for _, scriptSpec := range componentDef.Spec.Scripts { - configTemplates = append(configTemplates, appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: scriptSpec, + itemStatus := intctrlutil.GetItemStatus(status, item.Name) + if itemStatus == nil || itemStatus.Phase == "" { + ctx.Log.WithName(item.Name).Info(fmt.Sprintf("ComponentParameters cr is creating: %v", item)) + status.ConfigurationItemStatus = append(status.ConfigurationItemStatus, parametersv1alpha1.ConfigTemplateItemDetailStatus{ + Name: item.Name, + Phase: parametersv1alpha1.CInitPhase, + UpdateRevision: strconv.FormatInt(generation, 10), }) + itemStatus = intctrlutil.GetItemStatus(status, item.Name) } - if len(componentDef.Spec.Configs) > 0 { - // Check reload configure config template - for _, validator := range validators { - if err := validator(componentDef); err != nil { - return nil, err - } - } - } - return append(configTemplates, componentDef.Spec.Configs...), nil -} - -func checkConfigTemplate(client client.Client, ctx intctrlutil.RequestCtx, obj client.Object) (bool, error) { - handler := func(configSpecs []appsv1.ComponentConfigSpec) (bool, error) { - return validateConfigTemplate(client, ctx, configSpecs) + if !isReconcileStatus(itemStatus.Phase) { + ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration cr is creating or deleting and pass: %v", itemStatus)) + return nil } - return handleConfigTemplate(obj, handler) + return itemStatus } -func updateLabelsByConfigSpec[T generics.Object, PT generics.PObject[T]](cli client.Client, ctx intctrlutil.RequestCtx, obj PT) (bool, error) { - handler := func(configSpecs []appsv1.ComponentConfigSpec) (bool, error) { - patch := client.MergeFrom(PT(obj.DeepCopy())) - configuration.BuildConfigConstraintLabels(obj, configSpecs) - return true, cli.Patch(ctx.Ctx, obj, patch) - } - return handleConfigTemplate(obj, handler) +func isReconcileStatus(phase parametersv1alpha1.ParameterPhase) bool { + return phase != "" && + phase != parametersv1alpha1.CCreatingPhase && + phase != parametersv1alpha1.CDeletingPhase } -func validateConfigTemplate(cli client.Client, ctx intctrlutil.RequestCtx, configSpecs []appsv1.ComponentConfigSpec) (bool, error) { - // validate ConfigTemplate - foundAndCheckConfigSpec := func(configSpec appsv1.ComponentConfigSpec, logger logr.Logger) (*appsv1beta1.ConfigConstraint, error) { - if _, err := getConfigMapByTemplateName(cli, ctx, configSpec.TemplateRef, configSpec.Namespace); err != nil { - logger.Error(err, "failed to get config template cm object!") - return nil, err - } - if configSpec.VolumeName == "" && configuration.InjectEnvEnabled(configSpec) { - return nil, core.MakeError("config template volume name and envFrom is empty!") - } - if configSpec.ConfigConstraintRef == "" { - return nil, nil - } - configKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - configObj := &appsv1beta1.ConfigConstraint{} - if err := cli.Get(ctx.Ctx, configKey, configObj); err != nil { - logger.Error(err, "failed to get template cm object!") - return nil, err - } - return configObj, nil - } - - for _, templateRef := range configSpecs { - logger := ctx.Log.WithValues("templateName", templateRef.Name).WithValues("configMapName", templateRef.TemplateRef) - configConstraint, err := foundAndCheckConfigSpec(templateRef, logger) +func buildTemplateVars(ctx context.Context, cli client.Reader, + compDef *appsv1.ComponentDefinition, synthesizedComp *component.SynthesizedComponent) error { + if compDef != nil && len(compDef.Spec.Vars) > 0 { + templateVars, _, err := component.ResolveTemplateNEnvVars(ctx, cli, synthesizedComp, compDef.Spec.Vars) if err != nil { - logger.Error(err, "failed to validate config template!") - return false, err - } - if configConstraint == nil || configConstraint.Spec.ReloadAction == nil { - continue - } - if err := cfgcm.ValidateReloadOptions(configConstraint.Spec.ReloadAction, cli, ctx.Ctx); err != nil { - return false, err - } - if !validateConfigConstraintStatus(configConstraint.Status) { - errMsg := fmt.Sprintf("Configuration template CR[%s] status not ready! current status: %s", configConstraint.Name, configConstraint.Status.Phase) - logger.V(1).Info(errMsg) - return false, fmt.Errorf("%s", errMsg) + return err } + synthesizedComp.TemplateVars = templateVars } - return true, nil -} - -func validateConfigConstraintStatus(ccStatus appsv1beta1.ConfigConstraintStatus) bool { - return ccStatus.Phase == appsv1beta1.CCAvailablePhase -} - -func updateConfigConstraintStatus(cli client.Client, ctx intctrlutil.RequestCtx, configConstraint *appsv1beta1.ConfigConstraint, phase appsv1beta1.ConfigConstraintPhase) error { - patch := client.MergeFrom(configConstraint.DeepCopy()) - configConstraint.Status.Phase = phase - configConstraint.Status.ObservedGeneration = configConstraint.Generation - return cli.Status().Patch(ctx.Ctx, configConstraint, patch) -} - -func createConfigPatch(cfg *corev1.ConfigMap, formatter *appsv1beta1.FileFormatConfig, cmKeys []string) (*core.ConfigPatchInfo, bool, error) { - // support full update - if formatter == nil { - return nil, true, nil - } - lastConfig, err := getLastVersionConfig(cfg) - if err != nil { - return nil, false, core.WrapError(err, "failed to get last version data. config[%v]", client.ObjectKeyFromObject(cfg)) - } - - return core.CreateConfigPatch(lastConfig, cfg.Data, formatter.Format, cmKeys, true) -} - -func updateConfigSchema(cc *appsv1beta1.ConfigConstraint, cli client.Client, ctx context.Context) error { - schema := cc.Spec.ParametersSchema - if schema == nil || schema.CUE == "" { - return nil - } - - // Because the conversion of cue to openAPISchema is restricted, and the definition of some cue may not be converted into openAPISchema, and won't return error. - openAPISchema, err := openapi.GenerateOpenAPISchema(schema.CUE, schema.TopLevelKey) - if err != nil { - return err - } - if openAPISchema == nil { - return nil - } - if reflect.DeepEqual(openAPISchema, schema.SchemaInJSON) { - return nil - } - - ccPatch := client.MergeFrom(cc.DeepCopy()) - cc.Spec.ParametersSchema.SchemaInJSON = openAPISchema - return cli.Patch(ctx, cc, ccPatch) + return nil } diff --git a/controllers/parameters/config_util_test.go b/controllers/parameters/config_util_test.go deleted file mode 100644 index 25485aaaf7d..00000000000 --- a/controllers/parameters/config_util_test.go +++ /dev/null @@ -1,202 +0,0 @@ -/* -Copyright (C) 2022-2024 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" - - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - "github.com/apecloud/kubeblocks/pkg/generics" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" - testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" -) - -var _ = Describe("ConfigWrapper util test", func() { - var ( - // ctrl *gomock.Controller - // mockClient *mock_client.MockClient - k8sMockClient *testutil.K8sClientMockHelper - - reqCtx = intctrlutil.RequestCtx{ - Ctx: ctx, - Log: log.FromContext(ctx).WithValues("reconfigure_for_test", testCtx.DefaultNamespace), - } - ) - - var ( - configMapObj *corev1.ConfigMap - configConstraintObj *appsv1beta1.ConfigConstraint - compDefObj *appsv1.ComponentDefinition - ) - - cleanEnv := func() { - // 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, - // in race conditions, it will find the existence of old objects, resulting failure to - // create the new objects. - By("clean resources") - - // delete rest mocked objects - inNS := client.InNamespace(testCtx.DefaultNamespace) - ml := client.HasLabels{testCtx.TestObjLabelKey} - // namespaced - testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml) - // non-namespaced - testapps.ClearResources(&testCtx, generics.ComponentDefinitionSignature, ml) - testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) - } - - BeforeEach(func() { - cleanEnv() - - // Add any setup steps that needs to be executed before each test - k8sMockClient = testutil.NewK8sMockClient() - - By("creating a cluster") - configMapObj = testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-template.yaml", &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace()) - - configConstraintObj = testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) - - By("Create a componentDefinition obj") - compDefObj = testapps.NewComponentDefinitionFactory(compDefName). - WithRandomName(). - SetDefaultSpec(). - AddConfigTemplate(configSpecName, configMapObj.Name, configConstraintObj.Name, testCtx.DefaultNamespace, configVolumeName). - Create(&testCtx). - GetObject() - }) - - AfterEach(func() { - // Add any teardown steps that needs to be executed after each test - cleanEnv() - - k8sMockClient.Finish() - }) - - Context("ComponentDefinition CR test", func() { - It("Should success without error", func() { - availableTPL := configConstraintObj.DeepCopy() - availableTPL.Status.Phase = appsv1beta1.CCAvailablePhase - - k8sMockClient.MockPatchMethod(testutil.WithSucceed()) - k8sMockClient.MockListMethod(testutil.WithSucceed()) - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSequenceResult( - map[client.ObjectKey][]testutil.MockGetReturned{ - client.ObjectKeyFromObject(configMapObj): {{ - Object: nil, - Err: cfgcore.MakeError("failed to get cc object"), - }, { - Object: configMapObj, - Err: nil, - }}, - client.ObjectKeyFromObject(configConstraintObj): {{ - Object: nil, - Err: cfgcore.MakeError("failed to get cc object"), - }, { - Object: configConstraintObj, - Err: nil, - }, { - Object: availableTPL, - Err: nil, - }}, - }, - ), testutil.WithAnyTimes())) - - _, err := checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).ShouldNot(Succeed()) - Expect(err.Error()).Should(ContainSubstring("failed to get cc object")) - - _, err = checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).ShouldNot(Succeed()) - Expect(err.Error()).Should(ContainSubstring("failed to get cc object")) - - _, err = checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).ShouldNot(Succeed()) - Expect(err.Error()).Should(ContainSubstring("status not ready")) - - ok, err := checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).Should(Succeed()) - Expect(ok).Should(BeTrue()) - - ok, err = updateLabelsByConfigSpec(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).Should(Succeed()) - Expect(ok).Should(BeTrue()) - - _, err = updateLabelsByConfigSpec(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).Should(Succeed()) - - err = DeleteConfigMapFinalizer(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).Should(Succeed()) - }) - }) - - Context("ComponentDefinition CR test without config Constraints", func() { - It("Should success without error", func() { - // remove ConfigConstraintRef - _, err := handleConfigTemplate(compDefObj, func(templates []appsv1.ComponentConfigSpec) (bool, error) { - return true, nil - }, func(compDef *appsv1.ComponentDefinition) error { - if len(compDef.Spec.Configs) == 0 { - return nil - } - for i := range compDef.Spec.Configs { - tpl := &compDef.Spec.Configs[i] - tpl.ConfigConstraintRef = "" - } - return nil - }) - Expect(err).Should(Succeed()) - - availableTPL := configConstraintObj.DeepCopy() - availableTPL.Status.Phase = appsv1beta1.CCAvailablePhase - - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSequenceResult( - map[client.ObjectKey][]testutil.MockGetReturned{ - client.ObjectKeyFromObject(configMapObj): {{ - Object: nil, - Err: cfgcore.MakeError("failed to get cc object"), - }, { - Object: configMapObj, - Err: nil, - }}}, - ), testutil.WithAnyTimes())) - - _, err = checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).ShouldNot(Succeed()) - Expect(err.Error()).Should(ContainSubstring("failed to get cc object")) - - ok, err := checkConfigTemplate(k8sMockClient.Client(), reqCtx, compDefObj) - Expect(err).Should(Succeed()) - Expect(ok).Should(BeTrue()) - }) - }) -}) diff --git a/controllers/parameters/configconstraint_controller.go b/controllers/parameters/configconstraint_controller.go deleted file mode 100644 index c6041dbf13b..00000000000 --- a/controllers/parameters/configconstraint_controller.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Copyright (C) 2022-2024 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" - "time" - - 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" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" -) - -// ConfigConstraintReconciler reconciles a ConfigConstraint object -type ConfigConstraintReconciler struct { - client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder -} - -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configconstraints,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configconstraints/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configconstraints/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. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ConfigConstraint object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile -func (r *ConfigConstraintReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - reqCtx := intctrlutil.RequestCtx{ - Ctx: ctx, - Req: req, - Log: log.FromContext(ctx).WithName("ConfigConstraintReconcile").WithValues("ConfigConstraint", req.NamespacedName.Name), - Recorder: r.Recorder, - } - - configConstraint := &appsv1beta1.ConfigConstraint{} - if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, configConstraint); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") - } - - res, err := intctrlutil.HandleCRDeletion(reqCtx, r, configConstraint, constant.ConfigFinalizerName, func() (*ctrl.Result, error) { - recordEvent := func() { - r.Recorder.Event(configConstraint, corev1.EventTypeWarning, "ExistsReferencedResources", - "cannot be deleted because of existing referencing of ClusterDefinition.") - } - if configConstraint.Status.Phase != appsv1beta1.CCDeletingPhase { - err := updateConfigConstraintStatus(r.Client, reqCtx, configConstraint, appsv1beta1.CCDeletingPhase) - // if fail to update ConfigConstraint status, return error, - // so that it can be retried - if err != nil { - return nil, err - } - } - if res, err := intctrlutil.ValidateReferenceCR(reqCtx, r.Client, configConstraint, - cfgcore.GenerateConstraintsUniqLabelKeyWithConfig(configConstraint.GetName()), - recordEvent, &appsv1.ClusterDefinitionList{}, &appsv1.ComponentDefinitionList{}); res != nil || err != nil { - return res, err - } - return nil, nil - }) - if res != nil { - return *res, err - } - - if configConstraint.Status.ObservedGeneration == configConstraint.Generation && configConstraint.Status.ConfigConstraintTerminalPhases() { - return intctrlutil.Reconciled() - } - - if ok, err := checkConfigConstraint(reqCtx, configConstraint); !ok || err != nil { - return intctrlutil.RequeueAfter(time.Second, reqCtx.Log, "ValidateConfigurationTemplate") - } - - // Automatically convert cue to openAPISchema. - if err := updateConfigSchema(configConstraint, r.Client, ctx); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to generate openAPISchema") - } - - err = updateConfigConstraintStatus(r.Client, reqCtx, configConstraint, appsv1beta1.CCAvailablePhase) - if err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") - } - intctrlutil.RecordCreatedEvent(r.Recorder, configConstraint) - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ConfigConstraintReconciler) SetupWithManager(mgr ctrl.Manager) error { - return intctrlutil.NewControllerManagedBy(mgr). - For(&appsv1beta1.ConfigConstraint{}). - // for other resource - Owns(&corev1.ConfigMap{}). // TODO(leon) - Complete(r) -} diff --git a/controllers/parameters/configconstraint_controller_test.go b/controllers/parameters/configconstraint_controller_test.go deleted file mode 100644 index ae0f09cb27e..00000000000 --- a/controllers/parameters/configconstraint_controller_test.go +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright (C) 2022-2024 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 ( - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - intctrlutil "github.com/apecloud/kubeblocks/pkg/generics" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" -) - -var _ = Describe("ConfigConstraint Controller", func() { - cleanEnv := func() { - // 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, - // in race conditions, it will find the existence of old objects, resulting failure to - // create the new objects. - By("clean resources") - - // delete cluster(and all dependent sub-resources), cluster definition - testapps.ClearClusterResources(&testCtx) - - // delete rest mocked objects - inNS := client.InNamespace(testCtx.DefaultNamespace) - ml := client.HasLabels{testCtx.TestObjLabelKey} - // non-namespaced - testapps.ClearResources(&testCtx, intctrlutil.ConfigConstraintSignature, ml) - // namespaced - testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ConfigMapSignature, true, inNS, ml) - } - - BeforeEach(cleanEnv) - - AfterEach(cleanEnv) - - Context("Create config constraint with cue validate", func() { - It("Should ready", func() { - By("creating a configmap and a config constraint") - configmap := testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-template.yaml", &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace()) - constraint := testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) - constraintKey := client.ObjectKeyFromObject(constraint) - - By("Create a componentDefinition obj") - compDefObj := testapps.NewComponentDefinitionFactory(compDefName). - WithRandomName(). - SetDefaultSpec(). - AddConfigTemplate(configSpecName, configmap.Name, constraint.Name, testCtx.DefaultNamespace, configVolumeName). - AddLabels(cfgcore.GenerateTPLUniqLabelKeyWithConfig(configSpecName), configmap.Name, - cfgcore.GenerateConstraintsUniqLabelKeyWithConfig(constraint.Name), constraint.Name). - Create(&testCtx). - GetObject() - - By("check ConfigConstraint(template) status and finalizer") - Eventually(testapps.CheckObj(&testCtx, constraintKey, - func(g Gomega, tpl *appsv1beta1.ConfigConstraint) { - g.Expect(tpl.Status.Phase).To(BeEquivalentTo(appsv1alpha1.AvailablePhase)) - g.Expect(tpl.Finalizers).To(ContainElement(constant.ConfigFinalizerName)) - })).Should(Succeed()) - - By("By delete ConfigConstraint") - Expect(k8sClient.Delete(testCtx.Ctx, constraint)).Should(Succeed()) - - By("check ConfigConstraint should not be deleted") - log.Log.Info("expect that ConfigConstraint is not deleted.") - Consistently(testapps.CheckObjExists(&testCtx, constraintKey, &appsv1beta1.ConfigConstraint{}, true)).Should(Succeed()) - - By("check ConfigConstraint status should be deleting") - Eventually(testapps.CheckObj(&testCtx, constraintKey, - func(g Gomega, tpl *appsv1beta1.ConfigConstraint) { - g.Expect(tpl.Status.Phase).To(BeEquivalentTo(appsv1beta1.CCDeletingPhase)) - })).Should(Succeed()) - - By("By delete referencing componentdefinition") - Expect(k8sClient.Delete(testCtx.Ctx, compDefObj)).Should(Succeed()) - - By("check ConfigConstraint should be deleted") - Eventually(testapps.CheckObjExists(&testCtx, constraintKey, &appsv1beta1.ConfigConstraint{}, false), time.Second*60, time.Second*1).Should(Succeed()) - }) - }) - - Context("Create config constraint without cue validate", func() { - It("Should ready", func() { - By("creating a configmap and a config constraint") - - _ = testapps.CreateCustomizedObj(&testCtx, "resources/mysql-config-template.yaml", &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace()) - - constraint := testapps.CreateCustomizedObj(&testCtx, "resources/mysql-config-constraint-not-validate.yaml", - &appsv1beta1.ConfigConstraint{}) - - By("check config constraint status") - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(constraint), - func(g Gomega, tpl *appsv1beta1.ConfigConstraint) { - g.Expect(tpl.Status.Phase).Should(BeEquivalentTo(appsv1alpha1.AvailablePhase)) - })).Should(Succeed()) - }) - }) -}) diff --git a/controllers/parameters/configuration_controller.go b/controllers/parameters/configuration_controller.go deleted file mode 100644 index 4a97f7a2ef4..00000000000 --- a/controllers/parameters/configuration_controller.go +++ /dev/null @@ -1,243 +0,0 @@ -/* -Copyright (C) 2022-2024 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" - "math" - "strconv" - "time" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - 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/log" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/multicluster" - "github.com/apecloud/kubeblocks/pkg/controller/render" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - viper "github.com/apecloud/kubeblocks/pkg/viperx" -) - -// ConfigurationReconciler reconciles a Configuration object -type ConfigurationReconciler struct { - client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder -} - -const reconcileInterval = time.Second * 2 - -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations/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. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Configuration object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// 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 *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - reqCtx := intctrlutil.RequestCtx{ - Ctx: ctx, - Req: req, - Log: log.FromContext(ctx).WithName("ConfigurationReconcile").WithValues("configuration", req.NamespacedName), - Recorder: r.Recorder, - } - - config := &appsv1alpha1.Configuration{} - if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, config); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "cannot find configuration") - } - - res, err := intctrlutil.HandleCRDeletion(reqCtx, r, config, constant.ConfigFinalizerName, nil) - if res != nil { - return *res, err - } - - tasks := make([]Task, 0, len(config.Spec.ConfigItemDetails)) - for _, item := range config.Spec.ConfigItemDetails { - if status := fromItemStatus(reqCtx, &config.Status, item); status != nil { - tasks = append(tasks, NewTask(item, status)) - } - } - if len(tasks) == 0 { - return intctrlutil.Reconciled() - } - - fetcherTask := &Task{} - err = fetcherTask.Init(&render.ResourceCtx{ - Context: ctx, - Client: r.Client, - Namespace: config.Namespace, - ClusterName: config.Spec.ClusterRef, - ComponentName: config.Spec.ComponentName, - }, fetcherTask).Cluster(). - ComponentAndComponentDef(). - ComponentSpec(). - Complete() - if err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to get related object.") - } - - if !fetcherTask.ClusterObj.GetDeletionTimestamp().IsZero() { - reqCtx.Log.Info("cluster is deleting, skip reconcile") - return intctrlutil.Reconciled() - } - if fetcherTask.ClusterComObj == nil || fetcherTask.ComponentObj == nil { - return r.failWithInvalidComponent(config, reqCtx) - } - if err := r.runTasks(TaskContext{config, ctx, fetcherTask}, tasks); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to run configuration reconcile task.") - } - if !isAllReady(config) { - return intctrlutil.RequeueAfter(reconcileInterval, reqCtx.Log, "") - } - return intctrlutil.Reconciled() -} - -func (r *ConfigurationReconciler) failWithInvalidComponent(configuration *appsv1alpha1.Configuration, reqCtx intctrlutil.RequestCtx) (ctrl.Result, error) { - msg := fmt.Sprintf("not found cluster component or cluster definition component: [%s]", configuration.Spec.ComponentName) - reqCtx.Log.Error(fmt.Errorf("%s", msg), "") - patch := client.MergeFrom(configuration.DeepCopy()) - configuration.Status.Message = msg - if err := r.Client.Status().Patch(reqCtx.Ctx, configuration, patch); err != nil { - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to update configuration status.") - } - return intctrlutil.Reconciled() -} - -func isAllReady(configuration *appsv1alpha1.Configuration) bool { - for _, item := range configuration.Spec.ConfigItemDetails { - itemStatus := configuration.Status.GetItemStatus(item.Name) - if itemStatus != nil && !isFinishStatus(itemStatus.Phase) { - return false - } - } - return true -} - -func (r *ConfigurationReconciler) runTasks(taskCtx TaskContext, tasks []Task) (err error) { - var ( - errs []error - synthesizedComp *component.SynthesizedComponent - configuration = taskCtx.configuration - ) - - // build synthesized component for the component - synthesizedComp, err = component.BuildSynthesizedComponent(taskCtx.ctx, r.Client, - taskCtx.fetcher.ComponentDefObj, taskCtx.fetcher.ComponentObj, taskCtx.fetcher.ClusterObj) - if err == nil { - err = buildTemplateVars(taskCtx.ctx, r.Client, taskCtx.fetcher.ComponentDefObj, synthesizedComp) - } - if err != nil { - return err - } - - // TODO manager multiple version - patch := client.MergeFrom(configuration.DeepCopy()) - revision := strconv.FormatInt(configuration.GetGeneration(), 10) - for _, task := range tasks { - if err := task.Do(taskCtx.fetcher, synthesizedComp, revision); err != nil { - errs = append(errs, err) - continue - } - } - - configuration.Status.Message = "" - if len(errs) > 0 { - configuration.Status.Message = utilerrors.NewAggregate(errs).Error() - } - if err := r.Client.Status().Patch(taskCtx.ctx, configuration, patch); err != nil { - errs = append(errs, err) - } - if len(errs) == 0 { - return nil - } - return utilerrors.NewAggregate(errs) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ConfigurationReconciler) SetupWithManager(mgr ctrl.Manager, multiClusterMgr multicluster.Manager) error { - b := intctrlutil.NewControllerManagedBy(mgr). - For(&appsv1alpha1.Configuration{}). - WithOptions(controller.Options{ - MaxConcurrentReconciles: int(math.Ceil(viper.GetFloat64(constant.CfgKBReconcileWorkers) / 2)), - }). - Owns(&corev1.ConfigMap{}) - - if multiClusterMgr != nil { - multiClusterMgr.Own(b, &corev1.ConfigMap{}, &appsv1alpha1.Configuration{}) - } - - return b.Complete(r) -} - -func fromItemStatus(ctx intctrlutil.RequestCtx, status *appsv1alpha1.ConfigurationStatus, item appsv1alpha1.ConfigurationItemDetail) *appsv1alpha1.ConfigurationItemDetailStatus { - if item.ConfigSpec == nil { - ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration is creating and pass: %s", item.Name)) - return nil - } - itemStatus := status.GetItemStatus(item.Name) - if itemStatus == nil || itemStatus.Phase == "" { - ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration cr is creating and pass: %v", item)) - return nil - } - if !isReconcileStatus(itemStatus.Phase) { - ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration cr is creating or deleting and pass: %v", itemStatus)) - return nil - } - return itemStatus -} - -func isReconcileStatus(phase appsv1alpha1.ConfigurationPhase) bool { - return phase != "" && - phase != appsv1alpha1.CCreatingPhase && - phase != appsv1alpha1.CDeletingPhase -} - -func isFinishStatus(phase appsv1alpha1.ConfigurationPhase) bool { - return phase == appsv1alpha1.CFinishedPhase || phase == appsv1alpha1.CFailedAndPausePhase -} - -func buildTemplateVars(ctx context.Context, cli client.Reader, - compDef *appsv1.ComponentDefinition, synthesizedComp *component.SynthesizedComponent) error { - if compDef != nil && len(compDef.Spec.Vars) > 0 { - templateVars, _, err := component.ResolveTemplateNEnvVars(ctx, cli, synthesizedComp, compDef.Spec.Vars) - if err != nil { - return err - } - synthesizedComp.TemplateVars = templateVars - } - return nil -} diff --git a/controllers/parameters/configuration_controller_test.go b/controllers/parameters/configuration_controller_test.go deleted file mode 100644 index 53b64146d1d..00000000000 --- a/controllers/parameters/configuration_controller_test.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -Copyright (C) 2022-2024 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 ( - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - "github.com/apecloud/kubeblocks/pkg/controller/render" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" -) - -var _ = Describe("Configuration Controller", func() { - - BeforeEach(cleanEnv) - - AfterEach(cleanEnv) - - Context("When updating configuration", func() { - It("Should reconcile success", func() { - _, _, clusterObj, componentObj, synthesizedComp := mockReconcileResource() - - cfgKey := client.ObjectKey{ - Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), - Namespace: testCtx.DefaultNamespace, - } - checkCfgStatus := func(phase appsv1alpha1.ConfigurationPhase) func() bool { - return func() bool { - cfg := &appsv1alpha1.Configuration{} - Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) - itemStatus := cfg.Status.GetItemStatus(configSpecName) - return itemStatus != nil && itemStatus.Phase == phase - } - } - - By("wait for configuration status to be init phase.") - Eventually(checkCfgStatus(appsv1alpha1.CInitPhase)).Should(BeFalse()) - Expect(initConfiguration(&render.ResourceCtx{ - Client: k8sClient, - Context: ctx, - Namespace: testCtx.DefaultNamespace, - ClusterName: clusterName, - ComponentName: defaultCompName, - }, synthesizedComp, clusterObj, componentObj)).Should(Succeed()) - - Eventually(checkCfgStatus(appsv1alpha1.CFinishedPhase)).Should(BeTrue()) - - By("reconfiguring parameters.") - Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *appsv1alpha1.Configuration) { - cfg.Spec.GetConfigurationItem(configSpecName).ConfigFileParams = map[string]appsv1alpha1.ConfigParams{ - "my.cnf": { - Parameters: map[string]*string{ - "max_connections": cfgutil.ToPointer("1000"), - "gtid_mode": cfgutil.ToPointer("ON"), - }, - }, - } - })).Should(Succeed()) - - Eventually(func(g Gomega) { - cfg := &appsv1alpha1.Configuration{} - g.Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) - itemStatus := cfg.Status.GetItemStatus(configSpecName) - g.Expect(itemStatus).ShouldNot(BeNil()) - g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) - g.Expect(itemStatus.Phase).Should(BeEquivalentTo(appsv1alpha1.CFinishedPhase)) - }, time.Second*60, time.Second*1).Should(Succeed()) - }) - - It("Invalid component test", func() { - _, _, clusterObj, componentObj, synthesizedComp := mockReconcileResource() - - cfgKey := client.ObjectKey{ - Name: core.GenerateComponentConfigurationName(clusterName, "invalid-component"), - Namespace: testCtx.DefaultNamespace, - } - - Expect(initConfiguration(&render.ResourceCtx{ - Client: k8sClient, - Context: ctx, - Namespace: testCtx.DefaultNamespace, - ClusterName: clusterName, - ComponentName: "invalid-component", - }, synthesizedComp, clusterObj, componentObj)).Should(Succeed()) - - Eventually(func(g Gomega) { - cfg := &appsv1alpha1.Configuration{} - g.Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) - g.Expect(cfg.Status.Message).Should(ContainSubstring("not found cluster component")) - }, time.Second*60, time.Second*1).Should(Succeed()) - }) - }) - - Context("When updating configuration with injectEnvTo", func() { - It("Should reconcile success", func() { - _, _, clusterObj, componentObj, synthesizedComp := mockReconcileResource() - synthesizedComp.ConfigTemplates[0].AsSecret = cfgutil.ToPointer(true) - synthesizedComp.ConfigTemplates[0].InjectEnvTo = []string{"mock-container"} - - cfgKey := client.ObjectKey{ - Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), - Namespace: testCtx.DefaultNamespace, - } - renderedKey := client.ObjectKey{ - Name: core.GetComponentCfgName(synthesizedComp.ClusterName, synthesizedComp.Name, synthesizedComp.ConfigTemplates[0].Name), - Namespace: testCtx.DefaultNamespace, - } - checkCfgStatus := func(phase appsv1alpha1.ConfigurationPhase) func() bool { - return func() bool { - cfg := &appsv1alpha1.Configuration{} - Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) - itemStatus := cfg.Status.GetItemStatus(configSpecName) - return itemStatus != nil && itemStatus.Phase == phase - } - } - - By("wait for configuration status to be init phase.") - Eventually(checkCfgStatus(appsv1alpha1.CInitPhase)).Should(BeFalse()) - Expect(initConfiguration(&render.ResourceCtx{ - Client: k8sClient, - Context: ctx, - Namespace: testCtx.DefaultNamespace, - ClusterName: clusterName, - ComponentName: defaultCompName, - }, synthesizedComp, clusterObj, componentObj)).Should(Succeed()) - - Eventually(checkCfgStatus(appsv1alpha1.CFinishedPhase)).Should(BeTrue()) - - Eventually(testapps.CheckObjExists(&testCtx, renderedKey, &corev1.ConfigMap{}, false)).Should(Succeed()) - Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKey{ - Name: core.GenerateEnvFromName(renderedKey.Name), - Namespace: renderedKey.Namespace, - }, &corev1.Secret{}, true)).Should(Succeed()) - - By("reconfiguring parameters.") - Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *appsv1alpha1.Configuration) { - cfg.Spec.GetConfigurationItem(configSpecName).ConfigFileParams = map[string]appsv1alpha1.ConfigParams{ - "my.cnf": { - Parameters: map[string]*string{ - "max_connections": cfgutil.ToPointer("1000"), - "gtid_mode": cfgutil.ToPointer("ON"), - }, - }, - } - })).Should(Succeed()) - - Eventually(func(g Gomega) { - cfg := &appsv1alpha1.Configuration{} - g.Expect(k8sClient.Get(ctx, cfgKey, cfg)).Should(Succeed()) - itemStatus := cfg.Status.GetItemStatus(configSpecName) - g.Expect(itemStatus).ShouldNot(BeNil()) - g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) - g.Expect(itemStatus.Phase).Should(BeEquivalentTo(appsv1alpha1.CFinishedPhase)) - }, time.Second*60, time.Second*1).Should(Succeed()) - - Eventually(testapps.CheckObjExists(&testCtx, renderedKey, &corev1.ConfigMap{}, false)).Should(Succeed()) - Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKey{ - Name: core.GenerateEnvFromName(renderedKey.Name), - Namespace: renderedKey.Namespace, - }, &corev1.Secret{}, true)).Should(Succeed()) - }) - - }) -}) diff --git a/controllers/parameters/configuration_test.go b/controllers/parameters/configuration_test.go index 9efaace02bc..4dc60ab0627 100644 --- a/controllers/parameters/configuration_test.go +++ b/controllers/parameters/configuration_test.go @@ -30,16 +30,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" - configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" - "github.com/apecloud/kubeblocks/pkg/controller/render" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" + "github.com/apecloud/kubeblocks/test/testdata" ) const ( @@ -52,14 +51,18 @@ const ( cmName = "mysql-tree-node-template-8.0" paramsDefName = "mysql-params-def" pdcrName = "config-test-pdcr" + envTestFileKey = "env_test" ) -func mockConfigResource() (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint) { +func mockSchemaData() string { + cue, _ := testdata.GetTestDataFileContent("cue_testdata/wesql.cue") + return string(cue) +} + +func mockConfigResource() (*corev1.ConfigMap, *parametersv1alpha1.ParametersDefinition) { By("Create a config template obj") - configmap := testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-template.yaml", &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace(), - testapps.WithLabels( + configmap := testparameters.NewComponentTemplateFactory(configSpecName, testCtx.DefaultNamespace). + AddLabels( constant.AppNameLabelKey, clusterName, constant.AppInstanceLabelKey, clusterName, constant.KBAppComponentLabelKey, defaultCompName, @@ -67,58 +70,52 @@ func mockConfigResource() (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint) { constant.CMConfigurationConstraintsNameLabelKey, cmName, constant.CMConfigurationSpecProviderLabelKey, configSpecName, constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType, - ), - testapps.WithAnnotations( + ). + AddAnnotations( constant.KBParameterUpdateSourceAnnotationKey, constant.ReconfigureManagerSource, constant.ConfigurationRevision, "1", - constant.CMInsEnableRerenderTemplateKey, "true")) - - By("Create a config constraint obj") - constraint := testapps.CreateCustomizedObj(&testCtx, - "resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) - - By("check config constraint") - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(constraint), func(g Gomega, tpl *appsv1beta1.ConfigConstraint) { - g.Expect(tpl.Status.Phase).Should(BeEquivalentTo(appsv1alpha1.AvailablePhase)) - })).Should(Succeed()) + constant.CMInsEnableRerenderTemplateKey, "true"). + AddConfigFile(envTestFileKey, "abcde=1234"). + Create(&testCtx). + GetObject() - By("Create a configuration obj") - // test-cluster-mysql-mysql-config-tpl - configuration := builder.NewConfigurationBuilder(testCtx.DefaultNamespace, core.GenerateComponentConfigurationName(clusterName, defaultCompName)). - ClusterRef(clusterName). - Component(defaultCompName). - AddConfigurationItem(appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: configSpecName, - TemplateRef: configmap.Name, - Namespace: configmap.Namespace, - VolumeName: configVolumeName, - }, - ConfigConstraintRef: constraint.Name, - }). + By("Create a parameters definition obj") + paramsdef := testparameters.NewParametersDefinitionFactory(paramsDefName). + SetReloadAction(testparameters.WithNoneAction()). + Schema(mockSchemaData()). + Create(&testCtx). GetObject() - Expect(testCtx.CreateObj(testCtx.Ctx, configuration)).Should(Succeed()) - return configmap, constraint + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(paramsdef), func(g Gomega, def *parametersv1alpha1.ParametersDefinition) { + g.Expect(def.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.PDAvailablePhase)) + })).Should(Succeed()) + return configmap, paramsdef } -func mockReconcileResource() (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint, *appsv1.Cluster, *appsv1.Component, *component.SynthesizedComponent) { - configmap, constraint := mockConfigResource() +func mockReconcileResource() (*corev1.ConfigMap, *parametersv1alpha1.ParametersDefinition, *appsv1.Cluster, *appsv1.Component, *component.SynthesizedComponent) { + configmap, paramsDef := mockConfigResource() By("Create a component definition obj and mock to available") compDefObj := testapps.NewComponentDefinitionFactory(compDefName). WithRandomName(). SetDefaultSpec(). - AddConfigTemplate(configSpecName, configmap.Name, constraint.Name, testCtx.DefaultNamespace, configVolumeName). - AddLabels(core.GenerateTPLUniqLabelKeyWithConfig(configSpecName), configmap.Name, - core.GenerateConstraintsUniqLabelKeyWithConfig(constraint.Name), constraint.Name). + AddConfigTemplate(configSpecName, configmap.Name, testCtx.DefaultNamespace, configVolumeName). Create(&testCtx). GetObject() Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { obj.Status.Phase = appsv1.AvailablePhase })()).Should(Succeed()) + pdcr := testparameters.NewParametersDrivenConfigFactory(pdcrName). + SetParametersDefs(paramsDef.GetName()). + SetComponentDefinition(compDefObj.GetName()). + SetTemplateName(configSpecName). + Create(&testCtx). + GetObject() + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(pdcr), func(obj *parametersv1alpha1.ParameterDrivenConfigRender) { + obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + })()).Should(Succeed()) + By("Creating a cluster") clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, ""). AddComponent(defaultCompName, compDefObj.GetName()). @@ -132,7 +129,21 @@ func mockReconcileResource() (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint, AddLabels(constant.AppInstanceLabelKey, clusterName). SetUID(types.UID(fmt.Sprintf("%s-%s", clusterObj.Name, "test-uid"))). SetReplicas(1). - Create(&testCtx).GetObject() + Create(&testCtx). + GetObject() + + By("Create a componentParameter obj") + componentParameter := builder.NewComponentParameterBuilder(testCtx.DefaultNamespace, core.GenerateComponentConfigurationName(clusterName, defaultCompName)). + ClusterRef(clusterName). + Component(defaultCompName). + AddConfigurationItem(appsv1.ComponentTemplateSpec{ + Name: configSpecName, + TemplateRef: configmap.Name, + Namespace: configmap.Namespace, + VolumeName: configVolumeName, + }). + GetObject() + Expect(testCtx.CreateObj(testCtx.Ctx, componentParameter)).Should(Succeed()) container := *builder.NewContainerBuilder("mock-container"). AddVolumeMounts(corev1.VolumeMount{ @@ -151,27 +162,7 @@ func mockReconcileResource() (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint, synthesizedComp, err := component.BuildSynthesizedComponent(testCtx.Ctx, testCtx.Cli, compDefObj, compObj, clusterObj) Expect(err).ShouldNot(HaveOccurred()) - return configmap, constraint, clusterObj, compObj, synthesizedComp -} - -func initConfiguration(resourceCtx *render.ResourceCtx, - synthesizedComponent *component.SynthesizedComponent, - clusterObj *appsv1.Cluster, - componentObj *appsv1.Component) error { - return configctrl.NewCreatePipeline(render.ReconcileCtx{ - ResourceCtx: resourceCtx, - Component: componentObj, - SynthesizedComponent: synthesizedComponent, - Cluster: clusterObj, - PodSpec: synthesizedComponent.PodSpec, - }). - Prepare(). - UpdateConfiguration(). // reconcile Configuration - Configuration(). // sync Configuration - CreateConfigTemplate(). - UpdateConfigRelatedObject(). - UpdateConfigurationStatus(). - Complete() + return configmap, paramsDef, clusterObj, compObj, synthesizedComp } func cleanEnv() { diff --git a/controllers/parameters/parallel_upgrade_policy.go b/controllers/parameters/parallel_upgrade_policy.go index b653b607071..fa1d0b66cf5 100644 --- a/controllers/parameters/parallel_upgrade_policy.go +++ b/controllers/parameters/parallel_upgrade_policy.go @@ -22,43 +22,44 @@ package parameters import ( corev1 "k8s.io/api/core/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" podutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) -type parallelUpgradePolicy struct { -} +var restartContainerPolicyInstance = &restartContainerUpgradePolicy{} + +type restartContainerUpgradePolicy struct{} func init() { - RegisterPolicy(appsv1alpha1.RestartPolicy, ¶llelUpgradePolicy{}) + registerPolicy(parametersv1alpha1.RestartContainerPolicy, restartContainerPolicyInstance) } -func (p *parallelUpgradePolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { +func (p *restartContainerUpgradePolicy) Upgrade(rctx reconfigureContext) (ReturnedStatus, error) { funcs := GetInstanceSetRollingUpgradeFuncs() - pods, err := funcs.GetPodsFunc(params) + pods, err := funcs.GetPodsFunc(rctx) if err != nil { return makeReturnedStatus(ESFailedAndRetry), err } - return p.restartPods(params, pods, funcs) + return p.restartPods(rctx, pods, funcs) } -func (p *parallelUpgradePolicy) GetPolicyName() string { - return string(appsv1alpha1.RestartPolicy) +func (p *restartContainerUpgradePolicy) GetPolicyName() string { + return string(parametersv1alpha1.RestartContainerPolicy) } -func (p *parallelUpgradePolicy) restartPods(params reconfigureParams, pods []corev1.Pod, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { - var configKey = params.getConfigKey() - var configVersion = params.getTargetVersionHash() +func (p *restartContainerUpgradePolicy) restartPods(rctx reconfigureContext, pods []corev1.Pod, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { + var configKey = rctx.getConfigKey() + var configVersion = rctx.getTargetVersionHash() for _, pod := range pods { if podutil.IsMatchConfigVersion(&pod, configKey, configVersion) { continue } - if err := funcs.RestartContainerFunc(&pod, params.Ctx.Ctx, params.ContainerNames, params.ReconfigureClientFactory); err != nil { + if err := funcs.RestartContainerFunc(&pod, rctx.Ctx, rctx.ContainerNames, rctx.ReconfigureClientFactory); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } - if err := updatePodLabelsWithConfigVersion(&pod, configKey, configVersion, params.Client, params.Ctx.Ctx); err != nil { + if err := updatePodLabelsWithConfigVersion(&pod, configKey, configVersion, rctx.Client, rctx.Ctx); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } } diff --git a/controllers/parameters/parallel_upgrade_policy_test.go b/controllers/parameters/parallel_upgrade_policy_test.go index 9cb78e3e059..ec4e04c48ee 100644 --- a/controllers/parameters/parallel_upgrade_policy_test.go +++ b/controllers/parameters/parallel_upgrade_policy_test.go @@ -27,22 +27,22 @@ import ( cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" - mock_proto "github.com/apecloud/kubeblocks/pkg/configuration/proto/mocks" + mockproto "github.com/apecloud/kubeblocks/pkg/configuration/proto/mocks" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) -var parallelPolicy = parallelUpgradePolicy{} +var parallelPolicy = restartContainerUpgradePolicy{} var _ = Describe("Reconfigure ParallelPolicy", func() { var ( k8sMockClient *testutil.K8sClientMockHelper - reconfigureClient *mock_proto.MockReconfigureClient + reconfigureClient *mockproto.MockReconfigureClient ) BeforeEach(func() { k8sMockClient = testutil.NewK8sMockClient() - reconfigureClient = mock_proto.NewMockReconfigureClient(k8sMockClient.Controller()) + reconfigureClient = mockproto.NewMockReconfigureClient(k8sMockClient.Controller()) }) AfterEach(func() { @@ -51,7 +51,7 @@ var _ = Describe("Reconfigure ParallelPolicy", func() { Context("parallel reconfigure policy test", func() { It("Should success without error", func() { - Expect(parallelPolicy.GetPolicyName()).Should(BeEquivalentTo("parallel")) + Expect(parallelPolicy.GetPolicyName()).Should(BeEquivalentTo("restartContainer")) // mock client update caller k8sMockClient.MockPatchMethod(testutil.WithSucceed(testutil.WithTimes(3))) diff --git a/controllers/parameters/paramconfigrenderer_controller.go b/controllers/parameters/paramconfigrenderer_controller.go index fb8b0dce744..597a6bc16c8 100644 --- a/controllers/parameters/paramconfigrenderer_controller.go +++ b/controllers/parameters/paramconfigrenderer_controller.go @@ -109,7 +109,7 @@ func (r *ParameterDrivenConfigRenderReconciler) validate(ctx intctrlutil.Request if err := validateParametersDefs(ctx, cli, parameterTemplate.ParametersDefs); err != nil { return err } - if err := validateParametersConfigs(parameterTemplate.Configs, intctrlutil.TransformConfigTemplate(cmpd.Spec.Configs)); err != nil { + if err := validateParametersConfigs(parameterTemplate.Configs, cmpd.Spec.Configs); err != nil { return err } return nil diff --git a/controllers/parameters/paramconfigrenderer_controller_test.go b/controllers/parameters/paramconfigrenderer_controller_test.go index 97b2016f712..57ecebade86 100644 --- a/controllers/parameters/paramconfigrenderer_controller_test.go +++ b/controllers/parameters/paramconfigrenderer_controller_test.go @@ -52,7 +52,7 @@ var _ = Describe("ParamConfigRenderer Controller", func() { By("Create a component definition obj and mock to available") compDefObj := testapps.NewComponentDefinitionFactory(compDefName). SetDefaultSpec(). - AddConfigTemplate(configSpecName, configmap.Name, "", testCtx.DefaultNamespace, configVolumeName). + AddConfigTemplate(configSpecName, configmap.Name, testCtx.DefaultNamespace, configVolumeName). Create(&testCtx). GetObject() Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { diff --git a/controllers/parameters/parameter_controller.go b/controllers/parameters/parameter_controller.go new file mode 100644 index 00000000000..95ed571788c --- /dev/null +++ b/controllers/parameters/parameter_controller.go @@ -0,0 +1,225 @@ +/* +Copyright (C) 2022-2024 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" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "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/render" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +// ParameterReconciler reconciles a Parameter object +type ParameterReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=parameters,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=parameters/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=parameters.kubeblocks.io,resources=parameters/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 *ParameterReconciler) 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("ParameterReconciler"). + WithValues("Namespace", req.Namespace, "Parameter", req.Name), + } + + parameter := ¶metersv1alpha1.Parameter{} + if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, parameter); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") + } + + res, err := intctrlutil.HandleCRDeletion(reqCtx, r, parameter, constant.ConfigFinalizerName, nil) + if res != nil { + return *res, err + } + return r.reconcile(reqCtx, parameter) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ParameterReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(¶metersv1alpha1.Parameter{}). + Complete(r) +} + +func (r *ParameterReconciler) handleComponent(rctx *ReconcileContext, updatedParameters parametersv1alpha1.ComponentParameters, parameter *parametersv1alpha1.Parameter) error { + configmaps, err := resolveComponentRefConfigMap(rctx) + if err != nil { + return err + } + + handles := []reconfigureReconcileHandle{ + prepareResources, + syncComponentParameterStatus, + classifyParameters(updatedParameters, configmaps), + updateCustomTemplates, + updateParameters, + updateComponentParameterStatus(configmaps), + } + + for _, handle := range handles { + if err := handle(rctx, parameter); err != nil { + return err + } + } + return nil +} + +func (r *ParameterReconciler) reconcile(reqCtx intctrlutil.RequestCtx, parameter *parametersv1alpha1.Parameter) (ctrl.Result, error) { + if intctrlutil.ParametersTerminalPhases(parameter.Status, parameter.Generation) { + return intctrlutil.Reconciled() + } + + if err := r.validate(parameter, reqCtx.Ctx); err != nil { + return r.fail(reqCtx, parameter, err) + } + patch := parameter.DeepCopy() + rctxs, params := r.generateParameterTaskContext(reqCtx, parameter) + for i, rctx := range rctxs { + if err := r.handleComponent(rctx, params[i], parameter); err != nil { + return r.fail(reqCtx, parameter, err) + } + } + finished := syncParameterStatus(¶meter.Status) + return updateParameterStatus(reqCtx, r.Client, parameter, patch, finished) +} + +func (r *ParameterReconciler) generateParameterTaskContext(reqCtx intctrlutil.RequestCtx, parameter *parametersv1alpha1.Parameter) ([]*ReconcileContext, []parametersv1alpha1.ComponentParameters) { + var rctxs []*ReconcileContext + var params []parametersv1alpha1.ComponentParameters + for _, component := range parameter.Spec.ComponentParameters { + params = append(params, component.Parameters) + rctxs = append(rctxs, newParameterReconcileContext(reqCtx, + &render.ResourceCtx{ + Context: reqCtx.Ctx, + Client: r.Client, + Namespace: parameter.Namespace, + ClusterName: parameter.Spec.ClusterName, + ComponentName: component.ComponentName, + }, nil, "", nil)) + } + return rctxs, params +} + +func (r *ParameterReconciler) validate(parameter *parametersv1alpha1.Parameter, ctx context.Context) error { + if len(parameter.Spec.ComponentParameters) == 0 { + return intctrlutil.NewFatalError("required component parameters") + } + + for _, component := range parameter.Spec.ComponentParameters { + if len(component.Parameters) == 0 && len(component.CustomTemplates) == 0 { + return intctrlutil.NewErrorf(intctrlutil.ErrorTypeFatal, "required parameters or custom templates for component[%s]", component.ComponentName) + } + if err := validateCustomTemplate(ctx, r.Client, component.CustomTemplates); err != nil { + return err + } + } + return nil +} + +func (r *ParameterReconciler) failWithTerminalReconcile(reqCtx intctrlutil.RequestCtx, parameter *parametersv1alpha1.Parameter, err error) (ctrl.Result, error) { + patch := parameter.DeepCopy() + parameter.Status.Phase = parametersv1alpha1.CMergeFailedPhase + parameter.Status.Message = err.Error() + return updateParameterStatus(reqCtx, r.Client, parameter, patch, true) +} + +func (r *ParameterReconciler) fail(reqCtx intctrlutil.RequestCtx, parameter *parametersv1alpha1.Parameter, err error) (ctrl.Result, error) { + if intctrlutil.IsTargetError(err, intctrlutil.ErrorTypeFatal) { + return r.failWithTerminalReconcile(reqCtx, parameter, err) + } + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") +} + +func validateCustomTemplate(ctx context.Context, cli client.Client, templates map[string]appsv1.ConfigTemplateExtension) error { + for configSpec, custom := range templates { + cm := &corev1.ConfigMap{} + err := cli.Get(ctx, types.NamespacedName{Name: custom.TemplateRef, Namespace: custom.Namespace}, cm) + if err != nil { + if apierrors.IsNotFound(err) { + return intctrlutil.NewErrorf(intctrlutil.ErrorTypeFatal, "not found configmap[%s] for custom template: %s", custom.TemplateRef, configSpec) + } + return err + } + } + return nil +} + +func updateParameterStatus(reqCtx intctrlutil.RequestCtx, cli client.Client, parameter *parametersv1alpha1.Parameter, patch *parametersv1alpha1.Parameter, finished bool) (ctrl.Result, error) { + parameter.Status.ObservedGeneration = parameter.Generation + if err := cli.Status().Patch(reqCtx.Ctx, parameter, client.MergeFrom(patch)); err != nil { + return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") + } + if finished { + return intctrlutil.Reconciled() + } + return intctrlutil.RequeueAfter(ConfigReconcileInterval, reqCtx.Log, "") +} + +func syncParameterStatus(parameterStatus *parametersv1alpha1.ParameterStatus) bool { + var finished = true + + defer func() { + if finished && !intctrlutil.IsFailedPhase(parameterStatus.Phase) { + parameterStatus.Phase = parametersv1alpha1.CFinishedPhase + } + }() + + for _, status := range parameterStatus.ReconfiguringStatus { + switch status.Phase { + case parametersv1alpha1.CMergeFailedPhase: + parameterStatus.Phase = parametersv1alpha1.CMergeFailedPhase + return true + case parametersv1alpha1.CFailedAndPausePhase: + parameterStatus.Phase = parametersv1alpha1.CFailedAndPausePhase + return true + case parametersv1alpha1.CFinishedPhase: + continue + default: + parameterStatus.Phase = parametersv1alpha1.CRunningPhase + finished = false + } + } + return finished +} diff --git a/controllers/parameters/parameter_controller_test.go b/controllers/parameters/parameter_controller_test.go new file mode 100644 index 00000000000..d2847e8e5f2 --- /dev/null +++ b/controllers/parameters/parameter_controller_test.go @@ -0,0 +1,143 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parameters + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + configcore "github.com/apecloud/kubeblocks/pkg/configuration/core" + "github.com/apecloud/kubeblocks/pkg/controller/component" + testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" +) + +var _ = Describe("Parameter Controller", func() { + + var compParamKey types.NamespacedName + var comp *component.SynthesizedComponent + + BeforeEach(cleanEnv) + + AfterEach(cleanEnv) + + prepareTestEnv := func() { + _, _, _, _, comp = mockReconcileResource() + compParamKey = types.NamespacedName{ + Namespace: testCtx.DefaultNamespace, + Name: configcore.GenerateComponentConfigurationName(comp.ClusterName, comp.Name), + } + + Eventually(testapps.CheckObj(&testCtx, compParamKey, func(g Gomega, compParameter *parametersv1alpha1.ComponentParameter) { + g.Expect(compParameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + g.Expect(compParameter.Status.ObservedGeneration).Should(BeEquivalentTo(int64(1))) + })).Should(Succeed()) + } + + Context("parameter update", func() { + It("Should reconcile success", func() { + prepareTestEnv() + + By("submit the parameter update request") + key := testapps.GetRandomizedKey(comp.Namespace, comp.FullCompName) + parameterObj := testparameters.NewParameterFactory(key.Name, key.Namespace, comp.ClusterName, comp.Name). + AddParameters("innodb-buffer-pool-size", "1024M"). + AddParameters("max_connections", "100"). + Create(&testCtx). + GetObject() + + By("check component parameter status") + Eventually(testapps.CheckObj(&testCtx, compParamKey, func(g Gomega, compParameter *parametersv1alpha1.ComponentParameter) { + g.Expect(compParameter.Status.ObservedGeneration).Should(BeEquivalentTo(int64(2))) + g.Expect(compParameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + }), time.Second*10).Should(Succeed()) + + By("check parameter status") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { + g.Expect(parameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + }) + + It("parameters validate fails", func() { + prepareTestEnv() + + By("submit the parameter update request with invalid max_connection") + key := testapps.GetRandomizedKey(comp.Namespace, comp.FullCompName) + parameterObj := testparameters.NewParameterFactory(key.Name, key.Namespace, comp.ClusterName, comp.Name). + AddParameters("max_connections", "-100"). + Create(&testCtx). + GetObject() + + By("check parameter status") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { + g.Expect(parameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CMergeFailedPhase)) + })).Should(Succeed()) + }) + }) + + Context("custom template update", func() { + It("update user template", func() { + prepareTestEnv() + + By("create custom template object") + configmap := testparameters.NewComponentTemplateFactory(configSpecName, testCtx.DefaultNamespace). + WithRandomName(). + Create(&testCtx). + GetObject() + + By("submit the custom template request") + key := testapps.GetRandomizedKey(comp.Namespace, comp.FullCompName) + parameterObj := testparameters.NewParameterFactory(key.Name, key.Namespace, comp.ClusterName, comp.Name). + AddCustomTemplate(configSpecName, configmap.Name, configmap.Namespace). + Create(&testCtx). + GetObject() + + By("check component parameter status") + Eventually(testapps.CheckObj(&testCtx, compParamKey, func(g Gomega, compParameter *parametersv1alpha1.ComponentParameter) { + g.Expect(compParameter.Status.ObservedGeneration).Should(BeEquivalentTo(int64(2))) + g.Expect(compParameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + }), time.Second*10).Should(Succeed()) + + By("check parameter status") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { + g.Expect(parameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) + })).Should(Succeed()) + }) + + It("custom template failed", func() { + prepareTestEnv() + + By("submit the custom template request") + key := testapps.GetRandomizedKey(comp.Namespace, comp.FullCompName) + parameterObj := testparameters.NewParameterFactory(key.Name, key.Namespace, comp.ClusterName, comp.Name). + AddCustomTemplate(configSpecName, "not-exist-tpl", testCtx.DefaultNamespace). + Create(&testCtx). + GetObject() + + By("check parameter status") + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(parameterObj), func(g Gomega, parameter *parametersv1alpha1.Parameter) { + g.Expect(parameter.Status.Phase).Should(BeEquivalentTo(parametersv1alpha1.CMergeFailedPhase)) + })).Should(Succeed()) + }) + }) +}) diff --git a/controllers/parameters/parameter_util.go b/controllers/parameters/parameter_util.go new file mode 100644 index 00000000000..607e33cf931 --- /dev/null +++ b/controllers/parameters/parameter_util.go @@ -0,0 +1,256 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parameters + +import ( + "fmt" + "reflect" + + "github.com/imdario/mergo" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +type reconfigureReconcileHandle func(*ReconcileContext, *parametersv1alpha1.Parameter) error + +func updateComponentParameterStatus(configmaps map[string]*corev1.ConfigMap) func(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + return func(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + status := safeResolveComponentStatus(¶meter.Status, rctx.ComponentName) + if !intctrlutil.IsParameterFinished(status.Phase) { + syncReconfiguringPhase(rctx, status, configmaps) + } + return nil + } +} + +func syncReconfiguringPhase(rctx *ReconcileContext, status *parametersv1alpha1.ComponentReconfiguringStatus, configmaps map[string]*corev1.ConfigMap) { + var finished = true + + updateStatus := func() { + if finished { + status.Phase = parametersv1alpha1.CFinishedPhase + } else { + status.Phase = parametersv1alpha1.CRunningPhase + } + } + + for _, parameterStatus := range status.ParameterStatus { + if parameterStatus.Phase == parametersv1alpha1.CMergeFailedPhase { + status.Phase = parametersv1alpha1.CMergeFailedPhase + return + } + cm := configmaps[parameterStatus.Name] + compSpec := intctrlutil.GetConfigTemplateItem(&rctx.ComponentParameterObj.Spec, parameterStatus.Name) + compStatus := intctrlutil.GetItemStatus(&rctx.ComponentParameterObj.Status, parameterStatus.Name) + if compStatus == nil || compSpec == nil || cm == nil { + rctx.Log.Info("component status or spec not found", "component", parameterStatus.Name, "template", parameterStatus.Name) + continue + } + parameterStatus.Phase = intctrlutil.GetConfigSpecReconcilePhase(cm, *compSpec, compStatus) + if finished { + finished = intctrlutil.IsParameterFinished(parameterStatus.Phase) + } + if parameterStatus.Phase == parametersv1alpha1.CFailedAndPausePhase { + status.Phase = parametersv1alpha1.CFailedAndPausePhase + return + } + } + + updateStatus() +} + +func mergeWithOverride(dst, src interface{}) error { + return mergo.Merge(dst, src, mergo.WithOverride) +} + +func updateParameters(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + var updated bool + + compStatus := intctrlutil.GetParameterStatus(¶meter.Status, rctx.ComponentName) + if compStatus == nil || intctrlutil.IsParameterFinished(compStatus.Phase) { + return nil + } + + patch := rctx.ComponentParameterObj.DeepCopy() + var item *parametersv1alpha1.ConfigTemplateItemDetail + for _, status := range compStatus.ParameterStatus { + if item = intctrlutil.GetConfigTemplateItem(&rctx.ComponentParameterObj.Spec, status.Name); item == nil { + status.Phase = parametersv1alpha1.CMergeFailedPhase + continue + } + if err := mergeWithOverride(&item.ConfigFileParams, status.UpdatedParameters); err != nil { + status.Phase = parametersv1alpha1.CMergeFailedPhase + return err + } + if status.CustomTemplate != nil { + item.CustomTemplates = status.CustomTemplate + } + updated = true + status.Phase = parametersv1alpha1.CMergedPhase + } + + if updated && !reflect.DeepEqual(patch, rctx.ComponentParameterObj) { + return rctx.Client.Patch(rctx.Ctx, rctx.ComponentParameterObj, client.MergeFrom(patch)) + } + return nil +} + +func updateCustomTemplates(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + component := intctrlutil.GetParameter(¶meter.Spec, rctx.ComponentName) + if component == nil || len(component.CustomTemplates) == 0 { + return nil + } + + for tpl, componentParameter := range component.CustomTemplates { + status := safeResolveComponentParameterStatus(¶meter.Status, component.ComponentName, tpl) + status.CustomTemplate = componentParameter.DeepCopy() + } + return nil +} + +func classifyParameters(updatedParameters parametersv1alpha1.ComponentParameters, configmaps map[string]*corev1.ConfigMap) func(*ReconcileContext, *parametersv1alpha1.Parameter) error { + return func(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + classParameters := configctrl.ClassifyComponentParameters(updatedParameters, + flatten(rctx.ParametersDefs), + rctx.ComponentDefObj.Spec.Configs, + configmaps, + ) + for tpl, m := range classParameters { + configDescs := intctrlutil.GetComponentConfigDescriptions(&rctx.ConfigRender.Spec, tpl) + if len(configDescs) == 0 { + return fmt.Errorf("not found config description from pdcr: %s", tpl) + } + if err := validateComponentParameter(toArray(rctx.ParametersDefs), configDescs, m); err != nil { + return intctrlutil.NewFatalError(err.Error()) + } + safeUpdateComponentParameterStatus(¶meter.Status, rctx.ComponentName, tpl, m) + } + return nil + } +} + +func validateComponentParameter(parametersDefs []*parametersv1alpha1.ParametersDefinition, descs []parametersv1alpha1.ComponentConfigDescription, parameters map[string]*parametersv1alpha1.ParametersInFile) error { + if len(parametersDefs) == 0 || len(descs) == 0 { + return nil + } + _, err := configctrl.DoMerge(resolveBaseData(parameters), configctrl.DerefMapValues(parameters), parametersDefs, descs) + return err +} + +func resolveBaseData(updatedParameters map[string]*parametersv1alpha1.ParametersInFile) map[string]string { + baseData := make(map[string]string) + for key := range updatedParameters { + baseData[key] = "" + } + return baseData +} + +func toArray(paramsDefs map[string]*parametersv1alpha1.ParametersDefinition) []*parametersv1alpha1.ParametersDefinition { + var defs []*parametersv1alpha1.ParametersDefinition + for _, def := range paramsDefs { + defs = append(defs, def) + } + return defs +} + +func safeResolveComponentStatus(status *parametersv1alpha1.ParameterStatus, componentName string) *parametersv1alpha1.ComponentReconfiguringStatus { + compStatus := intctrlutil.GetParameterStatus(status, componentName) + if compStatus != nil { + return compStatus + } + + status.ReconfiguringStatus = append(status.ReconfiguringStatus, + parametersv1alpha1.ComponentReconfiguringStatus{ + ComponentName: componentName, + Phase: parametersv1alpha1.CInitPhase, + }) + return intctrlutil.GetParameterStatus(status, componentName) +} + +func safeResolveComponentParameterStatus(status *parametersv1alpha1.ParameterStatus, componentName string, tpl string) *parametersv1alpha1.ReconfiguringStatus { + compStatus := safeResolveComponentStatus(status, componentName) + parameterStatus := intctrlutil.GetParameterReconfiguringStatus(compStatus, tpl) + if parameterStatus != nil { + return parameterStatus + } + + compStatus.ParameterStatus = append(compStatus.ParameterStatus, + parametersv1alpha1.ReconfiguringStatus{ + ConfigTemplateItemDetailStatus: parametersv1alpha1.ConfigTemplateItemDetailStatus{ + Name: tpl, + }, + }) + return intctrlutil.GetParameterReconfiguringStatus(compStatus, tpl) +} + +func safeUpdateComponentParameterStatus(status *parametersv1alpha1.ParameterStatus, componentName string, tpl string, updatedParams map[string]*parametersv1alpha1.ParametersInFile) { + parameterStatus := safeResolveComponentParameterStatus(status, componentName, tpl) + parameterStatus.UpdatedParameters = configctrl.DerefMapValues(updatedParams) +} + +func flatten(parametersDefs map[string]*parametersv1alpha1.ParametersDefinition) []*parametersv1alpha1.ParametersDefinition { + var defs []*parametersv1alpha1.ParametersDefinition + for _, paramsDef := range parametersDefs { + defs = append(defs, paramsDef) + } + return defs +} + +func resolveComponentRefConfigMap(rctx *ReconcileContext) (map[string]*corev1.ConfigMap, error) { + configMapList := &corev1.ConfigMapList{} + matchLabels := client.MatchingLabels(constant.GetCompLabels(rctx.ClusterName, rctx.ComponentName)) + requiredLabels := client.HasLabels(reconfigureRequiredLabels) + if err := rctx.Client.List(rctx.Ctx, configMapList, client.InNamespace(rctx.Namespace), matchLabels, requiredLabels); err != nil { + return nil, err + } + + configs := make(map[string]*corev1.ConfigMap) + for i, cm := range configMapList.Items { + configs[cm.Labels[constant.CMConfigurationSpecProviderLabelKey]] = &configMapList.Items[i] + } + return configs, nil +} + +func prepareResources(rctx *ReconcileContext, _ *parametersv1alpha1.Parameter) error { + return rctx.Cluster(). + ComponentAndComponentDef(). + SynthesizedComponent(). + ComponentParameter(). + ParametersDefinitions(). + Complete() +} + +func syncComponentParameterStatus(rctx *ReconcileContext, parameter *parametersv1alpha1.Parameter) error { + syncConfigTemplateStatus := func(status *parametersv1alpha1.ComponentReconfiguringStatus, compParamStatus *parametersv1alpha1.ComponentParameterStatus) { + for i, parameterStatus := range status.ParameterStatus { + itemStatus := intctrlutil.GetItemStatus(compParamStatus, parameterStatus.Name) + if itemStatus != nil { + status.ParameterStatus[i].ConfigTemplateItemDetailStatus = *itemStatus + } + } + } + + for i := range parameter.Status.ReconfiguringStatus { + syncConfigTemplateStatus(¶meter.Status.ReconfiguringStatus[i], &rctx.ComponentParameterObj.Status) + } + return nil +} diff --git a/controllers/parameters/policy_util.go b/controllers/parameters/policy_util.go index 4d6998da5b3..ae62a8366c3 100644 --- a/controllers/parameters/policy_util.go +++ b/controllers/parameters/policy_util.go @@ -27,9 +27,13 @@ import ( "strconv" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" + cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" "github.com/apecloud/kubeblocks/pkg/constant" @@ -42,10 +46,10 @@ import ( ) // GetComponentPods gets all pods of the component. -func GetComponentPods(params reconfigureParams) ([]corev1.Pod, error) { +func GetComponentPods(params reconfigureContext) ([]corev1.Pod, error) { componentPods := make([]corev1.Pod, 0) for i := range params.InstanceSetUnits { - pods, err := intctrlutil.GetPodListByInstanceSet(params.Ctx.Ctx, params.Client, ¶ms.InstanceSetUnits[i]) + pods, err := intctrlutil.GetPodListByInstanceSet(params.Ctx, params.Client, ¶ms.InstanceSetUnits[i]) if err != nil { return nil, err } @@ -70,7 +74,7 @@ func CheckReconfigureUpdateProgress(pods []corev1.Pod, configKey, version string return readyPods } -func getPodsForOnlineUpdate(params reconfigureParams) ([]corev1.Pod, error) { +func getPodsForOnlineUpdate(params reconfigureContext) ([]corev1.Pod, error) { if len(params.InstanceSetUnits) > 1 { return nil, core.MakeError("component require only one InstanceSet, actual %d components", len(params.InstanceSetUnits)) } @@ -91,8 +95,8 @@ func getPodsForOnlineUpdate(params reconfigureParams) ([]corev1.Pod, error) { } // TODO commonOnlineUpdateWithPod migrate to sql command pipeline -func commonOnlineUpdateWithPod(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, updatedParams map[string]string) error { - address, err := cfgManagerGrpcURL(pod) +func commonOnlineUpdateWithPod(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, configFile string, updatedParams map[string]string) error { + address, err := resolveReloadServerGrpcURL(pod) if err != nil { return err } @@ -104,6 +108,7 @@ func commonOnlineUpdateWithPod(pod *corev1.Pod, ctx context.Context, createClien response, err := client.OnlineUpgradeParams(ctx, &cfgproto.OnlineUpgradeParamsRequest{ ConfigSpec: configSpec, Params: updatedParams, + ConfigFile: pointer.String(configFile), }) if err != nil { return err @@ -126,7 +131,7 @@ func commonStopContainerWithPod(pod *corev1.Pod, ctx context.Context, containerN containerIDs = append(containerIDs, containerID) } - address, err := cfgManagerGrpcURL(pod) + address, err := resolveReloadServerGrpcURL(pod) if err != nil { return err } @@ -150,19 +155,19 @@ func commonStopContainerWithPod(pod *corev1.Pod, ctx context.Context, containerN return nil } -func cfgManagerGrpcURL(pod *corev1.Pod) (string, error) { +func resolveReloadServerGrpcURL(pod *corev1.Pod) (string, error) { podPort := viper.GetInt(constant.ConfigManagerGPRCPortEnv) if pod.Spec.HostNetwork { - containerPort, err := configuration.GetConfigManagerGRPCPort(pod.Spec.Containers) + containerPort, err := configuration.ResolveReloadServerGRPCPort(pod.Spec.Containers) if err != nil { return "", err } podPort = int(containerPort) } - return getURLFromPod(pod, podPort) + return generateGrpcURL(pod, podPort) } -func getURLFromPod(pod *corev1.Pod, portPort int) (string, error) { +func generateGrpcURL(pod *corev1.Pod, portPort int) (string, error) { ip, err := ipAddressFromPod(pod.Status) if err != nil { return "", err @@ -236,3 +241,152 @@ func restartComponent(cli client.Client, ctx intctrlutil.RequestCtx, configKey s func updatedVersion(podTemplate *corev1.PodTemplateSpec, keyPath, expectedVersion string) bool { return podTemplate.Annotations != nil && podTemplate.Annotations[keyPath] == expectedVersion } + +type ReloadAction interface { + ExecReload() (ReturnedStatus, error) + ReloadType() string +} + +type reconfigureTask struct { + parametersv1alpha1.ReloadPolicy + taskCtx reconfigureContext +} + +func (r reconfigureTask) ReloadType() string { + return string(r.ReloadPolicy) +} + +func (r reconfigureTask) ExecReload() (ReturnedStatus, error) { + if executor, ok := upgradePolicyMap[r.ReloadPolicy]; ok { + return executor.Upgrade(r.taskCtx) + } + + return ReturnedStatus{}, fmt.Errorf("not support reload action[%s]", r.ReloadPolicy) +} + +func resolveReloadActionPolicy(jsonPatch string, + format *parametersv1alpha1.FileFormatConfig, + pd *parametersv1alpha1.ParametersDefinitionSpec) (parametersv1alpha1.ReloadPolicy, error) { + var policy = parametersv1alpha1.NonePolicy + dynamicUpdate, err := core.CheckUpdateDynamicParameters(format, pd, jsonPatch) + if err != nil { + return policy, err + } + + // make decision + switch { + case !dynamicUpdate && intctrlutil.NeedDynamicReloadAction(pd): // static parameters update + policy = parametersv1alpha1.DynamicReloadAndRestartPolicy + case cfgcm.IsAutoReload(pd.ReloadAction): // if core support hot update, don't need to do anything + policy = parametersv1alpha1.AsyncDynamicReloadPolicy + case enableSyncTrigger(pd.ReloadAction): // sync config-manager exec hot update + policy = parametersv1alpha1.SyncDynamicReloadPolicy + default: // config-manager auto trigger to hot update + policy = parametersv1alpha1.AsyncDynamicReloadPolicy + } + return policy, nil +} + +// genReconfigureActionTasks generates a list of reconfiguration tasks based on the provided templateSpec, +// reconfiguration context, configuration patch, and a restart flag. +func genReconfigureActionTasks(templateSpec *appsv1.ComponentTemplateSpec, rctx *ReconcileContext, patch *core.ConfigPatchInfo, restart bool) ([]ReloadAction, error) { + var tasks []ReloadAction + + // If the patch or ConfigRender is nil, return a single restart task. + if patch == nil || rctx.ConfigRender == nil { + return []ReloadAction{buildRestartTask(templateSpec, rctx)}, nil + } + + // needReloadAction determines if a reload action is needed based on the ParametersDefinition and ReloadPolicy. + needReloadAction := func(pd *parametersv1alpha1.ParametersDefinition, policy parametersv1alpha1.ReloadPolicy) bool { + return !restart || (policy == parametersv1alpha1.SyncDynamicReloadPolicy && intctrlutil.NeedDynamicReloadAction(&pd.Spec)) + } + + for key, jsonPatch := range patch.UpdateConfig { + pd, ok := rctx.ParametersDefs[key] + // If the ParametersDefinition or its ReloadAction is nil, continue to the next iteration. + if !ok || pd.Spec.ReloadAction == nil { + continue + } + configFormat := intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, key) + if configFormat == nil || configFormat.FileFormatConfig == nil { + continue + } + // Determine the appropriate ReloadPolicy. + policy, err := resolveReloadActionPolicy(string(jsonPatch), configFormat.FileFormatConfig, &pd.Spec) + if err != nil { + return nil, err + } + // If a reload action is needed, append a new reload action task to the tasks slice. + if needReloadAction(pd, policy) { + tasks = append(tasks, buildReloadActionTask(policy, templateSpec, rctx, pd, configFormat, patch)) + } + } + + // If no tasks were added, return a single restart task. + if len(tasks) == 0 { + return []ReloadAction{buildRestartTask(templateSpec, rctx)}, nil + } + + return tasks, nil +} + +func buildReloadActionTask(reloadPolicy parametersv1alpha1.ReloadPolicy, templateSpec *appsv1.ComponentTemplateSpec, rctx *ReconcileContext, pd *parametersv1alpha1.ParametersDefinition, configDescription *parametersv1alpha1.ComponentConfigDescription, patch *core.ConfigPatchInfo) reconfigureTask { + reCtx := reconfigureContext{ + RequestCtx: rctx.RequestCtx, + Client: rctx.Client, + ConfigTemplate: *templateSpec, + ConfigMap: rctx.ConfigMap, + ParametersDef: &pd.Spec, + ConfigDescription: configDescription, + Cluster: rctx.ClusterObj, + ContainerNames: rctx.Containers, + InstanceSetUnits: rctx.InstanceSetList, + ClusterComponent: rctx.ClusterComObj, + SynthesizedComponent: rctx.BuiltinComponent, + ReconfigureClientFactory: GetClientFactory(), + } + + if reloadPolicy == parametersv1alpha1.SyncDynamicReloadPolicy { + reCtx.UpdatedParameters = generateOnlineUpdateParams(patch, &pd.Spec, *configDescription) + } + + return reconfigureTask{ReloadPolicy: reloadPolicy, taskCtx: reCtx} +} + +func buildRestartTask(configTemplate *appsv1.ComponentTemplateSpec, rctx *ReconcileContext) reconfigureTask { + return reconfigureTask{ + ReloadPolicy: parametersv1alpha1.RestartPolicy, + taskCtx: reconfigureContext{ + RequestCtx: rctx.RequestCtx, + Client: rctx.Client, + ConfigTemplate: *configTemplate, + ClusterComponent: rctx.ClusterComObj, + SynthesizedComponent: rctx.BuiltinComponent, + InstanceSetUnits: rctx.InstanceSetList, + ConfigMap: rctx.ConfigMap, + }, + } +} + +func generateOnlineUpdateParams(configPatch *core.ConfigPatchInfo, paramDef *parametersv1alpha1.ParametersDefinitionSpec, description parametersv1alpha1.ComponentConfigDescription) map[string]string { + params := make(map[string]string) + dynamicAction := intctrlutil.NeedDynamicReloadAction(paramDef) + needReloadStaticParams := intctrlutil.ReloadStaticParameters(paramDef) + visualizedParams := core.GenerateVisualizedParamsList(configPatch, []parametersv1alpha1.ComponentConfigDescription{description}) + + for _, key := range visualizedParams { + if key.UpdateType != core.UpdatedType { + continue + } + for _, p := range key.Parameters { + if dynamicAction && !needReloadStaticParams && !core.IsDynamicParameter(p.Key, paramDef) { + continue + } + if p.Value != nil { + params[p.Key] = *p.Value + } + } + } + return params +} diff --git a/controllers/parameters/policy_util_test.go b/controllers/parameters/policy_util_test.go index 3e58877b4a8..12c7ac2e518 100644 --- a/controllers/parameters/policy_util_test.go +++ b/controllers/parameters/policy_util_test.go @@ -21,6 +21,7 @@ package parameters import ( "fmt" + "time" "github.com/sethvargo/go-password/password" corev1 "k8s.io/api/core/v1" @@ -33,9 +34,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -81,10 +81,10 @@ func newMockInstanceSet(replicas int, name string, labels map[string]string) wor } } -type ParamsOps func(params *reconfigureParams) +type ParamsOps func(params *reconfigureContext) func withMockInstanceSet(replicas int, labels map[string]string) ParamsOps { - return func(params *reconfigureParams) { + return func(params *reconfigureContext) { rand, _ := password.Generate(12, 8, 0, true, false) itsName := "test_" + rand params.InstanceSetUnits = []workloads.InstanceSet{ @@ -94,7 +94,7 @@ func withMockInstanceSet(replicas int, labels map[string]string) ParamsOps { } func withClusterComponent(replicas int) ParamsOps { - return func(params *reconfigureParams) { + return func(params *reconfigureContext) { params.ClusterComponent = &appsv1.ClusterComponentSpec{ Name: "test", Replicas: func() int32 { rep := int32(replicas); return rep }(), @@ -103,59 +103,39 @@ func withClusterComponent(replicas int) ParamsOps { } func withGRPCClient(clientFactory createReconfigureClient) ParamsOps { - return func(params *reconfigureParams) { + return func(params *reconfigureContext) { params.ReconfigureClientFactory = clientFactory } } func withConfigSpec(configSpecName string, data map[string]string) ParamsOps { - return func(params *reconfigureParams) { + return func(params *reconfigureContext) { params.ConfigMap = &corev1.ConfigMap{ Data: data, } - params.ConfigSpecName = configSpecName + params.ConfigTemplate.Name = configSpecName } } -func withConfigConstraintSpec(formatter *appsv1beta1.FileFormatConfig) ParamsOps { - return func(params *reconfigureParams) { - params.ConfigConstraint = &appsv1beta1.ConfigConstraintSpec{ +func withConfigDescription(formatter *parametersv1alpha1.FileFormatConfig) ParamsOps { + return func(params *reconfigureContext) { + params.ConfigDescription = ¶metersv1alpha1.ComponentConfigDescription{ + Name: "for-test", FileFormatConfig: formatter, } } } -func withConfigPatch(patch map[string]string) ParamsOps { - mockEmptyData := func(m map[string]string) map[string]string { - r := make(map[string]string, len(patch)) - for key := range m { - r[key] = "" - } - return r - } - transKeyPair := func(pts map[string]string) map[string]interface{} { - m := make(map[string]interface{}, len(pts)) - for key, value := range pts { - m[key] = value - } - return m - } - return func(params *reconfigureParams) { - cc := params.ConfigConstraint - newConfigData, _ := intctrlutil.MergeAndValidateConfigs(*cc, map[string]string{"for_test": ""}, nil, []core.ParamPairs{{ - Key: "for_test", - UpdatedParams: transKeyPair(patch), - }}) - configPatch, _, _ := core.CreateConfigPatch(mockEmptyData(newConfigData), newConfigData, cc.FileFormatConfig.Format, nil, false) - params.ConfigPatch = configPatch +func withUpdatedParameters(patch map[string]string) ParamsOps { + return func(params *reconfigureContext) { + params.UpdatedParameters = patch } } -func newMockReconfigureParams(testName string, cli client.Client, paramOps ...ParamsOps) reconfigureParams { - params := reconfigureParams{ - Restart: true, - Client: cli, - Ctx: intctrlutil.RequestCtx{ +func newMockReconfigureParams(testName string, cli client.Client, paramOps ...ParamsOps) reconfigureContext { + params := reconfigureContext{ + Client: cli, + RequestCtx: intctrlutil.RequestCtx{ Ctx: ctx, Log: log.FromContext(ctx).WithValues("policy_test", testName), Recorder: record.NewFakeRecorder(100), @@ -181,6 +161,7 @@ func newMockReconfigureParams(testName string, cli client.Client, paramOps ...Pa ObjectMeta: metav1.ObjectMeta{ Name: "test", }}, + ParametersDef: ¶metersv1alpha1.ParametersDefinitionSpec{}, } for _, customFn := range paramOps { customFn(¶ms) @@ -223,6 +204,26 @@ func withReadyPod(rMin, rMax int) PodOptions { } } +func withAvailablePod(rMin, rMax int) PodOptions { + return func(pod *corev1.Pod, index int) { + if index < rMin || index >= rMax { + return + } + + if pod.Status.Conditions == nil { + pod.Status.Conditions = make([]corev1.PodCondition, 0) + } + + h, _ := time.ParseDuration("-1h") + pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{ + Type: corev1.PodReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Now().Add(h)), + }) + pod.Status.Phase = corev1.PodRunning + } +} + func fromPodObjectList(pods []corev1.Pod) []runtime.Object { objs := make([]runtime.Object, len(pods)) for i := 0; i < len(pods); i++ { diff --git a/controllers/parameters/reconcile_task.go b/controllers/parameters/reconcile_task.go index d42e57c64d6..2b352c15197 100644 --- a/controllers/parameters/reconcile_task.go +++ b/controllers/parameters/reconcile_task.go @@ -24,13 +24,11 @@ import ( "strconv" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/render" @@ -40,44 +38,87 @@ import ( type Task struct { configctrl.ResourceFetcher[Task] - Status *appsv1alpha1.ConfigurationItemDetailStatus + Status *parametersv1alpha1.ConfigTemplateItemDetailStatus Name string - Do func(fetcher *Task, component *component.SynthesizedComponent, revision string) error + Do func(resource *Task, taskCtx *TaskContext, revision string) error } type TaskContext struct { - configuration *appsv1alpha1.Configuration - ctx context.Context - fetcher *Task + componentParameter *parametersv1alpha1.ComponentParameter + configRender *parametersv1alpha1.ParameterDrivenConfigRender + ctx context.Context + component *component.SynthesizedComponent + paramsDefs []*parametersv1alpha1.ParametersDefinition } -func NewTask(item appsv1alpha1.ConfigurationItemDetail, status *appsv1alpha1.ConfigurationItemDetailStatus) Task { +func NewTaskContext(ctx context.Context, cli client.Client, componentParameter *parametersv1alpha1.ComponentParameter, fetchTask *Task) (*TaskContext, error) { + // build synthesized component for the component + cmpd := fetchTask.ComponentDefObj + synthesizedComp, err := component.BuildSynthesizedComponent(ctx, cli, cmpd, fetchTask.ComponentObj, fetchTask.ClusterObj) + if err == nil { + err = buildTemplateVars(ctx, cli, fetchTask.ComponentDefObj, synthesizedComp) + } + if err != nil { + return nil, err + } + + configDefList := ¶metersv1alpha1.ParameterDrivenConfigRenderList{} + if err := cli.List(ctx, configDefList); err != nil { + return nil, err + } + + var paramsDefs []*parametersv1alpha1.ParametersDefinition + var configRender *parametersv1alpha1.ParameterDrivenConfigRender + for i, item := range configDefList.Items { + if item.Spec.ComponentDef != cmpd.Name { + 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, + component: synthesizedComp, + paramsDefs: paramsDefs, + }, nil +} + +func NewTask(item parametersv1alpha1.ConfigTemplateItemDetail, status *parametersv1alpha1.ConfigTemplateItemDetailStatus) Task { return Task{ Name: item.Name, - Do: func(fetcher *Task, synComponent *component.SynthesizedComponent, revision string) error { + Do: func(resource *Task, taskCtx *TaskContext, revision string) error { configSpec := item.ConfigSpec if configSpec == nil { return core.MakeError("not found config spec: %s", item.Name) } - if err := fetcher.ConfigMap(item.Name).Complete(); err != nil { - if !apierrors.IsNotFound(err) { - return err - } - if item.ConfigSpec.InjectEnvEnabled() && item.ConfigSpec.ToSecret() { - return syncSecretStatus(status) - } - return err + if err := resource.ConfigMap(item.Name).Complete(); err != nil { + return syncImpl(taskCtx, resource, item, status, revision, nil) } // Do reconcile for config template - configMap := fetcher.ConfigMapObj + configMap := resource.ConfigMapObj switch intctrlutil.GetConfigSpecReconcilePhase(configMap, item, status) { default: return syncStatus(configMap, status) - case appsv1alpha1.CPendingPhase, - appsv1alpha1.CMergeFailedPhase: - return syncImpl(fetcher, item, status, synComponent, revision, builder.ToV1ConfigSpec(configSpec)) - case appsv1alpha1.CCreatingPhase: + case parametersv1alpha1.CInitPhase, + parametersv1alpha1.CPendingPhase, + parametersv1alpha1.CMergeFailedPhase: + return syncImpl(taskCtx, resource, item, status, revision, configMap) + case parametersv1alpha1.CCreatingPhase: return nil } }, @@ -85,29 +126,20 @@ func NewTask(item appsv1alpha1.ConfigurationItemDetail, status *appsv1alpha1.Con } } -func syncSecretStatus(status *appsv1alpha1.ConfigurationItemDetailStatus) error { - status.Phase = appsv1alpha1.CFinishedPhase - if status.LastDoneRevision == "" { - status.LastDoneRevision = status.UpdateRevision - } - return nil -} - -func syncImpl(fetcher *Task, - item appsv1alpha1.ConfigurationItemDetail, - status *appsv1alpha1.ConfigurationItemDetailStatus, - synthesizedComponent *component.SynthesizedComponent, +func syncImpl(taskCtx *TaskContext, + fetcher *Task, + item parametersv1alpha1.ConfigTemplateItemDetail, + status *parametersv1alpha1.ConfigTemplateItemDetailStatus, revision string, - configSpec *appsv1.ComponentConfigSpec) (err error) { + configMap *corev1.ConfigMap) (err error) { err = configctrl.NewReconcilePipeline(render.ReconcileCtx{ - ResourceCtx: fetcher.ResourceCtx, + ResourceCtx: fromResourceCtx(fetcher.ResourceCtx), Cluster: fetcher.ClusterObj, Component: fetcher.ComponentObj, - SynthesizedComponent: synthesizedComponent, - PodSpec: synthesizedComponent.PodSpec, - }, item, status, configSpec). - ConfigMap(item.Name). - ConfigConstraints(configSpec.ConfigConstraintRef). + SynthesizedComponent: taskCtx.component, + PodSpec: taskCtx.component.PodSpec, + }, item, status, configMap, taskCtx.componentParameter). + ComponentAndComponentDef(). PrepareForTemplate(). RerenderTemplate(). ApplyParameters(). @@ -117,16 +149,16 @@ func syncImpl(fetcher *Task, if err != nil { status.Message = cfgutil.ToPointer(err.Error()) - status.Phase = appsv1alpha1.CMergeFailedPhase + status.Phase = parametersv1alpha1.CMergeFailedPhase } else { status.Message = nil - status.Phase = appsv1alpha1.CMergedPhase + status.Phase = parametersv1alpha1.CMergedPhase } status.UpdateRevision = revision return err } -func syncStatus(configMap *corev1.ConfigMap, status *appsv1alpha1.ConfigurationItemDetailStatus) (err error) { +func syncStatus(configMap *corev1.ConfigMap, status *parametersv1alpha1.ConfigTemplateItemDetailStatus) (err error) { annotations := configMap.GetAnnotations() // status.CurrentRevision = GetCurrentRevision(annotations) revisions := RetrieveRevision(annotations) @@ -142,16 +174,16 @@ func syncStatus(configMap *corev1.ConfigMap, status *appsv1alpha1.ConfigurationI return } -func updateLastDoneRevision(revision ConfigurationRevision, status *appsv1alpha1.ConfigurationItemDetailStatus) { - if revision.Phase == appsv1alpha1.CFinishedPhase { +func updateLastDoneRevision(revision ConfigurationRevision, status *parametersv1alpha1.ConfigTemplateItemDetailStatus) { + if revision.Phase == parametersv1alpha1.CFinishedPhase { status.LastDoneRevision = strconv.FormatInt(revision.Revision, 10) } } -func updateRevision(revision ConfigurationRevision, status *appsv1alpha1.ConfigurationItemDetailStatus) { +func updateRevision(revision ConfigurationRevision, status *parametersv1alpha1.ConfigTemplateItemDetailStatus) { if revision.StrRevision == status.UpdateRevision { status.Phase = revision.Phase - status.ReconcileDetail = &appsv1alpha1.ReconcileDetail{ + status.ReconcileDetail = ¶metersv1alpha1.ReconcileDetail{ CurrentRevision: revision.StrRevision, Policy: revision.Result.Policy, SucceedCount: revision.Result.SucceedCount, @@ -161,3 +193,29 @@ func updateRevision(revision ConfigurationRevision, status *appsv1alpha1.Configu } } } + +func prepareReconcileTask(reqCtx intctrlutil.RequestCtx, cli client.Client, componentParameter *parametersv1alpha1.ComponentParameter) (*Task, error) { + fetcherTask := &Task{} + err := fetcherTask.Init(&render.ResourceCtx{ + Context: reqCtx.Ctx, + Client: cli, + Namespace: componentParameter.Namespace, + ClusterName: componentParameter.Spec.ClusterName, + ComponentName: componentParameter.Spec.ComponentName, + }, fetcherTask).Cluster(). + ComponentAndComponentDef(). + ComponentSpec(). + Complete() + fetcherTask.ComponentParameterObj = componentParameter + return fetcherTask, err +} + +func fromResourceCtx(ctx *render.ResourceCtx) *render.ResourceCtx { + return &render.ResourceCtx{ + Context: ctx.Context, + Client: ctx.Client, + Namespace: ctx.Namespace, + ClusterName: ctx.ClusterName, + ComponentName: ctx.ComponentName, + } +} diff --git a/controllers/parameters/reconfigure_controller.go b/controllers/parameters/reconfigure_controller.go index a787920e966..a420bdf842f 100644 --- a/controllers/parameters/reconfigure_controller.go +++ b/controllers/parameters/reconfigure_controller.go @@ -39,7 +39,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/multicluster" @@ -106,15 +106,13 @@ func (r *ReconfigureReconciler) Reconcile(ctx context.Context, req ctrl.Request) reqCtx.Log = reqCtx.Log. WithValues("ClusterName", config.Labels[constant.AppInstanceLabelKey]). WithValues("ComponentName", config.Labels[constant.KBAppComponentLabelKey]) - if hash, ok := config.Labels[constant.CMInsConfigurationHashLabelKey]; ok && hash == config.ResourceVersion { - return intctrlutil.Reconciled() - } isAppliedConfigs, err := checkAndApplyConfigsChanged(r.Client, reqCtx, config) if err != nil { return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to check last-applied-configuration") - } else if isAppliedConfigs { - return updateConfigPhase(r.Client, reqCtx, config, appsv1alpha1.CFinishedPhase, configurationNoChangedMessage) + } + if isAppliedConfigs { + return updateConfigPhase(r.Client, reqCtx, config, parametersv1alpha1.CFinishedPhase, configurationNoChangedMessage) } resources, err := prepareRelatedResource(reqCtx, r.Client, config) @@ -128,7 +126,7 @@ func (r *ReconfigureReconciler) Reconcile(ctx context.Context, req ctrl.Request) corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureFailed, configurationNotRelatedComponentMessage) - return updateConfigPhase(r.Client, reqCtx, config, appsv1alpha1.CFinishedPhase, configurationNotRelatedComponentMessage) + return updateConfigPhase(r.Client, reqCtx, config, parametersv1alpha1.CFinishedPhase, configurationNotRelatedComponentMessage) } return r.sync(reqCtx, config, resources) @@ -164,27 +162,7 @@ func checkConfigurationObject(object client.Object) bool { } func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *corev1.ConfigMap, resources *reconfigureRelatedResource) (ctrl.Result, error) { - configPatch, forceRestart, err := createConfigPatch(configMap, resources.configConstraintObj.Spec.FileFormatConfig, resources.configSpec.Keys) - if err != nil { - return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) - } - - // No parameters updated - if configPatch != nil && !configPatch.IsModify { - reqCtx.Recorder.Event(configMap, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureRunning, "nothing changed, skip reconfigure") - return r.updateConfigCMStatus(reqCtx, configMap, core.ReconfigureNoChangeType, nil) - } - - if configPatch != nil { - reqCtx.Log.V(1).Info(fmt.Sprintf( - "reconfigure params: \n\tadd: %s\n\tdelete: %s\n\tupdate: %s", - configPatch.AddConfig, - configPatch.DeleteConfig, - configPatch.UpdateConfig)) - } - - reconcileContext := newConfigReconcileContext( - reqCtx.Ctx, + rctx := newParameterReconcileContext(reqCtx, &render.ResourceCtx{ Context: reqCtx.Ctx, Client: r.Client, @@ -195,37 +173,46 @@ func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *c configMap, resources.configSpec.Name, resources.componentMatchLabels()) - if err := reconcileContext.GetRelatedObjects(); err != nil { + if err := rctx.GetRelatedObjects(); err != nil { return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) } // Assumption: It is required that the cluster must have a component. - if reconcileContext.ClusterComObj == nil { + if rctx.ClusterComObj == nil { reqCtx.Log.Info("not found component.") return intctrlutil.Reconciled() } - if len(reconcileContext.InstanceSetList) == 0 { + if len(rctx.InstanceSetList) == 0 { reqCtx.Recorder.Event(configMap, corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureFailed, "the configmap is not used by any container, skip reconfigure") - return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, configurationNotUsingMessage) + return updateConfigPhase(r.Client, reqCtx, configMap, parametersv1alpha1.CFinishedPhase, configurationNotUsingMessage) + } + + configPatch, forceRestart, err := createConfigPatch(configMap, rctx.ConfigRender, rctx.ParametersDefs) + if err != nil { + return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) } - return r.performUpgrade(reconfigureParams{ - ConfigSpecName: resources.configSpec.Name, - ConfigPatch: configPatch, - ConfigMap: configMap, - ConfigConstraint: &resources.configConstraintObj.Spec, - Client: r.Client, - Ctx: reqCtx, - Cluster: reconcileContext.ClusterObj, - ContainerNames: reconcileContext.Containers, - InstanceSetUnits: reconcileContext.InstanceSetList, - ClusterComponent: reconcileContext.ClusterComObj, - SynthesizedComponent: reconcileContext.BuiltinComponent, - Restart: forceRestart || !cfgcm.IsSupportReload(resources.configConstraintObj.Spec.ReloadAction), - ReconfigureClientFactory: GetClientFactory(), - }) + // No parameters updated + if configPatch != nil && !configPatch.IsModify { + reqCtx.Recorder.Event(configMap, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureRunning, "nothing changed, skip reconfigure") + return r.updateConfigCMStatus(reqCtx, configMap, core.ReconfigureNoChangeType, nil) + } + + if configPatch != nil { + reqCtx.Log.V(1).Info(fmt.Sprintf( + "reconfigure params: \n\tadd: %s\n\tdelete: %s\n\tupdate: %s", + configPatch.AddConfig, + configPatch.DeleteConfig, + configPatch.UpdateConfig)) + } + + tasks, err := genReconfigureActionTasks(resources.configSpec, rctx, configPatch, forceRestart) + if err != nil { + return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) + } + return r.performUpgrade(rctx, tasks) } func (r *ReconfigureReconciler) updateConfigCMStatus(reqCtx intctrlutil.RequestCtx, cfg *corev1.ConfigMap, reconfigureType string, result *intctrlutil.Result) (ctrl.Result, error) { @@ -241,65 +228,47 @@ func (r *ReconfigureReconciler) updateConfigCMStatus(reqCtx intctrlutil.RequestC return intctrlutil.Reconciled() } -func (r *ReconfigureReconciler) performUpgrade(params reconfigureParams) (ctrl.Result, error) { - policy, err := NewReconfigurePolicy(params.ConfigConstraint, params.ConfigPatch, getUpgradePolicy(params.ConfigMap), params.Restart) - if err != nil { - return intctrlutil.RequeueWithErrorAndRecordEvent(params.ConfigMap, r.Recorder, err, params.Ctx.Log) +func (r *ReconfigureReconciler) performUpgrade(rctx *ReconcileContext, reloadTasks []ReloadAction) (ctrl.Result, error) { + var err error + var returnedStatus ReturnedStatus + var reloadType string + + for _, task := range reloadTasks { + reloadType = task.ReloadType() + returnedStatus, err = task.ExecReload() + if err != nil || returnedStatus.Status != ESNone { + return r.status(rctx, returnedStatus, reloadType, err) + } } + return r.succeed(rctx, reloadType, returnedStatus) +} - returnedStatus, err := policy.Upgrade(params) - if err != nil { - params.Ctx.Log.Error(err, "failed to update engine parameters") +func (r *ReconfigureReconciler) status(rctx *ReconcileContext, returnedStatus ReturnedStatus, policy string, err error) (ctrl.Result, error) { + updatePhase := func(phase parametersv1alpha1.ParameterPhase, options ...options) (ctrl.Result, error) { + return updateConfigPhaseWithResult(rctx.Client, rctx.RequestCtx, rctx.ConfigMap, reconciled(returnedStatus, policy, phase, options...)) } switch returnedStatus.Status { - default: - return updateConfigPhaseWithResult( - params.Client, - params.Ctx, - params.ConfigMap, - reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase, - withFailed(core.MakeError("unknown status"), false)), - ) case ESFailedAndRetry: - return updateConfigPhaseWithResult( - params.Client, - params.Ctx, - params.ConfigMap, - reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedPhase, - withFailed(err, true)), - ) + return updatePhase(parametersv1alpha1.CFailedPhase, withFailed(err, true)) case ESRetry: - return updateConfigPhaseWithResult( - params.Client, - params.Ctx, - params.ConfigMap, - reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CUpgradingPhase), - ) + return updatePhase(parametersv1alpha1.CUpgradingPhase) case ESFailed: - return updateConfigPhaseWithResult( - params.Client, - params.Ctx, - params.ConfigMap, - reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase, - withFailed(err, false)), - ) + return updatePhase(parametersv1alpha1.CFailedAndPausePhase, withFailed(err, false)) case ESNone: - params.Ctx.Recorder.Eventf( - params.ConfigMap, - corev1.EventTypeNormal, - appsv1alpha1.ReasonReconfigureSucceed, - "the reconfigure[%s] request[%s] has been processed successfully", - policy.GetPolicyName(), - getOpsRequestID(params.ConfigMap)) - result := reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFinishedPhase) - return r.updateConfigCMStatus(params.Ctx, params.ConfigMap, policy.GetPolicyName(), &result) + return r.succeed(rctx, policy, returnedStatus) + default: + return updatePhase(parametersv1alpha1.CFailedAndPausePhase, withFailed(core.MakeError("unknown status"), false)) } } -func getOpsRequestID(cm *corev1.ConfigMap) string { - if len(cm.Annotations) != 0 { - return cm.Annotations[constant.LastAppliedOpsCRAnnotationKey] - } - return "" +func (r *ReconfigureReconciler) succeed(rctx *ReconcileContext, reloadType string, returnedStatus ReturnedStatus) (ctrl.Result, error) { + rctx.Recorder.Eventf(rctx.ConfigMap, + corev1.EventTypeNormal, + appsv1alpha1.ReasonReconfigureSucceed, + "the reconfigure[%s] has been processed successfully", + reloadType) + + result := reconciled(returnedStatus, reloadType, parametersv1alpha1.CFinishedPhase) + return r.updateConfigCMStatus(rctx.RequestCtx, rctx.ConfigMap, reloadType, &result) } diff --git a/controllers/parameters/reconfigure_policy.go b/controllers/parameters/reconfigure_policy.go index d7d6753a977..03dce1747bf 100644 --- a/controllers/parameters/reconfigure_policy.go +++ b/controllers/parameters/reconfigure_policy.go @@ -29,10 +29,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" - configmanager "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" "github.com/apecloud/kubeblocks/pkg/configuration/util" @@ -67,7 +65,7 @@ type ReturnedStatus struct { type reconfigurePolicy interface { // Upgrade is to enable the configuration to take effect. - Upgrade(params reconfigureParams) (ReturnedStatus, error) + Upgrade(rctx reconfigureContext) (ReturnedStatus, error) // GetPolicyName returns name of policy. GetPolicyName() string @@ -75,21 +73,27 @@ type reconfigurePolicy interface { type AutoReloadPolicy struct{} -type reconfigureParams struct { - // Only supports restart pod or container. - Restart bool +type reconfigureContext struct { + intctrlutil.RequestCtx + Client client.Client + + Cluster *appsv1.Cluster + ConfigTemplate appsv1.ComponentTemplateSpec + + // Associated component for cluster. + ClusterComponent *appsv1.ClusterComponentSpec - // Name is a config template name. - ConfigSpecName string + // Associated component for component and component definition. + SynthesizedComponent *component.SynthesizedComponent - // Configuration files patch. - ConfigPatch *core.ConfigPatchInfo + // List of InstanceSet using this config template. + InstanceSetUnits []workloads.InstanceSet // Configmap object of the configuration template instance in the component. ConfigMap *corev1.ConfigMap // ConfigConstraint pointer - ConfigConstraint *appsv1beta1.ConfigConstraintSpec + // ConfigConstraint *appsv1beta1.ConfigConstraintSpec // For grpc factory ReconfigureClientFactory createReconfigureClient @@ -97,19 +101,9 @@ type reconfigureParams struct { // List of containers using this config volume. ContainerNames []string - Client client.Client - Ctx intctrlutil.RequestCtx - - Cluster *appsv1.Cluster - - // Associated component for cluster. - ClusterComponent *appsv1.ClusterComponentSpec - - // Associated component for component and component definition. - SynthesizedComponent *component.SynthesizedComponent - - // List of InstanceSet using this config template. - InstanceSetUnits []workloads.InstanceSet + ConfigDescription *parametersv1alpha1.ComponentConfigDescription + ParametersDef *parametersv1alpha1.ParametersDefinitionSpec + UpdatedParameters map[string]string } var ( @@ -124,10 +118,10 @@ var ( } ) -var upgradePolicyMap = map[appsv1alpha1.UpgradePolicy]reconfigurePolicy{} +var upgradePolicyMap = map[parametersv1alpha1.ReloadPolicy]reconfigurePolicy{} func init() { - RegisterPolicy(appsv1alpha1.AsyncDynamicReloadPolicy, &AutoReloadPolicy{}) + registerPolicy(parametersv1alpha1.AsyncDynamicReloadPolicy, &AutoReloadPolicy{}) } // GetClientFactory support ut mock @@ -135,21 +129,26 @@ func GetClientFactory() createReconfigureClient { return newGRPCClient } -func (param *reconfigureParams) getConfigKey() string { - return param.ConfigSpecName +func (param *reconfigureContext) getConfigKey() string { + key := param.ConfigTemplate.Name + if param.ConfigDescription != nil && param.ConfigDescription.Name != "" { + hash, _ := util.ComputeHash(param.ConfigDescription.Name) + key = key + "/" + hash + } + return key } -func (param *reconfigureParams) getTargetVersionHash() string { +func (param *reconfigureContext) getTargetVersionHash() string { hash, err := util.ComputeHash(param.ConfigMap.Data) if err != nil { - param.Ctx.Log.Error(err, "failed to get configuration version!") + param.Log.Error(err, "failed to get configuration version!") return "" } return hash } -func (param *reconfigureParams) maxRollingReplicas() int32 { +func (param *reconfigureContext) maxRollingReplicas() int32 { var ( defaultRolling int32 = 1 r int32 @@ -177,7 +176,7 @@ func (param *reconfigureParams) maxRollingReplicas() int32 { // TODO(xingran&zhangtao): review this logic, set to nil in new API version v, isPercentage, err := intctrlutil.GetIntOrPercentValue(maxUnavailable) if err != nil { - param.Ctx.Log.Error(err, "failed to get maxUnavailable!") + param.Log.Error(err, "failed to get maxUnavailable!") return defaultRolling } @@ -189,72 +188,29 @@ func (param *reconfigureParams) maxRollingReplicas() int32 { return max(r, defaultRolling) } -func (param *reconfigureParams) getTargetReplicas() int { +func (param *reconfigureContext) getTargetReplicas() int { return int(param.ClusterComponent.Replicas) } -func (param *reconfigureParams) podMinReadySeconds() int32 { +func (param *reconfigureContext) podMinReadySeconds() int32 { minReadySeconds := param.SynthesizedComponent.MinReadySeconds return max(minReadySeconds, viper.GetInt32(constant.PodMinReadySecondsEnv)) } -func RegisterPolicy(policy appsv1alpha1.UpgradePolicy, action reconfigurePolicy) { +func registerPolicy(policy parametersv1alpha1.ReloadPolicy, action reconfigurePolicy) { upgradePolicyMap[policy] = action } -func (receiver AutoReloadPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { +func (receiver AutoReloadPolicy) Upgrade(params reconfigureContext) (ReturnedStatus, error) { _ = params return makeReturnedStatus(ESNone), nil } func (receiver AutoReloadPolicy) GetPolicyName() string { - return string(appsv1alpha1.AsyncDynamicReloadPolicy) -} - -func NewReconfigurePolicy(cc *appsv1beta1.ConfigConstraintSpec, cfgPatch *core.ConfigPatchInfo, policy appsv1alpha1.UpgradePolicy, restart bool) (reconfigurePolicy, error) { - if cfgPatch != nil && !cfgPatch.IsModify { - // not walk here - return nil, core.MakeError("cfg not modify. [%v]", cfgPatch) - } - - // if not specify policy, auto decision reconfiguring policy. - if enableAutoDecision(restart, policy) { - dynamicUpdate, err := core.IsUpdateDynamicParameters(cc, cfgPatch) - if err != nil { - return nil, err - } - - // make decision - switch { - case !dynamicUpdate: // static parameters update - case configmanager.IsAutoReload(cc.ReloadAction): // if core support hot update, don't need to do anything - policy = appsv1alpha1.AsyncDynamicReloadPolicy - case enableSyncTrigger(cc.ReloadAction): // sync config-manager exec hot update - policy = appsv1alpha1.SyncDynamicReloadPolicy - default: // config-manager auto trigger to hot update - policy = appsv1alpha1.AsyncDynamicReloadPolicy - } - } - - // if not specify policy, or cannot decision policy, use default policy. - if policy == appsv1alpha1.NonePolicy { - policy = appsv1alpha1.NormalPolicy - if cc.NeedDynamicReloadAction() && enableSyncTrigger(cc.ReloadAction) { - policy = appsv1alpha1.DynamicReloadAndRestartPolicy - } - } - - if action, ok := upgradePolicyMap[policy]; ok { - return action, nil - } - return nil, core.MakeError("not supported upgrade policy:[%s]", policy) -} - -func enableAutoDecision(restart bool, policy appsv1alpha1.UpgradePolicy) bool { - return !restart && policy == appsv1alpha1.NonePolicy + return string(parametersv1alpha1.AsyncDynamicReloadPolicy) } -func enableSyncTrigger(reloadAction *appsv1beta1.ReloadAction) bool { +func enableSyncTrigger(reloadAction *parametersv1alpha1.ReloadAction) bool { if reloadAction == nil { return false } @@ -293,7 +249,7 @@ func makeReturnedStatus(status ExecStatus, ops ...func(status *ReturnedStatus)) return ret } -func fromWorkloadObjects(params reconfigureParams) []client.Object { +func fromWorkloadObjects(params reconfigureContext) []client.Object { r := make([]client.Object, 0) for _, unit := range params.InstanceSetUnits { r = append(r, &unit) diff --git a/controllers/parameters/relatedresource.go b/controllers/parameters/relatedresource.go index c84a8079943..ca1f8f0ac7e 100644 --- a/controllers/parameters/relatedresource.go +++ b/controllers/parameters/relatedresource.go @@ -26,8 +26,7 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/render" @@ -37,23 +36,21 @@ import ( type reconfigureRelatedResource struct { ctx context.Context client client.Client - configSpec *appsv1alpha1.ComponentConfigSpec + configSpec *appsv1.ComponentTemplateSpec clusterName string componentName string - configMapObj *corev1.ConfigMap - configConstraintObj *appsv1beta1.ConfigConstraint + configMapObj *corev1.ConfigMap } func prepareRelatedResource(reqCtx intctrlutil.RequestCtx, client client.Client, cm *corev1.ConfigMap) (*reconfigureRelatedResource, error) { configResources := reconfigureRelatedResource{ - configConstraintObj: &appsv1beta1.ConfigConstraint{}, - configMapObj: cm, - ctx: reqCtx.Ctx, - client: client, - clusterName: cm.Labels[constant.AppInstanceLabelKey], - componentName: cm.Labels[constant.KBAppComponentLabelKey], + configMapObj: cm, + ctx: reqCtx.Ctx, + client: client, + clusterName: cm.Labels[constant.AppInstanceLabelKey], + componentName: cm.Labels[constant.KBAppComponentLabelKey], } fetcher := configctrl.NewResourceFetcher(&render.ResourceCtx{ @@ -63,35 +60,30 @@ func prepareRelatedResource(reqCtx intctrlutil.RequestCtx, client client.Client, ClusterName: configResources.clusterName, ComponentName: configResources.componentName, }) - if fetcher.Configuration(); fetcher.Err != nil { + if fetcher.ComponentParameter(); fetcher.Err != nil { return nil, fetcher.Err } - if fetcher.ConfigurationObj == nil { + if fetcher.ComponentParameterObj == nil { return nil, fmt.Errorf("not found configuration object for configmap: %s", cm.Name) } - if err := prepareCC(&configResources, fetcher, cm); err != nil { + if err := resolveComponentTemplateSpec(&configResources, fetcher, cm); err != nil { return nil, fetcher.Err } return &configResources, nil } -func prepareCC(resources *reconfigureRelatedResource, fetcher *configctrl.Fetcher, cm *corev1.ConfigMap) error { +func resolveComponentTemplateSpec(resources *reconfigureRelatedResource, fetcher *configctrl.Fetcher, cm *corev1.ConfigMap) error { configSpecName, ok := cm.Labels[constant.CMConfigurationSpecProviderLabelKey] if !ok { return nil } - configSpec := fetcher.ConfigurationObj.Spec.GetConfigSpec(configSpecName) + configSpec := intctrlutil.GetConfigTemplateItem(&fetcher.ComponentParameterObj.Spec, configSpecName) if configSpec == nil { - return fmt.Errorf("not found config spec: %s in configuration[%s]", configSpecName, fetcher.ConfigurationObj.Name) + return fmt.Errorf("not found config spec: %s in configuration[%s]", configSpecName, fetcher.ComponentParameterObj.Name) } - if configSpec.ConfigConstraintRef == "" { - return nil - } - fetcher.ConfigConstraints(configSpec.ConfigConstraintRef) - resources.configSpec = configSpec - resources.configConstraintObj = fetcher.ConfigConstraintObj - return fetcher.Err + resources.configSpec = configSpec.ConfigSpec + return nil } func (r *reconfigureRelatedResource) componentMatchLabels() map[string]string { diff --git a/controllers/parameters/simple_policy.go b/controllers/parameters/restart_policy.go similarity index 59% rename from controllers/parameters/simple_policy.go rename to controllers/parameters/restart_policy.go index a2756618446..9a0cc04e102 100644 --- a/controllers/parameters/simple_policy.go +++ b/controllers/parameters/restart_policy.go @@ -24,48 +24,50 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" ) -type simplePolicy struct { -} +var restartPolicyInstance = &restartPolicy{} + +type restartPolicy struct{} func init() { - RegisterPolicy(appsv1alpha1.NormalPolicy, &simplePolicy{}) + registerPolicy(parametersv1alpha1.RestartPolicy, restartPolicyInstance) } -func (s *simplePolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { - params.Ctx.Log.V(1).Info("simple policy begin....") +func (s *restartPolicy) Upgrade(rctx reconfigureContext) (ReturnedStatus, error) { + rctx.Log.V(1).Info("simple policy begin....") - return restartAndCheckComponent(params, GetInstanceSetRollingUpgradeFuncs(), fromWorkloadObjects(params)) + return restartAndVerifyComponent(rctx, GetInstanceSetRollingUpgradeFuncs(), fromWorkloadObjects(rctx)) } -func (s *simplePolicy) GetPolicyName() string { - return string(appsv1alpha1.NormalPolicy) +func (s *restartPolicy) GetPolicyName() string { + return string(parametersv1alpha1.RestartPolicy) } -func restartAndCheckComponent(param reconfigureParams, funcs RollingUpgradeFuncs, objs []client.Object) (ReturnedStatus, error) { +func restartAndVerifyComponent(rctx reconfigureContext, funcs RollingUpgradeFuncs, objs []client.Object) (ReturnedStatus, error) { var ( - newVersion = param.getTargetVersionHash() - configKey = param.getConfigKey() + newVersion = rctx.getTargetVersionHash() + configKey = rctx.getConfigKey() retStatus = ESRetry progress = core.NotStarted ) recordEvent := func(obj client.Object) { - param.Ctx.Recorder.Eventf(obj, + rctx.Recorder.Eventf(obj, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureRestart, - "restarting component[%s] in cluster[%s], version: %s", param.ClusterComponent.Name, param.Cluster.Name, newVersion) + "restarting component[%s] in cluster[%s], version: %s", rctx.ClusterComponent.Name, rctx.Cluster.Name, newVersion) } - if obj, err := funcs.RestartComponent(param.Client, param.Ctx, configKey, newVersion, objs, recordEvent); err != nil { - param.Ctx.Recorder.Eventf(obj, + if obj, err := funcs.RestartComponent(rctx.Client, rctx.RequestCtx, configKey, newVersion, objs, recordEvent); err != nil { + rctx.Recorder.Eventf(obj, corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureRestartFailed, - "failed to restart component[%s] in cluster[%s], version: %s", client.ObjectKeyFromObject(obj), param.Cluster.Name, newVersion) + "failed to restart component[%s] in cluster[%s], version: %s", client.ObjectKeyFromObject(obj), rctx.Cluster.Name, newVersion) return makeReturnedStatus(ESFailedAndRetry), err } - pods, err := funcs.GetPodsFunc(param) + pods, err := funcs.GetPodsFunc(rctx) if err != nil { return makeReturnedStatus(ESFailedAndRetry), err } diff --git a/controllers/parameters/simple_policy_test.go b/controllers/parameters/restart_policy_test.go similarity index 92% rename from controllers/parameters/simple_policy_test.go rename to controllers/parameters/restart_policy_test.go index 7a8d0bab096..44216a753c6 100644 --- a/controllers/parameters/simple_policy_test.go +++ b/controllers/parameters/restart_policy_test.go @@ -26,17 +26,17 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) -var _ = Describe("Reconfigure simplePolicy", func() { +var _ = Describe("Reconfigure restartPolicy", func() { var ( k8sMockClient *testutil.K8sClientMockHelper - simplePolicy = upgradePolicyMap[appsv1alpha1.NormalPolicy] + simplePolicy = upgradePolicyMap[parametersv1alpha1.RestartPolicy] ) BeforeEach(func() { @@ -56,9 +56,9 @@ var _ = Describe("Reconfigure simplePolicy", func() { Context("simple reconfigure policy test", func() { It("Should success without error", func() { - Expect(simplePolicy.GetPolicyName()).Should(BeEquivalentTo("simple")) + Expect(simplePolicy.GetPolicyName()).Should(BeEquivalentTo("restart")) - mockParam := newMockReconfigureParams("simplePolicy", k8sMockClient.Client(), + mockParam := newMockReconfigureParams("restartPolicy", k8sMockClient.Client(), withMockInstanceSet(2, nil), withConfigSpec("for_test", map[string]string{ "key": "value", @@ -116,7 +116,7 @@ var _ = Describe("Reconfigure simplePolicy", func() { Context("simple reconfigure policy test with Replication", func() { It("Should success", func() { - mockParam := newMockReconfigureParams("simplePolicy", k8sMockClient.Client(), + mockParam := newMockReconfigureParams("restartPolicy", k8sMockClient.Client(), withMockInstanceSet(2, nil), withConfigSpec("for_test", map[string]string{ "key": "value", @@ -149,7 +149,7 @@ var _ = Describe("Reconfigure simplePolicy", func() { Context("simple reconfigure policy test for not supported component", func() { It("Should failed", func() { // not support type - mockParam := newMockReconfigureParams("simplePolicy", k8sMockClient.Client(), + mockParam := newMockReconfigureParams("restartPolicy", k8sMockClient.Client(), withMockInstanceSet(2, nil), withConfigSpec("for_test", map[string]string{ "key": "value", @@ -208,7 +208,7 @@ var _ = Describe("Reconfigure simplePolicy", func() { // Context("simple reconfigure policy test without not configmap volume", func() { // It("Should failed", func() { // // mock not cc - // mockParam := newMockReconfigureParams("simplePolicy", nil, + // mockParam := newMockReconfigureParams("restartPolicy", nil, // withMockInstanceSet(2, nil), // withConfigSpec("not_tpl_name", map[string]string{ // "key": "value", @@ -218,7 +218,7 @@ var _ = Describe("Reconfigure simplePolicy", func() { // Name: "for_test", // VolumeName: "test_volume", // }}})) - // status, err := simplePolicy.Upgrade(mockParam) + // status, err := restartPolicy.Upgrade(mockParam) // Expect(err).ShouldNot(Succeed()) // Expect(err.Error()).Should(ContainSubstring("failed to find config meta")) // Expect(status.Status).Should(BeEquivalentTo(ESFailed)) diff --git a/controllers/parameters/revision.go b/controllers/parameters/revision.go index 40ca05591ea..a20730fa06e 100644 --- a/controllers/parameters/revision.go +++ b/controllers/parameters/revision.go @@ -27,7 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" @@ -36,7 +36,7 @@ import ( type ConfigurationRevision struct { Revision int64 StrRevision string - Phase appsv1alpha1.ConfigurationPhase + Phase parametersv1alpha1.ParameterPhase Result intctrlutil.Result } @@ -115,7 +115,7 @@ func parseResult(data string, revision string) intctrlutil.Result { } err := json.Unmarshal([]byte(data), &result) if err != nil { - result.Phase = appsv1alpha1.ConfigurationPhase(data) + result.Phase = parametersv1alpha1.ParameterPhase(data) } return result } diff --git a/controllers/parameters/revision_test.go b/controllers/parameters/revision_test.go index 76720065655..820cddc60ae 100644 --- a/controllers/parameters/revision_test.go +++ b/controllers/parameters/revision_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" @@ -95,9 +96,9 @@ func TestParseRevision(t *testing.T) { want: ConfigurationRevision{ StrRevision: "120000", Revision: 120000, - Phase: appsv1alpha1.CPendingPhase, + Phase: parametersv1alpha1.CPendingPhase, Result: intctrlutil.Result{ - Phase: appsv1alpha1.CPendingPhase, + Phase: parametersv1alpha1.CPendingPhase, Revision: "120000", }, }, @@ -179,9 +180,9 @@ func TestGetLastRevision(t *testing.T) { want: ConfigurationRevision{ Revision: 2, StrRevision: "2", - Phase: appsv1alpha1.CRunningPhase, + Phase: parametersv1alpha1.CRunningPhase, Result: intctrlutil.Result{ - Phase: appsv1alpha1.CRunningPhase, + Phase: parametersv1alpha1.CRunningPhase, Revision: "2", }, }, diff --git a/controllers/parameters/rolling_upgrade_policy.go b/controllers/parameters/rolling_upgrade_policy.go index 0626a1ac79d..f72ec7a2a84 100644 --- a/controllers/parameters/rolling_upgrade_policy.go +++ b/controllers/parameters/rolling_upgrade_policy.go @@ -21,103 +21,93 @@ package parameters import ( "context" - "os" + "log" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" podutil "github.com/apecloud/kubeblocks/pkg/controllerutil" viper "github.com/apecloud/kubeblocks/pkg/viperx" ) const ( - // StatefulSetSpec.Spec.MinReadySeconds - // units: s defaultMinReadySeconds = 10 ) -type rollingUpgradePolicy struct { -} +var rollingUpgradePolicyInstance = &rollingUpgradePolicy{} + +type rollingUpgradePolicy struct{} func init() { - RegisterPolicy(appsv1alpha1.RollingPolicy, &rollingUpgradePolicy{}) + registerPolicy(parametersv1alpha1.RollingPolicy, rollingUpgradePolicyInstance) if err := viper.BindEnv(constant.PodMinReadySecondsEnv); err != nil { - os.Exit(-1) + log.Fatalf("failed to bind environment variable: %v", err) } viper.SetDefault(constant.PodMinReadySecondsEnv, defaultMinReadySeconds) } -func (r *rollingUpgradePolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { - return performRollingUpgrade(params, GetInstanceSetRollingUpgradeFuncs()) +// Upgrade performs a rolling upgrade based on the provided parameters. +func (r *rollingUpgradePolicy) Upgrade(rctx reconfigureContext) (ReturnedStatus, error) { + return performRollingUpgrade(rctx, GetInstanceSetRollingUpgradeFuncs()) } func (r *rollingUpgradePolicy) GetPolicyName() string { - return string(appsv1alpha1.RollingPolicy) + return string(parametersv1alpha1.RollingPolicy) } -func canPerformUpgrade(pods []corev1.Pod, params reconfigureParams) bool { - target := params.getTargetReplicas() - // TODO(xingran&zhangtao): review this logic - return len(pods) == target - - /* - if len(pods) < target { - params.Ctx.Log.Info(fmt.Sprintf("component pods are not all ready, %d pods are ready, which is less than the expected replicas(%d).", len(pods), target)) - return false - } - return true - */ +func canPerformUpgrade(pods []corev1.Pod, params reconfigureContext) bool { + return len(pods) == params.getTargetReplicas() } -func performRollingUpgrade(params reconfigureParams, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { - pods, err := funcs.GetPodsFunc(params) +func performRollingUpgrade(rctx reconfigureContext, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { + pods, err := funcs.GetPodsFunc(rctx) if err != nil { return makeReturnedStatus(ESFailedAndRetry), err } var ( - rollingReplicas = params.maxRollingReplicas() - configKey = params.getConfigKey() - configVersion = params.getTargetVersionHash() + rollingReplicas = rctx.maxRollingReplicas() + configKey = rctx.getConfigKey() + configVersion = rctx.getTargetVersionHash() ) - if !canPerformUpgrade(pods, params) { + if !canPerformUpgrade(pods, rctx) { return makeReturnedStatus(ESRetry), nil } - podStats := staticPodStats(pods, params.getTargetReplicas(), params.podMinReadySeconds()) - podWins := markDynamicCursor(pods, podStats, configKey, configVersion, rollingReplicas) - if !validPodState(podWins) { - params.Ctx.Log.Info("wait for pod stat ready.") + podStatus := classifyPodByStats(pods, rctx.getTargetReplicas(), rctx.podMinReadySeconds()) + updateWindow := markDynamicSwitchWindow(pods, podStatus, configKey, configVersion, rollingReplicas) + if !canSafeUpdatePods(updateWindow) { + rctx.Log.Info("wait for pod stat ready.") return makeReturnedStatus(ESRetry), nil } - waitRollingPods := podWins.getWaitRollingPods() - if len(waitRollingPods) == 0 { - return makeReturnedStatus(ESNone, withSucceed(int32(podStats.targetReplica)), withExpected(int32(podStats.targetReplica))), nil + podsToUpgrade := updateWindow.getPendingUpgradePods() + if len(podsToUpgrade) == 0 { + return makeReturnedStatus(ESNone, withSucceed(int32(podStatus.targetReplica)), withExpected(int32(podStatus.targetReplica))), nil } - for _, pod := range waitRollingPods { - if podStats.isUpdating(&pod) { - params.Ctx.Log.Info("pod is in rolling update.", "pod name", pod.Name) + for _, pod := range podsToUpgrade { + if podStatus.isUpdating(&pod) { + rctx.Log.Info("pod is in rolling update.", "pod name", pod.Name) continue } - if err := funcs.RestartContainerFunc(&pod, params.Ctx.Ctx, params.ContainerNames, params.ReconfigureClientFactory); err != nil { + if err := funcs.RestartContainerFunc(&pod, rctx.Ctx, rctx.ContainerNames, rctx.ReconfigureClientFactory); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } - if err := updatePodLabelsWithConfigVersion(&pod, configKey, configVersion, params.Client, params.Ctx.Ctx); err != nil { + if err := updatePodLabelsWithConfigVersion(&pod, configKey, configVersion, rctx.Client, rctx.Ctx); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } } return makeReturnedStatus(ESRetry, - withExpected(int32(podStats.targetReplica)), - withSucceed(int32(len(podStats.updated)+len(podStats.updating)))), nil + withExpected(int32(podStatus.targetReplica)), + withSucceed(int32(len(podStatus.updated)+len(podStatus.updating)))), nil } -func validPodState(wind switchWindow) bool { +func canSafeUpdatePods(wind switchWindow) bool { for i := 0; i < wind.begin; i++ { pod := &wind.pods[i] if !wind.isReady(pod) { @@ -127,7 +117,7 @@ func validPodState(wind switchWindow) bool { return true } -func markDynamicCursor(pods []corev1.Pod, podsStats *componentPodStats, configKey, currentVersion string, rollingReplicas int32) switchWindow { +func markDynamicSwitchWindow(pods []corev1.Pod, podsStats *componentPodStats, configKey, currentVersion string, rollingReplicas int32) switchWindow { podWindows := switchWindow{ end: 0, begin: len(pods), @@ -135,7 +125,6 @@ func markDynamicCursor(pods []corev1.Pod, podsStats *componentPodStats, configKe componentPodStats: podsStats, } - // find update last for i := podsStats.targetReplica - 1; i >= 0; i-- { pod := &pods[i] if !podutil.IsMatchConfigVersion(pod, configKey, currentVersion) { @@ -160,7 +149,7 @@ func markDynamicCursor(pods []corev1.Pod, podsStats *componentPodStats, configKe return podWindows } -func staticPodStats(pods []corev1.Pod, targetReplicas int, minReadySeconds int32) *componentPodStats { +func classifyPodByStats(pods []corev1.Pod, targetReplicas int, minReadySeconds int32) *componentPodStats { podsStats := &componentPodStats{ updated: make(map[string]*corev1.Pod), updating: make(map[string]*corev1.Pod), @@ -183,15 +172,10 @@ func staticPodStats(pods []corev1.Pod, targetReplicas int, minReadySeconds int32 } type componentPodStats struct { - // failed to start pod - ready map[string]*corev1.Pod - available map[string]*corev1.Pod - - // updated pod count - updated map[string]*corev1.Pod - updating map[string]*corev1.Pod - - // expected pod + ready map[string]*corev1.Pod + available map[string]*corev1.Pod + updated map[string]*corev1.Pod + updating map[string]*corev1.Pod targetReplica int } @@ -213,12 +197,11 @@ func (s *componentPodStats) isUpdating(pod *corev1.Pod) bool { type switchWindow struct { begin int end int - - pods []corev1.Pod + pods []corev1.Pod *componentPodStats } -func (w *switchWindow) getWaitRollingPods() []corev1.Pod { +func (w *switchWindow) getPendingUpgradePods() []corev1.Pod { return w.pods[w.begin:w.end] } diff --git a/controllers/parameters/rolling_upgrade_policy_test.go b/controllers/parameters/rolling_upgrade_policy_test.go new file mode 100644 index 00000000000..2c614a0103e --- /dev/null +++ b/controllers/parameters/rolling_upgrade_policy_test.go @@ -0,0 +1,169 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package parameters + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/golang/mock/gomock" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" + mockproto "github.com/apecloud/kubeblocks/pkg/configuration/proto/mocks" + "github.com/apecloud/kubeblocks/pkg/constant" + testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" +) + +var _ = Describe("Reconfigure RollingPolicy", func() { + + var ( + k8sMockClient *testutil.K8sClientMockHelper + mockParam reconfigureContext + reconfigureClient *mockproto.MockReconfigureClient + + defaultReplica = 3 + rollingPolicy = upgradePolicyMap[parametersv1alpha1.RollingPolicy] + ) + + updateLabelPatch := func(pods []corev1.Pod, patch *corev1.Pod) { + patchKey := client.ObjectKeyFromObject(patch) + for i := range pods { + orgPod := &pods[i] + if client.ObjectKeyFromObject(orgPod) == patchKey { + orgPod.Labels = patch.Labels + break + } + } + } + + createReconfigureParam := func(replicas int) reconfigureContext { + return newMockReconfigureParams("rollingPolicy", k8sMockClient.Client(), + withMockInstanceSet(replicas, nil), + withConfigSpec("for_test", map[string]string{ + "key": "value", + }), + withConfigDescription(¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}), + withGRPCClient(func(addr string) (cfgproto.ReconfigureClient, error) { + return reconfigureClient, nil + }), + withClusterComponent(replicas), + // withCDComponent(compType, []appsv1alpha1.ComponentConfigSpec{{ + // ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ + // Name: "for_test", + // VolumeName: "test_volume", + // }}}) + ) + } + + BeforeEach(func() { + k8sMockClient = testutil.NewK8sMockClient() + reconfigureClient = mockproto.NewMockReconfigureClient(k8sMockClient.Controller()) + mockParam = createReconfigureParam(defaultReplica) + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + k8sMockClient.Finish() + }) + + // TODO(component) + Context("consensus rolling reconfigure policy test", func() { + It("Should success without error", func() { + Expect(rollingPolicy.GetPolicyName()).Should(BeEquivalentTo("rolling")) + + mockLeaderLabel := func(pod *corev1.Pod, i int) { + if pod.Labels == nil { + pod.Labels = make(map[string]string, 1) + } + if i == 1 { + pod.Labels[constant.RoleLabelKey] = "leader" + } else { + pod.Labels[constant.RoleLabelKey] = "follower" + } + } + + acc := 0 + mockPods := [][]corev1.Pod{ + newMockPodsWithInstanceSet(&mockParam.InstanceSetUnits[0], 2), + newMockPodsWithInstanceSet(&mockParam.InstanceSetUnits[0], 5, + mockLeaderLabel), + newMockPodsWithInstanceSet(&mockParam.InstanceSetUnits[0], 3, + withReadyPod(0, 0), + withAvailablePod(0, 3), + mockLeaderLabel), + } + + k8sMockClient.MockListMethod(testutil.WithListReturned( + testutil.WithConstructListSequenceResult([][]runtime.Object{ + fromPodObjectList(mockPods[0]), + fromPodObjectList(mockPods[1]), + fromPodObjectList(mockPods[2]), + }, func(sequence int, r []runtime.Object) { acc = sequence }), testutil.WithAnyTimes())) + + k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { + pod, _ := obj.(*corev1.Pod) + // mock patch + updateLabelPatch(mockPods[acc], pod) + return nil + }, testutil.WithAnyTimes())) + + reconfigureClient.EXPECT().StopContainer(gomock.Any(), gomock.Any()). + Return(&cfgproto.StopContainerResponse{}, nil). + AnyTimes() + + // mock wait the number of pods to target replicas + status, err := rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESRetry)) + + // mock wait the number of pods to ready status + status, err = rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESRetry)) + + // upgrade pod-0 + status, err = rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESRetry)) + Expect(mockPods[acc][2].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) + Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) + Expect(mockPods[acc][0].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) + + // upgrade pod-2 + status, err = rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESRetry)) + Expect(mockPods[acc][0].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) + Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).ShouldNot(BeEquivalentTo(mockParam.getTargetVersionHash())) + + // upgrade pod-1 + status, err = rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESRetry)) + Expect(mockPods[acc][1].Labels[mockParam.getConfigKey()]).Should(BeEquivalentTo(mockParam.getTargetVersionHash())) + + // finish check, not upgrade + status, err = rollingPolicy.Upgrade(mockParam) + Expect(err).Should(Succeed()) + Expect(status.Status).Should(BeEquivalentTo(ESNone)) + }) + }) +}) diff --git a/controllers/parameters/suite_test.go b/controllers/parameters/suite_test.go index f5bc59397a4..e0a00261af4 100644 --- a/controllers/parameters/suite_test.go +++ b/controllers/parameters/suite_test.go @@ -29,25 +29,30 @@ import ( "github.com/go-logr/logr" "go.uber.org/zap/zapcore" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/server" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" + "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/testutil" viper "github.com/apecloud/kubeblocks/pkg/viperx" ) +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + var cfg *rest.Config var k8sClient client.Client var testEnv *envtest.Environment @@ -55,10 +60,10 @@ var ctx context.Context var cancel context.CancelFunc var testCtx testutil.TestContext -func TestReconfigure(t *testing.T) { +func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Configuration Suite") + RunSpecs(t, "Controller Suite") } var _ = BeforeSuite(func() { @@ -84,6 +89,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + err = parametersv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) err = appsv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) err = appsv1beta1.AddToScheme(scheme.Scheme) @@ -92,8 +99,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = workloadsv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - err = parametersv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) + model.AddScheme(parametersv1alpha1.AddToScheme) // +kubebuilder:scaffold:scheme @@ -107,30 +113,23 @@ var _ = BeforeSuite(func() { Metrics: server.Options{BindAddress: "0"}, Client: client.Options{ Cache: &client.CacheOptions{ - DisableFor: intctrlutil.GetUncachedObjects(), + DisableFor: append(intctrlutil.GetUncachedObjects(), ¶metersv1alpha1.ComponentParameter{}), }, }, }) Expect(err).ToNot(HaveOccurred()) - err = (&ReconfigureReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("reconfigure-controller"), - }).SetupWithManager(k8sManager, nil) - Expect(err).ToNot(HaveOccurred()) - - err = (&ConfigConstraintReconciler{ + err = (&ParameterReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-cc-controller"), + Recorder: k8sManager.GetEventRecorderFor("parameter-controller"), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - err = (&ConfigurationReconciler{ + err = (&ComponentParameterReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), + Recorder: k8sManager.GetEventRecorderFor("component-parameter-controller"), }).SetupWithManager(k8sManager, nil) Expect(err).ToNot(HaveOccurred()) @@ -148,6 +147,13 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&ReconfigureReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("reconfigure-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + testCtx = testutil.NewDefaultTestContext(ctx, k8sClient, testEnv) go func() { @@ -155,12 +161,12 @@ var _ = BeforeSuite(func() { err = k8sManager.Start(ctx) Expect(err).ToNot(HaveOccurred(), "failed to run manager") }() + }) var _ = AfterSuite(func() { cancel() By("tearing down the test environment") - err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) diff --git a/controllers/parameters/sync_upgrade_policy.go b/controllers/parameters/sync_upgrade_policy.go index a8c8b1ff8a3..d04ad83bf4b 100644 --- a/controllers/parameters/sync_upgrade_policy.go +++ b/controllers/parameters/sync_upgrade_policy.go @@ -26,40 +26,35 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" - podutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) -type syncPolicy struct { -} +var syncPolicyInstance = &syncPolicy{} + +type syncPolicy struct{} func init() { - RegisterPolicy(appsv1alpha1.SyncDynamicReloadPolicy, &syncPolicy{}) + registerPolicy(parametersv1alpha1.SyncDynamicReloadPolicy, syncPolicyInstance) } func (o *syncPolicy) GetPolicyName() string { - return string(appsv1alpha1.SyncDynamicReloadPolicy) + return string(parametersv1alpha1.SyncDynamicReloadPolicy) } -func (o *syncPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { - configPatch := params.ConfigPatch - if !configPatch.IsModify { - return makeReturnedStatus(ESNone), nil - } - - updatedParameters := getOnlineUpdateParams(configPatch, params.ConfigConstraint) +func (o *syncPolicy) Upgrade(rctx reconfigureContext) (ReturnedStatus, error) { + updatedParameters := rctx.UpdatedParameters if len(updatedParameters) == 0 { return makeReturnedStatus(ESNone), nil } funcs := GetInstanceSetRollingUpgradeFuncs() - pods, err := funcs.GetPodsFunc(params) + pods, err := funcs.GetPodsFunc(rctx) if err != nil { return makeReturnedStatus(ESFailedAndRetry), err } - return sync(params, updatedParameters, pods, funcs) + return sync(rctx, updatedParameters, pods, funcs) } func matchLabel(pods []corev1.Pod, selector *metav1.LabelSelector) ([]corev1.Pod, error) { @@ -77,18 +72,19 @@ func matchLabel(pods []corev1.Pod, selector *metav1.LabelSelector) ([]corev1.Pod return result, nil } -func sync(params reconfigureParams, updatedParameters map[string]string, pods []corev1.Pod, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { +func sync(rctx reconfigureContext, updatedParameters map[string]string, pods []corev1.Pod, funcs RollingUpgradeFuncs) (ReturnedStatus, error) { var ( r = ESNone total = int32(len(pods)) - replicas = int32(params.getTargetReplicas()) + replicas = int32(rctx.getTargetReplicas()) progress = core.NotStarted err error - ctx = params.Ctx.Ctx - configKey = params.getConfigKey() - versionHash = params.getTargetVersionHash() - selector = params.ConfigConstraint.GetPodSelector() + ctx = rctx.Ctx + configKey = rctx.getConfigKey() + versionHash = rctx.getTargetVersionHash() + selector = intctrlutil.GetPodSelector(rctx.ParametersDef) + fileName string ) if selector != nil { @@ -98,26 +94,27 @@ func sync(params reconfigureParams, updatedParameters map[string]string, pods [] return makeReturnedStatus(ESFailedAndRetry), err } if len(pods) == 0 { - params.Ctx.Log.Info(fmt.Sprintf("no pods to update, and retry, selector: %v", selector)) + rctx.Log.Info(fmt.Sprintf("no pods to update, and retry, selector: %v", selector)) return makeReturnedStatus(ESRetry), nil } + if rctx.ConfigDescription != nil { + fileName = rctx.ConfigDescription.Name + } requireUpdatedCount := int32(len(pods)) for _, pod := range pods { - params.Ctx.Log.V(1).Info(fmt.Sprintf("sync pod: %s", pod.Name)) - if podutil.IsMatchConfigVersion(&pod, configKey, versionHash) { + rctx.Log.V(1).Info(fmt.Sprintf("sync pod: %s", pod.Name)) + if intctrlutil.IsMatchConfigVersion(&pod, configKey, versionHash) { progress++ continue } - if !podutil.PodIsReady(&pod) { + if !intctrlutil.PodIsReady(&pod) { continue } - err = funcs.OnlineUpdatePodFunc(&pod, ctx, params.ReconfigureClientFactory, params.ConfigSpecName, updatedParameters) - if err != nil { + if err = funcs.OnlineUpdatePodFunc(&pod, ctx, rctx.ReconfigureClientFactory, rctx.ConfigTemplate.Name, fileName, updatedParameters); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } - err = updatePodLabelsWithConfigVersion(&pod, configKey, versionHash, params.Client, ctx) - if err != nil { + if err = updatePodLabelsWithConfigVersion(&pod, configKey, versionHash, rctx.Client, ctx); err != nil { return makeReturnedStatus(ESFailedAndRetry), err } progress++ @@ -128,23 +125,3 @@ func sync(params reconfigureParams, updatedParameters map[string]string, pods [] } return makeReturnedStatus(r, withExpected(requireUpdatedCount), withSucceed(progress)), nil } - -func getOnlineUpdateParams(configPatch *core.ConfigPatchInfo, cc *appsv1beta1.ConfigConstraintSpec) map[string]string { - r := make(map[string]string) - dynamicAction := cc.NeedDynamicReloadAction() - needReloadStaticParameters := cc.ReloadStaticParameters() - parameters := core.GenerateVisualizedParamsList(configPatch, cc.FileFormatConfig, nil) - for _, key := range parameters { - if key.UpdateType == core.UpdatedType { - for _, p := range key.Parameters { - if dynamicAction && !needReloadStaticParameters && !core.IsDynamicParameter(p.Key, cc) { - continue - } - if p.Value != nil { - r[p.Key] = *p.Value - } - } - } - } - return r -} diff --git a/controllers/parameters/sync_upgrade_policy_test.go b/controllers/parameters/sync_upgrade_policy_test.go index 5ef13709de4..7723ff9d40f 100644 --- a/controllers/parameters/sync_upgrade_policy_test.go +++ b/controllers/parameters/sync_upgrade_policy_test.go @@ -28,9 +28,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" - mock_proto "github.com/apecloud/kubeblocks/pkg/configuration/proto/mocks" + mockproto "github.com/apecloud/kubeblocks/pkg/configuration/proto/mocks" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -40,12 +40,12 @@ var _ = Describe("Reconfigure OperatorSyncPolicy", func() { var ( k8sMockClient *testutil.K8sClientMockHelper - reconfigureClient *mock_proto.MockReconfigureClient + reconfigureClient *mockproto.MockReconfigureClient ) BeforeEach(func() { k8sMockClient = testutil.NewK8sMockClient() - reconfigureClient = mock_proto.NewMockReconfigureClient(k8sMockClient.Controller()) + reconfigureClient = mockproto.NewMockReconfigureClient(k8sMockClient.Controller()) }) AfterEach(func() { @@ -55,7 +55,7 @@ var _ = Describe("Reconfigure OperatorSyncPolicy", func() { Context("sync reconfigure policy test", func() { It("Should success without error", func() { By("check policy name") - Expect(operatorSyncPolicy.GetPolicyName()).Should(BeEquivalentTo("operatorSyncUpdate")) + Expect(operatorSyncPolicy.GetPolicyName()).Should(BeEquivalentTo("syncReload")) By("prepare reconfigure policy params") mockParam := newMockReconfigureParams("operatorSyncPolicy", k8sMockClient.Client(), @@ -64,8 +64,8 @@ var _ = Describe("Reconfigure OperatorSyncPolicy", func() { }), withMockInstanceSet(3, nil), withConfigSpec("for_test", map[string]string{"a": "c b e f"}), - withConfigConstraintSpec(&appsv1beta1.FileFormatConfig{Format: appsv1beta1.RedisCfg}), - withConfigPatch(map[string]string{ + withConfigDescription(¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.RedisCfg}), + withUpdatedParameters(map[string]string{ "a": "c b e f", }), withClusterComponent(3)) @@ -106,7 +106,7 @@ var _ = Describe("Reconfigure OperatorSyncPolicy", func() { Context("sync reconfigure policy with selector test", func() { It("Should success without error", func() { By("check policy name") - Expect(operatorSyncPolicy.GetPolicyName()).Should(BeEquivalentTo("operatorSyncUpdate")) + Expect(operatorSyncPolicy.GetPolicyName()).Should(BeEquivalentTo("syncReload")) By("prepare reconfigure policy params") mockParam := newMockReconfigureParams("operatorSyncPolicy", k8sMockClient.Client(), @@ -115,17 +115,19 @@ var _ = Describe("Reconfigure OperatorSyncPolicy", func() { }), withMockInstanceSet(3, nil), withConfigSpec("for_test", map[string]string{"a": "c b e f"}), - withConfigConstraintSpec(&appsv1beta1.FileFormatConfig{Format: appsv1beta1.RedisCfg}), - withConfigPatch(map[string]string{ + withConfigDescription(¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.RedisCfg}), + withUpdatedParameters(map[string]string{ "a": "c b e f", }), withClusterComponent(3)) // add selector - mockParam.ConfigConstraint.ReloadAction = &appsv1beta1.ReloadAction{ - TargetPodSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "primary": "true", + mockParam.ParametersDef = ¶metersv1alpha1.ParametersDefinitionSpec{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + TargetPodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "primary": "true", + }, }, }, } diff --git a/controllers/parameters/types.go b/controllers/parameters/types.go index d25047a9494..4d3f73fe6a4 100644 --- a/controllers/parameters/types.go +++ b/controllers/parameters/types.go @@ -31,11 +31,11 @@ import ( type createReconfigureClient func(addr string) (cfgproto.ReconfigureClient, error) -type GetPodsFunc func(params reconfigureParams) ([]corev1.Pod, error) +type GetPodsFunc func(params reconfigureContext) ([]corev1.Pod, error) type RestartComponent func(client client.Client, ctx intctrlutil.RequestCtx, key string, version string, objs []client.Object, recordEvent func(obj client.Object)) (client.Object, error) type RestartContainerFunc func(pod *corev1.Pod, ctx context.Context, containerName []string, createConnFn createReconfigureClient) error -type OnlineUpdatePodFunc func(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, updatedParams map[string]string) error +type OnlineUpdatePodFunc func(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, configFile string, updatedParams map[string]string) error // Node: Distinguish between implementation and interface. diff --git a/controllers/trace/reconciler_tree.go b/controllers/trace/reconciler_tree.go index 8a8d54a230d..a8fcbe1fb1f 100644 --- a/controllers/trace/reconciler_tree.go +++ b/controllers/trace/reconciler_tree.go @@ -178,7 +178,7 @@ func newComponentReconciler(cli client.Client, recorder record.EventRecorder) re } func newConfigurationReconciler(cli client.Client, recorder record.EventRecorder) reconcile.Reconciler { - return ¶meters.ConfigurationReconciler{ + return ¶meters.ComponentParameterReconciler{ Client: cli, Scheme: cli.Scheme(), Recorder: recorder, diff --git a/deploy/helm/config/rbac/role.yaml b/deploy/helm/config/rbac/role.yaml index 9ef7735f3b9..5a4f001216d 100644 --- a/deploy/helm/config/rbac/role.yaml +++ b/deploy/helm/config/rbac/role.yaml @@ -175,58 +175,6 @@ rules: - get - patch - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints/finalizers - verbs: - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configconstraints/status - verbs: - - get - - patch - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations/finalizers - verbs: - - update -- apiGroups: - - apps.kubeblocks.io - resources: - - configurations/status - verbs: - - get - - patch - - update - apiGroups: - apps.kubeblocks.io resources: @@ -863,6 +811,32 @@ rules: - get - patch - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters/finalizers + verbs: + - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - componentparameters/status + verbs: + - get + - patch + - update - apiGroups: - parameters.kubeblocks.io resources: @@ -889,6 +863,32 @@ rules: - get - patch - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters/finalizers + verbs: + - update +- apiGroups: + - parameters.kubeblocks.io + resources: + - parameters/status + verbs: + - get + - patch + - update - apiGroups: - parameters.kubeblocks.io resources: diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..a4a5b67da5c 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -4233,36 +4233,6 @@ spec: This field is immutable. items: properties: - asEnvFrom: - description: |- - Specifies the containers to inject the ConfigMap parameters as environment variables. - - - This is useful when application images accept parameters through environment variables and - generate the final configuration file in the startup script based on these variables. - - - This field allows users to specify a list of container names, and KubeBlocks will inject the environment - variables converted from the ConfigMap into these designated containers. This provides a flexible way to - pass the configuration items from the ConfigMap to the container without modifying the image. - - - Deprecated: `asEnvFrom` has been deprecated since 0.9.0 and will be removed in 0.10.0. - Use `injectEnvTo` instead. - items: - type: string - type: array - x-kubernetes-list-type: set - asSecret: - description: Whether to store the final rendered parameters - as a secret. - type: boolean - constraintRef: - description: Specifies the name of the referenced configuration - constraints object. - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ - type: string defaultMode: description: |- The operator attempts to set default file permissions for scripts (0555) and configurations (0444). @@ -4283,40 +4253,6 @@ spec: Refers to documents of k8s.ConfigMapVolumeSource.defaultMode for more information. format: int32 type: integer - injectEnvTo: - description: |- - Specifies the containers to inject the ConfigMap parameters as environment variables. - - - This is useful when application images accept parameters through environment variables and - generate the final configuration file in the startup script based on these variables. - - - This field allows users to specify a list of container names, and KubeBlocks will inject the environment - variables converted from the ConfigMap into these designated containers. This provides a flexible way to - pass the configuration items from the ConfigMap to the container without modifying the image. - items: - type: string - type: array - x-kubernetes-list-type: set - keys: - description: |- - Specifies the configuration files within the ConfigMap that support dynamic updates. - - - A configuration template (provided in the form of a ConfigMap) may contain templates for multiple - configuration files. - Each configuration file corresponds to a key in the ConfigMap. - Some of these configuration files may support dynamic modification and reloading without requiring - a pod restart. - - - If empty or omitted, all configuration files in the ConfigMap are assumed to support dynamic updates, - and ConfigConstraint applies to all keys. - items: - type: string - type: array - x-kubernetes-list-type: set name: description: Specifies the name of the configuration template. maxLength: 63 @@ -4330,28 +4266,6 @@ spec: maxLength: 63 pattern: ^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$ type: string - reRenderResourceTypes: - description: |- - Specifies whether the configuration needs to be re-rendered after v-scale or h-scale operations to reflect changes. - - - In some scenarios, the configuration may need to be updated to reflect the changes in resource allocation - or cluster topology. Examples: - - - - Redis: adjust maxmemory after v-scale operation. - - 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. - enum: - - vscale - - hscale - - tls - type: string - type: array - x-kubernetes-list-type: set templateRef: description: Specifies the name of the referenced configuration template ConfigMap object. diff --git a/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml b/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml index 4dd1102ce19..09366c612c2 100755 --- a/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml +++ b/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml @@ -4187,89 +4187,28 @@ spec: componentName: description: Specifies the name of the Component. type: string - configurations: + parameters: description: |- - Contains a list of ConfigurationItem objects, specifying the Component's configuration template name, - upgrade policy, and parameter key-value pairs to be updated. + Specifies a list of key-value pairs representing parameters and their corresponding values + within a single configuration file. + This field is used to override or set the values of parameters without modifying the entire configuration file. items: properties: - keys: - description: |- - Sets the configuration files and their associated parameters that need to be updated. - It should contain at least one item. - items: - properties: - fileContent: - description: |- - Specifies the content of the entire configuration file. - This field is used to update the complete configuration file. - - - Either the `parameters` field or the `fileContent` field must be set, but not both. - type: string - key: - description: |- - Represents a key in the configuration template(as ConfigMap). - Each key in the ConfigMap corresponds to a specific configuration file. - type: string - parameters: - description: |- - Specifies a list of key-value pairs representing parameters and their corresponding values - within a single configuration file. - This field is used to override or set the values of parameters without modifying the entire configuration file. - - - Either the `parameters` field or the `fileContent` field must be set, but not both. - items: - properties: - key: - description: Represents the name of the parameter - that is to be updated. - type: string - value: - description: |- - Represents the parameter values that are to be updated. - If set to nil, the parameter defined by the Key field will be removed from the configuration file. - type: string - required: - - key - type: object - type: array - required: - - key - type: object - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - key - x-kubernetes-list-type: map name: - description: Specifies the name of the configuration template. - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + description: Specifies the name of the parameter that + is to be updated. type: string - policy: - description: Defines the upgrade policy for the configuration. - enum: - - simple - - parallel - - rolling - - autoReload - - operatorSyncUpdate - - dynamicReloadBeginRestart + value: + description: |- + Specifies the parameter values that are to be updated. + If set to nil, the parameter defined by the Key field will be removed from the configuration file. type: string required: - - keys - name type: object - minItems: 1 type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map required: - componentName - - configurations type: object type: array x-kubernetes-list-map-keys: @@ -8617,183 +8556,6 @@ spec: description: Represents the progress of the OpsRequest. pattern: ^(\d+|\-)/(\d+|\-)$ type: string - reconfiguringStatusAsComponent: - additionalProperties: - properties: - conditions: - description: |- - Describes the reconfiguring detail status. - Possible condition types include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - items: - description: "Condition contains details for one aspect of - the current state of this API Resource.\n---\nThis struct - is intended for direct use as an array at the field path - .status.conditions. For example,\n\n\n\ttype FooStatus - struct{\n\t // Represents the observations of a foo's - current state.\n\t // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // - +listType=map\n\t // +listMapKey=type\n\t Conditions - []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" - patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - configurationStatus: - description: Describes the status of the component reconfiguring. - items: - properties: - expectedCount: - default: -1 - description: Represents the total count of pods intended - to be updated by a configuration change. - format: int32 - type: integer - lastAppliedConfiguration: - additionalProperties: - type: string - description: Stores the last applied configuration. - type: object - lastStatus: - description: |- - Records the last state of the reconfiguration finite state machine. - Possible values include "None", "Retry", "Failed", "NotSupport", "FailedAndRetry". - - - - "None" describes fsm has finished and quit. - - "Retry" describes fsm is running. - - "Failed" describes fsm is failed and exited. - - "NotSupport" describes fsm does not support the feature. - - "FailedAndRetry" describes fsm is failed in current state, but can be retried. - type: string - message: - description: Provides details about the operation. - type: string - name: - description: Indicates the name of the configuration template - (as ConfigMap). - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ - type: string - status: - description: |- - Represents the current state of the reconfiguration state machine. - Possible values include "Creating", "Init", "Running", "Pending", "Merged", "MergeFailed", "FailedAndPause", - "Upgrading", "Deleting", "FailedAndRetry", "Finished", "ReconfigurePersisting", "ReconfigurePersisted". - type: string - succeedCount: - default: 0 - description: Records the number of pods successfully updated - following a configuration change. - format: int32 - type: integer - updatePolicy: - description: Records the UpgradePolicy of the configuration - change operation. - enum: - - simple - - parallel - - rolling - - autoReload - - operatorSyncUpdate - - dynamicReloadBeginRestart - type: string - updatedParameters: - description: Contains the updated parameters. - properties: - addedKeys: - additionalProperties: - type: string - description: Maps newly added configuration files - to their content. - type: object - deletedKeys: - additionalProperties: - type: string - description: Lists the name of configuration files - that have been deleted. - type: object - updatedKeys: - additionalProperties: - type: string - description: Maps the name of configuration files - to their updated content, detailing the changes - made. - type: object - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - configurationStatus - type: object - description: Records the status of a reconfiguring operation if `opsRequest.spec.type` - equals to "Reconfiguring". - type: object startTimestamp: description: Records the time when the OpsRequest started processing. format: date-time diff --git a/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml b/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml index 276f009e40d..a36f8a49a3b 100644 --- a/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml +++ b/deploy/helm/crds/parameters.kubeblocks.io_componentparameters.yaml @@ -192,13 +192,6 @@ spec: pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string payload: - additionalProperties: - description: |- - RawMessage is a raw encoded JSON value. - It implements Marshaler and Unmarshaler and can - be used to delay JSON decoding or precompute a JSON encoding. - format: byte - type: string description: |- External controllers can trigger a configuration rerender by modifying this field. diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 7d57cf754d7..6bf0faa5998 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -1241,8 +1241,8 @@ and bind Services at Cluster creation time with clusterComponentSpec.Servi configs
- -[]ComponentConfigSpec + +[]ComponentTemplateSpec @@ -4662,138 +4662,6 @@ string -

ComponentConfigSpec -

-

-(Appears on:ComponentDefinitionSpec) -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-ComponentTemplateSpec
- - -ComponentTemplateSpec - - -
-

-(Members of ComponentTemplateSpec are embedded into this type.) -

-
-keys
- -[]string - -
-(Optional) -

Specifies the configuration files within the ConfigMap that support dynamic updates.

-

A configuration template (provided in the form of a ConfigMap) may contain templates for multiple -configuration files. -Each configuration file corresponds to a key in the ConfigMap. -Some of these configuration files may support dynamic modification and reloading without requiring -a pod restart.

-

If empty or omitted, all configuration files in the ConfigMap are assumed to support dynamic updates, -and ConfigConstraint applies to all keys.

-
-constraintRef
- -string - -
-(Optional) -

Specifies the name of the referenced configuration constraints object.

-
-asEnvFrom
- -[]string - -
-(Optional) -

Specifies the containers to inject the ConfigMap parameters as environment variables.

-

This is useful when application images accept parameters through environment variables and -generate the final configuration file in the startup script based on these variables.

-

This field allows users to specify a list of container names, and KubeBlocks will inject the environment -variables converted from the ConfigMap into these designated containers. This provides a flexible way to -pass the configuration items from the ConfigMap to the container without modifying the image.

-

Deprecated: asEnvFrom has been deprecated since 0.9.0 and will be removed in 0.10.0. -Use injectEnvTo instead.

-
-injectEnvTo
- -[]string - -
-(Optional) -

Specifies the containers to inject the ConfigMap parameters as environment variables.

-

This is useful when application images accept parameters through environment variables and -generate the final configuration file in the startup script based on these variables.

-

This field allows users to specify a list of container names, and KubeBlocks will inject the environment -variables converted from the ConfigMap into these designated containers. This provides a flexible way to -pass the configuration items from the ConfigMap to the container without modifying the image.

-
-reRenderResourceTypes
- - -[]RerenderResourceType - - -
-(Optional) -

Specifies whether the configuration needs to be re-rendered after v-scale or h-scale operations to reflect changes.

-

In some scenarios, the configuration may need to be updated to reflect the changes in resource allocation -or cluster topology. Examples:

-
    -
  • Redis: adjust maxmemory after v-scale operation.
  • -
  • MySQL: increase max connections after v-scale operation.
  • -
  • Zookeeper: update zoo.cfg with new node addresses after h-scale operation.
  • -
-
-asSecret
- -bool - -
-(Optional) -

Whether to store the final rendered parameters as a secret.

-

ComponentDefinitionSpec

@@ -5090,8 +4958,8 @@ and bind Services at Cluster creation time with clusterComponentSpec.Servi configs
- -[]ComponentConfigSpec + +[]ComponentTemplateSpec @@ -6551,7 +6419,7 @@ ProvisionSecretRef

ComponentTemplateSpec

-(Appears on:ComponentConfigSpec, ComponentDefinitionSpec, SidecarDefinitionSpec) +(Appears on:ComponentDefinitionSpec, SidecarDefinitionSpec)

@@ -8896,9 +8764,6 @@ int32

RerenderResourceType (string alias)

-

-(Appears on:ComponentConfigSpec) -

RerenderResourceType defines the resource requirements for a component.

diff --git a/pkg/configuration/config_manager/builder.go b/pkg/configuration/config_manager/builder.go index 383a9f4fdab..03f85d2a13a 100644 --- a/pkg/configuration/config_manager/builder.go +++ b/pkg/configuration/config_manager/builder.go @@ -38,7 +38,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" @@ -98,9 +98,9 @@ func getWatchedVolume(volumeDirs []corev1.VolumeMount, buildParams []ConfigSpecM continue } switch param.ReloadType { - case appsv1beta1.TPLScriptType: + case parametersv1alpha1.TPLScriptType: return core.IsWatchModuleForTplTrigger(param.ReloadAction.TPLScriptTrigger) - case appsv1beta1.ShellType: + case parametersv1alpha1.ShellType: return core.IsWatchModuleForShellTrigger(param.ReloadAction.ShellTrigger) default: return true @@ -224,7 +224,7 @@ func buildConfigSpecHandleMeta(cli client.Client, ctx context.Context, buildPara return err } } - if buildParam.ReloadType == appsv1beta1.TPLScriptType { + if buildParam.ReloadType == parametersv1alpha1.TPLScriptType { return buildTPLScriptCM(buildParam, cmBuildParam, cli, ctx) } return nil @@ -262,7 +262,7 @@ func buildTPLScriptCM(configSpecBuildMeta *ConfigSpecMeta, manager *CfgManagerBu return nil } -func buildDownwardAPIVolume(manager *CfgManagerBuildParams, fieldInfo appsv1beta1.DownwardAPIChangeTriggeredAction) { +func buildDownwardAPIVolume(manager *CfgManagerBuildParams, fieldInfo parametersv1alpha1.DownwardAPIChangeTriggeredAction) { manager.DownwardAPIVolumes = append(manager.DownwardAPIVolumes, corev1.VolumeMount{ Name: fieldInfo.Name, MountPath: fieldInfo.MountPoint, @@ -353,7 +353,7 @@ func mergeWithOverride(dst, src interface{}) { _ = mergo.Merge(dst, src, mergo.WithOverride) } -func checkAndUpdateReloadYaml(data map[string]string, reloadConfig string, formatterConfig appsv1beta1.FileFormatConfig) (map[string]string, error) { +func checkAndUpdateReloadYaml(data map[string]string, reloadConfig string, formatterConfig parametersv1alpha1.FileFormatConfig) (map[string]string, error) { configObject := make(map[string]interface{}) if content, ok := data[reloadConfig]; ok { if err := yaml.Unmarshal([]byte(content), &configObject); err != nil { @@ -379,7 +379,7 @@ func checkAndUpdateReloadYaml(data map[string]string, reloadConfig string, forma return data, nil } -func buildCfgManagerScripts(options appsv1beta1.ScriptConfig, manager *CfgManagerBuildParams, cli client.Client, ctx context.Context, configSpec appsv1.ComponentConfigSpec) error { +func buildCfgManagerScripts(options parametersv1alpha1.ScriptConfig, manager *CfgManagerBuildParams, cli client.Client, ctx context.Context, configSpec appsv1.ComponentTemplateSpec) error { mountPoint := filepath.Join(KBScriptVolumePath, configSpec.Name) referenceCMKey := client.ObjectKey{ Namespace: options.Namespace, @@ -396,19 +396,15 @@ func buildCfgManagerScripts(options appsv1beta1.ScriptConfig, manager *CfgManage return nil } -func GetConfigMountPoint(configSpec appsv1.ComponentConfigSpec) string { - return filepath.Join(KBConfigVolumePath, configSpec.Name) -} - -func GetScriptsMountPoint(configSpec appsv1.ComponentConfigSpec) string { +func GetScriptsMountPoint(configSpec appsv1.ComponentTemplateSpec) string { return filepath.Join(KBScriptVolumePath, configSpec.Name) } -func GetScriptsVolumeName(configSpec appsv1.ComponentConfigSpec) string { +func GetScriptsVolumeName(configSpec appsv1.ComponentTemplateSpec) string { return fmt.Sprintf("%s%s", scriptVolumePrefix, configSpec.Name) } -func GetConfigVolumeName(configSpec appsv1.ComponentConfigSpec) string { +func GetConfigVolumeName(configSpec appsv1.ComponentTemplateSpec) string { return fmt.Sprintf("%s%s", configVolumePrefix, configSpec.Name) } diff --git a/pkg/configuration/config_manager/builder_test.go b/pkg/configuration/config_manager/builder_test.go index ade5da8b0dc..756b505d7bf 100644 --- a/pkg/configuration/config_manager/builder_test.go +++ b/pkg/configuration/config_manager/builder_test.go @@ -34,7 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -78,39 +78,39 @@ var _ = Describe("Config Builder Test", func() { Name: "pg_config", }} } - newReloadOptions := func(t appsv1beta1.DynamicReloadType, sync *bool) *appsv1beta1.ReloadAction { - signalHandle := &appsv1beta1.UnixSignalTrigger{ + newReloadOptions := func(t parametersv1alpha1.DynamicReloadType, sync *bool) *parametersv1alpha1.ReloadAction { + signalHandle := ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: "postgres", - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, } - shellHandle := &appsv1beta1.ShellTrigger{ + shellHandle := ¶metersv1alpha1.ShellTrigger{ Command: []string{"pwd"}, } - scriptHandle := &appsv1beta1.TPLScriptTrigger{ + scriptHandle := ¶metersv1alpha1.TPLScriptTrigger{ Sync: sync, - ScriptConfig: appsv1beta1.ScriptConfig{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: "reload-script", Namespace: scriptsNS, }, } - autoHandle := &appsv1beta1.AutoTrigger{ + autoHandle := ¶metersv1alpha1.AutoTrigger{ ProcessName: "postgres", } switch t { default: return nil - case appsv1beta1.UnixSignalType: - return &appsv1beta1.ReloadAction{ + case parametersv1alpha1.UnixSignalType: + return ¶metersv1alpha1.ReloadAction{ UnixSignalTrigger: signalHandle} - case appsv1beta1.ShellType: - return &appsv1beta1.ReloadAction{ + case parametersv1alpha1.ShellType: + return ¶metersv1alpha1.ReloadAction{ ShellTrigger: shellHandle} - case appsv1beta1.TPLScriptType: - return &appsv1beta1.ReloadAction{ + case parametersv1alpha1.TPLScriptType: + return ¶metersv1alpha1.ReloadAction{ TPLScriptTrigger: scriptHandle} - case appsv1beta1.AutoType: - return &appsv1beta1.ReloadAction{ + case parametersv1alpha1.AutoType: + return ¶metersv1alpha1.ReloadAction{ AutoTrigger: autoHandle} } } @@ -118,11 +118,9 @@ var _ = Describe("Config Builder Test", func() { return []ConfigSpecMeta{ { ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "pg_config", - VolumeName: "pg_config", - }, + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "pg_config", + VolumeName: "pg_config", }, }, }, @@ -137,14 +135,13 @@ var _ = Describe("Config Builder Test", func() { Namespace: "default", }, }, - ComponentName: "test", - Volumes: newVolumeMounts(), - ConfigSpecsBuildParams: newConfigSpecMeta(), - ConfigLazyRenderedVolumes: make(map[string]corev1.VolumeMount), - DownwardAPIVolumes: make([]corev1.VolumeMount, 0), + ComponentName: "test", + Volumes: newVolumeMounts(), + ConfigSpecsBuildParams: newConfigSpecMeta(), + DownwardAPIVolumes: make([]corev1.VolumeMount, 0), } if hasScripts { - param.ConfigSpecsBuildParams[0].ScriptConfig = []appsv1beta1.ScriptConfig{ + param.ConfigSpecsBuildParams[0].ScriptConfig = []parametersv1alpha1.ScriptConfig{ { Namespace: scriptsNS, ScriptConfigMapRef: scriptsName, @@ -181,8 +178,8 @@ formatterConfig: mockK8sCli.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes())) } - newDownwardAPIVolumes := func() []appsv1beta1.DownwardAPIChangeTriggeredAction { - return []appsv1beta1.DownwardAPIChangeTriggeredAction{ + newDownwardAPIVolumes := func() []parametersv1alpha1.DownwardAPIChangeTriggeredAction { + return []parametersv1alpha1.DownwardAPIChangeTriggeredAction{ { Name: "downward-api", MountPoint: "/etc/podinfo", @@ -203,11 +200,11 @@ formatterConfig: It("builds unixSignal reloader correctly", func() { param := newCMBuildParams(false) mockTplScriptCM() - reloadOptions := newReloadOptions(appsv1beta1.UnixSignalType, nil) + reloadOptions := newReloadOptions(parametersv1alpha1.UnixSignalType, nil) for i := range param.ConfigSpecsBuildParams { buildParam := ¶m.ConfigSpecsBuildParams[i] buildParam.ReloadAction = reloadOptions - buildParam.ReloadType = appsv1beta1.UnixSignalType + buildParam.ReloadType = parametersv1alpha1.UnixSignalType } Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), ctx, param, newVolumeMounts2())).Should(Succeed()) for _, arg := range []string{`--volume-dir`, `/postgresql/conf`, `--volume-dir`, `/postgresql/conf2`} { @@ -227,11 +224,11 @@ formatterConfig: mockK8sCli.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithTimes(2))) param := newCMBuildParams(true) - reloadOptions := newReloadOptions(appsv1beta1.ShellType, nil) + reloadOptions := newReloadOptions(parametersv1alpha1.ShellType, nil) for i := range param.ConfigSpecsBuildParams { buildParam := ¶m.ConfigSpecsBuildParams[i] buildParam.ReloadAction = reloadOptions - buildParam.ReloadType = appsv1beta1.ShellType + buildParam.ReloadType = parametersv1alpha1.ShellType } Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) for _, arg := range []string{`--volume-dir`, `/postgresql/conf`} { @@ -242,11 +239,11 @@ formatterConfig: It("builds tplScriptsTrigger reloader correctly", func() { mockTplScriptCM() param := newCMBuildParams(false) - reloadOptions := newReloadOptions(appsv1beta1.TPLScriptType, syncFn(true)) + reloadOptions := newReloadOptions(parametersv1alpha1.TPLScriptType, syncFn(true)) for i := range param.ConfigSpecsBuildParams { buildParam := ¶m.ConfigSpecsBuildParams[i] buildParam.ReloadAction = reloadOptions - buildParam.ReloadType = appsv1beta1.TPLScriptType + buildParam.ReloadType = parametersv1alpha1.TPLScriptType } Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) for _, arg := range []string{`--operator-update-enable`} { @@ -257,11 +254,11 @@ formatterConfig: It("builds tplScriptsTrigger reloader correctly with sync", func() { mockTplScriptCM() param := newCMBuildParams(false) - reloadOptions := newReloadOptions(appsv1beta1.TPLScriptType, syncFn(false)) + reloadOptions := newReloadOptions(parametersv1alpha1.TPLScriptType, syncFn(false)) for i := range param.ConfigSpecsBuildParams { buildParam := ¶m.ConfigSpecsBuildParams[i] buildParam.ReloadAction = reloadOptions - buildParam.ReloadType = appsv1beta1.TPLScriptType + buildParam.ReloadType = parametersv1alpha1.TPLScriptType } Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) for _, arg := range []string{`--volume-dir`, `/postgresql/conf`} { @@ -274,7 +271,7 @@ formatterConfig: param := newCMBuildParams(false) buildParam := ¶m.ConfigSpecsBuildParams[0] buildParam.DownwardAPIOptions = newDownwardAPIVolumes() - buildParam.ReloadAction = newReloadOptions(appsv1beta1.TPLScriptType, syncFn(true)) + buildParam.ReloadAction = newReloadOptions(parametersv1alpha1.TPLScriptType, syncFn(true)) Expect(BuildConfigManagerContainerParams(mockK8sCli.Client(), context.TODO(), param, newVolumeMounts())).Should(Succeed()) Expect(FindVolumeMount(param.DownwardAPIVolumes, buildParam.DownwardAPIOptions[0].Name)).ShouldNot(BeNil()) }) @@ -305,7 +302,7 @@ func TestCheckAndUpdateReloadYaml(t *testing.T) { type args struct { data map[string]string reloadConfig string - formatterConfig *appsv1beta1.FileFormatConfig + formatterConfig *parametersv1alpha1.FileFormatConfig } tests := []struct { name string @@ -320,8 +317,8 @@ fileRegex: my.cnf scripts: reload.tpl `}, reloadConfig: "reload.yaml", - formatterConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, + formatterConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, }, }, wantErr: false, @@ -337,7 +334,7 @@ formatterConfig: args: args{ data: map[string]string{}, reloadConfig: "reload.yaml", - formatterConfig: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.Ini}, + formatterConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}, }, wantErr: true, want: map[string]string{}, diff --git a/pkg/configuration/config_manager/config_handler.go b/pkg/configuration/config_manager/config_handler.go index 2fb9c1665eb..07456085899 100644 --- a/pkg/configuration/config_manager/config_handler.go +++ b/pkg/configuration/config_manager/config_handler.go @@ -33,7 +33,7 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/gotemplate" @@ -43,10 +43,10 @@ type configVolumeHandleMeta struct { ConfigHandler mountPoint []string - reloadType appsv1beta1.DynamicReloadType + reloadType parametersv1alpha1.DynamicReloadType configSpec appsv1alpha1.ComponentTemplateSpec - formatterConfig *appsv1beta1.FileFormatConfig + formatterConfig *parametersv1alpha1.FileFormatConfig } func (s *configVolumeHandleMeta) OnlineUpdate(_ context.Context, _ string, _ map[string]string) error { @@ -148,7 +148,7 @@ func (u *unixSignalHandler) MountPoint() []string { return []string{u.mountPoint} } -func CreateSignalHandler(sig appsv1beta1.SignalType, processName string, mountPoint string) (ConfigHandler, error) { +func CreateSignalHandler(sig parametersv1alpha1.SignalType, processName string, mountPoint string) (ConfigHandler, error) { signal, ok := allUnixSignals[sig] if !ok { err := cfgcore.MakeError("not supported unix signal: %s", sig) @@ -347,7 +347,7 @@ func (s *shellCommandHandler) isDownwardAPITrigger() bool { return s.downwardAPITrigger } -func createConfigVolumeMeta(configSpecName string, reloadType appsv1beta1.DynamicReloadType, mountPoint []string, formatterConfig *appsv1beta1.FileFormatConfig) configVolumeHandleMeta { +func createConfigVolumeMeta(configSpecName string, reloadType parametersv1alpha1.DynamicReloadType, mountPoint []string, formatterConfig *parametersv1alpha1.FileFormatConfig) configVolumeHandleMeta { return configVolumeHandleMeta{ reloadType: reloadType, mountPoint: mountPoint, @@ -364,11 +364,11 @@ func isShellCommand(configMeta *ConfigSpecInfo) bool { configMeta.ReloadAction.ShellTrigger != nil } -func isBatchReloadMode(shellAction *appsv1beta1.ShellTrigger) bool { +func isBatchReloadMode(shellAction *parametersv1alpha1.ShellTrigger) bool { return shellAction.BatchReload != nil && *shellAction.BatchReload } -func isValidBatchReload(shellAction *appsv1beta1.ShellTrigger) bool { +func isValidBatchReload(shellAction *parametersv1alpha1.ShellTrigger) bool { return isBatchReloadMode(shellAction) && len(shellAction.BatchParamsFormatterTemplate) > 0 } @@ -397,7 +397,7 @@ func CreateExecHandler(command []string, mountPoint string, configMeta *ConfigSp return nil, err } - var formatterConfig *appsv1beta1.FileFormatConfig + var formatterConfig *parametersv1alpha1.FileFormatConfig if backupPath != "" && configMeta != nil && configMeta.ReloadAction != nil { if err := checkAndBackup(*configMeta, []string{configMeta.MountPoint}, filter, backupPath); err != nil { return nil, err @@ -418,7 +418,7 @@ func CreateExecHandler(command []string, mountPoint string, configMeta *ConfigSp // for downward api watch downwardAPIMountPoint: cfgutil.ToSet(handler).AsSlice(), downwardAPIHandler: handler, - configVolumeHandleMeta: createConfigVolumeMeta(configMeta.ConfigSpec.Name, appsv1beta1.ShellType, []string{mountPoint}, formatterConfig), + configVolumeHandleMeta: createConfigVolumeMeta(configMeta.ConfigSpec.Name, parametersv1alpha1.ShellType, []string{mountPoint}, formatterConfig), isBatchReload: isBatchReload(configMeta), batchInputTemplate: getBatchInputTemplate(configMeta), } @@ -436,13 +436,10 @@ func checkAndBackup(configMeta ConfigSpecInfo, dirs []string, filter regexFilter } func fromConfigSpecInfo(meta *ConfigSpecInfo) string { - if meta == nil || len(meta.ConfigSpec.Keys) == 0 { + if meta == nil || len(meta.ConfigFile) == 0 { return "" } - if len(meta.ConfigSpec.Keys) == 1 { - return meta.ConfigSpec.Keys[0] - } - return "( " + strings.Join(meta.ConfigSpec.Keys, " | ") + " )" + return meta.ConfigFile } func createDownwardHandler(meta *ConfigSpecInfo) (map[string]ConfigHandler, error) { @@ -452,11 +449,10 @@ func createDownwardHandler(meta *ConfigSpecInfo) (map[string]ConfigHandler, erro handlers := make(map[string]ConfigHandler) for _, field := range meta.DownwardAPIOptions { - mockConfigSpec := &ConfigSpecInfo{ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: strings.Join([]string{meta.ConfigSpec.Name, field.Name}, "."), - VolumeName: field.MountPoint, - }}} + mockConfigSpec := &ConfigSpecInfo{ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: strings.Join([]string{meta.ConfigSpec.Name, field.Name}, "."), + VolumeName: field.MountPoint, + }} h, err := CreateExecHandler(field.Command, field.MountPoint, mockConfigSpec, "") if err != nil { return nil, err @@ -481,7 +477,7 @@ type tplScriptHandler struct { } func (u *tplScriptHandler) OnlineUpdate(ctx context.Context, name string, updatedParams map[string]string) error { - logger.V(1).Info(fmt.Sprintf("online update[%v]", updatedParams)) + logger.V(1).Info(fmt.Sprintf("online update[%v]", updatedParams), "file", name) return wrapGoTemplateRun(ctx, u.tplScripts, u.tplContent, @@ -534,7 +530,7 @@ func CreateTPLScriptHandler(name, configPath string, dirs []string, backupPath s return nil, err } tplHandler := &tplScriptHandler{ - configVolumeHandleMeta: createConfigVolumeMeta(name, appsv1beta1.TPLScriptType, dirs, &tplConfig.FormatterConfig), + configVolumeHandleMeta: createConfigVolumeMeta(name, parametersv1alpha1.TPLScriptType, dirs, &tplConfig.FormatterConfig), tplContent: string(tplContent), tplScripts: tplScripts, fileFilter: filter, @@ -553,13 +549,13 @@ func CreateCombinedHandler(config string, backupPath string) (ConfigHandler, err shellTrigger := configMeta.ShellTrigger return CreateExecHandler(shellTrigger.Command, configMeta.MountPoint, &configMeta, filepath.Join(backupPath, configMeta.ConfigSpec.Name)) } - signalHandler := func(signalTrigger *appsv1beta1.UnixSignalTrigger, mountPoint string) (ConfigHandler, error) { + signalHandler := func(signalTrigger *parametersv1alpha1.UnixSignalTrigger, mountPoint string) (ConfigHandler, error) { if signalTrigger == nil { return nil, cfgcore.MakeError("signal trigger is nil") } return CreateSignalHandler(signalTrigger.Signal, signalTrigger.ProcessName, mountPoint) } - tplHandler := func(tplTrigger *appsv1beta1.TPLScriptTrigger, configMeta ConfigSpecInfo, backupPath string) (ConfigHandler, error) { + tplHandler := func(tplTrigger *parametersv1alpha1.TPLScriptTrigger, configMeta ConfigSpecInfo, backupPath string) (ConfigHandler, error) { if tplTrigger == nil { return nil, cfgcore.MakeError("tpl trigger is nil") } @@ -589,17 +585,21 @@ func CreateCombinedHandler(config string, backupPath string) (ConfigHandler, err switch configMeta.ReloadType { default: return nil, fmt.Errorf("not support reload type: %s", configMeta.ReloadType) - case appsv1beta1.ShellType: + case parametersv1alpha1.ShellType: h, err = shellHandler(configMeta, tmpPath) - case appsv1beta1.UnixSignalType: + case parametersv1alpha1.UnixSignalType: h, err = signalHandler(configMeta.ReloadAction.UnixSignalTrigger, configMeta.MountPoint) - case appsv1beta1.TPLScriptType: + case parametersv1alpha1.TPLScriptType: h, err = tplHandler(configMeta.ReloadAction.TPLScriptTrigger, configMeta, tmpPath) } if err != nil { return nil, err } - mHandler.handlers[configMeta.ConfigSpec.Name] = h + hkey := configMeta.ConfigSpec.Name + if configMeta.ConfigFile != "" { + hkey = hkey + "/" + configMeta.ConfigFile + } + mHandler.handlers[hkey] = h } return mHandler, nil } diff --git a/pkg/configuration/config_manager/config_handler_test.go b/pkg/configuration/config_manager/config_handler_test.go index 37d86851352..fb229d7c290 100644 --- a/pkg/configuration/config_manager/config_handler_test.go +++ b/pkg/configuration/config_manager/config_handler_test.go @@ -37,7 +37,7 @@ import ( "github.com/fsnotify/fsnotify" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -67,44 +67,41 @@ var _ = Describe("Config Handler Test", func() { DeferCleanup(mockK8sCli.Finish) }) - newConfigSpec := func() appsv1.ComponentConfigSpec { - return appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "config", - TemplateRef: "config-template", - VolumeName: "/opt/config", - Namespace: "default", - }, - ConfigConstraintRef: "config-constraint", + newConfigSpec := func() appsv1.ComponentTemplateSpec { + return appsv1.ComponentTemplateSpec{ + Name: "config", + TemplateRef: "config-template", + VolumeName: "/opt/config", + Namespace: "default", } } - newFormatter := func() appsv1beta1.FileFormatConfig { - return appsv1beta1.FileFormatConfig{ - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ + newFormatter := func() parametersv1alpha1.FileFormatConfig { + return parametersv1alpha1.FileFormatConfig{ + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "test", }, }, - Format: appsv1beta1.Ini, + Format: parametersv1alpha1.Ini, } } newUnixSignalConfig := func() ConfigSpecInfo { return ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: findCurrProcName(), - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, }}, - ReloadType: appsv1beta1.UnixSignalType, + ReloadType: parametersv1alpha1.UnixSignalType, MountPoint: "/tmp/test", ConfigSpec: newConfigSpec(), } } - newDownwardAPIOptions := func() []appsv1beta1.DownwardAPIChangeTriggeredAction { - return []appsv1beta1.DownwardAPIChangeTriggeredAction{ + newDownwardAPIOptions := func() []parametersv1alpha1.DownwardAPIChangeTriggeredAction { + return []parametersv1alpha1.DownwardAPIChangeTriggeredAction{ { Name: "labels", MountPoint: filepath.Join(tmpWorkDir, "labels"), @@ -120,12 +117,12 @@ var _ = Describe("Config Handler Test", func() { newDownwardAPIConfig := func() ConfigSpecInfo { return ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"sh", "-c", `echo "hello world" "$@"`}, }, }, - ReloadType: appsv1beta1.ShellType, + ReloadType: parametersv1alpha1.ShellType, MountPoint: tmpWorkDir, ConfigSpec: newConfigSpec(), FormatterConfig: newFormatter(), @@ -135,10 +132,10 @@ var _ = Describe("Config Handler Test", func() { newTPLScriptsConfig := func(configPath string) ConfigSpecInfo { return ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{}, + ReloadAction: ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{}, }, - ReloadType: appsv1beta1.TPLScriptType, + ReloadType: parametersv1alpha1.TPLScriptType, MountPoint: "/tmp/test", ConfigSpec: newConfigSpec(), FormatterConfig: newFormatter(), @@ -167,7 +164,7 @@ var _ = Describe("Config Handler Test", func() { Context("TestSimpleHandler", func() { It("CreateSignalHandler", func() { - _, err := CreateSignalHandler(appsv1beta1.SIGALRM, "test", "") + _, err := CreateSignalHandler(parametersv1alpha1.SIGALRM, "test", "") Expect(err).Should(Succeed()) _, err = CreateSignalHandler("NOSIGNAL", "test", "") Expect(err.Error()).To(ContainSubstring("not supported unix signal: NOSIGNAL")) @@ -179,10 +176,9 @@ var _ = Describe("Config Handler Test", func() { _, err = CreateExecHandler([]string{}, "", nil, "") Expect(err.Error()).To(ContainSubstring("invalid command")) c, err := CreateExecHandler([]string{"go", "version"}, "", &ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test", - }}}, + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "for_test", + }}, "") Expect(err).Should(Succeed()) Expect(c.VolumeHandle(context.Background(), fsnotify.Event{})).Should(Succeed()) @@ -273,11 +269,11 @@ var _ = Describe("Config Handler Test", func() { }) It("should succeed on reload individually", func() { configSpec := ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"sh", "-c", `echo "hello world" "$@"`, "sh"}, }}, - ReloadType: appsv1beta1.ShellType, + ReloadType: parametersv1alpha1.ShellType, MountPoint: configPath, ConfigSpec: newConfigSpec(), FormatterConfig: newFormatter(), @@ -287,15 +283,15 @@ var _ = Describe("Config Handler Test", func() { Describe("Test reload in a batch", func() { It("should succeed on the default batch input format", func() { configSpec := ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"sh", "-c", `while IFS="=" read -r the_key the_val; do echo "key='$the_key'; val='$the_val'"; done`, }, BatchReload: util.ToPointer(true), BatchParamsFormatterTemplate: defaultBatchInputTemplate, }}, - ReloadType: appsv1beta1.ShellType, + ReloadType: parametersv1alpha1.ShellType, MountPoint: configPath, ConfigSpec: newConfigSpec(), FormatterConfig: newFormatter(), @@ -307,15 +303,15 @@ var _ = Describe("Config Handler Test", func() { {{ printf "%s:%s" $pKey $pValue }} {{- end }}` configSpec := ConfigSpecInfo{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + ReloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"sh", "-c", `while IFS=":" read -r the_key the_val; do echo "key='$the_key'; val='$the_val'"; done`, }, BatchReload: util.ToPointer(true), BatchParamsFormatterTemplate: customBatchInputTemplate, }}, - ReloadType: appsv1beta1.ShellType, + ReloadType: parametersv1alpha1.ShellType, MountPoint: configPath, ConfigSpec: newConfigSpec(), FormatterConfig: newFormatter(), diff --git a/pkg/configuration/config_manager/handler_util.go b/pkg/configuration/config_manager/handler_util.go index 5fe21f070fb..f8b735ac526 100644 --- a/pkg/configuration/config_manager/handler_util.go +++ b/pkg/configuration/config_manager/handler_util.go @@ -23,13 +23,14 @@ import ( "context" "path/filepath" "regexp" + "slices" "github.com/fsnotify/fsnotify" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" ) @@ -52,10 +53,10 @@ type CfgManagerBuildParams struct { ConfigSpecsBuildParams []ConfigSpecMeta // init tools container - ToolsContainers []corev1.Container - DownwardAPIVolumes []corev1.VolumeMount - CMConfigVolumes []corev1.Volume - ConfigLazyRenderedVolumes map[string]corev1.VolumeMount + ToolsContainers []corev1.Container + DownwardAPIVolumes []corev1.VolumeMount + CMConfigVolumes []corev1.Volume + // ConfigLazyRenderedVolumes map[string]corev1.VolumeMount // support custom config manager sidecar ConfigManagerReloadPath string `json:"configManagerReloadPath"` @@ -64,36 +65,48 @@ type CfgManagerBuildParams struct { ContainerPort int32 `json:"containerPort"` } -func IsSupportReload(reload *appsv1beta1.ReloadAction) bool { +func NeedRestart(paramsDefs map[string]*parametersv1alpha1.ParametersDefinition, patch *core.ConfigPatchInfo) bool { + if patch == nil { + return false + } + for key := range patch.UpdateConfig { + if paramsDef, ok := paramsDefs[key]; !ok || !IsSupportReload(paramsDef.Spec.ReloadAction) { + return true + } + } + return false +} + +func IsSupportReload(reload *parametersv1alpha1.ReloadAction) bool { return reload != nil && isValidReloadPolicy(*reload) } -func isValidReloadPolicy(reload appsv1beta1.ReloadAction) bool { +func isValidReloadPolicy(reload parametersv1alpha1.ReloadAction) bool { return reload.AutoTrigger != nil || reload.ShellTrigger != nil || reload.TPLScriptTrigger != nil || reload.UnixSignalTrigger != nil } -func IsAutoReload(reload *appsv1beta1.ReloadAction) bool { +func IsAutoReload(reload *parametersv1alpha1.ReloadAction) bool { return reload != nil && reload.AutoTrigger != nil } -func FromReloadTypeConfig(reloadAction *appsv1beta1.ReloadAction) appsv1beta1.DynamicReloadType { +func FromReloadTypeConfig(reloadAction *parametersv1alpha1.ReloadAction) parametersv1alpha1.DynamicReloadType { switch { case reloadAction.UnixSignalTrigger != nil: - return appsv1beta1.UnixSignalType + return parametersv1alpha1.UnixSignalType case reloadAction.ShellTrigger != nil: - return appsv1beta1.ShellType + return parametersv1alpha1.ShellType case reloadAction.TPLScriptTrigger != nil: - return appsv1beta1.TPLScriptType + return parametersv1alpha1.TPLScriptType case reloadAction.AutoTrigger != nil: - return appsv1beta1.AutoType + return parametersv1alpha1.AutoType } return "" } -func ValidateReloadOptions(reloadAction *appsv1beta1.ReloadAction, cli client.Client, ctx context.Context) error { +func ValidateReloadOptions(reloadAction *parametersv1alpha1.ReloadAction, cli client.Client, ctx context.Context) error { switch { case reloadAction.UnixSignalTrigger != nil: return checkSignalTrigger(reloadAction.UnixSignalTrigger) @@ -107,7 +120,7 @@ func ValidateReloadOptions(reloadAction *appsv1beta1.ReloadAction, cli client.Cl return core.MakeError("require special reload type!") } -func checkTPLScriptTrigger(options *appsv1beta1.TPLScriptTrigger, cli client.Client, ctx context.Context) error { +func checkTPLScriptTrigger(options *parametersv1alpha1.TPLScriptTrigger, cli client.Client, ctx context.Context) error { cm := corev1.ConfigMap{} return cli.Get(ctx, client.ObjectKey{ Namespace: options.Namespace, @@ -115,14 +128,14 @@ func checkTPLScriptTrigger(options *appsv1beta1.TPLScriptTrigger, cli client.Cli }, &cm) } -func checkShellTrigger(options *appsv1beta1.ShellTrigger) error { +func checkShellTrigger(options *parametersv1alpha1.ShellTrigger) error { if len(options.Command) == 0 { return core.MakeError("required shell trigger") } return nil } -func checkSignalTrigger(options *appsv1beta1.UnixSignalTrigger) error { +func checkSignalTrigger(options *parametersv1alpha1.UnixSignalTrigger) error { signal := options.Signal if !IsValidUnixSignal(signal) { return core.MakeError("this special signal [%s] is not supported now.", signal) @@ -155,34 +168,83 @@ func CreateValidConfigMapFilter() NotifyEventFilter { } } -func GetSupportReloadConfigSpecs(configSpecs []appsv1.ComponentConfigSpec, cli client.Client, ctx context.Context) ([]ConfigSpecMeta, error) { +func actionToolsImage(reloadAction *parametersv1alpha1.ReloadAction) *parametersv1alpha1.ToolsSetup { + if reloadAction != nil && reloadAction.ShellTrigger != nil { + return reloadAction.ShellTrigger.ToolsSetup + } + return nil +} + +func actionToolsScripts(paramDef parametersv1alpha1.ParametersDefinitionSpec) []parametersv1alpha1.ScriptConfig { + uniqueSlice := func(items []parametersv1alpha1.ScriptConfig) []parametersv1alpha1.ScriptConfig { + var uniqItems []parametersv1alpha1.ScriptConfig + for _, item := range items { + if !slices.Contains(uniqItems, item) { + uniqItems = append(uniqItems, item) + } + } + return uniqItems + } + + scriptConfigs := make([]parametersv1alpha1.ScriptConfig, 0) + for _, action := range paramDef.DownwardAPIChangeTriggeredActions { + if action.ScriptConfig != nil { + scriptConfigs = append(scriptConfigs, *action.ScriptConfig) + } + } + if paramDef.ReloadAction == nil { + return uniqueSlice(scriptConfigs) + } + if paramDef.ReloadAction.ShellTrigger != nil && paramDef.ReloadAction.ShellTrigger.ScriptConfig != nil { + scriptConfigs = append(scriptConfigs, *paramDef.ReloadAction.ShellTrigger.ScriptConfig) + } + return uniqueSlice(scriptConfigs) +} + +func GetSupportReloadConfigSpecs(configSpecs []appsv1.ComponentTemplateSpec, + configDescs []parametersv1alpha1.ComponentConfigDescription, + paramsDefs []*parametersv1alpha1.ParametersDefinition) ([]ConfigSpecMeta, error) { + resolveReloadAction := func(fileName string) *parametersv1alpha1.ParametersDefinition { + for _, paramsDef := range paramsDefs { + if paramsDef.Spec.FileName == fileName { + return paramsDef + } + } + return nil + } + resolveConfigTemplate := func(name string) *appsv1.ComponentTemplateSpec { + for i, configSpec := range configSpecs { + if configSpec.Name == name { + return &configSpecs[i] + } + } + return nil + } + var reloadConfigSpecMeta []ConfigSpecMeta - for _, configSpec := range configSpecs { - // pass if support change and reload ConfigMap when parameters change - if !core.NeedReloadVolume(configSpec) { + for _, desc := range configDescs { + paramsDef := resolveReloadAction(desc.Name) + if paramsDef == nil || paramsDef.Spec.ReloadAction == nil || desc.FileFormatConfig == nil { continue } - ccKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - cc := &appsv1beta1.ConfigConstraint{} - if err := cli.Get(ctx, ccKey, cc); err != nil { - return nil, core.WrapError(err, "failed to get ConfigConstraint, key[%v]", ccKey) + configSpec := resolveConfigTemplate(desc.TemplateName) + if configSpec == nil { + continue } - reloadOptions := cc.Spec.ReloadAction - if !IsSupportReload(reloadOptions) || IsAutoReload(reloadOptions) { + action := paramsDef.Spec.ReloadAction + if !IsSupportReload(action) || IsAutoReload(action) { continue } reloadConfigSpecMeta = append(reloadConfigSpecMeta, ConfigSpecMeta{ - ToolsImageSpec: cc.Spec.GetToolsSetup(), - ScriptConfig: cc.Spec.GetScriptConfigs(), + ToolsImageSpec: actionToolsImage(action), + ScriptConfig: actionToolsScripts(paramsDef.Spec), ConfigSpecInfo: ConfigSpecInfo{ - ReloadAction: cc.Spec.ReloadAction, - ConfigSpec: configSpec, - ReloadType: FromReloadTypeConfig(reloadOptions), - DownwardAPIOptions: cc.Spec.DownwardAPIChangeTriggeredActions, - FormatterConfig: *cc.Spec.FileFormatConfig, + ReloadAction: action, + FormatterConfig: *desc.FileFormatConfig, + ConfigSpec: *configSpec, + ReloadType: FromReloadTypeConfig(action), + ConfigFile: desc.Name, + DownwardAPIOptions: paramsDef.Spec.DownwardAPIChangeTriggeredActions, }, }) } @@ -223,6 +285,6 @@ func isSubPathMount(v *corev1.VolumeMount) bool { func isSyncReloadAction(meta ConfigSpecInfo) bool { // If synchronous reloadAction is supported, kubelet limitations can be ignored. - return meta.ReloadType == appsv1beta1.TPLScriptType && !core.IsWatchModuleForTplTrigger(meta.TPLScriptTrigger) || - meta.ReloadType == appsv1beta1.ShellType && !core.IsWatchModuleForShellTrigger(meta.ShellTrigger) + return meta.ReloadType == parametersv1alpha1.TPLScriptType && !core.IsWatchModuleForTplTrigger(meta.TPLScriptTrigger) || + meta.ReloadType == parametersv1alpha1.ShellType && !core.IsWatchModuleForShellTrigger(meta.ShellTrigger) } diff --git a/pkg/configuration/config_manager/handler_util_test.go b/pkg/configuration/config_manager/handler_util_test.go index 81320ee0310..9db9a416187 100644 --- a/pkg/configuration/config_manager/handler_util_test.go +++ b/pkg/configuration/config_manager/handler_util_test.go @@ -33,7 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" @@ -41,7 +41,7 @@ import ( func TestIsSupportReload(t *testing.T) { type args struct { - reload *appsv1beta1.ReloadAction + reload *parametersv1alpha1.ReloadAction } tests := []struct { name string @@ -56,16 +56,16 @@ func TestIsSupportReload(t *testing.T) { }, { name: "reload_test_with_empty_reload_options", args: args{ - reload: &appsv1beta1.ReloadAction{}, + reload: ¶metersv1alpha1.ReloadAction{}, }, want: false, }, { name: "reload_test_with_unix_signal", args: args{ - reload: &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + reload: ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: "test", - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, }, }, }, @@ -73,8 +73,8 @@ func TestIsSupportReload(t *testing.T) { }, { name: "reload_test_with_shell", args: args{ - reload: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + reload: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: strings.Fields("pg_ctl reload"), }, }, @@ -83,9 +83,9 @@ func TestIsSupportReload(t *testing.T) { }, { name: "reload_test_with_tpl_script", args: args{ - reload: &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + reload: ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: "cm", Namespace: "default", }, @@ -96,8 +96,8 @@ func TestIsSupportReload(t *testing.T) { }, { name: "auto_trigger_reload_test_with_process_name", args: args{ - reload: &appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{ + reload: ¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{ ProcessName: "test", }, }, @@ -106,8 +106,8 @@ func TestIsSupportReload(t *testing.T) { }, { name: "auto_trigger_reload_test", args: args{ - reload: &appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{}, + reload: ¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{}, }, }, want: true, @@ -134,28 +134,34 @@ var _ = Describe("Handler Util Test", func() { DeferCleanup(mockK8sCli.Finish) }) - mockConfigConstraint := func(ccName string, reloadOptions *appsv1beta1.ReloadAction) *appsv1beta1.ConfigConstraint { - return &appsv1beta1.ConfigConstraint{ + mockParametersDef := func(ccName string, reloadOptions *parametersv1alpha1.ReloadAction) *parametersv1alpha1.ParametersDefinition { + return ¶metersv1alpha1.ParametersDefinition{ ObjectMeta: metav1.ObjectMeta{ Name: ccName, }, - Spec: appsv1beta1.ConfigConstraintSpec{ + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "test", ReloadAction: reloadOptions, - FileFormatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, - }, }} } - - mockConfigSpec := func(ccName string) appsv1.ComponentConfigSpec { - return appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - TemplateRef: "config_template", - Namespace: "default", - VolumeName: "for_test", + mockConfigDescription := func(tplName string, format parametersv1alpha1.CfgFileFormat) []parametersv1alpha1.ComponentConfigDescription { + return []parametersv1alpha1.ComponentConfigDescription{ + { + Name: "test", + TemplateName: tplName, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: format, + }, }, - ConfigConstraintRef: ccName, + } + } + + mockConfigSpec := func(tplName string) appsv1.ComponentTemplateSpec { + return appsv1.ComponentTemplateSpec{ + Name: tplName, + TemplateRef: "config_template", + Namespace: "default", + VolumeName: "for_test", } } @@ -167,7 +173,7 @@ var _ = Describe("Handler Util Test", func() { ) type args struct { - reloadAction *appsv1beta1.ReloadAction + reloadAction *parametersv1alpha1.ReloadAction cli client.Client ctx context.Context } @@ -178,17 +184,17 @@ var _ = Describe("Handler Util Test", func() { }{{ name: "unixSignalTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ - Signal: appsv1beta1.SIGHUP, + reloadAction: ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ + Signal: parametersv1alpha1.SIGHUP, }}, }, wantErr: false, }, { name: "unixSignalTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ Signal: "SIGNOEXIST", }}, }, @@ -196,8 +202,8 @@ var _ = Describe("Handler Util Test", func() { }, { name: "shellTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: nil, }}, }, @@ -205,8 +211,8 @@ var _ = Describe("Handler Util Test", func() { }, { name: "shellTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: strings.Fields("go"), }}, }, @@ -214,9 +220,9 @@ var _ = Describe("Handler Util Test", func() { }, { name: "TPLScriptTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: "test", }, }}, @@ -227,9 +233,9 @@ var _ = Describe("Handler Util Test", func() { }, { name: "TPLScriptTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: "test", }, }}, @@ -240,8 +246,8 @@ var _ = Describe("Handler Util Test", func() { }, { name: "autoTriggerTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{ + reloadAction: ¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{ ProcessName: "test", }}, }, @@ -249,8 +255,8 @@ var _ = Describe("Handler Util Test", func() { }, { name: "autoTriggerTest", args: args{ - reloadAction: &appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{}}, + reloadAction: ¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{}}, }, wantErr: false, }} @@ -264,34 +270,31 @@ var _ = Describe("Handler Util Test", func() { Context("TestGetSupportReloadConfigSpecs", func() { It("not support reload", func() { - configSpecs, err := GetSupportReloadConfigSpecs([]appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - }}}, nil, nil) + configSpecs, err := GetSupportReloadConfigSpecs([]appsv1.ComponentTemplateSpec{{ + Name: "test", + }}, nil, nil) Expect(err).Should(Succeed()) Expect(len(configSpecs)).Should(BeEquivalentTo(0)) }) - It("not ConfigConstraint ", func() { - configSpecs, err := GetSupportReloadConfigSpecs([]appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - TemplateRef: "config_template", - Namespace: "default", - }}}, nil, nil) + It("not ComponentConfigDescription", func() { + configSpecs, err := GetSupportReloadConfigSpecs([]appsv1.ComponentTemplateSpec{{ + Name: "test", + TemplateRef: "config_template", + Namespace: "default", + }}, nil, []*parametersv1alpha1.ParametersDefinition{mockParametersDef("test", nil)}) Expect(err).Should(Succeed()) Expect(len(configSpecs)).Should(BeEquivalentTo(0)) }) - It("not support reload", func() { + It("not support reload for paramsDef", func() { ccName := "config_constraint" - mockK8sCli.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - mockConfigConstraint(ccName, nil), - }), testutil.WithTimes(1))) - + configtpl := mockConfigSpec(ccName) configSpecs, err := GetSupportReloadConfigSpecs( - []appsv1.ComponentConfigSpec{mockConfigSpec(ccName)}, - mockK8sCli.Client(), ctx) + []appsv1.ComponentTemplateSpec{configtpl}, + mockConfigDescription(configtpl.Name, parametersv1alpha1.Ini), + nil, + ) Expect(err).Should(Succeed()) Expect(len(configSpecs)).Should(BeEquivalentTo(0)) @@ -299,74 +302,55 @@ var _ = Describe("Handler Util Test", func() { It("normal test", func() { ccName := "config_constraint" - cc := mockConfigConstraint(ccName, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + pd := mockParametersDef(ccName, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: "test", - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, }, }) - mockK8sCli.MockGetMethod(testutil.WithGetReturned( - testutil.WithConstructSimpleGetResult([]client.Object{cc}), - testutil.WithTimes(1))) + cd := mockConfigDescription(ccName, parametersv1alpha1.Ini) configSpecs, err := GetSupportReloadConfigSpecs( - []appsv1.ComponentConfigSpec{mockConfigSpec(ccName)}, - mockK8sCli.Client(), ctx) + []appsv1.ComponentTemplateSpec{mockConfigSpec(ccName)}, + cd, + []*parametersv1alpha1.ParametersDefinition{pd}, + ) Expect(err).Should(Succeed()) Expect(len(configSpecs)).Should(BeEquivalentTo(1)) Expect(configSpecs[0].ConfigSpec).Should(BeEquivalentTo(mockConfigSpec(ccName))) - Expect(configSpecs[0].ReloadType).Should(BeEquivalentTo(appsv1beta1.UnixSignalType)) - Expect(configSpecs[0].FormatterConfig).Should(BeEquivalentTo(*cc.Spec.FileFormatConfig)) - }) - - It("auto trigger test", func() { - ccName := "auto_trigger_config_constraint" - cc := mockConfigConstraint(ccName, &appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{ - ProcessName: "test", - }, - }) - mockK8sCli.MockGetMethod(testutil.WithGetReturned( - testutil.WithConstructSimpleGetResult([]client.Object{cc}), - testutil.WithTimes(1))) - - configSpecs, err := GetSupportReloadConfigSpecs( - []appsv1.ComponentConfigSpec{mockConfigSpec(ccName)}, - mockK8sCli.Client(), ctx) - - Expect(err).Should(Succeed()) - Expect(len(configSpecs)).Should(BeEquivalentTo(0)) + Expect(configSpecs[0].ReloadType).Should(BeEquivalentTo(parametersv1alpha1.UnixSignalType)) + Expect(&configSpecs[0].FormatterConfig).Should(BeEquivalentTo(cd[0].FileFormatConfig)) }) }) Context("TestFromReloadTypeConfig", func() { It("TestSignalTrigger", func() { - Expect(appsv1beta1.UnixSignalType).Should(BeEquivalentTo(FromReloadTypeConfig(&appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + Expect(parametersv1alpha1.UnixSignalType).Should(BeEquivalentTo(FromReloadTypeConfig(¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: "test", - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, }}))) }) It("TestAutoTrigger", func() { - Expect(appsv1beta1.AutoType).Should(BeEquivalentTo(FromReloadTypeConfig(&appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{ + Expect(parametersv1alpha1.AutoType).Should(BeEquivalentTo(FromReloadTypeConfig(¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{ ProcessName: "test", }}))) }) It("TestShellTrigger", func() { - Expect(appsv1beta1.ShellType).Should(BeEquivalentTo(FromReloadTypeConfig(&appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + Expect(parametersv1alpha1.ShellType).Should(BeEquivalentTo(FromReloadTypeConfig(¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"/bin/true"}, }}))) }) It("TestTplScriptsTrigger", func() { - Expect(appsv1beta1.TPLScriptType).Should(BeEquivalentTo(FromReloadTypeConfig(&appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + Expect(parametersv1alpha1.TPLScriptType).Should(BeEquivalentTo(FromReloadTypeConfig(¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: "test", Namespace: "default", }, @@ -374,31 +358,31 @@ var _ = Describe("Handler Util Test", func() { }) It("TestInvalidTrigger", func() { - Expect("").Should(BeEquivalentTo(FromReloadTypeConfig(&appsv1beta1.ReloadAction{}))) + Expect("").Should(BeEquivalentTo(FromReloadTypeConfig(¶metersv1alpha1.ReloadAction{}))) }) }) Context("TestValidateReloadOptions", func() { It("TestSignalTrigger", func() { - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{ ProcessName: "test", - Signal: appsv1beta1.SIGHUP, + Signal: parametersv1alpha1.SIGHUP, }}, nil, nil), ).Should(Succeed()) }) It("TestSignalTrigger", func() { - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{ - AutoTrigger: &appsv1beta1.AutoTrigger{ + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{ + AutoTrigger: ¶metersv1alpha1.AutoTrigger{ ProcessName: "test", }}, nil, nil), ).Should(Succeed()) }) It("TestShellTrigger", func() { - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Command: []string{"/bin/true"}, }}, nil, nil), ).Should(Succeed()) @@ -418,9 +402,9 @@ var _ = Describe("Handler Util Test", func() { }), testutil.WithTimes(2))) By("Test valid") - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: testName1, Namespace: ns, }, @@ -428,9 +412,9 @@ var _ = Describe("Handler Util Test", func() { ).Should(Succeed()) By("Test invalid") - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ - ScriptConfig: appsv1beta1.ScriptConfig{ + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ + ScriptConfig: parametersv1alpha1.ScriptConfig{ ScriptConfigMapRef: testName2, Namespace: ns, }, @@ -439,20 +423,19 @@ var _ = Describe("Handler Util Test", func() { }) It("TestInvalidTrigger", func() { - Expect(ValidateReloadOptions(&appsv1beta1.ReloadAction{}, nil, nil)).ShouldNot(Succeed()) + Expect(ValidateReloadOptions(¶metersv1alpha1.ReloadAction{}, nil, nil)).ShouldNot(Succeed()) }) }) }) func TestFilterSubPathVolumeMount(t *testing.T) { - createConfigMeta := func(volumeName string, reloadType appsv1beta1.DynamicReloadType, reloadAction *appsv1beta1.ReloadAction) ConfigSpecMeta { + createConfigMeta := func(volumeName string, reloadType parametersv1alpha1.DynamicReloadType, reloadAction *parametersv1alpha1.ReloadAction) ConfigSpecMeta { return ConfigSpecMeta{ConfigSpecInfo: ConfigSpecInfo{ ReloadAction: reloadAction, ReloadType: reloadType, - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - VolumeName: volumeName, - }}}} + ConfigSpec: appsv1.ComponentTemplateSpec{ + VolumeName: volumeName, + }}} } type args struct { @@ -467,16 +450,16 @@ func TestFilterSubPathVolumeMount(t *testing.T) { name: "test1", args: args{ metas: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Sync: cfgutil.ToPointer(true), }, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ Sync: cfgutil.ToPointer(true), }, }), @@ -488,13 +471,13 @@ func TestFilterSubPathVolumeMount(t *testing.T) { }, }, want: []ConfigSpecMeta{ - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Sync: cfgutil.ToPointer(true), }, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ Sync: cfgutil.ToPointer(true), }, }), @@ -503,14 +486,14 @@ func TestFilterSubPathVolumeMount(t *testing.T) { name: "test2", args: args{ metas: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{}, + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{}, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{}, + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{}, }), }, volumes: []corev1.VolumeMount{ @@ -520,57 +503,57 @@ func TestFilterSubPathVolumeMount(t *testing.T) { }, }, want: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{}, + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{}, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{}, + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{}, }), }, }, { name: "test3", args: args{ metas: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{}, + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{}, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{}, + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{}, }), }, volumes: []corev1.VolumeMount{}, }, want: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{}, + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{}, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{}, + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{}, }), }, }, { name: "test4", args: args{ metas: []ConfigSpecMeta{ - createConfigMeta("test1", appsv1beta1.UnixSignalType, &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{}, + createConfigMeta("test1", parametersv1alpha1.UnixSignalType, ¶metersv1alpha1.ReloadAction{ + UnixSignalTrigger: ¶metersv1alpha1.UnixSignalTrigger{}, }), - createConfigMeta("test2", appsv1beta1.ShellType, &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ + createConfigMeta("test2", parametersv1alpha1.ShellType, ¶metersv1alpha1.ReloadAction{ + ShellTrigger: ¶metersv1alpha1.ShellTrigger{ Sync: cfgutil.ToPointer(false), }, }), - createConfigMeta("test3", appsv1beta1.TPLScriptType, &appsv1beta1.ReloadAction{ - TPLScriptTrigger: &appsv1beta1.TPLScriptTrigger{ + createConfigMeta("test3", parametersv1alpha1.TPLScriptType, ¶metersv1alpha1.ReloadAction{ + TPLScriptTrigger: ¶metersv1alpha1.TPLScriptTrigger{ Sync: cfgutil.ToPointer(false), }, }), diff --git a/pkg/configuration/config_manager/proc_util.go b/pkg/configuration/config_manager/proc_util.go index ec972dd0f72..69391b0a5ea 100644 --- a/pkg/configuration/config_manager/proc_util.go +++ b/pkg/configuration/config_manager/proc_util.go @@ -25,7 +25,7 @@ import ( "github.com/shirou/gopsutil/v3/process" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" ) @@ -71,7 +71,7 @@ func findParentPIDByProcessName(processName string, ctx ...context.Context) (PID return InvalidPID, cfgcore.MakeError("cannot find pid of process name: [%s]", processName) } -func IsValidUnixSignal(sig appsv1beta1.SignalType) bool { +func IsValidUnixSignal(sig parametersv1alpha1.SignalType) bool { _, ok := allUnixSignals[sig] return ok } diff --git a/pkg/configuration/config_manager/reload_util.go b/pkg/configuration/config_manager/reload_util.go index 83f718532be..a6e163b1520 100644 --- a/pkg/configuration/config_manager/reload_util.go +++ b/pkg/configuration/config_manager/reload_util.go @@ -31,7 +31,7 @@ import ( "strings" "text/template/parse" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/gotemplate" @@ -51,7 +51,7 @@ const ( // for testing var newCommandChannel = NewCommandChannel -func OnlineUpdateParamsHandle(tplScriptPath string, formatConfig *appsv1beta1.FileFormatConfig, dataType, dsn string) (DynamicUpdater, error) { +func OnlineUpdateParamsHandle(tplScriptPath string, formatConfig *parametersv1alpha1.FileFormatConfig, dataType, dsn string) (DynamicUpdater, error) { tplContent, err := os.ReadFile(tplScriptPath) if err != nil { return nil, err @@ -81,7 +81,7 @@ func checkTPLScript(tplName string, tplContent string) error { return err } -func wrapGoTemplateRun(ctx context.Context, tplScriptPath string, tplContent string, updatedParams map[string]string, formatConfig *appsv1beta1.FileFormatConfig, dataType string, dsn string) error { +func wrapGoTemplateRun(ctx context.Context, tplScriptPath string, tplContent string, updatedParams map[string]string, formatConfig *parametersv1alpha1.FileFormatConfig, dataType string, dsn string) error { var ( err error commandChannel DynamicParamUpdater @@ -103,7 +103,7 @@ func wrapGoTemplateRun(ctx context.Context, tplScriptPath string, tplContent str return err } -func constructReloadBuiltinFuncs(ctx context.Context, cc DynamicParamUpdater, formatConfig *appsv1beta1.FileFormatConfig) *gotemplate.BuiltInObjectsFunc { +func constructReloadBuiltinFuncs(ctx context.Context, cc DynamicParamUpdater, formatConfig *parametersv1alpha1.FileFormatConfig) *gotemplate.BuiltInObjectsFunc { return &gotemplate.BuiltInObjectsFunc{ builtInExecFunctionName: func(command string, args ...string) (string, error) { execCommand := exec.CommandContext(ctx, command, args...) @@ -137,7 +137,7 @@ func constructReloadBuiltinFuncs(ctx context.Context, cc DynamicParamUpdater, fo } } -func createUpdatedParamsPatch(newVersion []string, oldVersion []string, formatCfg *appsv1beta1.FileFormatConfig) (map[string]string, error) { +func createUpdatedParamsPatch(newVersion []string, oldVersion []string, formatCfg *parametersv1alpha1.FileFormatConfig) (map[string]string, error) { patchOption := core.CfgOption{ Type: core.CfgTplType, CfgType: formatCfg.Format, @@ -158,7 +158,7 @@ func createUpdatedParamsPatch(newVersion []string, oldVersion []string, formatCf return nil, err } - params := core.GenerateVisualizedParamsList(patch, formatCfg, nil) + params := core.GenerateVisualizedParamsList(patch, core.ToV1ConfigDescription(newVersion, formatCfg)) r := make(map[string]string) for _, key := range params { if key.UpdateType != core.DeletedType { @@ -317,10 +317,7 @@ func copyFileContents(src, dst string) error { func NeedSharedProcessNamespace(configSpecs []ConfigSpecMeta) bool { for _, configSpec := range configSpecs { - if configSpec.ConfigSpec.ConfigConstraintRef == "" { - continue - } - if configSpec.ReloadType == appsv1beta1.UnixSignalType { + if configSpec.ReloadType == parametersv1alpha1.UnixSignalType { return true } } diff --git a/pkg/configuration/config_manager/reload_util_test.go b/pkg/configuration/config_manager/reload_util_test.go index 8e1bda2f461..dc41e17292f 100644 --- a/pkg/configuration/config_manager/reload_util_test.go +++ b/pkg/configuration/config_manager/reload_util_test.go @@ -38,7 +38,7 @@ import ( "go.uber.org/zap" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/gotemplate" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" ) @@ -53,7 +53,7 @@ func TestCreateUpdatedParamsPatch(t *testing.T) { type args struct { newVersion string oldVersion string - formatCfg *appsv1beta1.FileFormatConfig + formatCfg *parametersv1alpha1.FileFormatConfig } tests := []struct { name string @@ -65,9 +65,9 @@ func TestCreateUpdatedParamsPatch(t *testing.T) { args: args{ newVersion: filepath.Join(rootPath, "currentVersion"), oldVersion: filepath.Join(rootPath, "lastVersion"), - formatCfg: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{IniConfig: &appsv1beta1.IniConfig{ + formatCfg: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "mysqld", }}, }}, @@ -139,7 +139,7 @@ func TestOnlineUpdateParamsHandle(t *testing.T) { type args struct { tplScriptPath string - formatConfig *appsv1beta1.FileFormatConfig + formatConfig *parametersv1alpha1.FileFormatConfig dataType string dsn string } @@ -152,8 +152,8 @@ func TestOnlineUpdateParamsHandle(t *testing.T) { name: "online_update_params_handle_test", args: args{ tplScriptPath: filepath.Join(tmpTestData, partroniPath), - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, }, dsn: server.URL, dataType: "patroni", @@ -238,13 +238,13 @@ var _ = Describe("ReloadUtil Test", func() { AfterEach(func() { }) - createIniFormatter := func(sectionName string) *appsv1beta1.FileFormatConfig { - return &appsv1beta1.FileFormatConfig{ - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ + createIniFormatter := func(sectionName string) *parametersv1alpha1.FileFormatConfig { + return ¶metersv1alpha1.FileFormatConfig{ + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: sectionName, }}, - Format: appsv1beta1.Ini, + Format: parametersv1alpha1.Ini, } } @@ -335,11 +335,10 @@ var _ = Describe("ReloadUtil Test", func() { name: "test2", args: []ConfigSpecMeta{{ ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - TemplateRef: "test_cm", - }}, + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "test", + TemplateRef: "test_cm", + }, }}, }, want: false, @@ -347,50 +346,38 @@ var _ = Describe("ReloadUtil Test", func() { name: "test3", args: []ConfigSpecMeta{{ ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - TemplateRef: "test_cm", - }, - ConfigConstraintRef: "cc2", + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "test", + TemplateRef: "test_cm", }, - ReloadType: appsv1beta1.ShellType, + ReloadType: parametersv1alpha1.ShellType, }, }, { ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test2", - TemplateRef: "test_cm", - }, - ConfigConstraintRef: "cc3", + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "test2", + TemplateRef: "test_cm", }, - ReloadType: appsv1beta1.TPLScriptType, + ReloadType: parametersv1alpha1.TPLScriptType, }}}, want: false, }, { name: "test4", args: []ConfigSpecMeta{{ ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - TemplateRef: "test_cm", - }, - ConfigConstraintRef: "cc1", + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "test", + TemplateRef: "test_cm", }, - ReloadType: appsv1beta1.UnixSignalType, + ReloadType: parametersv1alpha1.UnixSignalType, }, }, { ConfigSpecInfo: ConfigSpecInfo{ - ConfigSpec: appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test2", - TemplateRef: "test_cm", - }, - ConfigConstraintRef: "cc3", + ConfigSpec: appsv1.ComponentTemplateSpec{ + Name: "test2", + TemplateRef: "test_cm", }, - ReloadType: appsv1beta1.TPLScriptType, + ReloadType: parametersv1alpha1.TPLScriptType, }}}, want: true, }} diff --git a/pkg/configuration/config_manager/signal_darwin.go b/pkg/configuration/config_manager/signal_darwin.go index bfe8c48463d..3ac5cdfddbb 100644 --- a/pkg/configuration/config_manager/signal_darwin.go +++ b/pkg/configuration/config_manager/signal_darwin.go @@ -25,39 +25,39 @@ import ( "os" "syscall" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) -var allUnixSignals = map[appsv1beta1.SignalType]os.Signal{ - appsv1beta1.SIGHUP: syscall.SIGHUP, // reload signal for mysql 8.x.xxx - appsv1beta1.SIGINT: syscall.SIGINT, - appsv1beta1.SIGQUIT: syscall.SIGQUIT, - appsv1beta1.SIGILL: syscall.SIGILL, - appsv1beta1.SIGTRAP: syscall.SIGTRAP, - appsv1beta1.SIGABRT: syscall.SIGABRT, - appsv1beta1.SIGBUS: syscall.SIGBUS, - appsv1beta1.SIGFPE: syscall.SIGFPE, - appsv1beta1.SIGKILL: syscall.SIGKILL, - appsv1beta1.SIGUSR1: syscall.SIGUSR1, - appsv1beta1.SIGSEGV: syscall.SIGSEGV, - appsv1beta1.SIGUSR2: syscall.SIGUSR2, - appsv1beta1.SIGPIPE: syscall.SIGPIPE, - appsv1beta1.SIGALRM: syscall.SIGALRM, - appsv1beta1.SIGTERM: syscall.SIGTERM, - // appsv1beta1.SIGSTKFLT: syscall.SIGSTKFLT, - appsv1beta1.SIGCHLD: syscall.SIGCHLD, - appsv1beta1.SIGCONT: syscall.SIGCONT, - appsv1beta1.SIGSTOP: syscall.SIGSTOP, - appsv1beta1.SIGTSTP: syscall.SIGTSTP, - appsv1beta1.SIGTTIN: syscall.SIGTTIN, - appsv1beta1.SIGTTOU: syscall.SIGTTOU, - appsv1beta1.SIGURG: syscall.SIGURG, - appsv1beta1.SIGXCPU: syscall.SIGXCPU, - appsv1beta1.SIGXFSZ: syscall.SIGXFSZ, - appsv1beta1.SIGVTALRM: syscall.SIGVTALRM, - appsv1beta1.SIGPROF: syscall.SIGPROF, - appsv1beta1.SIGWINCH: syscall.SIGWINCH, - appsv1beta1.SIGIO: syscall.SIGIO, - // appsv1beta1.SIGPWR: syscall.SIGPWR, - appsv1beta1.SIGSYS: syscall.SIGSYS, +var allUnixSignals = map[parametersv1alpha1.SignalType]os.Signal{ + parametersv1alpha1.SIGHUP: syscall.SIGHUP, // reload signal for mysql 8.x.xxx + parametersv1alpha1.SIGINT: syscall.SIGINT, + parametersv1alpha1.SIGQUIT: syscall.SIGQUIT, + parametersv1alpha1.SIGILL: syscall.SIGILL, + parametersv1alpha1.SIGTRAP: syscall.SIGTRAP, + parametersv1alpha1.SIGABRT: syscall.SIGABRT, + parametersv1alpha1.SIGBUS: syscall.SIGBUS, + parametersv1alpha1.SIGFPE: syscall.SIGFPE, + parametersv1alpha1.SIGKILL: syscall.SIGKILL, + parametersv1alpha1.SIGUSR1: syscall.SIGUSR1, + parametersv1alpha1.SIGSEGV: syscall.SIGSEGV, + parametersv1alpha1.SIGUSR2: syscall.SIGUSR2, + parametersv1alpha1.SIGPIPE: syscall.SIGPIPE, + parametersv1alpha1.SIGALRM: syscall.SIGALRM, + parametersv1alpha1.SIGTERM: syscall.SIGTERM, + // parametersv1alpha1.SIGSTKFLT: syscall.SIGSTKFLT, + parametersv1alpha1.SIGCHLD: syscall.SIGCHLD, + parametersv1alpha1.SIGCONT: syscall.SIGCONT, + parametersv1alpha1.SIGSTOP: syscall.SIGSTOP, + parametersv1alpha1.SIGTSTP: syscall.SIGTSTP, + parametersv1alpha1.SIGTTIN: syscall.SIGTTIN, + parametersv1alpha1.SIGTTOU: syscall.SIGTTOU, + parametersv1alpha1.SIGURG: syscall.SIGURG, + parametersv1alpha1.SIGXCPU: syscall.SIGXCPU, + parametersv1alpha1.SIGXFSZ: syscall.SIGXFSZ, + parametersv1alpha1.SIGVTALRM: syscall.SIGVTALRM, + parametersv1alpha1.SIGPROF: syscall.SIGPROF, + parametersv1alpha1.SIGWINCH: syscall.SIGWINCH, + parametersv1alpha1.SIGIO: syscall.SIGIO, + // parametersv1alpha1.SIGPWR: syscall.SIGPWR, + parametersv1alpha1.SIGSYS: syscall.SIGSYS, } diff --git a/pkg/configuration/config_manager/signal_linux.go b/pkg/configuration/config_manager/signal_linux.go index df1ad1da5b1..62e6124a44a 100644 --- a/pkg/configuration/config_manager/signal_linux.go +++ b/pkg/configuration/config_manager/signal_linux.go @@ -25,39 +25,39 @@ import ( "os" "syscall" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) -var allUnixSignals = map[appsv1beta1.SignalType]os.Signal{ - appsv1beta1.SIGHUP: syscall.SIGHUP, // reload signal for mysql 8.x.xxx - appsv1beta1.SIGINT: syscall.SIGINT, - appsv1beta1.SIGQUIT: syscall.SIGQUIT, - appsv1beta1.SIGILL: syscall.SIGILL, - appsv1beta1.SIGTRAP: syscall.SIGTRAP, - appsv1beta1.SIGABRT: syscall.SIGABRT, - appsv1beta1.SIGBUS: syscall.SIGBUS, - appsv1beta1.SIGFPE: syscall.SIGFPE, - appsv1beta1.SIGKILL: syscall.SIGKILL, - appsv1beta1.SIGUSR1: syscall.SIGUSR1, - appsv1beta1.SIGSEGV: syscall.SIGSEGV, - appsv1beta1.SIGUSR2: syscall.SIGUSR2, - appsv1beta1.SIGPIPE: syscall.SIGPIPE, - appsv1beta1.SIGALRM: syscall.SIGALRM, - appsv1beta1.SIGTERM: syscall.SIGTERM, - appsv1beta1.SIGSTKFLT: syscall.SIGSTKFLT, - appsv1beta1.SIGCHLD: syscall.SIGCHLD, - appsv1beta1.SIGCONT: syscall.SIGCONT, - appsv1beta1.SIGSTOP: syscall.SIGSTOP, - appsv1beta1.SIGTSTP: syscall.SIGTSTP, - appsv1beta1.SIGTTIN: syscall.SIGTTIN, - appsv1beta1.SIGTTOU: syscall.SIGTTOU, - appsv1beta1.SIGURG: syscall.SIGURG, - appsv1beta1.SIGXCPU: syscall.SIGXCPU, - appsv1beta1.SIGXFSZ: syscall.SIGXFSZ, - appsv1beta1.SIGVTALRM: syscall.SIGVTALRM, - appsv1beta1.SIGPROF: syscall.SIGPROF, - appsv1beta1.SIGWINCH: syscall.SIGWINCH, - appsv1beta1.SIGIO: syscall.SIGIO, - appsv1beta1.SIGPWR: syscall.SIGPWR, - appsv1beta1.SIGSYS: syscall.SIGSYS, +var allUnixSignals = map[parametersv1alpha1.SignalType]os.Signal{ + parametersv1alpha1.SIGHUP: syscall.SIGHUP, // reload signal for mysql 8.x.xxx + parametersv1alpha1.SIGINT: syscall.SIGINT, + parametersv1alpha1.SIGQUIT: syscall.SIGQUIT, + parametersv1alpha1.SIGILL: syscall.SIGILL, + parametersv1alpha1.SIGTRAP: syscall.SIGTRAP, + parametersv1alpha1.SIGABRT: syscall.SIGABRT, + parametersv1alpha1.SIGBUS: syscall.SIGBUS, + parametersv1alpha1.SIGFPE: syscall.SIGFPE, + parametersv1alpha1.SIGKILL: syscall.SIGKILL, + parametersv1alpha1.SIGUSR1: syscall.SIGUSR1, + parametersv1alpha1.SIGSEGV: syscall.SIGSEGV, + parametersv1alpha1.SIGUSR2: syscall.SIGUSR2, + parametersv1alpha1.SIGPIPE: syscall.SIGPIPE, + parametersv1alpha1.SIGALRM: syscall.SIGALRM, + parametersv1alpha1.SIGTERM: syscall.SIGTERM, + parametersv1alpha1.SIGSTKFLT: syscall.SIGSTKFLT, + parametersv1alpha1.SIGCHLD: syscall.SIGCHLD, + parametersv1alpha1.SIGCONT: syscall.SIGCONT, + parametersv1alpha1.SIGSTOP: syscall.SIGSTOP, + parametersv1alpha1.SIGTSTP: syscall.SIGTSTP, + parametersv1alpha1.SIGTTIN: syscall.SIGTTIN, + parametersv1alpha1.SIGTTOU: syscall.SIGTTOU, + parametersv1alpha1.SIGURG: syscall.SIGURG, + parametersv1alpha1.SIGXCPU: syscall.SIGXCPU, + parametersv1alpha1.SIGXFSZ: syscall.SIGXFSZ, + parametersv1alpha1.SIGVTALRM: syscall.SIGVTALRM, + parametersv1alpha1.SIGPROF: syscall.SIGPROF, + parametersv1alpha1.SIGWINCH: syscall.SIGWINCH, + parametersv1alpha1.SIGIO: syscall.SIGIO, + parametersv1alpha1.SIGPWR: syscall.SIGPWR, + parametersv1alpha1.SIGSYS: syscall.SIGSYS, } diff --git a/pkg/configuration/config_manager/type.go b/pkg/configuration/config_manager/type.go index 7ef426b01b6..ef054bd8379 100644 --- a/pkg/configuration/config_manager/type.go +++ b/pkg/configuration/config_manager/type.go @@ -25,7 +25,7 @@ import ( "github.com/fsnotify/fsnotify" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type ConfigHandler interface { @@ -35,13 +35,14 @@ type ConfigHandler interface { } type ConfigSpecInfo struct { - *appsv1beta1.ReloadAction `json:",inline"` + *parametersv1alpha1.ReloadAction `json:",inline"` - ReloadType appsv1beta1.DynamicReloadType `json:"reloadType"` - ConfigSpec appsv1.ComponentConfigSpec `json:"configSpec"` - FormatterConfig appsv1beta1.FileFormatConfig `json:"formatterConfig"` + ReloadType parametersv1alpha1.DynamicReloadType `json:"reloadType"` + ConfigSpec appsv1.ComponentTemplateSpec `json:"configSpec"` + FormatterConfig parametersv1alpha1.FileFormatConfig `json:"formatterConfig"` + ConfigFile string `json:"configFile"` - DownwardAPIOptions []appsv1beta1.DownwardAPIChangeTriggeredAction `json:"downwardAPIOptions"` + DownwardAPIOptions []parametersv1alpha1.DownwardAPIChangeTriggeredAction `json:"downwardAPIOptions"` // config volume mount path MountPoint string `json:"mountPoint"` @@ -51,8 +52,8 @@ type ConfigSpecInfo struct { type ConfigSpecMeta struct { ConfigSpecInfo `json:",inline"` - ScriptConfig []appsv1beta1.ScriptConfig - ToolsImageSpec *appsv1beta1.ToolsSetup + ScriptConfig []parametersv1alpha1.ScriptConfig + ToolsImageSpec *parametersv1alpha1.ToolsSetup } type TPLScriptConfig struct { @@ -61,13 +62,5 @@ type TPLScriptConfig struct { DataType string `json:"dataType"` DSN string `json:"dsn"` - FormatterConfig appsv1beta1.FileFormatConfig `json:"formatterConfig"` -} - -type ConfigLazyRenderedMeta struct { - *appsv1.ComponentConfigSpec `json:",inline"` - - // secondary template path - Templates []string `json:"templates"` - FormatterConfig appsv1beta1.FileFormatConfig `json:"formatterConfig"` + FormatterConfig parametersv1alpha1.FileFormatConfig `json:"formatterConfig"` } diff --git a/pkg/configuration/core/config.go b/pkg/configuration/core/config.go index 7cf55abd4ff..8f13166381c 100644 --- a/pkg/configuration/core/config.go +++ b/pkg/configuration/core/config.go @@ -27,7 +27,7 @@ import ( "github.com/StudioSol/set" "github.com/spf13/cast" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/unstructured" ) @@ -108,15 +108,30 @@ func init() { indexer: make(map[string]unstructured.ConfigObject, 1), } + configType := func(fileName string) parametersv1alpha1.CfgFileFormat { + fileType := option.CfgType + if option.FileFormatFn == nil { + return fileType + } + if fileConfig := option.FileFormatFn(fileName); fileConfig != nil { + fileType = fileConfig.Format + } + return fileType + } + var err error var index = 0 var v unstructured.ConfigObject + var format parametersv1alpha1.CfgFileFormat for fileName, content := range ctx.ConfigData { if ctx.CMKeys != nil && !ctx.CMKeys.InArray(fileName) { continue } - if v, err = unstructured.LoadConfig(fileName, content, option.CfgType); err != nil { - return nil, WrapError(err, "failed to load config: filename[%s], type[%s]", fileName, option.CfgType) + if format = configType(fileName); format == "" { + continue + } + if v, err = unstructured.LoadConfig(fileName, content, format); err != nil { + return nil, WrapError(err, "failed to load config: filename[%s], type[%s]", fileName, format) } meta.indexer[fileName] = v meta.v[index] = v @@ -229,9 +244,9 @@ func NewCfgOptions(filename string, options ...Option) CfgOpOption { return context } -func WithFormatterConfig(formatConfig *appsv1beta1.FileFormatConfig) Option { +func WithFormatterConfig(formatConfig *parametersv1alpha1.FileFormatConfig) Option { return func(ctx *CfgOpOption) { - if formatConfig.Format == appsv1beta1.Ini && formatConfig.IniConfig != nil { + if formatConfig.Format == parametersv1alpha1.Ini && formatConfig.IniConfig != nil { ctx.IniContext = &IniContext{ SectionName: formatConfig.IniConfig.SectionName, } @@ -239,8 +254,8 @@ func WithFormatterConfig(formatConfig *appsv1beta1.FileFormatConfig) Option { } } -func NestedPrefixField(formatConfig *appsv1beta1.FileFormatConfig) string { - if formatConfig != nil && formatConfig.Format == appsv1beta1.Ini && formatConfig.IniConfig != nil { +func NestedPrefixField(formatConfig *parametersv1alpha1.FileFormatConfig) string { + if formatConfig != nil && formatConfig.Format == parametersv1alpha1.Ini && formatConfig.IniConfig != nil { return formatConfig.IniConfig.SectionName } return "" @@ -304,25 +319,31 @@ func FromCMKeysSelector(keys []string) *set.LinkedHashSetString { return cmKeySet } -func GenerateVisualizedParamsList(configPatch *ConfigPatchInfo, formatConfig *appsv1beta1.FileFormatConfig, sets *set.LinkedHashSetString) []VisualizedParam { +func GenerateVisualizedParamsList(configPatch *ConfigPatchInfo, configDescs []parametersv1alpha1.ComponentConfigDescription) []VisualizedParam { if !configPatch.IsModify { return nil } - var trimPrefix = NestedPrefixField(formatConfig) + sets := NewConfigFileFilter(configDescs) + resolveParameterPrefix := func(file string) string { + fileConfig := ResolveConfigFormat(configDescs, file) + if fileConfig == nil { + return "" + } + return NestedPrefixField(fileConfig) + } r := make([]VisualizedParam, 0) - r = append(r, generateUpdateParam(configPatch.UpdateConfig, trimPrefix, sets)...) - r = append(r, generateUpdateKeyParam(configPatch.AddConfig, trimPrefix, AddedType, sets)...) - r = append(r, generateUpdateKeyParam(configPatch.DeleteConfig, trimPrefix, DeletedType, sets)...) + r = append(r, generateUpdateParam(configPatch.UpdateConfig, resolveParameterPrefix, sets)...) + r = append(r, generateUpdateKeyParam(configPatch.AddConfig, resolveParameterPrefix, AddedType, sets)...) + r = append(r, generateUpdateKeyParam(configPatch.DeleteConfig, resolveParameterPrefix, DeletedType, sets)...) return r } -func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, sets *set.LinkedHashSetString) []VisualizedParam { +func generateUpdateParam(updatedParams map[string][]byte, trimPrefix func(string) string, sets *set.LinkedHashSetString) []VisualizedParam { r := make([]VisualizedParam, 0, len(updatedParams)) for key, b := range updatedParams { - // TODO support keys if sets != nil && sets.Length() > 0 && !sets.InArray(key) { continue } @@ -330,7 +351,7 @@ func generateUpdateParam(updatedParams map[string][]byte, trimPrefix string, set if err := json.Unmarshal(b, &v); err != nil { return nil } - if params := checkAndFlattenMap(v, trimPrefix); params != nil { + if params := checkAndFlattenMap(v, trimPrefix(key)); params != nil { r = append(r, VisualizedParam{ Key: key, Parameters: params, @@ -382,14 +403,14 @@ func flattenMap(m map[string]interface{}, prefix string) []ParameterPair { return r } -func generateUpdateKeyParam(files map[string]interface{}, trimPrefix string, updatedType ParameterUpdateType, sets *set.LinkedHashSetString) []VisualizedParam { +func generateUpdateKeyParam(files map[string]interface{}, trimPrefix func(string) string, updatedType ParameterUpdateType, sets *set.LinkedHashSetString) []VisualizedParam { r := make([]VisualizedParam, 0, len(files)) for key, params := range files { if sets != nil && sets.Length() > 0 && !sets.InArray(key) { continue } - if params := checkAndFlattenMap(params, trimPrefix); params != nil { + if params := checkAndFlattenMap(params, trimPrefix(key)); params != nil { r = append(r, VisualizedParam{ Key: key, Parameters: params, diff --git a/pkg/configuration/core/config_patch.go b/pkg/configuration/core/config_patch.go index e633c4d93f0..a6ba0c1260f 100644 --- a/pkg/configuration/core/config_patch.go +++ b/pkg/configuration/core/config_patch.go @@ -20,7 +20,7 @@ along with this program. If not, see . package core import ( - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" ) @@ -90,7 +90,7 @@ func difference(base *cfgWrapper, target *cfgWrapper) (*ConfigPatchInfo, error) return reconfigureInfo, nil } -func TransformConfigPatchFromData(data map[string]string, format appsv1beta1.CfgFileFormat, keys []string) (*ConfigPatchInfo, error) { +func TransformConfigPatchFromData(data map[string]string, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec) (*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, format appsv1beta1.Cfg } return r } - patch, _, err := CreateConfigPatch(emptyData(data), data, format, keys, false) + patch, _, err := CreateConfigPatch(emptyData(data), data, configRender, false) return patch, err } diff --git a/pkg/configuration/core/config_patch_test.go b/pkg/configuration/core/config_patch_test.go index 21ea978dc65..a482978291c 100644 --- a/pkg/configuration/core/config_patch_test.go +++ b/pkg/configuration/core/config_patch_test.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/yaml" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" ) @@ -37,7 +37,7 @@ func TestConfigPatch(t *testing.T) { cfg, err := NewConfigLoader(CfgOption{ Type: CfgRawType, Log: log.FromContext(context.Background()), - CfgType: appsv1beta1.Ini, + CfgType: parametersv1alpha1.Ini, RawData: []byte(iniConfig), }) @@ -113,7 +113,7 @@ net: patchOption := CfgOption{ Type: CfgTplType, - CfgType: appsv1beta1.YAML, + CfgType: parametersv1alpha1.YAML, } patch, err := CreateMergePatch(&ConfigResource{ConfigData: map[string]string{"test": ""}}, &ConfigResource{ConfigData: map[string]string{"test": yamlContext}}, patchOption) require.Nil(t, err) @@ -130,7 +130,11 @@ 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}, appsv1beta1.Ini, nil) + got, err := TransformConfigPatchFromData(map[string]string{configFile: testData}, parametersv1alpha1.ParameterDrivenConfigRenderSpec{ + Configs: []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/configuration/core/config_patch_util.go b/pkg/configuration/core/config_patch_util.go index df69bcdb067..6c1089c1b5b 100644 --- a/pkg/configuration/core/config_patch_util.go +++ b/pkg/configuration/core/config_patch_util.go @@ -21,31 +21,33 @@ package core import ( "context" + "fmt" "github.com/StudioSol/set" "sigs.k8s.io/controller-runtime/pkg/log" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/unstructured" ) // CreateConfigPatch creates a patch for configuration files with different version. -func CreateConfigPatch(oldVersion, newVersion map[string]string, format appsv1beta1.CfgFileFormat, keys []string, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { +func CreateConfigPatch(oldVersion, newVersion map[string]string, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { var hasFilesUpdated = false + var keys = ResolveConfigFiles(configRender.Configs) if comparableAllFiles && len(keys) > 0 { hasFilesUpdated = checkExcludeConfigDifference(oldVersion, newVersion, keys) } - cmKeySet := FromCMKeysSelector(keys) + cmKeyFilter := NewConfigFileFilter(configRender.Configs) patch, err := CreateMergePatch( - FromConfigData(oldVersion, cmKeySet), - FromConfigData(newVersion, cmKeySet), + FromConfigData(oldVersion, cmKeyFilter), + FromConfigData(newVersion, cmKeyFilter), CfgOption{ - CfgType: format, - Type: CfgTplType, - Log: log.FromContext(context.TODO()), + FileFormatFn: WithConfigFileFormat(configRender.Configs), + Type: CfgTplType, + Log: log.FromContext(context.TODO()), }) return patch, hasFilesUpdated, err } @@ -67,7 +69,7 @@ func checkExcludeConfigDifference(oldVersion map[string]string, newVersion map[s return false } -func LoadRawConfigObject(data map[string]string, formatConfig *appsv1beta1.FileFormatConfig, keys []string) (map[string]unstructured.ConfigObject, error) { +func LoadRawConfigObject(data map[string]string, formatConfig *parametersv1alpha1.FileFormatConfig, keys []string) (map[string]unstructured.ConfigObject, error) { r := make(map[string]unstructured.ConfigObject) cmKeySet := FromCMKeysSelector(keys) for key, val := range data { @@ -83,7 +85,7 @@ func LoadRawConfigObject(data map[string]string, formatConfig *appsv1beta1.FileF return r, nil } -func FromConfigObject(name, config string, formatConfig *appsv1beta1.FileFormatConfig) (unstructured.ConfigObject, error) { +func FromConfigObject(name, config string, formatConfig *parametersv1alpha1.FileFormatConfig) (unstructured.ConfigObject, error) { configObject, err := unstructured.LoadConfig(name, config, formatConfig.Format) if err != nil { return nil, err @@ -97,19 +99,23 @@ func FromConfigObject(name, config string, formatConfig *appsv1beta1.FileFormatC // 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, formatterConfig *appsv1beta1.FileFormatConfig, configData []byte) (map[string]string, error) { +func TransformConfigFileToKeyValueMap(fileName string, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec, configData []byte) (map[string]string, error) { + formatterConfig := ResolveConfigFormat(configRender.Configs, fileName) + if formatterConfig == nil { + return nil, fmt.Errorf("not found file formatter config: [%s]", fileName) + } + oldData := map[string]string{ fileName: "", } newData := map[string]string{ fileName: string(configData), } - keys := []string{fileName} - patchInfo, _, err := CreateConfigPatch(oldData, newData, formatterConfig.Format, keys, false) + patchInfo, _, err := CreateConfigPatch(oldData, newData, configRender, false) if err != nil { return nil, err } - params := GenerateVisualizedParamsList(patchInfo, formatterConfig, nil) + params := GenerateVisualizedParamsList(patchInfo, configRender.Configs) result := make(map[string]string) for _, param := range params { if param.Key != fileName { @@ -123,3 +129,30 @@ func TransformConfigFileToKeyValueMap(fileName string, formatterConfig *appsv1be } return result, nil } + +func ResolveConfigFormat(descriptions []parametersv1alpha1.ComponentConfigDescription, file string) *parametersv1alpha1.FileFormatConfig { + for _, config := range descriptions { + if config.Name == file { + return config.FileFormatConfig + } + } + return nil +} + +func WithConfigFileFormat(descriptions []parametersv1alpha1.ComponentConfigDescription) func(file string) *parametersv1alpha1.FileFormatConfig { + return func(file string) *parametersv1alpha1.FileFormatConfig { + return ResolveConfigFormat(descriptions, file) + } +} + +func ResolveConfigFiles(descriptions []parametersv1alpha1.ComponentConfigDescription) []string { + var keys []string + for _, config := range descriptions { + keys = append(keys, config.Name) + } + return keys +} + +func NewConfigFileFilter(descriptions []parametersv1alpha1.ComponentConfigDescription) *util.Sets { + return util.NewSet(ResolveConfigFiles(descriptions)...) +} diff --git a/pkg/configuration/core/config_patch_util_test.go b/pkg/configuration/core/config_patch_util_test.go index e6d50786f6a..f1e6f4fd98f 100644 --- a/pkg/configuration/core/config_patch_util_test.go +++ b/pkg/configuration/core/config_patch_util_test.go @@ -20,10 +20,10 @@ along with this program. If not, see . package core import ( - "reflect" + "slices" "testing" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/test/testdata" ) @@ -196,7 +196,7 @@ max_connections=666 type args struct { oldVersion map[string]string newVersion map[string]string - format appsv1beta1.CfgFileFormat + format parametersv1alpha1.CfgFileFormat keys []string enableExcludeDiff bool } @@ -215,7 +215,7 @@ max_connections=666 newVersion: map[string]string{ "my.cnf": v2, }, - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, enableExcludeDiff: true, }, want: &ConfigPatchInfo{IsModify: true}, @@ -231,7 +231,7 @@ max_connections=666 "my.cnf": v2, "other.cnf": "context", }, - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, enableExcludeDiff: true, }, want: &ConfigPatchInfo{IsModify: true}, @@ -249,7 +249,7 @@ max_connections=666 "other.cnf": "context", }, keys: []string{"my.cnf"}, - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, enableExcludeDiff: true, }, want: &ConfigPatchInfo{IsModify: true}, @@ -266,7 +266,7 @@ max_connections=666 "other.cnf": "context difference", }, keys: []string{"my.cnf"}, - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, enableExcludeDiff: true, }, want: &ConfigPatchInfo{IsModify: false}, @@ -283,7 +283,7 @@ max_connections=666 "other.cnf": "context difference", }, keys: []string{"my.cnf"}, - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, enableExcludeDiff: false, }, want: &ConfigPatchInfo{IsModify: true}, @@ -291,7 +291,19 @@ max_connections=666 }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, tt.args.format, tt.args.keys, tt.args.enableExcludeDiff) + var configs []parametersv1alpha1.ComponentConfigDescription + for k := range tt.args.oldVersion { + if len(tt.args.keys) == 0 || slices.Contains(tt.args.keys, k) { + configs = append(configs, parametersv1alpha1.ComponentConfigDescription{ + Name: k, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: tt.args.format, + }, + }) + } + } + configRender := parametersv1alpha1.ParameterDrivenConfigRenderSpec{Configs: configs} + got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, configRender, tt.args.enableExcludeDiff) if (err != nil) != tt.wantErr { t.Errorf("CreateConfigPatch() error = %v, wantErr %v", err, tt.wantErr) return @@ -314,7 +326,7 @@ func TestLoadRawConfigObject(t *testing.T) { type args struct { data map[string]string - formatConfig *appsv1beta1.FileFormatConfig + formatConfig *parametersv1alpha1.FileFormatConfig keys []string } tests := []struct { @@ -325,10 +337,10 @@ func TestLoadRawConfigObject(t *testing.T) { name: "test", args: args{ data: map[string]string{"key": getFileContentFn("cue_testdata/mysql.cnf")}, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{ + IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "mysqld", }}, }}, @@ -337,8 +349,8 @@ func TestLoadRawConfigObject(t *testing.T) { name: "test", args: args{ data: map[string]string{"key": getFileContentFn("cue_testdata/pg14.conf")}, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, }}, wantErr: false, }, { @@ -349,8 +361,8 @@ func TestLoadRawConfigObject(t *testing.T) { "key2": getFileContentFn("cue_testdata/mysql.cnf"), }, keys: []string{"key"}, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Properties, }}, wantErr: false, }, { @@ -360,8 +372,8 @@ func TestLoadRawConfigObject(t *testing.T) { "key": getFileContentFn("cue_testdata/pg14.conf"), }, keys: []string{"key"}, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.XML, + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.XML, }}, wantErr: true, }} @@ -376,65 +388,65 @@ func TestLoadRawConfigObject(t *testing.T) { } } -func TestTransformConfigFileToKeyValueMap(t *testing.T) { - mysqlConfig := ` -[mysqld] -key_buffer_size=16777216 -log_error=/data/mysql/logs/mysql.log -` - mongodbConfig := ` -systemLog: - logRotate: reopen - path: /data/mongodb/logs/mongodb.log - verbosity: 0 -` - tests := []struct { - name string - fileName string - formatConfig *appsv1beta1.FileFormatConfig - configData []byte - expected map[string]string - }{{ - name: "mysql-test", - fileName: "my.cnf", - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ - SectionName: "mysqld", - }, - }, - }, - configData: []byte(mysqlConfig), - expected: map[string]string{ - "key_buffer_size": "16777216", - "log_error": "/data/mysql/logs/mysql.log", - }, - }, { - name: "mongodb-test", - fileName: "mongodb.conf", - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.YAML, - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ - SectionName: "default", - }, - }, - }, - configData: []byte(mongodbConfig), - expected: map[string]string{ - "systemLog.logRotate": "reopen", - "systemLog.path": "/data/mongodb/logs/mongodb.log", - "systemLog.verbosity": "0", - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, _ := TransformConfigFileToKeyValueMap(tt.fileName, tt.formatConfig, tt.configData) - if !reflect.DeepEqual(res, tt.expected) { - t.Errorf("TransformConfigFileToKeyValueMap() res = %v, res %v", res, tt.expected) - return - } - }) - } -} +// func TestTransformConfigFileToKeyValueMap(t *testing.T) { +// mysqlConfig := ` +// [mysqld] +// key_buffer_size=16777216 +// log_error=/data/mysql/logs/mysql.log +// ` +// mongodbConfig := ` +// systemLog: +// logRotate: reopen +// path: /data/mongodb/logs/mongodb.log +// verbosity: 0 +// ` +// tests := []struct { +// name string +// fileName string +// formatConfig *parametersv1alpha1.FileFormatConfig +// configData []byte +// expected map[string]string +// }{{ +// name: "mysql-test", +// fileName: "my.cnf", +// formatConfig: ¶metersv1alpha1.FileFormatConfig{ +// Format: parametersv1alpha1.Ini, +// FormatterAction: parametersv1alpha1.FormatterAction{ +// IniConfig: ¶metersv1alpha1.IniConfig{ +// SectionName: "mysqld", +// }, +// }, +// }, +// configData: []byte(mysqlConfig), +// expected: map[string]string{ +// "key_buffer_size": "16777216", +// "log_error": "/data/mysql/logs/mysql.log", +// }, +// }, { +// name: "mongodb-test", +// fileName: "mongodb.conf", +// formatConfig: ¶metersv1alpha1.FileFormatConfig{ +// Format: parametersv1alpha1.YAML, +// FormatterAction: parametersv1alpha1.FormatterAction{ +// IniConfig: ¶metersv1alpha1.IniConfig{ +// SectionName: "default", +// }, +// }, +// }, +// configData: []byte(mongodbConfig), +// expected: map[string]string{ +// "systemLog.logRotate": "reopen", +// "systemLog.path": "/data/mongodb/logs/mongodb.log", +// "systemLog.verbosity": "0", +// }, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// res, _ := TransformConfigFileToKeyValueMap(tt.fileName, tt.formatConfig, tt.configData) +// if !reflect.DeepEqual(res, tt.expected) { +// t.Errorf("TransformConfigFileToKeyValueMap() res = %v, res %v", res, tt.expected) +// return +// } +// }) +// } +// } diff --git a/pkg/configuration/core/config_query.go b/pkg/configuration/core/config_query.go index 46f3369c9a6..a9762cff971 100644 --- a/pkg/configuration/core/config_query.go +++ b/pkg/configuration/core/config_query.go @@ -25,7 +25,7 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/log" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) // GetParameterFromConfiguration gets configure parameter @@ -42,7 +42,7 @@ func GetParameterFromConfiguration(configMap *corev1.ConfigMap, allFiles bool, f wrapCfg, err := NewConfigLoader(CfgOption{ Type: CfgCmType, Log: log.FromContext(context.Background()), - CfgType: appsv1beta1.Ini, + CfgType: parametersv1alpha1.Ini, ConfigResource: FromConfigData(configMap.Data, nil), }) if err != nil { diff --git a/pkg/configuration/core/config_test.go b/pkg/configuration/core/config_test.go index fb77c15ee0d..0635a373b73 100644 --- a/pkg/configuration/core/config_test.go +++ b/pkg/configuration/core/config_test.go @@ -26,13 +26,12 @@ import ( "strings" "testing" - "github.com/StudioSol/set" "github.com/bhmj/jsonslice" "github.com/stretchr/testify/require" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" ) @@ -72,7 +71,7 @@ func TestConfigMapConfig(t *testing.T) { cfg, err := NewConfigLoader(CfgOption{ Type: CfgCmType, Log: log.FromContext(context.Background()), - CfgType: appsv1beta1.Ini, + CfgType: parametersv1alpha1.Ini, ConfigResource: &ConfigResource{ CfgKey: client.ObjectKey{ Name: "xxxx", // set cm name @@ -153,8 +152,7 @@ func TestConfigMapConfig(t *testing.T) { func TestGenerateVisualizedParamsList(t *testing.T) { type args struct { configPatch *ConfigPatchInfo - formatConfig *appsv1beta1.FileFormatConfig - sets *set.LinkedHashSetString + formatConfig *parametersv1alpha1.FileFormatConfig } var ( @@ -204,9 +202,9 @@ func TestGenerateVisualizedParamsList(t *testing.T) { configPatch: &ConfigPatchInfo{ IsModify: true, UpdateConfig: map[string][]byte{"key": testUpdatedParams}}, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{IniConfig: &appsv1beta1.IniConfig{ + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "mysqld", }}, }, @@ -233,9 +231,9 @@ func TestGenerateVisualizedParamsList(t *testing.T) { IsModify: true, AddConfig: map[string]interface{}{"key": testJSON}, }, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{IniConfig: &appsv1beta1.IniConfig{ + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "mysqld", }}, }, @@ -259,9 +257,9 @@ func TestGenerateVisualizedParamsList(t *testing.T) { IsModify: true, DeleteConfig: map[string]interface{}{"key": testJSON}, }, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{IniConfig: &appsv1beta1.IniConfig{ + formatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + FormatterAction: parametersv1alpha1.FormatterAction{IniConfig: ¶metersv1alpha1.IniConfig{ SectionName: "mysqld", }}, }, @@ -281,7 +279,7 @@ func TestGenerateVisualizedParamsList(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := GenerateVisualizedParamsList(tt.args.configPatch, tt.args.formatConfig, tt.args.sets) + got := GenerateVisualizedParamsList(tt.args.configPatch, ToV1ConfigDescription(resolveKey(tt.args.configPatch), tt.args.formatConfig)) sortParams(got) sortParams(tt.want) require.Equal(t, got, tt.want) @@ -289,6 +287,20 @@ func TestGenerateVisualizedParamsList(t *testing.T) { } } +func resolveKey(patch *ConfigPatchInfo) []string { + var keys []string + if len(patch.AddConfig) != 0 { + keys = append(keys, util.ToSet(patch.AddConfig).AsSlice()...) + } + if len(patch.UpdateConfig) != 0 { + keys = append(keys, util.ToSet(patch.UpdateConfig).AsSlice()...) + } + if len(patch.DeleteConfig) != 0 { + keys = append(keys, util.ToSet(patch.DeleteConfig).AsSlice()...) + } + return keys +} + func sortParams(param []VisualizedParam) { for _, v := range param { sort.SliceStable(v.Parameters, func(i, j int) bool { diff --git a/pkg/configuration/core/config_util.go b/pkg/configuration/core/config_util.go index 54cc6dfceac..00b8d3c66ff 100644 --- a/pkg/configuration/core/config_util.go +++ b/pkg/configuration/core/config_util.go @@ -21,15 +21,14 @@ package core import ( "context" + "path/filepath" "regexp" "strings" "github.com/spf13/cast" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" ) @@ -96,7 +95,7 @@ func FromStringPointerMap(m map[string]string) map[string]*string { return r } -func ApplyConfigPatch(baseCfg []byte, updatedParameters map[string]*string, formatConfig *appsv1beta1.FileFormatConfig) (string, error) { +func ApplyConfigPatch(baseCfg []byte, updatedParameters map[string]*string, formatConfig *parametersv1alpha1.FileFormatConfig) (string, error) { configLoaderOption := CfgOption{ Type: CfgRawType, Log: log.FromContext(context.TODO()), @@ -117,41 +116,27 @@ func ApplyConfigPatch(baseCfg []byte, updatedParameters map[string]*string, form return mergedConfig.Marshal() } -func NeedReloadVolume(config appsv1.ComponentConfigSpec) bool { - // TODO distinguish between scripts and configuration - return config.ConfigConstraintRef != "" -} - -func GetReloadOptions(cli client.Client, ctx context.Context, configSpecs []appsv1.ComponentConfigSpec) (*appsv1beta1.ReloadAction, *appsv1beta1.FileFormatConfig, error) { - for _, configSpec := range configSpecs { - if !NeedReloadVolume(configSpec) { - continue - } - ccKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - cfgConst := &appsv1beta1.ConfigConstraint{} - if err := cli.Get(ctx, ccKey, cfgConst); err != nil { - return nil, nil, WrapError(err, "failed to get ConfigConstraint, key[%v]", ccKey) - } - if cfgConst.Spec.ReloadAction != nil { - return cfgConst.Spec.ReloadAction, cfgConst.Spec.FileFormatConfig, nil - } - } - return nil, nil, nil -} - -func IsWatchModuleForShellTrigger(trigger *appsv1beta1.ShellTrigger) bool { +func IsWatchModuleForShellTrigger(trigger *parametersv1alpha1.ShellTrigger) bool { if trigger == nil || trigger.Sync == nil { return true } return !*trigger.Sync } -func IsWatchModuleForTplTrigger(trigger *appsv1beta1.TPLScriptTrigger) bool { +func IsWatchModuleForTplTrigger(trigger *parametersv1alpha1.TPLScriptTrigger) bool { if trigger == nil || trigger.Sync == nil { return true } return !*trigger.Sync } + +func ToV1ConfigDescription(keys []string, format *parametersv1alpha1.FileFormatConfig) []parametersv1alpha1.ComponentConfigDescription { + var configs []parametersv1alpha1.ComponentConfigDescription + for _, key := range keys { + configs = append(configs, parametersv1alpha1.ComponentConfigDescription{ + Name: filepath.Base(key), + FileFormatConfig: format, + }) + } + return configs +} diff --git a/pkg/configuration/core/config_util_test.go b/pkg/configuration/core/config_util_test.go index dc5674bec33..b21d46f13ee 100644 --- a/pkg/configuration/core/config_util_test.go +++ b/pkg/configuration/core/config_util_test.go @@ -19,333 +19,333 @@ along with this program. If not, see . package core -import ( - "reflect" - "strings" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" -) - -var _ = Describe("config_util", func() { - - var k8sMockClient *testutil.K8sClientMockHelper - - BeforeEach(func() { - // Add any setup steps that needs to be executed before each test - k8sMockClient = testutil.NewK8sMockClient() - }) - - AfterEach(func() { - // Add any teardown steps that needs to be executed after each test - k8sMockClient.Finish() - }) - - Context("common funcs test", func() { - It("GetReloadOptions Should success without error", func() { - mockTpl := appsv1beta1.ConfigConstraint{ - Spec: appsv1beta1.ConfigConstraintSpec{ - ReloadAction: &appsv1beta1.ReloadAction{ - UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ - Signal: "HUB", - ProcessName: "for_test", - }, - }, - }, - } - tests := []struct { - name string - tpls []appsv1.ComponentConfigSpec - want *appsv1beta1.ReloadAction - wantErr bool - }{{ - // empty config templates - name: "test", - tpls: nil, - want: nil, - wantErr: false, - }, { - // empty config templates - name: "test", - tpls: []appsv1.ComponentConfigSpec{}, - want: nil, - wantErr: false, - }, { - // config templates without configConstraintObj - name: "test", - tpls: []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test", - }, - }, { - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test2", - }, - }}, - want: nil, - wantErr: false, - }, { - // normal - name: "test", - tpls: []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test", - }, - ConfigConstraintRef: "eg_v1", - }}, - want: mockTpl.Spec.ReloadAction, - wantErr: false, - }, { - // not exist config constraint - name: "test", - tpls: []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test", - }, - ConfigConstraintRef: "not_exist", - }}, - want: nil, - wantErr: true, - }} - - k8sMockClient.MockGetMethod(testutil.WithGetReturned(func(key client.ObjectKey, obj client.Object) error { - if strings.Contains(key.Name, "not_exist") { - return MakeError("not exist config!") - } - testutil.SetGetReturnedObject(obj, &mockTpl) - return nil - }, testutil.WithMaxTimes(len(tests)))) - - for _, tt := range tests { - got, _, err := GetReloadOptions(k8sMockClient.Client(), ctx, tt.tpls) - Expect(err != nil).Should(BeEquivalentTo(tt.wantErr)) - Expect(reflect.DeepEqual(got, tt.want)).Should(BeTrue()) - } - }) - }) - -}) - -func TestMergeUpdatedConfig(t *testing.T) { - type args struct { - baseMap map[string]string - updatedMap map[string]string - } - tests := []struct { - name string - args args - want map[string]string - }{{ - name: "normal_test", - args: args{ - baseMap: map[string]string{ - "key1": "context1", - "key2": "context2", - "key3": "context3", - }, - updatedMap: map[string]string{ - "key2": "new context", - }, - }, - want: map[string]string{ - "key1": "context1", - "key2": "new context", - "key3": "context3", - }, - }, { - name: "not_expected_update_test", - args: args{ - baseMap: map[string]string{ - "key1": "context1", - "key2": "context2", - "key3": "context3", - }, - updatedMap: map[string]string{ - "key6": "context6", - }, - }, - want: map[string]string{ - "key1": "context1", - "key2": "context2", - "key3": "context3", - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := MergeUpdatedConfig(tt.args.baseMap, tt.args.updatedMap); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MergeUpdatedConfig() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestApplyConfigPatch(t *testing.T) { - type args struct { - baseCfg []byte - updatedParameters map[string]string - formatConfig *appsv1beta1.FileFormatConfig - } - tests := []struct { - name string - args args - want string - wantErr bool - }{{ - name: "normal_test", - args: args{ - baseCfg: []byte(`[test] -test=test`), - updatedParameters: map[string]string{ - "a": "b", - "max_connections": "600", - }, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ - SectionName: "test", - }}}, - }, - want: `[test] -a=b -max_connections=600 -test=test -`, - wantErr: false, - }, { - name: "normal_test", - args: args{ - baseCfg: []byte(` `), - updatedParameters: map[string]string{ - "a": "b", - "c": "d e f g", - }, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.RedisCfg, - }, - }, - want: "a b\nc d e f g", - wantErr: false, - }, { - name: "badcase_test", - args: args{ - baseCfg: []byte(` `), - updatedParameters: map[string]string{ - "ENABLE_MODULES": "true", - "HUGGINGFACE_APIKEY": "kssdlsdjskwssl", - }, - formatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Dotenv, - }, - }, - // fix begin - // ENABLE_MODULES=0x1400004f130 - // HUGGINGFACE_APIKEY=0x1400004f140 - want: "ENABLE_MODULES=true\nHUGGINGFACE_APIKEY=kssdlsdjskwssl\n", - wantErr: false, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ApplyConfigPatch(tt.args.baseCfg, FromStringPointerMap(tt.args.updatedParameters), tt.args.formatConfig) - if (err != nil) != tt.wantErr { - t.Errorf("ApplyConfigPatch() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ApplyConfigPatch() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFromValueToString(t *testing.T) { - type args struct { - val interface{} - } - tests := []struct { - name string - args args - want string - }{{ - name: "test", - args: args{ - val: "testTest", - }, - want: "testTest", - }, { - name: "test", - args: args{ - val: "", - }, - want: "", - }, { - name: "test", - args: args{ - val: nil, - }, - want: "", - }, { - name: "test", - args: args{ - val: "/abdet/sds", - }, - want: "", - }, { - name: "test", - args: args{ - val: "abdet/sds-", - }, - want: "", - }, { - name: "test", - args: args{ - val: "abcdASls-sda_102.382", - }, - want: "abcdASls-sda_102.382", - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := FromValueToString(tt.args.val); got != tt.want { - t.Errorf("FromValueToString() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFromStringMap(t *testing.T) { - type args struct { - m map[string]*string - } - tests := []struct { - name string - args args - want map[string]interface{} - }{{ - name: "test", - args: args{ - m: map[string]*string{ - "abcd": cfgutil.ToPointer("test"), - "null_field": nil, - }, - }, - want: map[string]interface{}{ - "abcd": "test", - "null_field": nil, - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := FromStringMap(tt.args.m); !reflect.DeepEqual(got, tt.want) { - t.Errorf("FromStringMap() = %v, want %v", got, tt.want) - } - }) - } -} +// import ( +// "reflect" +// "strings" +// "testing" +// +// . "github.com/onsi/ginkgo/v2" +// . "github.com/onsi/gomega" +// +// "sigs.k8s.io/controller-runtime/pkg/client" +// +// appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" +// appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" +// cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" +// testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" +// ) +// +// var _ = Describe("config_util", func() { +// +// var k8sMockClient *testutil.K8sClientMockHelper +// +// BeforeEach(func() { +// // Add any setup steps that needs to be executed before each test +// k8sMockClient = testutil.NewK8sMockClient() +// }) +// +// AfterEach(func() { +// // Add any teardown steps that needs to be executed after each test +// k8sMockClient.Finish() +// }) +// +// Context("common funcs test", func() { +// It("GetReloadOptions Should success without error", func() { +// mockTpl := appsv1beta1.ConfigConstraint{ +// Spec: appsv1beta1.ConfigConstraintSpec{ +// ReloadAction: &appsv1beta1.ReloadAction{ +// UnixSignalTrigger: &appsv1beta1.UnixSignalTrigger{ +// Signal: "HUB", +// ProcessName: "for_test", +// }, +// }, +// }, +// } +// tests := []struct { +// name string +// tpls []appsv1.ComponentConfigSpec +// want *appsv1beta1.ReloadAction +// wantErr bool +// }{{ +// // empty config templates +// name: "test", +// tpls: nil, +// want: nil, +// wantErr: false, +// }, { +// // empty config templates +// name: "test", +// tpls: []appsv1.ComponentConfigSpec{}, +// want: nil, +// wantErr: false, +// }, { +// // config templates without configConstraintObj +// name: "test", +// tpls: []appsv1.ComponentConfigSpec{{ +// ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ +// Name: "for_test", +// }, +// }, { +// ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ +// Name: "for_test2", +// }, +// }}, +// want: nil, +// wantErr: false, +// }, { +// // normal +// name: "test", +// tpls: []appsv1.ComponentConfigSpec{{ +// ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ +// Name: "for_test", +// }, +// ConfigConstraintRef: "eg_v1", +// }}, +// want: mockTpl.Spec.ReloadAction, +// wantErr: false, +// }, { +// // not exist config constraint +// name: "test", +// tpls: []appsv1.ComponentConfigSpec{{ +// ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ +// Name: "for_test", +// }, +// ConfigConstraintRef: "not_exist", +// }}, +// want: nil, +// wantErr: true, +// }} +// +// k8sMockClient.MockGetMethod(testutil.WithGetReturned(func(key client.ObjectKey, obj client.Object) error { +// if strings.Contains(key.Name, "not_exist") { +// return MakeError("not exist config!") +// } +// testutil.SetGetReturnedObject(obj, &mockTpl) +// return nil +// }, testutil.WithMaxTimes(len(tests)))) +// +// for _, tt := range tests { +// got, _, err := GetReloadOptions(k8sMockClient.Client(), ctx, tt.tpls) +// Expect(err != nil).Should(BeEquivalentTo(tt.wantErr)) +// Expect(reflect.DeepEqual(got, tt.want)).Should(BeTrue()) +// } +// }) +// }) +// +// }) +// +// func TestMergeUpdatedConfig(t *testing.T) { +// type args struct { +// baseMap map[string]string +// updatedMap map[string]string +// } +// tests := []struct { +// name string +// args args +// want map[string]string +// }{{ +// name: "normal_test", +// args: args{ +// baseMap: map[string]string{ +// "key1": "context1", +// "key2": "context2", +// "key3": "context3", +// }, +// updatedMap: map[string]string{ +// "key2": "new context", +// }, +// }, +// want: map[string]string{ +// "key1": "context1", +// "key2": "new context", +// "key3": "context3", +// }, +// }, { +// name: "not_expected_update_test", +// args: args{ +// baseMap: map[string]string{ +// "key1": "context1", +// "key2": "context2", +// "key3": "context3", +// }, +// updatedMap: map[string]string{ +// "key6": "context6", +// }, +// }, +// want: map[string]string{ +// "key1": "context1", +// "key2": "context2", +// "key3": "context3", +// }, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := MergeUpdatedConfig(tt.args.baseMap, tt.args.updatedMap); !reflect.DeepEqual(got, tt.want) { +// t.Errorf("MergeUpdatedConfig() = %v, want %v", got, tt.want) +// } +// }) +// } +// } +// +// func TestApplyConfigPatch(t *testing.T) { +// type args struct { +// baseCfg []byte +// updatedParameters map[string]string +// formatConfig *appsv1beta1.FileFormatConfig +// } +// tests := []struct { +// name string +// args args +// want string +// wantErr bool +// }{{ +// name: "normal_test", +// args: args{ +// baseCfg: []byte(`[test] +// test=test`), +// updatedParameters: map[string]string{ +// "a": "b", +// "max_connections": "600", +// }, +// formatConfig: &appsv1beta1.FileFormatConfig{ +// Format: appsv1beta1.Ini, +// FormatterAction: appsv1beta1.FormatterAction{ +// IniConfig: &appsv1beta1.IniConfig{ +// SectionName: "test", +// }}}, +// }, +// want: `[test] +// a=b +// max_connections=600 +// test=test +// `, +// wantErr: false, +// }, { +// name: "normal_test", +// args: args{ +// baseCfg: []byte(` `), +// updatedParameters: map[string]string{ +// "a": "b", +// "c": "d e f g", +// }, +// formatConfig: &appsv1beta1.FileFormatConfig{ +// Format: appsv1beta1.RedisCfg, +// }, +// }, +// want: "a b\nc d e f g", +// wantErr: false, +// }, { +// name: "badcase_test", +// args: args{ +// baseCfg: []byte(` `), +// updatedParameters: map[string]string{ +// "ENABLE_MODULES": "true", +// "HUGGINGFACE_APIKEY": "kssdlsdjskwssl", +// }, +// formatConfig: &appsv1beta1.FileFormatConfig{ +// Format: appsv1beta1.Dotenv, +// }, +// }, +// // fix begin +// // ENABLE_MODULES=0x1400004f130 +// // HUGGINGFACE_APIKEY=0x1400004f140 +// want: "ENABLE_MODULES=true\nHUGGINGFACE_APIKEY=kssdlsdjskwssl\n", +// wantErr: false, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// got, err := ApplyConfigPatch(tt.args.baseCfg, FromStringPointerMap(tt.args.updatedParameters), tt.args.formatConfig) +// if (err != nil) != tt.wantErr { +// t.Errorf("ApplyConfigPatch() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if got != tt.want { +// t.Errorf("ApplyConfigPatch() got = %v, want %v", got, tt.want) +// } +// }) +// } +// } +// +// func TestFromValueToString(t *testing.T) { +// type args struct { +// val interface{} +// } +// tests := []struct { +// name string +// args args +// want string +// }{{ +// name: "test", +// args: args{ +// val: "testTest", +// }, +// want: "testTest", +// }, { +// name: "test", +// args: args{ +// val: "", +// }, +// want: "", +// }, { +// name: "test", +// args: args{ +// val: nil, +// }, +// want: "", +// }, { +// name: "test", +// args: args{ +// val: "/abdet/sds", +// }, +// want: "", +// }, { +// name: "test", +// args: args{ +// val: "abdet/sds-", +// }, +// want: "", +// }, { +// name: "test", +// args: args{ +// val: "abcdASls-sda_102.382", +// }, +// want: "abcdASls-sda_102.382", +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := FromValueToString(tt.args.val); got != tt.want { +// t.Errorf("FromValueToString() = %v, want %v", got, tt.want) +// } +// }) +// } +// } +// +// func TestFromStringMap(t *testing.T) { +// type args struct { +// m map[string]*string +// } +// tests := []struct { +// name string +// args args +// want map[string]interface{} +// }{{ +// name: "test", +// args: args{ +// m: map[string]*string{ +// "abcd": cfgutil.ToPointer("test"), +// "null_field": nil, +// }, +// }, +// want: map[string]interface{}{ +// "abcd": "test", +// "null_field": nil, +// }, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := FromStringMap(tt.args.m); !reflect.DeepEqual(got, tt.want) { +// t.Errorf("FromStringMap() = %v, want %v", got, tt.want) +// } +// }) +// } +// } diff --git a/pkg/configuration/core/reconfigure_util.go b/pkg/configuration/core/reconfigure_util.go index ae294f109e6..2b407417be0 100644 --- a/pkg/configuration/core/reconfigure_util.go +++ b/pkg/configuration/core/reconfigure_util.go @@ -28,12 +28,24 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" ) func getUpdateParameterList(cfg *ConfigPatchInfo, trimField string) ([]string, error) { + var params []string + for _, diff := range cfg.UpdateConfig { + r, err := resolveUpdateParameter(diff, trimField) + if err != nil { + return nil, err + } + params = append(params, r...) + } + return params, nil +} + +func resolveUpdateParameter(diff []byte, trimField string) ([]string, error) { params := make([]string, 0) walkFn := func(parent, cur string, v reflect.Value, fn util.UpdateFn) error { if cur != "" { @@ -45,18 +57,16 @@ func getUpdateParameterList(cfg *ConfigPatchInfo, trimField string) ([]string, e return nil } - for _, diff := range cfg.UpdateConfig { - var err error - var updatedParams any - if err = json.Unmarshal(diff, &updatedParams); err != nil { - return nil, err - } - if updatedParams, err = trimNestedField(updatedParams, trimField); err != nil { - return nil, err - } - if err := util.UnstructuredObjectWalk(updatedParams, walkFn, true); err != nil { - return nil, WrapError(err, "failed to walk params: [%s]", diff) - } + var err error + var updatedParams any + if err = json.Unmarshal(diff, &updatedParams); err != nil { + return nil, err + } + if updatedParams, err = trimNestedField(updatedParams, trimField); err != nil { + return nil, err + } + if err := util.UnstructuredObjectWalk(updatedParams, walkFn, true); err != nil { + return nil, WrapError(err, "failed to walk params: [%s]", diff) } return params, nil } @@ -78,12 +88,12 @@ func trimNestedField(updatedParams any, trimField string) (any, error) { } // ValidateConfigPatch Verifies if the changed parameters have been removed -func ValidateConfigPatch(patch *ConfigPatchInfo, formatCfg *appsv1beta1.FileFormatConfig) error { +func ValidateConfigPatch(patch *ConfigPatchInfo, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec) error { if !patch.IsModify || len(patch.UpdateConfig) == 0 { return nil } - vParams := GenerateVisualizedParamsList(patch, formatCfg, nil) + vParams := GenerateVisualizedParamsList(patch, configRender.Configs) for _, param := range vParams { for _, p := range param.Parameters { if p.Value == nil { @@ -95,12 +105,51 @@ func ValidateConfigPatch(patch *ConfigPatchInfo, formatCfg *appsv1beta1.FileForm } // IsUpdateDynamicParameters checks if the changed parameters require a restart -func IsUpdateDynamicParameters(cc *appsv1beta1.ConfigConstraintSpec, cfg *ConfigPatchInfo) (bool, error) { +func IsUpdateDynamicParameters(config *parametersv1alpha1.FileFormatConfig, paramsDef *parametersv1alpha1.ParametersDefinitionSpec, cfg *ConfigPatchInfo) (bool, error) { if len(cfg.DeleteConfig) > 0 || len(cfg.AddConfig) > 0 { return false, nil } - updatedParams, err := getUpdateParameterList(cfg, NestedPrefixField(cc.FileFormatConfig)) + updatedParams, err := getUpdateParameterList(cfg, NestedPrefixField(config)) + if err != nil { + return false, err + } + if len(updatedParams) == 0 { + return true, nil + } + updatedParamsSet := util.NewSet(updatedParams...) + + // if ConfigConstraint has StaticParameters, check updated parameter + if len(paramsDef.StaticParameters) > 0 { + staticParams := util.NewSet(paramsDef.StaticParameters...) + union := util.Union(staticParams, updatedParamsSet) + if union.Length() > 0 { + return false, nil + } + // if no dynamicParameters is configured, reload is the default behavior + if len(paramsDef.DynamicParameters) == 0 { + return true, nil + } + } + + // if ConfigConstraint has DynamicParameter, and all updated params are dynamic + if len(paramsDef.DynamicParameters) > 0 { + dynamicParams := util.NewSet(paramsDef.DynamicParameters...) + diff := util.Difference(updatedParamsSet, dynamicParams) + return diff.Length() == 0, nil + } + + // if the updated parameter is not in list of DynamicParameter, + // it is StaticParameter by default, and restart is the default behavior. + return false, nil +} + +func CheckUpdateDynamicParameters(config *parametersv1alpha1.FileFormatConfig, paramsDef *parametersv1alpha1.ParametersDefinitionSpec, patch string) (bool, error) { + if patch == "" { + return true, nil + } + + updatedParams, err := resolveUpdateParameter([]byte(patch), NestedPrefixField(config)) if err != nil { return false, err } @@ -110,21 +159,21 @@ func IsUpdateDynamicParameters(cc *appsv1beta1.ConfigConstraintSpec, cfg *Config updatedParamsSet := util.NewSet(updatedParams...) // if ConfigConstraint has StaticParameters, check updated parameter - if len(cc.StaticParameters) > 0 { - staticParams := util.NewSet(cc.StaticParameters...) + if len(paramsDef.StaticParameters) > 0 { + staticParams := util.NewSet(paramsDef.StaticParameters...) union := util.Union(staticParams, updatedParamsSet) if union.Length() > 0 { return false, nil } // if no dynamicParameters is configured, reload is the default behavior - if len(cc.DynamicParameters) == 0 { + if len(paramsDef.DynamicParameters) == 0 { return true, nil } } // if ConfigConstraint has DynamicParameter, and all updated params are dynamic - if len(cc.DynamicParameters) > 0 { - dynamicParams := util.NewSet(cc.DynamicParameters...) + if len(paramsDef.DynamicParameters) > 0 { + dynamicParams := util.NewSet(paramsDef.DynamicParameters...) diff := util.Difference(updatedParamsSet, dynamicParams) return diff.Length() == 0, nil } @@ -135,12 +184,12 @@ func IsUpdateDynamicParameters(cc *appsv1beta1.ConfigConstraintSpec, cfg *Config } // IsDynamicParameter checks if the parameter supports hot update -func IsDynamicParameter(paramName string, cc *appsv1beta1.ConfigConstraintSpec) bool { - if len(cc.DynamicParameters) != 0 { - return slices.Contains(cc.DynamicParameters, paramName) +func IsDynamicParameter(paramName string, paramsDef *parametersv1alpha1.ParametersDefinitionSpec) bool { + if len(paramsDef.DynamicParameters) != 0 { + return slices.Contains(paramsDef.DynamicParameters, paramName) } - if len(cc.StaticParameters) != 0 { - return !slices.Contains(cc.StaticParameters, paramName) + if len(paramsDef.StaticParameters) != 0 { + return !slices.Contains(paramsDef.StaticParameters, paramName) } return false } diff --git a/pkg/configuration/core/reconfigure_util_test.go b/pkg/configuration/core/reconfigure_util_test.go index 8cd97559ea3..45ea5b4b846 100644 --- a/pkg/configuration/core/reconfigure_util_test.go +++ b/pkg/configuration/core/reconfigure_util_test.go @@ -28,7 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" ) @@ -82,134 +82,134 @@ func newCfgDiffMeta(testData string, add, delete map[string]interface{}) *Config } } -func TestIsUpdateDynamicParameters(t *testing.T) { - type args struct { - ccSpec *appsv1beta1.ConfigConstraintSpec - diff *ConfigPatchInfo - } - tests := []struct { - name string - args args - want bool - wantErr bool - }{{ - name: "test", - // null - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{}, - diff: newCfgDiffMeta(`null`, nil, nil), - }, - want: true, - wantErr: false, - }, { - name: "test", - // error - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{}, - diff: newCfgDiffMeta(`invalid json formatter`, nil, nil), - }, - want: false, - wantErr: true, - }, { - name: "test", - // add/delete config file - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{}, - diff: newCfgDiffMeta(`{}`, map[string]interface{}{"a": "b"}, nil), - }, - want: false, - wantErr: false, - }, { - name: "test", - // not set static or dynamic parameters - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{}, - diff: newCfgDiffMeta(`{"a":"b"}`, nil, nil), - }, - want: false, - wantErr: false, - }, { - name: "test", - // static parameters contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - StaticParameters: []string{"param1", "param2", "param3"}, - }, - diff: newCfgDiffMeta(`{"param3":"b"}`, nil, nil), - }, - want: false, - wantErr: false, - }, { - name: "test", - // static parameters not contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - StaticParameters: []string{"param1", "param2", "param3"}, - }, - diff: newCfgDiffMeta(`{"param4":"b"}`, nil, nil), - }, - want: true, - wantErr: false, - }, { - name: "test", - // dynamic parameters contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - DynamicParameters: []string{"param1", "param2", "param3"}, - }, - diff: newCfgDiffMeta(`{"param1":"b", "param3": 20}`, nil, nil), - }, - want: true, - wantErr: false, - }, { - name: "test", - // dynamic parameters not contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - DynamicParameters: []string{"param1", "param2", "param3"}, - }, - diff: newCfgDiffMeta(`{"param1":"b", "param4": 20}`, nil, nil), - }, - want: false, - wantErr: false, - }, { - name: "test", - // dynamic/static parameters not contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - DynamicParameters: []string{"dparam1", "dparam2", "dparam3"}, - StaticParameters: []string{"sparam1", "sparam2", "sparam3"}, - }, - diff: newCfgDiffMeta(`{"a":"b"}`, nil, nil), - }, - want: false, - wantErr: false, - }, { - name: "empty-test", - // dynamic/static parameters not contains - args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ - DynamicParameters: []string{}, - StaticParameters: []string{}, - }, - diff: newCfgDiffMeta(`{}`, nil, nil), - }, - want: true, - wantErr: false, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := IsUpdateDynamicParameters(tt.args.ccSpec, tt.args.diff) - if (err != nil) != tt.wantErr { - t.Errorf("IsUpdateDynamicParameters() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("IsUpdateDynamicParameters() got = %v, want %v", got, tt.want) - } - }) - } -} +// func TestIsUpdateDynamicParameters(t *testing.T) { +// type args struct { +// ccSpec *parametersv1alpha1.ParametersDefinitionSpec +// diff *ConfigPatchInfo +// } +// tests := []struct { +// name string +// args args +// want bool +// wantErr bool +// }{{ +// name: "test", +// // null +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{}, +// diff: newCfgDiffMeta(`null`, nil, nil), +// }, +// want: true, +// wantErr: false, +// }, { +// name: "test", +// // error +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{}, +// diff: newCfgDiffMeta(`invalid json formatter`, nil, nil), +// }, +// want: false, +// wantErr: true, +// }, { +// name: "test", +// // add/delete config file +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{}, +// diff: newCfgDiffMeta(`{}`, map[string]interface{}{"a": "b"}, nil), +// }, +// want: false, +// wantErr: false, +// }, { +// name: "test", +// // not set static or dynamic parameters +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{}, +// diff: newCfgDiffMeta(`{"a":"b"}`, nil, nil), +// }, +// want: false, +// wantErr: false, +// }, { +// name: "test", +// // static parameters contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// StaticParameters: []string{"param1", "param2", "param3"}, +// }, +// diff: newCfgDiffMeta(`{"param3":"b"}`, nil, nil), +// }, +// want: false, +// wantErr: false, +// }, { +// name: "test", +// // static parameters not contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// StaticParameters: []string{"param1", "param2", "param3"}, +// }, +// diff: newCfgDiffMeta(`{"param4":"b"}`, nil, nil), +// }, +// want: true, +// wantErr: false, +// }, { +// name: "test", +// // dynamic parameters contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// DynamicParameters: []string{"param1", "param2", "param3"}, +// }, +// diff: newCfgDiffMeta(`{"param1":"b", "param3": 20}`, nil, nil), +// }, +// want: true, +// wantErr: false, +// }, { +// name: "test", +// // dynamic parameters not contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// DynamicParameters: []string{"param1", "param2", "param3"}, +// }, +// diff: newCfgDiffMeta(`{"param1":"b", "param4": 20}`, nil, nil), +// }, +// want: false, +// wantErr: false, +// }, { +// name: "test", +// // dynamic/static parameters not contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// DynamicParameters: []string{"dparam1", "dparam2", "dparam3"}, +// StaticParameters: []string{"sparam1", "sparam2", "sparam3"}, +// }, +// diff: newCfgDiffMeta(`{"a":"b"}`, nil, nil), +// }, +// want: false, +// wantErr: false, +// }, { +// name: "empty-test", +// // dynamic/static parameters not contains +// args: args{ +// ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ +// DynamicParameters: []string{}, +// StaticParameters: []string{}, +// }, +// diff: newCfgDiffMeta(`{}`, nil, nil), +// }, +// want: true, +// wantErr: false, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// got, err := IsUpdateDynamicParameters(tt.args.ccSpec, tt.args.diff) +// if (err != nil) != tt.wantErr { +// t.Errorf("IsUpdateDynamicParameters() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if got != tt.want { +// t.Errorf("IsUpdateDynamicParameters() got = %v, want %v", got, tt.want) +// } +// }) +// } +// } func TestIsSchedulableConfigResource(t *testing.T) { tests := []struct { @@ -285,60 +285,60 @@ func TestSetParametersUpdateSource(t *testing.T) { require.False(t, IsNotUserReconfigureOperation(cm)) } -func TestValidateConfigPatch(t *testing.T) { - type args struct { - patch *ConfigPatchInfo - formatCfg *appsv1beta1.FileFormatConfig - } - tests := []struct { - name string - args args - wantErr bool - }{{ - name: "test", - args: args{ - patch: &ConfigPatchInfo{}, - formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, - }, - wantErr: false, - }, { - name: "test", - args: args{ - patch: &ConfigPatchInfo{ - IsModify: true, - UpdateConfig: map[string][]byte{ - "file1": []byte(`{"a":"b"}`), - }, - }, - formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, - }, - wantErr: false, - }, { - name: "test-failed", - args: args{ - patch: &ConfigPatchInfo{ - IsModify: true, - UpdateConfig: map[string][]byte{ - "file1": []byte(`{"a":null}`), - }, - }, - formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, - }, - wantErr: true, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ValidateConfigPatch(tt.args.patch, tt.args.formatCfg); (err != nil) != tt.wantErr { - t.Errorf("ValidateConfigPatch() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} +// func TestValidateConfigPatch(t *testing.T) { +// type args struct { +// patch *ConfigPatchInfo +// formatCfg *appsv1beta1.FileFormatConfig +// } +// tests := []struct { +// name string +// args args +// wantErr bool +// }{{ +// name: "test", +// args: args{ +// patch: &ConfigPatchInfo{}, +// formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, +// }, +// wantErr: false, +// }, { +// name: "test", +// args: args{ +// patch: &ConfigPatchInfo{ +// IsModify: true, +// UpdateConfig: map[string][]byte{ +// "file1": []byte(`{"a":"b"}`), +// }, +// }, +// formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, +// }, +// wantErr: false, +// }, { +// name: "test-failed", +// args: args{ +// patch: &ConfigPatchInfo{ +// IsModify: true, +// UpdateConfig: map[string][]byte{ +// "file1": []byte(`{"a":null}`), +// }, +// }, +// formatCfg: &appsv1beta1.FileFormatConfig{Format: appsv1beta1.YAML}, +// }, +// wantErr: true, +// }} +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if err := ValidateConfigPatch(tt.args.patch, tt.args.formatCfg); (err != nil) != tt.wantErr { +// t.Errorf("ValidateConfigPatch() error = %v, wantErr %v", err, tt.wantErr) +// } +// }) +// } +// } func TestIsDynamicParameter(t *testing.T) { type args struct { paramName string - ccSpec *appsv1beta1.ConfigConstraintSpec + ccSpec *parametersv1alpha1.ParametersDefinitionSpec } tests := []struct { name string @@ -349,14 +349,14 @@ func TestIsDynamicParameter(t *testing.T) { // null args: args{ paramName: "test", - ccSpec: &appsv1beta1.ConfigConstraintSpec{}, + ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{}, }, want: false, }, { name: "test", // static parameters contains args: args{ - ccSpec: &appsv1beta1.ConfigConstraintSpec{ + ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ StaticParameters: []string{"param1", "param2", "param3"}, }, paramName: "param3", @@ -367,7 +367,7 @@ func TestIsDynamicParameter(t *testing.T) { // static parameters not contains args: args{ paramName: "param4", - ccSpec: &appsv1beta1.ConfigConstraintSpec{ + ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ StaticParameters: []string{"param1", "param2", "param3"}, }, }, @@ -377,7 +377,7 @@ func TestIsDynamicParameter(t *testing.T) { // dynamic parameters contains args: args{ paramName: "param1", - ccSpec: &appsv1beta1.ConfigConstraintSpec{ + ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ DynamicParameters: []string{"param1", "param2", "param3"}, }, }, @@ -387,7 +387,7 @@ func TestIsDynamicParameter(t *testing.T) { // dynamic/static parameters not contains args: args{ paramName: "test", - ccSpec: &appsv1beta1.ConfigConstraintSpec{ + ccSpec: ¶metersv1alpha1.ParametersDefinitionSpec{ DynamicParameters: []string{"dparam1", "dparam2", "dparam3"}, StaticParameters: []string{"sparam1", "sparam2", "sparam3"}, }, diff --git a/pkg/configuration/core/suite_test.go b/pkg/configuration/core/suite_test.go index 2512f445d58..a4205b562fd 100644 --- a/pkg/configuration/core/suite_test.go +++ b/pkg/configuration/core/suite_test.go @@ -36,7 +36,7 @@ import ( var ctx context.Context var cancel context.CancelFunc -func TestNetwork(t *testing.T) { +func TestConfigCore(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TPL Test Suite") diff --git a/pkg/configuration/core/type.go b/pkg/configuration/core/type.go index 795923b9216..212624e0f2b 100644 --- a/pkg/configuration/core/type.go +++ b/pkg/configuration/core/type.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" ) @@ -43,7 +43,7 @@ const ( type RawConfig struct { // formatter - Type appsv1beta1.CfgFileFormat + Type parametersv1alpha1.CfgFileFormat RawData string } @@ -124,7 +124,9 @@ type CfgOption struct { Log logr.Logger // formatter - CfgType appsv1beta1.CfgFileFormat + CfgType parametersv1alpha1.CfgFileFormat + + FileFormatFn func(file string) *parametersv1alpha1.FileFormatConfig // Path for CfgLocalType test Path string diff --git a/pkg/configuration/proto/reconfigure.pb.go b/pkg/configuration/proto/reconfigure.pb.go index 1bac00e7613..47f273cfd51 100644 --- a/pkg/configuration/proto/reconfigure.pb.go +++ b/pkg/configuration/proto/reconfigure.pb.go @@ -1,17 +1,16 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v3.21.9 +// protoc v4.24.3 // source: reconfigure.proto package proto import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( @@ -122,6 +121,7 @@ type OnlineUpgradeParamsRequest struct { ConfigSpec string `protobuf:"bytes,1,opt,name=configSpec,proto3" json:"configSpec,omitempty"` Params map[string]string `protobuf:"bytes,2,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ConfigFile *string `protobuf:"bytes,3,opt,name=configFile,proto3,oneof" json:"configFile,omitempty"` } func (x *OnlineUpgradeParamsRequest) Reset() { @@ -170,6 +170,13 @@ func (x *OnlineUpgradeParamsRequest) GetParams() map[string]string { return nil } +func (x *OnlineUpgradeParamsRequest) GetConfigFile() string { + if x != nil && x.ConfigFile != nil { + return *x.ConfigFile + } + return "" +} + type OnlineUpgradeParamsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -229,7 +236,7 @@ var file_reconfigure_proto_rawDesc = []byte{ 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0xbe, 0x01, 0x0a, 0x1a, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, + 0xf2, 0x01, 0x0a, 0x1a, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x70, 0x65, 0x63, 0x12, 0x45, @@ -237,31 +244,34 @@ var file_reconfigure_proto_rawDesc = []byte{ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x3d, 0x0a, 0x1b, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, - 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, - 0xbb, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, - 0x4c, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, - 0x13, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x6e, 0x6c, - 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3d, 0x5a, - 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x65, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, + 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x46, 0x69, 0x6c, 0x65, 0x22, 0x3d, 0x0a, 0x1b, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, + 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x32, 0xbb, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x6f, + 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x5e, 0x0a, 0x13, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, + 0x64, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, + 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x70, 0x65, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -352,6 +362,7 @@ func file_reconfigure_proto_init() { } } } + file_reconfigure_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pkg/configuration/proto/reconfigure.proto b/pkg/configuration/proto/reconfigure.proto index 43e1d2b8441..f949b84e5dd 100644 --- a/pkg/configuration/proto/reconfigure.proto +++ b/pkg/configuration/proto/reconfigure.proto @@ -21,6 +21,7 @@ message StopContainerResponse { message OnlineUpgradeParamsRequest { string configSpec = 1; map params = 2; + optional string configFile = 3; } message OnlineUpgradeParamsResponse { diff --git a/pkg/configuration/proto/reconfigure_grpc.pb.go b/pkg/configuration/proto/reconfigure_grpc.pb.go index a3371018a7c..59849da9075 100644 --- a/pkg/configuration/proto/reconfigure_grpc.pb.go +++ b/pkg/configuration/proto/reconfigure_grpc.pb.go @@ -1,14 +1,13 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.9 +// - protoc v4.24.3 // source: reconfigure.proto package proto import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" diff --git a/pkg/configuration/validate/config_validate.go b/pkg/configuration/validate/config_validate.go index 957b7a3effd..6d43cfb800f 100644 --- a/pkg/configuration/validate/config_validate.go +++ b/pkg/configuration/validate/config_validate.go @@ -20,90 +20,52 @@ along with this program. If not, see . package validate import ( - "github.com/StudioSol/set" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/kube-openapi/pkg/validation/errors" kubeopenapispec "k8s.io/kube-openapi/pkg/validation/spec" "k8s.io/kube-openapi/pkg/validation/strfmt" "k8s.io/kube-openapi/pkg/validation/validate" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" ) type ValidatorOptions = func(key string) bool type ConfigValidator interface { - Validate(data map[string]string) error -} - -type cmKeySelector struct { - // A ConfigMap object may contain multiple configuration files and only some of them can be parsed and verified by kubeblocks, - // such as postgresql, there are two files pg_hba.conf & postgresql.conf in the ConfigMap, and we can only validate postgresql.conf, - // so pg_hba.conf file needs to be ignored during when doing verification. - // keySelector filters the keys in the configmap. - keySelector []ValidatorOptions + Validate(data string) error } type configCueValidator struct { - cmKeySelector - // cue describes configuration template cueScript string - cfgType appsv1beta1.CfgFileFormat -} - -func (s *cmKeySelector) filter(key string) bool { - if len(s.keySelector) == 0 { - return false - } - - for _, option := range s.keySelector { - if !option(key) { - return true - } - } - return false + cfgType parametersv1alpha1.CfgFileFormat } -func (c *configCueValidator) Validate(data map[string]string) error { +func (c *configCueValidator) Validate(content string) error { if c.cueScript == "" { return nil } - for key, content := range data { - if c.filter(key) { - continue - } - if err := ValidateConfigurationWithCue(c.cueScript, c.cfgType, content); err != nil { - return err - } - } - return nil + return ValidateConfigWithCue(c.cueScript, c.cfgType, content) } type schemaValidator struct { - cmKeySelector - typeName string schema *apiext.JSONSchemaProps - cfgType appsv1beta1.CfgFileFormat + cfgType parametersv1alpha1.CfgFileFormat } -func (s *schemaValidator) Validate(data map[string]string) error { +func (s *schemaValidator) Validate(content string) error { + var err error + var parameters map[string]interface{} + openAPITypes := &kubeopenapispec.Schema{} validator := validate.NewSchemaValidator(openAPITypes, nil, "", strfmt.Default) - for key, data := range data { - if s.filter(key) { - continue - } - cfg, err := LoadConfigObjectFromContent(s.cfgType, data) - if err != nil { - return err - } - res := validator.Validate(cfg) - if res.HasErrors() { - return core.WrapError(errors.CompositeValidationError(res.Errors...), "failed to schema validate for cfg: %s", key) - } + if parameters, err = LoadConfigObjectFromContent(s.cfgType, content); err != nil { + return err + } + if res := validator.Validate(parameters); res.HasErrors() { + return core.WrapError(errors.CompositeValidationError(res.Errors...), "failed to schema validate for config file") } return nil } @@ -111,49 +73,29 @@ func (s *schemaValidator) Validate(data map[string]string) error { type emptyValidator struct { } -func (e emptyValidator) Validate(_ map[string]string) error { +func (e emptyValidator) Validate(_ string) error { return nil } -func WithKeySelector(keys []string) ValidatorOptions { - var sets *set.LinkedHashSetString - if len(keys) > 0 { - sets = core.FromCMKeysSelector(keys) - } - return func(key string) bool { - return sets == nil || sets.InArray(key) - } -} - -func NewConfigValidator(configConstraint *appsv1beta1.ConfigConstraintSpec, options ...ValidatorOptions) ConfigValidator { - if configConstraint == nil || configConstraint.FileFormatConfig == nil { +func NewConfigValidator(paramsSchema *parametersv1alpha1.ParametersSchema, fileFormat *parametersv1alpha1.FileFormatConfig) ConfigValidator { + if fileFormat == nil { return &emptyValidator{} } - var ( - validator ConfigValidator - configSchema = configConstraint.ParametersSchema - ) - + var validator ConfigValidator switch { - case configSchema == nil: + case paramsSchema == nil: validator = &emptyValidator{} - case len(configSchema.CUE) != 0: + case len(paramsSchema.CUE) != 0: validator = &configCueValidator{ - cmKeySelector: cmKeySelector{ - keySelector: options, - }, - cfgType: configConstraint.FileFormatConfig.Format, - cueScript: configSchema.CUE, + cfgType: fileFormat.Format, + cueScript: paramsSchema.CUE, } - case configSchema.SchemaInJSON != nil: + case paramsSchema.SchemaInJSON != nil: validator = &schemaValidator{ - cmKeySelector: cmKeySelector{ - keySelector: options, - }, - typeName: configSchema.TopLevelKey, - cfgType: configConstraint.FileFormatConfig.Format, - schema: configSchema.SchemaInJSON, + typeName: paramsSchema.TopLevelKey, + cfgType: fileFormat.Format, + schema: paramsSchema.SchemaInJSON, } default: validator = &emptyValidator{} diff --git a/pkg/configuration/validate/config_validate_test.go b/pkg/configuration/validate/config_validate_test.go index 06b21aaa59e..81f8ab013a7 100644 --- a/pkg/configuration/validate/config_validate_test.go +++ b/pkg/configuration/validate/config_validate_test.go @@ -25,7 +25,7 @@ import ( "github.com/stretchr/testify/require" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/test/testdata" ) @@ -37,14 +37,9 @@ var fromTestData = func(fileName string) string { return string(content) } -var newFakeConfConstraint = func(cueFile string, cfgFormatter appsv1beta1.CfgFileFormat) *appsv1beta1.ConfigConstraintSpec { - return &appsv1beta1.ConfigConstraintSpec{ - ParametersSchema: &appsv1beta1.ParametersSchema{ - CUE: fromTestData(cueFile), - }, - FileFormatConfig: &appsv1beta1.FileFormatConfig{ - Format: cfgFormatter, - }, +var newFakeConfigSchema = func(cueFile string) *parametersv1alpha1.ParametersSchema { + return ¶metersv1alpha1.ParametersSchema{ + CUE: fromTestData(cueFile), } } @@ -52,8 +47,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { type args struct { cueFile string configFile string - format appsv1beta1.CfgFileFormat - options []ValidatorOptions + format parametersv1alpha1.CfgFileFormat } tests := []struct { name string @@ -64,7 +58,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/mongod.cue", configFile: "cue_testdata/mongod.conf", - format: appsv1beta1.YAML, + format: parametersv1alpha1.YAML, }, err: nil, }, { @@ -72,7 +66,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/wesql.cue", configFile: "cue_testdata/wesql.cnf", - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, }, err: nil, }, { @@ -80,7 +74,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/pg14.cue", configFile: "cue_testdata/pg14.conf", - format: appsv1beta1.Properties, + format: parametersv1alpha1.Properties, }, err: nil, }, { @@ -88,7 +82,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/clickhouse.cue", configFile: "cue_testdata/clickhouse.xml", - format: appsv1beta1.XML, + format: parametersv1alpha1.XML, }, err: nil, }, { @@ -96,7 +90,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/mysql.cue", configFile: "cue_testdata/mysql.cnf", - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, }, err: nil, }, { @@ -104,7 +98,7 @@ func TestSchemaValidatorWithCue(t *testing.T) { args: args{ cueFile: "cue_testdata/mysql.cue", configFile: "cue_testdata/mysql_err.cnf", - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, }, err: errors.New(`failed to render cue template configure: [mysqld.innodb_autoinc_lock_mode: 3 errors in empty disjunction: mysqld.innodb_autoinc_lock_mode: conflicting values 0 and 100: @@ -117,55 +111,33 @@ mysqld.innodb_autoinc_lock_mode: conflicting values 2 and 100: name: "configmap_key_filter", args: args{ cueFile: "cue_testdata/mysql.cue", - configFile: "cue_testdata/mysql_err.cnf", - format: appsv1beta1.Ini, - options: []ValidatorOptions{WithKeySelector([]string{"key2", "key3"})}, + configFile: "cue_testdata/mysql.cnf", + format: parametersv1alpha1.Ini, }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - validator := NewConfigValidator(newFakeConfConstraint(tt.args.cueFile, tt.args.format), tt.args.options...) + validator := NewConfigValidator(newFakeConfigSchema(tt.args.cueFile), ¶metersv1alpha1.FileFormatConfig{Format: tt.args.format}) require.NotNil(t, validator) - require.Equal(t, tt.err, validator.Validate( - map[string]string{ - "key": fromTestData(tt.args.configFile), - })) + require.Equal(t, tt.err, validator.Validate(fromTestData(tt.args.configFile))) }) } } func TestSchemaValidatorWithSelector(t *testing.T) { - validator := NewConfigValidator(newFakeConfConstraint("cue_testdata/mysql.cue", appsv1beta1.Ini)) + validator := NewConfigValidator(newFakeConfigSchema("cue_testdata/mysql.cue"), ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}) require.NotNil(t, validator) require.ErrorContains(t, validator.Validate( - map[string]string{ - "normal_key": fromTestData("cue_testdata/mysql.cnf"), - "abnormal_key": fromTestData("cue_testdata/mysql_err.cnf"), - }), "[mysqld.innodb_autoinc_lock_mode: 3 errors in empty disjunction") - - validator = NewConfigValidator(newFakeConfConstraint("cue_testdata/mysql.cue", appsv1beta1.Ini), WithKeySelector([]string{})) - require.NotNil(t, validator) - require.ErrorContains(t, validator.Validate( - map[string]string{ - "normal_key": fromTestData("cue_testdata/mysql.cnf"), - "abnormal_key": fromTestData("cue_testdata/mysql_err.cnf"), - }), "[mysqld.innodb_autoinc_lock_mode: 3 errors in empty disjunction") - - validator = NewConfigValidator(newFakeConfConstraint("cue_testdata/mysql.cue", appsv1beta1.Ini), WithKeySelector([]string{"normal_key"})) - require.NotNil(t, validator) - require.Nil(t, validator.Validate( - map[string]string{ - "normal_key": fromTestData("cue_testdata/mysql.cnf"), - "abnormal_key": fromTestData("cue_testdata/mysql_err.cnf"), - })) + fromTestData("cue_testdata/mysql_err.cnf"), + ), "[mysqld.innodb_autoinc_lock_mode: 3 errors in empty disjunction") } func TestSchemaValidatorWithOpenSchema(t *testing.T) { type args struct { cueFile string configFile string - format appsv1beta1.CfgFileFormat + format parametersv1alpha1.CfgFileFormat SchemaTypeName string } tests := []struct { @@ -177,23 +149,20 @@ func TestSchemaValidatorWithOpenSchema(t *testing.T) { args: args{ cueFile: "cue_testdata/mysql.cue", configFile: "cue_testdata/mysql.cnf", - format: appsv1beta1.Ini, + format: parametersv1alpha1.Ini, }, err: nil, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tplConstraint := newFakeConfConstraint(tt.args.cueFile, tt.args.format) + configSchema := newFakeConfigSchema(tt.args.cueFile) validator := &schemaValidator{ typeName: tt.args.SchemaTypeName, - cfgType: tplConstraint.FileFormatConfig.Format, - schema: tplConstraint.ParametersSchema.SchemaInJSON, + cfgType: tt.args.format, + schema: configSchema.SchemaInJSON, } - require.Equal(t, tt.err, validator.Validate( - map[string]string{ - "key": fromTestData(tt.args.configFile), - })) + require.Equal(t, tt.err, validator.Validate(fromTestData(tt.args.configFile))) }) } } diff --git a/pkg/configuration/validate/cue_util.go b/pkg/configuration/validate/cue_util.go index 23cedb6c713..ecdcc738868 100644 --- a/pkg/configuration/validate/cue_util.go +++ b/pkg/configuration/validate/cue_util.go @@ -23,7 +23,7 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/unstructured" ) @@ -56,16 +56,16 @@ func CueValidate(cueTpl string) error { return tpl.Validate() } -func ValidateConfigurationWithCue(cueString string, cfgType appsv1beta1.CfgFileFormat, rawData string) error { +func ValidateConfigWithCue(cueString string, cfgType parametersv1alpha1.CfgFileFormat, rawData string) error { parameters, err := LoadConfigObjectFromContent(cfgType, rawData) if err != nil { return core.WrapError(err, "failed to load configuration [%s]", rawData) } - return unstructuredDataValidateByCue(cueString, parameters, cfgType == appsv1beta1.Properties || cfgType == appsv1beta1.PropertiesPlus) + return unstructuredDataValidateByCue(cueString, parameters, cfgType == parametersv1alpha1.Properties || cfgType == parametersv1alpha1.PropertiesPlus) } -func LoadConfigObjectFromContent(cfgType appsv1beta1.CfgFileFormat, rawData string) (map[string]interface{}, error) { +func LoadConfigObjectFromContent(cfgType parametersv1alpha1.CfgFileFormat, rawData string) (map[string]interface{}, error) { configObject, err := unstructured.LoadConfig("validate", rawData, cfgType) if err != nil { return nil, err diff --git a/pkg/constant/payload.go b/pkg/constant/payload.go index 8de78bfb441..f3a3712778d 100644 --- a/pkg/constant/payload.go +++ b/pkg/constant/payload.go @@ -20,6 +20,7 @@ along with this program. If not, see . package constant const ( + TLSPayload = "tls" ComponentResourcePayload = "component-resource" ReplicasPayload = "replicas" BinaryVersionPayload = "binary-version" diff --git a/pkg/controller/builder/builder_component_definition.go b/pkg/controller/builder/builder_component_definition.go index 92497251145..293b4e05661 100644 --- a/pkg/controller/builder/builder_component_definition.go +++ b/pkg/controller/builder/builder_component_definition.go @@ -127,20 +127,16 @@ func (builder *ComponentDefinitionBuilder) AddServiceExt(name, serviceName strin return builder } -func (builder *ComponentDefinitionBuilder) AddConfigTemplate(name, configTemplateRef, configConstraintRef, - namespace, volumeName string, injectEnvTo ...string) *ComponentDefinitionBuilder { - config := appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: name, - TemplateRef: configTemplateRef, - Namespace: namespace, - VolumeName: volumeName, - }, - ConfigConstraintRef: configConstraintRef, - InjectEnvTo: injectEnvTo, +func (builder *ComponentDefinitionBuilder) AddConfigTemplate(name, configTemplateRef, + namespace, volumeName string) *ComponentDefinitionBuilder { + config := appsv1.ComponentTemplateSpec{ + Name: name, + TemplateRef: configTemplateRef, + Namespace: namespace, + VolumeName: volumeName, } if builder.get().Spec.Configs == nil { - builder.get().Spec.Configs = make([]appsv1.ComponentConfigSpec, 0) + builder.get().Spec.Configs = make([]appsv1.ComponentTemplateSpec, 0) } builder.get().Spec.Configs = append(builder.get().Spec.Configs, config) return builder diff --git a/pkg/controller/builder/builder_component_parameter_test.go b/pkg/controller/builder/builder_component_parameter_test.go index 2fc80ea7ee7..11ab1514632 100644 --- a/pkg/controller/builder/builder_component_parameter_test.go +++ b/pkg/controller/builder/builder_component_parameter_test.go @@ -21,7 +21,7 @@ import ( . "github.com/onsi/gomega" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" + configcore "github.com/apecloud/kubeblocks/pkg/configuration/core" ) var _ = Describe("configuration builder", func() { @@ -31,7 +31,7 @@ var _ = Describe("configuration builder", func() { componentName = "mysql" ns = "default" ) - name := core.GenerateComponentConfigurationName(clusterName, componentName) + name := configcore.GenerateComponentConfigurationName(clusterName, componentName) config := NewComponentParameterBuilder(ns, name). ClusterRef(clusterName). Component(componentName). diff --git a/pkg/controller/builder/builder_configuration.go b/pkg/controller/builder/builder_configuration.go deleted file mode 100644 index da28ba167a6..00000000000 --- a/pkg/controller/builder/builder_configuration.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright (C) 2022-2024 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 builder - -import ( - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" -) - -type ConfigurationBuilder struct { - BaseBuilder[appsv1alpha1.Configuration, *appsv1alpha1.Configuration, ConfigurationBuilder] -} - -func NewConfigurationBuilder(namespace, name string) *ConfigurationBuilder { - builder := &ConfigurationBuilder{} - builder.init(namespace, name, &appsv1alpha1.Configuration{}, builder) - return builder -} - -func (c *ConfigurationBuilder) ClusterRef(clusterName string) *ConfigurationBuilder { - c.get().Spec.ClusterRef = clusterName - return c -} - -func (c *ConfigurationBuilder) Component(component string) *ConfigurationBuilder { - c.get().Spec.ComponentName = component - return c -} - -func (c *ConfigurationBuilder) AddConfigurationItem(configSpec appsv1.ComponentConfigSpec) *ConfigurationBuilder { - c.get().Spec.ConfigItemDetails = append(c.get().Spec.ConfigItemDetails, - appsv1alpha1.ConfigurationItemDetail{ - Name: configSpec.Name, - ConfigSpec: ToV1alpha1ConfigSpec(configSpec.DeepCopy()), - }) - return c -} - -func ToV1ConfigSpec(spec *appsv1alpha1.ComponentConfigSpec) *appsv1.ComponentConfigSpec { - v1 := &appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: spec.Name, - TemplateRef: spec.TemplateRef, - Namespace: spec.Namespace, - VolumeName: spec.VolumeName, - DefaultMode: spec.DefaultMode, - }, - Keys: spec.Keys, - ConfigConstraintRef: spec.ConfigConstraintRef, - AsEnvFrom: spec.AsEnvFrom, - InjectEnvTo: spec.InjectEnvTo, - AsSecret: spec.AsSecret, - } - if spec.ReRenderResourceTypes != nil { - v1.ReRenderResourceTypes = make([]appsv1.RerenderResourceType, 0) - for _, r := range spec.ReRenderResourceTypes { - v1.ReRenderResourceTypes = append(v1.ReRenderResourceTypes, appsv1.RerenderResourceType(r)) - } - } - return v1 -} - -func ToV1alpha1ConfigSpec(spec *appsv1.ComponentConfigSpec) *appsv1alpha1.ComponentConfigSpec { - v1 := &appsv1alpha1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ - Name: spec.Name, - TemplateRef: spec.TemplateRef, - Namespace: spec.Namespace, - VolumeName: spec.VolumeName, - DefaultMode: spec.DefaultMode, - }, - Keys: spec.Keys, - ConfigConstraintRef: spec.ConfigConstraintRef, - AsEnvFrom: spec.AsEnvFrom, - InjectEnvTo: spec.InjectEnvTo, - AsSecret: spec.AsSecret, - } - if spec.ReRenderResourceTypes != nil { - v1.ReRenderResourceTypes = make([]appsv1alpha1.RerenderResourceType, 0) - for _, r := range spec.ReRenderResourceTypes { - v1.ReRenderResourceTypes = append(v1.ReRenderResourceTypes, appsv1alpha1.RerenderResourceType(r)) - } - } - return v1 -} diff --git a/pkg/controller/builder/builder_configuration_test.go b/pkg/controller/builder/builder_configuration_test.go deleted file mode 100644 index 2f66f308483..00000000000 --- a/pkg/controller/builder/builder_configuration_test.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright (C) 2022-2024 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 builder - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" -) - -var _ = Describe("configuration builder", func() { - It("should work well", func() { - const ( - clusterName = "test" - componentName = "mysql" - ns = "default" - ) - name := core.GenerateComponentConfigurationName(clusterName, componentName) - config := NewConfigurationBuilder(ns, name). - ClusterRef(clusterName). - Component(componentName). - AddConfigurationItem(appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "mysql-config", - }, - }). - AddConfigurationItem(appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "mysql-oteld-config", - }, - }). - GetObject() - - Expect(config.Name).Should(BeEquivalentTo(name)) - Expect(config.Spec.ClusterRef).Should(BeEquivalentTo(clusterName)) - Expect(config.Spec.ComponentName).Should(BeEquivalentTo(componentName)) - Expect(len(config.Spec.ConfigItemDetails)).Should(Equal(2)) - }) -}) diff --git a/pkg/controller/component/component_test.go b/pkg/controller/component/component_test.go index 7a119d62c22..4160f952465 100644 --- a/pkg/controller/component/component_test.go +++ b/pkg/controller/component/component_test.go @@ -20,9 +20,6 @@ along with this program. If not, see . package component import ( - "reflect" - "testing" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -191,56 +188,3 @@ var _ = Describe("Component", func() { }) }) }) - -func TestGetConfigSpecByName(t *testing.T) { - type args struct { - component *SynthesizedComponent - configSpec string - } - tests := []struct { - name string - args args - want *appsv1.ComponentConfigSpec - }{{ - name: "test", - args: args{ - component: &SynthesizedComponent{}, - configSpec: "for_test", - }, - want: nil, - }, { - name: "test", - args: args{ - component: &SynthesizedComponent{ - ConfigTemplates: []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test", - }}}, - }, - configSpec: "for-test", - }, - want: nil, - }, { - name: "test", - args: args{ - component: &SynthesizedComponent{ - ConfigTemplates: []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for-test", - }}}, - }, - configSpec: "for-test", - }, - want: &appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for-test", - }}, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetConfigSpecByName(tt.args.component, tt.args.configSpec); !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetConfigSpecByName() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/controller/component/sidecar.go b/pkg/controller/component/sidecar.go index 6dde9d62464..9bbe6987ac2 100644 --- a/pkg/controller/component/sidecar.go +++ b/pkg/controller/component/sidecar.go @@ -91,17 +91,7 @@ func buildSidecarVars(sidecarDef *appsv1.SidecarDefinition, synthesizedComp *Syn func buildSidecarConfigs(sidecarDef *appsv1.SidecarDefinition, synthesizedComp *SynthesizedComponent) error { if sidecarDef.Spec.Configs != nil { - templateToConfig := func() []appsv1.ComponentConfigSpec { - if len(sidecarDef.Spec.Configs) == 0 { - return nil - } - l := make([]appsv1.ComponentConfigSpec, 0) - for i := range sidecarDef.Spec.Configs { - l = append(l, appsv1.ComponentConfigSpec{ComponentTemplateSpec: sidecarDef.Spec.Configs[i]}) - } - return l - } - synthesizedComp.ConfigTemplates = append(synthesizedComp.ConfigTemplates, templateToConfig()...) + synthesizedComp.ConfigTemplates = append(synthesizedComp.ConfigTemplates, sidecarDef.Spec.Configs...) } return nil } diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go index f3f102ccd8f..df7e8641103 100644 --- a/pkg/controller/component/synthesize_component.go +++ b/pkg/controller/component/synthesize_component.go @@ -285,7 +285,7 @@ func mergeUserDefinedVolumes(synthesizedComp *SynthesizedComponent, comp *appsv1 return nil } for _, tpl := range synthesizedComp.ConfigTemplates { - if err := checkConfigNScriptTemplate(tpl.ComponentTemplateSpec); err != nil { + if err := checkConfigNScriptTemplate(tpl); err != nil { return err } } @@ -391,7 +391,7 @@ func overrideConfigTemplates(synthesizedComp *SynthesizedComponent, comp *appsv1 return nil } - templates := make(map[string]*appsv1.ComponentConfigSpec) + templates := make(map[string]*appsv1.ComponentTemplateSpec) for i, template := range synthesizedComp.ConfigTemplates { templates[template.Name] = &synthesizedComp.ConfigTemplates[i] } @@ -442,13 +442,3 @@ func buildRuntimeClassName(synthesizeComp *SynthesizedComponent, comp *appsv1.Co } synthesizeComp.PodSpec.RuntimeClassName = comp.Spec.RuntimeClassName } - -func GetConfigSpecByName(synthesizedComp *SynthesizedComponent, configSpec string) *appsv1.ComponentConfigSpec { - for i := range synthesizedComp.ConfigTemplates { - template := &synthesizedComp.ConfigTemplates[i] - if template.Name == configSpec { - return template - } - } - return nil -} diff --git a/pkg/controller/component/synthesize_component_test.go b/pkg/controller/component/synthesize_component_test.go index edde838f7f5..18e96e9b1ea 100644 --- a/pkg/controller/component/synthesize_component_test.go +++ b/pkg/controller/component/synthesize_component_test.go @@ -70,19 +70,15 @@ var _ = Describe("synthesized component", func() { Name: "test-compdef", }, Spec: appsv1.ComponentDefinitionSpec{ - Configs: []appsv1.ComponentConfigSpec{ + Configs: []appsv1.ComponentTemplateSpec{ { - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "app", - TemplateRef: "app", - VolumeName: "app", - }, + Name: "app", + TemplateRef: "app", + VolumeName: "app", }, { - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "external", - VolumeName: "external", - }, + Name: "external", + VolumeName: "external", }, }, }, diff --git a/pkg/controller/component/type.go b/pkg/controller/component/type.go index 7b6d06d9f20..fa98d3874c5 100644 --- a/pkg/controller/component/type.go +++ b/pkg/controller/component/type.go @@ -45,7 +45,7 @@ type SynthesizedComponent struct { SidecarVars []kbappsv1.EnvVar // vars defined by sidecars VolumeClaimTemplates []corev1.PersistentVolumeClaimTemplate `json:"volumeClaimTemplates,omitempty"` LogConfigs []kbappsv1.LogConfig `json:"logConfigs,omitempty"` - ConfigTemplates []kbappsv1.ComponentConfigSpec `json:"configTemplates,omitempty"` + ConfigTemplates []kbappsv1.ComponentTemplateSpec `json:"configTemplates,omitempty"` ScriptTemplates []kbappsv1.ComponentTemplateSpec `json:"scriptTemplates,omitempty"` TLSConfig *kbappsv1.TLSConfig `json:"tlsConfig"` ServiceAccountName string `json:"serviceAccountName,omitempty"` diff --git a/pkg/controller/configuration/annotation_utils.go b/pkg/controller/configuration/annotation_utils.go index a014293cb63..f70dca04099 100644 --- a/pkg/controller/configuration/annotation_utils.go +++ b/pkg/controller/configuration/annotation_utils.go @@ -21,25 +21,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" ) -// BuildConfigConstraintLabels builds config constraints labels for object -func BuildConfigConstraintLabels(object client.Object, configSpecs []appsv1.ComponentConfigSpec) { - asMapLabels := make(map[string]string) - for _, configSpec := range configSpecs { - asMapLabels[core.GenerateTPLUniqLabelKeyWithConfig(configSpec.Name)] = configSpec.TemplateRef - if len(configSpec.ConfigConstraintRef) != 0 { - asMapLabels[core.GenerateConstraintsUniqLabelKeyWithConfig(configSpec.ConfigConstraintRef)] = configSpec.ConfigConstraintRef - } - } - updateLabelsOrAnnotations(asMapLabels, object.GetLabels, object.SetLabels, constant.ConfigurationConstraintsLabelPrefixKey, constant.ConfigurationTplLabelPrefixKey) -} - // BuildConfigTemplateAnnotations builds config template annotations for object func BuildConfigTemplateAnnotations(object client.Object, synthesizedComp *component.SynthesizedComponent) { asMapAnnotations := make(map[string]string) diff --git a/pkg/controller/configuration/config_utils.go b/pkg/controller/configuration/config_utils.go index b287d6e6579..c0e9cbcac72 100644 --- a/pkg/controller/configuration/config_utils.go +++ b/pkg/controller/configuration/config_utils.go @@ -22,31 +22,32 @@ package configuration import ( "context" "fmt" + "reflect" "slices" "strings" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/factory" + "github.com/apecloud/kubeblocks/pkg/controller/render" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" viper "github.com/apecloud/kubeblocks/pkg/viperx" ) -func createConfigObjects(cli client.Client, ctx context.Context, objs []client.Object, excludeObjs []client.Object) error { +func createConfigObjects(cli client.Client, ctx context.Context, objs []*corev1.ConfigMap) error { for _, obj := range objs { - if slices.Contains(excludeObjs, obj) { - continue - } if err := cli.Create(ctx, obj, inDataContext()); err != nil { if !apierrors.IsAlreadyExists(err) { @@ -66,16 +67,28 @@ func createConfigObjects(cli client.Client, ctx context.Context, objs []client.O // buildConfigManagerWithComponent build the configmgr sidecar container and update it // into PodSpec if configuration reload option is on -func buildConfigManagerWithComponent(podSpec *corev1.PodSpec, configSpecs []appsv1.ComponentConfigSpec, - ctx context.Context, cli client.Client, cluster *appsv1.Cluster, synthesizedComp *component.SynthesizedComponent) error { - var err error - var buildParams *cfgcm.CfgManagerBuildParams +func buildConfigManagerWithComponent(rctx *render.ResourceCtx, cluster *appsv1.Cluster, synthesizedComp *component.SynthesizedComponent, cmpd *appsv1.ComponentDefinition) error { + var ( + err error + buildParams *cfgcm.CfgManagerBuildParams + + podSpec = synthesizedComp.PodSpec + configSpecs = synthesizedComp.ConfigTemplates + configRender *parametersv1alpha1.ParameterDrivenConfigRender + paramsDefs []*parametersv1alpha1.ParametersDefinition + ) volumeDirs, usingConfigSpecs := getUsingVolumesByConfigSpecs(podSpec, configSpecs) if len(volumeDirs) == 0 { return nil } - configSpecMetas, err := cfgcm.GetSupportReloadConfigSpecs(usingConfigSpecs, cli, ctx) + if configRender, paramsDefs, err = intctrlutil.ResolveCmpdParametersDefs(rctx.Context, rctx.Client, cmpd); err != nil { + return err + } + if configRender == nil || len(configRender.Spec.Configs) == 0 { + return nil + } + configSpecMetas, err := cfgcm.GetSupportReloadConfigSpecs(usingConfigSpecs, configRender.Spec.Configs, paramsDefs) if err != nil { return err } @@ -85,7 +98,7 @@ func buildConfigManagerWithComponent(podSpec *corev1.PodSpec, configSpecs []apps if len(configSpecMetas) == 0 { return nil } - if buildParams, err = buildConfigManagerParams(cli, ctx, cluster, synthesizedComp, configSpecMetas, volumeDirs, podSpec); err != nil { + if buildParams, err = buildConfigManagerParams(rctx.Client, rctx.Context, cluster, synthesizedComp, configSpecMetas, volumeDirs, podSpec); err != nil { return err } if buildParams == nil { @@ -161,18 +174,11 @@ func updateCfgManagerVolumes(podSpec *corev1.PodSpec, configManager *cfgcm.CfgMa } } podSpec.Volumes = podVolumes - - for volumeName, volume := range configManager.ConfigLazyRenderedVolumes { - usingContainers := intctrlutil.GetPodContainerWithVolumeMount(podSpec, volumeName) - for _, container := range usingContainers { - container.VolumeMounts = append(container.VolumeMounts, volume) - } - } } -func getUsingVolumesByConfigSpecs(podSpec *corev1.PodSpec, configSpecs []appsv1.ComponentConfigSpec) ([]corev1.VolumeMount, []appsv1.ComponentConfigSpec) { +func getUsingVolumesByConfigSpecs(podSpec *corev1.PodSpec, configSpecs []appsv1.ComponentTemplateSpec) ([]corev1.VolumeMount, []appsv1.ComponentTemplateSpec) { // Ignore useless configTemplate - usingConfigSpecs := make([]appsv1.ComponentConfigSpec, 0, len(configSpecs)) + usingConfigSpecs := make([]appsv1.ComponentTemplateSpec, 0, len(configSpecs)) config2Containers := make(map[string][]*corev1.Container) for _, configSpec := range configSpecs { usingContainers := intctrlutil.GetPodContainerWithVolumeMount(podSpec, configSpec.VolumeName) @@ -192,10 +198,6 @@ func getUsingVolumesByConfigSpecs(podSpec *corev1.PodSpec, configSpecs []appsv1. // Find out which configurations are used by the container volumeDirs := make([]corev1.VolumeMount, 0, len(configSpecs)+1) for _, configSpec := range usingConfigSpecs { - // Ignore config template, e.g scripts configmap - if !core.NeedReloadVolume(configSpec) { - continue - } sets := cfgutil.NewSet() for _, container := range config2Containers[configSpec.Name] { volume := intctrlutil.GetVolumeMountByVolume(container, configSpec.VolumeName) @@ -210,14 +212,13 @@ func getUsingVolumesByConfigSpecs(podSpec *corev1.PodSpec, configSpecs []appsv1. func buildConfigManagerParams(cli client.Client, ctx context.Context, cluster *appsv1.Cluster, comp *component.SynthesizedComponent, configSpecBuildParams []cfgcm.ConfigSpecMeta, volumeDirs []corev1.VolumeMount, podSpec *corev1.PodSpec) (*cfgcm.CfgManagerBuildParams, error) { cfgManagerParams := &cfgcm.CfgManagerBuildParams{ - ManagerName: constant.ConfigSidecarName, - ComponentName: comp.Name, - Image: viper.GetString(constant.KBToolsImage), - Volumes: volumeDirs, - Cluster: cluster, - ConfigSpecsBuildParams: configSpecBuildParams, - ConfigLazyRenderedVolumes: make(map[string]corev1.VolumeMount), - ContainerPort: viper.GetInt32(constant.ConfigManagerGPRCPortEnv), + ManagerName: constant.ConfigSidecarName, + ComponentName: comp.Name, + Image: viper.GetString(constant.KBToolsImage), + Volumes: volumeDirs, + Cluster: cluster, + ConfigSpecsBuildParams: configSpecBuildParams, + ContainerPort: viper.GetInt32(constant.ConfigManagerGPRCPortEnv), } if podSpec.HostNetwork { @@ -237,7 +238,7 @@ func buildConfigManagerParams(cli client.Client, ctx context.Context, cluster *a return cfgManagerParams, nil } -func GetConfigManagerGRPCPort(containers []corev1.Container) (int32, error) { +func ResolveReloadServerGRPCPort(containers []corev1.Container) (int32, error) { for _, container := range containers { if container.Name != constant.ConfigSidecarName { continue @@ -269,47 +270,121 @@ func findPortByPortName(container corev1.Container) (int32, bool) { } // UpdateConfigPayload updates the configuration payload -func UpdateConfigPayload(config *appsv1alpha1.ConfigurationSpec, component *component.SynthesizedComponent) (bool, error) { - updated := false - for i := range config.ConfigItemDetails { +func UpdateConfigPayload(config *parametersv1alpha1.ComponentParameterSpec, component *appsv1.ComponentSpec, configRender *parametersv1alpha1.ParameterDrivenConfigRenderSpec) error { + if len(configRender.Configs) == 0 { + return nil + } + + for i, item := range config.ConfigItemDetails { + configDescs := intctrlutil.GetComponentConfigDescriptions(configRender, item.Name) configSpec := &config.ConfigItemDetails[i] // check v-scale operation - if enableVScaleTrigger(configSpec.ConfigSpec) { + if enableVScaleTrigger(configDescs) { resourcePayload := intctrlutil.ResourcesPayloadForComponent(component.Resources) - ret, err := intctrlutil.CheckAndPatchPayload(configSpec, constant.ComponentResourcePayload, resourcePayload) - if err != nil { - return false, err + if _, err := intctrlutil.CheckAndPatchPayload(configSpec, constant.ComponentResourcePayload, resourcePayload); err != nil { + return err } - updated = updated || ret } // check h-scale operation - if enableHScaleTrigger(configSpec.ConfigSpec) { - ret, err := intctrlutil.CheckAndPatchPayload(configSpec, constant.ReplicasPayload, component.Replicas) - if err != nil { - return false, err + if enableHScaleTrigger(configDescs) { + if _, err := intctrlutil.CheckAndPatchPayload(configSpec, constant.ReplicasPayload, component.Replicas); err != nil { + return err + } + } + // check tls + if enableTLSTrigger(configDescs) { + if component.TLSConfig == nil { + continue + } + if _, err := intctrlutil.CheckAndPatchPayload(configSpec, constant.TLSPayload, component.TLSConfig); err != nil { + return err } - updated = updated || ret } } - return updated, nil + 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 enableHScaleTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { + return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentHScaleType) } -func validRerenderResources(configSpec *appsv1alpha1.ComponentConfigSpec) bool { - return configSpec != nil && len(configSpec.ReRenderResourceTypes) != 0 +func enableVScaleTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { + return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentVScaleType) } -func enableHScaleTrigger(configSpec *appsv1alpha1.ComponentConfigSpec) bool { - return validRerenderResources(configSpec) && slices.Contains(configSpec.ReRenderResourceTypes, appsv1alpha1.ComponentHScaleType) +func enableTLSTrigger(configDescs []parametersv1alpha1.ComponentConfigDescription) bool { + return rerenderConfigEnabled(configDescs, parametersv1alpha1.ComponentTLSType) } -func enableVScaleTrigger(configSpec *appsv1alpha1.ComponentConfigSpec) bool { - return validRerenderResources(configSpec) && slices.Contains(configSpec.ReRenderResourceTypes, appsv1alpha1.ComponentVScaleType) +func runningComponentParameter(ctx context.Context, reader client.Reader, comp *component.SynthesizedComponent) (*parametersv1alpha1.ComponentParameter, error) { + var componentParameter = ¶metersv1alpha1.ComponentParameter{} + + parameterKey := types.NamespacedName{ + Name: core.GenerateComponentConfigurationName(comp.ClusterName, comp.Name), + Namespace: comp.Namespace, + } + if err := reader.Get(ctx, parameterKey, componentParameter); err != nil { + return nil, client.IgnoreNotFound(err) + } + return componentParameter, nil } -func configSetFromComponent(templates []appsv1.ComponentConfigSpec) []string { - configSet := make([]string, 0) - for _, template := range templates { - configSet = append(configSet, template.Name) +func buildComponentParameter(ctx context.Context, reader client.Reader, comp *component.SynthesizedComponent, owner *appsv1.Component, cmpd *appsv1.ComponentDefinition, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*parametersv1alpha1.ComponentParameter, error) { + tpls, err := resolveComponentTemplate(ctx, reader, cmpd) + if err != nil { + return nil, err + } + + parameterObj := builder.NewComponentParameterBuilder(comp.Namespace, + core.GenerateComponentConfigurationName(comp.ClusterName, comp.Name)). + AddLabelsInMap(constant.GetCompLabelsWithDef(comp.ClusterName, comp.Name, cmpd.Name)). + ClusterRef(comp.ClusterName). + Component(comp.Name). + SetConfigurationItem(ClassifyParamsFromConfigTemplate(intctrlutil.TransformComponentParameters(owner.Spec.InitParameters), cmpd, paramsDefs, tpls)). + GetObject() + if err = intctrlutil.SetOwnerReference(owner, parameterObj); err != nil { + return nil, err + } + if configRender != nil { + err = UpdateConfigPayload(¶meterObj.Spec, &owner.Spec, &configRender.Spec) + } + return parameterObj, err +} + +func mergeComponentParameter(expected *parametersv1alpha1.ComponentParameter, existing *parametersv1alpha1.ComponentParameter) *parametersv1alpha1.ComponentParameter { + return MergeComponentParameter(expected, existing, func(dest, expected *parametersv1alpha1.ConfigTemplateItemDetail) { + if len(dest.ConfigFileParams) == 0 && len(expected.ConfigFileParams) != 0 { + dest.ConfigFileParams = expected.ConfigFileParams + } + dest.Payload = expected.Payload + }) +} + +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 { + cm := &corev1.ConfigMap{} + if err := reader.Get(ctx, client.ObjectKey{Name: config.TemplateRef, Namespace: config.Namespace}, cm); err != nil { + return nil, err + } + tpls[config.Name] = cm + } + return tpls, nil +} + +func updateComponentParameters(ctx context.Context, cli client.Client, expected, existing *parametersv1alpha1.ComponentParameter) error { + mergedObject := mergeComponentParameter(expected, existing) + if reflect.DeepEqual(mergedObject, existing) { + return nil } - return configSet + return cli.Patch(ctx, mergedObject, client.MergeFrom(existing)) } diff --git a/pkg/controller/configuration/configuration_test.go b/pkg/controller/configuration/configuration_test.go index 8e215bd0797..ff93a1b7505 100644 --- a/pkg/controller/configuration/configuration_test.go +++ b/pkg/controller/configuration/configuration_test.go @@ -34,25 +34,27 @@ import ( ) const ( - compDefName = "test-compdef" - clusterName = "test-cluster" - configTemplateName = "test-config-template" - scriptTemplateName = "test-script-template" - mysqlCompName = "mysql" - mysqlConfigName = "mysql-component-config" - mysqlConfigConstraintName = "mysql8.0-config-constraints" - mysqlScriptsTemplateName = "apecloud-mysql-scripts" + compDefName = "test-compdef" + clusterName = "test-cluster" + paramsDefName = "mysql-params-def" + pdcrName = "mysql-pdcr" + configTemplateName = "test-config-template" + scriptTemplateName = "test-script-template" + mysqlCompName = "mysql" + mysqlConfigName = "mysql-component-config" + mysqlScriptsTemplateName = "apecloud-mysql-scripts" ) func allFieldsCompDefObj(create bool) *appsv1.ComponentDefinition { compDef := testapps.NewComponentDefinitionFactory(compDefName). SetDefaultSpec(). - AddConfigTemplate(configTemplateName, mysqlConfigName, mysqlConfigConstraintName, testCtx.DefaultNamespace, testapps.ConfVolumeName). + AddConfigTemplate(configTemplateName, mysqlConfigName, testCtx.DefaultNamespace, testapps.ConfVolumeName). AddScriptTemplate(scriptTemplateName, mysqlScriptsTemplateName, testCtx.DefaultNamespace, testapps.ScriptsVolumeName, nil). GetObject() if create { Expect(testCtx.CreateObj(testCtx.Ctx, compDef)).Should(Succeed()) } + compDef.Status.Phase = appsv1.AvailablePhase return compDef } @@ -85,10 +87,6 @@ func newAllFieldsSynthesizedComponent(compDef *appsv1.ComponentDefinition, clust Expect(err).Should(Succeed()) Expect(synthesizeComp).ShouldNot(BeNil()) addTestVolumeMount(synthesizeComp.PodSpec, mysqlCompName) - if len(synthesizeComp.ConfigTemplates) > 0 { - configSpec := &synthesizeComp.ConfigTemplates[0] - configSpec.ReRenderResourceTypes = []appsv1.RerenderResourceType{appsv1.ComponentVScaleType, appsv1.ComponentHScaleType} - } return synthesizeComp } diff --git a/pkg/controller/configuration/envfrom_utils.go b/pkg/controller/configuration/envfrom_utils.go index 1fd30c76ae5..8ee6224d116 100644 --- a/pkg/controller/configuration/envfrom_utils.go +++ b/pkg/controller/configuration/envfrom_utils.go @@ -20,191 +20,72 @@ along with this program. If not, see . package configuration import ( - "context" - "github.com/spf13/cast" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/configuration/validate" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - "github.com/apecloud/kubeblocks/pkg/generics" ) -func injectTemplateEnvFrom(cluster *appsv1.Cluster, component *component.SynthesizedComponent, podSpec *corev1.PodSpec, cli client.Client, ctx context.Context, localObjs []client.Object) error { - var err error - var cm *corev1.ConfigMap +func InjectTemplateEnvFrom(component *component.SynthesizedComponent, + podSpec *corev1.PodSpec, + configRender *parametersv1alpha1.ParameterDrivenConfigRender, + tplObjs []*corev1.ConfigMap) ([]*corev1.ConfigMap, error) { + withEnvSource := func(name string) corev1.EnvFromSource { + return corev1.EnvFromSource{ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }}} + } - withEnvSource := func(asSecret bool) func(name string) corev1.EnvFromSource { - return func(name string) corev1.EnvFromSource { - if asSecret { - return corev1.EnvFromSource{ - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: name, - }}} - } - return corev1.EnvFromSource{ - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: name, - }}} + injectConfigmap := func(envMap map[string]string, templateName string, injectEnvs []string) *corev1.ConfigMap { + cmName := core.GetComponentCfgName(component.ClusterName, component.Name, templateName) + envSourceObject := builder.NewConfigMapBuilder(component.Namespace, core.GenerateEnvFromName(cmName)). + AddLabels(constant.CMConfigurationSpecProviderLabelKey, templateName). + AddLabelsInMap(constant.GetCompLabels(component.ClusterName, component.Name)). + SetData(envMap). + GetObject() + if podSpec != nil { + injectEnvFrom(podSpec.Containers, injectEnvs, envSourceObject.GetName(), withEnvSource) + injectEnvFrom(podSpec.InitContainers, injectEnvs, envSourceObject.GetName(), withEnvSource) } + return envSourceObject } - injectConfigmap := func(envMap map[string]string, configSpec appsv1.ComponentConfigSpec, cmName string) error { - envSourceObject, err := createOrUpdateResourceFromConfigTemplate(cluster, component, configSpec, client.ObjectKeyFromObject(cm), envMap, ctx, cli, true) - if err != nil { - return core.WrapError(err, "failed to generate env configmap[%s]", cmName) - } - if toSecret(configSpec) && configSpec.VolumeName != "" { - podSpec.Volumes = updateSecretVolumes(podSpec.Volumes, configSpec, envSourceObject, component) - } else { - injectEnvFrom(podSpec.Containers, containersInjectedTo(configSpec), envSourceObject.GetName(), withEnvSource(toSecret(configSpec))) - injectEnvFrom(podSpec.InitContainers, containersInjectedTo(configSpec), envSourceObject.GetName(), withEnvSource(toSecret(configSpec))) - } - return nil + if configRender == nil || len(configRender.Spec.Configs) == 0 { + return nil, nil } - for _, template := range component.ConfigTemplates { - if !InjectEnvEnabled(template) || template.ConfigConstraintRef == "" { + var cm *corev1.ConfigMap + var envObjs []*corev1.ConfigMap + for _, config := range configRender.Spec.Configs { + if len(config.InjectEnvTo) == 0 || config.FileFormatConfig == nil { continue } - cmName := core.GetComponentCfgName(cluster.Name, component.Name, template.Name) - if cm, err = fetchConfigmap(localObjs, cmName, cluster.Namespace, cli, ctx); err != nil { - return err - } - cc, err := getConfigConstraint(template, cli, ctx) - if err != nil { - return err - } - envMap, err := fromConfigmapFiles(fromConfigSpec(template, cm), cm, cc.FileFormatConfig) - if err != nil { - return err - } - if len(envMap) == 0 { + if cm = resolveConfigMap(tplObjs, config.Name); cm == nil { continue } - if err := injectConfigmap(envMap, template, cmName); err != nil { - return err - } - } - return nil -} - -func updateSecretVolumes(volumes []corev1.Volume, configSpec appsv1.ComponentConfigSpec, secret client.Object, component *component.SynthesizedComponent) []corev1.Volume { - sets := configSetFromComponent(component.ConfigTemplates) - createFn := func(_ string) corev1.Volume { - return corev1.Volume{ - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secret.GetName(), - DefaultMode: intctrlutil.BuildVolumeMode(sets, configSpec.ComponentTemplateSpec), - }, - }, - Name: configSpec.VolumeName, - } - } - volumes, _ = intctrlutil.CreateOrUpdateVolume(volumes, configSpec.VolumeName, createFn, nil) - return volumes -} - -func getConfigConstraint(template appsv1.ComponentConfigSpec, cli client.Client, ctx context.Context) (*appsv1beta1.ConfigConstraintSpec, error) { - ccKey := client.ObjectKey{ - Namespace: "", - Name: template.ConfigConstraintRef, - } - cc := &appsv1beta1.ConfigConstraint{} - if err := cli.Get(ctx, ccKey, cc); err != nil { - return nil, core.WrapError(err, "failed to get ConfigConstraint, key[%v]", ccKey) - } - if cc.Spec.FileFormatConfig == nil { - return nil, core.MakeError("ConfigConstraint[%v] is not a formatter", cc.Name) - } - return &cc.Spec, nil -} - -func fromConfigmapFiles(keys []string, cm *corev1.ConfigMap, formatter *appsv1beta1.FileFormatConfig) (map[string]string, error) { - mergeMap := func(dst, src map[string]string) { - for key, val := range src { - dst[key] = val - } - } - - gEnvMap := make(map[string]string) - for _, file := range keys { - envMap, err := fromFileContent(formatter, cm.Data[file]) + envMap, err := resolveParametersFromFileContent(config.FileFormatConfig, cm.Data[config.Name]) if err != nil { return nil, err } - mergeMap(gEnvMap, envMap) - } - return gEnvMap, nil -} - -func fetchConfigmap(localObjs []client.Object, cmName, namespace string, cli client.Client, ctx context.Context) (*corev1.ConfigMap, error) { - var ( - cmObj = &corev1.ConfigMap{} - cmKey = client.ObjectKey{Name: cmName, Namespace: namespace} - ) - - localObject := findMatchedLocalObject(localObjs, cmKey, generics.ToGVK(cmObj)) - if localObject != nil { - return localObject.(*corev1.ConfigMap), nil - } - if err := cli.Get(ctx, cmKey, cmObj, inDataContext()); err != nil { - return nil, err + envObjs = append(envObjs, injectConfigmap(envMap, config.TemplateName, config.InjectEnvTo)) } - return cmObj, nil + return envObjs, nil } -func createOrUpdateResourceFromConfigTemplate(cluster *appsv1.Cluster, component *component.SynthesizedComponent, template appsv1.ComponentConfigSpec, originKey client.ObjectKey, envMap map[string]string, ctx context.Context, cli client.Client, createOnly bool) (client.Object, error) { - cmKey := client.ObjectKey{ - Name: core.GenerateEnvFromName(originKey.Name), - Namespace: originKey.Namespace, - } - - updateObjectMeta := func(obj client.Object) { - obj.SetLabels(constant.GetConfigurationLabels(component.ClusterName, component.Name, template.Name)) - _ = intctrlutil.SetOwnerReference(cluster, obj) - } - - if toSecret(template) { - return updateOrCreateEnvObject(ctx, cli, &corev1.Secret{}, cmKey, func(c *corev1.Secret) { - c.StringData = envMap - updateObjectMeta(c) - }, createOnly) - } - return updateOrCreateEnvObject(ctx, cli, &corev1.ConfigMap{}, cmKey, func(c *corev1.ConfigMap) { - c.Data = envMap - updateObjectMeta(c) - }, createOnly) -} - -func updateOrCreateEnvObject[T generics.Object, PT generics.PObject[T]](ctx context.Context, cli client.Client, obj PT, objKey client.ObjectKey, updater func(PT), createOnly bool) (client.Object, error) { - err := cli.Get(ctx, objKey, obj, inDataContext()) - switch { - case err != nil: - if !apierrors.IsNotFound(err) { - return nil, err +func resolveConfigMap(localObjs []*corev1.ConfigMap, key string) *corev1.ConfigMap { + for _, obj := range localObjs { + if _, ok := obj.Data[key]; ok { + return obj } - obj.SetName(objKey.Name) - obj.SetNamespace(objKey.Namespace) - updater(obj) - return obj, cli.Create(ctx, obj, inDataContext()) - case err == nil && createOnly: - return obj, nil - default: - updater(obj) - return obj, cli.Update(ctx, obj, inDataContext()) } + return nil } func CheckEnvFrom(container *corev1.Container, cmName string) bool { @@ -230,7 +111,7 @@ func injectEnvFrom(containers []corev1.Container, injectEnvTo []string, cmName s } } -func fromFileContent(format *appsv1beta1.FileFormatConfig, configContext string) (map[string]string, error) { +func resolveParametersFromFileContent(format *parametersv1alpha1.FileFormatConfig, configContext string) (map[string]string, error) { keyValue, err := validate.LoadConfigObjectFromContent(format.Format, configContext) if err != nil { return nil, err @@ -241,40 +122,3 @@ func fromFileContent(format *appsv1beta1.FileFormatConfig, configContext string) } return envMap, nil } - -func fromConfigSpec(configSpec appsv1.ComponentConfigSpec, cm *corev1.ConfigMap) []string { - keys := configSpec.Keys - if len(keys) == 0 { - keys = cfgutil.ToSet(cm.Data).AsSlice() - } - return keys -} - -func SyncEnvSourceObject(configSpec appsv1.ComponentConfigSpec, cmObj *corev1.ConfigMap, cc *appsv1beta1.ConfigConstraintSpec, cli client.Client, ctx context.Context, cluster *appsv1.Cluster, component *component.SynthesizedComponent) error { - if !InjectEnvEnabled(configSpec) || cc == nil || cc.FileFormatConfig == nil { - return nil - } - envMap, err := fromConfigmapFiles(fromConfigSpec(configSpec, cmObj), cmObj, cc.FileFormatConfig) - if err != nil { - return err - } - if len(envMap) != 0 { - _, err = createOrUpdateResourceFromConfigTemplate(cluster, component, configSpec, client.ObjectKeyFromObject(cmObj), envMap, ctx, cli, false) - } - return err -} - -func InjectEnvEnabled(spec appsv1.ComponentConfigSpec) bool { - return len(spec.AsEnvFrom) > 0 || len(spec.InjectEnvTo) > 0 -} - -func toSecret(spec appsv1.ComponentConfigSpec) bool { - return spec.AsSecret != nil && *spec.AsSecret -} - -func containersInjectedTo(spec appsv1.ComponentConfigSpec) []string { - if len(spec.InjectEnvTo) != 0 { - return spec.InjectEnvTo - } - return spec.AsEnvFrom -} diff --git a/pkg/controller/configuration/envfrom_utils_test.go b/pkg/controller/configuration/envfrom_utils_test.go index 4c51f1b322d..87836027f84 100644 --- a/pkg/controller/configuration/envfrom_utils_test.go +++ b/pkg/controller/configuration/envfrom_utils_test.go @@ -24,15 +24,16 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/controller/component" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("ConfigEnvFrom test", func() { @@ -46,23 +47,35 @@ var _ = Describe("ConfigEnvFrom test", func() { compDef *appsv1.ComponentDefinition cluster *appsv1.Cluster - k8sMockClient *testutil.K8sClientMockHelper - origCMObject *corev1.ConfigMap - configConstraint *appsv1beta1.ConfigConstraint + k8sMockClient *testutil.K8sClientMockHelper + origCMObject *corev1.ConfigMap + configRender *parametersv1alpha1.ParameterDrivenConfigRender ) BeforeEach(func() { k8sMockClient = testutil.NewK8sMockClient() - cm := testapps.NewCustomizedObj("config/envfrom-config.yaml", &corev1.ConfigMap{}, - testCtx.UseDefaultNamespace()) - - configConstraint = testapps.NewCustomizedObj("config/envfrom-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) + cm := testparameters.NewComponentTemplateFactory("", testCtx.DefaultNamespace). + WithRandomName(). + AddConfigFile("env-file", ` +dbStorage_rocksDB_writeBufferSizeMB=8 +dbStorage_rocksDB_sstSizeInMB=64 +dbStorage_rocksDB_blockSize=65536 +dbStorage_rocksDB_bloomFilterBitsPerKey=10 +dbStorage_rocksDB_numLevels=-1 +dbStorage_rocksDB_numFilesInLevel0=4 +dbStorage_rocksDB_maxSizeInLevel1MB=256 +`).GetObject() + + configRender = testparameters.NewParametersDrivenConfigFactory(""). + WithRandomName(). + SetConfigDescription("env-file", cm.Name, + parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). + GetObject() compDef = testapps.NewComponentDefinitionFactory(compDefName). SetDefaultSpec(). - AddConfigTemplate(cm.Name, cm.Name, configConstraint.Name, testCtx.DefaultNamespace, "mysql-config", testapps.DefaultMySQLContainerName). + AddConfigTemplate(cm.Name, cm.Name, testCtx.DefaultNamespace, "mysql-config"). GetObject() pvcSpec := testapps.NewPVCSpec("1Gi") @@ -73,6 +86,8 @@ var _ = Describe("ConfigEnvFrom test", func() { origCMObject = cm.DeepCopy() origCMObject.Name = core.GetComponentCfgName(clusterName, mysqlCompName, cm.Name) + + _ = cluster }) AfterEach(func() { @@ -94,67 +109,62 @@ var _ = Describe("ConfigEnvFrom test", func() { }, }, } - k8sMockClient.MockGetMethod( - testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - origCMObject, - configConstraint, - }), testutil.WithAnyTimes())) - k8sMockClient.MockCreateMethod( - testutil.WithCreateReturned(testutil.WithCreatedFailedResult(), testutil.WithTimes(1)), - testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes()), - ) - - synthesizeComp.ConfigTemplates[0].AsSecret = cfgutil.ToPointer(true) - Expect(injectTemplateEnvFrom(cluster, synthesizeComp, podSpec, k8sMockClient.Client(), ctx, nil)).ShouldNot(Succeed()) - Expect(injectTemplateEnvFrom(cluster, synthesizeComp, podSpec, k8sMockClient.Client(), ctx, nil)).Should(Succeed()) - }) - - It("should SyncEnvSourceObject success", func() { - configSpec := compDef.Spec.Configs[0] - configSpec.Keys = []string{"env-config"} - - comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) - Expect(err).Should(Succeed()) - - synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) + desc := intctrlutil.GetComponentConfigDescription(&configRender.Spec, "env-file") + desc.InjectEnvTo = []string{testapps.DefaultMySQLContainerName} + objs, err := InjectTemplateEnvFrom(synthesizeComp, podSpec, configRender, []*corev1.ConfigMap{origCMObject}) Expect(err).Should(Succeed()) - - cmObj := origCMObject.DeepCopy() - cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - cmObj, - configConstraint, - }), testutil.WithAnyTimes())) - k8sMockClient.MockUpdateMethod(testutil.WithFailed(core.MakeError("failed to patch"), testutil.WithTimes(1)), - testutil.WithSucceed(), testutil.WithAnyTimes()) - - Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).ShouldNot(Succeed()) - Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) + Expect(objs).Should(HaveLen(1)) + Expect(generics.FindFunc(podSpec.Containers[0].EnvFrom, func(source corev1.EnvFromSource) bool { + return source.ConfigMapRef.Name == objs[0].Name + })).Should(HaveLen(1)) }) - It("SyncEnvSourceObject abnormal test", func() { - comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) - Expect(err).Should(Succeed()) - - synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) - Expect(err).Should(Succeed()) - - configSpec := compDef.Spec.Configs[0] - configSpec.InjectEnvTo = nil - Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) - - configSpec.InjectEnvTo = nil - cmObj := origCMObject.DeepCopy() - cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - cmObj, - configConstraint, - }), testutil.WithAnyTimes())) - k8sMockClient.MockUpdateMethod(testutil.WithSucceed(testutil.WithAnyTimes())) - - configSpec = compDef.Spec.Configs[0] - configSpec.Keys = []string{"env-config", "not-exist"} - Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) - }) + // It("should SyncEnvSourceObject success", func() { + // configSpec := compDef.Spec.Configs[0] + // configSpec.Keys = []string{"env-config"} + // + // comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) + // Expect(err).Should(Succeed()) + // + // synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) + // Expect(err).Should(Succeed()) + // + // cmObj := origCMObject.DeepCopy() + // cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) + // k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ + // cmObj, + // configConstraint, + // }), testutil.WithAnyTimes())) + // k8sMockClient.MockUpdateMethod(testutil.WithFailed(core.MakeError("failed to patch"), testutil.WithTimes(1)), + // testutil.WithSucceed(), testutil.WithAnyTimes()) + // + // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).ShouldNot(Succeed()) + // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) + // }) + // + // It("SyncEnvSourceObject abnormal test", func() { + // comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) + // Expect(err).Should(Succeed()) + // + // synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) + // Expect(err).Should(Succeed()) + // + // configSpec := compDef.Spec.Configs[0] + // configSpec.InjectEnvTo = nil + // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) + // + // configSpec.InjectEnvTo = nil + // cmObj := origCMObject.DeepCopy() + // cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) + // k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ + // cmObj, + // configConstraint, + // }), testutil.WithAnyTimes())) + // k8sMockClient.MockUpdateMethod(testutil.WithSucceed(testutil.WithAnyTimes())) + // + // configSpec = compDef.Spec.Configs[0] + // configSpec.Keys = []string{"env-config", "not-exist"} + // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) + // }) }) }) diff --git a/pkg/controller/configuration/mutation.go b/pkg/controller/configuration/mutation.go new file mode 100644 index 00000000000..483defbc3c0 --- /dev/null +++ b/pkg/controller/configuration/mutation.go @@ -0,0 +1,71 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configuration + +import ( + "k8s.io/apimachinery/pkg/util/sets" + + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +type MutateFunc func(dest, expected *parametersv1alpha1.ConfigTemplateItemDetail) + +func MergeComponentParameter(expected *parametersv1alpha1.ComponentParameter, existing *parametersv1alpha1.ComponentParameter, mutate MutateFunc) *parametersv1alpha1.ComponentParameter { + fromList := func(items []parametersv1alpha1.ConfigTemplateItemDetail) sets.Set[string] { + itemSet := sets.New[string]() + for _, item := range items { + itemSet.Insert(item.Name) + } + return itemSet + } + + // update cluster.spec.shardingSpecs[*].template.componentConfigItems.* + updateConfigSpec := func(item parametersv1alpha1.ConfigTemplateItemDetail) parametersv1alpha1.ConfigTemplateItemDetail { + if newItem := intctrlutil.GetConfigTemplateItem(&expected.Spec, item.Name); newItem != nil { + updated := item.DeepCopy() + mutate(updated, newItem) + return *updated + } + return item + } + + oldSets := fromList(existing.Spec.ConfigItemDetails) + newSets := fromList(expected.Spec.ConfigItemDetails) + addSets := newSets.Difference(oldSets) + delSets := oldSets.Difference(newSets) + + newConfigItems := make([]parametersv1alpha1.ConfigTemplateItemDetail, 0) + for _, item := range existing.Spec.ConfigItemDetails { + if !delSets.Has(item.Name) { + newConfigItems = append(newConfigItems, updateConfigSpec(item)) + } + } + for _, item := range expected.Spec.ConfigItemDetails { + if addSets.Has(item.Name) { + newConfigItems = append(newConfigItems, item) + } + } + + updated := existing.DeepCopy() + updated.SetLabels(intctrlutil.MergeMetadataMaps(expected.GetLabels(), updated.GetLabels())) + if len(expected.GetOwnerReferences()) != 0 { + updated.SetOwnerReferences(expected.GetOwnerReferences()) + } + updated.Spec.ConfigItemDetails = newConfigItems + return updated +} diff --git a/pkg/controller/configuration/operator.go b/pkg/controller/configuration/operator.go index 5e0e7e9a733..2299755ed93 100644 --- a/pkg/controller/configuration/operator.go +++ b/pkg/controller/configuration/operator.go @@ -59,21 +59,22 @@ func (c *configOperator) Reconcile() error { } return NewCreatePipeline(c.ReconcileCtx). + ComponentAndComponentDef(). Prepare(). - RenderScriptTemplate(). // render scriptTemplate into ConfigMap - UpdateConfiguration(). // create or update Configuration - Configuration(). // fetch the latest Configuration - CreateConfigTemplate(). // render configTemplate into ConfigMap (only for the first time) - UpdatePodVolumes(). // update podSpec.Volumes - BuildConfigManagerSidecar(). // build configManager sidecar and update podSpec.Containers and podSpec.InitContainers - UpdateConfigRelatedObject(). // handle InjectEnvTo, and create or update ConfigMaps - UpdateConfigurationStatus(). // update ConfigurationItemStatus revision and phase etc. + RenderScriptTemplate(). + SyncComponentParameter(). + ComponentParameter(). + CreateConfigTemplate(). + UpdatePodVolumes(). + BuildConfigManagerSidecar(). + UpdateConfigRelatedObject(). Complete() } func (c *configOperator) UpdateConfiguration() error { return NewCreatePipeline(c.ReconcileCtx). - UpdateConfiguration(). + ComponentAndComponentDef(). + SyncComponentParameter(). UpdateConfigRelatedObject(). Complete() } diff --git a/pkg/controller/configuration/operator_test.go b/pkg/controller/configuration/operator_test.go index 996aef1b51d..a9873991584 100644 --- a/pkg/controller/configuration/operator_test.go +++ b/pkg/controller/configuration/operator_test.go @@ -23,21 +23,19 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/golang/mock/gomock" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/render" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("ConfigurationOperatorTest", func() { @@ -47,8 +45,9 @@ var _ = Describe("ConfigurationOperatorTest", func() { var synthesizedComponent *component.SynthesizedComponent var configMapObj *corev1.ConfigMap var scriptsObj *corev1.ConfigMap - var configConstraint *appsv1beta1.ConfigConstraint - var configurationObj *appsv1alpha1.Configuration + var parametersDef *parametersv1alpha1.ParametersDefinition + var configRender *parametersv1alpha1.ParameterDrivenConfigRender + var componentParameter *parametersv1alpha1.ComponentParameter var k8sMockClient *testutil.K8sClientMockHelper createConfigReconcileTask := func() *configOperator { @@ -77,32 +76,18 @@ var _ = Describe("ConfigurationOperatorTest", func() { testapps.SetConfigMapData("test", "test")) scriptsObj = testapps.NewConfigMap("default", mysqlScriptsTemplateName, testapps.SetConfigMapData("script.sh", "echo \"hello\"")) - configurationObj = builder.NewConfigurationBuilder(testCtx.DefaultNamespace, + componentParameter = builder.NewComponentParameterBuilder(testCtx.DefaultNamespace, cfgcore.GenerateComponentConfigurationName(clusterName, mysqlCompName)). ClusterRef(clusterName). Component(mysqlCompName). GetObject() - configConstraint = &appsv1beta1.ConfigConstraint{ - ObjectMeta: metav1.ObjectMeta{ - Name: mysqlConfigConstraintName, - }, - Spec: appsv1beta1.ConfigConstraintSpec{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ - Command: []string{"echo", "hello"}, - Sync: pointer.Bool(true), - }, - }, - FileFormatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - FormatterAction: appsv1beta1.FormatterAction{ - IniConfig: &appsv1beta1.IniConfig{ - SectionName: "mysqld", - }, - }, - }, - }, - } + parametersDef = testparameters.NewParametersDefinitionFactory(paramsDefName).GetObject() + configRender = testparameters.NewParametersDrivenConfigFactory(pdcrName). + SetComponentDefinition(compDefObj.Name). + SetParametersDefs(paramsDefName). + GetObject() + parametersDef.Status.Phase = parametersv1alpha1.PDAvailablePhase + configRender.Status.Phase = parametersv1alpha1.PDAvailablePhase }) AfterEach(func() { @@ -114,103 +99,50 @@ var _ = Describe("ConfigurationOperatorTest", func() { k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult( []client.Object{ compDefObj, - clusterObj, - clusterObj, - scriptsObj, - configMapObj, - configConstraint, - configurationObj, - }, - ), testutil.WithAnyTimes())) - k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes())) - k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { - switch v := obj.(type) { - case *appsv1alpha1.Configuration: - if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) { - configurationObj.Spec = *v.Spec.DeepCopy() - configurationObj.Status = *v.Status.DeepCopy() - } - } - return nil - })) - k8sMockClient.MockStatusMethod(). - EXPECT(). - Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil) - Expect(createConfigReconcileTask().Reconcile()).Should(Succeed()) - }) - - It("BuildConfigManagerNPETest", func() { - configConstraintNpe := &appsv1beta1.ConfigConstraint{ - ObjectMeta: metav1.ObjectMeta{ - Name: mysqlConfigConstraintName + "_npe_test", - }, - Spec: appsv1beta1.ConfigConstraintSpec{ - ReloadAction: &appsv1beta1.ReloadAction{ - ShellTrigger: &appsv1beta1.ShellTrigger{ - Command: []string{"echo", "hello"}, - Sync: pointer.Bool(true), - ToolsSetup: &appsv1beta1.ToolsSetup{ - MountPoint: "/kb_tools", - ToolConfigs: []appsv1beta1.ToolConfig{ - { - Name: "tools_name", - AsContainerImage: pointer.Bool(true), - Image: "apecloud/tools:1234", - }, - }, - }, - }, - }, - FileFormatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Ini, - }, - }, - } - synthesizedComponent.ConfigTemplates = append(synthesizedComponent.ConfigTemplates, synthesizedComponent.ConfigTemplates[0]) - synthesizedComponent.ConfigTemplates[1].Name = "npe_test" - synthesizedComponent.ConfigTemplates[1].ConfigConstraintRef = configConstraintNpe.Name - - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult( - []client.Object{ - compDefObj, - clusterObj, + componentObj, clusterObj, scriptsObj, configMapObj, - configConstraint, - configurationObj, - configConstraintNpe, + parametersDef, + configRender, + componentParameter, }, ), testutil.WithAnyTimes())) k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes())) k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { switch v := obj.(type) { - case *appsv1alpha1.Configuration: - if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) { - configurationObj.Spec = *v.Spec.DeepCopy() - configurationObj.Status = *v.Status.DeepCopy() + case *parametersv1alpha1.ComponentParameter: + if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(componentParameter) { + componentParameter.Spec = *v.Spec.DeepCopy() + componentParameter.Status = *v.Status.DeepCopy() } } return nil })) - k8sMockClient.MockStatusMethod(). - EXPECT(). - Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil) + k8sMockClient.MockNListMethod(0, testutil.WithListReturned( + testutil.WithConstructListReturnedResult([]runtime.Object{configRender}), + testutil.WithAnyTimes(), + )) Expect(createConfigReconcileTask().Reconcile()).Should(Succeed()) }) It("EmptyConfigSpecTest", func() { - k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithTimes(1))) + // k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithTimes(1))) k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult( []client.Object{ compDefObj, + componentObj, clusterObj, - clusterObj, + configMapObj, + componentParameter, }, ), testutil.WithAnyTimes())) + k8sMockClient.MockPatchMethod(testutil.WithSucceed(), testutil.WithAnyTimes()) + k8sMockClient.MockNListMethod(0, testutil.WithListReturned( + testutil.WithConstructListReturnedResult([]runtime.Object{configRender}), + testutil.WithAnyTimes(), + )) synthesizedComponent.ConfigTemplates = nil synthesizedComponent.ScriptTemplates = nil diff --git a/pkg/controller/configuration/parameter_utils.go b/pkg/controller/configuration/parameter_utils.go index 87e2bd9bc6d..1840bfe317a 100644 --- a/pkg/controller/configuration/parameter_utils.go +++ b/pkg/controller/configuration/parameter_utils.go @@ -34,9 +34,8 @@ func ClassifyParamsFromConfigTemplate(params parametersv1alpha1.ComponentParamet tpls map[string]*corev1.ConfigMap) []parametersv1alpha1.ConfigTemplateItemDetail { var itemDetails []parametersv1alpha1.ConfigTemplateItemDetail - configs := intctrlutil.TransformConfigTemplate(cmpd.Spec.Configs) - classifyParams := ClassifyComponentParameters(params, paramsDefs, configs, tpls) - for _, template := range configs { + classifyParams := ClassifyComponentParameters(params, paramsDefs, cmpd.Spec.Configs, tpls) + for _, template := range cmpd.Spec.Configs { itemDetails = append(itemDetails, generateConfigTemplateItem(classifyParams, template)) } return itemDetails diff --git a/pkg/controller/configuration/patch_merger.go b/pkg/controller/configuration/patch_merger.go index d71e94f8396..e827b43df06 100644 --- a/pkg/controller/configuration/patch_merger.go +++ b/pkg/controller/configuration/patch_merger.go @@ -20,14 +20,15 @@ along with this program. If not, see . package configuration import ( - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) -func DoMerge(baseData map[string]string, patch map[string]appsv1alpha1.ConfigParams, cc *appsv1beta1.ConfigConstraint, configSpec appsv1.ComponentConfigSpec) (map[string]string, error) { +func DoMerge(baseData map[string]string, + patch map[string]parametersv1alpha1.ParametersInFile, + paramsDefs []*parametersv1alpha1.ParametersDefinition, + configDescs []parametersv1alpha1.ComponentConfigDescription) (map[string]string, error) { var ( updatedFiles = make(map[string]string, len(patch)) updatedParams = make([]core.ParamPairs, 0, len(patch)) @@ -44,22 +45,22 @@ func DoMerge(baseData map[string]string, patch map[string]appsv1alpha1.ConfigPar }) } } - return mergeUpdatedParams(baseData, updatedFiles, updatedParams, cc, configSpec) + return mergeUpdatedParams(baseData, updatedFiles, updatedParams, paramsDefs, configDescs) } func mergeUpdatedParams(base map[string]string, updatedFiles map[string]string, updatedParams []core.ParamPairs, - cc *appsv1beta1.ConfigConstraint, - tpl appsv1.ComponentConfigSpec) (map[string]string, error) { + paramsDefs []*parametersv1alpha1.ParametersDefinition, + configDescs []parametersv1alpha1.ComponentConfigDescription) (map[string]string, error) { updatedConfig := base // merge updated files into configmap if len(updatedFiles) != 0 { updatedConfig = core.MergeUpdatedConfig(base, updatedFiles) } - if cc == nil { + if len(configDescs) == 0 { return updatedConfig, nil } - return intctrlutil.MergeAndValidateConfigs(cc.Spec, updatedConfig, tpl.Keys, updatedParams) + return intctrlutil.MergeAndValidateConfigs(updatedConfig, updatedParams, paramsDefs, configDescs) } diff --git a/pkg/controller/configuration/pipeline.go b/pkg/controller/configuration/pipeline.go index cc93e65166c..32aa0534ddf 100644 --- a/pkg/controller/configuration/pipeline.go +++ b/pkg/controller/configuration/pipeline.go @@ -20,6 +20,7 @@ along with this program. If not, see . package configuration import ( + "context" "strconv" corev1 "k8s.io/api/core/v1" @@ -27,14 +28,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/render" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" ) type pipeline struct { @@ -43,15 +43,18 @@ type pipeline struct { ctx render.ReconcileCtx ResourceFetcher[pipeline] + + configRender *parametersv1alpha1.ParameterDrivenConfigRender + parametersDefs []*parametersv1alpha1.ParametersDefinition } type updatePipeline struct { renderWrapper renderWrapper reconcile bool - item appsv1alpha1.ConfigurationItemDetail - itemStatus *appsv1alpha1.ConfigurationItemDetailStatus - configSpec *appsv1.ComponentConfigSpec + item parametersv1alpha1.ConfigTemplateItemDetail + itemStatus *parametersv1alpha1.ConfigTemplateItemDetailStatus + configSpec *appsv1.ComponentTemplateSpec // replace of ConfigMapObj // originalCM *corev1.ConfigMap newCM *corev1.ConfigMap @@ -59,6 +62,9 @@ type updatePipeline struct { ctx render.ReconcileCtx ResourceFetcher[updatePipeline] + + configRender *parametersv1alpha1.ParameterDrivenConfigRender + parametersDefs []*parametersv1alpha1.ParametersDefinition } func NewCreatePipeline(ctx render.ReconcileCtx) *pipeline { @@ -66,22 +72,26 @@ func NewCreatePipeline(ctx render.ReconcileCtx) *pipeline { return p.Init(ctx.ResourceCtx, p) } -func NewReconcilePipeline(ctx render.ReconcileCtx, item appsv1alpha1.ConfigurationItemDetail, itemStatus *appsv1alpha1.ConfigurationItemDetailStatus, configSpec *appsv1.ComponentConfigSpec) *updatePipeline { +func NewReconcilePipeline(ctx render.ReconcileCtx, item parametersv1alpha1.ConfigTemplateItemDetail, itemStatus *parametersv1alpha1.ConfigTemplateItemDetailStatus, cm *corev1.ConfigMap, componentParameter *parametersv1alpha1.ComponentParameter) *updatePipeline { p := &updatePipeline{ reconcile: true, item: item, itemStatus: itemStatus, ctx: ctx, - configSpec: configSpec, + configSpec: item.ConfigSpec, } - return p.Init(ctx.ResourceCtx, p) + p.Init(ctx.ResourceCtx, p) + p.ConfigMapObj = cm + p.ComponentParameterObj = componentParameter + return p } func (p *pipeline) Prepare() *pipeline { buildTemplate := func() (err error) { ctx := p.ctx p.renderWrapper = newTemplateRenderWrapper(p.Context, ctx.Client, render.NewTemplateBuilder(&ctx), ctx.Cluster, ctx.Component) - return + p.configRender, p.parametersDefs, err = intctrlutil.ResolveCmpdParametersDefs(ctx.Context, ctx.Client, p.ComponentDefObj) + return err } return p.Wrap(buildTemplate) @@ -90,27 +100,29 @@ func (p *pipeline) Prepare() *pipeline { func (p *pipeline) RenderScriptTemplate() *pipeline { return p.Wrap(func() error { ctx := p.ctx - return p.renderWrapper.renderScriptTemplate(ctx.Cluster, ctx.SynthesizedComponent, ctx.Cache) + return p.renderWrapper.renderScriptTemplate(ctx.Cluster, ctx.SynthesizedComponent, ctx.Cache, ctx.Component) }) } -func (p *pipeline) UpdateConfiguration() *pipeline { +func (p *pipeline) SyncComponentParameter() *pipeline { buildConfiguration := func() (err error) { - expectedConfiguration := p.createConfiguration() - if intctrlutil.SetControllerReference(p.ctx.Component, expectedConfiguration) != nil { - return + var existingObject *parametersv1alpha1.ComponentParameter + var expectedObject *parametersv1alpha1.ComponentParameter + + if existingObject, err = runningComponentParameter(p.Context, p.Client, p.ctx.SynthesizedComponent); err != nil { + return err + } + if expectedObject, err = buildComponentParameter(p.Context, p.Client, p.ctx.SynthesizedComponent, p.ctx.Component, p.ComponentDefObj, p.configRender, p.parametersDefs); err != nil { + return err } - _, _ = UpdateConfigPayload(&expectedConfiguration.Spec, p.ctx.SynthesizedComponent) - existingConfiguration := appsv1alpha1.Configuration{} - err = p.ResourceFetcher.Client.Get(p.Context, client.ObjectKeyFromObject(expectedConfiguration), &existingConfiguration) switch { - case err == nil: - return p.updateConfiguration(expectedConfiguration, &existingConfiguration) - case apierrors.IsNotFound(err): - return p.ResourceFetcher.Client.Create(p.Context, expectedConfiguration) + case expectedObject == nil: + return p.Client.Delete(p.Context, existingObject) + case existingObject == nil: + return p.Client.Create(p.Context, expectedObject) default: - return err + return updateComponentParameters(p.Context, p.Client, expectedObject, existingObject) } } return p.Wrap(buildConfiguration) @@ -119,130 +131,39 @@ func (p *pipeline) UpdateConfiguration() *pipeline { func (p *pipeline) CreateConfigTemplate() *pipeline { return p.Wrap(func() error { ctx := p.ctx - return p.renderWrapper.renderConfigTemplate(ctx.Cluster, ctx.SynthesizedComponent, ctx.Cache, p.ConfigurationObj) - }) -} - -func (p *pipeline) UpdateConfigurationStatus() *pipeline { - return p.Wrap(func() error { - if p.ConfigurationObj == nil { - return nil - } - - existing := p.ConfigurationObj - reversion := fromConfiguration(existing) - patch := client.MergeFrom(existing) - updated := existing.DeepCopy() - for _, item := range existing.Spec.ConfigItemDetails { - CheckAndUpdateItemStatus(updated, item, reversion) - } - return p.ResourceFetcher.Client.Status().Patch(p.Context, updated, patch) + revision := strconv.FormatInt(p.ComponentParameterObj.GetGeneration(), 10) + return p.renderWrapper.renderConfigTemplate(ctx.Cluster, ctx.SynthesizedComponent, ctx.Cache, p.ComponentParameterObj, p.configRender, p.parametersDefs, revision) }) } -func CheckAndUpdateItemStatus(updated *appsv1alpha1.Configuration, item appsv1alpha1.ConfigurationItemDetail, reversion string) { - foundStatus := func(name string) *appsv1alpha1.ConfigurationItemDetailStatus { - for i := range updated.Status.ConfigurationItemStatus { - status := &updated.Status.ConfigurationItemStatus[i] - if status.Name == name { - return status - } - } - return nil - } - - status := foundStatus(item.Name) - if status != nil && status.Phase == "" { - status.Phase = appsv1alpha1.CInitPhase - } - if status == nil { - updated.Status.ConfigurationItemStatus = append(updated.Status.ConfigurationItemStatus, - appsv1alpha1.ConfigurationItemDetailStatus{ - Name: item.Name, - Phase: appsv1alpha1.CInitPhase, - UpdateRevision: reversion, - }) - } -} - func (p *pipeline) UpdatePodVolumes() *pipeline { + mapName := func(tpl appsv1.ComponentTemplateSpec) string { + return tpl.Name + } return p.Wrap(func() error { return intctrlutil.CreateOrUpdatePodVolumes(p.ctx.PodSpec, p.renderWrapper.volumes, - configSetFromComponent(p.ctx.SynthesizedComponent.ConfigTemplates)) + generics.Map(p.ctx.SynthesizedComponent.ConfigTemplates, mapName)) }) } func (p *pipeline) BuildConfigManagerSidecar() *pipeline { return p.Wrap(func() error { - return buildConfigManagerWithComponent(p.ctx.PodSpec, p.ctx.SynthesizedComponent.ConfigTemplates, p.Context, p.Client, p.ctx.Cluster, p.ctx.SynthesizedComponent) + return buildConfigManagerWithComponent(p.ctx.ResourceCtx, p.ctx.Cluster, p.ctx.SynthesizedComponent, p.ComponentDefObj) }) } func (p *pipeline) UpdateConfigRelatedObject() *pipeline { updateMeta := func() error { - if err := injectTemplateEnvFrom(p.ctx.Cluster, p.ctx.SynthesizedComponent, p.ctx.PodSpec, p.Client, p.Context, p.renderWrapper.renderedObjs); err != nil { + if err := syncInjectEnvFromCM(p.Context, p.Client, p.ctx.SynthesizedComponent, p.configRender, p.renderWrapper.renderedObjs, true); err != nil { return err } - return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs, p.renderWrapper.renderedSecretObjs) + return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs) } return p.Wrap(updateMeta) } -func (p *pipeline) createConfiguration() *appsv1alpha1.Configuration { - builder := builder.NewConfigurationBuilder(p.Namespace, - core.GenerateComponentConfigurationName(p.ClusterName, p.ComponentName), - ) - for _, template := range p.ctx.SynthesizedComponent.ConfigTemplates { - builder.AddConfigurationItem(template) - } - return builder.Component(p.ComponentName). - ClusterRef(p.ClusterName). - AddLabelsInMap(constant.GetCompLabels(p.ClusterName, p.ComponentName)). - GetObject() -} - -func (p *pipeline) updateConfiguration(expected *appsv1alpha1.Configuration, existing *appsv1alpha1.Configuration) error { - fromMap := func(items []appsv1alpha1.ConfigurationItemDetail) *cfgutil.Sets { - sets := cfgutil.NewSet() - for _, item := range items { - sets.Add(item.Name) - } - return sets - } - - updateConfigSpec := func(item appsv1alpha1.ConfigurationItemDetail) appsv1alpha1.ConfigurationItemDetail { - if newItem := expected.Spec.GetConfigurationItem(item.Name); newItem != nil { - item.ConfigSpec = newItem.ConfigSpec - } - return item - } - - oldSets := fromMap(existing.Spec.ConfigItemDetails) - newSets := fromMap(expected.Spec.ConfigItemDetails) - - addSets := cfgutil.Difference(newSets, oldSets) - delSets := cfgutil.Difference(oldSets, newSets) - - newConfigItems := make([]appsv1alpha1.ConfigurationItemDetail, 0) - for _, item := range existing.Spec.ConfigItemDetails { - if !delSets.InArray(item.Name) { - newConfigItems = append(newConfigItems, updateConfigSpec(item)) - } - } - for _, item := range expected.Spec.ConfigItemDetails { - if addSets.InArray(item.Name) { - newConfigItems = append(newConfigItems, item) - } - } - - patch := client.MergeFrom(existing) - updated := existing.DeepCopy() - updated.Spec.ConfigItemDetails = newConfigItems - return p.Client.Patch(p.Context, updated, patch) -} - func (p *updatePipeline) isDone() bool { return !p.reconcile } @@ -254,34 +175,19 @@ func (p *updatePipeline) PrepareForTemplate() *updatePipeline { return } p.renderWrapper = newTemplateRenderWrapper(p.Context, p.Client, render.NewTemplateBuilder(&p.ctx), p.ctx.Cluster, p.ctx.Component) + p.configRender, p.parametersDefs, err = intctrlutil.ResolveCmpdParametersDefs(p.Context, p.Client, p.ComponentDefObj) return } return p.Wrap(buildTemplate) } -func (p *updatePipeline) ConfigSpec() *appsv1.ComponentConfigSpec { - return p.configSpec -} - -func (p *updatePipeline) InitConfigSpec() *updatePipeline { - return p.Wrap(func() (err error) { - if p.configSpec == nil { - p.configSpec = component.GetConfigSpecByName(p.ctx.SynthesizedComponent, p.item.Name) - if p.configSpec == nil { - return core.MakeError("not found config spec: %s", p.item.Name) - } - } - return - }) -} - func (p *updatePipeline) RerenderTemplate() *updatePipeline { return p.Wrap(func() (err error) { if p.isDone() { return } if intctrlutil.IsRerender(p.ConfigMapObj, p.item) { - p.newCM, err = p.renderWrapper.rerenderConfigTemplate(p.ctx.Cluster, p.ctx.SynthesizedComponent, *p.configSpec, &p.item) + p.newCM, err = p.renderWrapper.rerenderConfigTemplate(p.ctx.Cluster, p.ctx.SynthesizedComponent, *p.configSpec, &p.item, p.configRender, p.parametersDefs) } else { p.newCM = p.ConfigMapObj.DeepCopy() } @@ -290,23 +196,22 @@ func (p *updatePipeline) RerenderTemplate() *updatePipeline { } func (p *updatePipeline) ApplyParameters() *updatePipeline { - patchMerge := func(p *updatePipeline, spec appsv1.ComponentConfigSpec, cm *corev1.ConfigMap, item appsv1alpha1.ConfigurationItemDetail) error { - if p.isDone() || len(item.ConfigFileParams) == 0 { + patchMerge := func(p *updatePipeline, cm *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail) error { + if p.isDone() || len(item.ConfigFileParams) == 0 || p.configRender == nil { return nil } - newData, err := DoMerge(cm.Data, item.ConfigFileParams, p.ConfigConstraintObj, spec) + newData, err := DoMerge(cm.Data, item.ConfigFileParams, p.parametersDefs, p.configRender.Spec.Configs) if err != nil { return err } - if p.ConfigConstraintObj == nil { + if p.configRender == nil { cm.Data = newData return nil } p.configPatch, _, err = core.CreateConfigPatch(cm.Data, newData, - p.ConfigConstraintObj.Spec.FileFormatConfig.Format, - p.configSpec.Keys, + p.configRender.Spec, false) if err != nil { return err @@ -319,7 +224,7 @@ func (p *updatePipeline) ApplyParameters() *updatePipeline { if p.isDone() { return nil } - return patchMerge(p, *p.configSpec, p.newCM, p.item) + return patchMerge(p, p.newCM, p.item) }) } @@ -350,14 +255,15 @@ func (p *updatePipeline) UpdateConfigVersion(revision string) *updatePipeline { // TODO(leon) func (p *updatePipeline) Sync() *updatePipeline { return p.Wrap(func() error { - if p.ConfigConstraintObj != nil && !p.isDone() { - if err := SyncEnvSourceObject(*p.configSpec, p.newCM, &p.ConfigConstraintObj.Spec, p.Client, p.Context, p.ctx.Cluster, p.ctx.SynthesizedComponent); err != nil { + if !p.isDone() { + if err := syncInjectEnvFromCM(p.Context, p.Client, p.ctx.SynthesizedComponent, p.configRender, []*corev1.ConfigMap{p.newCM}, false); err != nil { return err } } - if InjectEnvEnabled(*p.configSpec) && toSecret(*p.configSpec) { - return nil + if err := intctrlutil.SetControllerReference(p.ComponentParameterObj, p.newCM); err != nil { + return err } + switch { case p.isDone(): return nil @@ -375,15 +281,29 @@ func (p *updatePipeline) Sync() *updatePipeline { }) } -func (p *updatePipeline) SyncStatus() *updatePipeline { - return p.Wrap(func() (err error) { - if p.isDone() { - return +func syncInjectEnvFromCM(ctx context.Context, cli client.Client, synthesizedComp *component.SynthesizedComponent, configRender *parametersv1alpha1.ParameterDrivenConfigRender, configMaps []*corev1.ConfigMap, onlyCreate bool) error { + var podSpec *corev1.PodSpec + + if onlyCreate { + podSpec = synthesizedComp.PodSpec + } + envObjs, err := InjectTemplateEnvFrom(synthesizedComp, podSpec, configRender, configMaps) + if err != nil { + return err + } + for _, obj := range envObjs { + if err = cli.Create(ctx, obj, inDataContext()); err == nil { + continue } - if p.configSpec == nil || p.itemStatus == nil { - return + if !apierrors.IsAlreadyExists(err) { + return err } - p.itemStatus.Phase = appsv1alpha1.CMergedPhase - return - }) + if onlyCreate { + continue + } + if err = cli.Update(ctx, obj, inDataContext()); err != nil { + return err + } + } + return nil } diff --git a/pkg/controller/configuration/pipeline_test.go b/pkg/controller/configuration/pipeline_test.go index b037cf77e41..f250495596c 100644 --- a/pkg/controller/configuration/pipeline_test.go +++ b/pkg/controller/configuration/pipeline_test.go @@ -28,12 +28,11 @@ import ( "github.com/golang/mock/gomock" 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" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/controller/builder" @@ -41,6 +40,7 @@ import ( "github.com/apecloud/kubeblocks/pkg/controller/render" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("ConfigurationPipelineTest", func() { @@ -51,8 +51,9 @@ var _ = Describe("ConfigurationPipelineTest", func() { var compDefObj *appsv1.ComponentDefinition var synthesizedComponent *component.SynthesizedComponent var configMapObj *corev1.ConfigMap - var configConstraint *appsv1beta1.ConfigConstraint - var configurationObj *appsv1alpha1.Configuration + var parametersDef *parametersv1alpha1.ParametersDefinition + var componentParameter *parametersv1alpha1.ComponentParameter + var configRender *parametersv1alpha1.ParameterDrivenConfigRender var k8sMockClient *testutil.K8sClientMockHelper mockAPIResource := func(lazyFetcher testutil.Getter) { @@ -62,28 +63,34 @@ var _ = Describe("ConfigurationPipelineTest", func() { clusterObj, clusterObj, configMapObj, - configConstraint, - configurationObj, + parametersDef, + componentObj, + componentParameter, + configRender, }, lazyFetcher), testutil.WithAnyTimes())) k8sMockClient.MockCreateMethod(testutil.WithCreateReturned(testutil.WithCreatedSucceedResult(), testutil.WithAnyTimes())) k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { switch v := obj.(type) { - case *appsv1alpha1.Configuration: - if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) { - configurationObj.Spec = *v.Spec.DeepCopy() - configurationObj.Status = *v.Status.DeepCopy() + case *parametersv1alpha1.ComponentParameter: + if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(componentParameter) { + componentParameter.Spec = *v.Spec.DeepCopy() + componentParameter.Status = *v.Status.DeepCopy() } } return nil }, testutil.WithAnyTimes())) + k8sMockClient.MockNListMethod(0, testutil.WithListReturned( + testutil.WithConstructListReturnedResult([]runtime.Object{configRender}), + testutil.WithAnyTimes(), + )) k8sMockClient.MockStatusMethod(). EXPECT(). Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { switch v := obj.(type) { - case *appsv1alpha1.Configuration: - if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(configurationObj) { - configurationObj.Status = *v.Status.DeepCopy() + case *parametersv1alpha1.ComponentParameter: + if client.ObjectKeyFromObject(obj) == client.ObjectKeyFromObject(componentParameter) { + componentParameter.Status = *v.Status.DeepCopy() } } return nil @@ -109,20 +116,20 @@ checkpoint_flush_after = '32' checkpoint_timeout = '15min' max_connections = '1000' `)) - configurationObj = builder.NewConfigurationBuilder(testCtx.DefaultNamespace, + componentParameter = builder.NewComponentParameterBuilder(testCtx.DefaultNamespace, cfgcore.GenerateComponentConfigurationName(clusterName, mysqlCompName)). ClusterRef(clusterName). Component(mysqlCompName). GetObject() - configConstraint = &appsv1beta1.ConfigConstraint{ - ObjectMeta: metav1.ObjectMeta{ - Name: mysqlConfigConstraintName, - }, - Spec: appsv1beta1.ConfigConstraintSpec{ - FileFormatConfig: &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, - }, - }} + + parametersDef = testparameters.NewParametersDefinitionFactory(paramsDefName).GetObject() + configRender = testparameters.NewParametersDrivenConfigFactory(pdcrName). + SetConfigDescription(testConfigFile, configTemplateName, parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). + SetComponentDefinition(compDefObj.Name). + SetParametersDefs(paramsDefName). + GetObject() + parametersDef.Status.Phase = parametersv1alpha1.PDAvailablePhase + configRender.Status.Phase = parametersv1alpha1.PDAvailablePhase }) AfterEach(func() { @@ -131,9 +138,6 @@ max_connections = '1000' Context("ConfigPipelineTest", func() { It("NormalTest", func() { - By("mock configSpec keys") - synthesizedComponent.ConfigTemplates[0].Keys = []string{testConfigFile} - By("create configuration resource") createPipeline := NewCreatePipeline(render.ReconcileCtx{ ResourceCtx: &render.ResourceCtx{ @@ -163,20 +167,25 @@ max_connections = '1000' return false, nil }) - err := createPipeline.Prepare(). - UpdateConfiguration(). // reconcile Configuration - Configuration(). // sync Configuration + err := createPipeline. + ComponentAndComponentDef(). + Prepare(). + SyncComponentParameter(). + ComponentParameter(). CreateConfigTemplate(). UpdatePodVolumes(). BuildConfigManagerSidecar(). UpdateConfigRelatedObject(). - UpdateConfigurationStatus(). Complete() Expect(err).Should(Succeed()) By("update configuration resource for mocking reconfiguring") - item := configurationObj.Spec.ConfigItemDetails[0] - item.ConfigFileParams = map[string]appsv1alpha1.ConfigParams{ + item := componentParameter.Spec.ConfigItemDetails[0] + status := ¶metersv1alpha1.ConfigTemplateItemDetailStatus{ + Name: item.Name, + Phase: parametersv1alpha1.CInitPhase, + } + item.ConfigFileParams = map[string]parametersv1alpha1.ParametersInFile{ testConfigFile: { Parameters: map[string]*string{ "max_connections": cfgutil.ToPointer("2000"), @@ -192,34 +201,16 @@ max_connections = '1000' Component: componentObj, SynthesizedComponent: synthesizedComponent, PodSpec: synthesizedComponent.PodSpec, - }, item, &configurationObj.Status.ConfigurationItemStatus[0], nil) + }, item, status, configMapObj, componentParameter) By("update configuration resource") - err = reconcileTask.InitConfigSpec(). - Configuration(). - ConfigMap(configTemplateName). - ConfigConstraints(reconcileTask.ConfigSpec().ConfigConstraintRef). - PrepareForTemplate(). - RerenderTemplate(). - ApplyParameters(). - UpdateConfigVersion(strconv.FormatInt(reconcileTask.ConfigurationObj.GetGeneration(), 10)). - Sync(). - SyncStatus(). - Complete() - Expect(err).Should(Succeed()) - - By("rerender configuration template") - reconcileTask.item.Version = "v2" - err = reconcileTask.InitConfigSpec(). - Configuration(). - ConfigMap(configTemplateName). - ConfigConstraints(reconcileTask.ConfigSpec().ConfigConstraintRef). + err = reconcileTask. + ComponentAndComponentDef(). PrepareForTemplate(). RerenderTemplate(). ApplyParameters(). - UpdateConfigVersion(strconv.FormatInt(reconcileTask.ConfigurationObj.GetGeneration(), 10)). + UpdateConfigVersion(strconv.FormatInt(reconcileTask.ComponentParameterObj.GetGeneration(), 10)). Sync(). - SyncStatus(). Complete() Expect(err).Should(Succeed()) }) diff --git a/pkg/controller/configuration/resource_wrapper.go b/pkg/controller/configuration/resource_wrapper.go index fac7eabc839..b327a591786 100644 --- a/pkg/controller/configuration/resource_wrapper.go +++ b/pkg/controller/configuration/resource_wrapper.go @@ -34,7 +34,7 @@ import ( cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/render" - "github.com/apecloud/kubeblocks/pkg/controllerutil" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) type ResourceFetcher[T any] struct { @@ -86,7 +86,7 @@ func (r *ResourceFetcher[T]) ComponentAndComponentDef() *T { } return r.Wrap(func() error { r.ComponentObj = &appsv1.Component{} - err := r.Client.Get(r.Context, componentKey, r.ComponentObj) + err := r.Client.Get(r.Context, componentKey, r.ComponentObj, inDataContext()) if apierrors.IsNotFound(err) { return nil } else if err != nil { @@ -101,7 +101,7 @@ func (r *ResourceFetcher[T]) ComponentAndComponentDef() *T { Name: r.ComponentObj.Spec.CompDef, } r.ComponentDefObj = &appsv1.ComponentDefinition{} - if err := r.Client.Get(r.Context, compDefKey, r.ComponentDefObj); err != nil { + if err := r.Client.Get(r.Context, compDefKey, r.ComponentDefObj, inDataContext()); err != nil { return err } if r.ComponentDefObj.Status.Phase != appsv1.AvailablePhase { @@ -113,7 +113,7 @@ func (r *ResourceFetcher[T]) ComponentAndComponentDef() *T { func (r *ResourceFetcher[T]) ComponentSpec() *T { return r.Wrap(func() (err error) { - r.ClusterComObj, err = controllerutil.GetComponentSpecByName(r.Context, r.Client, r.ClusterObj, r.ComponentName) + r.ClusterComObj, err = intctrlutil.GetComponentSpecByName(r.Context, r.Client, r.ClusterObj, r.ComponentName) if err != nil { return err } diff --git a/pkg/controller/configuration/template_merger.go b/pkg/controller/configuration/template_merger.go index 3ba346e6085..8c92ff8ef60 100644 --- a/pkg/controller/configuration/template_merger.go +++ b/pkg/controller/configuration/template_merger.go @@ -20,12 +20,10 @@ along with this program. If not, see . package configuration import ( - "context" - - "sigs.k8s.io/controller-runtime/pkg/client" + "fmt" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/render" ) @@ -40,11 +38,12 @@ type TemplateMerger interface { } type mergeContext struct { - template appsv1.ConfigTemplateExtension - configSpec appsv1.ComponentConfigSpec - ccSpec *appsv1beta1.ConfigConstraintSpec - render.TemplateRender + + template appsv1.ConfigTemplateExtension + configSpec appsv1.ComponentTemplateSpec + paramsDefs []*parametersv1alpha1.ParametersDefinition + configRender *parametersv1alpha1.ParameterDrivenConfigRender } func (m *mergeContext) renderTemplate() (map[string]string, error) { @@ -57,7 +56,7 @@ func (m *mergeContext) renderTemplate() (map[string]string, error) { if err != nil { return nil, err } - if err := validateRawData(configs, m.configSpec, m.ccSpec); err != nil { + if err := validateRenderedData(configs, m.paramsDefs, m.configRender); err != nil { return nil, err } return configs, nil @@ -84,8 +83,10 @@ type configOnlyAddMerger struct { } func (c *configPatcher) Merge(baseData map[string]string, updatedData map[string]string) (map[string]string, error) { - formatter := c.ccSpec.FileFormatConfig - configPatch, err := core.TransformConfigPatchFromData(updatedData, formatter.Format, c.configSpec.Keys) + if c.configRender == nil || len(c.configRender.Spec.Configs) == 0 { + return nil, fmt.Errorf("not support patch merge policy") + } + configPatch, err := core.TransformConfigPatchFromData(updatedData, c.configRender.Spec) if err != nil { return nil, err } @@ -93,20 +94,26 @@ func (c *configPatcher) Merge(baseData map[string]string, updatedData map[string return baseData, nil } + params := core.GenerateVisualizedParamsList(configPatch, c.configRender.Spec.Configs) mergedData := copyMap(baseData) - params := core.GenerateVisualizedParamsList(configPatch, formatter, nil) for key, patch := range splitParameters(params) { v, ok := baseData[key] if !ok { mergedData[key] = updatedData[key] continue } - newConfig, err := core.ApplyConfigPatch([]byte(v), patch, formatter) + newConfig, err := core.ApplyConfigPatch([]byte(v), patch, core.ResolveConfigFormat(c.configRender.Spec.Configs, key)) if err != nil { return nil, err } mergedData[key] = newConfig } + + for key, content := range updatedData { + if _, ok := mergedData[key]; !ok { + mergedData[key] = content + } + } return mergedData, err } @@ -120,14 +127,16 @@ func (c *configOnlyAddMerger) Merge(baseData map[string]string, updatedData map[ func NewTemplateMerger(template appsv1.ConfigTemplateExtension, templateRender render.TemplateRender, - configSpec appsv1.ComponentConfigSpec, - ccSpec *appsv1beta1.ConfigConstraintSpec) (TemplateMerger, error) { + configSpec appsv1.ComponentTemplateSpec, + paramsDefs []*parametersv1alpha1.ParametersDefinition, + configRender *parametersv1alpha1.ParameterDrivenConfigRender, +) (TemplateMerger, error) { templateData := &mergeContext{ - configSpec: configSpec, - template: template, - ccSpec: ccSpec, - + configSpec: configSpec, + template: template, TemplateRender: templateRender, + paramsDefs: paramsDefs, + configRender: configRender, } var merger TemplateMerger @@ -148,25 +157,11 @@ func NewTemplateMerger(template appsv1.ConfigTemplateExtension, func mergerConfigTemplate(template appsv1.ConfigTemplateExtension, templateRender render.TemplateRender, - configSpec appsv1.ComponentConfigSpec, + configSpec appsv1.ComponentTemplateSpec, baseData map[string]string, - ctx context.Context, cli client.Client) (map[string]string, error) { - if configSpec.ConfigConstraintRef == "" { - return nil, core.MakeError("ConfigConstraintRef require not empty, configSpec[%v]", configSpec.Name) - } - ccObj := &appsv1beta1.ConfigConstraint{} - ccKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - if err := cli.Get(ctx, ccKey, ccObj); err != nil { - return nil, core.WrapError(err, "failed to get ConfigConstraint, key[%v]", configSpec) - } - if ccObj.Spec.FileFormatConfig == nil { - return nil, core.MakeError("importedConfigTemplate require ConfigConstraint.Spec.FileFormatConfig, configSpec[%v]", configSpec) - } - - templateMerger, err := NewTemplateMerger(template, templateRender, configSpec, &ccObj.Spec) + paramsDefs []*parametersv1alpha1.ParametersDefinition, + configRender *parametersv1alpha1.ParameterDrivenConfigRender) (map[string]string, error) { + templateMerger, err := NewTemplateMerger(template, templateRender, configSpec, paramsDefs, configRender) if err != nil { return nil, err } diff --git a/pkg/controller/configuration/template_merger_test.go b/pkg/controller/configuration/template_merger_test.go index 332fae7bc9b..7f82692847f 100644 --- a/pkg/controller/configuration/template_merger_test.go +++ b/pkg/controller/configuration/template_merger_test.go @@ -27,15 +27,16 @@ 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" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/render" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("TemplateMergerTest", func() { @@ -71,10 +72,11 @@ max_connections=666 ) var ( - mockClient *testutil.K8sClientMockHelper - templateBuilder render.TemplateRender - configSpec appsv1.ComponentConfigSpec - configConstraintObj *appsv1beta1.ConfigConstraint + mockClient *testutil.K8sClientMockHelper + templateBuilder render.TemplateRender + configSpec appsv1.ComponentTemplateSpec + paramsDefs *parametersv1alpha1.ParametersDefinition + pdcr *parametersv1alpha1.ParameterDrivenConfigRender baseCMObject *corev1.ConfigMap updatedCMObject *corev1.ConfigMap @@ -82,9 +84,13 @@ max_connections=666 BeforeEach(func() { mockClient = testutil.NewK8sMockClient() - configConstraintObj = testapps.CheckedCreateCustomizedObj(&testCtx, - "resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) + paramsDefs = testparameters.NewParametersDefinitionFactory("test-pd"). + SetConfigFile(testConfigName). + GetObject() + pdcr = testparameters.NewParametersDrivenConfigFactory("test-pdcr"). + SetTemplateName(testConfigSpecName). + GetObject() + baseCMObject = &corev1.ConfigMap{ Data: map[string]string{ testConfigName: baseConfig, @@ -102,22 +108,18 @@ max_connections=666 updatedCMObject.SetName(updatedCMName) updatedCMObject.SetNamespace("default") - configSpec = appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: testConfigSpecName, - TemplateRef: baseCMObject.GetName(), - Namespace: "default", - }, - Keys: []string{"my.cnf"}, - ConfigConstraintRef: configConstraintObj.GetName(), + configSpec = appsv1.ComponentTemplateSpec{ + Name: testConfigSpecName, + TemplateRef: baseCMObject.GetName(), + Namespace: "default", } templateBuilder = render.NewTemplateBuilder(&render.ReconcileCtx{ ResourceCtx: &render.ResourceCtx{ Context: ctx, Client: mockClient.Client(), + Namespace: testCtx.DefaultNamespace, ClusterName: testClusterName, - Namespace: "default", }, Cluster: &appsv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ @@ -132,8 +134,11 @@ max_connections=666 mockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ baseCMObject, updatedCMObject, - configConstraintObj, }), testutil.WithAnyTimes())) + mockClient.MockNListMethod(0, testutil.WithListReturned( + testutil.WithConstructListReturnedResult([]runtime.Object{pdcr}), + testutil.WithAnyTimes(), + )) }) AfterEach(func() { @@ -150,11 +155,11 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, ctx, mockClient.Client()) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) Expect(err).To(Succeed()) Expect(mergedData).Should(HaveLen(2)) - configReaders, err := cfgcore.LoadRawConfigObject(mergedData, configConstraintObj.Spec.FileFormatConfig, configSpec.Keys) + configReaders, err := cfgcore.LoadRawConfigObject(mergedData, pdcr.Spec.Configs[0].FileFormatConfig, []string{testConfigName}) Expect(err).Should(Succeed()) Expect(configReaders).Should(HaveLen(1)) configObject := configReaders[testConfigName] @@ -177,12 +182,12 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, ctx, mockClient.Client()) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) Expect(err).Should(Succeed()) Expect(mergedData).Should(HaveLen(2)) Expect(reflect.DeepEqual(mergedData, updatedCMObject.Data)).Should(BeTrue()) - configReaders, err := cfgcore.LoadRawConfigObject(mergedData, configConstraintObj.Spec.FileFormatConfig, configSpec.Keys) + configReaders, err := cfgcore.LoadRawConfigObject(mergedData, pdcr.Spec.Configs[0].FileFormatConfig, []string{testConfigName}) Expect(err).Should(Succeed()) Expect(configReaders).Should(HaveLen(1)) configObject := configReaders[testConfigName] @@ -202,7 +207,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, ctx, mockClient.Client()) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) Expect(err).ShouldNot(Succeed()) }) }) @@ -216,7 +221,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, ctx, mockClient.Client()) + mergedData, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) Expect(err).Should(Succeed()) Expect(reflect.DeepEqual(mergedData, updatedCMObject.Data)).Should(BeTrue()) }) @@ -231,25 +236,11 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, ctx, mockClient.Client()) - Expect(err).ShouldNot(Succeed()) - }) - - It("not configconstraint", func() { - importedTemplate := appsv1.ConfigTemplateExtension{ - Namespace: "default", - TemplateRef: updatedCMObject.GetName(), - Policy: "none", - } - - tmpCM := baseCMObject.DeepCopy() - tmpConfigSpec := configSpec.DeepCopy() - tmpConfigSpec.ConfigConstraintRef = "" - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, *tmpConfigSpec, tmpCM.Data, ctx, mockClient.Client()) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, pdcr) Expect(err).ShouldNot(Succeed()) }) - It("not formatter", func() { + It("not parameterDrivenConfigRender", func() { importedTemplate := appsv1.ConfigTemplateExtension{ Namespace: "default", TemplateRef: updatedCMObject.GetName(), @@ -257,10 +248,8 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - tmpConfigSpec := configSpec.DeepCopy() - tmpConfigSpec.ConfigConstraintRef = "not_exist" - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, *tmpConfigSpec, tmpCM.Data, ctx, mockClient.Client()) - Expect(err).ShouldNot(Succeed()) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, ¶metersv1alpha1.ParameterDrivenConfigRender{}) + Expect(err).Should(Succeed()) }) }) }) diff --git a/pkg/controller/configuration/template_wrapper.go b/pkg/controller/configuration/template_wrapper.go index f3bde360380..f2585a3e9e4 100644 --- a/pkg/controller/configuration/template_wrapper.go +++ b/pkg/controller/configuration/template_wrapper.go @@ -22,9 +22,8 @@ package configuration import ( "context" "encoding/json" + "fmt" "reflect" - "strconv" - "strings" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -32,8 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/configuration/validate" @@ -49,9 +47,7 @@ type renderWrapper struct { volumes map[string]appsv1.ComponentTemplateSpec templateAnnotations map[string]string - renderedObjs []client.Object - - renderedSecretObjs []client.Object + renderedObjs []*corev1.ConfigMap ctx context.Context cli client.Client @@ -102,10 +98,13 @@ func (wrapper *renderWrapper) checkRerenderTemplateSpec(cfgCMName string, localO } func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1.Cluster, - component *component.SynthesizedComponent, localObjs []client.Object, configuration *appsv1alpha1.Configuration) error { - revision := fromConfiguration(configuration) + component *component.SynthesizedComponent, + localObjs []client.Object, + componentParameter *parametersv1alpha1.ComponentParameter, + configRender *parametersv1alpha1.ParameterDrivenConfigRender, + defs []*parametersv1alpha1.ParametersDefinition, revision string) error { for _, configSpec := range component.ConfigTemplates { - var item *appsv1alpha1.ConfigurationItemDetail + var item *parametersv1alpha1.ConfigTemplateItemDetail cmName := core.GetComponentCfgName(cluster.Name, component.Name, configSpec.Name) origCMObj, err := wrapper.checkRerenderTemplateSpec(cmName, localObjs) if err != nil { @@ -116,20 +115,21 @@ func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1.Cluster, // and does not update the ConfigMap objects in the subsequent reconfiguration process. // The subsequent reconfiguration process is handled by the Configuration controller. if origCMObj != nil { - wrapper.addVolumeMountMeta(configSpec.ComponentTemplateSpec, origCMObj, false, !toSecret(configSpec)) + wrapper.addVolumeMountMeta(configSpec, origCMObj, false) continue } - if configuration != nil { - item = configuration.Spec.GetConfigurationItem(configSpec.Name) + item = intctrlutil.GetConfigTemplateItem(&componentParameter.Spec, configSpec.Name) + if item == nil { + return fmt.Errorf("config template item not found: %s", configSpec.Name) } - newCMObj, err := wrapper.rerenderConfigTemplate(cluster, component, configSpec, item) + newCMObj, err := wrapper.rerenderConfigTemplate(cluster, component, configSpec, item, configRender, defs) if err != nil { return err } - if err := applyUpdatedParameters(item, newCMObj, configSpec, wrapper.cli, wrapper.ctx); err != nil { + if newCMObj, err = applyUpdatedParameters(item, newCMObj, configRender, defs); err != nil { return err } - if err := wrapper.addRenderedObject(configSpec.ComponentTemplateSpec, newCMObj, configuration, !toSecret(configSpec)); err != nil { + if err := wrapper.addRenderedObject(configSpec, newCMObj, componentParameter); err != nil { return err } if err := updateConfigMetaForCM(newCMObj, item, revision); err != nil { @@ -139,14 +139,7 @@ func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1.Cluster, return nil } -func fromConfiguration(configuration *appsv1alpha1.Configuration) string { - if configuration == nil { - return "" - } - return strconv.FormatInt(configuration.GetGeneration(), 10) -} - -func updateConfigMetaForCM(newCMObj *corev1.ConfigMap, item *appsv1alpha1.ConfigurationItemDetail, revision string) (err error) { +func updateConfigMetaForCM(newCMObj *corev1.ConfigMap, item *parametersv1alpha1.ConfigTemplateItemDetail, revision string) (err error) { if item == nil { return } @@ -163,78 +156,69 @@ func updateConfigMetaForCM(newCMObj *corev1.ConfigMap, item *appsv1alpha1.Config hash, _ := cfgutil.ComputeHash(newCMObj.Data) annotations[constant.CMInsCurrentConfigurationHashLabelKey] = hash annotations[constant.ConfigurationRevision] = revision - annotations[constant.CMConfigurationTemplateVersion] = item.Version newCMObj.Annotations = annotations return } -func applyUpdatedParameters(item *appsv1alpha1.ConfigurationItemDetail, cm *corev1.ConfigMap, configSpec appsv1.ComponentConfigSpec, cli client.Client, ctx context.Context) (err error) { - var newData map[string]string - var configConstraint *appsv1beta1.ConfigConstraint - - if item == nil || len(item.ConfigFileParams) == 0 { - return - } - if configSpec.ConfigConstraintRef != "" { - configConstraint, err = fetchConfigConstraint(configSpec.ConfigConstraintRef, ctx, cli) - } - if err != nil { - return +func applyUpdatedParameters(item *parametersv1alpha1.ConfigTemplateItemDetail, orig *corev1.ConfigMap, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { + if configRender == nil || len(configRender.Spec.Configs) == 0 { + return nil, fmt.Errorf("not support parameter reconfigure") } - newData, err = DoMerge(cm.Data, item.ConfigFileParams, configConstraint, configSpec) + + newData, err := DoMerge(orig.Data, item.ConfigFileParams, paramsDefs, configRender.Spec.Configs) if err != nil { - return + return nil, err } - cm.Data = newData - return + + expected := orig.DeepCopy() + expected.Data = newData + return expected, nil } func (wrapper *renderWrapper) rerenderConfigTemplate(cluster *appsv1.Cluster, component *component.SynthesizedComponent, - configSpec appsv1.ComponentConfigSpec, - item *appsv1alpha1.ConfigurationItemDetail, -) (*corev1.ConfigMap, error) { + configSpec appsv1.ComponentTemplateSpec, + item *parametersv1alpha1.ConfigTemplateItemDetail, + configRender *parametersv1alpha1.ParameterDrivenConfigRender, + defs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { cmName := core.GetComponentCfgName(cluster.Name, component.Name, configSpec.Name) - newCMObj, err := wrapper.RenderComponentTemplate(configSpec.ComponentTemplateSpec, cmName, func(m map[string]string) error { - return validateRenderedData(m, configSpec, wrapper.ctx, wrapper.cli) + newCMObj, err := wrapper.RenderComponentTemplate(configSpec, cmName, func(m map[string]string) error { + return validateRenderedData(m, defs, configRender) }) if err != nil { return nil, err } // render user specified template - if item != nil && item.ImportTemplateRef != nil { + if item != nil && item.CustomTemplates != nil { newData, err := mergerConfigTemplate( appsv1.ConfigTemplateExtension{ - TemplateRef: item.ImportTemplateRef.TemplateRef, - Namespace: item.ImportTemplateRef.Namespace, - Policy: appsv1.MergedPolicy(item.ImportTemplateRef.Policy), + TemplateRef: item.CustomTemplates.TemplateRef, + Namespace: item.CustomTemplates.Namespace, + Policy: item.CustomTemplates.Policy, }, wrapper.TemplateRender, configSpec, newCMObj.Data, - wrapper.ctx, - wrapper.cli) + defs, + configRender) if err != nil { return nil, err } newCMObj.Data = newData } UpdateCMConfigSpecLabels(newCMObj, configSpec) - if InjectEnvEnabled(configSpec) && toSecret(configSpec) { - wrapper.renderedSecretObjs = append(wrapper.renderedSecretObjs, newCMObj) - } return newCMObj, nil } func (wrapper *renderWrapper) renderScriptTemplate(cluster *appsv1.Cluster, component *component.SynthesizedComponent, - localObjs []client.Object) error { + localObjs []client.Object, owner client.Object) error { for _, templateSpec := range component.ScriptTemplates { cmName := core.GetComponentCfgName(cluster.Name, component.Name, templateSpec.Name) object := findMatchedLocalObject(localObjs, client.ObjectKey{ Name: cmName, Namespace: wrapper.cluster.Namespace}, generics.ToGVK(&corev1.ConfigMap{})) if object != nil { - wrapper.addVolumeMountMeta(templateSpec, object, false, true) + wrapper.addVolumeMountMeta(templateSpec, object.(*corev1.ConfigMap), false) continue } @@ -243,34 +227,27 @@ func (wrapper *renderWrapper) renderScriptTemplate(cluster *appsv1.Cluster, comp if err != nil { return err } - if err := wrapper.addRenderedObject(templateSpec, cm, nil, true); err != nil { + if err := wrapper.addRenderedObject(templateSpec, cm, owner); err != nil { return err } } return nil } -func (wrapper *renderWrapper) addRenderedObject(templateSpec appsv1.ComponentTemplateSpec, cm *corev1.ConfigMap, configuration *appsv1alpha1.Configuration, asVolume bool) (err error) { +func (wrapper *renderWrapper) addRenderedObject(templateSpec appsv1.ComponentTemplateSpec, cm *corev1.ConfigMap, owner client.Object) (err error) { // The owner of the configmap object is a cluster, // in order to manage the life cycle of configmap - if configuration != nil { - err = intctrlutil.SetControllerReference(configuration, cm) - } else { - err = intctrlutil.SetControllerReference(wrapper.component, cm) - } - if err != nil { + if err = intctrlutil.SetControllerReference(owner, cm); err != nil { return err } core.SetParametersUpdateSource(cm, constant.ReconfigureManagerSource) - wrapper.addVolumeMountMeta(templateSpec, cm, true, asVolume) + wrapper.addVolumeMountMeta(templateSpec, cm, true) return nil } -func (wrapper *renderWrapper) addVolumeMountMeta(templateSpec appsv1.ComponentTemplateSpec, object client.Object, rendered bool, asVolume bool) { - if asVolume { - wrapper.volumes[object.GetName()] = templateSpec - } +func (wrapper *renderWrapper) addVolumeMountMeta(templateSpec appsv1.ComponentTemplateSpec, object *corev1.ConfigMap, rendered bool) { + wrapper.volumes[object.GetName()] = templateSpec if rendered { wrapper.renderedObjs = append(wrapper.renderedObjs, object) } @@ -311,51 +288,47 @@ func findMatchedLocalObject(localObjs []client.Object, objKey client.ObjectKey, return nil } -func UpdateCMConfigSpecLabels(cm *corev1.ConfigMap, configSpec appsv1.ComponentConfigSpec) { +func UpdateCMConfigSpecLabels(cm *corev1.ConfigMap, configSpec appsv1.ComponentTemplateSpec) { if cm.Labels == nil { cm.Labels = make(map[string]string) } - cm.Labels[constant.CMConfigurationSpecProviderLabelKey] = configSpec.Name cm.Labels[constant.CMConfigurationTemplateNameLabelKey] = configSpec.TemplateRef - if configSpec.ConfigConstraintRef != "" { - cm.Labels[constant.CMConfigurationConstraintsNameLabelKey] = configSpec.ConfigConstraintRef - } - - if len(configSpec.Keys) != 0 { - cm.Labels[constant.CMConfigurationCMKeysLabelKey] = strings.Join(configSpec.Keys, ",") - } } -func fetchConfigConstraint(ccName string, ctx context.Context, cli client.Client) (*appsv1beta1.ConfigConstraint, error) { - ccKey := client.ObjectKey{ - Name: ccName, +// validateRenderedData validates config file against constraint +func validateRenderedData(renderedData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, configRender *parametersv1alpha1.ParameterDrivenConfigRender) error { + if len(paramsDefs) == 0 || configRender == nil || len(configRender.Spec.Configs) == 0 { + return nil } - configConstraint := &appsv1beta1.ConfigConstraint{} - if err := cli.Get(ctx, ccKey, configConstraint); err != nil { - return nil, core.WrapError(err, "failed to get ConfigConstraint, key[%s]", ccName) + for _, paramsDef := range paramsDefs { + fileName := paramsDef.Spec.FileName + if paramsDef.Spec.ParametersSchema == nil { + continue + } + if _, ok := renderedData[fileName]; !ok { + continue + } + if fileConfig := resolveFileFormatConfig(configRender.Spec.Configs, fileName); fileConfig != nil { + if err := validateConfigContent(renderedData[fileName], ¶msDef.Spec, fileConfig); err != nil { + return err + } + } } - return configConstraint, nil + return nil } -// validateRenderedData validates config file against constraint -func validateRenderedData( - renderedData map[string]string, - configSpec appsv1.ComponentConfigSpec, - ctx context.Context, - cli client.Client) error { - if configSpec.ConfigConstraintRef == "" { - return nil - } - configConstraint, err := fetchConfigConstraint(configSpec.ConfigConstraintRef, ctx, cli) - if err != nil { - return err +func resolveFileFormatConfig(configDescs []parametersv1alpha1.ComponentConfigDescription, fileName string) *parametersv1alpha1.FileFormatConfig { + for i, configDesc := range configDescs { + if fileName == configDesc.Name { + return configDescs[i].FileFormatConfig + } } - return validateRawData(renderedData, configSpec, &configConstraint.Spec) + return nil } -func validateRawData(renderedData map[string]string, configSpec appsv1.ComponentConfigSpec, cc *appsv1beta1.ConfigConstraintSpec) error { - configChecker := validate.NewConfigValidator(cc, validate.WithKeySelector(configSpec.Keys)) +func validateConfigContent(renderedData string, paramsDef *parametersv1alpha1.ParametersDefinitionSpec, fileFormat *parametersv1alpha1.FileFormatConfig) error { + configChecker := validate.NewConfigValidator(paramsDef.ParametersSchema, fileFormat) // NOTE: It is necessary to verify the correctness of the data if err := configChecker.Validate(renderedData); err != nil { return core.WrapError(err, "failed to validate configmap") diff --git a/pkg/controller/configuration/tool_image_builder.go b/pkg/controller/configuration/tool_image_builder.go index 410665409e0..eb32d077b26 100644 --- a/pkg/controller/configuration/tool_image_builder.go +++ b/pkg/controller/configuration/tool_image_builder.go @@ -24,7 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/factory" @@ -49,7 +49,7 @@ func buildReloadToolsContainer(cfgManagerParams *cfgcm.CfgManagerBuildParams, po var toolsPath string var sidecarImage string - var toolContainers []appsv1beta1.ToolConfig + var toolContainers []parametersv1alpha1.ToolConfig for _, buildParam := range cfgManagerParams.ConfigSpecsBuildParams { if buildParam.ToolsImageSpec == nil { continue @@ -60,7 +60,7 @@ func buildReloadToolsContainer(cfgManagerParams *cfgcm.CfgManagerBuildParams, po } toolsImageMap[toolImage.Name] = buildParam replaceToolsImageHolder(&toolImage, podSpec, buildParam.ConfigSpec.VolumeName) - if toolImage.AsSidecarContainerImage() && sidecarImage == "" { + if intctrlutil.AsSidecarContainerImage(toolImage) && sidecarImage == "" { sidecarImage = toolImage.Image } else { toolContainers = append(toolContainers, toolImage) @@ -87,7 +87,7 @@ func buildReloadToolsContainer(cfgManagerParams *cfgcm.CfgManagerBuildParams, po return err } -func checkAndInstallToolsImageVolume(toolContainers []appsv1beta1.ToolConfig, buildParams []cfgcm.ConfigSpecMeta, useBuiltinSidecarImage bool) ([]appsv1beta1.ToolConfig, string) { +func checkAndInstallToolsImageVolume(toolContainers []parametersv1alpha1.ToolConfig, buildParams []cfgcm.ConfigSpecMeta, useBuiltinSidecarImage bool) ([]parametersv1alpha1.ToolConfig, string) { var configManagerBinaryPath string for _, buildParam := range buildParams { if buildParam.ToolsImageSpec == nil { @@ -101,19 +101,19 @@ func checkAndInstallToolsImageVolume(toolContainers []appsv1beta1.ToolConfig, bu return toolContainers, configManagerBinaryPath } -func containerExists(containers []appsv1beta1.ToolConfig, containerName string) bool { - return generics.CountFunc(containers, func(container appsv1beta1.ToolConfig) bool { +func containerExists(containers []parametersv1alpha1.ToolConfig, containerName string) bool { + return generics.CountFunc(containers, func(container parametersv1alpha1.ToolConfig) bool { return container.Name == containerName }) != 0 } -func checkAndCreateConfigManagerToolsContainer(toolContainers []appsv1beta1.ToolConfig, mountPoint string) []appsv1beta1.ToolConfig { +func checkAndCreateConfigManagerToolsContainer(toolContainers []parametersv1alpha1.ToolConfig, mountPoint string) []parametersv1alpha1.ToolConfig { if containerExists(toolContainers, installConfigMangerToolContainerName) { return toolContainers } kbToolsImage := viper.GetString(constant.KBToolsImage) - toolContainers = append(toolContainers, appsv1beta1.ToolConfig{ + toolContainers = append(toolContainers, parametersv1alpha1.ToolConfig{ Name: installConfigMangerToolContainerName, Image: kbToolsImage, Command: []string{"cp", constant.ConfigManagerToolPath, mountPoint}, @@ -121,7 +121,7 @@ func checkAndCreateConfigManagerToolsContainer(toolContainers []appsv1beta1.Tool return toolContainers } -func replaceToolsImageHolder(toolConfig *appsv1beta1.ToolConfig, podSpec *corev1.PodSpec, volumeName string) { +func replaceToolsImageHolder(toolConfig *parametersv1alpha1.ToolConfig, podSpec *corev1.PodSpec, volumeName string) { switch { case toolConfig.Image == kbToolsImagePlaceHolder: toolConfig.Image = viper.GetString(constant.KBToolsImage) diff --git a/pkg/controller/configuration/tool_image_builder_test.go b/pkg/controller/configuration/tool_image_builder_test.go index 8ae252fb8d2..d40d287c80b 100644 --- a/pkg/controller/configuration/tool_image_builder_test.go +++ b/pkg/controller/configuration/tool_image_builder_test.go @@ -26,7 +26,7 @@ import ( corev1 "k8s.io/api/core/v1" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" @@ -67,12 +67,12 @@ var _ = Describe("ToolsImageBuilderTest", func() { ConfigSpecsBuildParams: []cfgcm.ConfigSpecMeta{{ ConfigSpecInfo: cfgcm.ConfigSpecInfo{ ConfigSpec: clusterComponent.ConfigTemplates[0], - ReloadType: appsv1beta1.TPLScriptType, - FormatterConfig: appsv1beta1.FileFormatConfig{}, + ReloadType: parametersv1alpha1.TPLScriptType, + FormatterConfig: parametersv1alpha1.FileFormatConfig{}, }, - ToolsImageSpec: &appsv1beta1.ToolsSetup{ + ToolsImageSpec: ¶metersv1alpha1.ToolsSetup{ MountPoint: "/opt/tools", - ToolConfigs: []appsv1beta1.ToolConfig{ + ToolConfigs: []parametersv1alpha1.ToolConfig{ { Name: "test", Image: "test_images", @@ -92,7 +92,6 @@ var _ = Describe("ToolsImageBuilderTest", func() { }, }, }}, - ConfigLazyRenderedVolumes: make(map[string]corev1.VolumeMount), } cfgManagerParams.ConfigSpecsBuildParams[0].ConfigSpec.VolumeName = "data" Expect(buildReloadToolsContainer(cfgManagerParams, &its.Spec.Template.Spec)).Should(Succeed()) diff --git a/pkg/controller/factory/builder.go b/pkg/controller/factory/builder.go index 428e4d9e41f..9f0df095f6b 100644 --- a/pkg/controller/factory/builder.go +++ b/pkg/controller/factory/builder.go @@ -29,7 +29,7 @@ import ( "k8s.io/klog/v2" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/common" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" @@ -292,7 +292,7 @@ func getSidecarBinaryPath(buildParams *cfgcm.CfgManagerBuildParams) string { return constant.ConfigManagerToolPath } -func BuildCfgManagerToolsContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, toolsMetas []appsv1beta1.ToolConfig, toolsMap map[string]cfgcm.ConfigSpecMeta) ([]corev1.Container, error) { +func BuildCfgManagerToolsContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, toolsMetas []parametersv1alpha1.ToolConfig, toolsMap map[string]cfgcm.ConfigSpecMeta) ([]corev1.Container, error) { toolContainers := make([]corev1.Container, 0, len(toolsMetas)) for _, toolConfig := range toolsMetas { toolContainerBuilder := builder.NewContainerBuilder(toolConfig.Name). diff --git a/pkg/controller/factory/builder_test.go b/pkg/controller/factory/builder_test.go index 7af11a7e784..7f6adb586b6 100644 --- a/pkg/controller/factory/builder_test.go +++ b/pkg/controller/factory/builder_test.go @@ -30,7 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/constant" @@ -131,15 +131,12 @@ var _ = Describe("builder", func() { It("builds ConfigMap with template correctly", func() { config := map[string]string{} _, cluster, synthesizedComponent := newClusterObjs(nil) - tplCfg := appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "test-config-tpl", - TemplateRef: "test-config-tpl", - }, - ConfigConstraintRef: "test-config-constraint", + tplCfg := appsv1.ComponentTemplateSpec{ + Name: "test-config-tpl", + TemplateRef: "test-config-tpl", } configmap := BuildConfigMapWithTemplate(cluster, synthesizedComponent, config, - "test-cm", tplCfg.ComponentTemplateSpec) + "test-cm", tplCfg) Expect(configmap).ShouldNot(BeNil()) }) @@ -182,12 +179,11 @@ var _ = Describe("builder", func() { It("builds cfg manager tools correctly", func() { _, cluster, _ := newClusterObjs(nil) cfgManagerParams := &cfgcm.CfgManagerBuildParams{ - ManagerName: constant.ConfigSidecarName, - Image: viper.GetString(constant.KBToolsImage), - Cluster: cluster, - ConfigLazyRenderedVolumes: make(map[string]corev1.VolumeMount), + ManagerName: constant.ConfigSidecarName, + Image: viper.GetString(constant.KBToolsImage), + Cluster: cluster, } - toolContainers := []appsv1beta1.ToolConfig{ + toolContainers := []parametersv1alpha1.ToolConfig{ {Name: "test-tool", Image: "test-image", Command: []string{"sh"}}, } diff --git a/pkg/controller/plan/prepare_test.go b/pkg/controller/plan/prepare_test.go index 96521aaec54..07440023fea 100644 --- a/pkg/controller/plan/prepare_test.go +++ b/pkg/controller/plan/prepare_test.go @@ -27,13 +27,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/render" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" ) var _ = Describe("Prepare Test", func() { @@ -48,10 +50,13 @@ var _ = Describe("Prepare Test", func() { ml := client.HasLabels{testCtx.TestObjLabelKey} // non-namespaced - testapps.ClearResources(&testCtx, generics.ConfigConstraintSignature, ml) + testapps.ClearResources(&testCtx, generics.ParameterDrivenConfigRenderSignature, ml) + testapps.ClearResources(&testCtx, generics.ParametersDefinitionSignature, ml) + testapps.ClearResources(&testCtx, generics.ComponentDefinitionSignature, ml) // namespaced testapps.ClearResources(&testCtx, generics.ConfigMapSignature, inNS, ml) + testapps.ClearResources(&testCtx, generics.ComponentSignature, inNS, ml) } BeforeEach(func() { @@ -66,13 +71,15 @@ var _ = Describe("Prepare Test", func() { compDefName = "test-compdef" clusterName = "test-cluster" mysqlCompName = "mysql" + paramsDefName = "mysql-params-def" + pdcrName = "mysql-pdcr" + envFileName = "test" ) var ( - compDefObj *appsv1.ComponentDefinition - cluster *appsv1.Cluster - comp *appsv1.Component - configSpecName string + compDefObj *appsv1.ComponentDefinition + cluster *appsv1.Cluster + comp *appsv1.Component ) Context("create cluster with component and component definition API, testing render configuration", func() { @@ -86,14 +93,50 @@ var _ = Describe("Prepare Test", func() { AddVolumeMounts("mysql", []corev1.VolumeMount{{Name: testapps.DefaultConfigSpecVolumeName, MountPath: "/mnt/config"}}). Create(&testCtx). GetObject() + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(compDefObj), func(obj *appsv1.ComponentDefinition) { + obj.Status.Phase = appsv1.AvailablePhase + })()).Should(Succeed()) } BeforeEach(func() { createAllTypesClusterDef() - testapps.CreateCustomizedObj(&testCtx, "config/envfrom-config.yaml", &corev1.ConfigMap{}, testCtx.UseDefaultNamespace()) - tpl := testapps.CreateCustomizedObj(&testCtx, "config/envfrom-constraint.yaml", &appsv1beta1.ConfigConstraint{}) - configSpecName = tpl.Name + parametersDef := testparameters.NewParametersDefinitionFactory(paramsDefName). + SetConfigFile(envFileName). + Schema(""). + Create(&testCtx). + GetObject() + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(parametersDef), func(obj *parametersv1alpha1.ParametersDefinition) { + obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + })()).Should(Succeed()) + + configRender := testparameters.NewParametersDrivenConfigFactory(pdcrName). + SetConfigDescription(envFileName, testapps.DefaultConfigSpecName, parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). + SetComponentDefinition(compDefObj.Name). + SetParametersDefs(paramsDefName). + Create(&testCtx). + GetObject() + + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(configRender), func(obj *parametersv1alpha1.ParameterDrivenConfigRender) { + config := intctrlutil.GetComponentConfigDescription(&obj.Spec, envFileName) + config.InjectEnvTo = []string{compDefObj.Spec.Runtime.Containers[0].Name} + })()).Should(Succeed()) + + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configRender), func(obj *parametersv1alpha1.ParameterDrivenConfigRender) { + obj.Status.Phase = parametersv1alpha1.PDAvailablePhase + })()).Should(Succeed()) + + testparameters.NewComponentTemplateFactory(testapps.DefaultConfigSpecTplRef, testCtx.DefaultNamespace). + AddConfigFile(envFileName, ` +dbStorage_rocksDB_writeBufferSizeMB=8 +dbStorage_rocksDB_sstSizeInMB=64 +dbStorage_rocksDB_blockSize=65536 +dbStorage_rocksDB_bloomFilterBitsPerKey=10 +dbStorage_rocksDB_numLevels=-1 +dbStorage_rocksDB_numFilesInLevel0=4 +dbStorage_rocksDB_maxSizeInLevel1MB=256 +`). + Create(&testCtx) pvcSpec := testapps.NewPVCSpec("1Gi") cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, ""). @@ -107,6 +150,7 @@ var _ = Describe("Prepare Test", func() { comp, err = component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) Expect(err).Should(Succeed()) comp.SetUID("test-uid") + Expect(testCtx.CreateObj(ctx, comp)).Should(Succeed()) }) It("render configuration should success", func() { @@ -122,7 +166,7 @@ var _ = Describe("Prepare Test", func() { } err = RenderConfigNScriptFiles(resCtx, cluster, comp, synthesizeComp, synthesizeComp.PodSpec, nil) Expect(err).Should(Succeed()) - Expect(configuration.CheckEnvFrom(&synthesizeComp.PodSpec.Containers[0], cfgcore.GenerateEnvFromName(cfgcore.GetComponentCfgName(cluster.Name, synthesizeComp.Name, configSpecName)))).Should(BeFalse()) + Expect(configuration.CheckEnvFrom(&synthesizeComp.PodSpec.Containers[0], cfgcore.GenerateEnvFromName(cfgcore.GetComponentCfgName(cluster.Name, synthesizeComp.Name, testapps.DefaultConfigSpecName)))).Should(BeTrue()) }) }) }) diff --git a/pkg/controller/plan/suite_test.go b/pkg/controller/plan/suite_test.go index af72c0ff9a3..d13fc3d1c99 100644 --- a/pkg/controller/plan/suite_test.go +++ b/pkg/controller/plan/suite_test.go @@ -43,6 +43,7 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/testutil" viper "github.com/apecloud/kubeblocks/pkg/viperx" @@ -115,6 +116,9 @@ var _ = BeforeSuite(func() { err = snapshotv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = parametersv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/pkg/controller/render/template_render_test.go b/pkg/controller/render/template_render_test.go index 19d2c6f9df6..5dcb426ef69 100644 --- a/pkg/controller/render/template_render_test.go +++ b/pkg/controller/render/template_render_test.go @@ -33,7 +33,6 @@ import ( "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/component" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" ) @@ -78,7 +77,7 @@ var _ = Describe("TemplateWrapperTest", func() { compDefObj = testapps.NewComponentDefinitionFactory(compDefName). WithRandomName(). SetDefaultSpec(). - AddConfigTemplate(mysqlConfigName, configMapObj.Name, "", ns, configVolumeName). + AddConfigTemplate(mysqlConfigName, configMapObj.Name, ns, configVolumeName). GetObject() clusterObj = testapps.NewClusterFactory(ns, clusterName, ""). @@ -104,7 +103,7 @@ var _ = Describe("TemplateWrapperTest", func() { It("TestConfigSpec without template", func() { mockK8sCli.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{}), testutil.WithAnyTimes())) - Expect(renderTemplate(intctrlutil.TransformConfigTemplate(clusterComponent.ConfigTemplates))).ShouldNot(Succeed()) + Expect(renderTemplate(clusterComponent.ConfigTemplates)).ShouldNot(Succeed()) }) It("TestConfigSpec with exist configmap", func() { @@ -112,7 +111,7 @@ var _ = Describe("TemplateWrapperTest", func() { configMapObj, }), testutil.WithAnyTimes())) - Expect(renderTemplate(intctrlutil.TransformConfigTemplate(clusterComponent.ConfigTemplates))).Should(Succeed()) + Expect(renderTemplate(clusterComponent.ConfigTemplates)).Should(Succeed()) }) }) }) diff --git a/pkg/controllerutil/config_util.go b/pkg/controllerutil/config_util.go index e00ab7ade0b..a5a5e1f83b8 100644 --- a/pkg/controllerutil/config_util.go +++ b/pkg/controllerutil/config_util.go @@ -32,20 +32,18 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/configuration/validate" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/generics" ) type Result struct { - Phase v1alpha1.ConfigurationPhase `json:"phase"` - Revision string `json:"revision"` - Policy string `json:"policy"` - ExecResult string `json:"execResult"` + Phase parametersv1alpha1.ParameterPhase `json:"phase"` + Revision string `json:"revision"` + Policy string `json:"policy"` + ExecResult string `json:"execResult"` SucceedCount int32 `json:"succeedCount"` ExpectedCount int32 `json:"expectedCount"` @@ -56,22 +54,22 @@ type Result struct { } // MergeAndValidateConfigs merges and validates configuration files -func MergeAndValidateConfigs(configConstraint appsv1beta1.ConfigConstraintSpec, baseConfigs map[string]string, cmKey []string, updatedParams []core.ParamPairs) (map[string]string, error) { +func MergeAndValidateConfigs(baseConfigs map[string]string, + updatedParams []core.ParamPairs, + paramsDefs []*parametersv1alpha1.ParametersDefinition, + configDescs []parametersv1alpha1.ComponentConfigDescription) (map[string]string, error) { var ( - err error - fc = configConstraint.FileFormatConfig - - newCfg map[string]string - configOperator core.ConfigOperator - updatedKeys = util.NewSet() + err error + newCfg map[string]string + configOperator core.ConfigOperator + updatedFileList []string ) - cmKeySet := core.FromCMKeysSelector(cmKey) configLoaderOption := core.CfgOption{ Type: core.CfgCmType, Log: log.FromContext(context.TODO()), - CfgType: fc.Format, - ConfigResource: core.FromConfigData(baseConfigs, cmKeySet), + FileFormatFn: core.WithConfigFileFormat(configDescs), + ConfigResource: core.FromConfigData(baseConfigs, core.NewConfigFileFilter(configDescs)), } if configOperator, err = core.NewConfigLoader(configLoaderOption); err != nil { return nil, err @@ -79,14 +77,18 @@ func MergeAndValidateConfigs(configConstraint appsv1beta1.ConfigConstraintSpec, // merge param to config file for _, params := range updatedParams { - validUpdatedParameters := filterImmutableParameters(params.UpdatedParams, configConstraint.ImmutableParameters) + validUpdatedParameters := filterImmutableParameters(params.UpdatedParams, params.Key, paramsDefs) if len(validUpdatedParameters) == 0 { continue } - if err := configOperator.MergeFrom(validUpdatedParameters, core.NewCfgOptions(params.Key, core.WithFormatterConfig(fc))); err != nil { + fc := core.ResolveConfigFormat(configDescs, params.Key) + if fc == nil { + return nil, fmt.Errorf("not support the config updated: %s", params.Key) + } + if err = configOperator.MergeFrom(validUpdatedParameters, core.NewCfgOptions(params.Key, core.WithFormatterConfig(fc))); err != nil { return nil, err } - updatedKeys.Add(params.Key) + updatedFileList = append(updatedFileList, params.Key) } if newCfg, err = configOperator.ToCfgContent(); err != nil { @@ -96,11 +98,23 @@ func MergeAndValidateConfigs(configConstraint appsv1beta1.ConfigConstraintSpec, // The ToCfgContent interface returns the file contents of all keys, the configuration file is encoded and decoded into keys, // the content may be different with the original file, such as comments, blank lines, etc, // in order to minimize the impact on the original file, only update the changed part. - updatedCfg := fromUpdatedConfig(newCfg, updatedKeys) - if err = validate.NewConfigValidator(&configConstraint, validate.WithKeySelector(cmKey)).Validate(updatedCfg); err != nil { - return nil, core.WrapError(err, "failed to validate updated config") + updatedCfgFiles := make(map[string]string, len(updatedFileList)) + for _, key := range updatedFileList { + updatedCfgFiles[key] = newCfg[key] + paramsDef := resolveParametersDef(paramsDefs, key) + if paramsDef == nil { + continue + } + fc := core.ResolveConfigFormat(configDescs, key) + if fc == nil { + continue + } + if err = validate.NewConfigValidator(paramsDef.Spec.ParametersSchema, fc).Validate(updatedCfgFiles[key]); err != nil { + return nil, core.WrapError(err, "failed to validate updated config") + } } - return core.MergeUpdatedConfig(baseConfigs, updatedCfg), nil + + return core.MergeUpdatedConfig(baseConfigs, updatedCfgFiles), nil } // fromUpdatedConfig filters out changed file contents. @@ -119,7 +133,7 @@ func fromUpdatedConfig(m map[string]string, sets *set.LinkedHashSetString) map[s } // IsApplyConfigChanged checks if the configuration is changed -func IsApplyConfigChanged(configMap *corev1.ConfigMap, item v1alpha1.ConfigurationItemDetail) bool { +func IsApplyConfigChanged(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail) bool { if configMap == nil { return false } @@ -128,27 +142,23 @@ func IsApplyConfigChanged(configMap *corev1.ConfigMap, item v1alpha1.Configurati if !ok { return false } - var target v1alpha1.ConfigurationItemDetail - if err := json.Unmarshal([]byte(lastAppliedVersion), &target); err != nil { + lastItem := parametersv1alpha1.ConfigTemplateItemDetail{} + if err := json.Unmarshal([]byte(lastAppliedVersion), &lastItem); err != nil { return false } - - return reflect.DeepEqual(target, item) + return reflect.DeepEqual(lastItem, item) } // IsRerender checks if the configuration template is changed -func IsRerender(configMap *corev1.ConfigMap, item v1alpha1.ConfigurationItemDetail) bool { +func IsRerender(configMap *corev1.ConfigMap, item parametersv1alpha1.ConfigTemplateItemDetail) bool { if configMap == nil { return true } - if item.Version == "" && item.Payload.Data == nil && item.ImportTemplateRef == nil { + if len(item.Payload) == 0 && item.CustomTemplates == nil { return false } - if version := configMap.Annotations[constant.CMConfigurationTemplateVersion]; version != item.Version { - return true - } - var updatedVersion v1alpha1.ConfigurationItemDetail + var updatedVersion parametersv1alpha1.ConfigTemplateItemDetail updatedVersionStr, ok := configMap.Annotations[constant.ConfigAppliedVersionAnnotationKey] if ok && updatedVersionStr != "" { if err := json.Unmarshal([]byte(updatedVersionStr), &updatedVersion); err != nil { @@ -156,35 +166,35 @@ func IsRerender(configMap *corev1.ConfigMap, item v1alpha1.ConfigurationItemDeta } } return !reflect.DeepEqual(updatedVersion.Payload, item.Payload) || - !reflect.DeepEqual(updatedVersion.ImportTemplateRef, item.ImportTemplateRef) + !reflect.DeepEqual(updatedVersion.CustomTemplates, item.CustomTemplates) } // GetConfigSpecReconcilePhase gets the configuration phase func GetConfigSpecReconcilePhase(configMap *corev1.ConfigMap, - item v1alpha1.ConfigurationItemDetail, - status *v1alpha1.ConfigurationItemDetailStatus) v1alpha1.ConfigurationPhase { + item parametersv1alpha1.ConfigTemplateItemDetail, + status *parametersv1alpha1.ConfigTemplateItemDetailStatus) parametersv1alpha1.ParameterPhase { if status == nil || status.Phase == "" { - return v1alpha1.CCreatingPhase + return parametersv1alpha1.CCreatingPhase } if !IsApplyConfigChanged(configMap, item) { - return v1alpha1.CPendingPhase + return parametersv1alpha1.CPendingPhase } return status.Phase } -func CheckAndPatchPayload(item *v1alpha1.ConfigurationItemDetail, payloadID string, payload interface{}) (bool, error) { +func CheckAndPatchPayload(item *parametersv1alpha1.ConfigTemplateItemDetail, payloadID string, payload interface{}) (bool, error) { if item == nil { return false, nil } - if item.Payload.Data == nil { - item.Payload.Data = make(map[string]interface{}) + if item.Payload == nil { + item.Payload = make(map[string]json.RawMessage) } - oldPayload, ok := item.Payload.Data[payloadID] + oldPayload, ok := item.Payload[payloadID] if !ok && payload == nil { return false, nil } if payload == nil { - delete(item.Payload.Data, payloadID) + delete(item.Payload, payloadID) return true, nil } newPayload, err := buildPayloadAsUnstructuredObject(payload) @@ -194,20 +204,16 @@ func CheckAndPatchPayload(item *v1alpha1.ConfigurationItemDetail, payloadID stri if oldPayload != nil && reflect.DeepEqual(oldPayload, newPayload) { return false, nil } - item.Payload.Data[payloadID] = newPayload + item.Payload[payloadID] = newPayload return true, nil } -func buildPayloadAsUnstructuredObject(payload interface{}) (interface{}, error) { +func buildPayloadAsUnstructuredObject(payload interface{}) (json.RawMessage, error) { b, err := json.Marshal(payload) if err != nil { return nil, err } - var unstructuredObj any - if err = json.Unmarshal(b, &unstructuredObj); err != nil { - return nil, err - } - return unstructuredObj, nil + return b, nil } func ResourcesPayloadForComponent(resources corev1.ResourceRequirements) any { @@ -221,11 +227,23 @@ func ResourcesPayloadForComponent(resources corev1.ResourceRequirements) any { } } -func filterImmutableParameters(parameters map[string]any, immutableParams []string) map[string]any { - if len(immutableParams) == 0 || len(parameters) == 0 { +func resolveParametersDef(paramsDefs []*parametersv1alpha1.ParametersDefinition, fileName string) *parametersv1alpha1.ParametersDefinition { + pos := generics.FindFirstFunc(paramsDefs, func(paramsDef *parametersv1alpha1.ParametersDefinition) bool { + return paramsDef.Spec.FileName == fileName + }) + if pos >= 0 { + return paramsDefs[pos] + } + return nil +} + +func filterImmutableParameters(parameters map[string]any, fileName string, paramsDefs []*parametersv1alpha1.ParametersDefinition) map[string]any { + paramsDef := resolveParametersDef(paramsDefs, fileName) + if paramsDef == nil || len(paramsDef.Spec.ImmutableParameters) == 0 { return parameters } + immutableParams := paramsDef.Spec.ImmutableParameters validParameters := make(map[string]any, len(parameters)) for key, val := range parameters { if !slices.Contains(immutableParams, key) { @@ -289,3 +307,25 @@ func ResolveComponentConfigRender(ctx context.Context, reader client.Reader, cmp } return nil, nil } + +func NeedDynamicReloadAction(pd *parametersv1alpha1.ParametersDefinitionSpec) bool { + if pd.MergeReloadAndRestart != nil { + return !*pd.MergeReloadAndRestart + } + return false +} + +func ReloadStaticParameters(pd *parametersv1alpha1.ParametersDefinitionSpec) bool { + if pd.ReloadStaticParamsBeforeRestart != nil { + return *pd.ReloadStaticParamsBeforeRestart + } + return false +} + +func TransformComponentParameters(params []appsv1.ComponentParameter) parametersv1alpha1.ComponentParameters { + ret := make(parametersv1alpha1.ComponentParameters, len(params)) + for _, param := range params { + ret[param.Name] = param.Value + } + return ret +} diff --git a/pkg/controllerutil/config_util_test.go b/pkg/controllerutil/config_util_test.go index 0dfdd6b203e..8856e31877a 100644 --- a/pkg/controllerutil/config_util_test.go +++ b/pkg/controllerutil/config_util_test.go @@ -31,13 +31,12 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" "github.com/apecloud/kubeblocks/test/testdata" ) @@ -89,7 +88,7 @@ func TestFromUpdatedConfig(t *testing.T) { func TestIsRerender(t *testing.T) { type args struct { cm *corev1.ConfigMap - item v1alpha1.ConfigurationItemDetail + item parametersv1alpha1.ConfigTemplateItemDetail } tests := []struct { name string @@ -100,7 +99,7 @@ func TestIsRerender(t *testing.T) { name: "test", args: args{ cm: nil, - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", }, }, @@ -109,58 +108,23 @@ func TestIsRerender(t *testing.T) { name: "test", args: args{ cm: builder.NewConfigMapBuilder("default", "test").GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", }, }, want: false, - }, { - name: "test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - GetObject(), - item: v1alpha1.ConfigurationItemDetail{ - Name: "test", - Version: "v1", - }, - }, - want: true, - }, { - name: "test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.CMConfigurationTemplateVersion, "v1"). - GetObject(), - item: v1alpha1.ConfigurationItemDetail{ - Name: "test", - Version: "v2", - }, - }, - want: true, - }, { - name: "test", - args: args{ - cm: builder.NewConfigMapBuilder("default", "test"). - AddAnnotations(constant.CMConfigurationTemplateVersion, "v1"). - GetObject(), - item: v1alpha1.ConfigurationItemDetail{ - Name: "test", - Version: "v1", - }, - }, - want: false, }, { name: "import-template-test", args: args{ cm: builder.NewConfigMapBuilder("default", "test"). AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ""). GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", - ImportTemplateRef: &v1alpha1.ConfigTemplateExtension{ + CustomTemplates: &appsv1.ConfigTemplateExtension{ TemplateRef: "contig-test-template", Namespace: "default", - Policy: v1alpha1.PatchPolicy, + Policy: appsv1.PatchPolicy, }, }, }, @@ -171,7 +135,7 @@ func TestIsRerender(t *testing.T) { cm: builder.NewConfigMapBuilder("default", "test"). AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ` { - "importTemplateRef": { + "userConfigTemplates": { "templateRef": "contig-test-template", "namespace": "default", "policy": "patch" @@ -179,12 +143,12 @@ func TestIsRerender(t *testing.T) { } `). GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", - ImportTemplateRef: &v1alpha1.ConfigTemplateExtension{ + CustomTemplates: &appsv1.ConfigTemplateExtension{ TemplateRef: "contig-test-template", Namespace: "default", - Policy: v1alpha1.PatchPolicy, + Policy: appsv1.PatchPolicy, }, }, }, @@ -195,28 +159,22 @@ func TestIsRerender(t *testing.T) { cm: builder.NewConfigMapBuilder("default", "test"). AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ""). GetObject(), - item: v1alpha1.ConfigurationItemDetail{ - Name: "test", - Payload: v1alpha1.Payload{ - Data: map[string]any{ - "key": "value", - }, - }, + item: parametersv1alpha1.ConfigTemplateItemDetail{ + Name: "test", + Payload: parametersv1alpha1.Payload{}, }, }, - want: true, + want: false, }, { name: "payload-test", args: args{ cm: builder.NewConfigMapBuilder("default", "test"). AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, ` {"payload":{"key":"value"}} `). GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", - Payload: v1alpha1.Payload{ - Data: map[string]any{ - "key": "value", - }, + Payload: parametersv1alpha1.Payload{ + "key": transformPayload("value"), }, }, }, @@ -234,48 +192,48 @@ func TestIsRerender(t *testing.T) { func TestGetConfigSpecReconcilePhase(t *testing.T) { type args struct { cm *corev1.ConfigMap - item v1alpha1.ConfigurationItemDetail - status *v1alpha1.ConfigurationItemDetailStatus + item parametersv1alpha1.ConfigTemplateItemDetail + status *parametersv1alpha1.ConfigTemplateItemDetailStatus } tests := []struct { name string args args - want v1alpha1.ConfigurationPhase + want parametersv1alpha1.ParameterPhase }{{ name: "test", args: args{ cm: nil, - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", }, }, - want: v1alpha1.CCreatingPhase, + want: parametersv1alpha1.CCreatingPhase, }, { name: "test", args: args{ cm: builder.NewConfigMapBuilder("default", "test").GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", }, - status: &v1alpha1.ConfigurationItemDetailStatus{ - Phase: v1alpha1.CInitPhase, + status: ¶metersv1alpha1.ConfigTemplateItemDetailStatus{ + Phase: parametersv1alpha1.CInitPhase, }, }, - want: v1alpha1.CPendingPhase, + want: parametersv1alpha1.CPendingPhase, }, { name: "test", args: args{ cm: builder.NewConfigMapBuilder("default", "test"). AddAnnotations(constant.ConfigAppliedVersionAnnotationKey, `{"name":"test"}`). GetObject(), - item: v1alpha1.ConfigurationItemDetail{ + item: parametersv1alpha1.ConfigTemplateItemDetail{ Name: "test", }, - status: &v1alpha1.ConfigurationItemDetailStatus{ - Phase: v1alpha1.CUpgradingPhase, + status: ¶metersv1alpha1.ConfigTemplateItemDetailStatus{ + Phase: parametersv1alpha1.CUpgradingPhase, }, }, - want: v1alpha1.CUpgradingPhase, + want: parametersv1alpha1.CUpgradingPhase, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -303,26 +261,22 @@ var _ = Describe("config_util", func() { Context("MergeAndValidateConfigs", func() { It("Should succeed with no error", func() { type args struct { - configConstraint appsv1beta1.ConfigConstraintSpec - baseCfg map[string]string - updatedParams []core.ParamPairs - cmKeys []string + parametersDef parametersv1alpha1.ParametersDefinition + baseCfg map[string]string + updatedParams []core.ParamPairs + configs []parametersv1alpha1.ComponentConfigDescription } - configConstraintObj := testapps.NewCustomizedObj("resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}, func(cc *appsv1beta1.ConfigConstraint) { - if ccContext, err := testdata.GetTestDataFileContent("cue_testdata/pg14.cue"); err == nil { - cc.Spec.ParametersSchema = &appsv1beta1.ParametersSchema{ - CUE: string(ccContext), - } - } - cc.Spec.FileFormatConfig = &appsv1beta1.FileFormatConfig{ - Format: appsv1beta1.Properties, - } - }) - - cfgContext, err := testdata.GetTestDataFileContent("cue_testdata/pg14.conf") - Expect(err).Should(Succeed()) + cfgContext, _ := testdata.GetTestDataFileContent("cue_testdata/pg14.conf") + ccContext, _ := testdata.GetTestDataFileContent("cue_testdata/pg14.cue") + paramsDef := parametersv1alpha1.ParametersDefinition{ + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "key", + ParametersSchema: ¶metersv1alpha1.ParametersSchema{ + CUE: string(ccContext), + }, + }, + } tests := []struct { name string @@ -332,7 +286,7 @@ var _ = Describe("config_util", func() { }{{ name: "pg1_merge", args: args{ - configConstraint: configConstraintObj.Spec, + parametersDef: paramsDef, baseCfg: map[string]string{ "key": string(cfgContext), "key2": "not support context", @@ -346,7 +300,7 @@ var _ = Describe("config_util", func() { }, }, }, - cmKeys: []string{"key", "key3"}, + configs: []parametersv1alpha1.ComponentConfigDescription{{Name: "key", FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}}}, }, want: map[string]string{ "max_connections": "200", @@ -355,7 +309,7 @@ var _ = Describe("config_util", func() { }, { name: "not_support_key_updated", args: args{ - configConstraint: configConstraintObj.Spec, + parametersDef: paramsDef, baseCfg: map[string]string{ "key": string(cfgContext), "key2": "not_support_context", @@ -369,20 +323,20 @@ var _ = Describe("config_util", func() { }, }, }, - cmKeys: []string{"key1", "key2"}, + configs: []parametersv1alpha1.ComponentConfigDescription{{Name: "key2", FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}}}, }, wantErr: true, }} for _, tt := range tests { - got, err := MergeAndValidateConfigs(tt.args.configConstraint, tt.args.baseCfg, tt.args.cmKeys, tt.args.updatedParams) + got, err := MergeAndValidateConfigs(tt.args.baseCfg, tt.args.updatedParams, []*parametersv1alpha1.ParametersDefinition{&tt.args.parametersDef}, tt.args.configs) Expect(err != nil).Should(BeEquivalentTo(tt.wantErr)) if tt.wantErr { continue } option := core.CfgOption{ - Type: core.CfgTplType, - CfgType: tt.args.configConstraint.FileFormatConfig.Format, + Type: core.CfgTplType, + FileFormatFn: core.WithConfigFileFormat(tt.args.configs), } patch, err := core.CreateMergePatch(&core.ConfigResource{ @@ -403,7 +357,7 @@ var _ = Describe("config_util", func() { func TestCheckAndPatchPayload(t *testing.T) { type args struct { - item *v1alpha1.ConfigurationItemDetail + item *parametersv1alpha1.ConfigTemplateItemDetail payloadID string payload interface{} } @@ -415,7 +369,7 @@ func TestCheckAndPatchPayload(t *testing.T) { }{{ name: "test", args: args{ - item: &v1alpha1.ConfigurationItemDetail{}, + item: ¶metersv1alpha1.ConfigTemplateItemDetail{}, payloadID: constant.BinaryVersionPayload, payload: "md5-12912uy1232o9y2", }, @@ -430,11 +384,9 @@ func TestCheckAndPatchPayload(t *testing.T) { }, { name: "test-delete-payload", args: args{ - item: &v1alpha1.ConfigurationItemDetail{ - Payload: v1alpha1.Payload{ - Data: map[string]any{ - constant.BinaryVersionPayload: "md5-12912uy1232o9y2", - }, + item: ¶metersv1alpha1.ConfigTemplateItemDetail{ + Payload: parametersv1alpha1.Payload{ + constant.BinaryVersionPayload: json.RawMessage("md5-12912uy1232o9y2"), }, }, payloadID: constant.BinaryVersionPayload, @@ -444,17 +396,15 @@ func TestCheckAndPatchPayload(t *testing.T) { }, { name: "test-update-payload", args: args{ - item: &v1alpha1.ConfigurationItemDetail{ - Payload: v1alpha1.Payload{ - Data: map[string]any{ - constant.BinaryVersionPayload: "md5-12912uy1232o9y2", - constant.ComponentResourcePayload: map[string]any{ - "limit": map[string]string{ - "cpu": "100m", - "memory": "100Mi", - }, + 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, @@ -526,9 +476,20 @@ func Test_filterImmutableParameters(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := filterImmutableParameters(tt.args.parameters, tt.args.immutableParams); !reflect.DeepEqual(got, tt.want) { + paramsDefs := []*parametersv1alpha1.ParametersDefinition{{ + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "test", + ImmutableParameters: tt.args.immutableParams, + }, + }} + if got := filterImmutableParameters(tt.args.parameters, "test", paramsDefs); !reflect.DeepEqual(got, tt.want) { t.Errorf("filterImmutableParameters() = %v, want %v", got, tt.want) } }) } } + +func transformPayload(data interface{}) json.RawMessage { + raw, _ := buildPayloadAsUnstructuredObject(data) + return raw +} diff --git a/pkg/dataprotection/backup/scheduler.go b/pkg/dataprotection/backup/scheduler.go index f2971d94fb7..5147152a6c6 100644 --- a/pkg/dataprotection/backup/scheduler.go +++ b/pkg/dataprotection/backup/scheduler.go @@ -456,17 +456,7 @@ func (s *Scheduler) reconfigure(schedulePolicy *dpv1alpha1.SchedulePolicy) error ComponentOps: opsv1alpha1.ComponentOps{ ComponentName: targetPodSelector.MatchLabels[constant.KBAppComponentLabelKey], }, - Configurations: []opsv1alpha1.ConfigurationItem{ - { - Name: configRef.Name, - Keys: []opsv1alpha1.ParameterConfig{ - { - Key: configRef.Key, - Parameters: parameters, - }, - }, - }, - }, + Parameters: ToV1ReconfigureParameters(parameters), }, }, }, @@ -507,3 +497,14 @@ func (s *Scheduler) reconcileReconfigure(backupSchedule *dpv1alpha1.BackupSchedu } return nil } + +func ToV1ReconfigureParameters(parameters []opsv1alpha1.ParameterPair) []appsv1.ComponentParameter { + params := make([]appsv1.ComponentParameter, len(parameters)) + for _, parameter := range parameters { + params = append(params, appsv1.ComponentParameter{ + Name: parameter.Key, + Value: parameter.Value, + }) + } + return params +} diff --git a/pkg/generics/slices.go b/pkg/generics/slices.go index 5d7a7bfd474..2c0519bc381 100644 --- a/pkg/generics/slices.go +++ b/pkg/generics/slices.go @@ -47,3 +47,11 @@ func FindFirstFunc[S ~[]E, E any](s S, f func(E) bool) int { } return -1 } + +func Map[E any, F any](s []E, f func(E) F) []F { + var arr []F + for _, e := range s { + arr = append(arr, f(e)) + } + return arr +} diff --git a/pkg/operations/ops_util.go b/pkg/operations/ops_util.go index ee4133a3c8b..d0c2ca66370 100644 --- a/pkg/operations/ops_util.go +++ b/pkg/operations/ops_util.go @@ -31,9 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" opsutil "github.com/apecloud/kubeblocks/pkg/operations/util" @@ -57,8 +55,6 @@ type handleStatusProgressWithComponent func(reqCtx intctrlutil.RequestCtx, pgRes *progressResource, compStatus *opsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, succeedCount int32, err error) -type handleReconfigureOpsStatus func(cmStatus *opsv1alpha1.ConfigurationItemStatus) error - // getClusterDefByName gets the ClusterDefinition object by the name. func getClusterDefByName(ctx context.Context, cli client.Client, clusterDefName string) (*appsv1.ClusterDefinition, error) { clusterDef := &appsv1.ClusterDefinition{} @@ -175,32 +171,6 @@ func isOpsRequestFailedPhase(opsRequestPhase opsv1alpha1.OpsPhase) bool { return opsRequestPhase == opsv1alpha1.OpsFailedPhase } -// patchReconfigureOpsStatus when Reconfigure is running, we should update status to OpsRequest.Status.ConfigurationStatus. -// -// NOTES: -// opsStatus describes status of OpsRequest. -// reconfiguringStatus describes status of reconfiguring operation, which contains multiple configuration templates. -// cmStatus describes status of configmap, it is uniquely associated with a configuration template, which contains multiple keys, each key is name of a configuration file. -// execStatus describes the result of the execution of the state machine, which is designed to solve how to conduct the reconfiguring operation, such as whether to restart, how to send a signal to the process. -func updateReconfigureStatusByCM(reconfiguringStatus *opsv1alpha1.ReconfiguringStatus, tplName string, - handleReconfigureStatus handleReconfigureOpsStatus) error { - for i, cmStatus := range reconfiguringStatus.ConfigurationStatus { - if cmStatus.Name == tplName { - // update cmStatus - return handleReconfigureStatus(&reconfiguringStatus.ConfigurationStatus[i]) - } - } - cmCount := len(reconfiguringStatus.ConfigurationStatus) - reconfiguringStatus.ConfigurationStatus = append(reconfiguringStatus.ConfigurationStatus, opsv1alpha1.ConfigurationItemStatus{ - Name: tplName, - Status: appsv1alpha1.ReasonReconfigurePersisting, - SucceedCount: core.NotStarted, - ExpectedCount: core.Unconfirmed, - }) - cmStatus := &reconfiguringStatus.ConfigurationStatus[cmCount] - return handleReconfigureStatus(cmStatus) -} - // validateOpsWaitingPhase validates whether the current cluster phase is expected, and whether the waiting time exceeds the limit. // only requests with `Pending` phase will be validated. func validateOpsNeedWaitingClusterPhase(cluster *appsv1.Cluster, ops *opsv1alpha1.OpsRequest, opsBehaviour OpsBehaviour) error { diff --git a/pkg/operations/reconfigure.go b/pkg/operations/reconfigure.go index 399b81f228f..bc9c86ca109 100644 --- a/pkg/operations/reconfigure.go +++ b/pkg/operations/reconfigure.go @@ -20,20 +20,15 @@ along with this program. If not, see . package operations import ( - "fmt" "time" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" - "github.com/apecloud/kubeblocks/pkg/controller/render" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/controller/builder" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -63,266 +58,60 @@ func (r *reconfigureAction) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, return nil } -func handleReconfigureStatusProgress(result *appsv1alpha1.ReconcileDetail, opsStatus *opsv1alpha1.OpsRequestStatus, phase appsv1alpha1.ConfigurationPhase) handleReconfigureOpsStatus { - return func(cmStatus *opsv1alpha1.ConfigurationItemStatus) (err error) { - // the Pending phase is waiting to be executed, and there is currently no valid ReconcileDetail information. - if result != nil && phase != appsv1alpha1.CPendingPhase { - cmStatus.LastAppliedStatus = result.ExecResult - cmStatus.UpdatePolicy = appsv1alpha1.UpgradePolicy(result.Policy) - cmStatus.SucceedCount = result.SucceedCount - cmStatus.ExpectedCount = result.ExpectedCount - cmStatus.Message = result.ErrMessage - cmStatus.Status = string(phase) - } - return - } -} - -func handleNewReconfigureRequest(configPatch *core.ConfigPatchInfo, lastAppliedConfigs map[string]string) handleReconfigureOpsStatus { - return func(cmStatus *opsv1alpha1.ConfigurationItemStatus) (err error) { - cmStatus.Status = appsv1alpha1.ReasonReconfigurePersisted - cmStatus.LastAppliedConfiguration = lastAppliedConfigs - if configPatch != nil { - cmStatus.UpdatedParameters = opsv1alpha1.UpdatedParameters{ - AddedKeys: i2sMap(configPatch.AddConfig), - UpdatedKeys: b2sMap(configPatch.UpdateConfig), - DeletedKeys: i2sMap(configPatch.DeleteConfig), - } - } - return - } -} - -func (r *reconfigureAction) syncDependResources(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, configSpec opsv1alpha1.ConfigurationItem, componentName string) (*configctrl.Fetcher, error) { - fetcher := configctrl.NewResourceFetcher(&render.ResourceCtx{ - Context: reqCtx.Ctx, - Client: cli, - Namespace: opsRes.Cluster.Namespace, - ClusterName: opsRes.Cluster.Name, - ComponentName: componentName, - }) +func (r *reconfigureAction) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) (opsv1alpha1.OpsPhase, time.Duration, error) { - fetcher.ClusterObj = opsRes.Cluster - err := fetcher. - Configuration(). - ConfigMap(configSpec.Name). - Complete() - if err != nil { - return nil, err + var parameters = ¶metersv1alpha1.Parameter{} + if err := cli.Get(reqCtx.Ctx, client.ObjectKeyFromObject(resource.OpsRequest), parameters); err != nil { + return "", 30 * time.Second, err } - return fetcher, nil -} - -func (r *reconfigureAction) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) (opsv1alpha1.OpsPhase, time.Duration, error) { - var ( - isFinished = true - opsRequest = resource.OpsRequest.Spec - ) - // Node: support multiple component opsDeepCopy := resource.OpsRequest.DeepCopy() - statusAsComponents := make([]opsv1alpha1.ConfigurationItemStatus, 0) - for _, reconfigureParams := range fromReconfigureOperations(opsRequest, reqCtx, cli, resource) { - phase, err := r.doSyncReconfigureStatus(reconfigureParams) - switch { - case err != nil: - return "", 30 * time.Second, err - case phase == opsv1alpha1.OpsFailedPhase: - return opsv1alpha1.OpsFailedPhase, 0, nil - case phase != opsv1alpha1.OpsSucceedPhase: - isFinished = false - } - statusAsComponents = append(statusAsComponents, reconfigureParams.configurationStatus.ConfigurationStatus[0]) + if !intctrlutil.IsParameterFinished(parameters.Status.Phase) { + return syncReconfigureForOps(reqCtx, cli, resource, opsDeepCopy, opsv1alpha1.OpsRunningPhase) } - phase := opsv1alpha1.OpsRunningPhase - if isFinished { - phase = opsv1alpha1.OpsSucceedPhase + if parameters.Status.Phase == parametersv1alpha1.CFinishedPhase { + return syncReconfigureForOps(reqCtx, cli, resource, opsDeepCopy, opsv1alpha1.OpsSucceedPhase) } - return syncReconfigureForOps(reqCtx, cli, resource, statusAsComponents, opsDeepCopy, phase) -} - -func fromReconfigureOperations(request opsv1alpha1.OpsRequestSpec, reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) (reconfigures []reconfigureParams) { - var operations []opsv1alpha1.Reconfigure - operations = append(operations, request.Reconfigures...) - for _, reconfigure := range operations { - if len(reconfigure.Configurations) == 0 { - continue - } - reconfigures = append(reconfigures, reconfigureParams{ - resource: resource, - reqCtx: reqCtx, - cli: cli, - clusterName: resource.Cluster.Name, - componentName: reconfigure.ComponentName, - opsRequest: resource.OpsRequest, - configurationItem: reconfigure.Configurations[0], - configurationStatus: initReconfigureStatus(resource.OpsRequest, reconfigure.ComponentName), - }) - } - return reconfigures + return syncReconfigureForOps(reqCtx, cli, resource, opsDeepCopy, opsv1alpha1.OpsFailedPhase) } -func syncReconfigureForOps(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource, statusAsComponents []opsv1alpha1.ConfigurationItemStatus, opsDeepCopy *opsv1alpha1.OpsRequest, phase opsv1alpha1.OpsPhase) (opsv1alpha1.OpsPhase, time.Duration, error) { - succeedCount := 0 - expectedCount := 0 - opsRequest := resource.OpsRequest - invalidProgress := false - for _, status := range statusAsComponents { - if status.SucceedCount < 0 || status.ExpectedCount < 0 { - invalidProgress = true - break - } - succeedCount += int(status.SucceedCount) - expectedCount += int(status.ExpectedCount) - } - if !invalidProgress { - opsRequest.Status.Progress = fmt.Sprintf("%d/%d", succeedCount, expectedCount) - } +func syncReconfigureForOps(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource, opsDeepCopy *opsv1alpha1.OpsRequest, phase opsv1alpha1.OpsPhase) (opsv1alpha1.OpsPhase, time.Duration, error) { if err := PatchOpsStatusWithOpsDeepCopy(reqCtx.Ctx, cli, resource, opsDeepCopy, phase); err != nil { return "", 30 * time.Second, err } return phase, 30 * time.Second, nil } -func (r *reconfigureAction) doSyncReconfigureStatus(params reconfigureParams) (opsv1alpha1.OpsPhase, error) { - configSpec := params.configurationItem - resource, err := r.syncDependResources(params.reqCtx, - params.cli, params.resource, configSpec, params.componentName) +func (r *reconfigureAction) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) (err error) { + parameter, err := buildReconfigureParameter(resource.OpsRequest) if err != nil { - return "", err - } - - item := resource.ConfigurationObj.Spec.GetConfigurationItem(configSpec.Name) - itemStatus := resource.ConfigurationObj.Status.GetItemStatus(configSpec.Name) - if item == nil || itemStatus == nil { - return opsv1alpha1.OpsRunningPhase, nil - } - - switch phase := reconfiguringPhase(resource, *item, itemStatus); phase { - case appsv1alpha1.CCreatingPhase, appsv1alpha1.CInitPhase: - return opsv1alpha1.OpsFailedPhase, core.MakeError("the configuration is creating or initializing, is not ready to reconfigure") - case appsv1alpha1.CFailedAndPausePhase: - return opsv1alpha1.OpsFailedPhase, - syncStatus(params.configurationStatus, params.resource, itemStatus, phase) - case appsv1alpha1.CFinishedPhase: - return opsv1alpha1.OpsSucceedPhase, - syncStatus(params.configurationStatus, params.resource, itemStatus, phase) - default: - return opsv1alpha1.OpsRunningPhase, - syncStatus(params.configurationStatus, params.resource, itemStatus, phase) + return err } -} -func (r *reconfigureAction) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) error { - opsRequest := resource.OpsRequest.Spec - // Node: support multiple component - for _, reconfigureParams := range fromReconfigureOperations(opsRequest, reqCtx, cli, resource) { - if err := r.doReconfiguring(reconfigureParams); err != nil { - return err + var param = ¶metersv1alpha1.Parameter{} + if err = cli.Get(reqCtx.Ctx, client.ObjectKeyFromObject(parameter), param); err != nil { + if client.IgnoreNotFound(err) == nil { + return cli.Create(reqCtx.Ctx, parameter) } - } - return nil -} - -func (r *reconfigureAction) doReconfiguring(params reconfigureParams) error { - if !needReconfigure(params.opsRequest, params.configurationStatus) { - return nil - } - - item := params.configurationItem - opsPipeline := newPipeline(reconfigureContext{ - cli: params.cli, - reqCtx: params.reqCtx, - resource: params.resource, - config: item, - clusterName: params.clusterName, - componentName: params.componentName, - }) - - result := opsPipeline. - Configuration(). - Validate(). - ConfigMap(item.Name). - ConfigConstraints(). - Merge(). - UpdateOpsLabel(). - Sync(). - Complete() - - if result.err != nil { - return processMergedFailed(params.resource, result.failed, result.err) - } - - params.reqCtx.Recorder.Eventf(params.resource.OpsRequest, - corev1.EventTypeNormal, - appsv1alpha1.ReasonReconfigurePersisted, - "the reconfiguring operation of component[%s] in cluster[%s] merged successfully", params.componentName, params.clusterName) - - // merged successfully - if err := updateReconfigureStatusByCM(params.configurationStatus, opsPipeline.configSpec.Name, - handleNewReconfigureRequest(result.configPatch, result.lastAppliedConfigs)); err != nil { return err } - condition := constructReconfiguringConditions(result, params.resource, opsPipeline.configSpec) - meta.SetStatusCondition(¶ms.configurationStatus.Conditions, *condition) return nil } -func needReconfigure(request *opsv1alpha1.OpsRequest, status *opsv1alpha1.ReconfiguringStatus) bool { - // Update params to configmap - if request.Spec.Type != opsv1alpha1.ReconfiguringType { - return false +func buildReconfigureParameter(ops *opsv1alpha1.OpsRequest) (*parametersv1alpha1.Parameter, error) { + if len(ops.Spec.Reconfigures) == 0 { + return nil, intctrlutil.NewErrorf(intctrlutil.ErrorTypeFatal, `invalid reconfigure request: %s`, ops.GetName()) } - // Check if the reconfiguring operation has been processed. - for _, condition := range status.Conditions { - if isExpectedPhase(condition, []string{appsv1alpha1.ReasonReconfigurePersisted, appsv1alpha1.ReasonReconfigureNoChanged}, metav1.ConditionTrue) { - return false + paramBuilder := builder.NewParameterBuilder(ops.Namespace, ops.GetName()). + ClusterRef(ops.Spec.ClusterName) + for _, reconfigure := range ops.Spec.Reconfigures { + if len(reconfigure.Parameters) != 0 { + paramBuilder.SetComponentParameters(reconfigure.ComponentName, intctrlutil.TransformComponentParameters(reconfigure.Parameters)) } } - return true -} - -func syncStatus(reconfiguringStatus *opsv1alpha1.ReconfiguringStatus, - opsRes *OpsResource, - status *appsv1alpha1.ConfigurationItemDetailStatus, - phase appsv1alpha1.ConfigurationPhase) error { - err := updateReconfigureStatusByCM(reconfiguringStatus, status.Name, - handleReconfigureStatusProgress(status.ReconcileDetail, &opsRes.OpsRequest.Status, phase)) - meta.SetStatusCondition(&reconfiguringStatus.Conditions, *opsv1alpha1.NewReconfigureRunningCondition( - opsRes.OpsRequest, string(phase), status.Name)) - return err -} -func reconfiguringPhase(resource *configctrl.Fetcher, - detail appsv1alpha1.ConfigurationItemDetail, - status *appsv1alpha1.ConfigurationItemDetailStatus) appsv1alpha1.ConfigurationPhase { - if status.ReconcileDetail == nil || status.ReconcileDetail.CurrentRevision != status.UpdateRevision { - return appsv1alpha1.CPendingPhase - } - return intctrlutil.GetConfigSpecReconcilePhase(resource.ConfigMapObj, detail, status) -} - -func isExpectedPhase(condition metav1.Condition, expectedTypes []string, expectedStatus metav1.ConditionStatus) bool { - for _, t := range expectedTypes { - if t == condition.Type && condition.Status == expectedStatus { - return true - } - } - return false -} - -func initReconfigureStatus(opsRequest *opsv1alpha1.OpsRequest, componentName string) *opsv1alpha1.ReconfiguringStatus { - status := &opsRequest.Status - if status.ReconfiguringStatusAsComponent == nil { - status.ReconfiguringStatusAsComponent = make(map[string]*opsv1alpha1.ReconfiguringStatus) - } - if _, ok := status.ReconfiguringStatusAsComponent[componentName]; !ok { - status.ReconfiguringStatusAsComponent[componentName] = &opsv1alpha1.ReconfiguringStatus{ - ConfigurationStatus: make([]opsv1alpha1.ConfigurationItemStatus, 0), - } - } - return status.ReconfiguringStatusAsComponent[componentName] + return paramBuilder.GetObject(), nil } diff --git a/pkg/operations/reconfigure_pipeline.go b/pkg/operations/reconfigure_pipeline.go deleted file mode 100644 index 40f3dbd0a57..00000000000 --- a/pkg/operations/reconfigure_pipeline.go +++ /dev/null @@ -1,253 +0,0 @@ -/* -Copyright (C) 2022-2024 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 operations - -import ( - "fmt" - "slices" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" - "github.com/apecloud/kubeblocks/pkg/controller/builder" - configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" - "github.com/apecloud/kubeblocks/pkg/controller/render" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" -) - -type reconfigureContext struct { - // reconfiguring request - config opsv1alpha1.ConfigurationItem - - cli client.Client - reqCtx intctrlutil.RequestCtx - resource *OpsResource - - clusterName string - componentName string -} - -type pipeline struct { - isFailed bool - - updatedParameters []cfgcore.ParamPairs - mergedConfig map[string]string - configPatch *cfgcore.ConfigPatchInfo - isFileUpdated bool - - updatedObject *appsv1alpha1.Configuration - configConstraint *appsv1beta1.ConfigConstraint - configSpec *appsv1.ComponentConfigSpec - - reconfigureContext - configctrl.ResourceFetcher[pipeline] -} - -func newPipeline(ctx reconfigureContext) *pipeline { - pipeline := &pipeline{reconfigureContext: ctx} - pipeline.Init(&render.ResourceCtx{ - Client: ctx.cli, - Context: ctx.reqCtx.Ctx, - Namespace: ctx.resource.OpsRequest.Namespace, - ClusterName: ctx.clusterName, - ComponentName: ctx.componentName, - }, pipeline) - pipeline.ClusterObj = ctx.resource.Cluster - return pipeline -} - -func (p *pipeline) Validate() *pipeline { - validateFn := func() error { - if p.ConfigurationObj == nil { - return cfgcore.MakeError("failed to found configuration of component[%s] in the cluster[%s]", - p.reconfigureContext.componentName, - p.reconfigureContext.clusterName, - ) - } - - item := p.ConfigurationObj.Spec.GetConfigurationItem(p.config.Name) - if item == nil || item.ConfigSpec == nil { - p.isFailed = true - return cfgcore.MakeError("failed to reconfigure, not existed config[%s]", p.config.Name) - } - - p.configSpec = builder.ToV1ConfigSpec(item.ConfigSpec) - return nil - } - - return p.Wrap(validateFn) -} - -func (p *pipeline) ConfigConstraints() *pipeline { - validateFn := func() (err error) { - if !hasFileUpdate(p.config) { - p.isFailed = true - err = cfgcore.MakeError( - "current configSpec not support reconfigure, configSpec: %v", - p.configSpec.Name) - } - return - } - - fetchCCFn := func() error { - ccKey := client.ObjectKey{ - Name: p.configSpec.ConfigConstraintRef, - } - p.configConstraint = &appsv1beta1.ConfigConstraint{} - if err := p.cli.Get(p.reqCtx.Ctx, ccKey, p.configConstraint); err != nil { - return err - } - if p.configConstraint.Spec.FileFormatConfig == nil { - return errors.Wrap(field.Invalid(field.NewPath("spec.fileFormatConfig"), nil, - "fileFormatConfig is empty"), - fmt.Sprintf("invalid configconstraint: %s", p.configSpec.ConfigConstraintRef)) - } - return nil - } - - return p.Wrap(func() error { - if p.configSpec.ConfigConstraintRef == "" { - return validateFn() - } else { - return fetchCCFn() - } - }) -} - -func (p *pipeline) doMergeImpl(parameters opsv1alpha1.ConfigurationItem) error { - newConfigObj := p.ConfigurationObj.DeepCopy() - - item := newConfigObj.Spec.GetConfigurationItem(p.config.Name) - if item == nil { - return cfgcore.MakeError("not found config item: %s", parameters.Name) - } - - configSpec := p.configSpec - if item.ConfigFileParams == nil { - item.ConfigFileParams = make(map[string]appsv1alpha1.ConfigParams) - } - filter := validate.WithKeySelector(configSpec.Keys) - paramFilter := createImmutableParamsFilter(p.configConstraint) - for _, key := range parameters.Keys { - // patch parameters - if configSpec.ConfigConstraintRef != "" && filter(key.Key) { - if key.FileContent != "" { - return cfgcore.MakeError("not allowed to update file content: %s", key.Key) - } - updateParameters(item, key.Key, key.Parameters, paramFilter) - p.updatedParameters = append(p.updatedParameters, cfgcore.ParamPairs{ - Key: key.Key, - UpdatedParams: fromKeyValuePair(key.Parameters), - }) - continue - } - // update file content - if len(key.Parameters) != 0 { - return cfgcore.MakeError("not allowed to patch parameters: %s", key.Key) - } - updateFileContent(item, key.Key, key.FileContent) - p.isFileUpdated = true - } - p.updatedObject = newConfigObj - return p.createUpdatePatch(item, configSpec) -} - -func (p *pipeline) createUpdatePatch(item *appsv1alpha1.ConfigurationItemDetail, configSpec *appsv1.ComponentConfigSpec) error { - if p.configConstraint == nil { - return nil - } - - updatedData, err := configctrl.DoMerge(p.ConfigMapObj.Data, item.ConfigFileParams, p.configConstraint, *configSpec) - if err != nil { - p.isFailed = true - return err - } - p.configPatch, _, err = cfgcore.CreateConfigPatch(p.ConfigMapObj.Data, - updatedData, - p.configConstraint.Spec.FileFormatConfig.Format, - p.configSpec.Keys, - false) - return err -} - -func (p *pipeline) doMerge() error { - if p.ConfigurationObj == nil { - return cfgcore.MakeError("not found config: %s", - cfgcore.GenerateComponentConfigurationName(p.clusterName, p.componentName)) - } - return p.doMergeImpl(p.config) -} - -func (p *pipeline) Merge() *pipeline { - return p.Wrap(p.doMerge) -} - -func (p *pipeline) UpdateOpsLabel() *pipeline { - updateFn := func() error { - if len(p.updatedParameters) == 0 || - p.configConstraint == nil || - p.configConstraint.Spec.FileFormatConfig == nil { - return nil - } - - request := p.resource.OpsRequest - newRequest := request.DeepCopy() - deepObject := client.MergeFrom(newRequest.DeepCopy()) - formatter := p.configConstraint.Spec.FileFormatConfig - updateOpsLabelWithReconfigure(newRequest, p.updatedParameters, p.ConfigMapObj.Data, formatter) - return p.cli.Patch(p.reqCtx.Ctx, newRequest, deepObject) - } - - return p.Wrap(updateFn) -} - -func (p *pipeline) Sync() *pipeline { - return p.Wrap(func() error { - return p.Client.Patch(p.reqCtx.Ctx, p.updatedObject, client.MergeFrom(p.ConfigurationObj)) - }) -} - -func (p *pipeline) Complete() reconfiguringResult { - if p.Err != nil { - return makeReconfiguringResult(p.Err, withFailed(p.isFailed)) - } - - return makeReconfiguringResult(nil, - withReturned(p.mergedConfig, p.configPatch), - withNoFormatFilesUpdated(p.isFileUpdated), - ) -} - -func createImmutableParamsFilter(cc *appsv1beta1.ConfigConstraint) validate.ValidatorOptions { - var immutableParams []string - if cc != nil { - immutableParams = cc.Spec.ImmutableParameters - } - return func(key string) bool { - return len(immutableParams) == 0 || !slices.Contains(immutableParams, key) - } -} diff --git a/pkg/operations/reconfigure_pipeline_test.go b/pkg/operations/reconfigure_pipeline_test.go deleted file mode 100644 index 96d6150c471..00000000000 --- a/pkg/operations/reconfigure_pipeline_test.go +++ /dev/null @@ -1,347 +0,0 @@ -/* -Copyright (C) 2022-2024 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 operations - -import ( - "reflect" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controller/builder" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" - testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" - testops "github.com/apecloud/kubeblocks/pkg/testutil/operations" -) - -var _ = Describe("Reconfigure util test", func() { - - var ( - k8sMockClient *testutil.K8sClientMockHelper - tpl appsv1.ComponentConfigSpec - tpl2 appsv1.ComponentConfigSpec - updatedCfg opsv1alpha1.ConfigurationItem - ) - - const ( - clusterName = "mysql-test" - componentName = "mysql" - ) - - mockCfgTplObj := func(tpl appsv1.ComponentConfigSpec) (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint, *appsv1alpha1.Configuration) { - By("By assure an cm obj") - - cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml", - &corev1.ConfigMap{}, - testapps.WithNamespacedName(core.GetComponentCfgName(clusterName, componentName, tpl.Name), testCtx.DefaultNamespace)) - cfgTpl := testapps.NewCustomizedObj("operations_config/config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}, - testapps.WithNamespacedName(tpl.ConfigConstraintRef, tpl.Namespace)) - - configuration := builder.NewConfigurationBuilder(testCtx.DefaultNamespace, - core.GenerateComponentConfigurationName(clusterName, componentName)). - ClusterRef(clusterName). - Component(componentName). - AddConfigurationItem(tpl). - AddConfigurationItem(tpl2) - return cfgCM, cfgTpl, configuration.GetObject() - } - - BeforeEach(func() { - k8sMockClient = testutil.NewK8sMockClient() - tpl = appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test", - TemplateRef: "cm_obj", - }, - ConfigConstraintRef: "cfg_constraint_obj", - Keys: []string{"my.cnf"}, - } - tpl2 = appsv1.ComponentConfigSpec{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "for_test2", - TemplateRef: "cm_obj", - }, - } - updatedCfg = opsv1alpha1.ConfigurationItem{ - Name: tpl.Name, - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - Parameters: []opsv1alpha1.ParameterPair{ - { - Key: "x1", - Value: func() *string { v := "y1"; return &v }(), - }, - { - Key: "x2", - Value: func() *string { v := "y2"; return &v }(), - }, - { - Key: "server-id", - Value: nil, // delete parameter - }}, - }}, - } - }) - - AfterEach(func() { - // Add any teardown steps that needs to be executed after each test - k8sMockClient.Finish() - }) - - Context("updateConfigConfigmapResource test", func() { - It("Should success without error", func() { - diffCfg := `{"mysqld":{"x1":"y1","x2":"y2"}}` - - cmObj, tplObj, configObj := mockCfgTplObj(tpl) - tpl2Key := client.ObjectKey{ - Namespace: cmObj.Namespace, - Name: core.GetComponentCfgName(clusterName, componentName, tpl2.Name), - } - k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSequenceResult(map[client.ObjectKey][]testutil.MockGetReturned{ - // for cm - client.ObjectKeyFromObject(cmObj): {{ - Object: nil, - Err: core.MakeError("failed to get cm object"), - }, { - Object: cmObj, - Err: nil, - }}, - tpl2Key: {{ - Object: cmObj, - Err: nil, - }}, - // for tpl - client.ObjectKeyFromObject(tplObj): {{ - Object: nil, - Err: core.MakeError("failed to get tpl object"), - }, { - Object: tplObj, - Err: nil, - }}, - // for configuration - client.ObjectKeyFromObject(configObj): {{ - Object: nil, - // Err: core.MakeError("failed to get configuration object"), - }, { - Object: configObj, - }}, - }), testutil.WithAnyTimes())) - - k8sMockClient.MockPatchMethod(testutil.WithPatchReturned(func(obj client.Object, patch client.Patch) error { - if cm, ok := obj.(*corev1.ConfigMap); ok { - cmObj.Data = cm.Data - } - return nil - }, testutil.WithAnyTimes())) - - opsRes := &OpsResource{ - Recorder: k8sManager.GetEventRecorderFor("Reconfiguring"), - OpsRequest: testops.NewOpsRequestObj("reconfigure-ops-"+testCtx.GetRandomStr(), testCtx.DefaultNamespace, //nolint:goconst - clusterName, opsv1alpha1.ReconfiguringType), - } - reqCtx := intctrlutil.RequestCtx{ - Ctx: testCtx.Ctx, - Log: log.FromContext(ctx).WithName("Reconfiguring"), - Recorder: opsRes.Recorder, - } - - By("Configuration object failed.") - // mock failed - // r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate) - r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName) - Expect(r.err).ShouldNot(Succeed()) - Expect(r.err.Error()).Should(ContainSubstring("failed to found configuration of component")) - - By("CM object failed.") - // mock failed - // r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate) - r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName) - Expect(r.err).ShouldNot(Succeed()) - Expect(r.err.Error()).Should(ContainSubstring("failed to get cm object")) - - By("TPL object failed.") - // mock failed - r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName) - Expect(r.err).ShouldNot(Succeed()) - Expect(r.err.Error()).Should(ContainSubstring("failed to get tpl object")) - - By("update validate failed.") - r = testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, opsv1alpha1.ConfigurationItem{ - Name: tpl.Name, - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - Parameters: []opsv1alpha1.ParameterPair{ - { - Key: "innodb_autoinc_lock_mode", - Value: func() *string { v := "100"; return &v }(), // invalid value - }, - }, - }}, - }, clusterName, componentName) - Expect(r.failed).Should(BeTrue()) - Expect(r.err).ShouldNot(Succeed()) - Expect(r.err.Error()).Should(ContainSubstring(` -mysqld.innodb_autoinc_lock_mode: conflicting values 0 and 100: - 9:36 - 12:18 -mysqld.innodb_autoinc_lock_mode: conflicting values 1 and 100: - 9:40 - 12:18 -mysqld.innodb_autoinc_lock_mode: conflicting values 2 and 100: - 9:44 - 12:18`)) - - By("normal params update") - { - // r := updateConfigConfigmapResource(updatedCfg, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate) - r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedCfg, clusterName, componentName) - Expect(r.err).Should(Succeed()) - Expect(r.noFormatFilesUpdated).Should(BeFalse()) - Expect(r.configPatch).ShouldNot(BeNil()) - diff := r.configPatch - Expect(diff.IsModify).Should(BeTrue()) - Expect(diff.UpdateConfig["my.cnf"]).Should(BeEquivalentTo(diffCfg)) - } - - // normal params update - By("normal file update with configSpec keys") - { - updatedFiles := opsv1alpha1.ConfigurationItem{ - Name: tpl2.Name, - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - FileContent: ` -[mysqld] -x1=y1 -z2=y2 -`, - }}, - } - - _ = updatedFiles - // r := updateConfigConfigmapResource(updatedFiles, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate) - r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName) - Expect(r.err).Should(Succeed()) - } - - // not params update, but file update - By("normal file update with configSpec keys") - { - oldConfig := cmObj.Data - newMyCfg := oldConfig["my.cnf"] - newMyCfg += ` -# for test -# not valid parameter -` - updatedFiles := opsv1alpha1.ConfigurationItem{ - Name: tpl2.Name, - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - FileContent: newMyCfg, - }}, - } - - _ = updatedFiles - // r := updateConfigConfigmapResource(updatedFiles, tpl, client.ObjectKeyFromObject(cmObj), ctx, k8sMockClient.Client(), "test", mockUpdate) - r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName) - Expect(r.err).Should(Succeed()) - Expect(r.configPatch).Should(BeNil()) - Expect(r.noFormatFilesUpdated).Should(BeTrue()) - } - - By("normal file update without configSpec keys") - { - updatedFiles := opsv1alpha1.ConfigurationItem{ - Name: tpl.Name, - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "config2.txt", - FileContent: `# for test`, - }}, - } - - _ = updatedFiles - r := testUpdateConfigConfigmapResource(reqCtx, k8sMockClient.Client(), opsRes, updatedFiles, clusterName, componentName) - Expect(r.err).Should(Succeed()) - diff := r.configPatch - Expect(diff.IsModify).Should(BeFalse()) - } - }) - }) - -}) - -func Test_createImmutableParamsFilter(t *testing.T) { - type args struct { - cc *appsv1beta1.ConfigConstraint - inputs []string - } - tests := []struct { - name string - args args - want []bool - }{{ - name: "test", - args: args{ - inputs: []string{"abcd", "ef"}, - }, - want: []bool{true, true}, - }, { - name: "test", - args: args{ - cc: &appsv1beta1.ConfigConstraint{ - Spec: appsv1beta1.ConfigConstraintSpec{ - ImmutableParameters: []string{"a", "b", "c", "d"}, - }, - }, - inputs: []string{"a", "ef", "efg", "d"}, - }, - want: []bool{false, true, true, false}, - }, { - name: "test", - args: args{ - cc: &appsv1beta1.ConfigConstraint{}, - inputs: []string{"a", "ef", "efg", "d"}, - }, - want: []bool{true, true, true, true}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - filter := createImmutableParamsFilter(tt.args.cc) - for i, key := range tt.args.inputs { - if got := filter(key); !reflect.DeepEqual(got, tt.want[i]) { - t.Errorf("createImmutableParamsFilter() = %v, want %v", got, tt.want) - } - } - }) - } -} diff --git a/pkg/operations/reconfigure_test.go b/pkg/operations/reconfigure_test.go index 5d7ebd1bace..c8bb32641ec 100644 --- a/pkg/operations/reconfigure_test.go +++ b/pkg/operations/reconfigure_test.go @@ -20,24 +20,15 @@ along with this program. If not, see . package operations import ( - "encoding/json" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/spf13/cast" - corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/builder" - configutil "github.com/apecloud/kubeblocks/pkg/controller/configuration" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" opsutil "github.com/apecloud/kubeblocks/pkg/operations/util" @@ -76,277 +67,59 @@ var _ = Describe("Reconfigure OpsRequest", func() { AfterEach(cleanEnv) - initClusterForOps := func(opsRes *OpsResource) { - Expect(opsutil.UpdateClusterOpsAnnotations(ctx, k8sClient, opsRes.Cluster, nil)).Should(Succeed()) - opsRes.Cluster.Status.Phase = appsv1.RunningClusterPhase - } - - assureCfgTplObj := func(tplName, cmName, ns string) (*corev1.ConfigMap, *appsv1beta1.ConfigConstraint) { - By("Assuring an cm obj") - cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml", - &corev1.ConfigMap{}, testapps.WithNamespacedName(cmName, ns)) - cfgTpl := testapps.NewCustomizedObj("operations_config/config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}, testapps.WithNamespacedName(tplName, ns)) - Expect(testCtx.CheckedCreateObj(ctx, cfgCM)).Should(Succeed()) - Expect(testCtx.CheckedCreateObj(ctx, cfgTpl)).Should(Succeed()) - - return cfgCM, cfgTpl - } - - assureConfigInstanceObj := func(clusterName, componentName, ns string, compDef *appsv1.ComponentDefinition) (*appsv1alpha1.Configuration, *corev1.ConfigMap) { - if len(compDef.Spec.Configs) == 0 { - return nil, nil - } - - By("create configuration cr") - configuration := builder.NewConfigurationBuilder(testCtx.DefaultNamespace, core.GenerateComponentConfigurationName(clusterName, componentName)). - ClusterRef(clusterName). - Component(componentName) - for _, configSpec := range compDef.Spec.Configs { - configuration.AddConfigurationItem(configSpec) - } - Expect(testCtx.CheckedCreateObj(ctx, configuration.GetObject())).Should(Succeed()) - - // update status - By("update configuration status") - revision := "1" - Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configuration.GetObject()), - func(config *appsv1alpha1.Configuration) { - revision = cast.ToString(config.GetGeneration()) - for _, item := range config.Spec.ConfigItemDetails { - configutil.CheckAndUpdateItemStatus(config, item, revision) - } - })).Should(Succeed()) - - By("create configmap for configSpecs") - var cmObj *corev1.ConfigMap - for _, configSpec := range compDef.Spec.Configs { - cmInsName := core.GetComponentCfgName(clusterName, componentName, configSpec.Name) - By("create configmap: " + cmInsName) - cfgCM := testapps.NewCustomizedObj("operations_config/config-template.yaml", - &corev1.ConfigMap{}, - testapps.WithNamespacedName(cmInsName, ns), - testapps.WithLabels( - constant.AppNameLabelKey, clusterName, - constant.ConfigurationRevision, revision, - constant.AppInstanceLabelKey, clusterName, - constant.KBAppComponentLabelKey, componentName, - constant.CMConfigurationTemplateNameLabelKey, configSpec.TemplateRef, - constant.CMConfigurationConstraintsNameLabelKey, configSpec.ConfigConstraintRef, - constant.CMConfigurationSpecProviderLabelKey, configSpec.Name, - constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType, - ), - ) - Expect(testCtx.CheckedCreateObj(ctx, cfgCM)).Should(Succeed()) - cmObj = cfgCM - } - return configuration.GetObject(), cmObj - } - - assureMockReconfigureData := func(policyName string) (*OpsResource, *appsv1alpha1.Configuration, *corev1.ConfigMap) { - By("init operations resources ") - opsRes, compDef, clusterObject := initOperationsResources(compDefName, clusterName) - - By("Test Reconfigure") - { - // mock cluster is Running to support reconfiguring ops - By("mock cluster status") - patch := client.MergeFrom(clusterObject.DeepCopy()) - clusterObject.Status.Phase = appsv1.RunningClusterPhase - Expect(k8sClient.Status().Patch(ctx, clusterObject, patch)).Should(Succeed()) - } - - By("mock config tpl") - cmObj, tplObj := assureCfgTplObj("mysql-tpl-test", "mysql-cm-test", testCtx.DefaultNamespace) - - By("update clusterdefinition tpl") - patch := client.MergeFrom(compDef.DeepCopy()) - compDef.Spec.Configs = []appsv1.ComponentConfigSpec{{ - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: "mysql-test", - TemplateRef: cmObj.Name, - VolumeName: "mysql-config", - Namespace: testCtx.DefaultNamespace, - }, - ConfigConstraintRef: tplObj.Name, - }} - Expect(k8sClient.Patch(ctx, compDef, patch)).Should(Succeed()) - - By("mock config cm object") - config, cfgObj := assureConfigInstanceObj(clusterName, defaultCompName, testCtx.DefaultNamespace, compDef) - - return opsRes, config, cfgObj - } - Context("Test Reconfigure", func() { - It("Test Reconfigure OpsRequest with restart", func() { - opsRes, configuration, _ := assureMockReconfigureData("simple") - reqCtx := intctrlutil.RequestCtx{ - Ctx: testCtx.Ctx, - Log: log.FromContext(ctx).WithName("Reconfigure"), - Recorder: opsRes.Recorder, - } - - By("mock reconfigure success") - ops := testops.NewOpsRequestObj("reconfigure-ops-"+randomStr, testCtx.DefaultNamespace, + It("Test Reconfigure OpsRequest", func() { + By("init operations resources ") + reqCtx := intctrlutil.RequestCtx{Ctx: ctx} + opsRes, _, _ := initOperationsResources(compDefName, clusterName) + testapps.MockInstanceSetComponent(&testCtx, clusterName, defaultCompName) + By("create Start opsRequest") + ops := testops.NewOpsRequestObj("start-ops-"+randomStr, testCtx.DefaultNamespace, clusterName, opsv1alpha1.ReconfiguringType) ops.Spec.Reconfigures = []opsv1alpha1.Reconfigure{ { - Configurations: []opsv1alpha1.ConfigurationItem{{ - Name: "mysql-test", - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - Parameters: []opsv1alpha1.ParameterPair{ - { - Key: "binlog_stmt_cache_size", - Value: func() *string { v := "4096"; return &v }(), - }, - { - Key: "key", - Value: func() *string { v := "abcd"; return &v }(), - }, - }, - }}, - }}, ComponentOps: opsv1alpha1.ComponentOps{ComponentName: defaultCompName}, + Parameters: []appsv1.ComponentParameter{ + { + Name: "max_connections", + Value: pointer.String("200"), + }, { + Name: "key", + Value: pointer.String("abcd"), + }}, }, } + opsRes.OpsRequest = testops.CreateOpsRequest(ctx, testCtx, ops) - By("Init Reconfiguring opsrequest") - opsRes.OpsRequest = ops - Expect(testCtx.CheckedCreateObj(ctx, ops)).Should(Succeed()) - initClusterForOps(opsRes) + By("test start action and reconcile function") + Expect(opsutil.UpdateClusterOpsAnnotations(ctx, k8sClient, opsRes.Cluster, nil)).Should(Succeed()) - opsManager := GetOpsManager() - By("init ops phase") opsRes.OpsRequest.Status.Phase = opsv1alpha1.OpsPendingPhase - _, err := opsManager.Do(reqCtx, k8sClient, opsRes) + _, err := GetOpsManager().Do(reqCtx, k8sClient, opsRes) Expect(err).ShouldNot(HaveOccurred()) - - By("Reconfigure configure") - _, err = opsManager.Do(reqCtx, k8sClient, opsRes) + Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsCreatingPhase)) + // do start action + _, err = GetOpsManager().Do(reqCtx, k8sClient, opsRes) + Expect(err).ShouldNot(HaveOccurred()) + _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) Expect(err).ShouldNot(HaveOccurred()) - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) - _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) - Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(opsv1alpha1.OpsRunningPhase)) - - By("mock configuration.status.phase to Finished") - var item *appsv1alpha1.ConfigurationItemDetail - Eventually(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configuration), - func(config *appsv1alpha1.Configuration) { - item = config.Spec.GetConfigurationItem("mysql-test") - for i := 0; i < len(config.Status.ConfigurationItemStatus); i++ { - config.Status.ConfigurationItemStatus[i].Phase = appsv1alpha1.CFinishedPhase - if config.Status.ConfigurationItemStatus[i].Name == item.Name { - config.Status.ConfigurationItemStatus[i].ReconcileDetail = &appsv1alpha1.ReconcileDetail{ - Policy: "simple", - CurrentRevision: config.Status.ConfigurationItemStatus[i].UpdateRevision, - SucceedCount: 2, - ExpectedCount: 2, - } - } - } - })).Should(Succeed()) - - By("mock configmap controller to updated") - Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKey{ - Name: core.GetComponentCfgName(clusterName, defaultCompName, "mysql-test"), - Namespace: testCtx.DefaultNamespace}, - func(cm *corev1.ConfigMap) { - b, err := json.Marshal(item) - Expect(err).ShouldNot(HaveOccurred()) - if cm.Annotations == nil { - cm.Annotations = make(map[string]string) - } - cm.Annotations[constant.ConfigAppliedVersionAnnotationKey] = string(b) - b, err = json.Marshal(intctrlutil.Result{ - Phase: appsv1alpha1.CFinishedPhase, - Policy: "simple", - ExecResult: "none", - }) - Expect(err).ShouldNot(HaveOccurred()) - cm.Annotations[core.GenerateRevisionPhaseKey("1")] = string(b) - })).Should(Succeed()) - - By("Reconfigure operation success") - // Expect(reAction.Handle(eventContext, ops.Name, opsv1alpha1.OpsSucceedPhase, nil)).Should(Succeed()) - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) - _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) - Expect(opsRes.OpsRequest.Status.Phase).Should(Equal(opsv1alpha1.OpsSucceedPhase)) - }) + Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsRunningPhase)) - It("Test Reconfigure OpsRequest with autoReload", func() { - opsRes, _, _ := assureMockReconfigureData("autoReload") - reqCtx := intctrlutil.RequestCtx{ - Ctx: testCtx.Ctx, - Log: log.FromContext(ctx).WithName("Reconfigure"), - Recorder: opsRes.Recorder, - } + var param = ¶metersv1alpha1.Parameter{} + err = k8sClient.Get(testCtx.Ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), param) + Expect(err).Should(BeNil()) - By("mock reconfigure success") - ops := testops.NewOpsRequestObj("reconfigure-ops-"+randomStr+"-reload", testCtx.DefaultNamespace, - clusterName, opsv1alpha1.ReconfiguringType) - ops.Spec.Reconfigures = []opsv1alpha1.Reconfigure{ - { - Configurations: []opsv1alpha1.ConfigurationItem{{ - Name: "mysql-test", - Keys: []opsv1alpha1.ParameterConfig{{ - Key: "my.cnf", - Parameters: []opsv1alpha1.ParameterPair{ - { - Key: "binlog_stmt_cache_size", - Value: func() *string { v := "4096"; return &v }(), - }}, - }}, - }}, - ComponentOps: opsv1alpha1.ComponentOps{ComponentName: defaultCompName}, - }, - } - - By("Init Reconfiguring opsrequest") - opsRes.OpsRequest = ops - Expect(testCtx.CheckedCreateObj(ctx, ops)).Should(Succeed()) - initClusterForOps(opsRes) - - opsManager := GetOpsManager() - // reAction := reconfigureAction{} - By("Reconfigure configure") - opsRes.OpsRequest.Status.Phase = opsv1alpha1.OpsPendingPhase - _, err := opsManager.Do(reqCtx, k8sClient, opsRes) - Expect(err).ShouldNot(HaveOccurred()) - Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsCreatingPhase)) - // do reconfigure - _, err = opsManager.Do(reqCtx, k8sClient, opsRes) - Expect(err).ShouldNot(HaveOccurred()) - By("configuration Reconcile callback") + // mock parameter phase to reconfiguring + Expect(testapps.ChangeObjStatus(&testCtx, param, func() { + param.Status.Phase = parametersv1alpha1.CFinishedPhase + })).Should(Succeed()) - // Expect(reAction.Handle(eventContext, ops.Name, opsv1alpha1.OpsSucceedPhase, nil)).Should(Succeed()) - By("Reconfigure configure") - _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) - // mock cluster.status.component.phase to Updating - mockClusterCompPhase := func(clusterObj *appsv1.Cluster, phase appsv1.ComponentPhase) { - clusterObject := clusterObj.DeepCopy() - patch := client.MergeFrom(clusterObject.DeepCopy()) - compStatus := clusterObject.Status.Components[defaultCompName] - compStatus.Phase = phase - clusterObject.Status.Components[defaultCompName] = compStatus - Expect(k8sClient.Status().Patch(ctx, clusterObject, patch)).Should(Succeed()) - } - mockClusterCompPhase(opsRes.Cluster, appsv1.UpdatingComponentPhase) - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.Cluster), opsRes.Cluster)).Should(Succeed()) + _, err = GetOpsManager().Reconcile(reqCtx, k8sClient, opsRes) + Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsSucceedPhase)) - By("check cluster.status.components[*].phase == Reconfiguring") - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.OpsRequest), opsRes.OpsRequest)).Should(Succeed()) - Expect(opsRes.Cluster.Status.Components[defaultCompName].Phase).Should(Equal(appsv1.UpdatingComponentPhase)) // appsv1.ReconfiguringPhase - // TODO: add status condition expect - _, _ = opsManager.Reconcile(reqCtx, k8sClient, opsRes) - // mock cluster.status.component.phase to Running - mockClusterCompPhase(opsRes.Cluster, appsv1.RunningComponentPhase) + Expect(err).Should(BeNil()) - By("check cluster.status.components[*].phase == Running") - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(opsRes.Cluster), opsRes.Cluster)).Should(Succeed()) - Expect(opsRes.Cluster.Status.Components[defaultCompName].Phase).Should(Equal(appsv1.RunningComponentPhase)) }) }) }) diff --git a/pkg/operations/reconfigure_util.go b/pkg/operations/reconfigure_util.go deleted file mode 100644 index b539ab77ed9..00000000000 --- a/pkg/operations/reconfigure_util.go +++ /dev/null @@ -1,265 +0,0 @@ -/* -Copyright (C) 2022-2024 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 operations - -import ( - "encoding/json" - "fmt" - - "github.com/spf13/cast" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/log" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" -) - -type reconfiguringResult struct { - failed bool - noFormatFilesUpdated bool - configPatch *core.ConfigPatchInfo - lastAppliedConfigs map[string]string - err error -} - -func updateOpsLabelWithReconfigure(obj *opsv1alpha1.OpsRequest, params []core.ParamPairs, orinalData map[string]string, formatter *appsv1beta1.FileFormatConfig) { - var maxLabelCount = 16 - updateLabel := func(param map[string]interface{}) { - if obj.Labels == nil { - obj.Labels = make(map[string]string) - } - for key, val := range param { - if maxLabelCount <= 0 { - return - } - paramName := core.GetValidFieldName(key) - if core.IsValidLabelKeyOrValue(paramName) { - obj.Labels[paramName] = core.FromValueToString(val) - } - maxLabelCount-- - } - } - updateAnnotation := func(keyFile string, param map[string]interface{}) { - data, ok := orinalData[keyFile] - if !ok { - return - } - if obj.Annotations == nil { - obj.Annotations = make(map[string]string) - } - oldValue, err := fetchOriginalValue(keyFile, data, param, formatter) - if err != nil { - log.Log.Error(err, "failed to fetch original value") - return - } - obj.Annotations[keyFile] = oldValue - } - - for _, param := range params { - updateLabel(param.UpdatedParams) - if maxLabelCount <= 0 { - return - } - updateAnnotation(param.Key, param.UpdatedParams) - } -} - -func fetchOriginalValue(keyFile, data string, params map[string]interface{}, formatter *appsv1beta1.FileFormatConfig) (string, error) { - baseConfigObj, err := core.FromConfigObject(keyFile, data, formatter) - if err != nil { - return "", err - } - r := make(map[string]string, len(params)) - for key := range params { - oldVal := baseConfigObj.Get(key) - if oldVal != nil { - r[key] = cast.ToString(oldVal) - } - } - b, err := json.Marshal(r) - return string(b), err -} - -func fromKeyValuePair(parameters []opsv1alpha1.ParameterPair) map[string]interface{} { - m := make(map[string]interface{}, len(parameters)) - for _, param := range parameters { - if param.Value != nil { - m[param.Key] = *param.Value - } else { - m[param.Key] = nil - } - } - return m -} - -func withFailed(failed bool) func(result *reconfiguringResult) { - return func(result *reconfiguringResult) { - result.failed = failed - } -} - -func withReturned(configs map[string]string, patch *core.ConfigPatchInfo) func(result *reconfiguringResult) { - return func(result *reconfiguringResult) { - result.lastAppliedConfigs = configs - result.configPatch = patch - } -} - -func withNoFormatFilesUpdated(changed bool) func(result *reconfiguringResult) { - return func(result *reconfiguringResult) { - result.noFormatFilesUpdated = changed - } -} - -func makeReconfiguringResult(err error, ops ...func(*reconfiguringResult)) reconfiguringResult { - result := reconfiguringResult{ - failed: false, - err: err, - } - for _, o := range ops { - o(&result) - } - return result -} - -func constructReconfiguringConditions(result reconfiguringResult, resource *OpsResource, configSpec *appsv1.ComponentConfigSpec) *metav1.Condition { - if result.noFormatFilesUpdated || (result.configPatch != nil && result.configPatch.IsModify) { - return opsv1alpha1.NewReconfigureRunningCondition( - resource.OpsRequest, - appsv1alpha1.ReasonReconfigurePersisted, - configSpec.Name, - formatConfigPatchToMessage(result.configPatch, nil)) - } - return opsv1alpha1.NewReconfigureRunningCondition( - resource.OpsRequest, - appsv1alpha1.ReasonReconfigureNoChanged, - configSpec.Name, - formatConfigPatchToMessage(result.configPatch, nil)) -} - -func i2sMap(config map[string]interface{}) map[string]string { - if len(config) == 0 { - return nil - } - m := make(map[string]string, len(config)) - for key, value := range config { - data, _ := json.Marshal(value) - m[key] = string(data) - } - return m -} - -func b2sMap(config map[string][]byte) map[string]string { - if len(config) == 0 { - return nil - } - m := make(map[string]string, len(config)) - for key, value := range config { - m[key] = string(value) - } - return m -} - -func processMergedFailed(resource *OpsResource, isInvalid bool, err error) error { - if !isInvalid { - return core.WrapError(err, "failed to update param!") - } - - // if failed to validate configure, set opsRequest to failed and return - failedCondition := opsv1alpha1.NewReconfigureFailedCondition(resource.OpsRequest, err) - resource.OpsRequest.SetStatusCondition(*failedCondition) - return intctrlutil.NewFatalError(err.Error()) -} - -func formatConfigPatchToMessage(configPatch *core.ConfigPatchInfo, execStatus *core.PolicyExecStatus) string { - policyName := "" - if execStatus != nil { - policyName = fmt.Sprintf("updated policy: <%s>, ", execStatus.PolicyName) - } - if configPatch == nil { - return fmt.Sprintf("%supdated full config files.", policyName) - } - return fmt.Sprintf("%supdated: %s, added: %s, deleted:%s", - policyName, - configPatch.UpdateConfig, - configPatch.AddConfig, - configPatch.DeleteConfig) -} - -func updateFileContent(item *appsv1alpha1.ConfigurationItemDetail, key string, content string) { - params, ok := item.ConfigFileParams[key] - if !ok { - item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ - Content: &content, - } - return - } - item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ - Parameters: params.Parameters, - Content: &content, - } -} - -func updateParameters(item *appsv1alpha1.ConfigurationItemDetail, key string, parameters []opsv1alpha1.ParameterPair, filter validate.ValidatorOptions) { - updatedParams := make(map[string]*string, len(parameters)) - for _, parameter := range parameters { - if filter(parameter.Key) { - updatedParams[parameter.Key] = parameter.Value - } - } - - params, ok := item.ConfigFileParams[key] - if !ok { - item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ - Parameters: updatedParams, - } - return - } - - item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ - Content: params.Content, - Parameters: mergeMaps(params.Parameters, updatedParams), - } -} - -func mergeMaps(m1 map[string]*string, m2 map[string]*string) map[string]*string { - merged := make(map[string]*string) - for key, value := range m1 { - merged[key] = value - } - for key, value := range m2 { - merged[key] = value - } - return merged -} - -func hasFileUpdate(config opsv1alpha1.ConfigurationItem) bool { - for _, key := range config.Keys { - if key.FileContent != "" { - return true - } - } - return false -} diff --git a/pkg/operations/reconfigure_util_test.go b/pkg/operations/reconfigure_util_test.go deleted file mode 100644 index 729b020607d..00000000000 --- a/pkg/operations/reconfigure_util_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright (C) 2022-2024 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 operations - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" - - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" -) - -func testUpdateConfigConfigmapResource( - reqCtx intctrlutil.RequestCtx, - cli client.Client, - resource *OpsResource, - config opsv1alpha1.ConfigurationItem, - clusterName, componentName string) reconfiguringResult { - - return newPipeline(reconfigureContext{ - cli: cli, - reqCtx: reqCtx, - resource: resource, - config: config, - clusterName: clusterName, - componentName: componentName, - }).Configuration(). - Validate(). - ConfigMap(config.Name). - ConfigConstraints(). - Merge(). - UpdateOpsLabel(). - Sync(). - Complete() -} diff --git a/pkg/operations/suite_test.go b/pkg/operations/suite_test.go index ab2c3f438eb..80f23578346 100644 --- a/pkg/operations/suite_test.go +++ b/pkg/operations/suite_test.go @@ -45,6 +45,7 @@ import ( appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" intctrlcomp "github.com/apecloud/kubeblocks/pkg/controller/component" @@ -117,6 +118,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = workloads.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = parametersv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:scheme diff --git a/pkg/operations/type.go b/pkg/operations/type.go index 54e159d32a4..8336d21b02a 100644 --- a/pkg/operations/type.go +++ b/pkg/operations/type.go @@ -72,18 +72,6 @@ type OpsBehaviour struct { OpsHandler OpsHandler } -type reconfigureParams struct { - resource *OpsResource - reqCtx intctrlutil.RequestCtx - cli client.Client - - clusterName string - componentName string - opsRequest *opsv1alpha1.OpsRequest - configurationItem opsv1alpha1.ConfigurationItem - configurationStatus *opsv1alpha1.ReconfiguringStatus -} - type OpsResource struct { OpsDef *opsv1alpha1.OpsDefinition OpsRequest *opsv1alpha1.OpsRequest diff --git a/pkg/testutil/apps/componentdefinition_factory.go b/pkg/testutil/apps/componentdefinition_factory.go index 8871c693202..d2b6bf1fcd3 100644 --- a/pkg/testutil/apps/componentdefinition_factory.go +++ b/pkg/testutil/apps/componentdefinition_factory.go @@ -171,28 +171,24 @@ func (f *MockComponentDefinitionFactory) AddServiceExt(name, serviceName string, return f } -func (f *MockComponentDefinitionFactory) AddConfigTemplate(name, configTemplateRef, configConstraintRef, - namespace, volumeName string, injectEnvTo ...string) *MockComponentDefinitionFactory { - config := kbappsv1.ComponentConfigSpec{ - ComponentTemplateSpec: kbappsv1.ComponentTemplateSpec{ - Name: name, - TemplateRef: configTemplateRef, - Namespace: namespace, - VolumeName: volumeName, - }, - ConfigConstraintRef: configConstraintRef, - InjectEnvTo: injectEnvTo, +func (f *MockComponentDefinitionFactory) AddConfigTemplate(name, configTemplateRef, + namespace, volumeName string) *MockComponentDefinitionFactory { + config := kbappsv1.ComponentTemplateSpec{ + Name: name, + TemplateRef: configTemplateRef, + Namespace: namespace, + VolumeName: volumeName, } if f.Get().Spec.Configs == nil { - f.Get().Spec.Configs = make([]kbappsv1.ComponentConfigSpec, 0) + f.Get().Spec.Configs = make([]kbappsv1.ComponentTemplateSpec, 0) } f.Get().Spec.Configs = append(f.Get().Spec.Configs, config) return f } -func (f *MockComponentDefinitionFactory) AddConfigs(configs []kbappsv1.ComponentConfigSpec) *MockComponentDefinitionFactory { +func (f *MockComponentDefinitionFactory) AddConfigs(configs []kbappsv1.ComponentTemplateSpec) *MockComponentDefinitionFactory { if f.Get().Spec.Configs == nil { - f.Get().Spec.Configs = make([]kbappsv1.ComponentConfigSpec, 0) + f.Get().Spec.Configs = make([]kbappsv1.ComponentTemplateSpec, 0) } f.Get().Spec.Configs = append(f.Get().Spec.Configs, configs...) return f diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go index c18fa35603d..4775fa57885 100644 --- a/pkg/testutil/apps/constant.go +++ b/pkg/testutil/apps/constant.go @@ -251,14 +251,11 @@ var ( }, } - DefaultCompDefConfigs = []appsv1.ComponentConfigSpec{ + DefaultCompDefConfigs = []appsv1.ComponentTemplateSpec{ { - ComponentTemplateSpec: appsv1.ComponentTemplateSpec{ - Name: DefaultConfigSpecName, - TemplateRef: DefaultConfigSpecTplRef, - VolumeName: DefaultConfigSpecVolumeName, - }, - ConfigConstraintRef: DefaultConfigSpecConstraintRef, + Name: DefaultConfigSpecName, + TemplateRef: DefaultConfigSpecTplRef, + VolumeName: DefaultConfigSpecVolumeName, }, } diff --git a/pkg/testutil/k8s/k8sclient_util.go b/pkg/testutil/k8s/k8sclient_util.go index 4457d1a9e30..0bf725c70b9 100644 --- a/pkg/testutil/k8s/k8sclient_util.go +++ b/pkg/testutil/k8s/k8sclient_util.go @@ -33,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/apecloud/kubeblocks/pkg/generics" mock_client "github.com/apecloud/kubeblocks/pkg/testutil/k8s/mocks" ) @@ -172,6 +173,29 @@ func (helper *K8sClientMockHelper) MockDeleteMethod(options ...any) { helper.mockMethod(&helper.updateCaller, options...) } +func WithArgsNum(num int) []any { + matchers := make([]any, num) + for i := 0; i < num; i++ { + matchers[i] = gomock.Any() + } + return matchers +} + +func (helper *K8sClientMockHelper) MockNListMethod(argsN int, options ...any) { + helper.listCaller.Caller(func() (CallerFunction, DoReturnedFunction) { + caller := func() *gomock.Call { + return helper.k8sClient.EXPECT().List(gomock.Any(), gomock.Any(), WithArgsNum(argsN)...) + } + doAndReturn := func(caller *gomock.Call, fnWrap HandleListReturnedObject) { + caller.DoAndReturn(func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + return fnWrap(list) + }) + } + return caller, doAndReturn + }) + helper.mockMethod(&helper.listCaller, options...) +} + func (helper *K8sClientMockHelper) MockListMethod(options ...any) { helper.listCaller.Caller(func() (CallerFunction, DoReturnedFunction) { caller := func() *gomock.Call { @@ -344,14 +368,21 @@ func WithCreatedFailedResult() HandleCreateReturnedObject { type Getter = func(key client.ObjectKey, obj client.Object) (bool, error) func WithConstructSimpleGetResult(mockObjs []client.Object, get ...Getter) HandleGetReturnedObject { - mockMap := make(map[client.ObjectKey]client.Object, len(mockObjs)) + mockMap := make(map[schema.GroupVersionKind]map[client.ObjectKey]client.Object, len(mockObjs)) for _, obj := range mockObjs { - mockMap[client.ObjectKeyFromObject(obj)] = obj + kind := generics.ToGVK(obj) + if _, ok := mockMap[kind]; !ok { + mockMap[kind] = make(map[client.ObjectKey]client.Object) + } + mockMap[kind][client.ObjectKeyFromObject(obj)] = obj } return func(key client.ObjectKey, obj client.Object) error { - if mockObj, ok := mockMap[key]; ok { - SetGetReturnedObject(obj, mockObj) - return nil + kind := generics.ToGVK(obj) + if mockMap[kind] != nil { + if mockObj, ok := mockMap[kind][key]; ok { + SetGetReturnedObject(obj, mockObj) + return nil + } } if len(get) > 0 { processed, err := get[0](key, obj) diff --git a/pkg/testutil/parameters/componenttemplate_factory.go b/pkg/testutil/parameters/componenttemplate_factory.go index b1e0a83b99b..9e169d65014 100644 --- a/pkg/testutil/parameters/componenttemplate_factory.go +++ b/pkg/testutil/parameters/componenttemplate_factory.go @@ -60,14 +60,4 @@ slow_query_log=0 [client] socket=/var/run/mysqld/mysqld.sock host=localhost -{{- if $.component.tlsConfig }} -{{- $ca_file := getCAFile }} -{{- $cert_file := getCertFile }} -{{- $key_file := getKeyFile }} -# tls -require_secure_transport=ON -ssl_ca={{ $ca_file }} -ssl_cert={{ $cert_file }} -ssl_key={{ $key_file }} -{{- end }} ` diff --git a/pkg/unstructured/config_object.go b/pkg/unstructured/config_object.go index 2de8b04321b..2377532201e 100644 --- a/pkg/unstructured/config_object.go +++ b/pkg/unstructured/config_object.go @@ -23,13 +23,13 @@ import ( "fmt" "sync" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type ConfigObjectCreator = func(name string) ConfigObject type ConfigObjectRegistry struct { - objectCreator map[appsv1beta1.CfgFileFormat]ConfigObjectCreator + objectCreator map[parametersv1alpha1.CfgFileFormat]ConfigObjectCreator } var ( @@ -39,16 +39,16 @@ var ( func CfgObjectRegistry() *ConfigObjectRegistry { ConfigRegistryOnce.Do(func() { - configObjectRegistry = &ConfigObjectRegistry{objectCreator: make(map[appsv1beta1.CfgFileFormat]ConfigObjectCreator)} + configObjectRegistry = &ConfigObjectRegistry{objectCreator: make(map[parametersv1alpha1.CfgFileFormat]ConfigObjectCreator)} }) return configObjectRegistry } -func (c *ConfigObjectRegistry) RegisterConfigCreator(format appsv1beta1.CfgFileFormat, creator ConfigObjectCreator) { +func (c *ConfigObjectRegistry) RegisterConfigCreator(format parametersv1alpha1.CfgFileFormat, creator ConfigObjectCreator) { c.objectCreator[format] = creator } -func (c *ConfigObjectRegistry) GetConfigObject(name string, format appsv1beta1.CfgFileFormat) (ConfigObject, error) { +func (c *ConfigObjectRegistry) GetConfigObject(name string, format parametersv1alpha1.CfgFileFormat) (ConfigObject, error) { creator, ok := c.objectCreator[format] if !ok { return nil, fmt.Errorf("not supported type[%s]", format) @@ -56,7 +56,7 @@ func (c *ConfigObjectRegistry) GetConfigObject(name string, format appsv1beta1.C return creator(name), nil } -func LoadConfig(name string, content string, format appsv1beta1.CfgFileFormat) (ConfigObject, error) { +func LoadConfig(name string, content string, format parametersv1alpha1.CfgFileFormat) (ConfigObject, error) { configObject, err := CfgObjectRegistry().GetConfigObject(name, format) if err != nil { return nil, err diff --git a/pkg/unstructured/properties.go b/pkg/unstructured/properties.go index a8c7ba6a7f9..42c15b83235 100644 --- a/pkg/unstructured/properties.go +++ b/pkg/unstructured/properties.go @@ -25,7 +25,7 @@ import ( "github.com/magiconair/properties" "github.com/spf13/cast" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type propertiesConfig struct { @@ -36,7 +36,7 @@ type propertiesConfig struct { const commentPrefix = "# " func init() { - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.PropertiesPlus, func(name string) ConfigObject { + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.PropertiesPlus, func(name string) ConfigObject { return &propertiesConfig{name: name} }) } diff --git a/pkg/unstructured/properties_test.go b/pkg/unstructured/properties_test.go index d61ca99f388..f8309fb2d9e 100644 --- a/pkg/unstructured/properties_test.go +++ b/pkg/unstructured/properties_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) func TestPropertiesFormat(t *testing.T) { @@ -53,7 +53,7 @@ brokerMaxConnections=0 # The maximum number of connections per IP. If it exceeds, new connections are rejected. brokerMaxConnectionsPerIp=0 ` - propsConfigObj, err := LoadConfig("props_test", propsContext, appsv1beta1.PropertiesPlus) + propsConfigObj, err := LoadConfig("props_test", propsContext, parametersv1alpha1.PropertiesPlus) assert.Nil(t, err) assert.EqualValues(t, propsConfigObj.Get("brokerDeduplicationProducerInactivityTimeoutMinutes"), "360") @@ -70,7 +70,7 @@ brokerMaxConnectionsPerIp=0 dumpContext, err := propsConfigObj.Marshal() assert.Nil(t, err) - newObj, err := LoadConfig("props_test", dumpContext, appsv1beta1.PropertiesPlus) + newObj, err := LoadConfig("props_test", dumpContext, parametersv1alpha1.PropertiesPlus) assert.Nil(t, err) assert.EqualValues(t, newObj.GetAllParameters(), propsConfigObj.GetAllParameters()) @@ -84,7 +84,7 @@ brokerMaxConnectionsPerIp=0 } func TestPropertiesEmpty(t *testing.T) { - propsConfigObj, err := LoadConfig("props_test", "", appsv1beta1.PropertiesPlus) + propsConfigObj, err := LoadConfig("props_test", "", parametersv1alpha1.PropertiesPlus) assert.Nil(t, err) v, err := propsConfigObj.Marshal() diff --git a/pkg/unstructured/redis_config.go b/pkg/unstructured/redis_config.go index e95f02b96bd..56de901bfbc 100644 --- a/pkg/unstructured/redis_config.go +++ b/pkg/unstructured/redis_config.go @@ -28,7 +28,7 @@ import ( "github.com/StudioSol/set" "github.com/spf13/cast" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type redisConfig struct { @@ -39,7 +39,7 @@ type redisConfig struct { } func init() { - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.RedisCfg, func(name string) ConfigObject { + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.RedisCfg, func(name string) ConfigObject { return &redisConfig{name: name} }) } diff --git a/pkg/unstructured/redis_config_test.go b/pkg/unstructured/redis_config_test.go index ebacc4e64e6..fb58a3064d7 100644 --- a/pkg/unstructured/redis_config_test.go +++ b/pkg/unstructured/redis_config_test.go @@ -26,12 +26,12 @@ import ( "github.com/stretchr/testify/require" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/test/testdata" ) func TestRedisConfig(t *testing.T) { - c, err := LoadConfig("test", "", appsv1beta1.RedisCfg) + c, err := LoadConfig("test", "", parametersv1alpha1.RedisCfg) require.Nil(t, err) tests := []struct { @@ -96,7 +96,7 @@ func TestRedisConfigGetAllParameters(t *testing.T) { }{{ name: "multi field update test", fn: func() ConfigObject { - c, _ := LoadConfig("test", "", appsv1beta1.RedisCfg) + c, _ := LoadConfig("test", "", parametersv1alpha1.RedisCfg) _ = c.Update("port", "123") _ = c.Update("a b", "123 234") _ = c.Update("a c", "345") @@ -114,7 +114,7 @@ func TestRedisConfigGetAllParameters(t *testing.T) { }, { name: "multi field update and delete test", fn: func() ConfigObject { - c, _ := LoadConfig("test", "", appsv1beta1.RedisCfg) + c, _ := LoadConfig("test", "", parametersv1alpha1.RedisCfg) _ = c.Update("port", "123") _ = c.Update("a b", "123 234") _ = c.Update("a c", "345") @@ -195,7 +195,7 @@ zset-max-listpack-value 64` }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - config, err := LoadConfig(tt.name, tt.input, appsv1beta1.RedisCfg) + config, err := LoadConfig(tt.name, tt.input, parametersv1alpha1.RedisCfg) if (err != nil) != tt.wantErr { t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/unstructured/viper_wrap.go b/pkg/unstructured/viper_wrap.go index 1c191602bde..23c0092fa75 100644 --- a/pkg/unstructured/viper_wrap.go +++ b/pkg/unstructured/viper_wrap.go @@ -29,24 +29,23 @@ import ( oviper "github.com/spf13/viper" "gopkg.in/ini.v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type viperWrap struct { *oviper.Viper name string - format appsv1beta1.CfgFileFormat + format parametersv1alpha1.CfgFileFormat } func init() { - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.Ini, createViper(appsv1beta1.Ini)) - // CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.YAML, createViper(appsv1beta1.YAML)) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.JSON, createViper(appsv1beta1.JSON)) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.Dotenv, createViper(appsv1beta1.Dotenv)) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.HCL, createViper(appsv1beta1.HCL)) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.TOML, createViper(appsv1beta1.TOML)) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.Properties, createViper(appsv1beta1.Properties)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.Ini, createViper(parametersv1alpha1.Ini)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.JSON, createViper(parametersv1alpha1.JSON)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.Dotenv, createViper(parametersv1alpha1.Dotenv)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.HCL, createViper(parametersv1alpha1.HCL)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.TOML, createViper(parametersv1alpha1.TOML)) + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.Properties, createViper(parametersv1alpha1.Properties)) } func (v *viperWrap) GetString(key string) (string, error) { @@ -95,9 +94,9 @@ func (v viperWrap) Unmarshal(str string) error { return v.ReadConfig(bytes.NewReader([]byte(str))) } -func newCfgViper(cfgType appsv1beta1.CfgFileFormat) *oviper.Viper { +func newCfgViper(cfgType parametersv1alpha1.CfgFileFormat) *oviper.Viper { defaultKeySep := DelimiterDot - if cfgType == appsv1beta1.Properties || cfgType == appsv1beta1.Dotenv { + if cfgType == parametersv1alpha1.Properties || cfgType == parametersv1alpha1.Dotenv { defaultKeySep = CfgDelimiterPlaceholder } // TODO config constraint support LoadOptions @@ -109,7 +108,7 @@ func newCfgViper(cfgType appsv1beta1.CfgFileFormat) *oviper.Viper { return v } -func createViper(format appsv1beta1.CfgFileFormat) ConfigObjectCreator { +func createViper(format parametersv1alpha1.CfgFileFormat) ConfigObjectCreator { return func(name string) ConfigObject { return &viperWrap{ name: name, diff --git a/pkg/unstructured/viper_wrap_test.go b/pkg/unstructured/viper_wrap_test.go index 71df2832be3..d1bbe96a557 100644 --- a/pkg/unstructured/viper_wrap_test.go +++ b/pkg/unstructured/viper_wrap_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) func TestIniFormat(t *testing.T) { @@ -41,7 +41,7 @@ plugin-load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semis port=3306 ` - iniConfigObj, err := LoadConfig("ini_test", iniContext, appsv1beta1.Ini) + iniConfigObj, err := LoadConfig("ini_test", iniContext, parametersv1alpha1.Ini) assert.Nil(t, err) assert.EqualValues(t, iniConfigObj.Get("mysqld.gtid_mode"), "OFF") @@ -77,7 +77,7 @@ autovacuum_freeze_max_age = '100000000' autovacuum_max_workers = '1' autovacuum_naptime = '1min' ` - propConfigObj, err := LoadConfig("prop_test", propertiesContext, appsv1beta1.Properties) + propConfigObj, err := LoadConfig("prop_test", propertiesContext, parametersv1alpha1.Properties) assert.Nil(t, err) assert.EqualValues(t, propConfigObj.Get("auto_explain.log_nested_statements"), "'True'") @@ -107,7 +107,7 @@ func TestJSONFormat(t *testing.T) { "type": "student" }` - jsonConfigObj, err := LoadConfig("json_test", jsonContext, appsv1beta1.JSON) + jsonConfigObj, err := LoadConfig("json_test", jsonContext, parametersv1alpha1.JSON) assert.Nil(t, err) assert.EqualValues(t, jsonConfigObj.Get("id"), "0001") diff --git a/pkg/unstructured/xml_config.go b/pkg/unstructured/xml_config.go index b9e3c1da80a..de41505b9e8 100644 --- a/pkg/unstructured/xml_config.go +++ b/pkg/unstructured/xml_config.go @@ -26,7 +26,7 @@ import ( mxjv2 "github.com/clbanning/mxj/v2" "github.com/spf13/cast" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type xmlConfig struct { @@ -42,7 +42,7 @@ func init() { // enable cast to int mxjv2.CastValuesToInt(true) - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.XML, func(name string) ConfigObject { + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.XML, func(name string) ConfigObject { return &xmlConfig{name: name} }) } diff --git a/pkg/unstructured/xml_config_test.go b/pkg/unstructured/xml_config_test.go index fc722deb8e4..2c9ada2b16a 100644 --- a/pkg/unstructured/xml_config_test.go +++ b/pkg/unstructured/xml_config_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) func TestXMLFormat(t *testing.T) { @@ -47,7 +47,7 @@ func TestXMLFormat(t *testing.T) { ` - xmlConfigObj, err := LoadConfig("xml_test", xmlContext, appsv1beta1.XML) + xmlConfigObj, err := LoadConfig("xml_test", xmlContext, parametersv1alpha1.XML) assert.Nil(t, err) assert.EqualValues(t, xmlConfigObj.Get("profiles.default.max_threads"), 8) @@ -64,7 +64,7 @@ func TestXMLFormat(t *testing.T) { dumpContext, err := xmlConfigObj.Marshal() assert.Nil(t, err) - newObj, err := LoadConfig("xml_test", dumpContext, appsv1beta1.XML) + newObj, err := LoadConfig("xml_test", dumpContext, parametersv1alpha1.XML) assert.Nil(t, err) assert.EqualValues(t, newObj.GetAllParameters(), xmlConfigObj.GetAllParameters()) diff --git a/pkg/unstructured/yaml_config.go b/pkg/unstructured/yaml_config.go index 3f2af663a98..afe0ade19f5 100644 --- a/pkg/unstructured/yaml_config.go +++ b/pkg/unstructured/yaml_config.go @@ -26,7 +26,7 @@ import ( "github.com/spf13/cast" "gopkg.in/yaml.v2" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" ) type yamlConfig struct { @@ -35,7 +35,7 @@ type yamlConfig struct { } func init() { - CfgObjectRegistry().RegisterConfigCreator(appsv1beta1.YAML, func(name string) ConfigObject { + CfgObjectRegistry().RegisterConfigCreator(parametersv1alpha1.YAML, func(name string) ConfigObject { return &yamlConfig{name: name} }) } diff --git a/pkg/unstructured/yaml_config_test.go b/pkg/unstructured/yaml_config_test.go index 97970f40522..b744705b7cb 100644 --- a/pkg/unstructured/yaml_config_test.go +++ b/pkg/unstructured/yaml_config_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/util" "github.com/apecloud/kubeblocks/test/testdata" ) @@ -44,7 +44,7 @@ spec: name: postgresql-configuration ` - yamlConfigObj, err := LoadConfig("yaml_test", yamlContext, appsv1beta1.YAML) + yamlConfigObj, err := LoadConfig("yaml_test", yamlContext, parametersv1alpha1.YAML) assert.Nil(t, err) assert.EqualValues(t, yamlConfigObj.Get("spec.clusterRef"), "pg") @@ -65,7 +65,7 @@ func TestYAMLFormatForBadCase(t *testing.T) { b, err := testdata.GetTestDataFileContent("config_encoding/prometheus.yaml") assert.Nil(t, err) - yamlConfigObj, err := LoadConfig("yaml_test", string(b), appsv1beta1.YAML) + yamlConfigObj, err := LoadConfig("yaml_test", string(b), parametersv1alpha1.YAML) assert.Nil(t, err) assert.NotNil(t, yamlConfigObj) yamlConfigObj.GetAllParameters() From 5c196c68f61aa309c7995182c04919c49d48526d Mon Sep 17 00:00:00 2001 From: sophon Date: Thu, 12 Dec 2024 18:17:53 +0800 Subject: [PATCH 2/6] fix: pdcr phase --- controllers/parameters/paramconfigrenderer_controller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/parameters/paramconfigrenderer_controller.go b/controllers/parameters/paramconfigrenderer_controller.go index 597a6bc16c8..ec101dd23b5 100644 --- a/controllers/parameters/paramconfigrenderer_controller.go +++ b/controllers/parameters/paramconfigrenderer_controller.go @@ -88,9 +88,9 @@ func (r *ParameterDrivenConfigRenderReconciler) reconcile(reqCtx intctrlutil.Req if err := r.validate(reqCtx, r.Client, ¶meterTemplate.Spec); err != nil { if uErr := r.unavailable(reqCtx.Ctx, r.Client, parameterTemplate, err); uErr != nil { - return intctrlutil.CheckedRequeueWithError(uErr, reqCtx.Log, "") + return intctrlutil.RequeueWithError(uErr, reqCtx.Log, "") } - return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") + return intctrlutil.RequeueWithError(err, reqCtx.Log, "") } if err := r.available(reqCtx.Ctx, r.Client, parameterTemplate); err != nil { From 86e1a6bddb3908851cffbdd188cb49c0515790f3 Mon Sep 17 00:00:00 2001 From: sophon Date: Fri, 13 Dec 2024 16:22:58 +0800 Subject: [PATCH 3/6] fix: not delete component --- controllers/parameters/componentparameter_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/parameters/componentparameter_controller.go b/controllers/parameters/componentparameter_controller.go index 9afa70ba4c4..ca0ab9a0d6e 100644 --- a/controllers/parameters/componentparameter_controller.go +++ b/controllers/parameters/componentparameter_controller.go @@ -186,7 +186,7 @@ func (r *ComponentParameterReconciler) deletionHandler(reqCtx intctrlutil.Reques return func() (*ctrl.Result, error) { var cms = &corev1.ConfigMapList{} matchLabels := client.MatchingLabels(constant.GetCompLabels(componentParameter.Spec.ClusterName, componentParameter.Spec.ComponentName)) - if err := r.Client.List(reqCtx.Ctx, cms, client.InNamespace(componentParameter.Name), matchLabels); err != nil { + if err := r.Client.List(reqCtx.Ctx, cms, client.InNamespace(componentParameter.Namespace), matchLabels); err != nil { return &reconcile.Result{}, err } if err := removeConfigRelatedFinalizer(reqCtx.Ctx, r.Client, cms.Items); err != nil { From 5f639432866a7b75b422ba80db647e614ad71239 Mon Sep 17 00:00:00 2001 From: sophon Date: Fri, 13 Dec 2024 17:38:48 +0800 Subject: [PATCH 4/6] fix: restart npe --- controllers/parameters/policy_util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/parameters/policy_util.go b/controllers/parameters/policy_util.go index ae62a8366c3..c443527b6e4 100644 --- a/controllers/parameters/policy_util.go +++ b/controllers/parameters/policy_util.go @@ -362,6 +362,7 @@ func buildRestartTask(configTemplate *appsv1.ComponentTemplateSpec, rctx *Reconc Client: rctx.Client, ConfigTemplate: *configTemplate, ClusterComponent: rctx.ClusterComObj, + Cluster: rctx.ClusterObj, SynthesizedComponent: rctx.BuiltinComponent, InstanceSetUnits: rctx.InstanceSetList, ConfigMap: rctx.ConfigMap, From ac6a607767fe22a5f3121874ba980bc070fa54c5 Mon Sep 17 00:00:00 2001 From: sophon Date: Wed, 25 Dec 2024 12:18:51 +0800 Subject: [PATCH 5/6] chore: auto filter config template --- .../paramconfigrenderer_controller.go | 91 +++++++++++++------ pkg/controller/configuration/config_utils.go | 4 +- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/controllers/parameters/paramconfigrenderer_controller.go b/controllers/parameters/paramconfigrenderer_controller.go index ec101dd23b5..67a95c1a751 100644 --- a/controllers/parameters/paramconfigrenderer_controller.go +++ b/controllers/parameters/paramconfigrenderer_controller.go @@ -23,6 +23,7 @@ import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" @@ -32,6 +33,7 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" + configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" ) @@ -85,14 +87,19 @@ func (r *ParameterDrivenConfigRenderReconciler) reconcile(reqCtx intctrlutil.Req if intctrlutil.ParametersDrivenConfigRenderTerminalPhases(parameterTemplate.Status, parameterTemplate.Generation) { return intctrlutil.Reconciled() } - - if err := r.validate(reqCtx, r.Client, ¶meterTemplate.Spec); err != nil { - if uErr := r.unavailable(reqCtx.Ctx, r.Client, parameterTemplate, err); uErr != nil { - return intctrlutil.RequeueWithError(uErr, reqCtx.Log, "") + cmpd := &appsv1.ComponentDefinition{} + if err := r.Get(reqCtx.Ctx, client.ObjectKey{Name: parameterTemplate.Spec.ComponentDef}, cmpd); 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, "") } @@ -101,11 +108,7 @@ func (r *ParameterDrivenConfigRenderReconciler) reconcile(reqCtx intctrlutil.Req return intctrlutil.Reconciled() } -func (r *ParameterDrivenConfigRenderReconciler) validate(ctx intctrlutil.RequestCtx, cli client.Client, parameterTemplate *parametersv1alpha1.ParamConfigRendererSpec) error { - cmpd := &appsv1.ComponentDefinition{} - if err := cli.Get(ctx.Ctx, client.ObjectKey{Name: parameterTemplate.ComponentDef}, cmpd); err != nil { - return err - } +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 } @@ -115,6 +118,55 @@ func (r *ParameterDrivenConfigRenderReconciler) validate(ctx intctrlutil.Request 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 = configctrl.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.ComponentTemplateSpec) error { for _, config := range configs { match := func(spec appsv1.ComponentTemplateSpec) bool { @@ -140,22 +192,3 @@ func validateParametersDefs(reqCtx intctrlutil.RequestCtx, cli client.Client, pa } 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) -} diff --git a/pkg/controller/configuration/config_utils.go b/pkg/controller/configuration/config_utils.go index c0e9cbcac72..ebf7f6ed658 100644 --- a/pkg/controller/configuration/config_utils.go +++ b/pkg/controller/configuration/config_utils.go @@ -339,7 +339,7 @@ func runningComponentParameter(ctx context.Context, reader client.Reader, comp * } func buildComponentParameter(ctx context.Context, reader client.Reader, comp *component.SynthesizedComponent, owner *appsv1.Component, cmpd *appsv1.ComponentDefinition, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*parametersv1alpha1.ComponentParameter, error) { - tpls, err := resolveComponentTemplate(ctx, reader, cmpd) + tpls, err := ResolveComponentTemplate(ctx, reader, cmpd) if err != nil { return nil, err } @@ -369,7 +369,7 @@ func mergeComponentParameter(expected *parametersv1alpha1.ComponentParameter, ex }) } -func resolveComponentTemplate(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) (map[string]*corev1.ConfigMap, error) { +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 { cm := &corev1.ConfigMap{} From 0f9f06e342e492c0c2c94bf154af4654c8496ecf Mon Sep 17 00:00:00 2001 From: sophon Date: Wed, 25 Dec 2024 13:06:29 +0800 Subject: [PATCH 6/6] rebase --- .../apps/transformer_component_tls_test.go | 4 +- .../componentparameter_controller_test.go | 63 ------- controllers/parameters/config_util.go | 2 +- controllers/parameters/configuration_test.go | 4 +- controllers/parameters/reconcile_task.go | 6 +- pkg/configuration/core/config_patch.go | 2 +- pkg/configuration/core/config_patch_test.go | 2 +- pkg/configuration/core/config_patch_util.go | 4 +- .../core/config_patch_util_test.go | 2 +- pkg/configuration/core/reconfigure_util.go | 2 +- pkg/controller/configuration/config_utils.go | 6 +- pkg/controller/configuration/envfrom_utils.go | 124 ------------- .../configuration/envfrom_utils_test.go | 170 ------------------ pkg/controller/configuration/operator_test.go | 4 +- pkg/controller/configuration/pipeline.go | 43 +---- pkg/controller/configuration/pipeline_test.go | 4 +- .../configuration/template_merger.go | 6 +- .../configuration/template_merger_test.go | 6 +- .../configuration/template_wrapper.go | 8 +- pkg/controller/plan/prepare_test.go | 15 +- pkg/controllerutil/config_util.go | 8 - 21 files changed, 36 insertions(+), 449 deletions(-) delete mode 100644 pkg/controller/configuration/envfrom_utils.go delete mode 100644 pkg/controller/configuration/envfrom_utils_test.go diff --git a/controllers/apps/transformer_component_tls_test.go b/controllers/apps/transformer_component_tls_test.go index 3a673d32486..e38d61bcb28 100644 --- a/controllers/apps/transformer_component_tls_test.go +++ b/controllers/apps/transformer_component_tls_test.go @@ -78,7 +78,7 @@ var _ = Describe("TLS self-signed cert function", func() { testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ConfigMapSignature, true, client.InNamespace(testCtx.DefaultNamespace)) // non-namespaced testapps.ClearResources(&testCtx, generics.ParametersDefinitionSignature, ml) - testapps.ClearResources(&testCtx, generics.ParameterDrivenConfigRenderSignature, ml) + testapps.ClearResources(&testCtx, generics.ParamConfigRendererSignature, ml) testapps.ClearResources(&testCtx, generics.BackupPolicyTemplateSignature, ml) } @@ -108,7 +108,7 @@ var _ = Describe("TLS self-signed cert function", func() { Create(&testCtx). GetObject() - testparameters.NewParametersDrivenConfigFactory(pdcrName). + testparameters.NewParamConfigRendererFactory(pdcrName). SetParametersDefs(paramsdef.Name). SetComponentDefinition(compDefObj.GetName()). SetTemplateName(configTemplateName). diff --git a/controllers/parameters/componentparameter_controller_test.go b/controllers/parameters/componentparameter_controller_test.go index e4e44d87680..1d95c5e1ed4 100644 --- a/controllers/parameters/componentparameter_controller_test.go +++ b/controllers/parameters/componentparameter_controller_test.go @@ -23,9 +23,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" @@ -41,24 +38,6 @@ var _ = Describe("ComponentParameter Controller", func() { AfterEach(cleanEnv) - updatePDCRForInjectEnv := func() { - Eventually(testapps.GetAndChangeObj(&testCtx, types.NamespacedName{Name: pdcrName}, func(pdcr *parametersv1alpha1.ParameterDrivenConfigRender) { - pdcr.Spec.Configs = append(pdcr.Spec.Configs, parametersv1alpha1.ComponentConfigDescription{ - Name: envTestFileKey, - TemplateName: configSpecName, - InjectEnvTo: []string{testapps.DefaultMySQLContainerName}, - FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ - Format: parametersv1alpha1.Properties, - }, - }) - })).Should(Succeed()) - - Eventually(testapps.CheckObj(&testCtx, types.NamespacedName{Name: pdcrName}, func(g Gomega, pdcr *parametersv1alpha1.ParameterDrivenConfigRender) { - g.Expect(pdcr.Spec.Configs).Should(HaveLen(2)) - g.Expect(pdcr.Spec.Configs[1].FileFormatConfig.Format).Should(BeEquivalentTo(parametersv1alpha1.Properties)) - })).Should(Succeed()) - } - Context("When updating configuration", func() { It("Should reconcile success", func() { mockReconcileResource() @@ -96,46 +75,4 @@ var _ = Describe("ComponentParameter Controller", func() { }) }) - - Context("When updating configuration with injectEnvTo", func() { - It("Should reconcile success", func() { - mockReconcileResource() - updatePDCRForInjectEnv() - - cfgKey := client.ObjectKey{ - Name: core.GenerateComponentConfigurationName(clusterName, defaultCompName), - Namespace: testCtx.DefaultNamespace, - } - envKey := client.ObjectKey{ - Name: core.GenerateEnvFromName(core.GetComponentCfgName(clusterName, defaultCompName, configSpecName)), - Namespace: testCtx.DefaultNamespace, - } - - By("reconfiguring parameters.") - Eventually(testapps.GetAndChangeObj(&testCtx, cfgKey, func(cfg *parametersv1alpha1.ComponentParameter) { - item := intctrlutil.GetConfigTemplateItem(&cfg.Spec, configSpecName) - item.ConfigFileParams = map[string]parametersv1alpha1.ParametersInFile{ - envTestFileKey: { - Parameters: map[string]*string{ - "max_connections": cfgutil.ToPointer("1000"), - "gtid_mode": cfgutil.ToPointer("ON"), - }, - }, - } - })).Should(Succeed()) - - Eventually(testapps.CheckObj(&testCtx, cfgKey, func(g Gomega, cfg *parametersv1alpha1.ComponentParameter) { - itemStatus := intctrlutil.GetItemStatus(&cfg.Status, configSpecName) - g.Expect(itemStatus).ShouldNot(BeNil()) - g.Expect(itemStatus.UpdateRevision).Should(BeEquivalentTo("2")) - g.Expect(itemStatus.Phase).Should(BeEquivalentTo(parametersv1alpha1.CFinishedPhase)) - })).Should(Succeed()) - - Eventually(testapps.CheckObj(&testCtx, envKey, func(g Gomega, envObj *corev1.ConfigMap) { - g.Expect(envObj.Data).Should(HaveKeyWithValue("max_connections", "1000")) - g.Expect(envObj.Data).Should(HaveKeyWithValue("gtid_mode", "ON")) - })).Should(Succeed()) - }) - - }) }) diff --git a/controllers/parameters/config_util.go b/controllers/parameters/config_util.go index b82f6edc135..1b61172ed21 100644 --- a/controllers/parameters/config_util.go +++ b/controllers/parameters/config_util.go @@ -59,7 +59,7 @@ func checkConfigLabels(object client.Object, requiredLabs []string) bool { return checkEnableCfgUpgrade(object) } -func createConfigPatch(cfg *corev1.ConfigMap, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs map[string]*parametersv1alpha1.ParametersDefinition) (*core.ConfigPatchInfo, bool, error) { +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 { return nil, true, nil } diff --git a/controllers/parameters/configuration_test.go b/controllers/parameters/configuration_test.go index 4dc60ab0627..15483c7ffaa 100644 --- a/controllers/parameters/configuration_test.go +++ b/controllers/parameters/configuration_test.go @@ -106,13 +106,13 @@ func mockReconcileResource() (*corev1.ConfigMap, *parametersv1alpha1.ParametersD obj.Status.Phase = appsv1.AvailablePhase })()).Should(Succeed()) - pdcr := testparameters.NewParametersDrivenConfigFactory(pdcrName). + 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.ParameterDrivenConfigRender) { + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(pdcr), func(obj *parametersv1alpha1.ParamConfigRenderer) { obj.Status.Phase = parametersv1alpha1.PDAvailablePhase })()).Should(Succeed()) diff --git a/controllers/parameters/reconcile_task.go b/controllers/parameters/reconcile_task.go index 2b352c15197..09b4fed2658 100644 --- a/controllers/parameters/reconcile_task.go +++ b/controllers/parameters/reconcile_task.go @@ -46,7 +46,7 @@ type Task struct { type TaskContext struct { componentParameter *parametersv1alpha1.ComponentParameter - configRender *parametersv1alpha1.ParameterDrivenConfigRender + configRender *parametersv1alpha1.ParamConfigRenderer ctx context.Context component *component.SynthesizedComponent paramsDefs []*parametersv1alpha1.ParametersDefinition @@ -63,13 +63,13 @@ func NewTaskContext(ctx context.Context, cli client.Client, componentParameter * return nil, err } - configDefList := ¶metersv1alpha1.ParameterDrivenConfigRenderList{} + configDefList := ¶metersv1alpha1.ParamConfigRendererList{} if err := cli.List(ctx, configDefList); err != nil { return nil, err } var paramsDefs []*parametersv1alpha1.ParametersDefinition - var configRender *parametersv1alpha1.ParameterDrivenConfigRender + var configRender *parametersv1alpha1.ParamConfigRenderer for i, item := range configDefList.Items { if item.Spec.ComponentDef != cmpd.Name { continue diff --git a/pkg/configuration/core/config_patch.go b/pkg/configuration/core/config_patch.go index a6ba0c1260f..e112aa3489e 100644 --- a/pkg/configuration/core/config_patch.go +++ b/pkg/configuration/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.ParameterDrivenConfigRenderSpec) (*ConfigPatchInfo, error) { +func TransformConfigPatchFromData(data map[string]string, configRender parametersv1alpha1.ParamConfigRendererSpec) (*ConfigPatchInfo, error) { emptyData := func(m map[string]string) map[string]string { r := make(map[string]string, len(m)) for key := range m { diff --git a/pkg/configuration/core/config_patch_test.go b/pkg/configuration/core/config_patch_test.go index a482978291c..47e1c474179 100644 --- a/pkg/configuration/core/config_patch_test.go +++ b/pkg/configuration/core/config_patch_test.go @@ -130,7 +130,7 @@ 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.ParameterDrivenConfigRenderSpec{ + got, err := TransformConfigPatchFromData(map[string]string{configFile: testData}, parametersv1alpha1.ParamConfigRendererSpec{ Configs: []parametersv1alpha1.ComponentConfigDescription{{ Name: "my.cnf", FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Ini}, diff --git a/pkg/configuration/core/config_patch_util.go b/pkg/configuration/core/config_patch_util.go index 6c1089c1b5b..e88a74f18f0 100644 --- a/pkg/configuration/core/config_patch_util.go +++ b/pkg/configuration/core/config_patch_util.go @@ -32,7 +32,7 @@ import ( ) // CreateConfigPatch creates a patch for configuration files with different version. -func CreateConfigPatch(oldVersion, newVersion map[string]string, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { +func CreateConfigPatch(oldVersion, newVersion map[string]string, configRender parametersv1alpha1.ParamConfigRendererSpec, comparableAllFiles bool) (*ConfigPatchInfo, bool, error) { var hasFilesUpdated = false var keys = ResolveConfigFiles(configRender.Configs) @@ -99,7 +99,7 @@ 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.ParameterDrivenConfigRenderSpec, configData []byte) (map[string]string, error) { +func TransformConfigFileToKeyValueMap(fileName string, configRender parametersv1alpha1.ParamConfigRendererSpec, configData []byte) (map[string]string, error) { formatterConfig := ResolveConfigFormat(configRender.Configs, fileName) if formatterConfig == nil { return nil, fmt.Errorf("not found file formatter config: [%s]", fileName) diff --git a/pkg/configuration/core/config_patch_util_test.go b/pkg/configuration/core/config_patch_util_test.go index f1e6f4fd98f..d492b455c18 100644 --- a/pkg/configuration/core/config_patch_util_test.go +++ b/pkg/configuration/core/config_patch_util_test.go @@ -302,7 +302,7 @@ max_connections=666 }) } } - configRender := parametersv1alpha1.ParameterDrivenConfigRenderSpec{Configs: configs} + configRender := parametersv1alpha1.ParamConfigRendererSpec{Configs: configs} got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, configRender, tt.args.enableExcludeDiff) if (err != nil) != tt.wantErr { t.Errorf("CreateConfigPatch() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/configuration/core/reconfigure_util.go b/pkg/configuration/core/reconfigure_util.go index 2b407417be0..0b970bdf430 100644 --- a/pkg/configuration/core/reconfigure_util.go +++ b/pkg/configuration/core/reconfigure_util.go @@ -88,7 +88,7 @@ func trimNestedField(updatedParams any, trimField string) (any, error) { } // ValidateConfigPatch Verifies if the changed parameters have been removed -func ValidateConfigPatch(patch *ConfigPatchInfo, configRender parametersv1alpha1.ParameterDrivenConfigRenderSpec) error { +func ValidateConfigPatch(patch *ConfigPatchInfo, configRender parametersv1alpha1.ParamConfigRendererSpec) error { if !patch.IsModify || len(patch.UpdateConfig) == 0 { return nil } diff --git a/pkg/controller/configuration/config_utils.go b/pkg/controller/configuration/config_utils.go index ebf7f6ed658..ea150402f8b 100644 --- a/pkg/controller/configuration/config_utils.go +++ b/pkg/controller/configuration/config_utils.go @@ -74,7 +74,7 @@ func buildConfigManagerWithComponent(rctx *render.ResourceCtx, cluster *appsv1.C podSpec = synthesizedComp.PodSpec configSpecs = synthesizedComp.ConfigTemplates - configRender *parametersv1alpha1.ParameterDrivenConfigRender + configRender *parametersv1alpha1.ParamConfigRenderer paramsDefs []*parametersv1alpha1.ParametersDefinition ) @@ -270,7 +270,7 @@ func findPortByPortName(container corev1.Container) (int32, bool) { } // UpdateConfigPayload updates the configuration payload -func UpdateConfigPayload(config *parametersv1alpha1.ComponentParameterSpec, component *appsv1.ComponentSpec, configRender *parametersv1alpha1.ParameterDrivenConfigRenderSpec) error { +func UpdateConfigPayload(config *parametersv1alpha1.ComponentParameterSpec, component *appsv1.ComponentSpec, configRender *parametersv1alpha1.ParamConfigRendererSpec) error { if len(configRender.Configs) == 0 { return nil } @@ -338,7 +338,7 @@ func runningComponentParameter(ctx context.Context, reader client.Reader, comp * return componentParameter, nil } -func buildComponentParameter(ctx context.Context, reader client.Reader, comp *component.SynthesizedComponent, owner *appsv1.Component, cmpd *appsv1.ComponentDefinition, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*parametersv1alpha1.ComponentParameter, error) { +func buildComponentParameter(ctx context.Context, reader client.Reader, comp *component.SynthesizedComponent, owner *appsv1.Component, cmpd *appsv1.ComponentDefinition, configRender *parametersv1alpha1.ParamConfigRenderer, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*parametersv1alpha1.ComponentParameter, error) { tpls, err := ResolveComponentTemplate(ctx, reader, cmpd) if err != nil { return nil, err diff --git a/pkg/controller/configuration/envfrom_utils.go b/pkg/controller/configuration/envfrom_utils.go deleted file mode 100644 index 8ee6224d116..00000000000 --- a/pkg/controller/configuration/envfrom_utils.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright (C) 2022-2024 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 configuration - -import ( - "github.com/spf13/cast" - corev1 "k8s.io/api/core/v1" - - parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/builder" - "github.com/apecloud/kubeblocks/pkg/controller/component" -) - -func InjectTemplateEnvFrom(component *component.SynthesizedComponent, - podSpec *corev1.PodSpec, - configRender *parametersv1alpha1.ParameterDrivenConfigRender, - tplObjs []*corev1.ConfigMap) ([]*corev1.ConfigMap, error) { - withEnvSource := func(name string) corev1.EnvFromSource { - return corev1.EnvFromSource{ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: name, - }}} - } - - injectConfigmap := func(envMap map[string]string, templateName string, injectEnvs []string) *corev1.ConfigMap { - cmName := core.GetComponentCfgName(component.ClusterName, component.Name, templateName) - envSourceObject := builder.NewConfigMapBuilder(component.Namespace, core.GenerateEnvFromName(cmName)). - AddLabels(constant.CMConfigurationSpecProviderLabelKey, templateName). - AddLabelsInMap(constant.GetCompLabels(component.ClusterName, component.Name)). - SetData(envMap). - GetObject() - if podSpec != nil { - injectEnvFrom(podSpec.Containers, injectEnvs, envSourceObject.GetName(), withEnvSource) - injectEnvFrom(podSpec.InitContainers, injectEnvs, envSourceObject.GetName(), withEnvSource) - } - return envSourceObject - } - - if configRender == nil || len(configRender.Spec.Configs) == 0 { - return nil, nil - } - - var cm *corev1.ConfigMap - var envObjs []*corev1.ConfigMap - for _, config := range configRender.Spec.Configs { - if len(config.InjectEnvTo) == 0 || config.FileFormatConfig == nil { - continue - } - if cm = resolveConfigMap(tplObjs, config.Name); cm == nil { - continue - } - envMap, err := resolveParametersFromFileContent(config.FileFormatConfig, cm.Data[config.Name]) - if err != nil { - return nil, err - } - envObjs = append(envObjs, injectConfigmap(envMap, config.TemplateName, config.InjectEnvTo)) - } - return envObjs, nil -} - -func resolveConfigMap(localObjs []*corev1.ConfigMap, key string) *corev1.ConfigMap { - for _, obj := range localObjs { - if _, ok := obj.Data[key]; ok { - return obj - } - } - return nil -} - -func CheckEnvFrom(container *corev1.Container, cmName string) bool { - for i := range container.EnvFrom { - source := &container.EnvFrom[i] - if source.ConfigMapRef != nil && source.ConfigMapRef.Name == cmName { - return true - } - if source.SecretRef != nil && source.SecretRef.Name == cmName { - return true - } - } - return false -} - -func injectEnvFrom(containers []corev1.Container, injectEnvTo []string, cmName string, fn func(string) corev1.EnvFromSource) { - sets := cfgutil.NewSet(injectEnvTo...) - for i := range containers { - container := &containers[i] - if sets.InArray(container.Name) && !CheckEnvFrom(container, cmName) { - container.EnvFrom = append(container.EnvFrom, fn(cmName)) - } - } -} - -func resolveParametersFromFileContent(format *parametersv1alpha1.FileFormatConfig, configContext string) (map[string]string, error) { - keyValue, err := validate.LoadConfigObjectFromContent(format.Format, configContext) - if err != nil { - return nil, err - } - envMap := make(map[string]string, len(keyValue)) - for key, v := range keyValue { - envMap[key] = cast.ToString(v) - } - return envMap, nil -} diff --git a/pkg/controller/configuration/envfrom_utils_test.go b/pkg/controller/configuration/envfrom_utils_test.go deleted file mode 100644 index 87836027f84..00000000000 --- a/pkg/controller/configuration/envfrom_utils_test.go +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright (C) 2022-2024 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 configuration - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - corev1 "k8s.io/api/core/v1" - - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controller/component" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" - "github.com/apecloud/kubeblocks/pkg/generics" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" - testutil "github.com/apecloud/kubeblocks/pkg/testutil/k8s" - testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" -) - -var _ = Describe("ConfigEnvFrom test", func() { - const ( - compDefName = "test-compdef" - clusterName = "test-cluster" - mysqlCompName = "mysql" - ) - - var ( - compDef *appsv1.ComponentDefinition - cluster *appsv1.Cluster - - k8sMockClient *testutil.K8sClientMockHelper - origCMObject *corev1.ConfigMap - configRender *parametersv1alpha1.ParameterDrivenConfigRender - ) - - BeforeEach(func() { - k8sMockClient = testutil.NewK8sMockClient() - - cm := testparameters.NewComponentTemplateFactory("", testCtx.DefaultNamespace). - WithRandomName(). - AddConfigFile("env-file", ` -dbStorage_rocksDB_writeBufferSizeMB=8 -dbStorage_rocksDB_sstSizeInMB=64 -dbStorage_rocksDB_blockSize=65536 -dbStorage_rocksDB_bloomFilterBitsPerKey=10 -dbStorage_rocksDB_numLevels=-1 -dbStorage_rocksDB_numFilesInLevel0=4 -dbStorage_rocksDB_maxSizeInLevel1MB=256 -`).GetObject() - - configRender = testparameters.NewParametersDrivenConfigFactory(""). - WithRandomName(). - SetConfigDescription("env-file", cm.Name, - parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). - GetObject() - - compDef = testapps.NewComponentDefinitionFactory(compDefName). - SetDefaultSpec(). - AddConfigTemplate(cm.Name, cm.Name, testCtx.DefaultNamespace, "mysql-config"). - GetObject() - - pvcSpec := testapps.NewPVCSpec("1Gi") - cluster = testapps.NewClusterFactory(testCtx.DefaultNamespace, clusterName, ""). - AddComponent(mysqlCompName, compDef.Name). - AddVolumeClaimTemplate(testapps.DataVolumeName, pvcSpec). - GetObject() - - origCMObject = cm.DeepCopy() - origCMObject.Name = core.GetComponentCfgName(clusterName, mysqlCompName, cm.Name) - - _ = cluster - }) - - AfterEach(func() { - k8sMockClient.Finish() - }) - - Context("test config template inject envfrom", func() { - It("should inject success", func() { - comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) - Expect(err).Should(Succeed()) - - synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) - Expect(err).Should(Succeed()) - - podSpec := &corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: testapps.DefaultMySQLContainerName, - }, - }, - } - desc := intctrlutil.GetComponentConfigDescription(&configRender.Spec, "env-file") - desc.InjectEnvTo = []string{testapps.DefaultMySQLContainerName} - objs, err := InjectTemplateEnvFrom(synthesizeComp, podSpec, configRender, []*corev1.ConfigMap{origCMObject}) - Expect(err).Should(Succeed()) - Expect(objs).Should(HaveLen(1)) - Expect(generics.FindFunc(podSpec.Containers[0].EnvFrom, func(source corev1.EnvFromSource) bool { - return source.ConfigMapRef.Name == objs[0].Name - })).Should(HaveLen(1)) - }) - - // It("should SyncEnvSourceObject success", func() { - // configSpec := compDef.Spec.Configs[0] - // configSpec.Keys = []string{"env-config"} - // - // comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) - // Expect(err).Should(Succeed()) - // - // synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) - // Expect(err).Should(Succeed()) - // - // cmObj := origCMObject.DeepCopy() - // cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) - // k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - // cmObj, - // configConstraint, - // }), testutil.WithAnyTimes())) - // k8sMockClient.MockUpdateMethod(testutil.WithFailed(core.MakeError("failed to patch"), testutil.WithTimes(1)), - // testutil.WithSucceed(), testutil.WithAnyTimes()) - // - // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).ShouldNot(Succeed()) - // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) - // }) - // - // It("SyncEnvSourceObject abnormal test", func() { - // comp, err := component.BuildComponent(cluster, &cluster.Spec.ComponentSpecs[0], nil, nil) - // Expect(err).Should(Succeed()) - // - // synthesizeComp, err := component.BuildSynthesizedComponent(ctx, testCtx.Cli, compDef, comp, cluster) - // Expect(err).Should(Succeed()) - // - // configSpec := compDef.Spec.Configs[0] - // configSpec.InjectEnvTo = nil - // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) - // - // configSpec.InjectEnvTo = nil - // cmObj := origCMObject.DeepCopy() - // cmObj.SetName(core.GenerateEnvFromName(origCMObject.Name)) - // k8sMockClient.MockGetMethod(testutil.WithGetReturned(testutil.WithConstructSimpleGetResult([]client.Object{ - // cmObj, - // configConstraint, - // }), testutil.WithAnyTimes())) - // k8sMockClient.MockUpdateMethod(testutil.WithSucceed(testutil.WithAnyTimes())) - // - // configSpec = compDef.Spec.Configs[0] - // configSpec.Keys = []string{"env-config", "not-exist"} - // Expect(SyncEnvSourceObject(configSpec, origCMObject, &configConstraint.Spec, k8sMockClient.Client(), ctx, cluster, synthesizeComp)).Should(Succeed()) - // }) - }) -}) diff --git a/pkg/controller/configuration/operator_test.go b/pkg/controller/configuration/operator_test.go index a9873991584..4b388992dfd 100644 --- a/pkg/controller/configuration/operator_test.go +++ b/pkg/controller/configuration/operator_test.go @@ -46,7 +46,7 @@ var _ = Describe("ConfigurationOperatorTest", func() { var configMapObj *corev1.ConfigMap var scriptsObj *corev1.ConfigMap var parametersDef *parametersv1alpha1.ParametersDefinition - var configRender *parametersv1alpha1.ParameterDrivenConfigRender + var configRender *parametersv1alpha1.ParamConfigRenderer var componentParameter *parametersv1alpha1.ComponentParameter var k8sMockClient *testutil.K8sClientMockHelper @@ -82,7 +82,7 @@ var _ = Describe("ConfigurationOperatorTest", func() { Component(mysqlCompName). GetObject() parametersDef = testparameters.NewParametersDefinitionFactory(paramsDefName).GetObject() - configRender = testparameters.NewParametersDrivenConfigFactory(pdcrName). + configRender = testparameters.NewParamConfigRendererFactory(pdcrName). SetComponentDefinition(compDefObj.Name). SetParametersDefs(paramsDefName). GetObject() diff --git a/pkg/controller/configuration/pipeline.go b/pkg/controller/configuration/pipeline.go index 32aa0534ddf..a66a7f87092 100644 --- a/pkg/controller/configuration/pipeline.go +++ b/pkg/controller/configuration/pipeline.go @@ -20,18 +20,15 @@ along with this program. If not, see . package configuration import ( - "context" "strconv" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "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/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/render" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" @@ -44,7 +41,7 @@ type pipeline struct { ctx render.ReconcileCtx ResourceFetcher[pipeline] - configRender *parametersv1alpha1.ParameterDrivenConfigRender + configRender *parametersv1alpha1.ParamConfigRenderer parametersDefs []*parametersv1alpha1.ParametersDefinition } @@ -63,7 +60,7 @@ type updatePipeline struct { ctx render.ReconcileCtx ResourceFetcher[updatePipeline] - configRender *parametersv1alpha1.ParameterDrivenConfigRender + configRender *parametersv1alpha1.ParamConfigRenderer parametersDefs []*parametersv1alpha1.ParametersDefinition } @@ -155,12 +152,8 @@ func (p *pipeline) BuildConfigManagerSidecar() *pipeline { func (p *pipeline) UpdateConfigRelatedObject() *pipeline { updateMeta := func() error { - if err := syncInjectEnvFromCM(p.Context, p.Client, p.ctx.SynthesizedComponent, p.configRender, p.renderWrapper.renderedObjs, true); err != nil { - return err - } return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs) } - return p.Wrap(updateMeta) } @@ -255,11 +248,6 @@ func (p *updatePipeline) UpdateConfigVersion(revision string) *updatePipeline { // TODO(leon) func (p *updatePipeline) Sync() *updatePipeline { return p.Wrap(func() error { - if !p.isDone() { - if err := syncInjectEnvFromCM(p.Context, p.Client, p.ctx.SynthesizedComponent, p.configRender, []*corev1.ConfigMap{p.newCM}, false); err != nil { - return err - } - } if err := intctrlutil.SetControllerReference(p.ComponentParameterObj, p.newCM); err != nil { return err } @@ -280,30 +268,3 @@ func (p *updatePipeline) Sync() *updatePipeline { return core.MakeError("unexpected condition") }) } - -func syncInjectEnvFromCM(ctx context.Context, cli client.Client, synthesizedComp *component.SynthesizedComponent, configRender *parametersv1alpha1.ParameterDrivenConfigRender, configMaps []*corev1.ConfigMap, onlyCreate bool) error { - var podSpec *corev1.PodSpec - - if onlyCreate { - podSpec = synthesizedComp.PodSpec - } - envObjs, err := InjectTemplateEnvFrom(synthesizedComp, podSpec, configRender, configMaps) - if err != nil { - return err - } - for _, obj := range envObjs { - if err = cli.Create(ctx, obj, inDataContext()); err == nil { - continue - } - if !apierrors.IsAlreadyExists(err) { - return err - } - if onlyCreate { - continue - } - if err = cli.Update(ctx, obj, inDataContext()); err != nil { - return err - } - } - return nil -} diff --git a/pkg/controller/configuration/pipeline_test.go b/pkg/controller/configuration/pipeline_test.go index f250495596c..ccba60c6953 100644 --- a/pkg/controller/configuration/pipeline_test.go +++ b/pkg/controller/configuration/pipeline_test.go @@ -53,7 +53,7 @@ var _ = Describe("ConfigurationPipelineTest", func() { var configMapObj *corev1.ConfigMap var parametersDef *parametersv1alpha1.ParametersDefinition var componentParameter *parametersv1alpha1.ComponentParameter - var configRender *parametersv1alpha1.ParameterDrivenConfigRender + var configRender *parametersv1alpha1.ParamConfigRenderer var k8sMockClient *testutil.K8sClientMockHelper mockAPIResource := func(lazyFetcher testutil.Getter) { @@ -123,7 +123,7 @@ max_connections = '1000' GetObject() parametersDef = testparameters.NewParametersDefinitionFactory(paramsDefName).GetObject() - configRender = testparameters.NewParametersDrivenConfigFactory(pdcrName). + configRender = testparameters.NewParamConfigRendererFactory(pdcrName). SetConfigDescription(testConfigFile, configTemplateName, parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). SetComponentDefinition(compDefObj.Name). SetParametersDefs(paramsDefName). diff --git a/pkg/controller/configuration/template_merger.go b/pkg/controller/configuration/template_merger.go index 8c92ff8ef60..012d1aa9ebd 100644 --- a/pkg/controller/configuration/template_merger.go +++ b/pkg/controller/configuration/template_merger.go @@ -43,7 +43,7 @@ type mergeContext struct { template appsv1.ConfigTemplateExtension configSpec appsv1.ComponentTemplateSpec paramsDefs []*parametersv1alpha1.ParametersDefinition - configRender *parametersv1alpha1.ParameterDrivenConfigRender + configRender *parametersv1alpha1.ParamConfigRenderer } func (m *mergeContext) renderTemplate() (map[string]string, error) { @@ -129,7 +129,7 @@ func NewTemplateMerger(template appsv1.ConfigTemplateExtension, templateRender render.TemplateRender, configSpec appsv1.ComponentTemplateSpec, paramsDefs []*parametersv1alpha1.ParametersDefinition, - configRender *parametersv1alpha1.ParameterDrivenConfigRender, + configRender *parametersv1alpha1.ParamConfigRenderer, ) (TemplateMerger, error) { templateData := &mergeContext{ configSpec: configSpec, @@ -160,7 +160,7 @@ func mergerConfigTemplate(template appsv1.ConfigTemplateExtension, configSpec appsv1.ComponentTemplateSpec, baseData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, - configRender *parametersv1alpha1.ParameterDrivenConfigRender) (map[string]string, error) { + configRender *parametersv1alpha1.ParamConfigRenderer) (map[string]string, error) { templateMerger, err := NewTemplateMerger(template, templateRender, configSpec, paramsDefs, configRender) if err != nil { return nil, err diff --git a/pkg/controller/configuration/template_merger_test.go b/pkg/controller/configuration/template_merger_test.go index 7f82692847f..32bb0316519 100644 --- a/pkg/controller/configuration/template_merger_test.go +++ b/pkg/controller/configuration/template_merger_test.go @@ -76,7 +76,7 @@ max_connections=666 templateBuilder render.TemplateRender configSpec appsv1.ComponentTemplateSpec paramsDefs *parametersv1alpha1.ParametersDefinition - pdcr *parametersv1alpha1.ParameterDrivenConfigRender + pdcr *parametersv1alpha1.ParamConfigRenderer baseCMObject *corev1.ConfigMap updatedCMObject *corev1.ConfigMap @@ -87,7 +87,7 @@ max_connections=666 paramsDefs = testparameters.NewParametersDefinitionFactory("test-pd"). SetConfigFile(testConfigName). GetObject() - pdcr = testparameters.NewParametersDrivenConfigFactory("test-pdcr"). + pdcr = testparameters.NewParamConfigRendererFactory("test-pdcr"). SetTemplateName(testConfigSpecName). GetObject() @@ -248,7 +248,7 @@ max_connections=666 } tmpCM := baseCMObject.DeepCopy() - _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, ¶metersv1alpha1.ParameterDrivenConfigRender{}) + _, err := mergerConfigTemplate(importedTemplate, templateBuilder, configSpec, tmpCM.Data, []*parametersv1alpha1.ParametersDefinition{paramsDefs}, ¶metersv1alpha1.ParamConfigRenderer{}) Expect(err).Should(Succeed()) }) }) diff --git a/pkg/controller/configuration/template_wrapper.go b/pkg/controller/configuration/template_wrapper.go index f2585a3e9e4..552ce5fe02d 100644 --- a/pkg/controller/configuration/template_wrapper.go +++ b/pkg/controller/configuration/template_wrapper.go @@ -101,7 +101,7 @@ func (wrapper *renderWrapper) renderConfigTemplate(cluster *appsv1.Cluster, component *component.SynthesizedComponent, localObjs []client.Object, componentParameter *parametersv1alpha1.ComponentParameter, - configRender *parametersv1alpha1.ParameterDrivenConfigRender, + configRender *parametersv1alpha1.ParamConfigRenderer, defs []*parametersv1alpha1.ParametersDefinition, revision string) error { for _, configSpec := range component.ConfigTemplates { var item *parametersv1alpha1.ConfigTemplateItemDetail @@ -160,7 +160,7 @@ func updateConfigMetaForCM(newCMObj *corev1.ConfigMap, item *parametersv1alpha1. return } -func applyUpdatedParameters(item *parametersv1alpha1.ConfigTemplateItemDetail, orig *corev1.ConfigMap, configRender *parametersv1alpha1.ParameterDrivenConfigRender, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { +func applyUpdatedParameters(item *parametersv1alpha1.ConfigTemplateItemDetail, orig *corev1.ConfigMap, configRender *parametersv1alpha1.ParamConfigRenderer, paramsDefs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { if configRender == nil || len(configRender.Spec.Configs) == 0 { return nil, fmt.Errorf("not support parameter reconfigure") } @@ -179,7 +179,7 @@ func (wrapper *renderWrapper) rerenderConfigTemplate(cluster *appsv1.Cluster, component *component.SynthesizedComponent, configSpec appsv1.ComponentTemplateSpec, item *parametersv1alpha1.ConfigTemplateItemDetail, - configRender *parametersv1alpha1.ParameterDrivenConfigRender, + configRender *parametersv1alpha1.ParamConfigRenderer, defs []*parametersv1alpha1.ParametersDefinition) (*corev1.ConfigMap, error) { cmName := core.GetComponentCfgName(cluster.Name, component.Name, configSpec.Name) newCMObj, err := wrapper.RenderComponentTemplate(configSpec, cmName, func(m map[string]string) error { @@ -297,7 +297,7 @@ func UpdateCMConfigSpecLabels(cm *corev1.ConfigMap, configSpec appsv1.ComponentT } // validateRenderedData validates config file against constraint -func validateRenderedData(renderedData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, configRender *parametersv1alpha1.ParameterDrivenConfigRender) error { +func validateRenderedData(renderedData map[string]string, paramsDefs []*parametersv1alpha1.ParametersDefinition, configRender *parametersv1alpha1.ParamConfigRenderer) error { if len(paramsDefs) == 0 || configRender == nil || len(configRender.Spec.Configs) == 0 { return nil } diff --git a/pkg/controller/plan/prepare_test.go b/pkg/controller/plan/prepare_test.go index 07440023fea..38c81125f55 100644 --- a/pkg/controller/plan/prepare_test.go +++ b/pkg/controller/plan/prepare_test.go @@ -28,11 +28,8 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" - cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/render" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" testparameters "github.com/apecloud/kubeblocks/pkg/testutil/parameters" @@ -50,7 +47,7 @@ var _ = Describe("Prepare Test", func() { ml := client.HasLabels{testCtx.TestObjLabelKey} // non-namespaced - testapps.ClearResources(&testCtx, generics.ParameterDrivenConfigRenderSignature, ml) + testapps.ClearResources(&testCtx, generics.ParamConfigRendererSignature, ml) testapps.ClearResources(&testCtx, generics.ParametersDefinitionSignature, ml) testapps.ClearResources(&testCtx, generics.ComponentDefinitionSignature, ml) @@ -110,19 +107,14 @@ var _ = Describe("Prepare Test", func() { obj.Status.Phase = parametersv1alpha1.PDAvailablePhase })()).Should(Succeed()) - configRender := testparameters.NewParametersDrivenConfigFactory(pdcrName). + configRender := testparameters.NewParamConfigRendererFactory(pdcrName). SetConfigDescription(envFileName, testapps.DefaultConfigSpecName, parametersv1alpha1.FileFormatConfig{Format: parametersv1alpha1.Properties}). SetComponentDefinition(compDefObj.Name). SetParametersDefs(paramsDefName). Create(&testCtx). GetObject() - Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(configRender), func(obj *parametersv1alpha1.ParameterDrivenConfigRender) { - config := intctrlutil.GetComponentConfigDescription(&obj.Spec, envFileName) - config.InjectEnvTo = []string{compDefObj.Spec.Runtime.Containers[0].Name} - })()).Should(Succeed()) - - Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configRender), func(obj *parametersv1alpha1.ParameterDrivenConfigRender) { + Expect(testapps.GetAndChangeObjStatus(&testCtx, client.ObjectKeyFromObject(configRender), func(obj *parametersv1alpha1.ParamConfigRenderer) { obj.Status.Phase = parametersv1alpha1.PDAvailablePhase })()).Should(Succeed()) @@ -166,7 +158,6 @@ dbStorage_rocksDB_maxSizeInLevel1MB=256 } err = RenderConfigNScriptFiles(resCtx, cluster, comp, synthesizeComp, synthesizeComp.PodSpec, nil) Expect(err).Should(Succeed()) - Expect(configuration.CheckEnvFrom(&synthesizeComp.PodSpec.Containers[0], cfgcore.GenerateEnvFromName(cfgcore.GetComponentCfgName(cluster.Name, synthesizeComp.Name, testapps.DefaultConfigSpecName)))).Should(BeTrue()) }) }) }) diff --git a/pkg/controllerutil/config_util.go b/pkg/controllerutil/config_util.go index a5a5e1f83b8..27e44397332 100644 --- a/pkg/controllerutil/config_util.go +++ b/pkg/controllerutil/config_util.go @@ -253,14 +253,6 @@ func filterImmutableParameters(parameters map[string]any, fileName string, param return validParameters } -func TransformConfigTemplate(configs []appsv1.ComponentConfigSpec) []appsv1.ComponentTemplateSpec { - arr := make([]appsv1.ComponentTemplateSpec, 0, len(configs)) - for _, config := range configs { - arr = append(arr, config.ComponentTemplateSpec) - } - return arr -} - func ResolveCmpdParametersDefs(ctx context.Context, reader client.Reader, cmpd *appsv1.ComponentDefinition) (*parametersv1alpha1.ParamConfigRenderer, []*parametersv1alpha1.ParametersDefinition, error) { var paramsDefs []*parametersv1alpha1.ParametersDefinition