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  }