1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package pod
18
19import (
20	"fmt"
21	"time"
22
23	"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.
32func 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// Visitor is called with each object name, and returns true if visiting should continue
52type Visitor func(name string) (shouldContinue bool)
53
54// VisitPodSecretNames invokes the visitor function with the name of every secret
55// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
56// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
57// Returns true if visiting completed, false if visiting was short-circuited.
58func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool {
59	for _, reference := range pod.Spec.ImagePullSecrets {
60		if !visitor(reference.Name) {
61			return false
62		}
63	}
64	for i := range pod.Spec.InitContainers {
65		if !visitContainerSecretNames(&pod.Spec.InitContainers[i], visitor) {
66			return false
67		}
68	}
69	for i := range pod.Spec.Containers {
70		if !visitContainerSecretNames(&pod.Spec.Containers[i], visitor) {
71			return false
72		}
73	}
74	var source *v1.VolumeSource
75
76	for i := range pod.Spec.Volumes {
77		source = &pod.Spec.Volumes[i].VolumeSource
78		switch {
79		case source.AzureFile != nil:
80			if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) {
81				return false
82			}
83		case source.CephFS != nil:
84			if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) {
85				return false
86			}
87		case source.Cinder != nil:
88			if source.Cinder.SecretRef != nil && !visitor(source.Cinder.SecretRef.Name) {
89				return false
90			}
91		case source.FlexVolume != nil:
92			if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) {
93				return false
94			}
95		case source.Projected != nil:
96			for j := range source.Projected.Sources {
97				if source.Projected.Sources[j].Secret != nil {
98					if !visitor(source.Projected.Sources[j].Secret.Name) {
99						return false
100					}
101				}
102			}
103		case source.RBD != nil:
104			if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) {
105				return false
106			}
107		case source.Secret != nil:
108			if !visitor(source.Secret.SecretName) {
109				return false
110			}
111		case source.ScaleIO != nil:
112			if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) {
113				return false
114			}
115		case source.ISCSI != nil:
116			if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
117				return false
118			}
119		case source.StorageOS != nil:
120			if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) {
121				return false
122			}
123		}
124	}
125	return true
126}
127
128func visitContainerSecretNames(container *v1.Container, visitor Visitor) bool {
129	for _, env := range container.EnvFrom {
130		if env.SecretRef != nil {
131			if !visitor(env.SecretRef.Name) {
132				return false
133			}
134		}
135	}
136	for _, envVar := range container.Env {
137		if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
138			if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
139				return false
140			}
141		}
142	}
143	return true
144}
145
146// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
147// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
148// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
149// Returns true if visiting completed, false if visiting was short-circuited.
150func VisitPodConfigmapNames(pod *v1.Pod, visitor Visitor) bool {
151	for i := range pod.Spec.InitContainers {
152		if !visitContainerConfigmapNames(&pod.Spec.InitContainers[i], visitor) {
153			return false
154		}
155	}
156	for i := range pod.Spec.Containers {
157		if !visitContainerConfigmapNames(&pod.Spec.Containers[i], visitor) {
158			return false
159		}
160	}
161	var source *v1.VolumeSource
162	for i := range pod.Spec.Volumes {
163		source = &pod.Spec.Volumes[i].VolumeSource
164		switch {
165		case source.Projected != nil:
166			for j := range source.Projected.Sources {
167				if source.Projected.Sources[j].ConfigMap != nil {
168					if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
169						return false
170					}
171				}
172			}
173		case source.ConfigMap != nil:
174			if !visitor(source.ConfigMap.Name) {
175				return false
176			}
177		}
178	}
179	return true
180}
181
182func visitContainerConfigmapNames(container *v1.Container, visitor Visitor) bool {
183	for _, env := range container.EnvFrom {
184		if env.ConfigMapRef != nil {
185			if !visitor(env.ConfigMapRef.Name) {
186				return false
187			}
188		}
189	}
190	for _, envVar := range container.Env {
191		if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
192			if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
193				return false
194			}
195		}
196	}
197	return true
198}
199
200// GetContainerStatus extracts the status of container "name" from "statuses".
201// It also returns if "name" exists.
202func GetContainerStatus(statuses []v1.ContainerStatus, name string) (v1.ContainerStatus, bool) {
203	for i := range statuses {
204		if statuses[i].Name == name {
205			return statuses[i], true
206		}
207	}
208	return v1.ContainerStatus{}, false
209}
210
211// GetExistingContainerStatus extracts the status of container "name" from "statuses",
212// and returns empty status if "name" does not exist.
213func GetExistingContainerStatus(statuses []v1.ContainerStatus, name string) v1.ContainerStatus {
214	for i := range statuses {
215		if statuses[i].Name == name {
216			return statuses[i]
217		}
218	}
219	return v1.ContainerStatus{}
220}
221
222// IsPodAvailable returns true if a pod is available; false otherwise.
223// Precondition for an available pod is that it must be ready. On top
224// of that, there are two cases when a pod can be considered available:
225// 1. minReadySeconds == 0, or
226// 2. LastTransitionTime (is set) + minReadySeconds < current time
227func IsPodAvailable(pod *v1.Pod, minReadySeconds int32, now metav1.Time) bool {
228	if !IsPodReady(pod) {
229		return false
230	}
231
232	c := GetPodReadyCondition(pod.Status)
233	minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second
234	if minReadySeconds == 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) {
235		return true
236	}
237	return false
238}
239
240// IsPodReady returns true if a pod is ready; false otherwise.
241func IsPodReady(pod *v1.Pod) bool {
242	return IsPodReadyConditionTrue(pod.Status)
243}
244
245// IsPodReady returns true if a pod is ready; false otherwise.
246func IsPodReadyConditionTrue(status v1.PodStatus) bool {
247	condition := GetPodReadyCondition(status)
248	return condition != nil && condition.Status == v1.ConditionTrue
249}
250
251// Extracts the pod ready condition from the given status and returns that.
252// Returns nil if the condition is not present.
253func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition {
254	_, condition := GetPodCondition(&status, v1.PodReady)
255	return condition
256}
257
258// GetPodCondition extracts the provided condition from the given status and returns that.
259// Returns nil and -1 if the condition is not present, and the index of the located condition.
260func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
261	if status == nil {
262		return -1, nil
263	}
264	return GetPodConditionFromList(status.Conditions, conditionType)
265}
266
267// GetPodConditionFromList extracts the provided condition from the given list of condition and
268// returns the index of the condition and the condition. Returns -1 and nil if the condition is not present.
269func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
270	if conditions == nil {
271		return -1, nil
272	}
273	for i := range conditions {
274		if conditions[i].Type == conditionType {
275			return i, &conditions[i]
276		}
277	}
278	return -1, nil
279}
280
281// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
282// status has changed.
283// Returns true if pod condition has changed or has been added.
284func UpdatePodCondition(status *v1.PodStatus, condition *v1.PodCondition) bool {
285	condition.LastTransitionTime = metav1.Now()
286	// Try to find this pod condition.
287	conditionIndex, oldCondition := GetPodCondition(status, condition.Type)
288
289	if oldCondition == nil {
290		// We are adding new pod condition.
291		status.Conditions = append(status.Conditions, *condition)
292		return true
293	} else {
294		// We are updating an existing condition, so we need to check if it has changed.
295		if condition.Status == oldCondition.Status {
296			condition.LastTransitionTime = oldCondition.LastTransitionTime
297		}
298
299		isEqual := condition.Status == oldCondition.Status &&
300			condition.Reason == oldCondition.Reason &&
301			condition.Message == oldCondition.Message &&
302			condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) &&
303			condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime)
304
305		status.Conditions[conditionIndex] = *condition
306		// Return true if one of the fields have changed.
307		return !isEqual
308	}
309}
310