mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Fix API doc and tolerance field handling when gate is flipped.
This commit is contained in:
parent
2dd9eda47f
commit
dc1696d807
@ -174,6 +174,10 @@ type HPAScalingRules struct {
|
|||||||
// replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not
|
// replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not
|
||||||
// set, the default cluster-wide tolerance is applied (by default 10%).
|
// set, the default cluster-wide tolerance is applied (by default 10%).
|
||||||
//
|
//
|
||||||
|
// For example, if autoscaling is configured with a memory consumption target of 100Mi,
|
||||||
|
// and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be
|
||||||
|
// triggered when the actual consumption falls below 95Mi or exceeds 101Mi.
|
||||||
|
//
|
||||||
// This is an alpha field and requires enabling the HPAConfigurableTolerance
|
// This is an alpha field and requires enabling the HPAConfigurableTolerance
|
||||||
// feature gate.
|
// feature gate.
|
||||||
//
|
//
|
||||||
|
@ -23,11 +23,9 @@ import (
|
|||||||
pathvalidation "k8s.io/apimachinery/pkg/api/validation/path"
|
pathvalidation "k8s.io/apimachinery/pkg/api/validation/path"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||||
corevalidation "k8s.io/kubernetes/pkg/apis/core/v1/validation"
|
corevalidation "k8s.io/kubernetes/pkg/apis/core/v1/validation"
|
||||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -103,48 +101,16 @@ func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectRefer
|
|||||||
|
|
||||||
// ValidateHorizontalPodAutoscaler validates a HorizontalPodAutoscaler and returns an
|
// ValidateHorizontalPodAutoscaler validates a HorizontalPodAutoscaler and returns an
|
||||||
// ErrorList with any errors.
|
// ErrorList with any errors.
|
||||||
func ValidateHorizontalPodAutoscaler(autoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList {
|
func ValidateHorizontalPodAutoscaler(autoscaler *autoscaling.HorizontalPodAutoscaler, opts HorizontalPodAutoscalerSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata"))
|
allErrs := apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, field.NewPath("metadata"))
|
||||||
|
|
||||||
// MinReplicasLowerBound represents a minimum value for minReplicas
|
|
||||||
// 0 when HPA scale-to-zero feature is enabled
|
|
||||||
var minReplicasLowerBound int32
|
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) {
|
|
||||||
minReplicasLowerBound = 0
|
|
||||||
} else {
|
|
||||||
minReplicasLowerBound = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := HorizontalPodAutoscalerSpecValidationOptions{
|
|
||||||
AllowTolerance: utilfeature.DefaultMutableFeatureGate.Enabled(features.HPAConfigurableTolerance),
|
|
||||||
MinReplicasLowerBound: minReplicasLowerBound,
|
|
||||||
}
|
|
||||||
|
|
||||||
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"), opts)...)
|
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"), opts)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateHorizontalPodAutoscalerUpdate validates an update to a HorizontalPodAutoscaler and returns an
|
// ValidateHorizontalPodAutoscalerUpdate validates an update to a HorizontalPodAutoscaler and returns an
|
||||||
// ErrorList with any errors.
|
// ErrorList with any errors.
|
||||||
func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler) field.ErrorList {
|
func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *autoscaling.HorizontalPodAutoscaler, opts HorizontalPodAutoscalerSpecValidationOptions) field.ErrorList {
|
||||||
allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata"))
|
allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata"))
|
||||||
|
|
||||||
// minReplicasLowerBound represents a minimum value for minReplicas
|
|
||||||
// 0 when HPA scale-to-zero feature is enabled or HPA object already has minReplicas=0
|
|
||||||
var minReplicasLowerBound int32
|
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) || (oldAutoscaler.Spec.MinReplicas != nil && *oldAutoscaler.Spec.MinReplicas == 0) {
|
|
||||||
minReplicasLowerBound = 0
|
|
||||||
} else {
|
|
||||||
minReplicasLowerBound = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := HorizontalPodAutoscalerSpecValidationOptions{
|
|
||||||
AllowTolerance: utilfeature.DefaultMutableFeatureGate.Enabled(features.HPAConfigurableTolerance),
|
|
||||||
MinReplicasLowerBound: minReplicasLowerBound,
|
|
||||||
}
|
|
||||||
|
|
||||||
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, field.NewPath("spec"), opts)...)
|
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, field.NewPath("spec"), opts)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -162,8 +128,6 @@ func ValidateHorizontalPodAutoscalerStatusUpdate(newAutoscaler, oldAutoscaler *a
|
|||||||
// HorizontalPodAutoscalerSpecValidationOptions contains the different settings for
|
// HorizontalPodAutoscalerSpecValidationOptions contains the different settings for
|
||||||
// HorizontalPodAutoscaler spec validation.
|
// HorizontalPodAutoscaler spec validation.
|
||||||
type HorizontalPodAutoscalerSpecValidationOptions struct {
|
type HorizontalPodAutoscalerSpecValidationOptions struct {
|
||||||
// Allow setting a tolerance on HPAScalingRules.
|
|
||||||
AllowTolerance bool
|
|
||||||
// The minimum value for minReplicas.
|
// The minimum value for minReplicas.
|
||||||
MinReplicasLowerBound int32
|
MinReplicasLowerBound int32
|
||||||
}
|
}
|
||||||
@ -234,12 +198,8 @@ func validateScalingRules(rules *autoscaling.HPAScalingRules, fldPath *field.Pat
|
|||||||
allErrs = append(allErrs, policyErrs...)
|
allErrs = append(allErrs, policyErrs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rules.Tolerance != nil {
|
||||||
if rules.Tolerance != nil && !opts.AllowTolerance {
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeQuantity(*rules.Tolerance, fldPath.Child("tolerance"))...)
|
||||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("tolerance"), "tolerance is not supported when the HPAConfigurableTolerance feature gate is not enabled"))
|
|
||||||
}
|
|
||||||
if rules.Tolerance != nil && rules.Tolerance.Sign() < 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("tolerance"), rules.Tolerance, "must be greater or equal to zero"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
|
@ -22,15 +22,21 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
"k8s.io/utils/ptr"
|
"k8s.io/utils/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hpaSpecValidationOpts = HorizontalPodAutoscalerSpecValidationOptions{
|
||||||
|
MinReplicasLowerBound: 1,
|
||||||
|
}
|
||||||
|
hpaScaleToZeroSpecValidationOpts = HorizontalPodAutoscalerSpecValidationOptions{
|
||||||
|
MinReplicasLowerBound: 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func TestValidateScale(t *testing.T) {
|
func TestValidateScale(t *testing.T) {
|
||||||
successCases := []autoscaling.Scale{{
|
successCases := []autoscaling.Scale{{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@ -163,7 +169,7 @@ func TestValidateBehavior(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
for _, behavior := range successCases {
|
for _, behavior := range successCases {
|
||||||
hpa := prepareHPAWithBehavior(behavior)
|
hpa := prepareHPAWithBehavior(behavior)
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&hpa, hpaSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,7 +363,7 @@ func TestValidateBehavior(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
for _, c := range errorCases {
|
for _, c := range errorCases {
|
||||||
hpa := prepareHPAWithBehavior(c.behavior)
|
hpa := prepareHPAWithBehavior(c.behavior)
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) == 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&hpa, hpaSpecValidationOpts); len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %s", c.msg)
|
t.Errorf("expected failure for %s", c.msg)
|
||||||
} else if !strings.Contains(errs[0].Error(), c.msg) {
|
} else if !strings.Contains(errs[0].Error(), c.msg) {
|
||||||
t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg)
|
t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg)
|
||||||
@ -615,7 +621,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
for _, successCase := range successCases {
|
for _, successCase := range successCases {
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&successCase, hpaSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1419,7 +1425,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range errorCases {
|
for _, c := range errorCases {
|
||||||
errs := ValidateHorizontalPodAutoscaler(&c.horizontalPodAutoscaler)
|
errs := ValidateHorizontalPodAutoscaler(&c.horizontalPodAutoscaler, hpaSpecValidationOpts)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %q", c.msg)
|
t.Errorf("expected failure for %q", c.msg)
|
||||||
} else if !strings.Contains(errs[0].Error(), c.msg) {
|
} else if !strings.Contains(errs[0].Error(), c.msg) {
|
||||||
@ -1490,7 +1496,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
|
|||||||
MinReplicas: utilpointer.Int32(1),
|
MinReplicas: utilpointer.Int32(1),
|
||||||
MaxReplicas: 5, Metrics: []autoscaling.MetricSpec{spec},
|
MaxReplicas: 5, Metrics: []autoscaling.MetricSpec{spec},
|
||||||
},
|
},
|
||||||
})
|
}, hpaSpecValidationOpts)
|
||||||
|
|
||||||
expectedMsg := "must populate information for the given metric source"
|
expectedMsg := "must populate information for the given metric source"
|
||||||
|
|
||||||
@ -1570,26 +1576,20 @@ func prepareMinReplicasCases(t *testing.T, minReplicas int32) []autoscaling.Hori
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerScaleToZeroEnabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerScaleToZeroEnabled(t *testing.T) {
|
||||||
// Enable HPAScaleToZero feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, true)
|
|
||||||
|
|
||||||
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
||||||
for _, successCase := range zeroMinReplicasCases {
|
for _, successCase := range zeroMinReplicasCases {
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&successCase, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerScaleToZeroDisabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerScaleToZeroDisabled(t *testing.T) {
|
||||||
// Disable HPAScaleToZero feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, false)
|
|
||||||
|
|
||||||
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
||||||
errorMsg := "must be greater than or equal to 1"
|
errorMsg := "must be greater than or equal to 1"
|
||||||
|
|
||||||
for _, errorCase := range zeroMinReplicasCases {
|
for _, errorCase := range zeroMinReplicasCases {
|
||||||
errs := ValidateHorizontalPodAutoscaler(&errorCase)
|
errs := ValidateHorizontalPodAutoscaler(&errorCase, hpaSpecValidationOpts)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %q", errorMsg)
|
t.Errorf("expected failure for %q", errorMsg)
|
||||||
} else if !strings.Contains(errs[0].Error(), errorMsg) {
|
} else if !strings.Contains(errs[0].Error(), errorMsg) {
|
||||||
@ -1601,43 +1601,37 @@ func TestValidateHorizontalPodAutoscalerScaleToZeroDisabled(t *testing.T) {
|
|||||||
|
|
||||||
for _, successCase := range nonZeroMinReplicasCases {
|
for _, successCase := range nonZeroMinReplicasCases {
|
||||||
successCase.Spec.MinReplicas = utilpointer.Int32(1)
|
successCase.Spec.MinReplicas = utilpointer.Int32(1)
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&successCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&successCase, hpaSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerUpdateScaleToZeroEnabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerUpdateScaleToZeroEnabled(t *testing.T) {
|
||||||
// Enable HPAScaleToZero feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, true)
|
|
||||||
|
|
||||||
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
||||||
nonZeroMinReplicasCases := prepareMinReplicasCases(t, 1)
|
nonZeroMinReplicasCases := prepareMinReplicasCases(t, 1)
|
||||||
|
|
||||||
for i, zeroCase := range zeroMinReplicasCases {
|
for i, zeroCase := range zeroMinReplicasCases {
|
||||||
nonZeroCase := nonZeroMinReplicasCases[i]
|
nonZeroCase := nonZeroMinReplicasCases[i]
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&nonZeroCase, &zeroCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscalerUpdate(&nonZeroCase, &zeroCase, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&zeroCase, &nonZeroCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscalerUpdate(&zeroCase, &nonZeroCase, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerScaleToZeroUpdateDisabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerScaleToZeroUpdateDisabled(t *testing.T) {
|
||||||
// Disable HPAScaleToZero feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, false)
|
|
||||||
|
|
||||||
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
zeroMinReplicasCases := prepareMinReplicasCases(t, 0)
|
||||||
nonZeroMinReplicasCases := prepareMinReplicasCases(t, 1)
|
nonZeroMinReplicasCases := prepareMinReplicasCases(t, 1)
|
||||||
errorMsg := "must be greater than or equal to 1"
|
errorMsg := "must be greater than or equal to 1"
|
||||||
|
|
||||||
for i, zeroCase := range zeroMinReplicasCases {
|
for i, zeroCase := range zeroMinReplicasCases {
|
||||||
nonZeroCase := nonZeroMinReplicasCases[i]
|
nonZeroCase := nonZeroMinReplicasCases[i]
|
||||||
errs := ValidateHorizontalPodAutoscalerUpdate(&zeroCase, &nonZeroCase)
|
errs := ValidateHorizontalPodAutoscalerUpdate(&zeroCase, &nonZeroCase, hpaSpecValidationOpts)
|
||||||
|
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("expected failure for %q", errorMsg)
|
t.Errorf("expected failure for %q", errorMsg)
|
||||||
@ -1645,20 +1639,13 @@ func TestValidateHorizontalPodAutoscalerScaleToZeroUpdateDisabled(t *testing.T)
|
|||||||
t.Errorf("unexpected error: %q, expected: %q", errs[0], errorMsg)
|
t.Errorf("unexpected error: %q, expected: %q", errs[0], errorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&zeroCase, &zeroCase); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscalerUpdate(&nonZeroCase, &zeroCase, hpaSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&nonZeroCase, &zeroCase); len(errs) != 0 {
|
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.T) {
|
||||||
// Enable HPAConfigurableTolerance feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, true)
|
|
||||||
|
|
||||||
policiesList := []autoscaling.HPAScalingPolicy{{
|
policiesList := []autoscaling.HPAScalingPolicy{{
|
||||||
Type: autoscaling.PodsScalingPolicy,
|
Type: autoscaling.PodsScalingPolicy,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@ -1695,7 +1682,7 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.
|
|||||||
ScaleDown: &c,
|
ScaleDown: &c,
|
||||||
}
|
}
|
||||||
hpa := prepareHPAWithBehavior(b)
|
hpa := prepareHPAWithBehavior(b)
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&hpa, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1713,14 +1700,14 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.
|
|||||||
Policies: policiesList,
|
Policies: policiesList,
|
||||||
Tolerance: ptr.To(resource.MustParse("-0.001")),
|
Tolerance: ptr.To(resource.MustParse("-0.001")),
|
||||||
},
|
},
|
||||||
msg: "greater or equal to zero",
|
msg: "greater than or equal to 0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rule: autoscaling.HPAScalingRules{
|
rule: autoscaling.HPAScalingRules{
|
||||||
Policies: policiesList,
|
Policies: policiesList,
|
||||||
Tolerance: resource.NewMilliQuantity(-10, resource.DecimalSI),
|
Tolerance: resource.NewMilliQuantity(-10, resource.DecimalSI),
|
||||||
},
|
},
|
||||||
msg: "greater or equal to zero",
|
msg: "greater than or equal to 0",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rule: autoscaling.HPAScalingRules{
|
rule: autoscaling.HPAScalingRules{
|
||||||
@ -1741,7 +1728,7 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.
|
|||||||
ScaleUp: &c.rule,
|
ScaleUp: &c.rule,
|
||||||
}
|
}
|
||||||
hpa := prepareHPAWithBehavior(b)
|
hpa := prepareHPAWithBehavior(b)
|
||||||
errs := ValidateHorizontalPodAutoscaler(&hpa)
|
errs := ValidateHorizontalPodAutoscaler(&hpa, hpaScaleToZeroSpecValidationOpts)
|
||||||
if len(errs) != 1 {
|
if len(errs) != 1 {
|
||||||
t.Fatalf("expected exactly one error, got: %v", errs)
|
t.Fatalf("expected exactly one error, got: %v", errs)
|
||||||
}
|
}
|
||||||
@ -1752,9 +1739,6 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceEnabled(t *testing.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing.T) {
|
||||||
// Disable HPAConfigurableTolerance feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, false)
|
|
||||||
|
|
||||||
maxPolicy := autoscaling.MaxPolicySelect
|
maxPolicy := autoscaling.MaxPolicySelect
|
||||||
policiesList := []autoscaling.HPAScalingPolicy{{
|
policiesList := []autoscaling.HPAScalingPolicy{{
|
||||||
Type: autoscaling.PodsScalingPolicy,
|
Type: autoscaling.PodsScalingPolicy,
|
||||||
@ -1780,7 +1764,7 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing
|
|||||||
ScaleDown: &c,
|
ScaleDown: &c,
|
||||||
}
|
}
|
||||||
hpa := prepareHPAWithBehavior(b)
|
hpa := prepareHPAWithBehavior(b)
|
||||||
if errs := ValidateHorizontalPodAutoscaler(&hpa); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscaler(&hpa, hpaSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1789,13 +1773,6 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing
|
|||||||
rule autoscaling.HPAScalingRules
|
rule autoscaling.HPAScalingRules
|
||||||
msg string
|
msg string
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
rule: autoscaling.HPAScalingRules{
|
|
||||||
Policies: policiesList,
|
|
||||||
Tolerance: resource.NewMilliQuantity(1, resource.DecimalSI),
|
|
||||||
},
|
|
||||||
msg: "not supported",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule: autoscaling.HPAScalingRules{},
|
rule: autoscaling.HPAScalingRules{},
|
||||||
msg: "at least one Policy",
|
msg: "at least one Policy",
|
||||||
@ -1812,7 +1789,7 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing
|
|||||||
ScaleUp: &c.rule,
|
ScaleUp: &c.rule,
|
||||||
}
|
}
|
||||||
hpa := prepareHPAWithBehavior(b)
|
hpa := prepareHPAWithBehavior(b)
|
||||||
errs := ValidateHorizontalPodAutoscaler(&hpa)
|
errs := ValidateHorizontalPodAutoscaler(&hpa, hpaSpecValidationOpts)
|
||||||
if len(errs) != 1 {
|
if len(errs) != 1 {
|
||||||
t.Fatalf("expected exactly one error, got: %v", errs)
|
t.Fatalf("expected exactly one error, got: %v", errs)
|
||||||
}
|
}
|
||||||
@ -1823,9 +1800,6 @@ func TestValidateHorizontalPodAutoscalerConfigurableToleranceDisabled(t *testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerUpdateConfigurableToleranceEnabled(t *testing.T) {
|
func TestValidateHorizontalPodAutoscalerUpdateConfigurableToleranceEnabled(t *testing.T) {
|
||||||
// Enable HPAConfigurableTolerance feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, true)
|
|
||||||
|
|
||||||
policiesList := []autoscaling.HPAScalingPolicy{{
|
policiesList := []autoscaling.HPAScalingPolicy{{
|
||||||
Type: autoscaling.PodsScalingPolicy,
|
Type: autoscaling.PodsScalingPolicy,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@ -1842,48 +1816,11 @@ func TestValidateHorizontalPodAutoscalerUpdateConfigurableToleranceEnabled(t *te
|
|||||||
Policies: policiesList,
|
Policies: policiesList,
|
||||||
}})
|
}})
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&withToleranceHPA, &withoutToleranceHPA); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscalerUpdate(&withToleranceHPA, &withoutToleranceHPA, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&withoutToleranceHPA, &withToleranceHPA); len(errs) != 0 {
|
if errs := ValidateHorizontalPodAutoscalerUpdate(&withoutToleranceHPA, &withToleranceHPA, hpaScaleToZeroSpecValidationOpts); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateHorizontalPodAutoscalerConfigurableToleranceUpdateDisabled(t *testing.T) {
|
|
||||||
// Disable HPAConfigurableTolerance feature gate.
|
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, false)
|
|
||||||
|
|
||||||
policiesList := []autoscaling.HPAScalingPolicy{{
|
|
||||||
Type: autoscaling.PodsScalingPolicy,
|
|
||||||
Value: 1,
|
|
||||||
PeriodSeconds: 1800,
|
|
||||||
}}
|
|
||||||
|
|
||||||
withToleranceHPA := prepareHPAWithBehavior(autoscaling.HorizontalPodAutoscalerBehavior{
|
|
||||||
ScaleUp: &autoscaling.HPAScalingRules{
|
|
||||||
Policies: policiesList,
|
|
||||||
Tolerance: resource.NewMilliQuantity(10, resource.DecimalSI),
|
|
||||||
}})
|
|
||||||
withoutToleranceHPA := prepareHPAWithBehavior(autoscaling.HorizontalPodAutoscalerBehavior{
|
|
||||||
ScaleUp: &autoscaling.HPAScalingRules{
|
|
||||||
Policies: policiesList,
|
|
||||||
}})
|
|
||||||
notSupportedErrorMsg := "not supported"
|
|
||||||
|
|
||||||
errs := ValidateHorizontalPodAutoscalerUpdate(&withToleranceHPA, &withoutToleranceHPA)
|
|
||||||
if len(errs) == 0 {
|
|
||||||
t.Errorf("expected failure for %q", notSupportedErrorMsg)
|
|
||||||
} else if !strings.Contains(errs[0].Error(), notSupportedErrorMsg) {
|
|
||||||
t.Errorf("unexpected error: %q, expected: %q", errs[0], notSupportedErrorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&withoutToleranceHPA, &withoutToleranceHPA); len(errs) != 0 {
|
|
||||||
t.Errorf("expected success: %v", errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs := ValidateHorizontalPodAutoscalerUpdate(&withoutToleranceHPA, &withToleranceHPA); len(errs) != 0 {
|
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,11 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||||
"k8s.io/kubernetes/pkg/apis/autoscaling/validation"
|
"k8s.io/kubernetes/pkg/apis/autoscaling/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,12 +72,15 @@ func (autoscalerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Obje
|
|||||||
|
|
||||||
// create cannot set status
|
// create cannot set status
|
||||||
newHPA.Status = autoscaling.HorizontalPodAutoscalerStatus{}
|
newHPA.Status = autoscaling.HorizontalPodAutoscalerStatus{}
|
||||||
|
|
||||||
|
dropDisabledFields(newHPA, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a new autoscaler.
|
// Validate validates a new autoscaler.
|
||||||
func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
autoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
|
autoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
|
||||||
return validation.ValidateHorizontalPodAutoscaler(autoscaler)
|
opts := validationOptionsForHorizontalPodAutoscaler(autoscaler, nil)
|
||||||
|
return validation.ValidateHorizontalPodAutoscaler(autoscaler, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
@ -98,11 +103,16 @@ func (autoscalerStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime
|
|||||||
oldHPA := old.(*autoscaling.HorizontalPodAutoscaler)
|
oldHPA := old.(*autoscaling.HorizontalPodAutoscaler)
|
||||||
// Update is not allowed to set status
|
// Update is not allowed to set status
|
||||||
newHPA.Status = oldHPA.Status
|
newHPA.Status = oldHPA.Status
|
||||||
|
|
||||||
|
dropDisabledFields(newHPA, oldHPA)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateHorizontalPodAutoscalerUpdate(obj.(*autoscaling.HorizontalPodAutoscaler), old.(*autoscaling.HorizontalPodAutoscaler))
|
newHPA := obj.(*autoscaling.HorizontalPodAutoscaler)
|
||||||
|
oldHPA := old.(*autoscaling.HorizontalPodAutoscaler)
|
||||||
|
opts := validationOptionsForHorizontalPodAutoscaler(newHPA, oldHPA)
|
||||||
|
return validation.ValidateHorizontalPodAutoscalerUpdate(newHPA, oldHPA, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnUpdate returns warnings for the given update.
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
@ -157,3 +167,48 @@ func (autoscalerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
|||||||
func (autoscalerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
func (autoscalerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validationOptionsForHorizontalPodAutoscaler(newHPA, oldHPA *autoscaling.HorizontalPodAutoscaler) validation.HorizontalPodAutoscalerSpecValidationOptions {
|
||||||
|
opts := validation.HorizontalPodAutoscalerSpecValidationOptions{
|
||||||
|
MinReplicasLowerBound: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
oldHasZeroMinReplicas := oldHPA != nil && (oldHPA.Spec.MinReplicas != nil && *oldHPA.Spec.MinReplicas == 0)
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.HPAScaleToZero) || oldHasZeroMinReplicas {
|
||||||
|
opts.MinReplicasLowerBound = 0
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropDisabledFields will drop any disabled fields that have not previously been
|
||||||
|
// set on the old HPA. oldHPA is ignored if nil.
|
||||||
|
func dropDisabledFields(newHPA, oldHPA *autoscaling.HorizontalPodAutoscaler) {
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.HPAConfigurableTolerance) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if toleranceInUse(oldHPA) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newBehavior := newHPA.Spec.Behavior
|
||||||
|
if newBehavior == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sr := range []*autoscaling.HPAScalingRules{newBehavior.ScaleDown, newBehavior.ScaleUp} {
|
||||||
|
if sr != nil {
|
||||||
|
sr.Tolerance = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toleranceInUse(hpa *autoscaling.HorizontalPodAutoscaler) bool {
|
||||||
|
if hpa == nil || hpa.Spec.Behavior == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, sr := range []*autoscaling.HPAScalingRules{hpa.Spec.Behavior.ScaleDown, hpa.Spec.Behavior.ScaleUp} {
|
||||||
|
if sr != nil && sr.Tolerance != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 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 horizontalpodautoscaler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type toleranceSet bool
|
||||||
|
type zeroMinReplicasSet bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
withTolerance toleranceSet = true
|
||||||
|
withoutTolerance = false
|
||||||
|
zeroMinReplicas zeroMinReplicasSet = true
|
||||||
|
oneMinReplicas = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrepareForCreateConfigurableToleranceEnabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, true)
|
||||||
|
hpa := prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(context.Background(), &hpa)
|
||||||
|
if hpa.Spec.Behavior.ScaleUp.Tolerance == nil {
|
||||||
|
t.Error("Expected tolerance field, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareForCreateConfigurableToleranceDisabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, false)
|
||||||
|
hpa := prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
|
||||||
|
Strategy.PrepareForCreate(context.Background(), &hpa)
|
||||||
|
if hpa.Spec.Behavior.ScaleUp.Tolerance != nil {
|
||||||
|
t.Errorf("Expected tolerance field wiped out, got %v", hpa.Spec.Behavior.ScaleUp.Tolerance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareForUpdateConfigurableToleranceEnabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, true)
|
||||||
|
newHPA := prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
oldHPA := prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
|
||||||
|
Strategy.PrepareForUpdate(context.Background(), &newHPA, &oldHPA)
|
||||||
|
if newHPA.Spec.Behavior.ScaleUp.Tolerance == nil {
|
||||||
|
t.Error("Expected tolerance field, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareForUpdateConfigurableToleranceDisabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAConfigurableTolerance, false)
|
||||||
|
newHPA := prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
oldHPA := prepareHPA(oneMinReplicas, withoutTolerance)
|
||||||
|
|
||||||
|
Strategy.PrepareForUpdate(context.Background(), &newHPA, &oldHPA)
|
||||||
|
if newHPA.Spec.Behavior.ScaleUp.Tolerance != nil {
|
||||||
|
t.Errorf("Expected tolerance field wiped out, got %v", newHPA.Spec.Behavior.ScaleUp.Tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
newHPA = prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
oldHPA = prepareHPA(oneMinReplicas, withTolerance)
|
||||||
|
Strategy.PrepareForUpdate(context.Background(), &newHPA, &oldHPA)
|
||||||
|
if newHPA.Spec.Behavior.ScaleUp.Tolerance == nil {
|
||||||
|
t.Errorf("Expected tolerance field not wiped out, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateOptionsScaleToZeroEnabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, true)
|
||||||
|
oneReplicasHPA := prepareHPA(oneMinReplicas, withoutTolerance)
|
||||||
|
|
||||||
|
opts := validationOptionsForHorizontalPodAutoscaler(&oneReplicasHPA, &oneReplicasHPA)
|
||||||
|
if opts.MinReplicasLowerBound != 0 {
|
||||||
|
t.Errorf("Expected zero minReplicasLowerBound, got %v", opts.MinReplicasLowerBound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateOptionsScaleToZeroDisabled(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HPAScaleToZero, false)
|
||||||
|
zeroReplicasHPA := prepareHPA(zeroMinReplicas, withoutTolerance)
|
||||||
|
oneReplicasHPA := prepareHPA(oneMinReplicas, withoutTolerance)
|
||||||
|
|
||||||
|
// MinReplicas should be 0 despite the gate being disabled since the old HPA
|
||||||
|
// had MinReplicas set to 0 already.
|
||||||
|
opts := validationOptionsForHorizontalPodAutoscaler(&zeroReplicasHPA, &zeroReplicasHPA)
|
||||||
|
if opts.MinReplicasLowerBound != 0 {
|
||||||
|
t.Errorf("Expected zero minReplicasLowerBound, got %v", opts.MinReplicasLowerBound)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = validationOptionsForHorizontalPodAutoscaler(&zeroReplicasHPA, &oneReplicasHPA)
|
||||||
|
if opts.MinReplicasLowerBound == 0 {
|
||||||
|
t.Errorf("Expected non-zero minReplicasLowerBound, got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareHPA(hasZeroMinReplicas zeroMinReplicasSet, hasTolerance toleranceSet) autoscaling.HorizontalPodAutoscaler {
|
||||||
|
tolerance := ptr.To(resource.MustParse("0.1"))
|
||||||
|
if !hasTolerance {
|
||||||
|
tolerance = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
minReplicas := int32(0)
|
||||||
|
if !hasZeroMinReplicas {
|
||||||
|
minReplicas = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoscaling.HorizontalPodAutoscaler{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "myautoscaler",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Spec: autoscaling.HorizontalPodAutoscalerSpec{
|
||||||
|
ScaleTargetRef: autoscaling.CrossVersionObjectReference{
|
||||||
|
Kind: "ReplicationController",
|
||||||
|
Name: "myrc",
|
||||||
|
},
|
||||||
|
MinReplicas: &minReplicas,
|
||||||
|
MaxReplicas: 5,
|
||||||
|
Metrics: []autoscaling.MetricSpec{{
|
||||||
|
Type: autoscaling.ResourceMetricSourceType,
|
||||||
|
Resource: &autoscaling.ResourceMetricSource{
|
||||||
|
Name: api.ResourceCPU,
|
||||||
|
Target: autoscaling.MetricTarget{
|
||||||
|
Type: autoscaling.UtilizationMetricType,
|
||||||
|
AverageUtilization: ptr.To(int32(70)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Behavior: &autoscaling.HorizontalPodAutoscalerBehavior{
|
||||||
|
ScaleUp: &autoscaling.HPAScalingRules{
|
||||||
|
Tolerance: tolerance,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -211,6 +211,10 @@ type HPAScalingRules struct {
|
|||||||
// replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not
|
// replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not
|
||||||
// set, the default cluster-wide tolerance is applied (by default 10%).
|
// set, the default cluster-wide tolerance is applied (by default 10%).
|
||||||
//
|
//
|
||||||
|
// For example, if autoscaling is configured with a memory consumption target of 100Mi,
|
||||||
|
// and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be
|
||||||
|
// triggered when the actual consumption falls below 95Mi or exceeds 101Mi.
|
||||||
|
//
|
||||||
// This is an alpha field and requires enabling the HPAConfigurableTolerance
|
// This is an alpha field and requires enabling the HPAConfigurableTolerance
|
||||||
// feature gate.
|
// feature gate.
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user