diff --git a/controllers/apps/configuration/config_annotation.go b/controllers/apps/configuration/config_annotation.go index c9779b547aa8..54865378c344 100644 --- a/controllers/apps/configuration/config_annotation.go +++ b/controllers/apps/configuration/config_annotation.go @@ -21,6 +21,7 @@ package configuration import ( "encoding/json" + "fmt" "strconv" corev1 "k8s.io/api/core/v1" @@ -34,6 +35,43 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) +type options = func(*intctrlutil.Result) + +func reconciled(status ReturnedStatus, policy string, phase appsv1alpha1.ConfigurationPhase, options ...options) intctrlutil.Result { + result := intctrlutil.Result{ + Policy: policy, + Phase: phase, + ExecResult: string(status.Status), + SucceedCount: status.SucceedCount, + ExpectedCount: status.ExpectedCount, + Retry: true, + } + for _, option := range options { + option(&result) + } + return result +} + +func unReconciled(phase appsv1alpha1.ConfigurationPhase, revision string, message string) intctrlutil.Result { + return intctrlutil.Result{ + Phase: phase, + Revision: revision, + Message: message, + Failed: false, + Retry: false, + } +} + +func withFailed(err error, retry bool) options { + return func(result *intctrlutil.Result) { + result.Retry = retry + if err != nil { + result.Failed = true + result.Message = err.Error() + } + } +} + func checkEnableCfgUpgrade(object client.Object) bool { // check user's upgrade switch // config.kubeblocks.io/disable-reconfigure = "false" @@ -51,7 +89,11 @@ func checkEnableCfgUpgrade(object client.Object) bool { return true } -func updateConfigPhase(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, phase appsv1alpha1.ConfigurationPhase, failed bool, retry bool) (ctrl.Result, error) { +func updateConfigPhase(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, phase appsv1alpha1.ConfigurationPhase, message string) (ctrl.Result, error) { + return updateConfigPhaseWithResult(cli, ctx, config, unReconciled(phase, "", message)) +} + +func updateConfigPhaseWithResult(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, result intctrlutil.Result) (ctrl.Result, error) { revision, ok := config.ObjectMeta.Annotations[constant.ConfigurationRevision] if !ok || revision == "" { return intctrlutil.Reconciled() @@ -62,16 +104,18 @@ func updateConfigPhase(cli client.Client, ctx intctrlutil.RequestCtx, config *co config.ObjectMeta.Annotations = map[string]string{} } - if failed { + if result.Failed { config.ObjectMeta.Annotations[constant.DisableUpgradeInsConfigurationAnnotationKey] = strconv.FormatBool(true) } GcConfigRevision(config) - config.ObjectMeta.Annotations[core.GenerateRevisionPhaseKey(revision)] = string(phase) + result.Revision = revision + b, _ := json.Marshal(result) + config.ObjectMeta.Annotations[core.GenerateRevisionPhaseKey(revision)] = string(b) if err := cli.Patch(ctx.Ctx, config, patch); err != nil { return intctrlutil.RequeueWithError(err, ctx.Log, "") } - if retry { + if result.Retry { return intctrlutil.RequeueAfter(ConfigReconcileInterval, ctx.Log, "") } return intctrlutil.Reconciled() @@ -88,14 +132,14 @@ func checkAndApplyConfigsChanged(client client.Client, ctx intctrlutil.RequestCt lastConfig, ok := annotations[constant.LastAppliedConfigAnnotationKey] if !ok { - return updateAppliedConfigs(client, ctx, cm, configData, core.ReconfigureCreatedPhase) + return updateAppliedConfigs(client, ctx, cm, configData, core.ReconfigureCreatedPhase, nil) } return lastConfig == string(configData), nil } // updateAppliedConfigs updates hash label and last applied config -func updateAppliedConfigs(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, configData []byte, reconfigurePhase string) (bool, error) { +func updateAppliedConfigs(cli client.Client, ctx intctrlutil.RequestCtx, config *corev1.ConfigMap, configData []byte, reconfigurePhase string, result *intctrlutil.Result) (bool, error) { patch := client.MergeFrom(config.DeepCopy()) if config.ObjectMeta.Annotations == nil { @@ -104,7 +148,11 @@ func updateAppliedConfigs(cli client.Client, ctx intctrlutil.RequestCtx, config GcConfigRevision(config) if revision, ok := config.ObjectMeta.Annotations[constant.ConfigurationRevision]; ok && revision != "" { - config.ObjectMeta.Annotations[core.GenerateRevisionPhaseKey(revision)] = string(appsv1alpha1.CFinishedPhase) + if result == nil { + result = util.ToPointer(unReconciled(appsv1alpha1.CFinishedPhase, "", fmt.Sprintf("phase: %s", reconfigurePhase))) + } + b, _ := json.Marshal(result) + config.ObjectMeta.Annotations[core.GenerateRevisionPhaseKey(revision)] = string(b) } config.ObjectMeta.Annotations[constant.LastAppliedConfigAnnotationKey] = string(configData) hash, err := util.ComputeHash(config.Data) diff --git a/controllers/apps/configuration/configuration_test.go b/controllers/apps/configuration/configuration_test.go index 2b090a53b05a..f6390eaa49ec 100644 --- a/controllers/apps/configuration/configuration_test.go +++ b/controllers/apps/configuration/configuration_test.go @@ -62,8 +62,9 @@ func mockConfigResource() (*corev1.ConfigMap, *appsv1alpha1.ConfigConstraint) { constant.CMConfigurationSpecProviderLabelKey, configSpecName, constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType, ), - testapps.WithAnnotations(constant.KBParameterUpdateSourceAnnotationKey, - constant.ReconfigureManagerSource, + testapps.WithAnnotations( + constant.KBParameterUpdateSourceAnnotationKey, constant.ReconfigureManagerSource, + constant.ConfigurationRevision, "1", constant.CMInsEnableRerenderTemplateKey, "true")) By("Create a config constraint obj") @@ -158,7 +159,7 @@ func initConfiguration(resourceCtx *intctrlutil.ResourceCtx, synthesizedComponen }). Prepare(). UpdateConfiguration(). // reconcile Configuration - Configuration(). // sync Configuration + Configuration(). // sync Configuration CreateConfigTemplate(). UpdateConfigRelatedObject(). UpdateConfigurationStatus(). diff --git a/controllers/apps/configuration/reconfigure_controller.go b/controllers/apps/configuration/reconfigure_controller.go index 1cdb47bf3927..777df655c8b9 100644 --- a/controllers/apps/configuration/reconfigure_controller.go +++ b/controllers/apps/configuration/reconfigure_controller.go @@ -53,6 +53,12 @@ const ( ConfigReconcileInterval = time.Second * 1 ) +const ( + configurationNoChangedMessage = "the configuration file has not been modified, skip reconfigure" + configurationNotUsingMessage = "the configmap is not used by any container, skip reconfigure" + configurationNotRelatedComponentMessage = "related component does not found any configSpecs, skip reconfigure" +) + var ConfigurationRequiredLabels = []string{ constant.AppNameLabelKey, constant.AppInstanceLabelKey, @@ -102,7 +108,7 @@ func (r *ReconfigureReconciler) Reconcile(ctx context.Context, req ctrl.Request) 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, false, false) + return updateConfigPhase(r.Client, reqCtx, config, appsv1alpha1.CFinishedPhase, configurationNoChangedMessage) } // process configuration without ConfigConstraints @@ -163,7 +169,7 @@ func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *c if configPatch != nil && !configPatch.IsModify { reqCtx.Recorder.Eventf(configMap, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureRunning, "nothing changed, skip reconfigure") - return r.updateConfigCMStatus(reqCtx, configMap, core.ReconfigureNoChangeType) + return r.updateConfigCMStatus(reqCtx, configMap, core.ReconfigureNoChangeType, nil) } if configPatch != nil { @@ -197,15 +203,17 @@ func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *c } if reconcileContext.ConfigSpec == nil { reqCtx.Log.Info(fmt.Sprintf("not found configSpec[%s] in the component[%s].", configSpecName, componentName)) - reqCtx.Recorder.Eventf(configMap, corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureFailed, - "related component does not have any configSpecs, skip reconfigure") - return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, false, false) + reqCtx.Recorder.Eventf(configMap, + corev1.EventTypeWarning, + appsv1alpha1.ReasonReconfigureFailed, + configurationNotRelatedComponentMessage) + return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, configurationNotRelatedComponentMessage) } if len(reconcileContext.StatefulSets) == 0 && len(reconcileContext.Deployments) == 0 { reqCtx.Recorder.Eventf(configMap, corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureFailed, "the configmap is not used by any container, skip reconfigure") - return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, false, false) + return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, configurationNotUsingMessage) } return r.performUpgrade(reconfigureParams{ @@ -227,13 +235,13 @@ func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *c }) } -func (r *ReconfigureReconciler) updateConfigCMStatus(reqCtx intctrlutil.RequestCtx, cfg *corev1.ConfigMap, reconfigureType string) (ctrl.Result, error) { +func (r *ReconfigureReconciler) updateConfigCMStatus(reqCtx intctrlutil.RequestCtx, cfg *corev1.ConfigMap, reconfigureType string, result *intctrlutil.Result) (ctrl.Result, error) { configData, err := json.Marshal(cfg.Data) if err != nil { return intctrlutil.RequeueWithErrorAndRecordEvent(cfg, r.Recorder, err, reqCtx.Log) } - if ok, err := updateAppliedConfigs(r.Client, reqCtx, cfg, configData, reconfigureType); err != nil || !ok { + if ok, err := updateAppliedConfigs(r.Client, reqCtx, cfg, configData, reconfigureType, result); err != nil || !ok { return intctrlutil.RequeueAfter(ConfigReconcileInterval, reqCtx.Log, "failed to patch status and retry...", "error", err) } @@ -263,21 +271,45 @@ func (r *ReconfigureReconciler) performUpgrade(params reconfigureParams) (ctrl.R switch returnedStatus.Status { default: - return updateConfigPhase(params.Client, params.Ctx, params.ConfigMap, appsv1alpha1.CFailedAndPausePhase, false, false) + return updateConfigPhaseWithResult( + params.Client, + params.Ctx, + params.ConfigMap, + reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase, + withFailed(core.MakeError("unknown status"), false)), + ) case ESFailedAndRetry: - return updateConfigPhase(params.Client, params.Ctx, params.ConfigMap, appsv1alpha1.CFailedPhase, false, true) + return updateConfigPhaseWithResult( + params.Client, + params.Ctx, + params.ConfigMap, + reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedPhase, + withFailed(err, true)), + ) case ESRetry: - return updateConfigPhase(params.Client, params.Ctx, params.ConfigMap, appsv1alpha1.CUpgradingPhase, false, true) + return updateConfigPhaseWithResult( + params.Client, + params.Ctx, + params.ConfigMap, + reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CUpgradingPhase), + ) case ESFailed: - return updateConfigPhase(params.Client, params.Ctx, params.ConfigMap, appsv1alpha1.CFailedAndPausePhase, true, false) + return updateConfigPhaseWithResult( + params.Client, + params.Ctx, + params.ConfigMap, + reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase), + ) case ESNone: - params.Ctx.Recorder.Eventf(params.ConfigMap, + params.Ctx.Recorder.Eventf( + params.ConfigMap, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureSucceed, "the reconfigure[%s] request[%s] has been processed successfully", policy.GetPolicyName(), getOpsRequestID(params.ConfigMap)) - return r.updateConfigCMStatus(params.Ctx, params.ConfigMap, policy.GetPolicyName()) + result := reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFinishedPhase) + return r.updateConfigCMStatus(params.Ctx, params.ConfigMap, policy.GetPolicyName(), &result) } } diff --git a/controllers/apps/configuration/revision.go b/controllers/apps/configuration/revision.go index a6507da06493..e314da0b3676 100644 --- a/controllers/apps/configuration/revision.go +++ b/controllers/apps/configuration/revision.go @@ -20,6 +20,7 @@ along with this program. If not, see . package configuration import ( + "encoding/json" "sort" "strconv" "strings" @@ -29,12 +30,14 @@ import ( appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/constant" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) type ConfigurationRevision struct { Revision int64 StrRevision string Phase appsv1alpha1.ConfigurationPhase + Result intctrlutil.Result } const revisionHistoryLimit = 10 @@ -78,18 +81,35 @@ func RetrieveRevision(annotations map[string]string) []ConfigurationRevision { return revisions } -func parseRevision(revision string, phase string) (ConfigurationRevision, error) { +func parseRevision(revision string, data string) (ConfigurationRevision, error) { v, err := strconv.ParseInt(revision, 10, 64) if err != nil { return ConfigurationRevision{}, err } + result := parseResult(data, revision) return ConfigurationRevision{ StrRevision: revision, Revision: v, - Phase: appsv1alpha1.ConfigurationPhase(phase), + Phase: result.Phase, + Result: result, }, nil } +func parseResult(data string, revision string) intctrlutil.Result { + result := intctrlutil.Result{ + Revision: revision, + } + data = strings.TrimSpace(data) + if data == "" { + return result + } + err := json.Unmarshal([]byte(data), &result) + if err != nil { + result.Phase = appsv1alpha1.ConfigurationPhase(data) + } + return result +} + func GetCurrentRevision(annotations map[string]string) string { if len(annotations) == 0 { return "" diff --git a/controllers/apps/configuration/revision_test.go b/controllers/apps/configuration/revision_test.go index 79f9de80290e..492a7fd3efba 100644 --- a/controllers/apps/configuration/revision_test.go +++ b/controllers/apps/configuration/revision_test.go @@ -53,7 +53,7 @@ func TestGcConfigRevision(t *testing.T) { AddAnnotations(core.GenerateRevisionPhaseKey("9"), "finished"). AddAnnotations(core.GenerateRevisionPhaseKey("10"), "finished"). AddAnnotations(core.GenerateRevisionPhaseKey("11"), "finished"). - AddAnnotations(core.GenerateRevisionPhaseKey("12"), "finished"). + AddAnnotations(core.GenerateRevisionPhaseKey("12"), `{"Phase":"Finished","Revision":"12","Policy":"","ExecResult":"","SucceedCount":0,"ExpectedCount":0,"Retry":false,"Failed":false,"Message":"the configuration file has not been modified, skip reconfigure"}`). GetObject() assert.Equal(t, 12, len(RetrieveRevision(cm.GetAnnotations()))) diff --git a/pkg/controllerutil/config_util.go b/pkg/controllerutil/config_util.go index 5c2e0d45bb27..58ec220d1bb8 100644 --- a/pkg/controllerutil/config_util.go +++ b/pkg/controllerutil/config_util.go @@ -55,6 +55,20 @@ type ConfigEventContext struct { PolicyStatus core.PolicyExecStatus } +type Result struct { + Phase v1alpha1.ConfigurationPhase + Revision string + Policy string + ExecResult string + + SucceedCount int32 + ExpectedCount int32 + + Retry bool + Failed bool + Message string +} + type ConfigEventHandler interface { Handle(eventContext ConfigEventContext, lastOpsRequest string, phase v1alpha1.OpsPhase, err error) error }