github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/restart.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  	"reflect"
    25  	"time"
    26  
    27  	appv1 "k8s.io/api/apps/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    33  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    34  	"github.com/1aal/kubeblocks/pkg/constant"
    35  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    36  )
    37  
    38  type restartOpsHandler struct{}
    39  
    40  var _ OpsHandler = restartOpsHandler{}
    41  
    42  func init() {
    43  	restartBehaviour := OpsBehaviour{
    44  		// if cluster is Abnormal or Failed, new opsRequest may repair it.
    45  		// TODO: we should add "force" flag for these opsRequest.
    46  		FromClusterPhases:                  appsv1alpha1.GetClusterUpRunningPhases(),
    47  		ToClusterPhase:                     appsv1alpha1.UpdatingClusterPhase,
    48  		OpsHandler:                         restartOpsHandler{},
    49  		ProcessingReasonInClusterCondition: ProcessingReasonRestarting,
    50  	}
    51  
    52  	opsMgr := GetOpsManager()
    53  	opsMgr.RegisterOps(appsv1alpha1.RestartType, restartBehaviour)
    54  }
    55  
    56  // ActionStartedCondition the started condition when handle the restart request.
    57  func (r restartOpsHandler) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*metav1.Condition, error) {
    58  	return appsv1alpha1.NewRestartingCondition(opsRes.OpsRequest), nil
    59  }
    60  
    61  // Action restarts components by updating StatefulSet.
    62  func (r restartOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
    63  	if opsRes.OpsRequest.Status.StartTimestamp.IsZero() {
    64  		return fmt.Errorf("status.startTimestamp can not be null")
    65  	}
    66  	componentNameMap := opsRes.OpsRequest.Spec.GetRestartComponentNameSet()
    67  	componentKindList := []client.ObjectList{
    68  		&appv1.DeploymentList{},
    69  		&appv1.StatefulSetList{},
    70  		&workloads.ReplicatedStateMachineList{},
    71  	}
    72  	for _, objectList := range componentKindList {
    73  		if err := restartComponent(reqCtx, cli, opsRes, componentNameMap, objectList); err != nil {
    74  			return err
    75  		}
    76  	}
    77  	return nil
    78  }
    79  
    80  // ReconcileAction will be performed when action is done and loops till OpsRequest.status.phase is Succeed/Failed.
    81  // the Reconcile function for restart opsRequest.
    82  func (r restartOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) {
    83  	return reconcileActionWithComponentOps(reqCtx, cli, opsRes, "restart", handleComponentStatusProgress)
    84  }
    85  
    86  // SaveLastConfiguration this operation only restart the pods of the component, no changes for Cluster.spec.
    87  // empty implementation here.
    88  func (r restartOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
    89  	return nil
    90  }
    91  
    92  // restartStatefulSet restarts statefulSet workload
    93  func restartComponent(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource, componentNameMap map[string]struct{}, objList client.ObjectList) error {
    94  	if err := cli.List(reqCtx.Ctx, objList,
    95  		client.InNamespace(opsRes.Cluster.Namespace),
    96  		client.MatchingLabels{constant.AppInstanceLabelKey: opsRes.Cluster.Name}); err != nil {
    97  		return err
    98  	}
    99  
   100  	items := reflect.ValueOf(objList).Elem().FieldByName("Items")
   101  	l := items.Len()
   102  	for i := 0; i < l; i++ {
   103  		// get the underlying object
   104  		object := items.Index(i).Addr().Interface().(client.Object)
   105  		template := items.Index(i).FieldByName("Spec").FieldByName("Template").Addr().Interface().(*corev1.PodTemplateSpec)
   106  		if isRestarted(opsRes, object, componentNameMap, template) {
   107  			continue
   108  		}
   109  		if err := cli.Update(reqCtx.Ctx, object); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // isRestarted checks whether the component has been restarted
   117  func isRestarted(opsRes *OpsResource, object client.Object, componentNameMap map[string]struct{}, podTemplate *corev1.PodTemplateSpec) bool {
   118  	cName := object.GetLabels()[constant.KBAppComponentLabelKey]
   119  	if _, ok := componentNameMap[cName]; !ok {
   120  		return true
   121  	}
   122  	if podTemplate.Annotations == nil {
   123  		podTemplate.Annotations = map[string]string{}
   124  	}
   125  	hasRestarted := true
   126  	startTimestamp := opsRes.OpsRequest.Status.StartTimestamp
   127  	stsRestartTimeStamp := podTemplate.Annotations[constant.RestartAnnotationKey]
   128  	if res, _ := time.Parse(time.RFC3339, stsRestartTimeStamp); startTimestamp.After(res) {
   129  		podTemplate.Annotations[constant.RestartAnnotationKey] = startTimestamp.Format(time.RFC3339)
   130  		hasRestarted = false
   131  	}
   132  	return hasRestarted
   133  }