k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/api/v1/pod/util.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes 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 pod
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/intstr"
    26  )
    27  
    28  // FindPort locates the container port for the given pod and portName.  If the
    29  // targetPort is a number, use that.  If the targetPort is a string, look that
    30  // string up in all named ports in all containers in the target pod.  If no
    31  // match is found, fail.
    32  func FindPort(pod *v1.Pod, svcPort *v1.ServicePort) (int, error) {
    33  	portName := svcPort.TargetPort
    34  	switch portName.Type {
    35  	case intstr.String:
    36  		name := portName.StrVal
    37  		for _, container := range pod.Spec.Containers {
    38  			for _, port := range container.Ports {
    39  				if port.Name == name && port.Protocol == svcPort.Protocol {
    40  					return int(port.ContainerPort), nil
    41  				}
    42  			}
    43  		}
    44  	case intstr.Int:
    45  		return portName.IntValue(), nil
    46  	}
    47  
    48  	return 0, fmt.Errorf("no suitable port for manifest: %s", pod.UID)
    49  }
    50  
    51  // ContainerType signifies container type
    52  type ContainerType int
    53  
    54  const (
    55  	// Containers is for normal containers
    56  	Containers ContainerType = 1 << iota
    57  	// InitContainers is for init containers
    58  	InitContainers
    59  	// EphemeralContainers is for ephemeral containers
    60  	EphemeralContainers
    61  )
    62  
    63  // AllContainers specifies that all containers be visited
    64  const AllContainers ContainerType = InitContainers | Containers | EphemeralContainers
    65  
    66  // AllFeatureEnabledContainers returns a ContainerType mask which includes all container
    67  // types except for the ones guarded by feature gate.
    68  func AllFeatureEnabledContainers() ContainerType {
    69  	return AllContainers
    70  }
    71  
    72  // ContainerVisitor is called with each container spec, and returns true
    73  // if visiting should continue.
    74  type ContainerVisitor func(container *v1.Container, containerType ContainerType) (shouldContinue bool)
    75  
    76  // Visitor is called with each object name, and returns true if visiting should continue
    77  type Visitor func(name string) (shouldContinue bool)
    78  
    79  func skipEmptyNames(visitor Visitor) Visitor {
    80  	return func(name string) bool {
    81  		if len(name) == 0 {
    82  			// continue visiting
    83  			return true
    84  		}
    85  		// delegate to visitor
    86  		return visitor(name)
    87  	}
    88  }
    89  
    90  // VisitContainers invokes the visitor function with a pointer to every container
    91  // spec in the given pod spec with type set in mask. If visitor returns false,
    92  // visiting is short-circuited. VisitContainers returns true if visiting completes,
    93  // false if visiting was short-circuited.
    94  func VisitContainers(podSpec *v1.PodSpec, mask ContainerType, visitor ContainerVisitor) bool {
    95  	if mask&InitContainers != 0 {
    96  		for i := range podSpec.InitContainers {
    97  			if !visitor(&podSpec.InitContainers[i], InitContainers) {
    98  				return false
    99  			}
   100  		}
   101  	}
   102  	if mask&Containers != 0 {
   103  		for i := range podSpec.Containers {
   104  			if !visitor(&podSpec.Containers[i], Containers) {
   105  				return false
   106  			}
   107  		}
   108  	}
   109  	if mask&EphemeralContainers != 0 {
   110  		for i := range podSpec.EphemeralContainers {
   111  			if !visitor((*v1.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), EphemeralContainers) {
   112  				return false
   113  			}
   114  		}
   115  	}
   116  	return true
   117  }
   118  
   119  // VisitPodSecretNames invokes the visitor function with the name of every secret
   120  // referenced by the pod spec. If visitor returns false, visiting is short-circuited.
   121  // Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
   122  // Returns true if visiting completed, false if visiting was short-circuited.
   123  func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool {
   124  	visitor = skipEmptyNames(visitor)
   125  	for _, reference := range pod.Spec.ImagePullSecrets {
   126  		if !visitor(reference.Name) {
   127  			return false
   128  		}
   129  	}
   130  	VisitContainers(&pod.Spec, AllContainers, func(c *v1.Container, containerType ContainerType) bool {
   131  		return visitContainerSecretNames(c, visitor)
   132  	})
   133  	var source *v1.VolumeSource
   134  
   135  	for i := range pod.Spec.Volumes {
   136  		source = &pod.Spec.Volumes[i].VolumeSource
   137  		switch {
   138  		case source.AzureFile != nil:
   139  			if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) {
   140  				return false
   141  			}
   142  		case source.CephFS != nil:
   143  			if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) {
   144  				return false
   145  			}
   146  		case source.Cinder != nil:
   147  			if source.Cinder.SecretRef != nil && !visitor(source.Cinder.SecretRef.Name) {
   148  				return false
   149  			}
   150  		case source.FlexVolume != nil:
   151  			if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) {
   152  				return false
   153  			}
   154  		case source.Projected != nil:
   155  			for j := range source.Projected.Sources {
   156  				if source.Projected.Sources[j].Secret != nil {
   157  					if !visitor(source.Projected.Sources[j].Secret.Name) {
   158  						return false
   159  					}
   160  				}
   161  			}
   162  		case source.RBD != nil:
   163  			if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) {
   164  				return false
   165  			}
   166  		case source.Secret != nil:
   167  			if !visitor(source.Secret.SecretName) {
   168  				return false
   169  			}
   170  		case source.ScaleIO != nil:
   171  			if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) {
   172  				return false
   173  			}
   174  		case source.ISCSI != nil:
   175  			if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
   176  				return false
   177  			}
   178  		case source.StorageOS != nil:
   179  			if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) {
   180  				return false
   181  			}
   182  		case source.CSI != nil:
   183  			if source.CSI.NodePublishSecretRef != nil && !visitor(source.CSI.NodePublishSecretRef.Name) {
   184  				return false
   185  			}
   186  		}
   187  	}
   188  	return true
   189  }
   190  
   191  // visitContainerSecretNames returns true unless the visitor returned false when invoked with a secret reference
   192  func visitContainerSecretNames(container *v1.Container, visitor Visitor) bool {
   193  	for _, env := range container.EnvFrom {
   194  		if env.SecretRef != nil {
   195  			if !visitor(env.SecretRef.Name) {
   196  				return false
   197  			}
   198  		}
   199  	}
   200  	for _, envVar := range container.Env {
   201  		if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
   202  			if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
   203  				return false
   204  			}
   205  		}
   206  	}
   207  	return true
   208  }
   209  
   210  // VisitPodConfigmapNames invokes the visitor function with the name of every configmap
   211  // referenced by the pod spec. If visitor returns false, visiting is short-circuited.
   212  // Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
   213  // Returns true if visiting completed, false if visiting was short-circuited.
   214  func VisitPodConfigmapNames(pod *v1.Pod, visitor Visitor) bool {
   215  	visitor = skipEmptyNames(visitor)
   216  	VisitContainers(&pod.Spec, AllContainers, func(c *v1.Container, containerType ContainerType) bool {
   217  		return visitContainerConfigmapNames(c, visitor)
   218  	})
   219  	var source *v1.VolumeSource
   220  	for i := range pod.Spec.Volumes {
   221  		source = &pod.Spec.Volumes[i].VolumeSource
   222  		switch {
   223  		case source.Projected != nil:
   224  			for j := range source.Projected.Sources {
   225  				if source.Projected.Sources[j].ConfigMap != nil {
   226  					if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
   227  						return false
   228  					}
   229  				}
   230  			}
   231  		case source.ConfigMap != nil:
   232  			if !visitor(source.ConfigMap.Name) {
   233  				return false
   234  			}
   235  		}
   236  	}
   237  	return true
   238  }
   239  
   240  // visitContainerConfigmapNames returns true unless the visitor returned false when invoked with a configmap reference
   241  func visitContainerConfigmapNames(container *v1.Container, visitor Visitor) bool {
   242  	for _, env := range container.EnvFrom {
   243  		if env.ConfigMapRef != nil {
   244  			if !visitor(env.ConfigMapRef.Name) {
   245  				return false
   246  			}
   247  		}
   248  	}
   249  	for _, envVar := range container.Env {
   250  		if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
   251  			if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
   252  				return false
   253  			}
   254  		}
   255  	}
   256  	return true
   257  }
   258  
   259  // GetContainerStatus extracts the status of container "name" from "statuses".
   260  // It returns true if "name" exists, else returns false.
   261  func GetContainerStatus(statuses []v1.ContainerStatus, name string) (v1.ContainerStatus, bool) {
   262  	for i := range statuses {
   263  		if statuses[i].Name == name {
   264  			return statuses[i], true
   265  		}
   266  	}
   267  	return v1.ContainerStatus{}, false
   268  }
   269  
   270  // GetExistingContainerStatus extracts the status of container "name" from "statuses",
   271  // It also returns if "name" exists.
   272  func GetExistingContainerStatus(statuses []v1.ContainerStatus, name string) v1.ContainerStatus {
   273  	status, _ := GetContainerStatus(statuses, name)
   274  	return status
   275  }
   276  
   277  // GetIndexOfContainerStatus gets the index of status of container "name" from "statuses",
   278  // It returns (index, true) if "name" exists, else returns (0, false).
   279  func GetIndexOfContainerStatus(statuses []v1.ContainerStatus, name string) (int, bool) {
   280  	for i := range statuses {
   281  		if statuses[i].Name == name {
   282  			return i, true
   283  		}
   284  	}
   285  	return 0, false
   286  }
   287  
   288  // IsPodAvailable returns true if a pod is available; false otherwise.
   289  // Precondition for an available pod is that it must be ready. On top
   290  // of that, there are two cases when a pod can be considered available:
   291  // 1. minReadySeconds == 0, or
   292  // 2. LastTransitionTime (is set) + minReadySeconds < current time
   293  func IsPodAvailable(pod *v1.Pod, minReadySeconds int32, now metav1.Time) bool {
   294  	if !IsPodReady(pod) {
   295  		return false
   296  	}
   297  
   298  	c := GetPodReadyCondition(pod.Status)
   299  	minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second
   300  	if minReadySeconds == 0 || (!c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time)) {
   301  		return true
   302  	}
   303  	return false
   304  }
   305  
   306  // IsPodReady returns true if a pod is ready; false otherwise.
   307  func IsPodReady(pod *v1.Pod) bool {
   308  	return IsPodReadyConditionTrue(pod.Status)
   309  }
   310  
   311  // IsPodTerminal returns true if a pod is terminal, all containers are stopped and cannot ever regress.
   312  func IsPodTerminal(pod *v1.Pod) bool {
   313  	return IsPodPhaseTerminal(pod.Status.Phase)
   314  }
   315  
   316  // IsPodPhaseTerminal returns true if the pod's phase is terminal.
   317  func IsPodPhaseTerminal(phase v1.PodPhase) bool {
   318  	return phase == v1.PodFailed || phase == v1.PodSucceeded
   319  }
   320  
   321  // IsPodReadyConditionTrue returns true if a pod is ready; false otherwise.
   322  func IsPodReadyConditionTrue(status v1.PodStatus) bool {
   323  	condition := GetPodReadyCondition(status)
   324  	return condition != nil && condition.Status == v1.ConditionTrue
   325  }
   326  
   327  // IsContainersReadyConditionTrue returns true if a pod is ready; false otherwise.
   328  func IsContainersReadyConditionTrue(status v1.PodStatus) bool {
   329  	condition := GetContainersReadyCondition(status)
   330  	return condition != nil && condition.Status == v1.ConditionTrue
   331  }
   332  
   333  // GetPodReadyCondition extracts the pod ready condition from the given status and returns that.
   334  // Returns nil if the condition is not present.
   335  func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition {
   336  	_, condition := GetPodCondition(&status, v1.PodReady)
   337  	return condition
   338  }
   339  
   340  // GetContainersReadyCondition extracts the containers ready condition from the given status and returns that.
   341  // Returns nil if the condition is not present.
   342  func GetContainersReadyCondition(status v1.PodStatus) *v1.PodCondition {
   343  	_, condition := GetPodCondition(&status, v1.ContainersReady)
   344  	return condition
   345  }
   346  
   347  // GetPodCondition extracts the provided condition from the given status and returns that.
   348  // Returns nil and -1 if the condition is not present, and the index of the located condition.
   349  func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
   350  	if status == nil {
   351  		return -1, nil
   352  	}
   353  	return GetPodConditionFromList(status.Conditions, conditionType)
   354  }
   355  
   356  // GetPodConditionFromList extracts the provided condition from the given list of condition and
   357  // returns the index of the condition and the condition. Returns -1 and nil if the condition is not present.
   358  func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
   359  	if conditions == nil {
   360  		return -1, nil
   361  	}
   362  	for i := range conditions {
   363  		if conditions[i].Type == conditionType {
   364  			return i, &conditions[i]
   365  		}
   366  	}
   367  	return -1, nil
   368  }
   369  
   370  // UpdatePodCondition updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
   371  // status has changed.
   372  // Returns true if pod condition has changed or has been added.
   373  func UpdatePodCondition(status *v1.PodStatus, condition *v1.PodCondition) bool {
   374  	condition.LastTransitionTime = metav1.Now()
   375  	// Try to find this pod condition.
   376  	conditionIndex, oldCondition := GetPodCondition(status, condition.Type)
   377  
   378  	if oldCondition == nil {
   379  		// We are adding new pod condition.
   380  		status.Conditions = append(status.Conditions, *condition)
   381  		return true
   382  	}
   383  	// We are updating an existing condition, so we need to check if it has changed.
   384  	if condition.Status == oldCondition.Status {
   385  		condition.LastTransitionTime = oldCondition.LastTransitionTime
   386  	}
   387  
   388  	isEqual := condition.Status == oldCondition.Status &&
   389  		condition.Reason == oldCondition.Reason &&
   390  		condition.Message == oldCondition.Message &&
   391  		condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) &&
   392  		condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime)
   393  
   394  	status.Conditions[conditionIndex] = *condition
   395  	// Return true if one of the fields have changed.
   396  	return !isEqual
   397  }