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 api 18 19import ( 20 "crypto/md5" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "strings" 25 "time" 26 27 "github.com/davecgh/go-spew/spew" 28 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/conversion" 32 "k8s.io/apimachinery/pkg/fields" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/selection" 36 "k8s.io/apimachinery/pkg/util/sets" 37) 38 39// Conversion error conveniently packages up errors in conversions. 40type ConversionError struct { 41 In, Out interface{} 42 Message string 43} 44 45// Return a helpful string about the error 46func (c *ConversionError) Error() string { 47 return spew.Sprintf( 48 "Conversion error: %s. (in: %v(%+v) out: %v)", 49 c.Message, reflect.TypeOf(c.In), c.In, reflect.TypeOf(c.Out), 50 ) 51} 52 53const ( 54 // annotation key prefix used to identify non-convertible json paths. 55 NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io" 56) 57 58// NonConvertibleFields iterates over the provided map and filters out all but 59// any keys with the "non-convertible.kubernetes.io" prefix. 60func NonConvertibleFields(annotations map[string]string) map[string]string { 61 nonConvertibleKeys := map[string]string{} 62 for key, value := range annotations { 63 if strings.HasPrefix(key, NonConvertibleAnnotationPrefix) { 64 nonConvertibleKeys[key] = value 65 } 66 } 67 return nonConvertibleKeys 68} 69 70// Semantic can do semantic deep equality checks for api objects. 71// Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true 72var Semantic = conversion.EqualitiesOrDie( 73 func(a, b resource.Quantity) bool { 74 // Ignore formatting, only care that numeric value stayed the same. 75 // TODO: if we decide it's important, it should be safe to start comparing the format. 76 // 77 // Uninitialized quantities are equivalent to 0 quantities. 78 return a.Cmp(b) == 0 79 }, 80 func(a, b metav1.Time) bool { 81 return a.UTC() == b.UTC() 82 }, 83 func(a, b labels.Selector) bool { 84 return a.String() == b.String() 85 }, 86 func(a, b fields.Selector) bool { 87 return a.String() == b.String() 88 }, 89) 90 91var standardResourceQuotaScopes = sets.NewString( 92 string(ResourceQuotaScopeTerminating), 93 string(ResourceQuotaScopeNotTerminating), 94 string(ResourceQuotaScopeBestEffort), 95 string(ResourceQuotaScopeNotBestEffort), 96) 97 98// IsStandardResourceQuotaScope returns true if the scope is a standard value 99func IsStandardResourceQuotaScope(str string) bool { 100 return standardResourceQuotaScopes.Has(str) 101} 102 103var podObjectCountQuotaResources = sets.NewString( 104 string(ResourcePods), 105) 106 107var podComputeQuotaResources = sets.NewString( 108 string(ResourceCPU), 109 string(ResourceMemory), 110 string(ResourceLimitsCPU), 111 string(ResourceLimitsMemory), 112 string(ResourceRequestsCPU), 113 string(ResourceRequestsMemory), 114) 115 116// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope 117func IsResourceQuotaScopeValidForResource(scope ResourceQuotaScope, resource string) bool { 118 switch scope { 119 case ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeNotBestEffort: 120 return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource) 121 case ResourceQuotaScopeBestEffort: 122 return podObjectCountQuotaResources.Has(resource) 123 default: 124 return true 125 } 126} 127 128var standardContainerResources = sets.NewString( 129 string(ResourceCPU), 130 string(ResourceMemory), 131) 132 133// IsStandardContainerResourceName returns true if the container can make a resource request 134// for the specified resource 135func IsStandardContainerResourceName(str string) bool { 136 return standardContainerResources.Has(str) 137} 138 139// IsOpaqueIntResourceName returns true if the resource name has the opaque 140// integer resource prefix. 141func IsOpaqueIntResourceName(name ResourceName) bool { 142 return strings.HasPrefix(string(name), ResourceOpaqueIntPrefix) 143} 144 145// OpaqueIntResourceName returns a ResourceName with the canonical opaque 146// integer prefix prepended. If the argument already has the prefix, it is 147// returned unmodified. 148func OpaqueIntResourceName(name string) ResourceName { 149 if IsOpaqueIntResourceName(ResourceName(name)) { 150 return ResourceName(name) 151 } 152 return ResourceName(fmt.Sprintf("%s%s", ResourceOpaqueIntPrefix, name)) 153} 154 155var standardLimitRangeTypes = sets.NewString( 156 string(LimitTypePod), 157 string(LimitTypeContainer), 158 string(LimitTypePersistentVolumeClaim), 159) 160 161// IsStandardLimitRangeType returns true if the type is Pod or Container 162func IsStandardLimitRangeType(str string) bool { 163 return standardLimitRangeTypes.Has(str) 164} 165 166var standardQuotaResources = sets.NewString( 167 string(ResourceCPU), 168 string(ResourceMemory), 169 string(ResourceRequestsCPU), 170 string(ResourceRequestsMemory), 171 string(ResourceRequestsStorage), 172 string(ResourceLimitsCPU), 173 string(ResourceLimitsMemory), 174 string(ResourcePods), 175 string(ResourceQuotas), 176 string(ResourceServices), 177 string(ResourceReplicationControllers), 178 string(ResourceSecrets), 179 string(ResourcePersistentVolumeClaims), 180 string(ResourceConfigMaps), 181 string(ResourceServicesNodePorts), 182 string(ResourceServicesLoadBalancers), 183) 184 185// IsStandardQuotaResourceName returns true if the resource is known to 186// the quota tracking system 187func IsStandardQuotaResourceName(str string) bool { 188 return standardQuotaResources.Has(str) 189} 190 191var standardResources = sets.NewString( 192 string(ResourceCPU), 193 string(ResourceMemory), 194 string(ResourceRequestsCPU), 195 string(ResourceRequestsMemory), 196 string(ResourceLimitsCPU), 197 string(ResourceLimitsMemory), 198 string(ResourcePods), 199 string(ResourceQuotas), 200 string(ResourceServices), 201 string(ResourceReplicationControllers), 202 string(ResourceSecrets), 203 string(ResourceConfigMaps), 204 string(ResourcePersistentVolumeClaims), 205 string(ResourceStorage), 206 string(ResourceRequestsStorage), 207) 208 209// IsStandardResourceName returns true if the resource is known to the system 210func IsStandardResourceName(str string) bool { 211 return standardResources.Has(str) 212} 213 214var integerResources = sets.NewString( 215 string(ResourcePods), 216 string(ResourceQuotas), 217 string(ResourceServices), 218 string(ResourceReplicationControllers), 219 string(ResourceSecrets), 220 string(ResourceConfigMaps), 221 string(ResourcePersistentVolumeClaims), 222 string(ResourceServicesNodePorts), 223 string(ResourceServicesLoadBalancers), 224) 225 226// IsIntegerResourceName returns true if the resource is measured in integer values 227func IsIntegerResourceName(str string) bool { 228 return integerResources.Has(str) || IsOpaqueIntResourceName(ResourceName(str)) 229} 230 231// this function aims to check if the service's ClusterIP is set or not 232// the objective is not to perform validation here 233func IsServiceIPSet(service *Service) bool { 234 return service.Spec.ClusterIP != ClusterIPNone && service.Spec.ClusterIP != "" 235} 236 237// this function aims to check if the service's cluster IP is requested or not 238func IsServiceIPRequested(service *Service) bool { 239 // ExternalName services are CNAME aliases to external ones. Ignore the IP. 240 if service.Spec.Type == ServiceTypeExternalName { 241 return false 242 } 243 return service.Spec.ClusterIP == "" 244} 245 246var standardFinalizers = sets.NewString( 247 string(FinalizerKubernetes), 248 metav1.FinalizerOrphanDependents, 249) 250 251// HasAnnotation returns a bool if passed in annotation exists 252func HasAnnotation(obj ObjectMeta, ann string) bool { 253 _, found := obj.Annotations[ann] 254 return found 255} 256 257// SetMetaDataAnnotation sets the annotation and value 258func SetMetaDataAnnotation(obj *ObjectMeta, ann string, value string) { 259 if obj.Annotations == nil { 260 obj.Annotations = make(map[string]string) 261 } 262 obj.Annotations[ann] = value 263} 264 265func IsStandardFinalizerName(str string) bool { 266 return standardFinalizers.Has(str) 267} 268 269// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, 270// only if they do not already exist 271func AddToNodeAddresses(addresses *[]NodeAddress, addAddresses ...NodeAddress) { 272 for _, add := range addAddresses { 273 exists := false 274 for _, existing := range *addresses { 275 if existing.Address == add.Address && existing.Type == add.Type { 276 exists = true 277 break 278 } 279 } 280 if !exists { 281 *addresses = append(*addresses, add) 282 } 283 } 284} 285 286func HashObject(obj runtime.Object, codec runtime.Codec) (string, error) { 287 data, err := runtime.Encode(codec, obj) 288 if err != nil { 289 return "", err 290 } 291 return fmt.Sprintf("%x", md5.Sum(data)), nil 292} 293 294// TODO: make method on LoadBalancerStatus? 295func LoadBalancerStatusEqual(l, r *LoadBalancerStatus) bool { 296 return ingressSliceEqual(l.Ingress, r.Ingress) 297} 298 299func ingressSliceEqual(lhs, rhs []LoadBalancerIngress) bool { 300 if len(lhs) != len(rhs) { 301 return false 302 } 303 for i := range lhs { 304 if !ingressEqual(&lhs[i], &rhs[i]) { 305 return false 306 } 307 } 308 return true 309} 310 311func ingressEqual(lhs, rhs *LoadBalancerIngress) bool { 312 if lhs.IP != rhs.IP { 313 return false 314 } 315 if lhs.Hostname != rhs.Hostname { 316 return false 317 } 318 return true 319} 320 321// TODO: make method on LoadBalancerStatus? 322func LoadBalancerStatusDeepCopy(lb *LoadBalancerStatus) *LoadBalancerStatus { 323 c := &LoadBalancerStatus{} 324 c.Ingress = make([]LoadBalancerIngress, len(lb.Ingress)) 325 for i := range lb.Ingress { 326 c.Ingress[i] = lb.Ingress[i] 327 } 328 return c 329} 330 331// GetAccessModesAsString returns a string representation of an array of access modes. 332// modes, when present, are always in the same order: RWO,ROX,RWX. 333func GetAccessModesAsString(modes []PersistentVolumeAccessMode) string { 334 modes = removeDuplicateAccessModes(modes) 335 modesStr := []string{} 336 if containsAccessMode(modes, ReadWriteOnce) { 337 modesStr = append(modesStr, "RWO") 338 } 339 if containsAccessMode(modes, ReadOnlyMany) { 340 modesStr = append(modesStr, "ROX") 341 } 342 if containsAccessMode(modes, ReadWriteMany) { 343 modesStr = append(modesStr, "RWX") 344 } 345 return strings.Join(modesStr, ",") 346} 347 348// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString 349func GetAccessModesFromString(modes string) []PersistentVolumeAccessMode { 350 strmodes := strings.Split(modes, ",") 351 accessModes := []PersistentVolumeAccessMode{} 352 for _, s := range strmodes { 353 s = strings.Trim(s, " ") 354 switch { 355 case s == "RWO": 356 accessModes = append(accessModes, ReadWriteOnce) 357 case s == "ROX": 358 accessModes = append(accessModes, ReadOnlyMany) 359 case s == "RWX": 360 accessModes = append(accessModes, ReadWriteMany) 361 } 362 } 363 return accessModes 364} 365 366// removeDuplicateAccessModes returns an array of access modes without any duplicates 367func removeDuplicateAccessModes(modes []PersistentVolumeAccessMode) []PersistentVolumeAccessMode { 368 accessModes := []PersistentVolumeAccessMode{} 369 for _, m := range modes { 370 if !containsAccessMode(accessModes, m) { 371 accessModes = append(accessModes, m) 372 } 373 } 374 return accessModes 375} 376 377func containsAccessMode(modes []PersistentVolumeAccessMode, mode PersistentVolumeAccessMode) bool { 378 for _, m := range modes { 379 if m == mode { 380 return true 381 } 382 } 383 return false 384} 385 386// ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format. 387func ParseRFC3339(s string, nowFn func() metav1.Time) (metav1.Time, error) { 388 if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil { 389 return metav1.Time{Time: t}, nil 390 } 391 t, err := time.Parse(time.RFC3339, s) 392 if err != nil { 393 return metav1.Time{}, err 394 } 395 return metav1.Time{Time: t}, nil 396} 397 398// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements 399// labels.Selector. 400func NodeSelectorRequirementsAsSelector(nsm []NodeSelectorRequirement) (labels.Selector, error) { 401 if len(nsm) == 0 { 402 return labels.Nothing(), nil 403 } 404 selector := labels.NewSelector() 405 for _, expr := range nsm { 406 var op selection.Operator 407 switch expr.Operator { 408 case NodeSelectorOpIn: 409 op = selection.In 410 case NodeSelectorOpNotIn: 411 op = selection.NotIn 412 case NodeSelectorOpExists: 413 op = selection.Exists 414 case NodeSelectorOpDoesNotExist: 415 op = selection.DoesNotExist 416 case NodeSelectorOpGt: 417 op = selection.GreaterThan 418 case NodeSelectorOpLt: 419 op = selection.LessThan 420 default: 421 return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator) 422 } 423 r, err := labels.NewRequirement(expr.Key, op, expr.Values) 424 if err != nil { 425 return nil, err 426 } 427 selector = selector.Add(*r) 428 } 429 return selector, nil 430} 431 432const ( 433 // TolerationsAnnotationKey represents the key of tolerations data (json serialized) 434 // in the Annotations of a Pod. 435 TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations" 436 437 // TaintsAnnotationKey represents the key of taints data (json serialized) 438 // in the Annotations of a Node. 439 TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints" 440 441 // SeccompPodAnnotationKey represents the key of a seccomp profile applied 442 // to all containers of a pod. 443 SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod" 444 445 // SeccompContainerAnnotationKeyPrefix represents the key of a seccomp profile applied 446 // to one container of a pod. 447 SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/" 448 449 // CreatedByAnnotation represents the key used to store the spec(json) 450 // used to create the resource. 451 CreatedByAnnotation = "kubernetes.io/created-by" 452 453 // PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized) 454 // in the Annotations of a Node. 455 PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods" 456 457 // SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure 458 // container of a pod. The annotation value is a comma separated list of sysctl_name=value 459 // key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by 460 // the kubelet. Pods with other sysctls will fail to launch. 461 SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls" 462 463 // UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure 464 // container of a pod. The annotation value is a comma separated list of sysctl_name=value 465 // key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly 466 // namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use 467 // is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet 468 // will fail to launch. 469 UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls" 470 471 // ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache 472 // an object (e.g. secret, config map) before fetching it again from apiserver. 473 // This annotation can be attached to node. 474 ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" 475 476 // AffinityAnnotationKey represents the key of affinity data (json serialized) 477 // in the Annotations of a Pod. 478 // TODO: remove when alpha support for affinity is removed 479 AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" 480) 481 482// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations 483// and converts it to the []Toleration type in api. 484func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]Toleration, error) { 485 var tolerations []Toleration 486 if len(annotations) > 0 && annotations[TolerationsAnnotationKey] != "" { 487 err := json.Unmarshal([]byte(annotations[TolerationsAnnotationKey]), &tolerations) 488 if err != nil { 489 return tolerations, err 490 } 491 } 492 return tolerations, nil 493} 494 495// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. 496// Returns true if something was updated, false otherwise. 497func AddOrUpdateTolerationInPod(pod *Pod, toleration *Toleration) (bool, error) { 498 podTolerations := pod.Spec.Tolerations 499 500 var newTolerations []Toleration 501 updated := false 502 for i := range podTolerations { 503 if toleration.MatchToleration(&podTolerations[i]) { 504 if Semantic.DeepEqual(toleration, podTolerations[i]) { 505 return false, nil 506 } 507 newTolerations = append(newTolerations, *toleration) 508 updated = true 509 continue 510 } 511 512 newTolerations = append(newTolerations, podTolerations[i]) 513 } 514 515 if !updated { 516 newTolerations = append(newTolerations, *toleration) 517 } 518 519 pod.Spec.Tolerations = newTolerations 520 return true, nil 521} 522 523// MatchToleration checks if the toleration matches tolerationToMatch. Tolerations are unique by <key,effect,operator,value>, 524// if the two tolerations have same <key,effect,operator,value> combination, regard as they match. 525// TODO: uniqueness check for tolerations in api validations. 526func (t *Toleration) MatchToleration(tolerationToMatch *Toleration) bool { 527 return t.Key == tolerationToMatch.Key && 528 t.Effect == tolerationToMatch.Effect && 529 t.Operator == tolerationToMatch.Operator && 530 t.Value == tolerationToMatch.Value 531} 532 533// TolerationToleratesTaint checks if the toleration tolerates the taint. 534func TolerationToleratesTaint(toleration *Toleration, taint *Taint) bool { 535 if len(toleration.Effect) != 0 && toleration.Effect != taint.Effect { 536 return false 537 } 538 539 if toleration.Key != taint.Key { 540 return false 541 } 542 // TODO: Use proper defaulting when Toleration becomes a field of PodSpec 543 if (len(toleration.Operator) == 0 || toleration.Operator == TolerationOpEqual) && toleration.Value == taint.Value { 544 return true 545 } 546 if toleration.Operator == TolerationOpExists { 547 return true 548 } 549 return false 550} 551 552// TaintToleratedByTolerations checks if taint is tolerated by any of the tolerations. 553func TaintToleratedByTolerations(taint *Taint, tolerations []Toleration) bool { 554 tolerated := false 555 for i := range tolerations { 556 if TolerationToleratesTaint(&tolerations[i], taint) { 557 tolerated = true 558 break 559 } 560 } 561 return tolerated 562} 563 564// MatchTaint checks if the taint matches taintToMatch. Taints are unique by key:effect, 565// if the two taints have same key:effect, regard as they match. 566func (t *Taint) MatchTaint(taintToMatch Taint) bool { 567 return t.Key == taintToMatch.Key && t.Effect == taintToMatch.Effect 568} 569 570// taint.ToString() converts taint struct to string in format key=value:effect or key:effect. 571func (t *Taint) ToString() string { 572 if len(t.Value) == 0 { 573 return fmt.Sprintf("%v:%v", t.Key, t.Effect) 574 } 575 return fmt.Sprintf("%v=%v:%v", t.Key, t.Value, t.Effect) 576} 577 578// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations 579// and converts it to the []Taint type in api. 580func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]Taint, error) { 581 var taints []Taint 582 if len(annotations) > 0 && annotations[TaintsAnnotationKey] != "" { 583 err := json.Unmarshal([]byte(annotations[TaintsAnnotationKey]), &taints) 584 if err != nil { 585 return []Taint{}, err 586 } 587 } 588 return taints, nil 589} 590 591// SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls 592// and a slice of unsafe Sysctls. This is only a convenience wrapper around 593// SysctlsFromPodAnnotation. 594func SysctlsFromPodAnnotations(a map[string]string) ([]Sysctl, []Sysctl, error) { 595 safe, err := SysctlsFromPodAnnotation(a[SysctlsPodAnnotationKey]) 596 if err != nil { 597 return nil, nil, err 598 } 599 unsafe, err := SysctlsFromPodAnnotation(a[UnsafeSysctlsPodAnnotationKey]) 600 if err != nil { 601 return nil, nil, err 602 } 603 604 return safe, unsafe, nil 605} 606 607// SysctlsFromPodAnnotation parses an annotation value into a slice of Sysctls. 608func SysctlsFromPodAnnotation(annotation string) ([]Sysctl, error) { 609 if len(annotation) == 0 { 610 return nil, nil 611 } 612 613 kvs := strings.Split(annotation, ",") 614 sysctls := make([]Sysctl, len(kvs)) 615 for i, kv := range kvs { 616 cs := strings.Split(kv, "=") 617 if len(cs) != 2 || len(cs[0]) == 0 { 618 return nil, fmt.Errorf("sysctl %q not of the format sysctl_name=value", kv) 619 } 620 sysctls[i].Name = cs[0] 621 sysctls[i].Value = cs[1] 622 } 623 return sysctls, nil 624} 625 626// PodAnnotationsFromSysctls creates an annotation value for a slice of Sysctls. 627func PodAnnotationsFromSysctls(sysctls []Sysctl) string { 628 if len(sysctls) == 0 { 629 return "" 630 } 631 632 kvs := make([]string, len(sysctls)) 633 for i := range sysctls { 634 kvs[i] = fmt.Sprintf("%s=%s", sysctls[i].Name, sysctls[i].Value) 635 } 636 return strings.Join(kvs, ",") 637} 638 639// GetAffinityFromPodAnnotations gets the json serialized affinity data from Pod.Annotations 640// and converts it to the Affinity type in api. 641// TODO: remove when alpha support for affinity is removed 642func GetAffinityFromPodAnnotations(annotations map[string]string) (*Affinity, error) { 643 if len(annotations) > 0 && annotations[AffinityAnnotationKey] != "" { 644 var affinity Affinity 645 err := json.Unmarshal([]byte(annotations[AffinityAnnotationKey]), &affinity) 646 if err != nil { 647 return nil, err 648 } 649 return &affinity, nil 650 } 651 return nil, nil 652} 653 654// GetPersistentVolumeClass returns StorageClassName. 655func GetPersistentVolumeClass(volume *PersistentVolume) string { 656 // Use beta annotation first 657 if class, found := volume.Annotations[BetaStorageClassAnnotation]; found { 658 return class 659 } 660 661 return volume.Spec.StorageClassName 662} 663 664// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was 665// requested, it returns "". 666func GetPersistentVolumeClaimClass(claim *PersistentVolumeClaim) string { 667 // Use beta annotation first 668 if class, found := claim.Annotations[BetaStorageClassAnnotation]; found { 669 return class 670 } 671 672 if claim.Spec.StorageClassName != nil { 673 return *claim.Spec.StorageClassName 674 } 675 676 return "" 677} 678 679// PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field. 680func PersistentVolumeClaimHasClass(claim *PersistentVolumeClaim) bool { 681 // Use beta annotation first 682 if _, found := claim.Annotations[BetaStorageClassAnnotation]; found { 683 return true 684 } 685 686 if claim.Spec.StorageClassName != nil { 687 return true 688 } 689 690 return false 691} 692