1/*
2Copyright 2014 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 helper
18
19import (
20	"fmt"
21	"strings"
22
23	v1 "k8s.io/api/core/v1"
24	"k8s.io/apimachinery/pkg/api/resource"
25	"k8s.io/apimachinery/pkg/labels"
26	"k8s.io/apimachinery/pkg/selection"
27	"k8s.io/apimachinery/pkg/util/validation"
28	"k8s.io/kubernetes/pkg/apis/core/helper"
29)
30
31// IsExtendedResourceName returns true if:
32// 1. the resource name is not in the default namespace;
33// 2. resource name does not have "requests." prefix,
34// to avoid confusion with the convention in quota
35// 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name
36func IsExtendedResourceName(name v1.ResourceName) bool {
37	if IsNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
38		return false
39	}
40	// Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
41	nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
42	if errs := validation.IsQualifiedName(string(nameForQuota)); len(errs) != 0 {
43		return false
44	}
45	return true
46}
47
48// IsPrefixedNativeResource returns true if the resource name is in the
49// *kubernetes.io/ namespace.
50func IsPrefixedNativeResource(name v1.ResourceName) bool {
51	return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
52}
53
54// IsNativeResource returns true if the resource name is in the
55// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
56// implicitly in the kubernetes.io/ namespace.
57func IsNativeResource(name v1.ResourceName) bool {
58	return !strings.Contains(string(name), "/") ||
59		IsPrefixedNativeResource(name)
60}
61
62// IsHugePageResourceName returns true if the resource name has the huge page
63// resource prefix.
64func IsHugePageResourceName(name v1.ResourceName) bool {
65	return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
66}
67
68// HugePageResourceName returns a ResourceName with the canonical hugepage
69// prefix prepended for the specified page size.  The page size is converted
70// to its canonical representation.
71func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName {
72	return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String()))
73}
74
75// HugePageSizeFromResourceName returns the page size for the specified huge page
76// resource name.  If the specified input is not a valid huge page resource name
77// an error is returned.
78func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) {
79	if !IsHugePageResourceName(name) {
80		return resource.Quantity{}, fmt.Errorf("resource name: %s is an invalid hugepage name", name)
81	}
82	pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix)
83	return resource.ParseQuantity(pageSize)
84}
85
86// HugePageUnitSizeFromByteSize returns hugepage size has the format.
87// `size` must be guaranteed to divisible into the largest units that can be expressed.
88// <size><unit-prefix>B (1024 = "1KB", 1048576 = "1MB", etc).
89func HugePageUnitSizeFromByteSize(size int64) (string, error) {
90	// hugePageSizeUnitList is borrowed from opencontainers/runc/libcontainer/cgroups/utils.go
91	var hugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
92	idx := 0
93	len := len(hugePageSizeUnitList) - 1
94	for size%1024 == 0 && idx < len {
95		size /= 1024
96		idx++
97	}
98	if size > 1024 && idx < len {
99		return "", fmt.Errorf("size: %d%s must be guaranteed to divisible into the largest units", size, hugePageSizeUnitList[idx])
100	}
101	return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil
102}
103
104// IsHugePageMedium returns true if the volume medium is in 'HugePages[-size]' format
105func IsHugePageMedium(medium v1.StorageMedium) bool {
106	if medium == v1.StorageMediumHugePages {
107		return true
108	}
109	return strings.HasPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
110}
111
112// HugePageSizeFromMedium returns the page size for the specified huge page medium.
113// If the specified input is not a valid huge page medium an error is returned.
114func HugePageSizeFromMedium(medium v1.StorageMedium) (resource.Quantity, error) {
115	if !IsHugePageMedium(medium) {
116		return resource.Quantity{}, fmt.Errorf("medium: %s is not a hugepage medium", medium)
117	}
118	if medium == v1.StorageMediumHugePages {
119		return resource.Quantity{}, fmt.Errorf("medium: %s doesn't have size information", medium)
120	}
121	pageSize := strings.TrimPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix))
122	return resource.ParseQuantity(pageSize)
123}
124
125// IsOvercommitAllowed returns true if the resource is in the default
126// namespace and is not hugepages.
127func IsOvercommitAllowed(name v1.ResourceName) bool {
128	return IsNativeResource(name) &&
129		!IsHugePageResourceName(name)
130}
131
132// IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume
133func IsAttachableVolumeResourceName(name v1.ResourceName) bool {
134	return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
135}
136
137// IsServiceIPSet aims to check if the service's ClusterIP is set or not
138// the objective is not to perform validation here
139func IsServiceIPSet(service *v1.Service) bool {
140	return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != ""
141}
142
143// LoadBalancerStatusEqual evaluates the given load balancers' ingress IP addresses
144// and hostnames and returns true if equal or false if otherwise
145// TODO: make method on LoadBalancerStatus?
146func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool {
147	return ingressSliceEqual(l.Ingress, r.Ingress)
148}
149
150func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool {
151	if len(lhs) != len(rhs) {
152		return false
153	}
154	for i := range lhs {
155		if !ingressEqual(&lhs[i], &rhs[i]) {
156			return false
157		}
158	}
159	return true
160}
161
162func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool {
163	if lhs.IP != rhs.IP {
164		return false
165	}
166	if lhs.Hostname != rhs.Hostname {
167		return false
168	}
169	return true
170}
171
172// GetAccessModesAsString returns a string representation of an array of access modes.
173// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
174func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
175	modes = removeDuplicateAccessModes(modes)
176	modesStr := []string{}
177	if ContainsAccessMode(modes, v1.ReadWriteOnce) {
178		modesStr = append(modesStr, "RWO")
179	}
180	if ContainsAccessMode(modes, v1.ReadOnlyMany) {
181		modesStr = append(modesStr, "ROX")
182	}
183	if ContainsAccessMode(modes, v1.ReadWriteMany) {
184		modesStr = append(modesStr, "RWX")
185	}
186	if ContainsAccessMode(modes, v1.ReadWriteOncePod) {
187		modesStr = append(modesStr, "RWOP")
188	}
189	return strings.Join(modesStr, ",")
190}
191
192// GetAccessModesFromString returns an array of AccessModes from a string created by GetAccessModesAsString
193func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
194	strmodes := strings.Split(modes, ",")
195	accessModes := []v1.PersistentVolumeAccessMode{}
196	for _, s := range strmodes {
197		s = strings.Trim(s, " ")
198		switch {
199		case s == "RWO":
200			accessModes = append(accessModes, v1.ReadWriteOnce)
201		case s == "ROX":
202			accessModes = append(accessModes, v1.ReadOnlyMany)
203		case s == "RWX":
204			accessModes = append(accessModes, v1.ReadWriteMany)
205		case s == "RWOP":
206			accessModes = append(accessModes, v1.ReadWriteOncePod)
207		}
208	}
209	return accessModes
210}
211
212// removeDuplicateAccessModes returns an array of access modes without any duplicates
213func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
214	accessModes := []v1.PersistentVolumeAccessMode{}
215	for _, m := range modes {
216		if !ContainsAccessMode(accessModes, m) {
217			accessModes = append(accessModes, m)
218		}
219	}
220	return accessModes
221}
222
223func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
224	for _, m := range modes {
225		if m == mode {
226			return true
227		}
228	}
229	return false
230}
231
232// NodeSelectorRequirementKeysExistInNodeSelectorTerms checks if a NodeSelectorTerm with key is already specified in terms
233func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorRequirement, terms []v1.NodeSelectorTerm) bool {
234	for _, req := range reqs {
235		for _, term := range terms {
236			for _, r := range term.MatchExpressions {
237				if r.Key == req.Key {
238					return true
239				}
240			}
241		}
242	}
243	return false
244}
245
246// TopologySelectorRequirementsAsSelector converts the []TopologySelectorLabelRequirement api type into a struct
247// that implements labels.Selector.
248func TopologySelectorRequirementsAsSelector(tsm []v1.TopologySelectorLabelRequirement) (labels.Selector, error) {
249	if len(tsm) == 0 {
250		return labels.Nothing(), nil
251	}
252
253	selector := labels.NewSelector()
254	for _, expr := range tsm {
255		r, err := labels.NewRequirement(expr.Key, selection.In, expr.Values)
256		if err != nil {
257			return nil, err
258		}
259		selector = selector.Add(*r)
260	}
261
262	return selector, nil
263}
264
265// MatchTopologySelectorTerms checks whether given labels match topology selector terms in ORed;
266// nil or empty term matches no objects; while empty term list matches all objects.
267func MatchTopologySelectorTerms(topologySelectorTerms []v1.TopologySelectorTerm, lbls labels.Set) bool {
268	if len(topologySelectorTerms) == 0 {
269		// empty term list matches all objects
270		return true
271	}
272
273	for _, req := range topologySelectorTerms {
274		// nil or empty term selects no objects
275		if len(req.MatchLabelExpressions) == 0 {
276			continue
277		}
278
279		labelSelector, err := TopologySelectorRequirementsAsSelector(req.MatchLabelExpressions)
280		if err != nil || !labelSelector.Matches(lbls) {
281			continue
282		}
283
284		return true
285	}
286
287	return false
288}
289
290// AddOrUpdateTolerationInPodSpec tries to add a toleration to the toleration list in PodSpec.
291// Returns true if something was updated, false otherwise.
292func AddOrUpdateTolerationInPodSpec(spec *v1.PodSpec, toleration *v1.Toleration) bool {
293	podTolerations := spec.Tolerations
294
295	var newTolerations []v1.Toleration
296	updated := false
297	for i := range podTolerations {
298		if toleration.MatchToleration(&podTolerations[i]) {
299			if helper.Semantic.DeepEqual(toleration, podTolerations[i]) {
300				return false
301			}
302			newTolerations = append(newTolerations, *toleration)
303			updated = true
304			continue
305		}
306
307		newTolerations = append(newTolerations, podTolerations[i])
308	}
309
310	if !updated {
311		newTolerations = append(newTolerations, *toleration)
312	}
313
314	spec.Tolerations = newTolerations
315	return true
316}
317
318// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list.
319// Returns true if something was updated, false otherwise.
320func AddOrUpdateTolerationInPod(pod *v1.Pod, toleration *v1.Toleration) bool {
321	return AddOrUpdateTolerationInPodSpec(&pod.Spec, toleration)
322}
323
324// GetMatchingTolerations returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise.
325func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) {
326	if len(taints) == 0 {
327		return true, []v1.Toleration{}
328	}
329	if len(tolerations) == 0 && len(taints) > 0 {
330		return false, []v1.Toleration{}
331	}
332	result := []v1.Toleration{}
333	for i := range taints {
334		tolerated := false
335		for j := range tolerations {
336			if tolerations[j].ToleratesTaint(&taints[i]) {
337				result = append(result, tolerations[j])
338				tolerated = true
339				break
340			}
341		}
342		if !tolerated {
343			return false, []v1.Toleration{}
344		}
345	}
346	return true, result
347}
348
349// ScopedResourceSelectorRequirementsAsSelector converts the ScopedResourceSelectorRequirement api type into a struct that implements
350// labels.Selector.
351func ScopedResourceSelectorRequirementsAsSelector(ssr v1.ScopedResourceSelectorRequirement) (labels.Selector, error) {
352	selector := labels.NewSelector()
353	var op selection.Operator
354	switch ssr.Operator {
355	case v1.ScopeSelectorOpIn:
356		op = selection.In
357	case v1.ScopeSelectorOpNotIn:
358		op = selection.NotIn
359	case v1.ScopeSelectorOpExists:
360		op = selection.Exists
361	case v1.ScopeSelectorOpDoesNotExist:
362		op = selection.DoesNotExist
363	default:
364		return nil, fmt.Errorf("%q is not a valid scope selector operator", ssr.Operator)
365	}
366	r, err := labels.NewRequirement(string(ssr.ScopeName), op, ssr.Values)
367	if err != nil {
368		return nil, err
369	}
370	selector = selector.Add(*r)
371	return selector, nil
372}
373