Merge pull request #74525 from gliush/configurable-hpa-limits

Configurable HorizontalPodAutoscaler
This commit is contained in:
Kubernetes Prow Robot 2019-12-11 03:18:05 -08:00 committed by GitHub
commit 928817a26a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 3852 additions and 244 deletions

View File

@ -83,6 +83,7 @@ API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta1,HorizontalP
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta1,HorizontalPodAutoscalerSpec,Metrics
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta1,HorizontalPodAutoscalerStatus,Conditions
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta1,HorizontalPodAutoscalerStatus,CurrentMetrics
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta2,HPAScalingRules,Policies
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta2,HorizontalPodAutoscalerList,Items
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta2,HorizontalPodAutoscalerSpec,Metrics
API rule violation: list_type_missing,k8s.io/api/autoscaling/v2beta2,HorizontalPodAutoscalerStatus,Conditions

View File

@ -5177,6 +5177,53 @@
],
"type": "object"
},
"io.k8s.api.autoscaling.v2beta2.HPAScalingPolicy": {
"description": "HPAScalingPolicy is a single policy which must hold true for a specified past interval.",
"properties": {
"periodSeconds": {
"description": "PeriodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).",
"format": "int32",
"type": "integer"
},
"type": {
"description": "Type is used to specify the scaling policy.",
"type": "string"
},
"value": {
"description": "Value contains the amount of change which is permitted by the policy. It must be greater than zero",
"format": "int32",
"type": "integer"
}
},
"required": [
"type",
"value",
"periodSeconds"
],
"type": "object"
},
"io.k8s.api.autoscaling.v2beta2.HPAScalingRules": {
"description": "HPAScalingRules configures the scaling behavior for one direction. These Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.",
"properties": {
"policies": {
"description": "policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid",
"items": {
"$ref": "#/definitions/io.k8s.api.autoscaling.v2beta2.HPAScalingPolicy"
},
"type": "array"
},
"selectPolicy": {
"description": "selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used.",
"type": "string"
},
"stabilizationWindowSeconds": {
"description": "StabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).",
"format": "int32",
"type": "integer"
}
},
"type": "object"
},
"io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscaler": {
"description": "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.",
"properties": {
@ -5210,6 +5257,20 @@
}
]
},
"io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerBehavior": {
"description": "HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively).",
"properties": {
"scaleDown": {
"$ref": "#/definitions/io.k8s.api.autoscaling.v2beta2.HPAScalingRules",
"description": "scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used)."
},
"scaleUp": {
"$ref": "#/definitions/io.k8s.api.autoscaling.v2beta2.HPAScalingRules",
"description": "scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of:\n * increase no more than 4 pods per 60 seconds\n * double the number of pods per 60 seconds\nNo stabilization is used."
}
},
"type": "object"
},
"io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerCondition": {
"description": "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.",
"properties": {
@ -5278,6 +5339,10 @@
"io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerSpec": {
"description": "HorizontalPodAutoscalerSpec describes the desired functionality of the HorizontalPodAutoscaler.",
"properties": {
"behavior": {
"$ref": "#/definitions/io.k8s.api.autoscaling.v2beta2.HorizontalPodAutoscalerBehavior",
"description": "behavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). If not set, the default HPAScalingRules for scale up and scale down are used."
},
"maxReplicas": {
"description": "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.",
"format": "int32",

View File

@ -79,6 +79,8 @@ func TestDefaulting(t *testing.T) {
{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {},
{Group: "autoscaling", Version: "v2beta1", Kind: "HorizontalPodAutoscaler"}: {},
{Group: "autoscaling", Version: "v2beta1", Kind: "HorizontalPodAutoscalerList"}: {},
{Group: "autoscaling", Version: "v2beta2", Kind: "HorizontalPodAutoscaler"}: {},
{Group: "autoscaling", Version: "v2beta2", Kind: "HorizontalPodAutoscalerList"}: {},
{Group: "batch", Version: "v1", Kind: "Job"}: {},
{Group: "batch", Version: "v1", Kind: "JobList"}: {},
{Group: "batch", Version: "v1beta1", Kind: "CronJob"}: {},

View File

@ -32,3 +32,7 @@ const HorizontalPodAutoscalerConditionsAnnotation = "autoscaling.alpha.kubernete
// metrics are present. This is here because it's used by both the v2beta1 defaulting
// logic, and the pseudo-defaulting done in v1 conversion.
const DefaultCPUUtilization = 80
// BehaviorSpecsAnnotation is the annotation which holds the HPA constraints specs
// when converting the `Behavior` field from autoscaling/v2beta2
const BehaviorSpecsAnnotation = "autoscaling.alpha.kubernetes.io/behavior"

View File

@ -90,6 +90,43 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
},
},
}
stabilizationWindow := int32(c.RandUint64())
maxPolicy := autoscaling.MaxPolicySelect
minPolicy := autoscaling.MinPolicySelect
s.Behavior = &autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindow,
SelectPolicy: &maxPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: int32(c.RandUint64()),
PeriodSeconds: int32(c.RandUint64()),
},
{
Type: autoscaling.PercentScalingPolicy,
Value: int32(c.RandUint64()),
PeriodSeconds: int32(c.RandUint64()),
},
},
},
ScaleDown: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: &stabilizationWindow,
SelectPolicy: &minPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: int32(c.RandUint64()),
PeriodSeconds: int32(c.RandUint64()),
},
{
Type: autoscaling.PercentScalingPolicy,
Value: int32(c.RandUint64()),
PeriodSeconds: int32(c.RandUint64()),
},
},
},
}
},
func(s *autoscaling.HorizontalPodAutoscalerStatus, c fuzz.Continue) {
c.FuzzNoCustom(s) // fuzz self without calling this function again

View File

@ -95,6 +95,90 @@ type HorizontalPodAutoscalerSpec struct {
// more information about how each type of metric must respond.
// +optional
Metrics []MetricSpec
// behavior configures the scaling behavior of the target
// in both Up and Down directions (scaleUp and scaleDown fields respectively).
// If not set, the default HPAScalingRules for scale up and scale down are used.
// +optional
Behavior *HorizontalPodAutoscalerBehavior
}
// HorizontalPodAutoscalerBehavior configures a scaling behavior for Up and Down direction
// (scaleUp and scaleDown fields respectively).
type HorizontalPodAutoscalerBehavior struct {
// scaleUp is scaling policy for scaling Up.
// If not set, the default value is the higher of:
// * increase no more than 4 pods per 60 seconds
// * double the number of pods per 60 seconds
// No stabilization is used.
// +optional
ScaleUp *HPAScalingRules
// scaleDown is scaling policy for scaling Down.
// If not set, the default value is to allow to scale down to minReplicas pods, with a
// 300 second stabilization window (i.e., the highest recommendation for
// the last 300sec is used).
// +optional
ScaleDown *HPAScalingRules
}
// ScalingPolicySelect is used to specify which policy should be used while scaling in a certain direction
type ScalingPolicySelect string
const (
// MaxPolicySelect selects the policy with the highest possible change.
MaxPolicySelect ScalingPolicySelect = "Max"
// MinPolicySelect selects the policy with the lowest possible change.
MinPolicySelect ScalingPolicySelect = "Min"
// DisabledPolicySelect disables the scaling in this direction.
DisabledPolicySelect ScalingPolicySelect = "Disabled"
)
// HPAScalingRules configures the scaling behavior for one direction.
// These Rules are applied after calculating DesiredReplicas from metrics for the HPA.
// They can limit the scaling velocity by specifying scaling policies.
// They can prevent flapping by specifying the stabilization window, so that the
// number of replicas is not set instantly, instead, the safest value from the stabilization
// window is chosen.
type HPAScalingRules struct {
// StabilizationWindowSeconds is the number of seconds for which past recommendations should be
// considered while scaling up or scaling down.
// StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour).
// If not set, use the default values:
// - For scale up: 0 (i.e. no stabilization is done).
// - For scale down: 300 (i.e. the stabilization window is 300 seconds long).
// +optional
StabilizationWindowSeconds *int32
// selectPolicy is used to specify which policy should be used.
// If not set, the default value MaxPolicySelect is used.
// +optional
SelectPolicy *ScalingPolicySelect
// policies is a list of potential scaling polices which can used during scaling.
// At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid
// +optional
Policies []HPAScalingPolicy
}
// HPAScalingPolicyType is the type of the policy which could be used while making scaling decisions.
type HPAScalingPolicyType string
const (
// PodsScalingPolicy is a policy used to specify a change in absolute number of pods.
PodsScalingPolicy HPAScalingPolicyType = "Pods"
// PercentScalingPolicy is a policy used to specify a relative amount of change with respect to
// the current number of pods.
PercentScalingPolicy HPAScalingPolicyType = "Percent"
)
// HPAScalingPolicy is a single policy which must hold true for a specified past interval.
type HPAScalingPolicy struct {
// Type is used to specify the scaling policy.
Type HPAScalingPolicyType
// Value contains the amount of change which is permitted by the policy.
// It must be greater than zero
Value int32
// PeriodSeconds specifies the window of time for which the policy should hold true.
// PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).
PeriodSeconds int32
}
// MetricSourceType indicates the type of metric.

View File

@ -289,9 +289,9 @@ func Convert_autoscaling_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(i
}
}
if len(otherMetrics) > 0 || len(in.Status.CurrentMetrics) > 0 || len(currentConditions) > 0 {
if len(otherMetrics) > 0 || len(in.Status.CurrentMetrics) > 0 || len(currentConditions) > 0 || in.Spec.Behavior != nil {
old := out.Annotations
out.Annotations = make(map[string]string, len(old)+3)
out.Annotations = make(map[string]string, len(old)+4)
for k, v := range old {
out.Annotations[k] = v
}
@ -313,6 +313,14 @@ func Convert_autoscaling_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(i
out.Annotations[autoscaling.MetricStatusesAnnotation] = string(currentMetricsEnc)
}
if in.Spec.Behavior != nil {
behaviorEnc, err := json.Marshal(in.Spec.Behavior)
if err != nil {
return err
}
out.Annotations[autoscaling.BehaviorSpecsAnnotation] = string(behaviorEnc)
}
if len(in.Status.Conditions) > 0 {
currentConditionsEnc, err := json.Marshal(currentConditions)
if err != nil {
@ -349,6 +357,15 @@ func Convert_v1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(i
delete(out.Annotations, autoscaling.MetricSpecsAnnotation)
}
if behaviorEnc, hasConstraints := out.Annotations[autoscaling.BehaviorSpecsAnnotation]; hasConstraints {
var behavior autoscaling.HorizontalPodAutoscalerBehavior
if err := json.Unmarshal([]byte(behaviorEnc), &behavior); err != nil {
return err
}
out.Spec.Behavior = &behavior
delete(out.Annotations, autoscaling.BehaviorSpecsAnnotation)
}
if currentMetricsEnc, hasCurrentMetrics := out.Annotations[autoscaling.MetricStatusesAnnotation]; hasCurrentMetrics {
// ignore any existing status values -- the ones here have more information
var currentMetrics []autoscalingv1.MetricStatus

View File

@ -403,6 +403,7 @@ func autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAuto
out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas))
out.MaxReplicas = in.MaxReplicas
// WARNING: in.Metrics requires manual conversion: does not exist in peer-type
// WARNING: in.Behavior requires manual conversion: does not exist in peer-type
return nil
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package v2beta1
import (
"encoding/json"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
v1 "k8s.io/api/core/v1"
@ -257,3 +259,45 @@ func Convert_v2beta1_PodsMetricStatus_To_autoscaling_PodsMetricStatus(in *autosc
}
return nil
}
func Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *autoscalingv2beta1.HorizontalPodAutoscaler, s conversion.Scope) error {
if err := autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in, out, s); err != nil {
return err
}
if in.Spec.Behavior != nil {
old := out.Annotations
out.Annotations = make(map[string]string, len(old)+1)
for k, v := range old {
out.Annotations[k] = v
}
behaviorEnc, err := json.Marshal(in.Spec.Behavior)
if err != nil {
return err
}
// Even if the annotation for behavior exists, we will just overwrite it
out.Annotations[autoscaling.BehaviorSpecsAnnotation] = string(behaviorEnc)
}
return nil
}
func Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *autoscalingv2beta1.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error {
if err := autoConvert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in, out, s); err != nil {
return err
}
if behaviorEnc, hasBehaviors := out.Annotations[autoscaling.BehaviorSpecsAnnotation]; hasBehaviors {
var behavior autoscaling.HorizontalPodAutoscalerBehavior
if err := json.Unmarshal([]byte(behaviorEnc), &behavior); err != nil {
return err
}
out.Spec.Behavior = &behavior
delete(out.Annotations, autoscaling.BehaviorSpecsAnnotation)
}
return nil
}
func Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *autoscalingv2beta1.HorizontalPodAutoscalerSpec, s conversion.Scope) error {
return autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in, out, s)
}

View File

@ -49,16 +49,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.HorizontalPodAutoscaler)(nil), (*autoscaling.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(a.(*v2beta1.HorizontalPodAutoscaler), b.(*autoscaling.HorizontalPodAutoscaler), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscaler)(nil), (*v2beta1.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(a.(*autoscaling.HorizontalPodAutoscaler), b.(*v2beta1.HorizontalPodAutoscaler), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.HorizontalPodAutoscalerCondition)(nil), (*autoscaling.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(a.(*v2beta1.HorizontalPodAutoscalerCondition), b.(*autoscaling.HorizontalPodAutoscalerCondition), scope)
}); err != nil {
@ -84,11 +74,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerSpec)(nil), (*v2beta1.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(a.(*autoscaling.HorizontalPodAutoscalerSpec), b.(*v2beta1.HorizontalPodAutoscalerSpec), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta1.HorizontalPodAutoscalerStatus)(nil), (*autoscaling.HorizontalPodAutoscalerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(a.(*v2beta1.HorizontalPodAutoscalerStatus), b.(*autoscaling.HorizontalPodAutoscalerStatus), scope)
}); err != nil {
@ -129,6 +114,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*autoscaling.HorizontalPodAutoscalerSpec)(nil), (*v2beta1.HorizontalPodAutoscalerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(a.(*autoscaling.HorizontalPodAutoscalerSpec), b.(*v2beta1.HorizontalPodAutoscalerSpec), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*autoscaling.HorizontalPodAutoscaler)(nil), (*v2beta1.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(a.(*autoscaling.HorizontalPodAutoscaler), b.(*v2beta1.HorizontalPodAutoscaler), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*autoscaling.MetricTarget)(nil), (*v2beta1.CrossVersionObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_MetricTarget_To_v2beta1_CrossVersionObjectReference(a.(*autoscaling.MetricTarget), b.(*v2beta1.CrossVersionObjectReference), scope)
}); err != nil {
@ -179,6 +174,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddConversionFunc((*v2beta1.HorizontalPodAutoscaler)(nil), (*autoscaling.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(a.(*v2beta1.HorizontalPodAutoscaler), b.(*autoscaling.HorizontalPodAutoscaler), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*v2beta1.ObjectMetricSource)(nil), (*autoscaling.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(a.(*v2beta1.ObjectMetricSource), b.(*autoscaling.ObjectMetricSource), scope)
}); err != nil {
@ -275,11 +275,6 @@ func autoConvert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAut
return nil
}
// Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler is an autogenerated conversion function.
func Convert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *v2beta1.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error {
return autoConvert_v2beta1_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in, out, s)
}
func autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *v2beta1.HorizontalPodAutoscaler, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil {
@ -291,11 +286,6 @@ func autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAut
return nil
}
// Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler is an autogenerated conversion function.
func Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in *autoscaling.HorizontalPodAutoscaler, out *v2beta1.HorizontalPodAutoscaler, s conversion.Scope) error {
return autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta1_HorizontalPodAutoscaler(in, out, s)
}
func autoConvert_v2beta1_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *v2beta1.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error {
out.Type = autoscaling.HorizontalPodAutoscalerConditionType(in.Type)
out.Status = autoscaling.ConditionStatus(in.Status)
@ -408,14 +398,10 @@ func autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPo
} else {
out.Metrics = nil
}
// WARNING: in.Behavior requires manual conversion: does not exist in peer-type
return nil
}
// Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec is an autogenerated conversion function.
func Convert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in *autoscaling.HorizontalPodAutoscalerSpec, out *v2beta1.HorizontalPodAutoscalerSpec, s conversion.Scope) error {
return autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta1_HorizontalPodAutoscalerSpec(in, out, s)
}
func autoConvert_v2beta1_HorizontalPodAutoscalerStatus_To_autoscaling_HorizontalPodAutoscalerStatus(in *v2beta1.HorizontalPodAutoscalerStatus, out *autoscaling.HorizontalPodAutoscalerStatus, s conversion.Scope) error {
out.ObservedGeneration = (*int64)(unsafe.Pointer(in.ObservedGeneration))
out.LastScaleTime = (*metav1.Time)(unsafe.Pointer(in.LastScaleTime))

View File

@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
@ -37,3 +37,14 @@ filegroup(
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["defaults_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/autoscaling/v2beta2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

View File

@ -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
}

View File

@ -0,0 +1,231 @@
/*
Copyright 2017 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 v2beta2_test
import (
"testing"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
. "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2"
utilpointer "k8s.io/utils/pointer"
"github.com/stretchr/testify/assert"
)
func TestGenerateScaleDownRules(t *testing.T) {
type TestCase struct {
rateDownPods int32
rateDownPodsPeriodSeconds int32
rateDownPercent int32
rateDownPercentPeriodSeconds int32
stabilizationSeconds *int32
selectPolicy *autoscalingv2.ScalingPolicySelect
expectedPolicies []autoscalingv2.HPAScalingPolicy
expectedStabilization *int32
expectedSelectPolicy string
annotation string
}
maxPolicy := autoscalingv2.MaxPolicySelect
minPolicy := autoscalingv2.MinPolicySelect
tests := []TestCase{
{
annotation: "Default values",
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PercentScalingPolicy, Value: 100, PeriodSeconds: 15},
},
expectedStabilization: nil,
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "All parameters are specified",
rateDownPods: 1,
rateDownPodsPeriodSeconds: 2,
rateDownPercent: 3,
rateDownPercentPeriodSeconds: 4,
stabilizationSeconds: utilpointer.Int32Ptr(25),
selectPolicy: &maxPolicy,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2},
{Type: autoscalingv2.PercentScalingPolicy, Value: 3, PeriodSeconds: 4},
},
expectedStabilization: utilpointer.Int32Ptr(25),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "Percent policy is specified",
rateDownPercent: 1,
rateDownPercentPeriodSeconds: 2,
selectPolicy: &minPolicy,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PercentScalingPolicy, Value: 1, PeriodSeconds: 2},
},
expectedStabilization: nil,
expectedSelectPolicy: string(autoscalingv2.MinPolicySelect),
},
{
annotation: "Pods policy is specified",
rateDownPods: 3,
rateDownPodsPeriodSeconds: 4,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 3, PeriodSeconds: 4},
},
expectedStabilization: nil,
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
}
for _, tc := range tests {
t.Run(tc.annotation, func(t *testing.T) {
scaleDownRules := &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: tc.stabilizationSeconds,
SelectPolicy: tc.selectPolicy,
}
if tc.rateDownPods != 0 || tc.rateDownPodsPeriodSeconds != 0 {
scaleDownRules.Policies = append(scaleDownRules.Policies, autoscalingv2.HPAScalingPolicy{
Type: autoscalingv2.PodsScalingPolicy, Value: tc.rateDownPods, PeriodSeconds: tc.rateDownPodsPeriodSeconds,
})
}
if tc.rateDownPercent != 0 || tc.rateDownPercentPeriodSeconds != 0 {
scaleDownRules.Policies = append(scaleDownRules.Policies, autoscalingv2.HPAScalingPolicy{
Type: autoscalingv2.PercentScalingPolicy, Value: tc.rateDownPercent, PeriodSeconds: tc.rateDownPercentPeriodSeconds,
})
}
down := GenerateHPAScaleDownRules(scaleDownRules)
assert.EqualValues(t, tc.expectedPolicies, down.Policies)
if tc.expectedStabilization != nil {
assert.Equal(t, *tc.expectedStabilization, *down.StabilizationWindowSeconds)
} else {
assert.Equal(t, tc.expectedStabilization, down.StabilizationWindowSeconds)
}
assert.Equal(t, autoscalingv2.ScalingPolicySelect(tc.expectedSelectPolicy), *down.SelectPolicy)
})
}
}
func TestGenerateScaleUpRules(t *testing.T) {
type TestCase struct {
rateUpPods int32
rateUpPodsPeriodSeconds int32
rateUpPercent int32
rateUpPercentPeriodSeconds int32
stabilizationSeconds *int32
selectPolicy *autoscalingv2.ScalingPolicySelect
expectedPolicies []autoscalingv2.HPAScalingPolicy
expectedStabilization *int32
expectedSelectPolicy string
annotation string
}
maxPolicy := autoscalingv2.MaxPolicySelect
minPolicy := autoscalingv2.MinPolicySelect
tests := []TestCase{
{
annotation: "Default values",
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 4, PeriodSeconds: 15},
{Type: autoscalingv2.PercentScalingPolicy, Value: 100, PeriodSeconds: 15},
},
expectedStabilization: utilpointer.Int32Ptr(0),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "All parameters are specified",
rateUpPods: 1,
rateUpPodsPeriodSeconds: 2,
rateUpPercent: 3,
rateUpPercentPeriodSeconds: 4,
stabilizationSeconds: utilpointer.Int32Ptr(25),
selectPolicy: &maxPolicy,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2},
{Type: autoscalingv2.PercentScalingPolicy, Value: 3, PeriodSeconds: 4},
},
expectedStabilization: utilpointer.Int32Ptr(25),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "Pod policy is specified",
rateUpPods: 1,
rateUpPodsPeriodSeconds: 2,
selectPolicy: &minPolicy,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 1, PeriodSeconds: 2},
},
expectedStabilization: utilpointer.Int32Ptr(0),
expectedSelectPolicy: string(autoscalingv2.MinPolicySelect),
},
{
annotation: "Percent policy is specified",
rateUpPercent: 7,
rateUpPercentPeriodSeconds: 10,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PercentScalingPolicy, Value: 7, PeriodSeconds: 10},
},
expectedStabilization: utilpointer.Int32Ptr(0),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "Pod policy and stabilization window are specified",
rateUpPodsPeriodSeconds: 2,
stabilizationSeconds: utilpointer.Int32Ptr(25),
rateUpPods: 4,
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PodsScalingPolicy, Value: 4, PeriodSeconds: 2},
},
expectedStabilization: utilpointer.Int32Ptr(25),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
{
annotation: "Percent policy and stabilization window are specified",
rateUpPercent: 7,
rateUpPercentPeriodSeconds: 60,
stabilizationSeconds: utilpointer.Int32Ptr(25),
expectedPolicies: []autoscalingv2.HPAScalingPolicy{
{Type: autoscalingv2.PercentScalingPolicy, Value: 7, PeriodSeconds: 60},
},
expectedStabilization: utilpointer.Int32Ptr(25),
expectedSelectPolicy: string(autoscalingv2.MaxPolicySelect),
},
}
for _, tc := range tests {
t.Run(tc.annotation, func(t *testing.T) {
scaleUpRules := &autoscalingv2.HPAScalingRules{
StabilizationWindowSeconds: tc.stabilizationSeconds,
SelectPolicy: tc.selectPolicy,
}
if tc.rateUpPods != 0 || tc.rateUpPodsPeriodSeconds != 0 {
scaleUpRules.Policies = append(scaleUpRules.Policies, autoscalingv2.HPAScalingPolicy{
Type: autoscalingv2.PodsScalingPolicy, Value: tc.rateUpPods, PeriodSeconds: tc.rateUpPodsPeriodSeconds,
})
}
if tc.rateUpPercent != 0 || tc.rateUpPercentPeriodSeconds != 0 {
scaleUpRules.Policies = append(scaleUpRules.Policies, autoscalingv2.HPAScalingPolicy{
Type: autoscalingv2.PercentScalingPolicy, Value: tc.rateUpPercent, PeriodSeconds: tc.rateUpPercentPeriodSeconds,
})
}
up := GenerateHPAScaleUpRules(scaleUpRules)
assert.Equal(t, tc.expectedPolicies, up.Policies)
if tc.expectedStabilization != nil {
assert.Equal(t, *tc.expectedStabilization, *up.StabilizationWindowSeconds)
} else {
assert.Equal(t, tc.expectedStabilization, up.StabilizationWindowSeconds)
}
assert.Equal(t, autoscalingv2.ScalingPolicySelect(tc.expectedSelectPolicy), *up.SelectPolicy)
})
}
}

View File

@ -17,5 +17,6 @@ limitations under the License.
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/autoscaling
// +k8s:conversion-gen-external-types=k8s.io/api/autoscaling/v2beta2
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/autoscaling/v2beta2
package v2beta2 // import "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2"

View File

@ -70,6 +70,26 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta2.HPAScalingPolicy)(nil), (*autoscaling.HPAScalingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(a.(*v2beta2.HPAScalingPolicy), b.(*autoscaling.HPAScalingPolicy), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*autoscaling.HPAScalingPolicy)(nil), (*v2beta2.HPAScalingPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(a.(*autoscaling.HPAScalingPolicy), b.(*v2beta2.HPAScalingPolicy), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta2.HPAScalingRules)(nil), (*autoscaling.HPAScalingRules)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(a.(*v2beta2.HPAScalingRules), b.(*autoscaling.HPAScalingRules), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*autoscaling.HPAScalingRules)(nil), (*v2beta2.HPAScalingRules)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(a.(*autoscaling.HPAScalingRules), b.(*v2beta2.HPAScalingRules), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta2.HorizontalPodAutoscaler)(nil), (*autoscaling.HorizontalPodAutoscaler)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(a.(*v2beta2.HorizontalPodAutoscaler), b.(*autoscaling.HorizontalPodAutoscaler), scope)
}); err != nil {
@ -80,6 +100,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta2.HorizontalPodAutoscalerBehavior)(nil), (*autoscaling.HorizontalPodAutoscalerBehavior)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(a.(*v2beta2.HorizontalPodAutoscalerBehavior), b.(*autoscaling.HorizontalPodAutoscalerBehavior), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*autoscaling.HorizontalPodAutoscalerBehavior)(nil), (*v2beta2.HorizontalPodAutoscalerBehavior)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(a.(*autoscaling.HorizontalPodAutoscalerBehavior), b.(*v2beta2.HorizontalPodAutoscalerBehavior), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v2beta2.HorizontalPodAutoscalerCondition)(nil), (*autoscaling.HorizontalPodAutoscalerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(a.(*v2beta2.HorizontalPodAutoscalerCondition), b.(*autoscaling.HorizontalPodAutoscalerCondition), scope)
}); err != nil {
@ -317,6 +347,54 @@ func Convert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(in
return autoConvert_autoscaling_ExternalMetricStatus_To_v2beta2_ExternalMetricStatus(in, out, s)
}
func autoConvert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in *v2beta2.HPAScalingPolicy, out *autoscaling.HPAScalingPolicy, s conversion.Scope) error {
out.Type = autoscaling.HPAScalingPolicyType(in.Type)
out.Value = in.Value
out.PeriodSeconds = in.PeriodSeconds
return nil
}
// Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy is an autogenerated conversion function.
func Convert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in *v2beta2.HPAScalingPolicy, out *autoscaling.HPAScalingPolicy, s conversion.Scope) error {
return autoConvert_v2beta2_HPAScalingPolicy_To_autoscaling_HPAScalingPolicy(in, out, s)
}
func autoConvert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in *autoscaling.HPAScalingPolicy, out *v2beta2.HPAScalingPolicy, s conversion.Scope) error {
out.Type = v2beta2.HPAScalingPolicyType(in.Type)
out.Value = in.Value
out.PeriodSeconds = in.PeriodSeconds
return nil
}
// Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy is an autogenerated conversion function.
func Convert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in *autoscaling.HPAScalingPolicy, out *v2beta2.HPAScalingPolicy, s conversion.Scope) error {
return autoConvert_autoscaling_HPAScalingPolicy_To_v2beta2_HPAScalingPolicy(in, out, s)
}
func autoConvert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in *v2beta2.HPAScalingRules, out *autoscaling.HPAScalingRules, s conversion.Scope) error {
out.StabilizationWindowSeconds = (*int32)(unsafe.Pointer(in.StabilizationWindowSeconds))
out.SelectPolicy = (*autoscaling.ScalingPolicySelect)(unsafe.Pointer(in.SelectPolicy))
out.Policies = *(*[]autoscaling.HPAScalingPolicy)(unsafe.Pointer(&in.Policies))
return nil
}
// Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules is an autogenerated conversion function.
func Convert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in *v2beta2.HPAScalingRules, out *autoscaling.HPAScalingRules, s conversion.Scope) error {
return autoConvert_v2beta2_HPAScalingRules_To_autoscaling_HPAScalingRules(in, out, s)
}
func autoConvert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in *autoscaling.HPAScalingRules, out *v2beta2.HPAScalingRules, s conversion.Scope) error {
out.StabilizationWindowSeconds = (*int32)(unsafe.Pointer(in.StabilizationWindowSeconds))
out.SelectPolicy = (*v2beta2.ScalingPolicySelect)(unsafe.Pointer(in.SelectPolicy))
out.Policies = *(*[]v2beta2.HPAScalingPolicy)(unsafe.Pointer(&in.Policies))
return nil
}
// Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules is an autogenerated conversion function.
func Convert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in *autoscaling.HPAScalingRules, out *v2beta2.HPAScalingRules, s conversion.Scope) error {
return autoConvert_autoscaling_HPAScalingRules_To_v2beta2_HPAScalingRules(in, out, s)
}
func autoConvert_v2beta2_HorizontalPodAutoscaler_To_autoscaling_HorizontalPodAutoscaler(in *v2beta2.HorizontalPodAutoscaler, out *autoscaling.HorizontalPodAutoscaler, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil {
@ -349,6 +427,28 @@ func Convert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutosca
return autoConvert_autoscaling_HorizontalPodAutoscaler_To_v2beta2_HorizontalPodAutoscaler(in, out, s)
}
func autoConvert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in *v2beta2.HorizontalPodAutoscalerBehavior, out *autoscaling.HorizontalPodAutoscalerBehavior, s conversion.Scope) error {
out.ScaleUp = (*autoscaling.HPAScalingRules)(unsafe.Pointer(in.ScaleUp))
out.ScaleDown = (*autoscaling.HPAScalingRules)(unsafe.Pointer(in.ScaleDown))
return nil
}
// Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior is an autogenerated conversion function.
func Convert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in *v2beta2.HorizontalPodAutoscalerBehavior, out *autoscaling.HorizontalPodAutoscalerBehavior, s conversion.Scope) error {
return autoConvert_v2beta2_HorizontalPodAutoscalerBehavior_To_autoscaling_HorizontalPodAutoscalerBehavior(in, out, s)
}
func autoConvert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in *autoscaling.HorizontalPodAutoscalerBehavior, out *v2beta2.HorizontalPodAutoscalerBehavior, s conversion.Scope) error {
out.ScaleUp = (*v2beta2.HPAScalingRules)(unsafe.Pointer(in.ScaleUp))
out.ScaleDown = (*v2beta2.HPAScalingRules)(unsafe.Pointer(in.ScaleDown))
return nil
}
// Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior is an autogenerated conversion function.
func Convert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in *autoscaling.HorizontalPodAutoscalerBehavior, out *v2beta2.HorizontalPodAutoscalerBehavior, s conversion.Scope) error {
return autoConvert_autoscaling_HorizontalPodAutoscalerBehavior_To_v2beta2_HorizontalPodAutoscalerBehavior(in, out, s)
}
func autoConvert_v2beta2_HorizontalPodAutoscalerCondition_To_autoscaling_HorizontalPodAutoscalerCondition(in *v2beta2.HorizontalPodAutoscalerCondition, out *autoscaling.HorizontalPodAutoscalerCondition, s conversion.Scope) error {
out.Type = autoscaling.HorizontalPodAutoscalerConditionType(in.Type)
out.Status = autoscaling.ConditionStatus(in.Status)
@ -406,6 +506,7 @@ func autoConvert_v2beta2_HorizontalPodAutoscalerSpec_To_autoscaling_HorizontalPo
out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas))
out.MaxReplicas = in.MaxReplicas
out.Metrics = *(*[]autoscaling.MetricSpec)(unsafe.Pointer(&in.Metrics))
out.Behavior = (*autoscaling.HorizontalPodAutoscalerBehavior)(unsafe.Pointer(in.Behavior))
return nil
}
@ -421,6 +522,7 @@ func autoConvert_autoscaling_HorizontalPodAutoscalerSpec_To_v2beta2_HorizontalPo
out.MinReplicas = (*int32)(unsafe.Pointer(in.MinReplicas))
out.MaxReplicas = in.MaxReplicas
out.Metrics = *(*[]v2beta2.MetricSpec)(unsafe.Pointer(&in.Metrics))
out.Behavior = (*v2beta2.HorizontalPodAutoscalerBehavior)(unsafe.Pointer(in.Behavior))
return nil
}

View File

@ -21,6 +21,7 @@ limitations under the License.
package v2beta2
import (
v2beta2 "k8s.io/api/autoscaling/v2beta2"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -28,5 +29,22 @@ import (
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&v2beta2.HorizontalPodAutoscaler{}, func(obj interface{}) {
SetObjectDefaults_HorizontalPodAutoscaler(obj.(*v2beta2.HorizontalPodAutoscaler))
})
scheme.AddTypeDefaultingFunc(&v2beta2.HorizontalPodAutoscalerList{}, func(obj interface{}) {
SetObjectDefaults_HorizontalPodAutoscalerList(obj.(*v2beta2.HorizontalPodAutoscalerList))
})
return nil
}
func SetObjectDefaults_HorizontalPodAutoscaler(in *v2beta2.HorizontalPodAutoscaler) {
SetDefaults_HorizontalPodAutoscaler(in)
}
func SetObjectDefaults_HorizontalPodAutoscalerList(in *v2beta2.HorizontalPodAutoscalerList) {
for i := range in.Items {
a := &in.Items[i]
SetObjectDefaults_HorizontalPodAutoscaler(a)
}
}

View File

@ -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()

View File

@ -94,6 +94,376 @@ func TestValidateScale(t *testing.T) {
}
}
func TestValidateBehavior(t *testing.T) {
maxPolicy := autoscaling.MaxPolicySelect
minPolicy := autoscaling.MinPolicySelect
disabledPolicy := autoscaling.DisabledPolicySelect
incorrectPolicy := autoscaling.ScalingPolicySelect("incorrect")
simplePoliciesList := []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PercentScalingPolicy,
Value: 10,
PeriodSeconds: 1,
},
{
Type: autoscaling.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 1800,
},
}
successCases := []autoscaling.HorizontalPodAutoscalerBehavior{
{
ScaleUp: nil,
ScaleDown: nil,
},
{
ScaleUp: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(3600),
SelectPolicy: &minPolicy,
Policies: simplePoliciesList,
},
ScaleDown: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(0),
SelectPolicy: &disabledPolicy,
Policies: simplePoliciesList,
},
},
{
ScaleUp: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(120),
SelectPolicy: &maxPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 2,
},
{
Type: autoscaling.PercentScalingPolicy,
Value: 3,
PeriodSeconds: 4,
},
{
Type: autoscaling.PodsScalingPolicy,
Value: 5,
PeriodSeconds: 6,
},
{
Type: autoscaling.PercentScalingPolicy,
Value: 7,
PeriodSeconds: 8,
},
},
},
ScaleDown: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(120),
SelectPolicy: &maxPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 2,
},
{
Type: autoscaling.PercentScalingPolicy,
Value: 3,
PeriodSeconds: 4,
},
{
Type: autoscaling.PodsScalingPolicy,
Value: 5,
PeriodSeconds: 6,
},
{
Type: autoscaling.PercentScalingPolicy,
Value: 7,
PeriodSeconds: 8,
},
},
},
},
}
for _, behavior := range successCases {
hpa := prepareHPAWithBehavior(behavior)
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := []struct {
behavior autoscaling.HorizontalPodAutoscalerBehavior
msg string
}{
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
SelectPolicy: &minPolicy,
},
},
msg: "spec.behavior.scaleUp.policies: Required value: must specify at least one Policy",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(3601),
SelectPolicy: &minPolicy,
Policies: simplePoliciesList,
},
},
msg: "spec.behavior.scaleUp.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 7,
PeriodSeconds: 1801,
},
},
},
},
msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
SelectPolicy: &incorrectPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 7,
PeriodSeconds: 8,
},
},
},
},
msg: `spec.behavior.scaleUp.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`,
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.HPAScalingPolicyType("hm"),
Value: 7,
PeriodSeconds: 8,
},
},
},
},
msg: `spec.behavior.scaleUp.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`,
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 8,
},
},
},
},
msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: 0: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: 8,
},
},
},
},
msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: 0: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: -1,
Value: 1,
},
},
},
},
msg: "spec.behavior.scaleUp.policies[0].periodSeconds: Invalid value: -1: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: 1,
Value: -1,
},
},
},
},
msg: "spec.behavior.scaleUp.policies[0].value: Invalid value: -1: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
SelectPolicy: &minPolicy,
},
},
msg: "spec.behavior.scaleDown.policies: Required value: must specify at least one Policy",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(3601),
SelectPolicy: &minPolicy,
Policies: simplePoliciesList,
},
},
msg: "spec.behavior.scaleDown.stabilizationWindowSeconds: Invalid value: 3601: must be less than or equal to 3600",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PercentScalingPolicy,
Value: 7,
PeriodSeconds: 1801,
},
},
},
},
msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 1801: must be less than or equal to 1800",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
SelectPolicy: &incorrectPolicy,
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 7,
PeriodSeconds: 8,
},
},
},
},
msg: `spec.behavior.scaleDown.selectPolicy: Unsupported value: "incorrect": supported values: "Disabled", "Max", "Min"`,
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.HPAScalingPolicyType("hm"),
Value: 7,
PeriodSeconds: 8,
},
},
},
},
msg: `spec.behavior.scaleDown.policies[0].type: Unsupported value: "hm": supported values: "Percent", "Pods"`,
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
Value: 8,
},
},
},
},
msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: 0: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: 8,
},
},
},
},
msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: 0: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: -1,
Value: 1,
},
},
},
},
msg: "spec.behavior.scaleDown.policies[0].periodSeconds: Invalid value: -1: must be greater than zero",
},
{
behavior: autoscaling.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscaling.HPAScalingRules{
Policies: []autoscaling.HPAScalingPolicy{
{
Type: autoscaling.PodsScalingPolicy,
PeriodSeconds: 1,
Value: -1,
},
},
},
},
msg: "spec.behavior.scaleDown.policies[0].value: Invalid value: -1: must be greater than zero",
},
}
for _, c := range errorCases {
hpa := prepareHPAWithBehavior(c.behavior)
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) == 0 {
t.Errorf("expected failure for %s", c.msg)
} else if !strings.Contains(errs[0].Error(), c.msg) {
t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg)
}
}
}
func prepareHPAWithBehavior(b autoscaling.HorizontalPodAutoscalerBehavior) autoscaling.HorizontalPodAutoscaler {
return autoscaling.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "myautoscaler",
Namespace: metav1.NamespaceDefault,
},
Spec: autoscaling.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscaling.CrossVersionObjectReference{
Kind: "ReplicationController",
Name: "myrc",
},
MinReplicas: utilpointer.Int32Ptr(1),
MaxReplicas: 5,
Metrics: []autoscaling.MetricSpec{
{
Type: autoscaling.ResourceMetricSourceType,
Resource: &autoscaling.ResourceMetricSource{
Name: api.ResourceCPU,
Target: autoscaling.MetricTarget{
Type: autoscaling.UtilizationMetricType,
AverageUtilization: utilpointer.Int32Ptr(70),
},
},
},
},
Behavior: &b,
},
}
}
func TestValidateHorizontalPodAutoscaler(t *testing.T) {
metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
if err != nil {

View File

@ -77,6 +77,53 @@ func (in *ExternalMetricStatus) DeepCopy() *ExternalMetricStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HPAScalingPolicy) DeepCopyInto(out *HPAScalingPolicy) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingPolicy.
func (in *HPAScalingPolicy) DeepCopy() *HPAScalingPolicy {
if in == nil {
return nil
}
out := new(HPAScalingPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HPAScalingRules) DeepCopyInto(out *HPAScalingRules) {
*out = *in
if in.StabilizationWindowSeconds != nil {
in, out := &in.StabilizationWindowSeconds, &out.StabilizationWindowSeconds
*out = new(int32)
**out = **in
}
if in.SelectPolicy != nil {
in, out := &in.SelectPolicy, &out.SelectPolicy
*out = new(ScalingPolicySelect)
**out = **in
}
if in.Policies != nil {
in, out := &in.Policies, &out.Policies
*out = make([]HPAScalingPolicy, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingRules.
func (in *HPAScalingRules) DeepCopy() *HPAScalingRules {
if in == nil {
return nil
}
out := new(HPAScalingRules)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscaler) DeepCopyInto(out *HorizontalPodAutoscaler) {
*out = *in
@ -105,6 +152,32 @@ func (in *HorizontalPodAutoscaler) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscalerBehavior) DeepCopyInto(out *HorizontalPodAutoscalerBehavior) {
*out = *in
if in.ScaleUp != nil {
in, out := &in.ScaleUp, &out.ScaleUp
*out = new(HPAScalingRules)
(*in).DeepCopyInto(*out)
}
if in.ScaleDown != nil {
in, out := &in.ScaleDown, &out.ScaleDown
*out = new(HPAScalingRules)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerBehavior.
func (in *HorizontalPodAutoscalerBehavior) DeepCopy() *HorizontalPodAutoscalerBehavior {
if in == nil {
return nil
}
out := new(HorizontalPodAutoscalerBehavior)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscalerCondition) DeepCopyInto(out *HorizontalPodAutoscalerCondition) {
*out = *in
@ -171,6 +244,11 @@ func (in *HorizontalPodAutoscalerSpec) DeepCopyInto(out *HorizontalPodAutoscaler
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Behavior != nil {
in, out := &in.Behavior, &out.Behavior
*out = new(HorizontalPodAutoscalerBehavior)
(*in).DeepCopyInto(*out)
}
return
}

View File

@ -58,6 +58,7 @@ go_test(
"//pkg/apis/apps/install:go_default_library",
"//pkg/apis/autoscaling:go_default_library",
"//pkg/apis/autoscaling/install:go_default_library",
"//pkg/apis/autoscaling/v2beta2:go_default_library",
"//pkg/apis/core/install:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/podautoscaler/metrics:go_default_library",
@ -89,6 +90,7 @@ go_test(
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/heapster/metrics/api/v1/types:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

View File

@ -61,6 +61,12 @@ type timestampedRecommendation struct {
timestamp time.Time
}
type timestampedScaleEvent struct {
replicaChange int32 // positive for scaleUp, negative for scaleDown
timestamp time.Time
outdated bool
}
// HorizontalController is responsible for the synchronizing HPA objects stored
// in the system with the actual deployments/replication controllers they
// control.
@ -89,6 +95,10 @@ type HorizontalController struct {
// Latest unstabilized recommendations for each autoscaler.
recommendations map[string][]timestampedRecommendation
// Latest autoscaler events
scaleUpEvents map[string][]timestampedScaleEvent
scaleDownEvents map[string][]timestampedScaleEvent
}
// NewHorizontalController creates a new HorizontalController.
@ -120,6 +130,8 @@ func NewHorizontalController(
queue: workqueue.NewNamedRateLimitingQueue(NewDefaultHPARateLimiter(resyncPeriod), "horizontalpodautoscaler"),
mapper: mapper,
recommendations: map[string][]timestampedRecommendation{},
scaleUpEvents: map[string][]timestampedScaleEvent{},
scaleDownEvents: map[string][]timestampedScaleEvent{},
}
hpaInformer.Informer().AddEventHandlerWithResyncPeriod(
@ -340,6 +352,8 @@ func (a *HorizontalController) reconcileKey(key string) (deleted bool, err error
if errors.IsNotFound(err) {
klog.Infof("Horizontal Pod Autoscaler %s has been deleted in %s", name, namespace)
delete(a.recommendations, key)
delete(a.scaleUpEvents, key)
delete(a.scaleDownEvents, key)
return true, nil
}
@ -623,7 +637,11 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho
if desiredReplicas < currentReplicas {
rescaleReason = "All metrics below target"
}
desiredReplicas = a.normalizeDesiredReplicas(hpa, key, currentReplicas, desiredReplicas, minReplicas)
if hpa.Spec.Behavior == nil {
desiredReplicas = a.normalizeDesiredReplicas(hpa, key, currentReplicas, desiredReplicas, minReplicas)
} else {
desiredReplicas = a.normalizeDesiredReplicasWithBehaviors(hpa, key, currentReplicas, desiredReplicas, minReplicas)
}
rescale = desiredReplicas != currentReplicas
}
@ -641,6 +659,7 @@ func (a *HorizontalController) reconcileAutoscaler(hpav1Shared *autoscalingv1.Ho
}
setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionTrue, "SucceededRescale", "the HPA controller was able to update the target scale to %d", desiredReplicas)
a.eventRecorder.Eventf(hpa, v1.EventTypeNormal, "SuccessfulRescale", "New size: %d; reason: %s", desiredReplicas, rescaleReason)
a.storeScaleEvent(hpa.Spec.Behavior, key, currentReplicas, desiredReplicas)
klog.Infof("Successful rescale of %s, old size: %d, new size: %d, reason: %s",
hpa.Name, currentReplicas, desiredReplicas, rescaleReason)
} else {
@ -697,6 +716,72 @@ func (a *HorizontalController) normalizeDesiredReplicas(hpa *autoscalingv2.Horiz
return desiredReplicas
}
// NormalizationArg is used to pass all needed information between functions as one structure
type NormalizationArg struct {
Key string
ScaleUpBehavior *autoscalingv2.HPAScalingRules
ScaleDownBehavior *autoscalingv2.HPAScalingRules
MinReplicas int32
MaxReplicas int32
CurrentReplicas int32
DesiredReplicas int32
}
// normalizeDesiredReplicasWithBehaviors takes the metrics desired replicas value and normalizes it:
// 1. Apply the basic conditions (i.e. < maxReplicas, > minReplicas, etc...)
// 2. Apply the scale up/down limits from the hpaSpec.Behaviors (i.e. add no more than 4 pods)
// 3. Apply the constraints period (i.e. add no more than 4 pods per minute)
// 4. Apply the stabilization (i.e. add no more than 4 pods per minute, and pick the smallest recommendation during last 5 minutes)
func (a *HorizontalController) normalizeDesiredReplicasWithBehaviors(hpa *autoscalingv2.HorizontalPodAutoscaler, key string, currentReplicas, prenormalizedDesiredReplicas, minReplicas int32) int32 {
a.maybeInitScaleDownStabilizationWindow(hpa)
normalizationArg := NormalizationArg{
Key: key,
ScaleUpBehavior: hpa.Spec.Behavior.ScaleUp,
ScaleDownBehavior: hpa.Spec.Behavior.ScaleDown,
MinReplicas: minReplicas,
MaxReplicas: hpa.Spec.MaxReplicas,
CurrentReplicas: currentReplicas,
DesiredReplicas: prenormalizedDesiredReplicas}
stabilizedRecommendation, reason, message := a.stabilizeRecommendationWithBehaviors(normalizationArg)
normalizationArg.DesiredReplicas = stabilizedRecommendation
if stabilizedRecommendation != prenormalizedDesiredReplicas {
// "ScaleUpStabilized" || "ScaleDownStabilized"
setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionTrue, reason, message)
} else {
setCondition(hpa, autoscalingv2.AbleToScale, v1.ConditionTrue, "ReadyForNewScale", "recommended size matches current size")
}
desiredReplicas, reason, message := a.convertDesiredReplicasWithBehaviorRate(normalizationArg)
if desiredReplicas == stabilizedRecommendation {
setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionFalse, reason, message)
} else {
setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, reason, message)
}
return desiredReplicas
}
func (a *HorizontalController) maybeInitScaleDownStabilizationWindow(hpa *autoscalingv2.HorizontalPodAutoscaler) {
behavior := hpa.Spec.Behavior
if behavior != nil && behavior.ScaleDown != nil && behavior.ScaleDown.StabilizationWindowSeconds == nil {
stabilizationWindowSeconds := (int32)(a.downscaleStabilisationWindow.Seconds())
hpa.Spec.Behavior.ScaleDown.StabilizationWindowSeconds = &stabilizationWindowSeconds
}
}
// getReplicasChangePerPeriod function find all the replica changes per period
func getReplicasChangePerPeriod(periodSeconds int32, scaleEvents []timestampedScaleEvent) int32 {
period := time.Second * time.Duration(periodSeconds)
cutoff := time.Now().Add(-period)
var replicas int32
for _, rec := range scaleEvents {
if rec.timestamp.After(cutoff) {
replicas += rec.replicaChange
}
}
return replicas
}
func (a *HorizontalController) getUnableComputeReplicaCountCondition(hpa *autoscalingv2.HorizontalPodAutoscaler, reason string, err error) (condition autoscalingv2.HorizontalPodAutoscalerCondition) {
a.eventRecorder.Event(hpa, v1.EventTypeWarning, reason, err.Error())
return autoscalingv2.HorizontalPodAutoscalerCondition{
@ -707,6 +792,140 @@ func (a *HorizontalController) getUnableComputeReplicaCountCondition(hpa *autosc
}
}
// storeScaleEvent stores (adds or replaces outdated) scale event.
// outdated events to be replaced were marked as outdated in the `markScaleEventsOutdated` function
func (a *HorizontalController) storeScaleEvent(behavior *autoscalingv2.HorizontalPodAutoscalerBehavior, key string, prevReplicas, newReplicas int32) {
if behavior == nil {
return // we should not store any event as they will not be used
}
var oldSampleIndex int
var longestPolicyPeriod int32
foundOldSample := false
if newReplicas > prevReplicas {
longestPolicyPeriod = getLongestPolicyPeriod(behavior.ScaleUp)
markScaleEventsOutdated(a.scaleUpEvents[key], longestPolicyPeriod)
replicaChange := newReplicas - prevReplicas
for i, event := range a.scaleUpEvents[key] {
if event.outdated {
foundOldSample = true
oldSampleIndex = i
}
}
newEvent := timestampedScaleEvent{replicaChange, time.Now(), false}
if foundOldSample {
a.scaleUpEvents[key][oldSampleIndex] = newEvent
} else {
a.scaleUpEvents[key] = append(a.scaleUpEvents[key], newEvent)
}
} else {
longestPolicyPeriod = getLongestPolicyPeriod(behavior.ScaleDown)
markScaleEventsOutdated(a.scaleDownEvents[key], longestPolicyPeriod)
replicaChange := prevReplicas - newReplicas
for i, event := range a.scaleDownEvents[key] {
if event.outdated {
foundOldSample = true
oldSampleIndex = i
}
}
newEvent := timestampedScaleEvent{replicaChange, time.Now(), false}
if foundOldSample {
a.scaleDownEvents[key][oldSampleIndex] = newEvent
} else {
a.scaleDownEvents[key] = append(a.scaleDownEvents[key], newEvent)
}
}
}
// stabilizeRecommendationWithBehaviors:
// - replaces old recommendation with the newest recommendation,
// - returns {max,min} of recommendations that are not older than constraints.Scale{Up,Down}.DelaySeconds
func (a *HorizontalController) stabilizeRecommendationWithBehaviors(args NormalizationArg) (int32, string, string) {
recommendation := args.DesiredReplicas
foundOldSample := false
oldSampleIndex := 0
var scaleDelaySeconds int32
var reason, message string
var betterRecommendation func(int32, int32) int32
if args.DesiredReplicas >= args.CurrentReplicas {
scaleDelaySeconds = *args.ScaleUpBehavior.StabilizationWindowSeconds
betterRecommendation = min
reason = "ScaleUpStabilized"
message = "recent recommendations were lower than current one, applying the lowest recent recommendation"
} else {
scaleDelaySeconds = *args.ScaleDownBehavior.StabilizationWindowSeconds
betterRecommendation = max
reason = "ScaleDownStabilized"
message = "recent recommendations were higher than current one, applying the highest recent recommendation"
}
maxDelaySeconds := max(*args.ScaleUpBehavior.StabilizationWindowSeconds, *args.ScaleDownBehavior.StabilizationWindowSeconds)
obsoleteCutoff := time.Now().Add(-time.Second * time.Duration(maxDelaySeconds))
cutoff := time.Now().Add(-time.Second * time.Duration(scaleDelaySeconds))
for i, rec := range a.recommendations[args.Key] {
if rec.timestamp.After(cutoff) {
recommendation = betterRecommendation(rec.recommendation, recommendation)
}
if rec.timestamp.Before(obsoleteCutoff) {
foundOldSample = true
oldSampleIndex = i
}
}
if foundOldSample {
a.recommendations[args.Key][oldSampleIndex] = timestampedRecommendation{args.DesiredReplicas, time.Now()}
} else {
a.recommendations[args.Key] = append(a.recommendations[args.Key], timestampedRecommendation{args.DesiredReplicas, time.Now()})
}
return recommendation, reason, message
}
// convertDesiredReplicasWithBehaviorRate performs the actual normalization, given the constraint rate
// It doesn't consider the stabilizationWindow, it is done separately
func (a *HorizontalController) convertDesiredReplicasWithBehaviorRate(args NormalizationArg) (int32, string, string) {
var possibleLimitingReason, possibleLimitingMessage string
if args.DesiredReplicas > args.CurrentReplicas {
scaleUpLimit := calculateScaleUpLimitWithScalingRules(args.CurrentReplicas, a.scaleUpEvents[args.Key], args.ScaleUpBehavior)
if scaleUpLimit < args.CurrentReplicas {
// We shouldn't scale up further until the scaleUpEvents will be cleaned up
scaleUpLimit = args.CurrentReplicas
}
maximumAllowedReplicas := args.MaxReplicas
if maximumAllowedReplicas > scaleUpLimit {
maximumAllowedReplicas = scaleUpLimit
possibleLimitingReason = "ScaleUpLimit"
possibleLimitingMessage = "the desired replica count is increasing faster than the maximum scale rate"
} else {
possibleLimitingReason = "TooManyReplicas"
possibleLimitingMessage = "the desired replica count is more than the maximum replica count"
}
if args.DesiredReplicas > maximumAllowedReplicas {
return maximumAllowedReplicas, possibleLimitingReason, possibleLimitingMessage
}
} else if args.DesiredReplicas < args.CurrentReplicas {
scaleDownLimit := calculateScaleDownLimitWithBehaviors(args.CurrentReplicas, a.scaleDownEvents[args.Key], args.ScaleDownBehavior)
if scaleDownLimit > args.CurrentReplicas {
// We shouldn't scale down further until the scaleDownEvents will be cleaned up
scaleDownLimit = args.CurrentReplicas
}
minimumAllowedReplicas := args.MinReplicas
if minimumAllowedReplicas < scaleDownLimit {
minimumAllowedReplicas = scaleDownLimit
possibleLimitingReason = "ScaleDownLimit"
possibleLimitingMessage = "the desired replica count is decreasing faster than the maximum scale rate"
} else {
possibleLimitingMessage = "the desired replica count is less than the minimum replica count"
possibleLimitingReason = "TooFewReplicas"
}
if args.DesiredReplicas < minimumAllowedReplicas {
return minimumAllowedReplicas, possibleLimitingReason, possibleLimitingMessage
}
}
return args.DesiredReplicas, "DesiredWithinRange", "the desired count is within the acceptable range"
}
// convertDesiredReplicas performs the actual normalization, without depending on `HorizontalController` or `HorizontalPodAutoscaler`
func convertDesiredReplicasWithRules(currentReplicas, desiredReplicas, hpaMinReplicas, hpaMaxReplicas int32) (int32, string, string) {
@ -750,6 +969,79 @@ func calculateScaleUpLimit(currentReplicas int32) int32 {
return int32(math.Max(scaleUpLimitFactor*float64(currentReplicas), scaleUpLimitMinimum))
}
// markScaleEventsOutdated set 'outdated=true' flag for all scale events that are not used by any HPA object
func markScaleEventsOutdated(scaleEvents []timestampedScaleEvent, longestPolicyPeriod int32) {
period := time.Second * time.Duration(longestPolicyPeriod)
cutoff := time.Now().Add(-period)
for i, event := range scaleEvents {
if event.timestamp.Before(cutoff) {
// outdated scale event are marked for later reuse
scaleEvents[i].outdated = true
}
}
}
func getLongestPolicyPeriod(scalingRules *autoscalingv2.HPAScalingRules) int32 {
var longestPolicyPeriod int32
for _, policy := range scalingRules.Policies {
if policy.PeriodSeconds > longestPolicyPeriod {
longestPolicyPeriod = policy.PeriodSeconds
}
}
return longestPolicyPeriod
}
// calculateScaleUpLimitWithScalingRules returns the maximum number of pods that could be added for the given HPAScalingRules
func calculateScaleUpLimitWithScalingRules(currentReplicas int32, scaleEvents []timestampedScaleEvent, scalingRules *autoscalingv2.HPAScalingRules) int32 {
var result int32 = 0
var proposed int32
var selectPolicyFn func(int32, int32) int32
if *scalingRules.SelectPolicy == autoscalingv2.DisabledPolicySelect {
return currentReplicas // Scaling is disabled
} else if *scalingRules.SelectPolicy == autoscalingv2.MinPolicySelect {
selectPolicyFn = min // For scaling up, the lowest change ('min' policy) produces a minimum value
} else {
selectPolicyFn = max // Use the default policy otherwise to produce a highest possible change
}
for _, policy := range scalingRules.Policies {
replicasAddedInCurrentPeriod := getReplicasChangePerPeriod(policy.PeriodSeconds, scaleEvents)
periodStartReplicas := currentReplicas - replicasAddedInCurrentPeriod
if policy.Type == autoscalingv2.PodsScalingPolicy {
proposed = int32(periodStartReplicas + policy.Value)
} else if policy.Type == autoscalingv2.PercentScalingPolicy {
// the proposal has to be rounded up because the proposed change might not increase the replica count causing the target to never scale up
proposed = int32(math.Ceil(float64(periodStartReplicas) * (1 + float64(policy.Value)/100)))
}
result = selectPolicyFn(result, proposed)
}
return result
}
// calculateScaleDownLimitWithBehavior returns the maximum number of pods that could be deleted for the given HPAScalingRules
func calculateScaleDownLimitWithBehaviors(currentReplicas int32, scaleEvents []timestampedScaleEvent, scalingRules *autoscalingv2.HPAScalingRules) int32 {
var result int32 = math.MaxInt32
var proposed int32
var selectPolicyFn func(int32, int32) int32
if *scalingRules.SelectPolicy == autoscalingv2.DisabledPolicySelect {
return currentReplicas // Scaling is disabled
} else if *scalingRules.SelectPolicy == autoscalingv2.MinPolicySelect {
selectPolicyFn = max // For scaling down, the lowest change ('min' policy) produces a maximum value
} else {
selectPolicyFn = min // Use the default policy otherwise to produce a highest possible change
}
for _, policy := range scalingRules.Policies {
replicasDeletedInCurrentPeriod := getReplicasChangePerPeriod(policy.PeriodSeconds, scaleEvents)
periodStartReplicas := currentReplicas + replicasDeletedInCurrentPeriod
if policy.Type == autoscalingv2.PodsScalingPolicy {
proposed = periodStartReplicas - policy.Value
} else if policy.Type == autoscalingv2.PercentScalingPolicy {
proposed = int32(float64(periodStartReplicas) * (1 - float64(policy.Value)/100))
}
result = selectPolicyFn(result, proposed)
}
return result
}
// scaleForResourceMappings attempts to fetch the scale for the
// resource with the given name and namespace, trying each RESTMapping
// in turn until a working one is found. If none work, the first error
@ -885,3 +1177,19 @@ func setConditionInList(inputList []autoscalingv2.HorizontalPodAutoscalerConditi
return resList
}
func max(a, b int32) int32 {
if a >= b {
return a
} else {
return b
}
}
func min(a, b int32) int32 {
if a <= b {
return a
} else {
return b
}
}

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ import (
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2beta1"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

File diff suppressed because it is too large Load Diff

View File

@ -64,6 +64,47 @@ message ExternalMetricStatus {
optional MetricValueStatus current = 2;
}
// HPAScalingPolicy is a single policy which must hold true for a specified past interval.
message HPAScalingPolicy {
// Type is used to specify the scaling policy.
optional string type = 1;
// Value contains the amount of change which is permitted by the policy.
// It must be greater than zero
optional int32 value = 2;
// PeriodSeconds specifies the window of time for which the policy should hold true.
// PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).
optional int32 periodSeconds = 3;
}
// HPAScalingRules configures the scaling behavior for one direction.
// These Rules are applied after calculating DesiredReplicas from metrics for the HPA.
// They can limit the scaling velocity by specifying scaling policies.
// They can prevent flapping by specifying the stabilization window, so that the
// number of replicas is not set instantly, instead, the safest value from the stabilization
// window is chosen.
message HPAScalingRules {
// StabilizationWindowSeconds is the number of seconds for which past recommendations should be
// considered while scaling up or scaling down.
// StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour).
// If not set, use the default values:
// - For scale up: 0 (i.e. no stabilization is done).
// - For scale down: 300 (i.e. the stabilization window is 300 seconds long).
// +optional
optional int32 stabilizationWindowSeconds = 3;
// selectPolicy is used to specify which policy should be used.
// If not set, the default value MaxPolicySelect is used.
// +optional
optional string selectPolicy = 1;
// policies is a list of potential scaling polices which can be used during scaling.
// At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid
// +optional
repeated HPAScalingPolicy policies = 2;
}
// HorizontalPodAutoscaler is the configuration for a horizontal pod
// autoscaler, which automatically manages the replica count of any resource
// implementing the scale subresource based on the metrics specified.
@ -83,6 +124,25 @@ message HorizontalPodAutoscaler {
optional HorizontalPodAutoscalerStatus status = 3;
}
// HorizontalPodAutoscalerBehavior configures the scaling behavior of the target
// in both Up and Down directions (scaleUp and scaleDown fields respectively).
message HorizontalPodAutoscalerBehavior {
// scaleUp is scaling policy for scaling Up.
// If not set, the default value is the higher of:
// * increase no more than 4 pods per 60 seconds
// * double the number of pods per 60 seconds
// No stabilization is used.
// +optional
optional HPAScalingRules scaleUp = 1;
// scaleDown is scaling policy for scaling Down.
// If not set, the default value is to allow to scale down to minReplicas pods, with a
// 300 second stabilization window (i.e., the highest recommendation for
// the last 300sec is used).
// +optional
optional HPAScalingRules scaleDown = 2;
}
// HorizontalPodAutoscalerCondition describes the state of
// a HorizontalPodAutoscaler at a certain point.
message HorizontalPodAutoscalerCondition {
@ -145,6 +205,12 @@ message HorizontalPodAutoscalerSpec {
// If not set, the default metric will be set to 80% average CPU utilization.
// +optional
repeated MetricSpec metrics = 4;
// behavior configures the scaling behavior of the target
// in both Up and Down directions (scaleUp and scaleDown fields respectively).
// If not set, the default HPAScalingRules for scale up and scale down are used.
// +optional
optional HorizontalPodAutoscalerBehavior behavior = 5;
}
// HorizontalPodAutoscalerStatus describes the current status of a horizontal pod autoscaler.

View File

@ -72,6 +72,12 @@ type HorizontalPodAutoscalerSpec struct {
// If not set, the default metric will be set to 80% average CPU utilization.
// +optional
Metrics []MetricSpec `json:"metrics,omitempty" protobuf:"bytes,4,rep,name=metrics"`
// behavior configures the scaling behavior of the target
// in both Up and Down directions (scaleUp and scaleDown fields respectively).
// If not set, the default HPAScalingRules for scale up and scale down are used.
// +optional
Behavior *HorizontalPodAutoscalerBehavior `json:"behavior,omitempty" protobuf:"bytes,5,opt,name=behavior"`
}
// CrossVersionObjectReference contains enough information to let you identify the referred resource.
@ -117,6 +123,84 @@ type MetricSpec struct {
External *ExternalMetricSource `json:"external,omitempty" protobuf:"bytes,5,opt,name=external"`
}
// HorizontalPodAutoscalerBehavior configures the scaling behavior of the target
// in both Up and Down directions (scaleUp and scaleDown fields respectively).
type HorizontalPodAutoscalerBehavior struct {
// scaleUp is scaling policy for scaling Up.
// If not set, the default value is the higher of:
// * increase no more than 4 pods per 60 seconds
// * double the number of pods per 60 seconds
// No stabilization is used.
// +optional
ScaleUp *HPAScalingRules `json:"scaleUp,omitempty" protobuf:"bytes,1,opt,name=scaleUp"`
// scaleDown is scaling policy for scaling Down.
// If not set, the default value is to allow to scale down to minReplicas pods, with a
// 300 second stabilization window (i.e., the highest recommendation for
// the last 300sec is used).
// +optional
ScaleDown *HPAScalingRules `json:"scaleDown,omitempty" protobuf:"bytes,2,opt,name=scaleDown"`
}
// ScalingPolicySelect is used to specify which policy should be used while scaling in a certain direction
type ScalingPolicySelect string
const (
// MaxPolicySelect selects the policy with the highest possible change.
MaxPolicySelect ScalingPolicySelect = "Max"
// MinPolicySelect selects the policy with the lowest possible change.
MinPolicySelect ScalingPolicySelect = "Min"
// DisabledPolicySelect disables the scaling in this direction.
DisabledPolicySelect ScalingPolicySelect = "Disabled"
)
// HPAScalingRules configures the scaling behavior for one direction.
// These Rules are applied after calculating DesiredReplicas from metrics for the HPA.
// They can limit the scaling velocity by specifying scaling policies.
// They can prevent flapping by specifying the stabilization window, so that the
// number of replicas is not set instantly, instead, the safest value from the stabilization
// window is chosen.
type HPAScalingRules struct {
// StabilizationWindowSeconds is the number of seconds for which past recommendations should be
// considered while scaling up or scaling down.
// StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour).
// If not set, use the default values:
// - For scale up: 0 (i.e. no stabilization is done).
// - For scale down: 300 (i.e. the stabilization window is 300 seconds long).
// +optional
StabilizationWindowSeconds *int32 `json:"stabilizationWindowSeconds" protobuf:"varint,3,opt,name=stabilizationWindowSeconds"`
// selectPolicy is used to specify which policy should be used.
// If not set, the default value MaxPolicySelect is used.
// +optional
SelectPolicy *ScalingPolicySelect `json:"selectPolicy,omitempty" protobuf:"bytes,1,opt,name=selectPolicy"`
// policies is a list of potential scaling polices which can be used during scaling.
// At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid
// +optional
Policies []HPAScalingPolicy `json:"policies,omitempty" protobuf:"bytes,2,rep,name=policies"`
}
// HPAScalingPolicyType is the type of the policy which could be used while making scaling decisions.
type HPAScalingPolicyType string
const (
// PodsScalingPolicy is a policy used to specify a change in absolute number of pods.
PodsScalingPolicy HPAScalingPolicyType = "Pods"
// PercentScalingPolicy is a policy used to specify a relative amount of change with respect to
// the current number of pods.
PercentScalingPolicy HPAScalingPolicyType = "Percent"
)
// HPAScalingPolicy is a single policy which must hold true for a specified past interval.
type HPAScalingPolicy struct {
// Type is used to specify the scaling policy.
Type HPAScalingPolicyType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=HPAScalingPolicyType"`
// Value contains the amount of change which is permitted by the policy.
// It must be greater than zero
Value int32 `json:"value" protobuf:"varint,2,opt,name=value"`
// PeriodSeconds specifies the window of time for which the policy should hold true.
// PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).
PeriodSeconds int32 `json:"periodSeconds" protobuf:"varint,3,opt,name=periodSeconds"`
}
// MetricSourceType indicates the type of metric.
type MetricSourceType string

View File

@ -58,6 +58,28 @@ func (ExternalMetricStatus) SwaggerDoc() map[string]string {
return map_ExternalMetricStatus
}
var map_HPAScalingPolicy = map[string]string{
"": "HPAScalingPolicy is a single policy which must hold true for a specified past interval.",
"type": "Type is used to specify the scaling policy.",
"value": "Value contains the amount of change which is permitted by the policy. It must be greater than zero",
"periodSeconds": "PeriodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min).",
}
func (HPAScalingPolicy) SwaggerDoc() map[string]string {
return map_HPAScalingPolicy
}
var map_HPAScalingRules = map[string]string{
"": "HPAScalingRules configures the scaling behavior for one direction. These Rules are applied after calculating DesiredReplicas from metrics for the HPA. They can limit the scaling velocity by specifying scaling policies. They can prevent flapping by specifying the stabilization window, so that the number of replicas is not set instantly, instead, the safest value from the stabilization window is chosen.",
"stabilizationWindowSeconds": "StabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).",
"selectPolicy": "selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used.",
"policies": "policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid",
}
func (HPAScalingRules) SwaggerDoc() map[string]string {
return map_HPAScalingRules
}
var map_HorizontalPodAutoscaler = map[string]string{
"": "HorizontalPodAutoscaler is the configuration for a horizontal pod autoscaler, which automatically manages the replica count of any resource implementing the scale subresource based on the metrics specified.",
"metadata": "metadata is the standard object metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
@ -69,6 +91,16 @@ func (HorizontalPodAutoscaler) SwaggerDoc() map[string]string {
return map_HorizontalPodAutoscaler
}
var map_HorizontalPodAutoscalerBehavior = map[string]string{
"": "HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively).",
"scaleUp": "scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of:\n * increase no more than 4 pods per 60 seconds\n * double the number of pods per 60 seconds\nNo stabilization is used.",
"scaleDown": "scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used).",
}
func (HorizontalPodAutoscalerBehavior) SwaggerDoc() map[string]string {
return map_HorizontalPodAutoscalerBehavior
}
var map_HorizontalPodAutoscalerCondition = map[string]string{
"": "HorizontalPodAutoscalerCondition describes the state of a HorizontalPodAutoscaler at a certain point.",
"type": "type describes the current condition",
@ -98,6 +130,7 @@ var map_HorizontalPodAutoscalerSpec = map[string]string{
"minReplicas": "minReplicas is the lower limit for the number of replicas to which the autoscaler can scale down. It defaults to 1 pod. minReplicas is allowed to be 0 if the alpha feature gate HPAScaleToZero is enabled and at least one Object or External metric is configured. Scaling is active as long as at least one metric value is available.",
"maxReplicas": "maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. It cannot be less that minReplicas.",
"metrics": "metrics contains the specifications for which to use to calculate the desired replica count (the maximum replica count across all metrics will be used). The desired replica count is calculated multiplying the ratio between the target value and the current value by the current number of pods. Ergo, metrics used must decrease as the pod count is increased, and vice-versa. See the individual metric source types for more information about how each type of metric must respond. If not set, the default metric will be set to 80% average CPU utilization.",
"behavior": "behavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). If not set, the default HPAScalingRules for scale up and scale down are used.",
}
func (HorizontalPodAutoscalerSpec) SwaggerDoc() map[string]string {

View File

@ -77,6 +77,53 @@ func (in *ExternalMetricStatus) DeepCopy() *ExternalMetricStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HPAScalingPolicy) DeepCopyInto(out *HPAScalingPolicy) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingPolicy.
func (in *HPAScalingPolicy) DeepCopy() *HPAScalingPolicy {
if in == nil {
return nil
}
out := new(HPAScalingPolicy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HPAScalingRules) DeepCopyInto(out *HPAScalingRules) {
*out = *in
if in.StabilizationWindowSeconds != nil {
in, out := &in.StabilizationWindowSeconds, &out.StabilizationWindowSeconds
*out = new(int32)
**out = **in
}
if in.SelectPolicy != nil {
in, out := &in.SelectPolicy, &out.SelectPolicy
*out = new(ScalingPolicySelect)
**out = **in
}
if in.Policies != nil {
in, out := &in.Policies, &out.Policies
*out = make([]HPAScalingPolicy, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HPAScalingRules.
func (in *HPAScalingRules) DeepCopy() *HPAScalingRules {
if in == nil {
return nil
}
out := new(HPAScalingRules)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscaler) DeepCopyInto(out *HorizontalPodAutoscaler) {
*out = *in
@ -105,6 +152,32 @@ func (in *HorizontalPodAutoscaler) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscalerBehavior) DeepCopyInto(out *HorizontalPodAutoscalerBehavior) {
*out = *in
if in.ScaleUp != nil {
in, out := &in.ScaleUp, &out.ScaleUp
*out = new(HPAScalingRules)
(*in).DeepCopyInto(*out)
}
if in.ScaleDown != nil {
in, out := &in.ScaleDown, &out.ScaleDown
*out = new(HPAScalingRules)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalPodAutoscalerBehavior.
func (in *HorizontalPodAutoscalerBehavior) DeepCopy() *HorizontalPodAutoscalerBehavior {
if in == nil {
return nil
}
out := new(HorizontalPodAutoscalerBehavior)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HorizontalPodAutoscalerCondition) DeepCopyInto(out *HorizontalPodAutoscalerCondition) {
*out = *in
@ -171,6 +244,11 @@ func (in *HorizontalPodAutoscalerSpec) DeepCopyInto(out *HorizontalPodAutoscaler
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Behavior != nil {
in, out := &in.Behavior, &out.Behavior
*out = new(HorizontalPodAutoscalerBehavior)
(*in).DeepCopyInto(*out)
}
return
}

View File

@ -138,34 +138,61 @@
}
}
}
]
],
"behavior": {
"scaleUp": {
"stabilizationWindowSeconds": 1761963371,
"selectPolicy": "0矀Kʝ瘴I\\p[ħsĨɆâĺɗŹ倗S",
"policies": [
{
"type": "嶗U",
"value": -1285424066,
"periodSeconds": -686523310
}
]
},
"scaleDown": {
"stabilizationWindowSeconds": 1206365825,
"selectPolicy": "/ɸɎ R§耶FfBls3!",
"policies": [
{
"type": "ɾģ毋Ó6dz娝嘚",
"value": 627713162,
"periodSeconds": 1255312175
}
]
}
}
},
"status": {
"observedGeneration": -6706242512760583856,
"currentReplicas": 1761963371,
"desiredReplicas": 645599318,
"observedGeneration": -7477362499801752548,
"currentReplicas": 267768240,
"desiredReplicas": -127849333,
"currentMetrics": [
{
"type": "矀Kʝ瘴I\\p[ħsĨɆâĺɗŹ倗S",
"type": "Ǖɳɷ9Ì崟¿瘦ɖ緕",
"object": {
"metric": {
"name": "46",
"selector": {
"matchLabels": {
"990-17-hg1-o-p665--4-j8---t6-r7---d--uml-89.n0v-1o-0hv--k6/0_OHz_.B-.-_w_--.8_r_N-.3n-x.-_-_-Nm-_X3.1d_YH3x---5": "s2oy"
"29.-_Z.0_1._hg._o_p665O_4Gj._BXt.O-7___-Y_um-_8r--684._V": "8_...E.-o"
},
"matchExpressions": [
{
"key": "rR4_7FA.2",
"operator": "DoesNotExist"
"key": "6-d42--clo90---461v-07r--0---8-30i-uo/9DF",
"operator": "In",
"values": [
"2hT.-z-._7-5lL..-_--.VEa-_gn.8-c.C3_F._oX-F9_.5vN5.25aWx.a"
]
}
]
}
},
"current": {
"value": "861",
"averageValue": "289",
"averageUtilization": 1980459939
"value": "526",
"averageValue": "860",
"averageUtilization": -126958936
},
"describedObject": {
"kind": "53",
@ -178,31 +205,31 @@
"name": "56",
"selector": {
"matchLabels": {
"7-gg93--5-------g1c-fr4/mQ.GM72_-c-.-.6--3-___t-Z8SUGP.-_.uB-.--.gb_2_-8-----yJY.__-X2": "0_.GgT7_7B_D-..-.k4u-zA_--_.-.6GA26C-s.Nj-d-E"
"d5-g-7-7---g88w2k4usz--mj-8o26--26-hs5-jeds4-4tz9x-4.i-l11q5--uk5mj-94-8134i5k6q6--5tu-tie4-7--gm4p-8y-99/N_g-..__._____K_g1cXfr4": "ET_..3dCv3j._.-_pP__up.2L_s-o779._-k-N"
},
"matchExpressions": [
{
"key": "1q5--uk5mj-94-8134i5k6q6--5tu-tie4-7--gm4p-83.91z---883d-vj/z.-_Z4.A",
"operator": "NotIn",
"key": "382m88w-pz9d4i-m7---k8235--8--c83-4b-9-1k.1f-53-x1y-8---3----p-pdn--j2---2--82--cj-1-s--op3w/3--Z1Tvw39F_C-rtSY.g._2F7.-_e..Or_-.3OHgt.U",
"operator": "In",
"values": [
"G.-_pP__up.2L_s-o779._-k-5___-Qq..csh-3--Z1Tvw39F_C-t"
"y.8_8"
]
}
]
}
},
"current": {
"value": "485",
"averageValue": "638",
"averageUtilization": 1333166203
"value": "671",
"averageValue": "683",
"averageUtilization": 1008425444
}
},
"resource": {
"name": "ɻ;襕ċ桉桃喕",
"name": "Ƈè*鑏='ʨ|ǓÓ敆OɈÏ 瞍髃",
"current": {
"value": "826",
"averageValue": "886",
"averageUtilization": -280562323
"value": "93",
"averageValue": "183",
"averageUtilization": -392406530
}
},
"external": {
@ -210,29 +237,32 @@
"name": "63",
"selector": {
"matchLabels": {
"Z": "C..7o_x3..-.8-Jp-9-4-Tm.__G-8...__.Q_c8.G.b_91"
"627-23---g-----p8-d5-8-m8i--k0j5-gr-y7nlp97v-0-1y7/2....3_t_-l..-.DG7r-3.----._4__XOnf_ZN.-_--r.E__-8": "K.-miJ4x-_0_5-_7"
},
"matchExpressions": [
{
"key": "7pdn--j2---25/I._31-_I-A-_3b6",
"operator": "Exists"
"key": "c-.F5_x.KNC0-.-m_0-m-6Sp_N-S..o",
"operator": "In",
"values": [
"g-_4Q__-v_t_u_.__I_-_-3-3--5X1rh-K5y_AzOBW.9oE9_6.--v7"
]
}
]
}
},
"current": {
"value": "108",
"averageValue": "909",
"averageUtilization": -1726456869
"value": "287",
"averageValue": "759",
"averageUtilization": -1175595426
}
}
}
],
"conditions": [
{
"type": "Ƚȿ醏g遧",
"status": "ɸĻo:{柯?B俋¬h`職铳s44矕Ƈ",
"lastTransitionTime": "2039-12-25T06:58:01Z",
"type": "`翾'ųŎ群E牬庘颮6(|ǖû",
"status": "龢ÄƤUǷ坒ŕF5o儎ĄÇ稕E",
"lastTransitionTime": "2682-06-14T06:09:58Z",
"reason": "70",
"message": "71"
}

View File

@ -30,6 +30,21 @@ metadata:
selfLink: "5"
uid: "7"
spec:
behavior:
scaleDown:
policies:
- periodSeconds: 1255312175
type: ɾģ毋Ó6dz娝嘚
value: 627713162
selectPolicy: /ɸɎ R§耶FfBls3!
stabilizationWindowSeconds: 1206365825
scaleUp:
policies:
- periodSeconds: -686523310
type: 嶗U
value: -1285424066
selectPolicy: 0矀Kʝ瘴I\p[ħsĨɆâĺɗŹ倗S
stabilizationWindowSeconds: 1761963371
maxReplicas: -1971381490
metrics:
- external:
@ -97,30 +112,32 @@ spec:
name: "20"
status:
conditions:
- lastTransitionTime: "2039-12-25T06:58:01Z"
- lastTransitionTime: "2682-06-14T06:09:58Z"
message: "71"
reason: "70"
status: ɸĻo:{柯?B俋¬h`職铳s44矕Ƈ
type: Ƚȿ醏g遧
status: 龢ÄƤUǷ坒ŕF5o儎ĄÇ稕E
type: '`翾''ųŎ群E牬庘颮6(|ǖû'
currentMetrics:
- external:
current:
averageUtilization: -1726456869
averageValue: "909"
value: "108"
averageUtilization: -1175595426
averageValue: "759"
value: "287"
metric:
name: "63"
selector:
matchExpressions:
- key: 7pdn--j2---25/I._31-_I-A-_3b6
operator: Exists
- key: c-.F5_x.KNC0-.-m_0-m-6Sp_N-S..o
operator: In
values:
- g-_4Q__-v_t_u_.__I_-_-3-3--5X1rh-K5y_AzOBW.9oE9_6.--v7
matchLabels:
Z: C..7o_x3..-.8-Jp-9-4-Tm.__G-8...__.Q_c8.G.b_91
627-23---g-----p8-d5-8-m8i--k0j5-gr-y7nlp97v-0-1y7/2....3_t_-l..-.DG7r-3.----._4__XOnf_ZN.-_--r.E__-8: K.-miJ4x-_0_5-_7
object:
current:
averageUtilization: 1980459939
averageValue: "289"
value: "861"
averageUtilization: -126958936
averageValue: "860"
value: "526"
describedObject:
apiVersion: "55"
kind: "53"
@ -129,32 +146,35 @@ status:
name: "46"
selector:
matchExpressions:
- key: rR4_7FA.2
operator: DoesNotExist
- key: 6-d42--clo90---461v-07r--0---8-30i-uo/9DF
operator: In
values:
- 2hT.-z-._7-5lL..-_--.VEa-_gn.8-c.C3_F._oX-F9_.5vN5.25aWx.a
matchLabels:
990-17-hg1-o-p665--4-j8---t6-r7---d--uml-89.n0v-1o-0hv--k6/0_OHz_.B-.-_w_--.8_r_N-.3n-x.-_-_-Nm-_X3.1d_YH3x---5: s2oy
29.-_Z.0_1._hg._o_p665O_4Gj._BXt.O-7___-Y_um-_8r--684._V: 8_...E.-o
pods:
current:
averageUtilization: 1333166203
averageValue: "638"
value: "485"
averageUtilization: 1008425444
averageValue: "683"
value: "671"
metric:
name: "56"
selector:
matchExpressions:
- key: 1q5--uk5mj-94-8134i5k6q6--5tu-tie4-7--gm4p-83.91z---883d-vj/z.-_Z4.A
operator: NotIn
- key: 382m88w-pz9d4i-m7---k8235--8--c83-4b-9-1k.1f-53-x1y-8---3----p-pdn--j2---2--82--cj-1-s--op3w/3--Z1Tvw39F_C-rtSY.g._2F7.-_e..Or_-.3OHgt.U
operator: In
values:
- G.-_pP__up.2L_s-o779._-k-5___-Qq..csh-3--Z1Tvw39F_C-t
- y.8_8
matchLabels:
7-gg93--5-------g1c-fr4/mQ.GM72_-c-.-.6--3-___t-Z8SUGP.-_.uB-.--.gb_2_-8-----yJY.__-X2: 0_.GgT7_7B_D-..-.k4u-zA_--_.-.6GA26C-s.Nj-d-E
? d5-g-7-7---g88w2k4usz--mj-8o26--26-hs5-jeds4-4tz9x-4.i-l11q5--uk5mj-94-8134i5k6q6--5tu-tie4-7--gm4p-8y-99/N_g-..__._____K_g1cXfr4
: ET_..3dCv3j._.-_pP__up.2L_s-o779._-k-N
resource:
current:
averageUtilization: -280562323
averageValue: "886"
value: "826"
name: ɻ;襕ċ桉桃喕
type: 矀Kʝ瘴I\p[ħsĨɆâĺɗŹ倗S
currentReplicas: 1761963371
desiredReplicas: 645599318
observedGeneration: -6706242512760583856
averageUtilization: -392406530
averageValue: "183"
value: "93"
name: Ƈè*鑏='ʨ|ǓÓ敆OɈÏ 瞍髃
type: Ǖɳɷ9Ì崟¿瘦ɖ緕
currentReplicas: 267768240
desiredReplicas: -127849333
observedGeneration: -7477362499801752548

View File

@ -3392,6 +3392,12 @@ func describeHorizontalPodAutoscalerV2beta2(hpa *autoscalingv2beta2.HorizontalPo
}
w.Write(LEVEL_0, "Min replicas:\t%s\n", minReplicas)
w.Write(LEVEL_0, "Max replicas:\t%d\n", hpa.Spec.MaxReplicas)
// only print the hpa behavior if present
if hpa.Spec.Behavior != nil {
w.Write(LEVEL_0, "Behavior:\n")
printDirectionBehavior(w, "Scale Up", hpa.Spec.Behavior.ScaleUp)
printDirectionBehavior(w, "Scale Down", hpa.Spec.Behavior.ScaleDown)
}
w.Write(LEVEL_0, "%s pods:\t", hpa.Spec.ScaleTargetRef.Kind)
w.Write(LEVEL_0, "%d current / %d desired\n", hpa.Status.CurrentReplicas, hpa.Status.DesiredReplicas)
@ -3412,6 +3418,26 @@ func describeHorizontalPodAutoscalerV2beta2(hpa *autoscalingv2beta2.HorizontalPo
})
}
func printDirectionBehavior(w PrefixWriter, direction string, rules *autoscalingv2beta2.HPAScalingRules) {
if rules != nil {
w.Write(LEVEL_1, "%s:\n", direction)
if rules.StabilizationWindowSeconds != nil {
w.Write(LEVEL_2, "Stabilization Window: %d seconds\n", *rules.StabilizationWindowSeconds)
}
if len(rules.Policies) > 0 {
if rules.SelectPolicy != nil {
w.Write(LEVEL_2, "Select Policy: %s\n", *rules.SelectPolicy)
} else {
w.Write(LEVEL_2, "Select Policy: %s\n", autoscalingv2beta2.MaxPolicySelect)
}
w.Write(LEVEL_2, "Policies:\n")
for _, p := range rules.Policies {
w.Write(LEVEL_3, "- Type: %s\tValue: %d\tPeriod: %d seconds\n", p.Type, p.Value, p.PeriodSeconds)
}
}
}
}
func describeHorizontalPodAutoscalerV1(hpa *autoscalingv1.HorizontalPodAutoscaler, events *corev1.EventList, d *HorizontalPodAutoscalerDescriber) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)

View File

@ -1773,6 +1773,7 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
minReplicasVal := int32(2)
targetUtilizationVal := int32(80)
currentUtilizationVal := int32(50)
maxSelectPolicy := autoscalingv2beta2.MaxPolicySelect
metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
if err != nil {
t.Errorf("unable to parse label selector: %v", err)
@ -2417,6 +2418,75 @@ func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
},
},
},
{
"scale up behavior specified",
autoscalingv2beta2.HorizontalPodAutoscaler{
Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{
Name: "behavior-target",
Kind: "Deployment",
},
MinReplicas: &minReplicasVal,
MaxReplicas: 10,
Metrics: []autoscalingv2beta2.MetricSpec{
{
Type: autoscalingv2beta2.ResourceMetricSourceType,
Resource: &autoscalingv2beta2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2beta2.MetricTarget{
Type: autoscalingv2beta2.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
Behavior: &autoscalingv2beta2.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(30),
SelectPolicy: &maxSelectPolicy,
Policies: []autoscalingv2beta2.HPAScalingPolicy{
{Type: autoscalingv2beta2.PodsScalingPolicy, Value: 10, PeriodSeconds: 10},
{Type: autoscalingv2beta2.PercentScalingPolicy, Value: 10, PeriodSeconds: 10},
},
},
},
},
},
},
{
"scale down behavior specified",
autoscalingv2beta2.HorizontalPodAutoscaler{
Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{
Name: "behavior-target",
Kind: "Deployment",
},
MinReplicas: &minReplicasVal,
MaxReplicas: 10,
Metrics: []autoscalingv2beta2.MetricSpec{
{
Type: autoscalingv2beta2.ResourceMetricSourceType,
Resource: &autoscalingv2beta2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2beta2.MetricTarget{
Type: autoscalingv2beta2.UtilizationMetricType,
AverageUtilization: &targetUtilizationVal,
},
},
},
},
Behavior: &autoscalingv2beta2.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: utilpointer.Int32Ptr(30),
Policies: []autoscalingv2beta2.HPAScalingPolicy{
{Type: autoscalingv2beta2.PodsScalingPolicy, Value: 10, PeriodSeconds: 10},
{Type: autoscalingv2beta2.PercentScalingPolicy, Value: 10, PeriodSeconds: 10},
},
},
},
},
},
},
}
for _, test := range testsV2beta2 {