diff --git a/pkg/apis/autoscaling/v2beta2/defaults.go b/pkg/apis/autoscaling/v2beta2/defaults.go index a3a061ce259..a688bfa6070 100644 --- a/pkg/apis/autoscaling/v2beta2/defaults.go +++ b/pkg/apis/autoscaling/v2beta2/defaults.go @@ -18,11 +18,52 @@ package v2beta2 import ( autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/apis/autoscaling" ) +var ( + // These constants repeats previous HPA behavior + scaleUpLimitPercent int32 = 100 + scaleUpLimitMinimumPods int32 = 4 + scaleUpPeriod int32 = 15 + scaleUpStabilizationSeconds int32 + maxPolicy = autoscalingv2beta2.MaxPolicySelect + defaultHPAScaleUpRules = autoscalingv2beta2.HPAScalingRules{ + StabilizationWindowSeconds: &scaleUpStabilizationSeconds, + SelectPolicy: &maxPolicy, + Policies: []autoscalingv2beta2.HPAScalingPolicy{ + { + Type: autoscalingv2beta2.PodsScalingPolicy, + Value: scaleUpLimitMinimumPods, + PeriodSeconds: scaleUpPeriod, + }, + { + Type: autoscalingv2beta2.PercentScalingPolicy, + Value: scaleUpLimitPercent, + PeriodSeconds: scaleUpPeriod, + }, + }, + } + scaleDownPeriod int32 = 15 + // Currently we can set the downscaleStabilizationWindow from the command line + // So we can not rewrite the command line option from here + scaleDownStabilizationSeconds *int32 = nil + scaleDownLimitPercent int32 = 100 + defaultHPAScaleDownRules = autoscalingv2beta2.HPAScalingRules{ + StabilizationWindowSeconds: scaleDownStabilizationSeconds, + SelectPolicy: &maxPolicy, + Policies: []autoscalingv2beta2.HPAScalingPolicy{ + { + Type: autoscalingv2beta2.PercentScalingPolicy, + Value: scaleDownLimitPercent, + PeriodSeconds: scaleDownPeriod, + }, + }, + } +) + func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } @@ -48,4 +89,46 @@ func SetDefaults_HorizontalPodAutoscaler(obj *autoscalingv2beta2.HorizontalPodAu }, } } + SetDefaults_HorizontalPodAutoscalerBehavior(obj) +} + +// SetDefaults_HorizontalPodAutoscalerBehavior fills the behavior if it is not null +func SetDefaults_HorizontalPodAutoscalerBehavior(obj *autoscalingv2beta2.HorizontalPodAutoscaler) { + // if behavior is specified, we should fill all the 'nil' values with the default ones + if obj.Spec.Behavior != nil { + obj.Spec.Behavior.ScaleUp = GenerateHPAScaleUpRules(obj.Spec.Behavior.ScaleUp) + obj.Spec.Behavior.ScaleDown = GenerateHPAScaleDownRules(obj.Spec.Behavior.ScaleDown) + } +} + +// GenerateHPAScaleUpRules returns a fully-initialized HPAScalingRules value +// We guarantee that no pointer in the structure will have the 'nil' value +func GenerateHPAScaleUpRules(scalingRules *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { + defaultScalingRules := defaultHPAScaleUpRules.DeepCopy() + return copyHPAScalingRules(scalingRules, defaultScalingRules) +} + +// GenerateHPAScaleDownRules returns a fully-initialized HPAScalingRules value +// We guarantee that no pointer in the structure will have the 'nil' value +// EXCEPT StabilizationWindowSeconds, for reasoning check the comment for defaultHPAScaleDownRules +func GenerateHPAScaleDownRules(scalingRules *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { + defaultScalingRules := defaultHPAScaleDownRules.DeepCopy() + return copyHPAScalingRules(scalingRules, defaultScalingRules) +} + +// copyHPAScalingRules copies all non-`nil` fields in HPA constraint structure +func copyHPAScalingRules(from, to *autoscalingv2beta2.HPAScalingRules) *autoscalingv2beta2.HPAScalingRules { + if from == nil { + return to + } + if from.SelectPolicy != nil { + to.SelectPolicy = from.SelectPolicy + } + if from.StabilizationWindowSeconds != nil { + to.StabilizationWindowSeconds = from.StabilizationWindowSeconds + } + if from.Policies != nil { + to.Policies = from.Policies + } + return to } diff --git a/pkg/apis/autoscaling/validation/validation.go b/pkg/apis/autoscaling/validation/validation.go index 97686ea410f..8e5b457b20f 100644 --- a/pkg/apis/autoscaling/validation/validation.go +++ b/pkg/apis/autoscaling/validation/validation.go @@ -30,6 +30,13 @@ import ( "k8s.io/kubernetes/pkg/features" ) +const ( + // MaxPeriodSeconds is the largest allowed scaling policy period (in seconds) + MaxPeriodSeconds int32 = 1800 + // MaxStabilizationWindowSeconds is the largest allowed stabilization window (in seconds) + MaxStabilizationWindowSeconds int32 = 3600 +) + // ValidateScale validates a Scale and returns an ErrorList with any errors. func ValidateScale(scale *autoscaling.Scale) field.ErrorList { allErrs := field.ErrorList{} @@ -65,6 +72,9 @@ func validateHorizontalPodAutoscalerSpec(autoscaler autoscaling.HorizontalPodAut if refErrs := validateMetrics(autoscaler.Metrics, fldPath.Child("metrics"), autoscaler.MinReplicas); len(refErrs) > 0 { allErrs = append(allErrs, refErrs...) } + if refErrs := validateBehavior(autoscaler.Behavior, fldPath.Child("behavior")); len(refErrs) > 0 { + allErrs = append(allErrs, refErrs...) + } return allErrs } @@ -165,6 +175,70 @@ func validateMetrics(metrics []autoscaling.MetricSpec, fldPath *field.Path, minR return allErrs } +func validateBehavior(behavior *autoscaling.HorizontalPodAutoscalerBehavior, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if behavior != nil { + if scaleUpErrs := validateScalingRules(behavior.ScaleUp, fldPath.Child("scaleUp")); len(scaleUpErrs) > 0 { + allErrs = append(allErrs, scaleUpErrs...) + } + if scaleDownErrs := validateScalingRules(behavior.ScaleDown, fldPath.Child("scaleDown")); len(scaleDownErrs) > 0 { + allErrs = append(allErrs, scaleDownErrs...) + } + } + return allErrs +} + +var validSelectPolicyTypes = sets.NewString(string(autoscaling.MaxPolicySelect), string(autoscaling.MinPolicySelect), string(autoscaling.DisabledPolicySelect)) +var validSelectPolicyTypesList = validSelectPolicyTypes.List() + +func validateScalingRules(rules *autoscaling.HPAScalingRules, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if rules != nil { + if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds, "must be greater than or equal to zero")) + } + if rules.StabilizationWindowSeconds != nil && *rules.StabilizationWindowSeconds > MaxStabilizationWindowSeconds { + allErrs = append(allErrs, field.Invalid(fldPath.Child("stabilizationWindowSeconds"), rules.StabilizationWindowSeconds, + fmt.Sprintf("must be less than or equal to %v", MaxStabilizationWindowSeconds))) + } + if rules.SelectPolicy != nil && !validSelectPolicyTypes.Has(string(*rules.SelectPolicy)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("selectPolicy"), rules.SelectPolicy, validSelectPolicyTypesList)) + } + policiesPath := fldPath.Child("policies") + if len(rules.Policies) == 0 { + allErrs = append(allErrs, field.Required(policiesPath, "must specify at least one Policy")) + } + for i, policy := range rules.Policies { + idxPath := policiesPath.Index(i) + if policyErrs := validateScalingPolicy(policy, idxPath); len(policyErrs) > 0 { + allErrs = append(allErrs, policyErrs...) + } + } + } + return allErrs +} + +var validPolicyTypes = sets.NewString(string(autoscaling.PodsScalingPolicy), string(autoscaling.PercentScalingPolicy)) +var validPolicyTypesList = validPolicyTypes.List() + +func validateScalingPolicy(policy autoscaling.HPAScalingPolicy, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if policy.Type != autoscaling.PodsScalingPolicy && policy.Type != autoscaling.PercentScalingPolicy { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), policy.Type, validPolicyTypesList)) + } + if policy.Value <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), policy.Value, "must be greater than zero")) + } + if policy.PeriodSeconds <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds, "must be greater than zero")) + } + if policy.PeriodSeconds > MaxPeriodSeconds { + allErrs = append(allErrs, field.Invalid(fldPath.Child("periodSeconds"), policy.PeriodSeconds, + fmt.Sprintf("must be less than or equal to %v", MaxPeriodSeconds))) + } + return allErrs +} + var validMetricSourceTypes = sets.NewString(string(autoscaling.ObjectMetricSourceType), string(autoscaling.PodsMetricSourceType), string(autoscaling.ResourceMetricSourceType), string(autoscaling.ExternalMetricSourceType)) var validMetricSourceTypesList = validMetricSourceTypes.List()