github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/pod_utils.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 controllerutil 21 22 import ( 23 "fmt" 24 "regexp" 25 "strconv" 26 "strings" 27 "time" 28 29 appsv1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 metautil "k8s.io/apimachinery/pkg/util/intstr" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 36 "github.com/1aal/kubeblocks/pkg/constant" 37 viper "github.com/1aal/kubeblocks/pkg/viperx" 38 ) 39 40 // statefulPodRegex is a regular expression that extracts the parent StatefulSet and ordinal from the Name of a Pod 41 var statefulPodRegex = regexp.MustCompile("(.*)-([0-9]+)$") 42 43 // GetParentNameAndOrdinal gets the name of pod's parent StatefulSet and pod's ordinal as extracted from its Name. If 44 // the Pod was not created by a StatefulSet, its parent is considered to be empty string, and its ordinal is considered 45 // to be -1. 46 func GetParentNameAndOrdinal(pod *corev1.Pod) (string, int) { 47 parent := "" 48 ordinal := -1 49 subMatches := statefulPodRegex.FindStringSubmatch(pod.Name) 50 if len(subMatches) < 3 { 51 return parent, ordinal 52 } 53 parent = subMatches[1] 54 if i, err := strconv.ParseInt(subMatches[2], 10, 32); err == nil { 55 ordinal = int(i) 56 } 57 return parent, ordinal 58 } 59 60 // GetContainerByConfigSpec searches for container using the configmap of config from the pod 61 // 62 // e.g.: 63 // ClusterDefinition.configTemplateRef: 64 // - Name: "mysql-8.0" 65 // VolumeName: "mysql_config" 66 // 67 // 68 // PodTemplate.containers[*].volumeMounts: 69 // - mountPath: /data/config 70 // name: mysql_config 71 // - mountPath: /data 72 // name: data 73 // - mountPath: /log 74 // name: log 75 func GetContainerByConfigSpec(podSpec *corev1.PodSpec, configs []appsv1alpha1.ComponentConfigSpec) *corev1.Container { 76 containers := podSpec.Containers 77 initContainers := podSpec.InitContainers 78 if container := getContainerWithTplList(containers, configs); container != nil { 79 return container 80 } 81 if container := getContainerWithTplList(initContainers, configs); container != nil { 82 return container 83 } 84 return nil 85 } 86 87 // GetPodContainerWithVolumeMount searches for containers mounting the volume 88 func GetPodContainerWithVolumeMount(podSpec *corev1.PodSpec, volumeName string) []*corev1.Container { 89 containers := podSpec.Containers 90 if len(containers) == 0 || volumeName == "" { 91 return nil 92 } 93 return getContainerWithVolumeMount(containers, volumeName) 94 } 95 96 // GetVolumeMountName finds the volume with mount name 97 func GetVolumeMountName(volumes []corev1.Volume, resourceName string) *corev1.Volume { 98 for i := range volumes { 99 if volumes[i].ConfigMap != nil && volumes[i].ConfigMap.Name == resourceName { 100 return &volumes[i] 101 } 102 if volumes[i].Projected == nil { 103 continue 104 } 105 for j := range volumes[i].Projected.Sources { 106 if volumes[i].Projected.Sources[j].ConfigMap != nil && volumes[i].Projected.Sources[j].ConfigMap.Name == resourceName { 107 return &volumes[i] 108 } 109 } 110 } 111 return nil 112 } 113 114 type containerNameFilter func(containerName string) bool 115 116 func GetContainersByConfigmap(containers []corev1.Container, volumeName string, cmName string, filters ...containerNameFilter) []string { 117 containerFilter := func(c corev1.Container) bool { 118 for _, f := range filters { 119 if (len(c.VolumeMounts) == 0 && len(c.EnvFrom) == 0) || 120 f(c.Name) { 121 return true 122 } 123 } 124 return false 125 } 126 127 tmpList := make([]string, 0, len(containers)) 128 for _, c := range containers { 129 if containerFilter(c) { 130 continue 131 } 132 for _, vm := range c.VolumeMounts { 133 if vm.Name == volumeName { 134 tmpList = append(tmpList, c.Name) 135 goto breakHere 136 } 137 } 138 if cmName == "" { 139 continue 140 } 141 for _, source := range c.EnvFrom { 142 if source.ConfigMapRef != nil && source.ConfigMapRef.Name == cmName { 143 tmpList = append(tmpList, c.Name) 144 break 145 } 146 } 147 breakHere: 148 } 149 return tmpList 150 } 151 152 func getContainerWithTplList(containers []corev1.Container, configs []appsv1alpha1.ComponentConfigSpec) *corev1.Container { 153 if len(containers) == 0 { 154 return nil 155 } 156 for i, c := range containers { 157 volumeMounts := c.VolumeMounts 158 if len(volumeMounts) > 0 && checkContainerWithVolumeMount(volumeMounts, configs) { 159 return &containers[i] 160 } 161 } 162 return nil 163 } 164 165 func checkContainerWithVolumeMount(volumeMounts []corev1.VolumeMount, configs []appsv1alpha1.ComponentConfigSpec) bool { 166 volumes := make(map[string]int) 167 for _, c := range configs { 168 for j, vm := range volumeMounts { 169 if vm.Name == c.VolumeName { 170 volumes[vm.Name] = j 171 break 172 } 173 } 174 } 175 return len(configs) == len(volumes) 176 } 177 178 func getContainerWithVolumeMount(containers []corev1.Container, volumeName string) []*corev1.Container { 179 mountContainers := make([]*corev1.Container, 0, len(containers)) 180 for i, c := range containers { 181 volumeMounts := c.VolumeMounts 182 for _, vm := range volumeMounts { 183 if vm.Name == volumeName { 184 mountContainers = append(mountContainers, &containers[i]) 185 break 186 } 187 } 188 } 189 return mountContainers 190 } 191 192 func GetVolumeMountByVolume(container *corev1.Container, volumeName string) *corev1.VolumeMount { 193 for _, volume := range container.VolumeMounts { 194 if volume.Name == volumeName { 195 return &volume 196 } 197 } 198 199 return nil 200 } 201 202 // GetCoreNum gets content of Resources.Limits.cpu 203 func GetCoreNum(container corev1.Container) int64 { 204 limits := container.Resources.Limits 205 if val, ok := (limits)[corev1.ResourceCPU]; ok { 206 return val.Value() 207 } 208 return 0 209 } 210 211 // GetMemorySize gets content of Resources.Limits.memory 212 func GetMemorySize(container corev1.Container) int64 { 213 limits := container.Resources.Limits 214 if val, ok := (limits)[corev1.ResourceMemory]; ok { 215 return val.Value() 216 } 217 return 0 218 } 219 220 // GetRequestMemorySize gets content of Resources.Limits.memory 221 func GetRequestMemorySize(container corev1.Container) int64 { 222 requests := container.Resources.Requests 223 if val, ok := (requests)[corev1.ResourceMemory]; ok { 224 return val.Value() 225 } 226 return 0 227 } 228 229 // GetStorageSizeFromPersistentVolume gets content of Resources.Requests.storage 230 func GetStorageSizeFromPersistentVolume(pvc corev1.PersistentVolumeClaimTemplate) int64 { 231 requests := pvc.Spec.Resources.Requests 232 if val, ok := (requests)[corev1.ResourceStorage]; ok { 233 return val.Value() 234 } 235 return -1 236 } 237 238 // PodIsReady checks if pod is ready 239 func PodIsReady(pod *corev1.Pod) bool { 240 if pod.Status.Conditions == nil { 241 return false 242 } 243 244 if pod.DeletionTimestamp != nil { 245 return false 246 } 247 248 for _, condition := range pod.Status.Conditions { 249 if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { 250 return true 251 } 252 } 253 return false 254 } 255 256 // GetContainerID gets the containerID from pod by name 257 func GetContainerID(pod *corev1.Pod, containerName string) string { 258 const containerSep = "//" 259 260 // container id is present in the form of <runtime>://<container-id> 261 // e.g: containerID: docker://27d1586d53ef9a6af5bd983831d13b6a38128119fadcdc22894d7b2397758eb5 262 for _, container := range pod.Status.ContainerStatuses { 263 if container.Name == containerName { 264 return strings.Split(container.ContainerID, containerSep)[1] 265 } 266 } 267 return "" 268 } 269 270 func isRunning(pod *corev1.Pod) bool { 271 return pod.Status.Phase == corev1.PodRunning && pod.DeletionTimestamp == nil 272 } 273 274 func IsAvailable(pod *corev1.Pod, minReadySeconds int32) bool { 275 if !isRunning(pod) { 276 return false 277 } 278 279 condition := GetPodCondition(&pod.Status, corev1.PodReady) 280 if condition == nil || condition.Status != corev1.ConditionTrue { 281 return false 282 } 283 if minReadySeconds == 0 { 284 return true 285 } 286 287 var ( 288 now = metav1.Now() 289 minDuration = time.Duration(minReadySeconds) * time.Second 290 lastTransitionTime = condition.LastTransitionTime 291 ) 292 293 return !lastTransitionTime.IsZero() && lastTransitionTime.Add(minDuration).Before(now.Time) 294 } 295 296 func GetPodCondition(status *corev1.PodStatus, conditionType corev1.PodConditionType) *corev1.PodCondition { 297 if len(status.Conditions) == 0 { 298 return nil 299 } 300 301 for i, condition := range status.Conditions { 302 if condition.Type == conditionType { 303 return &status.Conditions[i] 304 } 305 } 306 return nil 307 } 308 309 func IsMatchConfigVersion(obj client.Object, labelKey string, version string) bool { 310 labels := obj.GetLabels() 311 if len(labels) == 0 { 312 return false 313 } 314 if lastVersion, ok := labels[labelKey]; ok && lastVersion == version { 315 return true 316 } 317 return false 318 } 319 320 func GetIntOrPercentValue(intOrStr *metautil.IntOrString) (int, bool, error) { 321 if intOrStr.Type == metautil.Int { 322 return intOrStr.IntValue(), false, nil 323 } 324 325 // parse string 326 s := intOrStr.StrVal 327 if strings.HasSuffix(s, "%") { 328 s = strings.TrimSuffix(intOrStr.StrVal, "%") 329 } else { 330 return 0, false, fmt.Errorf("failed to parse percentage. [%s]", intOrStr.StrVal) 331 } 332 v, err := strconv.Atoi(s) 333 if err != nil { 334 return 0, false, fmt.Errorf("failed to atoi [%s], error: %v", intOrStr.StrVal, err) 335 } 336 return v, true, nil 337 } 338 339 // GetPortByPortName gets the Port from pod by name 340 func GetPortByPortName(pod *corev1.Pod, portName string) (int32, error) { 341 for _, container := range pod.Spec.Containers { 342 for _, port := range container.Ports { 343 if port.Name == portName { 344 return port.ContainerPort, nil 345 } 346 } 347 } 348 return 0, fmt.Errorf("port %s not found", portName) 349 } 350 351 func GetLorryGRPCPort(pod *corev1.Pod) (int32, error) { 352 return GetPortByPortName(pod, constant.LorryGRPCPortName) 353 } 354 355 func GetLorryHTTPPort(pod *corev1.Pod) (int32, error) { 356 return GetPortByPortName(pod, constant.LorryHTTPPortName) 357 } 358 359 // GuessLorryHTTPPort guesses lorry container and serving port. 360 // TODO(xuriwuyun): should provide a deterministic way to find the lorry serving port. 361 func GuessLorryHTTPPort(pod *corev1.Pod) (int32, error) { 362 lorryImage := viper.GetString(constant.KBToolsImage) 363 for _, container := range pod.Spec.Containers { 364 if container.Image != lorryImage { 365 continue 366 } 367 if len(container.Ports) > 0 { 368 return container.Ports[0].ContainerPort, nil 369 } 370 } 371 return 0, fmt.Errorf("lorry port not found") 372 } 373 374 // GetLorryContainerName gets the probe container from pod 375 func GetLorryContainerName(pod *corev1.Pod) (string, error) { 376 for _, container := range pod.Spec.Containers { 377 if len(container.Command) > 0 && strings.Contains(container.Command[0], "lorry") { 378 return container.Name, nil 379 } 380 } 381 return "", fmt.Errorf("lorry container not found") 382 } 383 384 // PodIsReadyWithLabel checks if pod is ready for ConsensusSet/ReplicationSet component, 385 // it will be available when the pod is ready and labeled with role. 386 func PodIsReadyWithLabel(pod corev1.Pod) bool { 387 if _, ok := pod.Labels[constant.RoleLabelKey]; !ok { 388 return false 389 } 390 391 return PodIsReady(&pod) 392 } 393 394 // PodIsControlledByLatestRevision checks if the pod is controlled by latest controller revision. 395 func PodIsControlledByLatestRevision(pod *corev1.Pod, sts *appsv1.StatefulSet) bool { 396 return GetPodRevision(pod) == sts.Status.UpdateRevision && sts.Status.ObservedGeneration == sts.Generation 397 } 398 399 // GetPodRevision gets the revision of Pod by inspecting the StatefulSetRevisionLabel. If pod has no revision empty 400 // string is returned. 401 func GetPodRevision(pod *corev1.Pod) string { 402 if pod.Labels == nil { 403 return "" 404 } 405 return pod.Labels[appsv1.StatefulSetRevisionLabel] 406 } 407 408 // ByPodName sorts a list of jobs by pod name 409 type ByPodName []corev1.Pod 410 411 // Len returns the length of byPodName for sort.Sort 412 func (c ByPodName) Len() int { 413 return len(c) 414 } 415 416 // Swap swaps the items for sort.Sort 417 func (c ByPodName) Swap(i, j int) { 418 c[i], c[j] = c[j], c[i] 419 } 420 421 // Less defines compare method for sort.Sort 422 func (c ByPodName) Less(i, j int) bool { 423 return c[i].Name < c[j].Name 424 } 425 426 // BuildPodHostDNS builds the host dns of pod. 427 // ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ 428 func BuildPodHostDNS(pod *corev1.Pod) string { 429 if pod == nil { 430 return "" 431 } 432 // build pod dns string 433 // ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ 434 if pod.Spec.Subdomain != "" { 435 hostDNS := []string{pod.Name} 436 if pod.Spec.Hostname != "" { 437 hostDNS[0] = pod.Spec.Hostname 438 } 439 hostDNS = append(hostDNS, pod.Spec.Subdomain) 440 return strings.Join(hostDNS, ".") 441 } 442 return pod.Status.PodIP 443 }