github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/policy_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 configuration 21 22 import ( 23 "context" 24 "fmt" 25 "net" 26 "sort" 27 "strconv" 28 29 appv1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 34 "github.com/1aal/kubeblocks/controllers/apps/components" 35 "github.com/1aal/kubeblocks/pkg/common" 36 "github.com/1aal/kubeblocks/pkg/configuration/core" 37 cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto" 38 "github.com/1aal/kubeblocks/pkg/constant" 39 "github.com/1aal/kubeblocks/pkg/controller/rsm" 40 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 41 "github.com/1aal/kubeblocks/pkg/generics" 42 viper "github.com/1aal/kubeblocks/pkg/viperx" 43 ) 44 45 func getDeploymentRollingPods(params reconfigureParams) ([]corev1.Pod, error) { 46 // util.GetComponentPodList supports deployment 47 return getReplicationSetPods(params) 48 } 49 50 func getReplicationSetPods(params reconfigureParams) ([]corev1.Pod, error) { 51 var ctx = params.Ctx 52 var cluster = params.Cluster 53 podList, err := components.GetComponentPodList(ctx.Ctx, params.Client, *cluster, params.ClusterComponent.Name) 54 if err != nil { 55 return nil, err 56 } 57 return podList.Items, nil 58 } 59 60 // GetComponentPods gets all pods of the component. 61 func GetComponentPods(params reconfigureParams) ([]corev1.Pod, error) { 62 componentPods := make([]corev1.Pod, 0) 63 for i := range params.ComponentUnits { 64 pods, err := common.GetPodListByStatefulSet(params.Ctx.Ctx, params.Client, ¶ms.ComponentUnits[i]) 65 if err != nil { 66 return nil, err 67 } 68 componentPods = append(componentPods, pods...) 69 } 70 return componentPods, nil 71 } 72 73 // CheckReconfigureUpdateProgress checks pods of the component is ready. 74 func CheckReconfigureUpdateProgress(pods []corev1.Pod, configKey, version string) int32 { 75 var ( 76 readyPods int32 = 0 77 cfgAnnotationKey = core.GenerateUniqKeyWithConfig(constant.UpgradeRestartAnnotationKey, configKey) 78 ) 79 80 for _, pod := range pods { 81 annotations := pod.Annotations 82 if len(annotations) != 0 && annotations[cfgAnnotationKey] == version && intctrlutil.PodIsReady(&pod) { 83 readyPods++ 84 } 85 } 86 return readyPods 87 } 88 89 func getStatefulSetPods(params reconfigureParams) ([]corev1.Pod, error) { 90 if len(params.ComponentUnits) != 1 { 91 return nil, core.MakeError("statefulSet component require only one statefulset, actual %d components", len(params.ComponentUnits)) 92 } 93 94 pods, err := GetComponentPods(params) 95 if err != nil { 96 return nil, err 97 } 98 99 sort.SliceStable(pods, func(i, j int) bool { 100 _, ordinal1 := intctrlutil.GetParentNameAndOrdinal(&pods[i]) 101 _, ordinal2 := intctrlutil.GetParentNameAndOrdinal(&pods[j]) 102 return ordinal1 < ordinal2 103 }) 104 return pods, nil 105 } 106 107 func getConsensusPods(params reconfigureParams) ([]corev1.Pod, error) { 108 if len(params.ComponentUnits) > 1 { 109 return nil, core.MakeError("consensus component require only one statefulset, actual %d components", len(params.ComponentUnits)) 110 } 111 112 if len(params.ComponentUnits) == 0 { 113 return nil, nil 114 } 115 116 pods, err := GetComponentPods(params) 117 // stsObj := ¶ms.ComponentUnits[0] 118 // pods, err := components.GetPodListByStatefulSetWithSelector(params.Ctx.Ctx, params.Client, stsObj, client.MatchingLabels{ 119 // constant.KBAppComponentLabelKey: params.ClusterComponent.Name, 120 // constant.AppInstanceLabelKey: params.Cluster.Name, 121 // }) 122 if err != nil { 123 return nil, err 124 } 125 126 // TODO: should resolve the dependency on consensus module 127 if params.Component.RSMSpec != nil { 128 rsm.SortPods(pods, rsm.ComposeRolePriorityMap(params.Component.RSMSpec.Roles), true) 129 } 130 return pods, nil 131 } 132 133 // TODO commonOnlineUpdateWithPod migrate to sql command pipeline 134 func commonOnlineUpdateWithPod(pod *corev1.Pod, ctx context.Context, createClient createReconfigureClient, configSpec string, updatedParams map[string]string) error { 135 address, err := cfgManagerGrpcURL(pod) 136 if err != nil { 137 return err 138 } 139 client, err := createClient(address) 140 if err != nil { 141 return err 142 } 143 144 response, err := client.OnlineUpgradeParams(ctx, &cfgproto.OnlineUpgradeParamsRequest{ 145 ConfigSpec: configSpec, 146 Params: updatedParams, 147 }) 148 if err != nil { 149 return err 150 } 151 152 errMessage := response.GetErrMessage() 153 if errMessage != "" { 154 return core.MakeError(errMessage) 155 } 156 return nil 157 } 158 159 func commonStopContainerWithPod(pod *corev1.Pod, ctx context.Context, containerNames []string, createClient createReconfigureClient) error { 160 containerIDs := make([]string, 0, len(containerNames)) 161 for _, name := range containerNames { 162 containerID := intctrlutil.GetContainerID(pod, name) 163 if containerID == "" { 164 return core.MakeError("failed to find container in pod[%s], name=%s", name, pod.Name) 165 } 166 containerIDs = append(containerIDs, containerID) 167 } 168 169 address, err := cfgManagerGrpcURL(pod) 170 if err != nil { 171 return err 172 } 173 // stop container 174 client, err := createClient(address) 175 if err != nil { 176 return err 177 } 178 179 response, err := client.StopContainer(ctx, &cfgproto.StopContainerRequest{ 180 ContainerIDs: containerIDs, 181 }) 182 if err != nil { 183 return err 184 } 185 186 errMessage := response.GetErrMessage() 187 if errMessage != "" { 188 return core.MakeError(errMessage) 189 } 190 return nil 191 } 192 193 func cfgManagerGrpcURL(pod *corev1.Pod) (string, error) { 194 podPort := viper.GetInt(constant.ConfigManagerGPRCPortEnv) 195 return getURLFromPod(pod, podPort) 196 } 197 198 func getURLFromPod(pod *corev1.Pod, portPort int) (string, error) { 199 ip := net.ParseIP(pod.Status.PodIP) 200 if ip == nil { 201 return "", core.MakeError("%s is not a valid IP", pod.Status.PodIP) 202 } 203 204 // Sanity check PodIP 205 if ip.To4() == nil && ip.To16() == nil { 206 return "", fmt.Errorf("%s is not a valid IPv4/IPv6 address", pod.Status.PodIP) 207 } 208 return net.JoinHostPort(ip.String(), strconv.Itoa(portPort)), nil 209 } 210 211 func restartWorkloadComponent[T generics.Object, PT generics.PObject[T], L generics.ObjList[T], PL generics.PObjList[T, L]](cli client.Client, ctx context.Context, annotationKey, annotationValue string, obj PT, _ func(T, PT, L, PL)) error { 212 template := transformPodTemplate(obj) 213 if updatedVersion(template, annotationKey, annotationValue) { 214 return nil 215 } 216 217 patch := client.MergeFrom(PT(obj.DeepCopy())) 218 if template.Annotations == nil { 219 template.Annotations = map[string]string{} 220 } 221 template.Annotations[annotationKey] = annotationValue 222 if err := cli.Patch(ctx, obj, patch); err != nil { 223 return err 224 } 225 return nil 226 } 227 228 func restartComponent(cli client.Client, ctx intctrlutil.RequestCtx, configKey string, newVersion string, objs []client.Object, recordEvent func(obj client.Object)) (client.Object, error) { 229 var err error 230 cfgAnnotationKey := core.GenerateUniqKeyWithConfig(constant.UpgradeRestartAnnotationKey, configKey) 231 for _, obj := range objs { 232 switch w := obj.(type) { 233 case *appv1.StatefulSet: 234 err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.StatefulSetSignature) 235 case *appv1.Deployment: 236 err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.DeploymentSignature) 237 case *workloads.ReplicatedStateMachine: 238 err = restartWorkloadComponent(cli, ctx.Ctx, cfgAnnotationKey, newVersion, w, generics.RSMSignature) 239 default: 240 // ignore other types workload 241 } 242 if err != nil { 243 return obj, err 244 } 245 if recordEvent != nil { 246 recordEvent(obj) 247 } 248 } 249 return nil, nil 250 } 251 252 func updatedVersion(podTemplate *corev1.PodTemplateSpec, keyPath, expectedVersion string) bool { 253 return podTemplate.Annotations != nil && podTemplate.Annotations[keyPath] == expectedVersion 254 }