github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/transformer_cluster_status.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 apps
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  
    27  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    28  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    29  	"github.com/1aal/kubeblocks/pkg/controller/model"
    30  )
    31  
    32  type ClusterStatusTransformer struct {
    33  	// replicasNotReadyCompNames records the component names that are not ready.
    34  	notReadyCompNames map[string]struct{}
    35  	// replicasNotReadyCompNames records the component names which replicas are not ready.
    36  	replicasNotReadyCompNames map[string]struct{}
    37  }
    38  
    39  var _ graph.Transformer = &ClusterStatusTransformer{}
    40  
    41  func (t *ClusterStatusTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
    42  	transCtx, _ := ctx.(*clusterTransformContext)
    43  	origCluster := transCtx.OrigCluster
    44  	cluster := transCtx.Cluster
    45  	graphCli, _ := transCtx.Client.(model.GraphClient)
    46  
    47  	updateObservedGeneration := func() {
    48  		cluster.Status.ObservedGeneration = cluster.Generation
    49  		cluster.Status.ClusterDefGeneration = transCtx.ClusterDef.Generation
    50  	}
    51  
    52  	switch {
    53  	case origCluster.IsUpdating():
    54  		transCtx.Logger.Info(fmt.Sprintf("update cluster status after applying resources, generation: %d", cluster.Generation))
    55  		updateObservedGeneration()
    56  		graphCli.Status(dag, origCluster, cluster)
    57  	case origCluster.IsStatusUpdating():
    58  		defer func() { graphCli.Status(dag, origCluster, cluster) }()
    59  		// reconcile the phase and conditions of the Cluster.status
    60  		if err := t.reconcileClusterStatus(cluster); err != nil {
    61  			return err
    62  		}
    63  	case origCluster.IsDeleting():
    64  		return fmt.Errorf("unexpected cluster status: %+v", origCluster)
    65  	default:
    66  		panic(fmt.Sprintf("runtime error - unknown cluster status: %+v", origCluster))
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func (t *ClusterStatusTransformer) reconcileClusterPhase(cluster *appsv1alpha1.Cluster) {
    73  	var (
    74  		isAllComponentCreating = true
    75  		isAllComponentRunning  = true
    76  		isAllComponentWorking  = true
    77  		hasComponentStopping   = false
    78  		isAllComponentStopped  = true
    79  		isAllComponentFailed   = true
    80  	)
    81  	isPhaseIn := func(phase appsv1alpha1.ClusterComponentPhase, phases ...appsv1alpha1.ClusterComponentPhase) bool {
    82  		for _, p := range phases {
    83  			if p == phase {
    84  				return true
    85  			}
    86  		}
    87  		return false
    88  	}
    89  	for _, status := range cluster.Status.Components {
    90  		phase := status.Phase
    91  		if !isPhaseIn(phase, appsv1alpha1.CreatingClusterCompPhase) {
    92  			isAllComponentCreating = false
    93  		}
    94  		if !isPhaseIn(phase, appsv1alpha1.RunningClusterCompPhase) {
    95  			isAllComponentRunning = false
    96  		}
    97  		if !isPhaseIn(phase, appsv1alpha1.CreatingClusterCompPhase,
    98  			appsv1alpha1.RunningClusterCompPhase,
    99  			appsv1alpha1.UpdatingClusterCompPhase) {
   100  			isAllComponentWorking = false
   101  		}
   102  		if isPhaseIn(phase, appsv1alpha1.StoppingClusterCompPhase) {
   103  			hasComponentStopping = true
   104  		}
   105  		if !isPhaseIn(phase, appsv1alpha1.StoppedClusterCompPhase) {
   106  			isAllComponentStopped = false
   107  		}
   108  		if !isPhaseIn(phase, appsv1alpha1.FailedClusterCompPhase) {
   109  			isAllComponentFailed = false
   110  		}
   111  	}
   112  
   113  	switch {
   114  	case isAllComponentRunning:
   115  		if cluster.Status.Phase != appsv1alpha1.RunningClusterPhase {
   116  			t.syncClusterPhaseToRunning(cluster)
   117  		}
   118  	case isAllComponentCreating:
   119  		cluster.Status.Phase = appsv1alpha1.CreatingClusterPhase
   120  	case isAllComponentWorking:
   121  		cluster.Status.Phase = appsv1alpha1.UpdatingClusterPhase
   122  	case isAllComponentStopped:
   123  		if cluster.Status.Phase != appsv1alpha1.StoppedClusterPhase {
   124  			t.syncClusterPhaseToStopped(cluster)
   125  		}
   126  	case hasComponentStopping:
   127  		cluster.Status.Phase = appsv1alpha1.StoppingClusterPhase
   128  	case isAllComponentFailed:
   129  		cluster.Status.Phase = appsv1alpha1.FailedClusterPhase
   130  	default:
   131  		cluster.Status.Phase = appsv1alpha1.AbnormalClusterPhase
   132  	}
   133  }
   134  
   135  // reconcileClusterStatus reconciles phase and conditions of the Cluster.status.
   136  func (t *ClusterStatusTransformer) reconcileClusterStatus(cluster *appsv1alpha1.Cluster) error {
   137  	if len(cluster.Status.Components) == 0 {
   138  		return nil
   139  	}
   140  	initClusterStatusParams := func() {
   141  		t.notReadyCompNames = map[string]struct{}{}
   142  		t.replicasNotReadyCompNames = map[string]struct{}{}
   143  	}
   144  	initClusterStatusParams()
   145  
   146  	// removes the invalid component of status.components which is deleted from spec.components.
   147  	t.removeInvalidCompStatus(cluster)
   148  
   149  	// do analysis of Cluster.Status.component and update the results to status synchronizer.
   150  	t.doAnalysisAndUpdateSynchronizer(cluster)
   151  
   152  	// handle the ready condition.
   153  	t.syncReadyConditionForCluster(cluster)
   154  
   155  	// sync the cluster phase.
   156  	t.reconcileClusterPhase(cluster)
   157  	return nil
   158  }
   159  
   160  // removeInvalidCompStatus removes the invalid component of status.components which is deleted from spec.components.
   161  func (t *ClusterStatusTransformer) removeInvalidCompStatus(cluster *appsv1alpha1.Cluster) {
   162  	// remove the invalid component in status.components when the component is deleted from spec.components.
   163  	tmpCompsStatus := map[string]appsv1alpha1.ClusterComponentStatus{}
   164  	compsStatus := cluster.Status.Components
   165  	for _, v := range cluster.Spec.ComponentSpecs {
   166  		if compStatus, ok := compsStatus[v.Name]; ok {
   167  			tmpCompsStatus[v.Name] = compStatus
   168  		}
   169  	}
   170  	// keep valid components' status
   171  	cluster.Status.Components = tmpCompsStatus
   172  }
   173  
   174  // doAnalysisAndUpdateSynchronizer analyzes the Cluster.Status.Components and updates the results to the synchronizer.
   175  func (t *ClusterStatusTransformer) doAnalysisAndUpdateSynchronizer(cluster *appsv1alpha1.Cluster) {
   176  	// analysis the status of components and calculate the cluster phase.
   177  	for k, v := range cluster.Status.Components {
   178  		if v.PodsReady == nil || !*v.PodsReady {
   179  			t.replicasNotReadyCompNames[k] = struct{}{}
   180  			t.notReadyCompNames[k] = struct{}{}
   181  		}
   182  		switch v.Phase {
   183  		case appsv1alpha1.AbnormalClusterCompPhase, appsv1alpha1.FailedClusterCompPhase:
   184  			t.notReadyCompNames[k] = struct{}{}
   185  		}
   186  	}
   187  }
   188  
   189  // syncReadyConditionForCluster syncs the cluster conditions with ClusterReady and ReplicasReady type.
   190  func (t *ClusterStatusTransformer) syncReadyConditionForCluster(cluster *appsv1alpha1.Cluster) {
   191  	if len(t.replicasNotReadyCompNames) == 0 {
   192  		// if all replicas of cluster are ready, set ReasonAllReplicasReady to status.conditions
   193  		readyCondition := newAllReplicasPodsReadyConditions()
   194  		meta.SetStatusCondition(&cluster.Status.Conditions, readyCondition)
   195  	} else {
   196  		meta.SetStatusCondition(&cluster.Status.Conditions, newReplicasNotReadyCondition(t.replicasNotReadyCompNames))
   197  	}
   198  
   199  	if len(t.notReadyCompNames) > 0 {
   200  		meta.SetStatusCondition(&cluster.Status.Conditions, newComponentsNotReadyCondition(t.notReadyCompNames))
   201  	}
   202  }
   203  
   204  // syncClusterPhaseToRunning syncs the cluster phase to Running.
   205  func (t *ClusterStatusTransformer) syncClusterPhaseToRunning(cluster *appsv1alpha1.Cluster) {
   206  	cluster.Status.Phase = appsv1alpha1.RunningClusterPhase
   207  	meta.SetStatusCondition(&cluster.Status.Conditions, newClusterReadyCondition(cluster.Name))
   208  }
   209  
   210  // syncClusterPhaseToStopped syncs the cluster phase to Stopped.
   211  func (t *ClusterStatusTransformer) syncClusterPhaseToStopped(cluster *appsv1alpha1.Cluster) {
   212  	cluster.Status.Phase = appsv1alpha1.StoppedClusterPhase
   213  }