| /* |
| Copyright 2016 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package validation |
| |
| import ( |
| "strings" |
| |
| apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" |
| pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| "k8s.io/kubernetes/pkg/apis/autoscaling" |
| apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" |
| ) |
| |
| func ValidateScale(scale *autoscaling.Scale) field.ErrorList { |
| allErrs := field.ErrorList{} |
| allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...) |
| |
| if scale.Spec.Replicas < 0 { |
| allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "replicas"), scale.Spec.Replicas, "must be greater than or equal to 0")) |
| } |
| |
| return allErrs |
| } |
| |
| // ValidateHorizontalPodAutoscaler can be used to check whether the given autoscaler name is valid. |
| // Prefix indicates this name will be used as part of generation, in which case trailing dashes are allowed. |
| var ValidateHorizontalPodAutoscalerName = apivalidation.ValidateReplicationControllerName |
| |
| func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.HorizontalPodAutoscalerSpec, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < 1 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), *autoscaler.MinReplicas, "must be greater than 0")) |
| } |
| if autoscaler.MaxReplicas < 1 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than 0")) |
| } |
| if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, "must be greater than or equal to `minReplicas`")) |
| } |
| if refErrs := ValidateCrossVersionObjectReference(autoscaler.ScaleTargetRef, fldPath.Child("scaleTargetRef")); len(refErrs) > 0 { |
| allErrs = append(allErrs, refErrs...) |
| } |
| if refErrs := validateMetrics(autoscaler.Metrics, fldPath.Child("metrics")); len(refErrs) > 0 { |
| allErrs = append(allErrs, refErrs...) |
| } |
| return allErrs |
| } |
| |
| func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectReference, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| if len(ref.Kind) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) |
| } else { |
| for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg)) |
| } |
| } |
| |
| if len(ref.Name) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) |
| } else { |
| for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg)) |
| } |
| } |
| |
| return allErrs |
| } |
| |
| func ValidateHorizontalPodAutoscaler(autoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList { |
| allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata")) |
| allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"))...) |
| return allErrs |
| } |
| |
| func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList { |
| allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata")) |
| allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, field.NewPath("spec"))...) |
| return allErrs |
| } |
| |
| func ValidateHorizontalPodAutoscalerStatusUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList { |
| allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata")) |
| status := newAutoscaler.Status |
| allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), field.NewPath("status", "currentReplicas"))...) |
| allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredReplicas), field.NewPath("status", "desiredReplicasa"))...) |
| return allErrs |
| } |
| |
| func validateMetrics(metrics []autoscaling.MetricSpec, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| for i, metricSpec := range metrics { |
| idxPath := fldPath.Index(i) |
| if targetErrs := validateMetricSpec(metricSpec, idxPath); len(targetErrs) > 0 { |
| allErrs = append(allErrs, targetErrs...) |
| } |
| } |
| |
| return allErrs |
| } |
| |
| var validMetricSourceTypes = sets.NewString(string(autoscaling.ObjectMetricSourceType), string(autoscaling.PodsMetricSourceType), string(autoscaling.ResourceMetricSourceType), string(autoscaling.ExternalMetricSourceType)) |
| var validMetricSourceTypesList = validMetricSourceTypes.List() |
| |
| func validateMetricSpec(spec autoscaling.MetricSpec, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if len(string(spec.Type)) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric source type")) |
| } |
| |
| if !validMetricSourceTypes.Has(string(spec.Type)) { |
| allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, validMetricSourceTypesList)) |
| } |
| |
| typesPresent := sets.NewString() |
| if spec.Object != nil { |
| typesPresent.Insert("object") |
| if typesPresent.Len() == 1 { |
| allErrs = append(allErrs, validateObjectSource(spec.Object, fldPath.Child("object"))...) |
| } |
| } |
| |
| if spec.External != nil { |
| typesPresent.Insert("external") |
| if typesPresent.Len() == 1 { |
| allErrs = append(allErrs, validateExternalSource(spec.External, fldPath.Child("external"))...) |
| } |
| } |
| |
| if spec.Pods != nil { |
| typesPresent.Insert("pods") |
| if typesPresent.Len() == 1 { |
| allErrs = append(allErrs, validatePodsSource(spec.Pods, fldPath.Child("pods"))...) |
| } |
| } |
| |
| if spec.Resource != nil { |
| typesPresent.Insert("resource") |
| if typesPresent.Len() == 1 { |
| allErrs = append(allErrs, validateResourceSource(spec.Resource, fldPath.Child("resource"))...) |
| } |
| } |
| |
| expectedField := strings.ToLower(string(spec.Type)) |
| |
| if !typesPresent.Has(expectedField) { |
| allErrs = append(allErrs, field.Required(fldPath.Child(expectedField), "must populate information for the given metric source")) |
| } |
| |
| if typesPresent.Len() != 1 { |
| typesPresent.Delete(expectedField) |
| for typ := range typesPresent { |
| allErrs = append(allErrs, field.Forbidden(fldPath.Child(typ), "must populate the given metric source only")) |
| } |
| } |
| |
| return allErrs |
| } |
| |
| func validateObjectSource(src *autoscaling.ObjectMetricSource, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| allErrs = append(allErrs, ValidateCrossVersionObjectReference(src.DescribedObject, fldPath.Child("describedObject"))...) |
| allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) |
| if &src.Target == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target"), "must specify a metric target")) |
| } else { |
| allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) |
| } |
| |
| if src.Target.Value == nil && src.Target.AverageValue == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value or averageValue")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateExternalSource(src *autoscaling.ExternalMetricSource, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) |
| if &src.Target == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target"), "must specify a metric target")) |
| } else { |
| allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) |
| } |
| |
| if src.Target.Value == nil && src.Target.AverageValue == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must set either a target value for metric or a per-pod target")) |
| } |
| |
| if src.Target.Value != nil && src.Target.AverageValue != nil { |
| allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("value"), "may not set both a target value for metric and a per-pod target")) |
| } |
| |
| return allErrs |
| } |
| |
| func validatePodsSource(src *autoscaling.PodsMetricSource, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| allErrs = append(allErrs, validateMetricIdentifier(src.Metric, fldPath.Child("metric"))...) |
| if &src.Target == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target"), "must specify a metric target")) |
| } else { |
| allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) |
| } |
| |
| if src.Target.AverageValue == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageValue"), "must specify a positive target averageValue")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateResourceSource(src *autoscaling.ResourceMetricSource, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if len(src.Name) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a resource name")) |
| } |
| if &src.Target == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target"), "must specify a metric target")) |
| } else { |
| allErrs = append(allErrs, validateMetricTarget(src.Target, fldPath.Child("target"))...) |
| } |
| |
| if src.Target.AverageUtilization == nil && src.Target.AverageValue == nil { |
| allErrs = append(allErrs, field.Required(fldPath.Child("target").Child("averageUtilization"), "must set either a target raw value or a target utilization")) |
| } |
| |
| if src.Target.AverageUtilization != nil && src.Target.AverageValue != nil { |
| allErrs = append(allErrs, field.Forbidden(fldPath.Child("target").Child("averageValue"), "may not set both a target raw value and a target utilization")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateMetricTarget(mt autoscaling.MetricTarget, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if len(mt.Type) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a metric target type")) |
| } |
| |
| if mt.Type != autoscaling.UtilizationMetricType && |
| mt.Type != autoscaling.ValueMetricType && |
| mt.Type != autoscaling.AverageValueMetricType { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), mt.Type, "must be either Utilization, Value, or AverageValue")) |
| } |
| |
| if mt.Value != nil && mt.Value.Sign() != 1 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), mt.Value, "must be positive")) |
| } |
| |
| if mt.AverageValue != nil && mt.AverageValue.Sign() != 1 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("averageValue"), mt.AverageValue, "must be positive")) |
| } |
| |
| if mt.AverageUtilization != nil && *mt.AverageUtilization < 1 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("averageUtilization"), mt.AverageUtilization, "must be greater than 0")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateMetricIdentifier(id autoscaling.MetricIdentifier, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if len(id.Name) == 0 { |
| allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a metric name")) |
| } else { |
| for _, msg := range pathvalidation.IsValidPathSegmentName(id.Name) { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), id.Name, msg)) |
| } |
| } |
| return allErrs |
| } |