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 }