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 }