github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package operations
    21  
    22  import (
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    30  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    31  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    32  )
    33  
    34  type reconfigureAction struct {
    35  }
    36  
    37  func init() {
    38  	reAction := reconfigureAction{}
    39  	opsManager := GetOpsManager()
    40  	reconfigureBehaviour := OpsBehaviour{
    41  		// REVIEW: can do opsrequest if not running?
    42  		FromClusterPhases: appsv1alpha1.GetReconfiguringRunningPhases(),
    43  		// TODO: add cluster reconcile Reconfiguring phase.
    44  		ToClusterPhase:                     appsv1alpha1.UpdatingClusterPhase,
    45  		OpsHandler:                         &reAction,
    46  		ProcessingReasonInClusterCondition: ProcessingReasonReconfiguring,
    47  	}
    48  	opsManager.RegisterOps(appsv1alpha1.ReconfiguringType, reconfigureBehaviour)
    49  }
    50  
    51  // ActionStartedCondition the started condition when handle the reconfiguring request.
    52  func (r *reconfigureAction) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*metav1.Condition, error) {
    53  	return appsv1alpha1.NewReconfigureCondition(opsRes.OpsRequest), nil
    54  }
    55  
    56  func (r *reconfigureAction) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
    57  	return nil
    58  }
    59  
    60  func handleReconfigureStatusProgress(result *appsv1alpha1.ReconcileDetail, opsStatus *appsv1alpha1.OpsRequestStatus, phase appsv1alpha1.ConfigurationPhase) handleReconfigureOpsStatus {
    61  	return func(cmStatus *appsv1alpha1.ConfigurationItemStatus) (err error) {
    62  		// the Pending phase is waiting to be executed, and there is currently no valid ReconcileDetail information.
    63  		if result != nil && phase != appsv1alpha1.CPendingPhase {
    64  			cmStatus.LastAppliedStatus = result.ExecResult
    65  			cmStatus.UpdatePolicy = appsv1alpha1.UpgradePolicy(result.Policy)
    66  			cmStatus.SucceedCount = result.SucceedCount
    67  			cmStatus.ExpectedCount = result.ExpectedCount
    68  			cmStatus.Message = result.ErrMessage
    69  			cmStatus.Status = string(phase)
    70  		}
    71  		if cmStatus.SucceedCount != core.Unconfirmed && cmStatus.ExpectedCount != core.Unconfirmed {
    72  			opsStatus.Progress = getSlowestReconfiguringProgress(opsStatus.ReconfiguringStatus.ConfigurationStatus)
    73  		}
    74  		return
    75  	}
    76  }
    77  
    78  func handleNewReconfigureRequest(configPatch *core.ConfigPatchInfo, lastAppliedConfigs map[string]string) handleReconfigureOpsStatus {
    79  	return func(cmStatus *appsv1alpha1.ConfigurationItemStatus) (err error) {
    80  		cmStatus.Status = appsv1alpha1.ReasonReconfigurePersisted
    81  		cmStatus.LastAppliedConfiguration = lastAppliedConfigs
    82  		if configPatch != nil {
    83  			cmStatus.UpdatedParameters = appsv1alpha1.UpdatedParameters{
    84  				AddedKeys:   i2sMap(configPatch.AddConfig),
    85  				UpdatedKeys: b2sMap(configPatch.UpdateConfig),
    86  				DeletedKeys: i2sMap(configPatch.DeleteConfig),
    87  			}
    88  		}
    89  		return
    90  	}
    91  }
    92  
    93  func (r *reconfigureAction) syncDependResources(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*intctrlutil.Fetcher, error) {
    94  	ops := &opsRes.OpsRequest.Spec
    95  	configSpec := ops.Reconfigure.Configurations[0]
    96  	fetcher := intctrlutil.NewResourceFetcher(&intctrlutil.ResourceCtx{
    97  		Context:       reqCtx.Ctx,
    98  		Client:        cli,
    99  		Namespace:     opsRes.Cluster.Namespace,
   100  		ClusterName:   ops.ClusterRef,
   101  		ComponentName: ops.Reconfigure.ComponentName,
   102  	})
   103  
   104  	err := fetcher.Cluster().
   105  		ClusterDef().
   106  		ClusterVer().
   107  		Configuration().
   108  		ConfigMap(configSpec.Name).
   109  		Complete()
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return fetcher, nil
   114  }
   115  
   116  func (r *reconfigureAction) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) {
   117  	status := opsRes.OpsRequest.Status
   118  	if len(status.Conditions) == 0 {
   119  		return status.Phase, 30 * time.Second, nil
   120  	}
   121  	if isNoChange(status.Conditions) {
   122  		return appsv1alpha1.OpsSucceedPhase, 0, nil
   123  	}
   124  
   125  	ops := &opsRes.OpsRequest.Spec
   126  	if ops.Reconfigure == nil || len(ops.Reconfigure.Configurations) == 0 {
   127  		return appsv1alpha1.OpsFailedPhase, 0, nil
   128  	}
   129  
   130  	resource, err := r.syncDependResources(reqCtx, cli, opsRes)
   131  	if err != nil {
   132  		return "", 30 * time.Second, err
   133  	}
   134  	configSpec := ops.Reconfigure.Configurations[0]
   135  	item := resource.ConfigurationObj.Spec.GetConfigurationItem(configSpec.Name)
   136  	itemStatus := resource.ConfigurationObj.Status.GetItemStatus(configSpec.Name)
   137  	if item == nil || itemStatus == nil {
   138  		return appsv1alpha1.OpsRunningPhase, 30 * time.Second, nil
   139  	}
   140  
   141  	switch phase := reconfiguringPhase(resource, *item, itemStatus); phase {
   142  	case appsv1alpha1.CCreatingPhase, appsv1alpha1.CInitPhase:
   143  		return appsv1alpha1.OpsFailedPhase, 0, core.MakeError("the configuration is creating or initializing, is not ready to reconfigure")
   144  	case appsv1alpha1.CFailedAndPausePhase:
   145  		return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsFailedPhase, appsv1alpha1.ReasonReconfigureFailed)
   146  	case appsv1alpha1.CFinishedPhase:
   147  		return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsSucceedPhase, appsv1alpha1.ReasonReconfigureSucceed)
   148  	default:
   149  		return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsRunningPhase, appsv1alpha1.ReasonReconfigureRunning)
   150  	}
   151  }
   152  
   153  func (r *reconfigureAction) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) error {
   154  	var (
   155  		opsRequest    = resource.OpsRequest
   156  		spec          = &opsRequest.Spec
   157  		clusterName   = spec.ClusterRef
   158  		componentName = spec.Reconfigure.ComponentName
   159  		reconfigure   = spec.Reconfigure
   160  	)
   161  
   162  	if !needReconfigure(opsRequest) {
   163  		return nil
   164  	}
   165  
   166  	// TODO support multi tpl conditions merge
   167  	item := reconfigure.Configurations[0]
   168  	opsPipeline := newPipeline(reconfigureContext{
   169  		cli:           cli,
   170  		reqCtx:        reqCtx,
   171  		resource:      resource,
   172  		config:        item,
   173  		clusterName:   clusterName,
   174  		componentName: componentName,
   175  	})
   176  
   177  	result := opsPipeline.
   178  		Configuration().
   179  		Validate().
   180  		ConfigMap(item.Name).
   181  		ConfigConstraints().
   182  		Merge().
   183  		UpdateOpsLabel().
   184  		Sync().
   185  		Complete()
   186  
   187  	if result.err != nil {
   188  		return processMergedFailed(resource, result.failed, result.err)
   189  	}
   190  
   191  	reqCtx.Recorder.Eventf(resource.OpsRequest,
   192  		corev1.EventTypeNormal,
   193  		appsv1alpha1.ReasonReconfigurePersisted,
   194  		"the reconfiguring operation of component[%s] in cluster[%s] merged successfully", componentName, clusterName)
   195  
   196  	// merged successfully
   197  	if err := patchReconfigureOpsStatus(resource, opsPipeline.configSpec.Name,
   198  		handleNewReconfigureRequest(result.configPatch, result.lastAppliedConfigs)); err != nil {
   199  		return err
   200  	}
   201  	condition := constructReconfiguringConditions(result, resource, opsPipeline.configSpec)
   202  	resource.OpsRequest.SetStatusCondition(*condition)
   203  	return nil
   204  }
   205  
   206  func needReconfigure(request *appsv1alpha1.OpsRequest) bool {
   207  	// Update params to configmap
   208  	if request.Spec.Type != appsv1alpha1.ReconfiguringType ||
   209  		request.Spec.Reconfigure == nil ||
   210  		len(request.Spec.Reconfigure.Configurations) == 0 {
   211  		return false
   212  	}
   213  
   214  	// Check if the reconfiguring operation has been processed.
   215  	for _, condition := range request.Status.Conditions {
   216  		if isExpectedPhase(condition, []string{appsv1alpha1.ReasonReconfigurePersisted, appsv1alpha1.ReasonReconfigureNoChanged}, metav1.ConditionTrue) {
   217  			return false
   218  		}
   219  	}
   220  	return true
   221  }
   222  
   223  func syncStatus(cli client.Client,
   224  	reqCtx intctrlutil.RequestCtx,
   225  	opsRes *OpsResource,
   226  	status *appsv1alpha1.ConfigurationItemDetailStatus,
   227  	phase appsv1alpha1.ConfigurationPhase,
   228  	opsPhase appsv1alpha1.OpsPhase,
   229  	reconfigurePhase string) (appsv1alpha1.OpsPhase, time.Duration, error) {
   230  	opsDeepCopy := opsRes.OpsRequest.DeepCopy()
   231  	if err := patchReconfigureOpsStatus(opsRes, status.Name,
   232  		handleReconfigureStatusProgress(status.ReconcileDetail, &opsRes.OpsRequest.Status, phase)); err != nil {
   233  		return "", 30 * time.Second, err
   234  	}
   235  	if err := PatchOpsStatusWithOpsDeepCopy(reqCtx.Ctx, cli, opsRes, opsDeepCopy, appsv1alpha1.OpsRunningPhase,
   236  		appsv1alpha1.NewReconfigureRunningCondition(opsRes.OpsRequest, reconfigurePhase, status.Name)); err != nil {
   237  		return "", 30 * time.Second, err
   238  	}
   239  	return opsPhase, 30 * time.Second, nil
   240  }
   241  
   242  func reconfiguringPhase(resource *intctrlutil.Fetcher,
   243  	detail appsv1alpha1.ConfigurationItemDetail,
   244  	status *appsv1alpha1.ConfigurationItemDetailStatus) appsv1alpha1.ConfigurationPhase {
   245  	if status.ReconcileDetail == nil || status.ReconcileDetail.CurrentRevision != status.UpdateRevision {
   246  		return appsv1alpha1.CPendingPhase
   247  	}
   248  	return intctrlutil.GetConfigSpecReconcilePhase(resource.ConfigMapObj, detail, status)
   249  }
   250  
   251  func isExpectedPhase(condition metav1.Condition, expectedTypes []string, expectedStatus metav1.ConditionStatus) bool {
   252  	for _, t := range expectedTypes {
   253  		if t == condition.Type && condition.Status == expectedStatus {
   254  			return true
   255  		}
   256  	}
   257  	return false
   258  }
   259  
   260  func isNoChange(conditions []metav1.Condition) bool {
   261  	for i := len(conditions); i > 0; i-- {
   262  		if isExpectedPhase(conditions[i-1], []string{appsv1alpha1.ReasonReconfigureNoChanged}, metav1.ConditionTrue) {
   263  			return true
   264  		}
   265  	}
   266  	return false
   267  }