github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/rsm/update_plan.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 rsm 21 22 import ( 23 "errors" 24 25 corev1 "k8s.io/api/core/v1" 26 27 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 28 "github.com/1aal/kubeblocks/pkg/controller/graph" 29 "github.com/1aal/kubeblocks/pkg/controller/model" 30 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 31 ) 32 33 type updatePlan interface { 34 // execute executes the plan 35 // return error when any error occurred 36 // return pods to be updated, 37 // nil slice means no pods need to be updated 38 execute() ([]*corev1.Pod, error) 39 } 40 41 type realUpdatePlan struct { 42 rsm workloads.ReplicatedStateMachine 43 pods []corev1.Pod 44 dag *graph.DAG 45 podsToBeUpdated []*corev1.Pod 46 } 47 48 var _ updatePlan = &realUpdatePlan{} 49 50 var ( 51 ErrContinue error 52 ErrWait = errors.New("wait") 53 ErrStop = errors.New("stop") 54 ) 55 56 // planWalkFunc decides whether vertex should be updated 57 // nil error means vertex should be updated 58 func (p *realUpdatePlan) planWalkFunc(vertex graph.Vertex) error { 59 v, _ := vertex.(*model.ObjectVertex) 60 if v.Obj == nil { 61 return ErrContinue 62 } 63 pod, ok := v.Obj.(*corev1.Pod) 64 if !ok { 65 return ErrContinue 66 } 67 68 // if DeletionTimestamp is not nil, it is terminating. 69 if !pod.DeletionTimestamp.IsZero() { 70 return ErrWait 71 } 72 73 // if pod is the latest version, we do nothing 74 if intctrlutil.GetPodRevision(pod) == p.rsm.Status.UpdateRevision { 75 if intctrlutil.PodIsReadyWithLabel(*pod) { 76 return ErrContinue 77 } else { 78 return ErrWait 79 } 80 } 81 82 // delete the pod to trigger associate StatefulSet to re-create it 83 p.podsToBeUpdated = append(p.podsToBeUpdated, pod) 84 return ErrStop 85 } 86 87 // build builds the update plan based on updateStrategy 88 func (p *realUpdatePlan) build() { 89 // make a root vertex with nil Obj 90 root := &model.ObjectVertex{} 91 p.dag.AddVertex(root) 92 93 if p.rsm.Spec.MemberUpdateStrategy == nil { 94 return 95 } 96 97 rolePriorityMap := ComposeRolePriorityMap(p.rsm.Spec.Roles) 98 SortPods(p.pods, rolePriorityMap, false) 99 100 // generate plan by MemberUpdateStrategy 101 switch *p.rsm.Spec.MemberUpdateStrategy { 102 case workloads.SerialUpdateStrategy: 103 p.buildSerialUpdatePlan() 104 case workloads.ParallelUpdateStrategy: 105 p.buildParallelUpdatePlan() 106 case workloads.BestEffortParallelUpdateStrategy: 107 p.buildBestEffortParallelUpdatePlan(rolePriorityMap) 108 } 109 } 110 111 // unknown & empty & learner & 1/2 followers -> 1/2 followers -> leader 112 func (p *realUpdatePlan) buildBestEffortParallelUpdatePlan(rolePriorityMap map[string]int) { 113 currentVertex, _ := model.FindRootVertex(p.dag) 114 preVertex := currentVertex 115 116 // append unknown, empty and learner 117 index := 0 118 podList := p.pods 119 for i, pod := range podList { 120 roleName := getRoleName(pod) 121 if rolePriorityMap[roleName] <= learnerPriority { 122 vertex := &model.ObjectVertex{Obj: &podList[i]} 123 p.dag.AddConnect(preVertex, vertex) 124 currentVertex = vertex 125 index++ 126 } 127 } 128 preVertex = currentVertex 129 130 // append 1/2 followers 131 podList = podList[index:] 132 followerCount := 0 133 for _, pod := range podList { 134 roleName := getRoleName(pod) 135 if rolePriorityMap[roleName] < leaderPriority { 136 followerCount++ 137 } 138 } 139 end := followerCount / 2 140 for i := 0; i < end; i++ { 141 vertex := &model.ObjectVertex{Obj: &podList[i]} 142 p.dag.AddConnect(preVertex, vertex) 143 currentVertex = vertex 144 } 145 preVertex = currentVertex 146 147 // append the other 1/2 followers 148 podList = podList[end:] 149 end = followerCount - end 150 for i := 0; i < end; i++ { 151 vertex := &model.ObjectVertex{Obj: &podList[i]} 152 p.dag.AddConnect(preVertex, vertex) 153 currentVertex = vertex 154 } 155 preVertex = currentVertex 156 157 // append leader 158 podList = podList[end:] 159 end = len(podList) 160 for i := 0; i < end; i++ { 161 vertex := &model.ObjectVertex{Obj: &podList[i]} 162 p.dag.AddConnect(preVertex, vertex) 163 } 164 } 165 166 // unknown & empty & leader & followers & learner 167 func (p *realUpdatePlan) buildParallelUpdatePlan() { 168 root, _ := model.FindRootVertex(p.dag) 169 for i := range p.pods { 170 vertex := &model.ObjectVertex{Obj: &p.pods[i]} 171 p.dag.AddConnect(root, vertex) 172 } 173 } 174 175 // unknown -> empty -> learner -> followers(none->readonly->readwrite) -> leader 176 func (p *realUpdatePlan) buildSerialUpdatePlan() { 177 preVertex, _ := model.FindRootVertex(p.dag) 178 for i := range p.pods { 179 vertex := &model.ObjectVertex{Obj: &p.pods[i]} 180 p.dag.AddConnect(preVertex, vertex) 181 preVertex = vertex 182 } 183 } 184 185 func (p *realUpdatePlan) execute() ([]*corev1.Pod, error) { 186 p.build() 187 if err := p.dag.WalkBFS(p.planWalkFunc); err != ErrContinue && err != ErrWait && err != ErrStop { 188 return nil, err 189 } 190 191 return p.podsToBeUpdated, nil 192 } 193 194 func newUpdatePlan(rsm workloads.ReplicatedStateMachine, pods []corev1.Pod) updatePlan { 195 return &realUpdatePlan{ 196 rsm: rsm, 197 pods: pods, 198 dag: graph.NewDAG(), 199 } 200 }