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