Change:
return a.updateStatusIfNeeded(hpaStatusOriginal, hpa) } <ins> // normalizeDesiredReplicas takes the metrics desired replicas value and normalizes it based on the appropriate conditions (i.e. < maxReplicas, > // minReplicas, etc...) func (a *HorizontalController) normalizeDesiredReplicas(hpa *autoscalingv2.HorizontalPodAutoscaler, currentReplicas int32, prenormalizedDesiredReplicas int32) int32 { var minReplicas int32 if hpa.Spec.MinReplicas != nil { minReplicas = *hpa.Spec.MinReplicas } else { minReplicas = 0 } desiredReplicas, condition, reason := convertDesiredReplicasWithRules(currentReplicas, prenormalizedDesiredReplicas, minReplicas, hpa.Spec.MaxReplicas) if desiredReplicas == prenormalizedDesiredReplicas { setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionFalse, condition, reason) } else { setCondition(hpa, autoscalingv2.ScalingLimited, v1.ConditionTrue, condition, reason) } return desiredReplicas } // convertDesiredReplicas performs the actual normalization, without depending on `HorizontalController` or `HorizontalPodAutoscaler` func convertDesiredReplicasWithRules(currentReplicas, desiredReplicas, hpaMinReplicas, hpaMaxReplicas int32) (int32, string, string) { var minimumAllowedReplicas int32 var maximumAllowedReplicas int32 var possibleLimitingCondition string var possibleLimitingReason string if hpaMinReplicas == 0 { minimumAllowedReplicas = 1 possibleLimitingReason = "the desired replica count is zero" } else { minimumAllowedReplicas = hpaMinReplicas possibleLimitingReason = "the desired replica count is less than the minimum replica count" } // Do not upscale too much to prevent incorrect rapid increase of the number of master replicas caused by // bogus CPU usage report from heapster/kubelet (like in issue #32304). scaleUpLimit := calculateScaleUpLimit(currentReplicas) if hpaMaxReplicas > scaleUpLimit { maximumAllowedReplicas = scaleUpLimit possibleLimitingCondition = "ScaleUpLimit" possibleLimitingReason = "the desired replica count is increasing faster than the maximum scale rate" } else { maximumAllowedReplicas = hpaMaxReplicas possibleLimitingCondition = "TooManyReplicas" possibleLimitingReason = "the desired replica count is more than the maximum replica count" } if desiredReplicas < minimumAllowedReplicas { possibleLimitingCondition = "TooFewReplicas" return minimumAllowedReplicas, possibleLimitingCondition, possibleLimitingReason } else if desiredReplicas > maximumAllowedReplicas { return maximumAllowedReplicas, possibleLimitingCondition, possibleLimitingReason } return desiredReplicas, "DesiredWithinRange", "the desired count is within the acceptable range" } func calculateScaleUpLimit(currentReplicas int32) int32 { return int32(math.Max(scaleUpLimitFactor*float64(currentReplicas), scaleUpLimitMinimum)) } </ins> func (a *HorizontalController) shouldScale(hpa *autoscalingv2.HorizontalPodAutoscaler, currentReplicas, desiredReplicas int32, timestamp time.Time) bool { if desiredReplicas == currentReplicas { return false

Discussion:
We recently had 2 serious bugs caused by non-trivial logic calculating desiredReplicas in hpa reconcileAutoscaler method (, ). I think we should extract this part of logic (starting around ) to a separate function and make sure it's covered with unittests for different possible edge condition. The logic itself takes a few ints as input (calculated desiredReplicas, min/max replicas, currentReplicas, ...) to produce a single integer value and set some status conditions, making it very easily unittestable if we extract it to a separate function. WDYT
+100. It would be super unfortunate if we had yet another issue in this area of the code.
agreed. you've been going over these pieces of code recently, you want to tackle this?
Yes, I'd love to! Thanks for the ping. I'll tackle this this weekend :)