github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/start.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  	"encoding/json"
    24  	"time"
    25  
    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/constant"
    31  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    32  )
    33  
    34  type StartOpsHandler struct{}
    35  
    36  var _ OpsHandler = StartOpsHandler{}
    37  
    38  func init() {
    39  	stopBehaviour := OpsBehaviour{
    40  		FromClusterPhases:                  []appsv1alpha1.ClusterPhase{appsv1alpha1.StoppedClusterPhase},
    41  		ToClusterPhase:                     appsv1alpha1.UpdatingClusterPhase, // appsv1alpha1.StartingPhase,
    42  		OpsHandler:                         StartOpsHandler{},
    43  		ProcessingReasonInClusterCondition: ProcessingReasonStarting,
    44  	}
    45  
    46  	opsMgr := GetOpsManager()
    47  	opsMgr.RegisterOps(appsv1alpha1.StartType, stopBehaviour)
    48  }
    49  
    50  // ActionStartedCondition the started condition when handling the start request.
    51  func (start StartOpsHandler) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*metav1.Condition, error) {
    52  	return appsv1alpha1.NewStartCondition(opsRes.OpsRequest), nil
    53  }
    54  
    55  // Action modifies Cluster.spec.components[*].replicas from the opsRequest
    56  func (start StartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
    57  	cluster := opsRes.Cluster
    58  	componentReplicasMap, err := start.getComponentReplicasSnapshot(cluster.Annotations)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	for i, v := range cluster.Spec.ComponentSpecs {
    63  		replicasOfSnapshot := componentReplicasMap[v.Name]
    64  		if replicasOfSnapshot == 0 {
    65  			continue
    66  		}
    67  		// only reset the component whose replicas number is 0
    68  		if v.Replicas == 0 {
    69  			cluster.Spec.ComponentSpecs[i].Replicas = replicasOfSnapshot
    70  		}
    71  	}
    72  	// delete the replicas snapshot of components from the cluster.
    73  	delete(cluster.Annotations, constant.SnapShotForStartAnnotationKey)
    74  	return cli.Update(reqCtx.Ctx, cluster)
    75  }
    76  
    77  // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed.
    78  // the Reconcile function for start opsRequest.
    79  func (start StartOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) {
    80  	getExpectReplicas := func(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 {
    81  		componentReplicasMap, _ := start.getComponentReplicasSnapshot(opsRequest.Annotations)
    82  		replicas, ok := componentReplicasMap[componentName]
    83  		if !ok {
    84  			return nil
    85  		}
    86  		return &replicas
    87  	}
    88  
    89  	handleComponentProgress := func(reqCtx intctrlutil.RequestCtx,
    90  		cli client.Client,
    91  		opsRes *OpsResource,
    92  		pgRes progressResource,
    93  		compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, int32, error) {
    94  		return handleComponentProgressForScalingReplicas(reqCtx, cli, opsRes, pgRes, compStatus, getExpectReplicas)
    95  	}
    96  	return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "", handleComponentProgress)
    97  }
    98  
    99  // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration
   100  func (start StartOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
   101  	opsRequest := opsRes.OpsRequest
   102  	lastComponentInfo := map[string]appsv1alpha1.LastComponentConfiguration{}
   103  	componentReplicasMap, err := start.getComponentReplicasSnapshot(opsRes.Cluster.Annotations)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if err = start.setOpsAnnotation(reqCtx, cli, opsRes, componentReplicasMap); err != nil {
   108  		return err
   109  	}
   110  	for _, v := range opsRes.Cluster.Spec.ComponentSpecs {
   111  		replicasOfSnapshot := componentReplicasMap[v.Name]
   112  		if replicasOfSnapshot == 0 {
   113  			continue
   114  		}
   115  		if v.Replicas == 0 {
   116  			copyReplicas := v.Replicas
   117  			lastComponentInfo[v.Name] = appsv1alpha1.LastComponentConfiguration{
   118  				Replicas: &copyReplicas,
   119  			}
   120  		}
   121  	}
   122  	opsRequest.Status.LastConfiguration.Components = lastComponentInfo
   123  	return nil
   124  }
   125  
   126  // setOpsAnnotation sets the replicas snapshot of components before stopping the cluster to the annotations of this opsRequest.
   127  func (start StartOpsHandler) setOpsAnnotation(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, componentReplicasMap map[string]int32) error {
   128  	annotations := opsRes.OpsRequest.Annotations
   129  	if annotations == nil {
   130  		annotations = map[string]string{}
   131  	}
   132  	componentReplicasSnapshot, err := json.Marshal(componentReplicasMap)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if _, ok := opsRes.OpsRequest.Annotations[constant.SnapShotForStartAnnotationKey]; !ok {
   137  		patch := client.MergeFrom(opsRes.OpsRequest.DeepCopy())
   138  		annotations[constant.SnapShotForStartAnnotationKey] = string(componentReplicasSnapshot)
   139  		opsRes.OpsRequest.Annotations = annotations
   140  		return cli.Patch(reqCtx.Ctx, opsRes.OpsRequest, patch)
   141  	}
   142  	return nil
   143  }
   144  
   145  // getComponentReplicasSnapshot gets the replicas snapshot of components from annotations.
   146  func (start StartOpsHandler) getComponentReplicasSnapshot(annotations map[string]string) (map[string]int32, error) {
   147  	componentReplicasMap := map[string]int32{}
   148  	snapshotForStart := annotations[constant.SnapShotForStartAnnotationKey]
   149  	if len(snapshotForStart) != 0 {
   150  		if err := json.Unmarshal([]byte(snapshotForStart), &componentReplicasMap); err != nil {
   151  			return componentReplicasMap, err
   152  		}
   153  	}
   154  	return componentReplicasMap, nil
   155  }