github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/native/pods.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package native 18 19 import ( 20 "fmt" 21 "strings" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/util/sets" 25 "k8s.io/klog/v2" 26 "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos" 27 kubelettypes "k8s.io/kubernetes/pkg/kubelet/types" 28 29 "github.com/kubewharf/katalyst-core/pkg/consts" 30 ) 31 32 var GetPodHostIPs = func(pod *v1.Pod) ([]string, bool) { 33 ip, ok := GetPodHostIP(pod) 34 if !ok { 35 return []string{}, false 36 } 37 return []string{ip}, true 38 } 39 40 func GetPodHostIP(pod *v1.Pod) (string, bool) { 41 if pod == nil { 42 return "", false 43 } 44 45 hostIP := pod.Status.HostIP 46 if len(hostIP) == 0 { 47 return "", false 48 } 49 return hostIP, true 50 } 51 52 // PodAnnotationFilter is used to filter pods annotated with a pair of specific key and value 53 func PodAnnotationFilter(pod *v1.Pod, key, value string) bool { 54 if pod == nil || pod.Annotations == nil { 55 return false 56 } 57 58 return pod.Annotations[key] == value 59 } 60 61 // FilterPods filter pods that filter func return true. 62 func FilterPods(pods []*v1.Pod, filterFunc func(*v1.Pod) (bool, error)) []*v1.Pod { 63 var filtered []*v1.Pod 64 for _, pod := range pods { 65 if pod == nil { 66 continue 67 } 68 69 if ok, err := filterFunc(pod); err != nil { 70 klog.Errorf("filter pod %v err: %v", pod.Name, err) 71 } else if ok { 72 filtered = append(filtered, pod) 73 } 74 } 75 76 return filtered 77 } 78 79 // SumUpPodRequestResources sum up resources in all containers request 80 // init container is included (count on the max request of all init containers) 81 func SumUpPodRequestResources(pod *v1.Pod) v1.ResourceList { 82 res := make(v1.ResourceList) 83 84 sumRequests := func(containers []v1.Container) { 85 for _, container := range containers { 86 res = AddResources(res, container.Resources.Requests) 87 } 88 89 if pod.Spec.Overhead != nil { 90 res = AddResources(res, pod.Spec.Overhead) 91 } 92 } 93 94 sumRequests(pod.Spec.Containers) 95 for _, container := range pod.Spec.InitContainers { 96 for resourceName := range container.Resources.Requests { 97 quantity := container.Resources.Requests[resourceName].DeepCopy() 98 if origin, ok := res[resourceName]; !ok || (&origin).Value() < quantity.Value() { 99 res[resourceName] = quantity 100 } 101 } 102 } 103 104 return res 105 } 106 107 // SumUpPodLimitResources sum up resources in all containers request 108 // init container is included (count on the max limit of all init containers) 109 func SumUpPodLimitResources(pod *v1.Pod) v1.ResourceList { 110 res := make(v1.ResourceList) 111 112 sumLimits := func(containers []v1.Container) { 113 for _, container := range containers { 114 res = AddResources(res, container.Resources.Limits) 115 } 116 117 if pod.Spec.Overhead != nil { 118 res = AddResources(res, pod.Spec.Overhead) 119 } 120 } 121 122 sumLimits(pod.Spec.Containers) 123 for _, container := range pod.Spec.InitContainers { 124 for resourceName := range container.Resources.Limits { 125 quantity := container.Resources.Limits[resourceName].DeepCopy() 126 if origin, ok := res[resourceName]; !ok || (&origin).Value() < quantity.Value() { 127 res[resourceName] = quantity 128 } 129 } 130 } 131 132 return res 133 } 134 135 func PodAndContainersAreTerminal(pod *v1.Pod) (containersTerminal, podWorkerTerminal bool) { 136 status := pod.Status 137 138 // A pod transitions into failed or succeeded from either container lifecycle (RestartNever container 139 // fails) or due to external events like deletion or eviction. A terminal pod *should* have no running 140 // containers, but to know that the pod has completed its lifecycle you must wait for containers to also 141 // be terminal. 142 containersTerminal = containerNotRunning(status.ContainerStatuses) 143 // The kubelet must accept config changes from the pod spec until it has reached a point where changes would 144 // have no effect on any running container. 145 podWorkerTerminal = status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && containersTerminal) 146 return 147 } 148 149 // PodIsTerminated returns whether the pod is at terminal state. 150 func PodIsTerminated(pod *v1.Pod) bool { 151 if pod == nil { 152 return true 153 } 154 _, podWorkerTerminal := PodAndContainersAreTerminal(pod) 155 return podWorkerTerminal 156 } 157 158 // PodIsReady returns whether the pod is at ready state. 159 func PodIsReady(pod *v1.Pod) bool { 160 if len(pod.Spec.Containers) != len(pod.Status.ContainerStatuses) { 161 return false 162 } 163 for _, containerStatus := range pod.Status.ContainerStatuses { 164 if !containerStatus.Ready { 165 return false 166 } 167 } 168 return true 169 } 170 171 // PodIsActive returns whether the pod is not terminated. 172 func PodIsActive(pod *v1.Pod) bool { 173 return !PodIsTerminated(pod) 174 } 175 176 // PodIsPending returns whether the pod is pending. 177 func PodIsPending(pod *v1.Pod) bool { 178 if pod == nil { 179 return false 180 } 181 return pod.Status.Phase == v1.PodPending 182 } 183 184 // FilterOutSkipEvictionPods return pods should be candidates to evict 185 // including native critical pods and user-defined filtered pods 186 func FilterOutSkipEvictionPods(pods []*v1.Pod, filterOutAnnotations, filterOutLabels sets.String) []*v1.Pod { 187 var filteredPods []*v1.Pod 188 filter: 189 for _, p := range pods { 190 if p == nil || kubelettypes.IsCriticalPod(p) { 191 continue 192 } 193 194 for key := range p.Annotations { 195 if filterOutAnnotations.Has(key) { 196 continue filter 197 } 198 } 199 200 for key := range p.Labels { 201 if filterOutLabels.Has(key) { 202 continue filter 203 } 204 } 205 206 filteredPods = append(filteredPods, p) 207 } 208 return filteredPods 209 } 210 211 // GeneratePodContainerName return a unique key for a container in a pod 212 func GeneratePodContainerName(podName, containerName string) consts.PodContainerName { 213 return consts.PodContainerName(podName + "," + containerName) 214 } 215 216 // ParsePodContainerName parse key and return pod name and container name 217 func ParsePodContainerName(key consts.PodContainerName) (string, string, error) { 218 containerKeys := strings.Split(string(key), ",") 219 if len(containerKeys) != 2 { 220 err := fmt.Errorf("split result's length mismatch") 221 return "", "", err 222 } 223 return containerKeys[0], containerKeys[1], nil 224 } 225 226 // GenerateContainerName return a unique key for a container 227 func GenerateContainerName(containerName string) consts.ContainerName { 228 return consts.ContainerName(containerName) 229 } 230 231 // ParseContainerName parse key and return container name 232 func ParseContainerName(key consts.ContainerName) string { 233 return string(key) 234 } 235 236 // CheckQosClassChanged checks whether the pod's QosClass will change if annotationResources are applied to this pod 237 func CheckQosClassChanged(resources map[string]v1.ResourceRequirements, pod *v1.Pod) (bool, error) { 238 if pod == nil { 239 return false, fmt.Errorf("pod is nil") 240 } 241 242 podCopy := &v1.Pod{} 243 podCopy.Spec.Containers = DeepCopyPodContainers(pod) 244 ApplyPodResources(resources, podCopy) 245 246 return qos.GetPodQOS(podCopy) != qos.GetPodQOS(pod), nil 247 } 248 249 // ApplyPodResources is used to apply map[string]v1.ResourceRequirements to the given pod, 250 // and ignore the container-names / resource-names that not appear in the given map param 251 func ApplyPodResources(resources map[string]v1.ResourceRequirements, pod *v1.Pod) { 252 for i := 0; i < len(pod.Spec.Containers); i++ { 253 if containerResource, ok := resources[pod.Spec.Containers[i].Name]; ok { 254 if pod.Spec.Containers[i].Resources.Requests == nil { 255 pod.Spec.Containers[i].Resources.Requests = v1.ResourceList{} 256 } 257 if containerResource.Requests != nil { 258 for resourceName, quantity := range containerResource.Requests { 259 pod.Spec.Containers[i].Resources.Requests[resourceName] = quantity 260 } 261 } 262 263 if pod.Spec.Containers[i].Resources.Limits == nil { 264 pod.Spec.Containers[i].Resources.Limits = v1.ResourceList{} 265 } 266 if containerResource.Limits != nil { 267 for resourceName, quantity := range containerResource.Limits { 268 pod.Spec.Containers[i].Resources.Limits[resourceName] = quantity 269 } 270 } 271 } 272 } 273 } 274 275 func GetPodNamespaceNameKeyMap(podList []*v1.Pod) map[string]*v1.Pod { 276 podMap := make(map[string]*v1.Pod, len(podList)) 277 for _, pod := range podList { 278 if pod == nil { 279 continue 280 } 281 282 key := GenerateUniqObjectNameKey(pod) 283 if oldPod, ok := podMap[key]; ok && oldPod.CreationTimestamp.After(pod.CreationTimestamp.Time) { 284 continue 285 } 286 287 podMap[key] = pod 288 } 289 return podMap 290 } 291 292 // IsAssignedPod selects pods that are assigned (scheduled and running). 293 func IsAssignedPod(pod *v1.Pod) bool { 294 return len(pod.Spec.NodeName) != 0 295 } 296 297 // ParseHostPortForPod gets host ports from pod spec 298 func ParseHostPortForPod(pod *v1.Pod, portName string) (int32, bool) { 299 for i := range pod.Spec.Containers { 300 return ParseHostPortsForContainer(&pod.Spec.Containers[i], portName) 301 } 302 return 0, false 303 } 304 305 // GetNamespacedNameListFromSlice returns a slice of namespaced name 306 func GetNamespacedNameListFromSlice(podSlice []*v1.Pod) []string { 307 namespacedNameList := make([]string, 0, len(podSlice)) 308 for _, pod := range podSlice { 309 namespacedNameList = append(namespacedNameList, pod.Namespace+"/"+pod.Name) 310 } 311 return namespacedNameList 312 } 313 314 // CheckDaemonPod returns true if pod is for DaemonSet 315 func CheckDaemonPod(pod *v1.Pod) bool { 316 for _, owner := range pod.OwnerReferences { 317 if owner.Kind == "DaemonSet" { 318 return true 319 } 320 } 321 return false 322 } 323 324 // GetContainerID gets container id from pod status by container name 325 func GetContainerID(pod *v1.Pod, containerName string) (string, error) { 326 if pod == nil { 327 return "", fmt.Errorf("empty pod") 328 } 329 330 for _, containerStatus := range pod.Status.ContainerStatuses { 331 if containerStatus.Name == containerName { 332 if containerStatus.ContainerID == "" { 333 return "", fmt.Errorf("empty container id in container statues of pod") 334 } 335 return TrimContainerIDPrefix(containerStatus.ContainerID), nil 336 } 337 } 338 339 return "", fmt.Errorf("container %s container id not found", containerName) 340 } 341 342 // GetContainerEnvs gets container envs from pod spec by container name and envs name 343 func GetContainerEnvs(pod *v1.Pod, containerName string, envs ...string) map[string]string { 344 if pod == nil { 345 return nil 346 } 347 348 envSet := sets.NewString(envs...) 349 envMap := make(map[string]string) 350 for _, container := range pod.Spec.Containers { 351 if container.Name != containerName { 352 continue 353 } 354 355 for _, env := range container.Env { 356 if envSet.Has(env.Name) { 357 envMap[env.Name] = env.Value 358 } 359 } 360 } 361 362 return envMap 363 } 364 365 // GetPodCondition extracts the given condition for the given pod 366 func GetPodCondition(pod *v1.Pod, conditionType v1.PodConditionType) (v1.PodCondition, bool) { 367 for _, condition := range pod.Status.Conditions { 368 if condition.Type == conditionType { 369 return condition, true 370 } 371 } 372 return v1.PodCondition{}, false 373 } 374 375 // DeepCopyPodContainers returns a deep-copied objects for v1.Container slice 376 func DeepCopyPodContainers(pod *v1.Pod) (containers []v1.Container) { 377 in, out := &pod.Spec.Containers, &containers 378 *out = make([]v1.Container, len(*in)) 379 for i := range *in { 380 (*in)[i].DeepCopyInto(&(*out)[i]) 381 } 382 return 383 } 384 385 // FilterPodAnnotations returns the needed annotations for the given pod. 386 func FilterPodAnnotations(filterKeys []string, pod *v1.Pod) map[string]string { 387 netAttrMap := make(map[string]string) 388 389 for _, attrKey := range filterKeys { 390 if attrVal, ok := pod.GetAnnotations()[attrKey]; ok { 391 netAttrMap[attrKey] = attrVal 392 } 393 } 394 395 return netAttrMap 396 }