github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/ops_progress_util.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  	"fmt"
    24  	"time"
    25  
    26  	"golang.org/x/exp/slices"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/client-go/tools/record"
    30  	"k8s.io/kubectl/pkg/util/podutils"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    34  	"github.com/1aal/kubeblocks/controllers/apps/components"
    35  	"github.com/1aal/kubeblocks/pkg/constant"
    36  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    37  )
    38  
    39  // getProgressObjectKey gets progress object key from the client.Object.
    40  func getProgressObjectKey(kind, name string) string {
    41  	return fmt.Sprintf("%s/%s", kind, name)
    42  }
    43  
    44  // isCompletedProgressStatus checks the progress detail with final state, either Failed or Succeed.
    45  func isCompletedProgressStatus(status appsv1alpha1.ProgressStatus) bool {
    46  	return slices.Contains([]appsv1alpha1.ProgressStatus{appsv1alpha1.SucceedProgressStatus,
    47  		appsv1alpha1.FailedProgressStatus}, status)
    48  }
    49  
    50  // setComponentStatusProgressDetail sets the corresponding progressDetail in progressDetails to newProgressDetail.
    51  // progressDetails must be non-nil.
    52  // 1. the startTime and endTime will be filled automatically.
    53  // 2. if the progressDetail of the specified objectKey does not exist, it will be appended to the progressDetails.
    54  func setComponentStatusProgressDetail(
    55  	recorder record.EventRecorder,
    56  	opsRequest *appsv1alpha1.OpsRequest,
    57  	progressDetails *[]appsv1alpha1.ProgressStatusDetail,
    58  	newProgressDetail appsv1alpha1.ProgressStatusDetail) {
    59  	if progressDetails == nil {
    60  		return
    61  	}
    62  	existingProgressDetail := findStatusProgressDetail(*progressDetails, newProgressDetail.ObjectKey)
    63  	if existingProgressDetail == nil {
    64  		updateProgressDetailTime(&newProgressDetail)
    65  		*progressDetails = append(*progressDetails, newProgressDetail)
    66  		sendProgressDetailEvent(recorder, opsRequest, newProgressDetail)
    67  		return
    68  	}
    69  	if existingProgressDetail.Status == newProgressDetail.Status &&
    70  		existingProgressDetail.Message == newProgressDetail.Message {
    71  		return
    72  	}
    73  	// if existing progress detail is 'Failed' and new progress detail is not 'Succeed', ignores the new one.
    74  	if existingProgressDetail.Status == appsv1alpha1.FailedProgressStatus &&
    75  		newProgressDetail.Status != appsv1alpha1.SucceedProgressStatus {
    76  		return
    77  	}
    78  	existingProgressDetail.Status = newProgressDetail.Status
    79  	existingProgressDetail.Message = newProgressDetail.Message
    80  	updateProgressDetailTime(existingProgressDetail)
    81  	sendProgressDetailEvent(recorder, opsRequest, newProgressDetail)
    82  }
    83  
    84  // findStatusProgressDetail finds the progressDetail of the specified objectKey in progressDetails.
    85  func findStatusProgressDetail(progressDetails []appsv1alpha1.ProgressStatusDetail,
    86  	objectKey string) *appsv1alpha1.ProgressStatusDetail {
    87  	for i := range progressDetails {
    88  		if progressDetails[i].ObjectKey == objectKey {
    89  			return &progressDetails[i]
    90  		}
    91  	}
    92  	return nil
    93  }
    94  
    95  // getProgressDetailEventType gets the event type with progressDetail status.
    96  func getProgressDetailEventType(status appsv1alpha1.ProgressStatus) string {
    97  	if status == appsv1alpha1.FailedProgressStatus {
    98  		return corev1.EventTypeWarning
    99  	}
   100  	return corev1.EventTypeNormal
   101  }
   102  
   103  // getProgressDetailEventReason gets the event reason with progressDetail status.
   104  func getProgressDetailEventReason(status appsv1alpha1.ProgressStatus) string {
   105  	switch status {
   106  	case appsv1alpha1.SucceedProgressStatus:
   107  		return "Succeed"
   108  	case appsv1alpha1.ProcessingProgressStatus:
   109  		return "Processing"
   110  	case appsv1alpha1.FailedProgressStatus:
   111  		return "Failed"
   112  	}
   113  	return ""
   114  }
   115  
   116  // sendProgressDetailEvent sends the progress detail changed events.
   117  func sendProgressDetailEvent(recorder record.EventRecorder,
   118  	opsRequest *appsv1alpha1.OpsRequest,
   119  	progressDetail appsv1alpha1.ProgressStatusDetail) {
   120  	status := progressDetail.Status
   121  	if status == appsv1alpha1.PendingProgressStatus {
   122  		return
   123  	}
   124  	recorder.Event(opsRequest, getProgressDetailEventType(status),
   125  		getProgressDetailEventReason(status), progressDetail.Message)
   126  }
   127  
   128  // updateProgressDetailTime updates the progressDetail startTime or endTime according to the status.
   129  func updateProgressDetailTime(progressDetail *appsv1alpha1.ProgressStatusDetail) {
   130  	if progressDetail.Status == appsv1alpha1.ProcessingProgressStatus &&
   131  		progressDetail.StartTime.IsZero() {
   132  		progressDetail.StartTime = metav1.NewTime(time.Now())
   133  	}
   134  	if isCompletedProgressStatus(progressDetail.Status) &&
   135  		progressDetail.EndTime.IsZero() {
   136  		progressDetail.EndTime = metav1.NewTime(time.Now())
   137  	}
   138  }
   139  
   140  // convertPodObjectKeyMap converts the object key map from the pod list.
   141  func convertPodObjectKeyMap(podList *corev1.PodList) map[string]struct{} {
   142  	podObjectKeyMap := map[string]struct{}{}
   143  	for _, v := range podList.Items {
   144  		objectKey := getProgressObjectKey(v.Kind, v.Name)
   145  		podObjectKeyMap[objectKey] = struct{}{}
   146  	}
   147  	return podObjectKeyMap
   148  }
   149  
   150  // removeStatelessExpiredPods if the object of progressDetail is not existing in k8s cluster, it indicates the pod is deleted.
   151  // For example, a replicaSet may attempt to create a pod multiple times till it succeeds.
   152  // so some pod may be expired, we should clear them.
   153  func removeStatelessExpiredPods(podList *corev1.PodList,
   154  	progressDetails []appsv1alpha1.ProgressStatusDetail) []appsv1alpha1.ProgressStatusDetail {
   155  	podObjectKeyMap := convertPodObjectKeyMap(podList)
   156  	newProgressDetails := make([]appsv1alpha1.ProgressStatusDetail, 0)
   157  	for _, v := range progressDetails {
   158  		if _, ok := podObjectKeyMap[v.ObjectKey]; ok {
   159  			newProgressDetails = append(newProgressDetails, v)
   160  		}
   161  	}
   162  	return newProgressDetails
   163  }
   164  
   165  // handleComponentStatusProgress handles the component status progressDetails.
   166  // if all the pods of the component are affected, use this function to reconcile the progressDetails.
   167  func handleComponentStatusProgress(
   168  	reqCtx intctrlutil.RequestCtx,
   169  	cli client.Client,
   170  	opsRes *OpsResource,
   171  	pgRes progressResource,
   172  	compStatus *appsv1alpha1.OpsRequestComponentStatus) (expectProgressCount int32, completedCount int32, err error) {
   173  	var (
   174  		podList             *corev1.PodList
   175  		clusterComponentDef = pgRes.clusterComponentDef
   176  		clusterComponent    = pgRes.clusterComponent
   177  	)
   178  	if clusterComponent == nil || clusterComponentDef == nil {
   179  		return
   180  	}
   181  	if podList, err = components.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, clusterComponent.Name); err != nil {
   182  		return
   183  	}
   184  	switch clusterComponentDef.WorkloadType {
   185  	case appsv1alpha1.Stateless:
   186  		completedCount, err = handleStatelessProgress(reqCtx, cli, opsRes, podList, pgRes, compStatus)
   187  	default:
   188  		completedCount, err = handleStatefulSetProgress(reqCtx, cli, opsRes, podList, pgRes, compStatus)
   189  	}
   190  	expectReplicas := clusterComponent.Replicas
   191  	if opsRes.OpsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase {
   192  		// only rollback the actual re-created pod during cancelling.
   193  		expectReplicas = int32(len(compStatus.ProgressDetails))
   194  	}
   195  	return expectReplicas, completedCount, err
   196  }
   197  
   198  // handleStatelessProgress handles the stateless component progressDetails.
   199  // For stateless component changes, it applies the Deployment updating policy.
   200  func handleStatelessProgress(reqCtx intctrlutil.RequestCtx,
   201  	cli client.Client,
   202  	opsRes *OpsResource,
   203  	podList *corev1.PodList,
   204  	pgRes progressResource,
   205  	compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, error) {
   206  	if compStatus.Phase == appsv1alpha1.RunningClusterCompPhase && pgRes.clusterComponent.Replicas != int32(len(podList.Items)) {
   207  		return 0, intctrlutil.NewError(intctrlutil.ErrorWaitCacheRefresh, "wait for the pods of deployment to be synchronized")
   208  	}
   209  	minReadySeconds, err := components.GetComponentDeployMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, pgRes.clusterComponent.Name)
   210  	if err != nil {
   211  		return 0, err
   212  	}
   213  	completedCount := handleRollingUpdateProgress(opsRes, podList, pgRes, compStatus, minReadySeconds)
   214  	compStatus.ProgressDetails = removeStatelessExpiredPods(podList, compStatus.ProgressDetails)
   215  	return completedCount, nil
   216  }
   217  
   218  // handleStatefulSetProgress handles the component progressDetails which using statefulSet workloads.
   219  func handleStatefulSetProgress(reqCtx intctrlutil.RequestCtx,
   220  	cli client.Client,
   221  	opsRes *OpsResource,
   222  	podList *corev1.PodList,
   223  	pgRes progressResource,
   224  	compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, error) {
   225  	minReadySeconds, err := components.GetComponentStsMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, pgRes.clusterComponent.Name)
   226  	if err != nil {
   227  		return 0, err
   228  	}
   229  	return handleRollingUpdateProgress(opsRes, podList, pgRes, compStatus, minReadySeconds), nil
   230  }
   231  
   232  // handleRollingUpdateProgress handles the component progressDetails during rolling update.
   233  func handleRollingUpdateProgress(
   234  	opsRes *OpsResource,
   235  	podList *corev1.PodList,
   236  	pgRes progressResource,
   237  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   238  	minReadySeconds int32) int32 {
   239  	if opsRes.OpsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase {
   240  		return handleCancelProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds)
   241  	}
   242  	return handleProgressForPodsRollingUpdate(opsRes, podList, pgRes, compStatus, minReadySeconds)
   243  }
   244  
   245  // handleProgressForPodsRollingUpdate handles the progress of pods during rolling update.
   246  func handleProgressForPodsRollingUpdate(
   247  	opsRes *OpsResource,
   248  	podList *corev1.PodList,
   249  	pgRes progressResource,
   250  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   251  	minReadySeconds int32) int32 {
   252  	workloadType := pgRes.clusterComponentDef.WorkloadType
   253  	opsRequest := opsRes.OpsRequest
   254  	opsStartTime := opsRequest.Status.StartTimestamp
   255  	var completedCount int32
   256  	for _, v := range podList.Items {
   257  		objectKey := getProgressObjectKey(v.Kind, v.Name)
   258  		progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey}
   259  		if podProcessedSuccessful(workloadType, opsStartTime, &v, minReadySeconds, compStatus.Phase, pgRes.opsIsCompleted) {
   260  			completedCount += 1
   261  			handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail)
   262  			continue
   263  		}
   264  		if podIsPendingDuringOperation(opsStartTime, &v) {
   265  			handlePendingProgressDetail(opsRes, compStatus, progressDetail)
   266  			continue
   267  		}
   268  		completedCount += handleFailedOrProcessingProgressDetail(opsRes, pgRes, compStatus, progressDetail, &v)
   269  	}
   270  	return completedCount
   271  }
   272  
   273  // handleCancelProgressForPodsRollingUpdate handles the cancel progress of pods during rolling update.
   274  func handleCancelProgressForPodsRollingUpdate(
   275  	opsRes *OpsResource,
   276  	podList *corev1.PodList,
   277  	pgRes progressResource,
   278  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   279  	minReadySeconds int32) int32 {
   280  	var newProgressDetails []appsv1alpha1.ProgressStatusDetail
   281  	for _, v := range compStatus.ProgressDetails {
   282  		// remove the pending progressDetail
   283  		if v.Status != appsv1alpha1.PendingProgressStatus {
   284  			newProgressDetails = append(newProgressDetails, v)
   285  		}
   286  	}
   287  	compStatus.ProgressDetails = newProgressDetails
   288  	opsCancelTime := opsRes.OpsRequest.Status.CancelTimestamp
   289  	workloadType := pgRes.clusterComponentDef.WorkloadType
   290  	pgRes.opsMessageKey = fmt.Sprintf("%s with rollback", pgRes.opsMessageKey)
   291  	var completedCount int32
   292  	for _, pod := range podList.Items {
   293  		objectKey := getProgressObjectKey(pod.Kind, pod.Name)
   294  		progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey}
   295  		if !pod.CreationTimestamp.Before(&opsCancelTime) &&
   296  			podIsAvailable(workloadType, &pod, minReadySeconds) {
   297  			completedCount += 1
   298  			handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail)
   299  			continue
   300  		}
   301  		if podIsPendingDuringOperation(opsCancelTime, &pod) {
   302  			continue
   303  		}
   304  		completedCount += handleFailedOrProcessingProgressDetail(opsRes, pgRes, compStatus, progressDetail, &pod)
   305  	}
   306  	return completedCount
   307  }
   308  
   309  func podIsAvailable(workloadType appsv1alpha1.WorkloadType, pod *corev1.Pod, minReadySeconds int32) bool {
   310  	if pod == nil {
   311  		return false
   312  	}
   313  	switch workloadType {
   314  	case appsv1alpha1.Consensus, appsv1alpha1.Replication:
   315  		return intctrlutil.PodIsReadyWithLabel(*pod)
   316  	case appsv1alpha1.Stateful, appsv1alpha1.Stateless:
   317  		return podutils.IsPodAvailable(pod, minReadySeconds, metav1.Time{Time: time.Now()})
   318  	default:
   319  		panic("unknown workload type")
   320  	}
   321  }
   322  
   323  // handlePendingProgressDetail handles the pending progressDetail and sets it to progressDetails.
   324  func handlePendingProgressDetail(opsRes *OpsResource,
   325  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   326  	progressDetail appsv1alpha1.ProgressStatusDetail,
   327  ) {
   328  	progressDetail.Status = appsv1alpha1.PendingProgressStatus
   329  	setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest,
   330  		&compStatus.ProgressDetails, progressDetail)
   331  }
   332  
   333  // handleSucceedProgressDetail handles the successful progressDetail and sets it to progressDetails.
   334  func handleSucceedProgressDetail(opsRes *OpsResource,
   335  	pgRes progressResource,
   336  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   337  	progressDetail appsv1alpha1.ProgressStatusDetail,
   338  ) {
   339  	progressDetail.SetStatusAndMessage(appsv1alpha1.SucceedProgressStatus,
   340  		getProgressSucceedMessage(pgRes.opsMessageKey, progressDetail.ObjectKey, pgRes.clusterComponent.Name))
   341  	setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest,
   342  		&compStatus.ProgressDetails, progressDetail)
   343  }
   344  
   345  // handleFailedOrProcessingProgressDetail handles failed or processing progressDetail and sets it to progressDetails.
   346  func handleFailedOrProcessingProgressDetail(opsRes *OpsResource,
   347  	pgRes progressResource,
   348  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   349  	progressDetail appsv1alpha1.ProgressStatusDetail,
   350  	pod *corev1.Pod) (completedCount int32) {
   351  	componentName := pgRes.clusterComponent.Name
   352  	opsStartTime := opsRes.OpsRequest.Status.StartTimestamp
   353  	if podIsFailedDuringOperation(opsStartTime, pod, compStatus.Phase, pgRes.opsIsCompleted) {
   354  		podMessage := getFailedPodMessage(opsRes.Cluster, componentName, pod)
   355  		// if the pod is not failed, return
   356  		if len(podMessage) == 0 {
   357  			return
   358  		}
   359  		message := getProgressFailedMessage(pgRes.opsMessageKey, progressDetail.ObjectKey, componentName, podMessage)
   360  		progressDetail.SetStatusAndMessage(appsv1alpha1.FailedProgressStatus, message)
   361  		completedCount = 1
   362  	} else {
   363  		progressDetail.SetStatusAndMessage(appsv1alpha1.ProcessingProgressStatus,
   364  			getProgressProcessingMessage(pgRes.opsMessageKey, progressDetail.ObjectKey, componentName))
   365  	}
   366  	setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest,
   367  		&compStatus.ProgressDetails, progressDetail)
   368  	return completedCount
   369  }
   370  
   371  // podIsPendingDuringOperation checks if pod is pending during the component's operation.
   372  func podIsPendingDuringOperation(opsStartTime metav1.Time, pod *corev1.Pod) bool {
   373  	return pod.CreationTimestamp.Before(&opsStartTime) && pod.DeletionTimestamp.IsZero()
   374  }
   375  
   376  // podIsFailedDuringOperation checks if pod is failed during operation.
   377  func podIsFailedDuringOperation(
   378  	opsStartTime metav1.Time,
   379  	pod *corev1.Pod,
   380  	componentPhase appsv1alpha1.ClusterComponentPhase,
   381  	opsIsCompleted bool) bool {
   382  	if !components.IsFailedOrAbnormal(componentPhase) {
   383  		return false
   384  	}
   385  	return !pod.CreationTimestamp.Before(&opsStartTime) || opsIsCompleted
   386  }
   387  
   388  // podProcessedSuccessful checks if the pod has been processed successfully:
   389  // 1. the pod is recreated after OpsRequest.status.startTime and pod is available.
   390  // 2. the component is running and pod is available.
   391  func podProcessedSuccessful(workloadType appsv1alpha1.WorkloadType,
   392  	opsStartTime metav1.Time,
   393  	pod *corev1.Pod,
   394  	minReadySeconds int32,
   395  	componentPhase appsv1alpha1.ClusterComponentPhase,
   396  	opsIsCompleted bool) bool {
   397  	if !podIsAvailable(workloadType, pod, minReadySeconds) {
   398  		return false
   399  	}
   400  	return (opsIsCompleted && componentPhase == appsv1alpha1.RunningClusterCompPhase) || !pod.CreationTimestamp.Before(&opsStartTime)
   401  }
   402  
   403  func getProgressProcessingMessage(opsMessageKey, objectKey, componentName string) string {
   404  	return fmt.Sprintf("Start to %s: %s in Component: %s", opsMessageKey, objectKey, componentName)
   405  }
   406  
   407  func getProgressSucceedMessage(opsMessageKey, objectKey, componentName string) string {
   408  	return fmt.Sprintf("Successfully %s: %s in Component: %s", opsMessageKey, objectKey, componentName)
   409  }
   410  
   411  func getProgressFailedMessage(opsMessageKey, objectKey, componentName, podMessage string) string {
   412  	return fmt.Sprintf("Failed to %s: %s in Component: %s, message: %s", opsMessageKey, objectKey, componentName, podMessage)
   413  }
   414  
   415  // getFailedPodMessage gets the failed pod message from cluster component status
   416  func getFailedPodMessage(cluster *appsv1alpha1.Cluster, componentName string, pod *corev1.Pod) string {
   417  	clusterCompStatus := cluster.Status.Components[componentName]
   418  	return clusterCompStatus.GetObjectMessage(pod.Kind, pod.Name)
   419  }
   420  
   421  func getComponentLastReplicas(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32 {
   422  	lastCompConfiguration := opsRequest.Status.LastConfiguration.Components[componentName]
   423  	if lastCompConfiguration.Replicas == nil {
   424  		return nil
   425  	}
   426  	if lastPods, ok := lastCompConfiguration.TargetResources[appsv1alpha1.PodsCompResourceKey]; ok {
   427  		lastActualComponents := int32(len(lastPods))
   428  		// may the actual pods not equals the component replicas
   429  		if lastActualComponents < *lastCompConfiguration.Replicas {
   430  			return &lastActualComponents
   431  		}
   432  	}
   433  	return lastCompConfiguration.Replicas
   434  }
   435  
   436  // handleComponentProgressDetails handles the component progressDetails when scale the replicas.
   437  // @return expectProgressCount,
   438  // @return completedCount
   439  // @return error
   440  func handleComponentProgressForScalingReplicas(reqCtx intctrlutil.RequestCtx,
   441  	cli client.Client,
   442  	opsRes *OpsResource,
   443  	pgRes progressResource,
   444  	compStatus *appsv1alpha1.OpsRequestComponentStatus,
   445  	getExpectReplicas func(opsRequest *appsv1alpha1.OpsRequest, componentName string) *int32) (int32, int32, error) {
   446  	var (
   447  		podList          *corev1.PodList
   448  		clusterComponent = pgRes.clusterComponent
   449  		opsRequest       = opsRes.OpsRequest
   450  		err              error
   451  	)
   452  	if clusterComponent == nil || pgRes.clusterComponentDef == nil {
   453  		return 0, 0, nil
   454  	}
   455  	expectReplicas := getExpectReplicas(opsRequest, clusterComponent.Name)
   456  	if expectReplicas == nil {
   457  		return 0, 0, nil
   458  	}
   459  	lastComponentReplicas := getComponentLastReplicas(opsRequest, clusterComponent.Name)
   460  	if lastComponentReplicas == nil {
   461  		return 0, 0, nil
   462  	}
   463  	// if replicas are not changed, return
   464  	if *lastComponentReplicas == *expectReplicas {
   465  		return 0, 0, nil
   466  	}
   467  	if podList, err = components.GetComponentPodList(reqCtx.Ctx, cli, *opsRes.Cluster, clusterComponent.Name); err != nil {
   468  		return 0, 0, err
   469  	}
   470  	actualPodsLen := int32(len(podList.Items))
   471  	if compStatus.Phase == appsv1alpha1.RunningClusterCompPhase && pgRes.clusterComponent.Replicas != actualPodsLen {
   472  		return 0, 0, intctrlutil.NewError(intctrlutil.ErrorWaitCacheRefresh, "wait for the pods of component to be synchronized")
   473  	}
   474  	if opsRequest.Status.Phase == appsv1alpha1.OpsCancellingPhase {
   475  		expectReplicas = opsRequest.Status.LastConfiguration.Components[clusterComponent.Name].Replicas
   476  	}
   477  	var (
   478  		isScaleOut          bool
   479  		expectProgressCount int32
   480  		completedCount      int32
   481  		dValue              = *expectReplicas - *lastComponentReplicas
   482  	)
   483  	if dValue > 0 {
   484  		expectProgressCount = dValue
   485  		isScaleOut = true
   486  	} else {
   487  		expectProgressCount = dValue * -1
   488  	}
   489  	if isScaleOut {
   490  		completedCount, err = handleScaleOutProgress(reqCtx, cli, opsRes, pgRes, podList, compStatus)
   491  		// if the workload type is Stateless, remove the progressDetails of the expired pods.
   492  		// because ReplicaSet may attempt to create a pod multiple times till it succeeds when scale out the replicas.
   493  		if pgRes.clusterComponentDef.WorkloadType == appsv1alpha1.Stateless {
   494  			compStatus.ProgressDetails = removeStatelessExpiredPods(podList, compStatus.ProgressDetails)
   495  		}
   496  	} else {
   497  		completedCount, err = handleScaleDownProgress(reqCtx, cli, opsRes, pgRes, podList, compStatus)
   498  	}
   499  	return getFinalExpectCount(compStatus, expectProgressCount), completedCount, err
   500  }
   501  
   502  // handleScaleOutProgress handles the progressDetails of scaled out replicas.
   503  func handleScaleOutProgress(reqCtx intctrlutil.RequestCtx,
   504  	cli client.Client,
   505  	opsRes *OpsResource,
   506  	pgRes progressResource,
   507  	podList *corev1.PodList,
   508  	compStatus *appsv1alpha1.OpsRequestComponentStatus) (int32, error) {
   509  	var componentName = pgRes.clusterComponent.Name
   510  	var workloadType = pgRes.clusterComponentDef.WorkloadType
   511  	minReadySeconds, err := components.GetComponentWorkloadMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, workloadType, componentName)
   512  	if err != nil {
   513  		return 0, err
   514  	}
   515  	var completedCount int32
   516  	for _, v := range podList.Items {
   517  		// only focus on the newly created pod when scaling out the replicas.
   518  		if v.CreationTimestamp.Before(&opsRes.OpsRequest.Status.StartTimestamp) {
   519  			continue
   520  		}
   521  		objectKey := getProgressObjectKey(v.Kind, v.Name)
   522  		progressDetail := appsv1alpha1.ProgressStatusDetail{ObjectKey: objectKey}
   523  		pgRes.opsMessageKey = "create"
   524  		if podIsAvailable(workloadType, &v, minReadySeconds) {
   525  			completedCount += 1
   526  			handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail)
   527  			continue
   528  		}
   529  		completedCount += handleFailedOrProcessingProgressDetail(opsRes, pgRes, compStatus, progressDetail, &v)
   530  	}
   531  	return completedCount, nil
   532  }
   533  
   534  // handleScaleDownProgress handles the progressDetails of scaled down replicas.
   535  func handleScaleDownProgress(
   536  	reqCtx intctrlutil.RequestCtx,
   537  	cli client.Client,
   538  	opsRes *OpsResource,
   539  	pgRes progressResource,
   540  	podList *corev1.PodList,
   541  	compStatus *appsv1alpha1.OpsRequestComponentStatus) (completedCount int32, err error) {
   542  	podMap := map[string]corev1.Pod{}
   543  	// record the deleting pod progressDetail
   544  	for _, v := range podList.Items {
   545  		objectKey := getProgressObjectKey(constant.PodKind, v.Name)
   546  		podMap[objectKey] = v
   547  		if v.DeletionTimestamp.IsZero() {
   548  			continue
   549  		}
   550  		setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest,
   551  			&compStatus.ProgressDetails, appsv1alpha1.ProgressStatusDetail{
   552  				ObjectKey: objectKey,
   553  				Status:    appsv1alpha1.ProcessingProgressStatus,
   554  				Message:   fmt.Sprintf("Start to delete pod: %s in Component: %s", objectKey, pgRes.clusterComponent.Name),
   555  			})
   556  	}
   557  	var workloadType = pgRes.clusterComponentDef.WorkloadType
   558  	var componentName = pgRes.clusterComponent.Name
   559  	minReadySeconds, err := components.GetComponentStsMinReadySeconds(reqCtx.Ctx, cli, *opsRes.Cluster, componentName)
   560  	if err != nil {
   561  		return 0, err
   562  	}
   563  
   564  	handleDeletionSuccessful := func(objectKey string) {
   565  		// if the pod is not in the podList, it means the pod has been deleted.
   566  		progressDetail := appsv1alpha1.ProgressStatusDetail{
   567  			ObjectKey: objectKey,
   568  			Status:    appsv1alpha1.SucceedProgressStatus,
   569  			Message:   fmt.Sprintf("Successfully delete pod: %s in Component: %s", objectKey, pgRes.clusterComponent.Name),
   570  		}
   571  		completedCount += 1
   572  		setComponentStatusProgressDetail(opsRes.Recorder, opsRes.OpsRequest,
   573  			&compStatus.ProgressDetails, progressDetail)
   574  	}
   575  
   576  	handleProgressDetails := func() {
   577  		for _, progressDetail := range compStatus.ProgressDetails {
   578  			if isCompletedProgressStatus(progressDetail.Status) {
   579  				completedCount += 1
   580  				continue
   581  			}
   582  			// if pod not exists, means successful deletion.
   583  			pod, ok := podMap[progressDetail.ObjectKey]
   584  			if !ok {
   585  				handleDeletionSuccessful(progressDetail.ObjectKey)
   586  				continue
   587  			}
   588  			// handle the re-created pods if these pods are failed before doing horizontal scaling.
   589  			pgRes.opsMessageKey = "re-create"
   590  			if podIsAvailable(workloadType, &pod, minReadySeconds) {
   591  				completedCount += 1
   592  				handleSucceedProgressDetail(opsRes, pgRes, compStatus, progressDetail)
   593  				continue
   594  			}
   595  			if pod.DeletionTimestamp.IsZero() {
   596  				completedCount += handleFailedOrProcessingProgressDetail(opsRes, pgRes, compStatus, progressDetail, &pod)
   597  			}
   598  		}
   599  	}
   600  
   601  	handleDeletedPodNotInProgressDetails := func() {
   602  		// pod may not be recorded in the progressDetails if deleted quickly or due to unknown reasons, but it has actually been deleted.
   603  		// compare with the last pods and current pods to check if pod is deleted.
   604  		lastComponentPodNames := getTargetResourcesOfLastComponent(opsRes.OpsRequest.Status.LastConfiguration, componentName, appsv1alpha1.PodsCompResourceKey)
   605  		for _, v := range lastComponentPodNames {
   606  			objectKey := getProgressObjectKey(constant.PodKind, v)
   607  			progressDetail := findStatusProgressDetail(compStatus.ProgressDetails, objectKey)
   608  			// if recorded in progressDetails, continue
   609  			if progressDetail != nil {
   610  				continue
   611  			}
   612  			if _, ok := podMap[objectKey]; ok {
   613  				continue
   614  			}
   615  			handleDeletionSuccessful(objectKey)
   616  		}
   617  	}
   618  	handleProgressDetails()
   619  	handleDeletedPodNotInProgressDetails()
   620  	return completedCount, nil
   621  }
   622  
   623  // getFinalExpectCount gets the number of pods which has been processed by controller.
   624  func getFinalExpectCount(compStatus *appsv1alpha1.OpsRequestComponentStatus, expectProgressCount int32) int32 {
   625  	progressDetailsLen := int32(len(compStatus.ProgressDetails))
   626  	if progressDetailsLen > expectProgressCount {
   627  		expectProgressCount = progressDetailsLen
   628  	}
   629  	return expectProgressCount
   630  }