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