diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 4705e11fed5..faa7df7c0f4 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -18,6 +18,7 @@ package api import ( "crypto/md5" + "encoding/json" "fmt" "reflect" "strings" @@ -429,6 +430,14 @@ func NodeSelectorRequirementsAsSelector(nsm []NodeSelectorRequirement) (labels.S } const ( + // TolerationsAnnotationKey represents the key of tolerations data (json serialized) + // in the Annotations of a Pod. + TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations" + + // TaintsAnnotationKey represents the key of taints data (json serialized) + // in the Annotations of a Node. + TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints" + // SeccompPodAnnotationKey represents the key of a seccomp profile applied // to all containers of a pod. SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod" @@ -463,8 +472,26 @@ const ( // an object (e.g. secret, config map) before fetching it again from apiserver. // This annotation can be attached to node. ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" + + // AffinityAnnotationKey represents the key of affinity data (json serialized) + // in the Annotations of a Pod. + // TODO: remove when alpha support for affinity is removed + AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" ) +// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations +// and converts it to the []Toleration type in api. +func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]Toleration, error) { + var tolerations []Toleration + if len(annotations) > 0 && annotations[TolerationsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TolerationsAnnotationKey]), &tolerations) + if err != nil { + return tolerations, err + } + } + return tolerations, nil +} + // AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. // Returns true if something was updated, false otherwise. func AddOrUpdateTolerationInPod(pod *Pod, toleration *Toleration) (bool, error) { @@ -548,6 +575,19 @@ func (t *Taint) ToString() string { return fmt.Sprintf("%v=%v:%v", t.Key, t.Value, t.Effect) } +// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations +// and converts it to the []Taint type in api. +func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]Taint, error) { + var taints []Taint + if len(annotations) > 0 && annotations[TaintsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TaintsAnnotationKey]), &taints) + if err != nil { + return []Taint{}, err + } + } + return taints, nil +} + // SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls // and a slice of unsafe Sysctls. This is only a convenience wrapper around // SysctlsFromPodAnnotation. @@ -596,6 +636,21 @@ func PodAnnotationsFromSysctls(sysctls []Sysctl) string { return strings.Join(kvs, ",") } +// GetAffinityFromPodAnnotations gets the json serialized affinity data from Pod.Annotations +// and converts it to the Affinity type in api. +// TODO: remove when alpha support for affinity is removed +func GetAffinityFromPodAnnotations(annotations map[string]string) (*Affinity, error) { + if len(annotations) > 0 && annotations[AffinityAnnotationKey] != "" { + var affinity Affinity + err := json.Unmarshal([]byte(annotations[AffinityAnnotationKey]), &affinity) + if err != nil { + return nil, err + } + return &affinity, nil + } + return nil, nil +} + // GetPersistentVolumeClass returns StorageClassName. func GetPersistentVolumeClass(volume *PersistentVolume) string { // Use beta annotation first diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 1dd8845200b..1ca11e43043 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -105,6 +105,14 @@ func ValidateDNS1123Subdomain(value string, fldPath *field.Path) field.ErrorList func ValidatePodSpecificAnnotations(annotations map[string]string, spec *api.PodSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + if annotations[api.AffinityAnnotationKey] != "" { + allErrs = append(allErrs, ValidateAffinityInPodAnnotations(annotations, fldPath)...) + } + + if annotations[api.TolerationsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...) + } + // TODO: remove these after we EOL the annotations. if hostname, exists := annotations[utilpod.PodHostnameAnnotation]; exists { allErrs = append(allErrs, ValidateDNS1123Label(hostname, fldPath.Key(utilpod.PodHostnameAnnotation))...) @@ -136,6 +144,40 @@ func ValidatePodSpecificAnnotations(annotations map[string]string, spec *api.Pod return allErrs } +// ValidateTolerationsInPodAnnotations tests that the serialized tolerations in Pod.Annotations has valid data +func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + tolerations, err := api.GetTolerationsFromPodAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, api.TolerationsAnnotationKey, err.Error())) + return allErrs + } + + if len(tolerations) > 0 { + allErrs = append(allErrs, validateTolerations(tolerations, fldPath.Child(api.TolerationsAnnotationKey))...) + } + + return allErrs +} + +// ValidateAffinityInPodAnnotations tests that the serialized Affinity in Pod.Annotations has valid data +func ValidateAffinityInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + affinity, err := api.GetAffinityFromPodAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, api.AffinityAnnotationKey, err.Error())) + return allErrs + } + if affinity == nil { + return allErrs + } + + allErrs = append(allErrs, validateAffinity(affinity, fldPath.Child("affinity"))...) + return allErrs +} + func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *api.Pod, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} newAnnotations := newPod.Annotations @@ -2907,6 +2949,23 @@ func ValidateReadOnlyPersistentDisks(volumes []api.Volume, fldPath *field.Path) return allErrs } +// ValidateTaintsInNodeAnnotations tests that the serialized taints in Node.Annotations has valid data +func ValidateTaintsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + taints, err := api.GetTaintsFromNodeAnnotations(annotations) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, api.TaintsAnnotationKey, err.Error())) + return allErrs + } + + if len(taints) > 0 { + allErrs = append(allErrs, validateNodeTaints(taints, fldPath.Child(api.TaintsAnnotationKey))...) + } + + return allErrs +} + // validateNodeTaints tests if given taints have valid data. func validateNodeTaints(taints []api.Taint, fldPath *field.Path) field.ErrorList { allErrors := field.ErrorList{} @@ -2943,6 +3002,11 @@ func validateNodeTaints(taints []api.Taint, fldPath *field.Path) field.ErrorList func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + + if annotations[api.TaintsAnnotationKey] != "" { + allErrs = append(allErrs, ValidateTaintsInNodeAnnotations(annotations, fldPath)...) + } + if annotations[api.PreferAvoidPodsAnnotationKey] != "" { allErrs = append(allErrs, ValidateAvoidPodsInNodeAnnotations(annotations, fldPath)...) } diff --git a/staging/src/k8s.io/client-go/pkg/api/helpers.go b/staging/src/k8s.io/client-go/pkg/api/helpers.go index 4705e11fed5..faa7df7c0f4 100644 --- a/staging/src/k8s.io/client-go/pkg/api/helpers.go +++ b/staging/src/k8s.io/client-go/pkg/api/helpers.go @@ -18,6 +18,7 @@ package api import ( "crypto/md5" + "encoding/json" "fmt" "reflect" "strings" @@ -429,6 +430,14 @@ func NodeSelectorRequirementsAsSelector(nsm []NodeSelectorRequirement) (labels.S } const ( + // TolerationsAnnotationKey represents the key of tolerations data (json serialized) + // in the Annotations of a Pod. + TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations" + + // TaintsAnnotationKey represents the key of taints data (json serialized) + // in the Annotations of a Node. + TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints" + // SeccompPodAnnotationKey represents the key of a seccomp profile applied // to all containers of a pod. SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod" @@ -463,8 +472,26 @@ const ( // an object (e.g. secret, config map) before fetching it again from apiserver. // This annotation can be attached to node. ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" + + // AffinityAnnotationKey represents the key of affinity data (json serialized) + // in the Annotations of a Pod. + // TODO: remove when alpha support for affinity is removed + AffinityAnnotationKey string = "scheduler.alpha.kubernetes.io/affinity" ) +// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations +// and converts it to the []Toleration type in api. +func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]Toleration, error) { + var tolerations []Toleration + if len(annotations) > 0 && annotations[TolerationsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TolerationsAnnotationKey]), &tolerations) + if err != nil { + return tolerations, err + } + } + return tolerations, nil +} + // AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. // Returns true if something was updated, false otherwise. func AddOrUpdateTolerationInPod(pod *Pod, toleration *Toleration) (bool, error) { @@ -548,6 +575,19 @@ func (t *Taint) ToString() string { return fmt.Sprintf("%v=%v:%v", t.Key, t.Value, t.Effect) } +// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations +// and converts it to the []Taint type in api. +func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]Taint, error) { + var taints []Taint + if len(annotations) > 0 && annotations[TaintsAnnotationKey] != "" { + err := json.Unmarshal([]byte(annotations[TaintsAnnotationKey]), &taints) + if err != nil { + return []Taint{}, err + } + } + return taints, nil +} + // SysctlsFromPodAnnotations parses the sysctl annotations into a slice of safe Sysctls // and a slice of unsafe Sysctls. This is only a convenience wrapper around // SysctlsFromPodAnnotation. @@ -596,6 +636,21 @@ func PodAnnotationsFromSysctls(sysctls []Sysctl) string { return strings.Join(kvs, ",") } +// GetAffinityFromPodAnnotations gets the json serialized affinity data from Pod.Annotations +// and converts it to the Affinity type in api. +// TODO: remove when alpha support for affinity is removed +func GetAffinityFromPodAnnotations(annotations map[string]string) (*Affinity, error) { + if len(annotations) > 0 && annotations[AffinityAnnotationKey] != "" { + var affinity Affinity + err := json.Unmarshal([]byte(annotations[AffinityAnnotationKey]), &affinity) + if err != nil { + return nil, err + } + return &affinity, nil + } + return nil, nil +} + // GetPersistentVolumeClass returns StorageClassName. func GetPersistentVolumeClass(volume *PersistentVolume) string { // Use beta annotation first