github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/ops_manager.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  	"sync"
    24  	"time"
    25  
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    30  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    31  )
    32  
    33  var (
    34  	opsManagerOnce sync.Once
    35  	opsManager     *OpsManager
    36  )
    37  
    38  // RegisterOps registers operation with OpsType and OpsBehaviour
    39  func (opsMgr *OpsManager) RegisterOps(opsType appsv1alpha1.OpsType, opsBehaviour OpsBehaviour) {
    40  	opsManager.OpsMap[opsType] = opsBehaviour
    41  	appsv1alpha1.OpsRequestBehaviourMapper[opsType] = appsv1alpha1.OpsRequestBehaviour{
    42  		FromClusterPhases:                  opsBehaviour.FromClusterPhases,
    43  		ToClusterPhase:                     opsBehaviour.ToClusterPhase,
    44  		ProcessingReasonInClusterCondition: opsBehaviour.ProcessingReasonInClusterCondition,
    45  	}
    46  }
    47  
    48  // Do the entry function for handling OpsRequest
    49  func (opsMgr *OpsManager) Do(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*ctrl.Result, error) {
    50  	var (
    51  		opsBehaviour OpsBehaviour
    52  		err          error
    53  		ok           bool
    54  		opsRequest   = opsRes.OpsRequest
    55  	)
    56  	if opsBehaviour, ok = opsMgr.OpsMap[opsRequest.Spec.Type]; !ok || opsBehaviour.OpsHandler == nil {
    57  		return nil, patchOpsHandlerNotSupported(reqCtx.Ctx, cli, opsRes)
    58  	}
    59  	// validate OpsRequest.spec
    60  	if err = opsRequest.Validate(reqCtx.Ctx, cli, opsRes.Cluster, true); err != nil {
    61  		if patchErr := patchValidateErrorCondition(reqCtx.Ctx, cli, opsRes, err.Error()); patchErr != nil {
    62  			return nil, patchErr
    63  		}
    64  		return nil, err
    65  	}
    66  	// validate entry condition for OpsRequest
    67  	if opsRequest.Status.Phase == appsv1alpha1.OpsPendingPhase {
    68  		if err = validateOpsWaitingPhase(opsRes.Cluster, opsRequest, opsBehaviour); err != nil {
    69  			// check if the error is caused by WaitForClusterPhaseErr  error
    70  			if _, ok := err.(*WaitForClusterPhaseErr); ok {
    71  				return intctrlutil.ResultToP(intctrlutil.RequeueAfter(time.Second, reqCtx.Log, ""))
    72  			}
    73  			if patchErr := patchValidateErrorCondition(reqCtx.Ctx, cli, opsRes, err.Error()); patchErr != nil {
    74  				return nil, patchErr
    75  			}
    76  			return nil, err
    77  		}
    78  	}
    79  
    80  	if opsRequest.Status.Phase != appsv1alpha1.OpsCreatingPhase {
    81  		// If the operation causes the cluster phase to change, the cluster needs to be locked.
    82  		// At the same time, only one operation is running if these operations are mutually exclusive(exist opsBehaviour.ToClusterPhase).
    83  		if err = addOpsRequestAnnotationToCluster(reqCtx.Ctx, cli, opsRes, opsBehaviour); err != nil {
    84  			return nil, err
    85  		}
    86  
    87  		opsDeepCopy := opsRequest.DeepCopy()
    88  		// save last configuration into status.lastConfiguration
    89  		if err = opsBehaviour.OpsHandler.SaveLastConfiguration(reqCtx, cli, opsRes); err != nil {
    90  			return nil, err
    91  		}
    92  
    93  		return &ctrl.Result{}, patchOpsRequestToCreating(reqCtx, cli, opsRes, opsDeepCopy, opsBehaviour.OpsHandler)
    94  	}
    95  
    96  	if err = opsBehaviour.OpsHandler.Action(reqCtx, cli, opsRes); err != nil {
    97  		// patch the status.phase to Failed when the error is FastFaileError, which means the operation is failed and there is no need to retry
    98  		if _, ok := err.(*FastFaileError); ok {
    99  			if patchErr := patchFastFailErrorCondition(reqCtx.Ctx, cli, opsRes, err); patchErr != nil {
   100  				return nil, patchErr
   101  			}
   102  		}
   103  		return nil, err
   104  	}
   105  
   106  	return nil, nil
   107  }
   108  
   109  // Reconcile entry function when OpsRequest.status.phase is Running.
   110  // loops till the operation is completed.
   111  func (opsMgr *OpsManager) Reconcile(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (time.Duration, error) {
   112  	var (
   113  		opsBehaviour    OpsBehaviour
   114  		ok              bool
   115  		err             error
   116  		requeueAfter    time.Duration
   117  		opsRequestPhase appsv1alpha1.OpsPhase
   118  		opsRequest      = opsRes.OpsRequest
   119  	)
   120  
   121  	if opsBehaviour, ok = opsMgr.OpsMap[opsRes.OpsRequest.Spec.Type]; !ok || opsBehaviour.OpsHandler == nil {
   122  		return 0, patchOpsHandlerNotSupported(reqCtx.Ctx, cli, opsRes)
   123  	}
   124  	opsRes.ToClusterPhase = opsBehaviour.ToClusterPhase
   125  	if opsRequestPhase, requeueAfter, err = opsBehaviour.OpsHandler.ReconcileAction(reqCtx, cli, opsRes); err != nil &&
   126  		!isOpsRequestFailedPhase(opsRequestPhase) {
   127  		// if the opsRequest phase is not failed, skipped
   128  		return requeueAfter, err
   129  	}
   130  	switch opsRequestPhase {
   131  	case appsv1alpha1.OpsSucceedPhase:
   132  		if opsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase {
   133  			return 0, PatchOpsStatus(reqCtx.Ctx, cli, opsRes, appsv1alpha1.OpsCancelledPhase, appsv1alpha1.NewCancelSucceedCondition(opsRequest.Name))
   134  		}
   135  		return 0, PatchOpsStatus(reqCtx.Ctx, cli, opsRes, opsRequestPhase, appsv1alpha1.NewSucceedCondition(opsRequest))
   136  	case appsv1alpha1.OpsFailedPhase:
   137  		if opsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase {
   138  			return 0, PatchOpsStatus(reqCtx.Ctx, cli, opsRes, appsv1alpha1.OpsCancelledPhase, appsv1alpha1.NewCancelFailedCondition(opsRequest, err))
   139  		}
   140  		return 0, PatchOpsStatus(reqCtx.Ctx, cli, opsRes, opsRequestPhase, appsv1alpha1.NewFailedCondition(opsRequest, err))
   141  	default:
   142  		return requeueAfter, nil
   143  	}
   144  }
   145  
   146  func GetOpsManager() *OpsManager {
   147  	opsManagerOnce.Do(func() {
   148  		opsManager = &OpsManager{OpsMap: make(map[appsv1alpha1.OpsType]OpsBehaviour)}
   149  	})
   150  	return opsManager
   151  }