Skip to content

Commit

Permalink
Introducing option to avoid scaling if value is 0 by default
Browse files Browse the repository at this point in the history
  • Loading branch information
Charly Fontaine committed Oct 10, 2023
1 parent a26e061 commit a702a69
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 1 deletion.
3 changes: 3 additions & 0 deletions api/v1alpha1/watermarkpodautoscaler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ type WatermarkPodAutoscalerSpec struct {
// Whether planned scale changes are actually applied
DryRun bool `json:"dryRun,omitempty"`

// Zero is a value that can lead to undesired outcomes, unless explicitely set the WPA will not take action if the value retrieved is 0.

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / build

`explicitely` is a misspelling of `explicitly` (misspell)

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / e2e (v1.14.10)

`explicitely` is a misspelling of `explicitly` (misspell)

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

`explicitely` is a misspelling of `explicitly` (misspell)

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / build

`explicitely` is a misspelling of `explicitly` (misspell)

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / e2e (v1.16.9)

`explicitely` is a misspelling of `explicitly` (misspell)

Check failure on line 108 in api/v1alpha1/watermarkpodautoscaler_types.go

View workflow job for this annotation

GitHub Actions / e2e (v1.21.1)

`explicitely` is a misspelling of `explicitly` (misspell)
TolerateZero bool `json:"tolerateZero,omitempty"`

// part of HorizontalPodAutoscalerSpec, see comments in the k8s-1.10.8 repo: staging/src/k8s.io/api/autoscaling/v1/types.go
// reference to scaled resource; horizontal pod autoscaler will learn the current resource consumption
// and will set the desired number of pods by using its Scale subresource.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ spec:
transition seamlessly, we validate that it is ]0;1[ in the code.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
tolerateZero:
description: Zero is a value that can lead to undesired outcomes,
unless explicitely set the WPA will not take action if the value
retrieved is 0.
type: boolean
upscaleDelayAboveWatermarkSeconds:
format: int32
minimum: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ spec:
description: Parameter used to be a float, in order to support the transition seamlessly, we validate that it is ]0;1[ in the code.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
tolerateZero:
description: Zero is a value that can lead to undesired outcomes, unless explicitely set the WPA will not take action if the value retrieved is 0.
type: boolean
upscaleDelayAboveWatermarkSeconds:
format: int32
minimum: 0
Expand Down
7 changes: 6 additions & 1 deletion controllers/replica_calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,17 @@ func (c *ReplicaCalculator) GetExternalMetricReplicas(logger logr.Logger, target
value.Delete(prometheus.Labels{wpaNamePromLabel: wpa.Name, metricNamePromLabel: metricName})
return ReplicaCalculation{0, 0, time.Time{}, 0, metricPosition{}}, fmt.Errorf("unable to get external metric %s/%s/%+v: %s", wpa.Namespace, metricName, selector, err) //nolint:errorlint
}
logger.Info("Metrics from the External Metrics Provider", "metrics", metrics)
logger.V(2).Info("Metrics from the External Metrics Provider", "metrics", metrics)

var sum int64
for _, val := range metrics {
sum += val
}
if sum == 0 && !wpa.Spec.TolerateZero {
logger.V(1).Info("Retrieved 0 from the External Metrics Provider which is not ignored by default, use Spec.TolerateZero to enable")
// We artificially set the metricPos between the watermarks to force the controller not to scale the target.
return ReplicaCalculation{currentReadyReplicas, 0, timestamp, currentReadyReplicas, metricPosition{false, false}}, nil
}

// if the average algorithm is used, the metrics retrieved has to be divided by the number of available replicas.
adjustedUsage := float64(sum) / averaged
Expand Down
75 changes: 75 additions & 0 deletions controllers/replica_calculator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,81 @@ func TestReplicaCalcAboveAbsoluteExternal_Upscale1(t *testing.T) {
tc.runTest(t)
}

func TestTolerateZeroDefault(t *testing.T) {
logf.SetLogger(zap.New())
metric1 := v1alpha1.MetricSpec{
Type: v1alpha1.ExternalMetricSourceType,
External: &v1alpha1.ExternalMetricSource{
MetricName: "deadbeef",
MetricSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
HighWatermark: resource.NewMilliQuantity(4000, resource.DecimalSI),
LowWatermark: resource.NewMilliQuantity(2000, resource.DecimalSI),
},
}

tc := replicaCalcTestCase{
expectedReplicas: 3,
readyReplicas: 3,
pos: metricPosition{
isAbove: false,
isBelow: false,
},
scale: makeScale(testDeploymentName, 3, map[string]string{"name": "test-pod"}),
wpa: &v1alpha1.WatermarkPodAutoscaler{
Spec: v1alpha1.WatermarkPodAutoscalerSpec{
Algorithm: "absolute",
Tolerance: *resource.NewMilliQuantity(20, resource.DecimalSI),
Metrics: []v1alpha1.MetricSpec{metric1},
ReplicaScalingAbsoluteModulo: v1alpha1.NewInt32(1),
},
},
metric: &metricInfo{
spec: metric1,
levels: []int64{0}, // We are much lower than the LowWatermark
expectedUtilization: 0,
},
}
tc.runTest(t)
}

func TestTolerateZeroEnabled(t *testing.T) {
logf.SetLogger(zap.New())
metric1 := v1alpha1.MetricSpec{
Type: v1alpha1.ExternalMetricSourceType,
External: &v1alpha1.ExternalMetricSource{
MetricName: "deadbeef",
MetricSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
HighWatermark: resource.NewMilliQuantity(4000, resource.DecimalSI),
LowWatermark: resource.NewMilliQuantity(2000, resource.DecimalSI),
},
}

tc := replicaCalcTestCase{
expectedReplicas: 1,
readyReplicas: 3,
pos: metricPosition{
isAbove: false,
isBelow: true,
},
scale: makeScale(testDeploymentName, 3, map[string]string{"name": "test-pod"}),
wpa: &v1alpha1.WatermarkPodAutoscaler{
Spec: v1alpha1.WatermarkPodAutoscalerSpec{
Algorithm: "absolute",
TolerateZero: true,
Tolerance: *resource.NewMilliQuantity(20, resource.DecimalSI),
Metrics: []v1alpha1.MetricSpec{metric1},
ReplicaScalingAbsoluteModulo: v1alpha1.NewInt32(1),
},
},
metric: &metricInfo{
spec: metric1,
levels: []int64{0}, // We are much lower than the LowWatermark
expectedUtilization: 0,
},
}
tc.runTest(t)
}

func TestReplicaCalcAboveAbsoluteExternal_Upscale2(t *testing.T) {
logf.SetLogger(zap.New())

Expand Down

0 comments on commit a702a69

Please sign in to comment.