Change:
We need an E2E test for HPA behavior. HPA v2beta2 is graduating to stable! KEP: Tracking issue: Working doc: Slack channel: #sig-autoscaling-api . on #sig-autoscaling-api "Hey, I want to do this." (just so two people don't start the same work) a new E2E test that tests . There are currently two kinds of HPA tests in the Kubernetes E2E suite. One tests and the other tests . The feature is independent of the kind of metrics HPA is using. After HPA calculates scale for all metrics, it select the maximum and then applies user configured limits which are , and . Behavior specifies a scale up and down limit in rate (relative and absolute) as well as a delay. Defaults are: New behavior tests could be part of the CPU or the custom metrics suites, it doesn't really matter. Whichever is easier to implement and has the least flakes. Cases we should test: scale down works. Set , verify scale up, then verify scale down happens within 1 minute. up delay works. Set , continuously send high usage and verify scale up happens only after 5 minutes. autoscaling works. Set and . This will the replacement for the special 0 replicas case when we deprecate it, we should make sure it reliably works. Send a pull request (one squashed commit please) and notify #sig-autoscaling-api so we can take a look too. It will probably need API review. Feel free to reach out for help on #sig-autoscaling-api if you have any questions!
/sig autoscaling /triage accepted /help
This request has been marked as needing help from a contributor. Please ensure the request meets the requirements listed . If this request no longer meets these requirements, the label can be removed by commenting with the command. <detailsIn response to : /sig autoscaling /triage accepted /help Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/assign danishprakash
GitHub didn't allow me to assign the following users: danishprakash. Note that only , repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time. For more information please see <detailsIn response to : /assign danishprakash Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/assign
Hey any update on this?
Yeah, I'll be sharing an update over the weekend.
The Kubernetes project currently lacks enough contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue or PR as fresh with Mark this issue or PR as rotten with Close this issue or PR with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle stale [1]:
The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue or PR as fresh with Close this issue or PR with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle rotten [1]:
The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Reopen this issue or PR with Mark this issue or PR as fresh with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /close [1]:
Closing this issue. <detailsIn response to : The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closedReopen this issue or PR with Mark this issue or PR as fresh with Offer to help out with [Issue Triage][1] Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/reopen are you planning to finish this task? Otherwise I'll take over.
You can't reopen an issue/PR unless you authored it or you are a collaborator. <detailsIn response to : /reopen Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/reopen /assign pbetkier
Reopened this issue. <detailsIn response to : /reopen /assign pbetkier Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Reopen this issue or PR with Mark this issue or PR as fresh with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /close [1]:
Closing this issue. <detailsIn response to : The Kubernetes project currently lacks enough active contributors to adequately respond to all issues and PRs. After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closedReopen this issue or PR with Mark this issue or PR as fresh with Offer to help out with [Issue Triage][1] Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/reopen
You can't reopen an issue/PR unless you authored it or you are a collaborator. <detailsIn response to : /reopen Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/remove-lifecycle rotten

Discussion:
f := framework.NewDefaultFramework("horizontal-pod-autoscaling") f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged <ins> hpaName := "consumer" podCPURequest := 500 targetCPUUtilizationPercent := 25 usageForSingleReplica := 110 </ins> fullWindowOfNewUsage := 30 * time.Second windowWithOldUsagePasses := 30 * time.Second newPodMetricsDelay := 15 * time.Second

ginkgo.Describe("with short downscale stabilization window", func() { ginkgo.It("should scale down soon after the stabilization period", func() { ginkgo.By("setting up resource consumer and HPA") <del> podCPURequest := 500 targetCPUUtilizationPercent := 25 usageForSingleReplica := 110 </del> initPods := 1 initCPUUsageTotal := initPods * usageForSingleReplica upScaleStabilization := 0 * time.Minute downScaleStabilization := 1 * time.Minute rc := e2eautoscaling.NewDynamicResourceConsumer( <del> "consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </del> <ins> hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </ins> initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, )

ginkgo.Describe("with long upscale stabilization window", func() { ginkgo.It("should scale up only after the stabilization period", func() { ginkgo.By("setting up resource consumer and HPA") <del> podCPURequest := 500 targetCPUUtilizationPercent := 25 usageForSingleReplica := 110 </del> initPods := 2 initCPUUsageTotal := initPods * usageForSingleReplica upScaleStabilization := 3 * time.Minute downScaleStabilization := 0 * time.Minute rc := e2eautoscaling.NewDynamicResourceConsumer( <del> "consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </del> <ins> hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </ins> initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, )

}) }) <del> ginkgo.Describe("with upscale autoscaling disabled", func() { </del> <ins> ginkgo.Describe("with autoscaling disabled", func() { </ins> ginkgo.It("shouldn't scale up", func() { ginkgo.By("setting up resource consumer and HPA") <del> podCPURequest := 500 targetCPUUtilizationPercent := 25 usageForSingleReplica := 110 </del> initPods := 1 initCPUUsageTotal := initPods * usageForSingleReplica rc := e2eautoscaling.NewDynamicResourceConsumer( <del> "consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </del> <ins> hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </ins> initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( <del> rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleUpDisabled(), </del> <ins> rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleDisabled(e2eautoscaling.ScaleUpDirection), </ins> ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name)

replicas := rc.GetReplicas() framework.ExpectEqual(replicas == initPods, true, "had %s replicas, still have %s replicas after time deadline", initPods, replicas) }) <del> }) ginkgo.Describe("with downscale autoscaling disabled", func() { </del> ginkgo.It("shouldn't scale down", func() { ginkgo.By("setting up resource consumer and HPA") <del> podCPURequest := 500 targetCPUUtilizationPercent := 25 usageForSingleReplica := 110 </del> initPods := 3 initCPUUsageTotal := initPods * usageForSingleReplica rc := e2eautoscaling.NewDynamicResourceConsumer( <del> "consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </del> <ins> hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, </ins> initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( <del> rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleDownDisabled(), </del> <ins> rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleDisabled(e2eautoscaling.ScaleDownDirection), </ins> ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name)

replicas := rc.GetReplicas() framework.ExpectEqual(replicas == initPods, true, "had %s replicas, still have %s replicas after time deadline", initPods, replicas) }) <ins> }) ginkgo.Describe("with scale limited by number of Pods rate", func() { ginkgo.It("should scale up no more than given number of Pods per minute", func() { ginkgo.By("setting up resource consumer and HPA") initPods := 1 initCPUUsageTotal := initPods * usageForSingleReplica limitWindowLength := 1 * time.Minute podsLimitPerMinute := 2 rc := e2eautoscaling.NewDynamicResourceConsumer( hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleLimitedByNumberOfPods(e2eautoscaling.ScaleUpDirection, int32(podsLimitPerMinute), int32(limitWindowLength.Seconds())), ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name) ginkgo.By("triggering scale up by increasing consumption") rc.ConsumeCPU(5 * usageForSingleReplica) waitStart := time.Now() rc.WaitForReplicas(3, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor3 := time.Now().Sub(waitStart) waitStart = time.Now() rc.WaitForReplicas(5, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor5 := time.Now().Sub(waitStart) ginkgo.By("verifying time waited for a scale up to 3 replicas") deadline := limitWindowLength + maxHPAReactionTime + maxResourceConsumerDelay // First scale event can happen right away, as there were no scale events in the past. framework.ExpectEqual(timeWaitedFor3 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor3, deadline) ginkgo.By("verifying time waited for a scale up to 5 replicas") // Second scale event needs to respect limit window. framework.ExpectEqual(timeWaitedFor5 > limitWindowLength, true, "waited %s, wanted to wait more than %s", timeWaitedFor5, limitWindowLength) framework.ExpectEqual(timeWaitedFor5 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor5, deadline) }) ginkgo.It("should scale down no more than given number of Pods per minute", func() { ginkgo.By("setting up resource consumer and HPA") initPods := 6 initCPUUsageTotal := initPods * usageForSingleReplica limitWindowLength := 1 * time.Minute podsLimitPerMinute := 2 rc := e2eautoscaling.NewDynamicResourceConsumer( hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleLimitedByNumberOfPods(e2eautoscaling.ScaleDownDirection, int32(podsLimitPerMinute), int32(limitWindowLength.Seconds())), ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name) ginkgo.By("triggering scale down by lowering consumption") rc.ConsumeCPU(1 * usageForSingleReplica) waitStart := time.Now() rc.WaitForReplicas(4, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor4 := time.Now().Sub(waitStart) waitStart = time.Now() rc.WaitForReplicas(2, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor2 := time.Now().Sub(waitStart) ginkgo.By("verifying time waited for a scale down to 4 replicas") deadline := limitWindowLength + maxHPAReactionTime + maxResourceConsumerDelay // First scale event can happen right away, as there were no scale events in the past. framework.ExpectEqual(timeWaitedFor4 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor4, deadline) ginkgo.By("verifying time waited for a scale down to 2 replicas") // Second scale event needs to respect limit window. framework.ExpectEqual(timeWaitedFor2 > limitWindowLength, true, "waited %s, wanted more than %s", timeWaitedFor2, limitWindowLength) framework.ExpectEqual(timeWaitedFor2 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor2, deadline) }) </ins> }) <ins> ginkgo.Describe("with scale limited by percentage", func() { ginkgo.It("should scale up no more than given percentage of current Pods per minute", func() { ginkgo.By("setting up resource consumer and HPA") initPods := 4 initCPUUsageTotal := initPods * usageForSingleReplica limitWindowLength := 1 * time.Minute percentageLimitPerMinute := 50 rc := e2eautoscaling.NewDynamicResourceConsumer( hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleLimitedByPercentage(e2eautoscaling.ScaleUpDirection, int32(percentageLimitPerMinute), int32(limitWindowLength.Seconds())), ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name) ginkgo.By("triggering scale up by increasing consumption") rc.ConsumeCPU(10 * usageForSingleReplica) waitStart := time.Now() rc.WaitForReplicas(6, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor6 := time.Now().Sub(waitStart) waitStart = time.Now() rc.WaitForReplicas(9, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor9 := time.Now().Sub(waitStart) ginkgo.By("verifying time waited for a scale up to 6 replicas") deadline := limitWindowLength + maxHPAReactionTime + maxResourceConsumerDelay // First scale event can happen right away, as there were no scale events in the past. framework.ExpectEqual(timeWaitedFor6 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor6, deadline) ginkgo.By("verifying time waited for a scale up to 9 replicas") // Second scale event needs to respect limit window. framework.ExpectEqual(timeWaitedFor9 > limitWindowLength, true, "waited %s, wanted to wait more than %s", timeWaitedFor9, limitWindowLength) framework.ExpectEqual(timeWaitedFor9 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor9, deadline) }) ginkgo.It("should scale down no more than given percentage of current Pods per minute", func() { ginkgo.By("setting up resource consumer and HPA") initPods := 8 initCPUUsageTotal := initPods * usageForSingleReplica limitWindowLength := 1 * time.Minute percentageLimitPerMinute := 50 rc := e2eautoscaling.NewDynamicResourceConsumer( hpaName, f.Namespace.Name, e2eautoscaling.KindDeployment, initPods, initCPUUsageTotal, 0, 0, int64(podCPURequest), 200, f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle, ) defer rc.CleanUp() hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior( rc, int32(targetCPUUtilizationPercent), 1, 10, e2eautoscaling.HPABehaviorWithScaleLimitedByPercentage(e2eautoscaling.ScaleDownDirection, int32(percentageLimitPerMinute), int32(limitWindowLength.Seconds())), ) defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name) ginkgo.By("triggering scale down by lowering consumption") rc.ConsumeCPU(1 * usageForSingleReplica) waitStart := time.Now() rc.WaitForReplicas(4, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor4 := time.Now().Sub(waitStart) waitStart = time.Now() rc.WaitForReplicas(2, maxHPAReactionTime+maxResourceConsumerDelay+limitWindowLength) timeWaitedFor2 := time.Now().Sub(waitStart) ginkgo.By("verifying time waited for a scale down to 4 replicas") deadline := limitWindowLength + maxHPAReactionTime + maxResourceConsumerDelay // First scale event can happen right away, as there were no scale events in the past. framework.ExpectEqual(timeWaitedFor4 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor4, deadline) ginkgo.By("verifying time waited for a scale down to 2 replicas") // Second scale event needs to respect limit window. framework.ExpectEqual(timeWaitedFor2 > limitWindowLength, true, "waited %s, wanted more than %s", timeWaitedFor2, limitWindowLength) framework.ExpectEqual(timeWaitedFor2 < deadline, true, "waited %s, wanted less than %s", timeWaitedFor2, deadline) }) }) </ins> })

KindReplicaSet = schema.GroupVersionKind{Group: "apps", Version: "v1beta2", Kind: "ReplicaSet"} ) <ins> // ScalingDirection identifies the scale direction for HPA Behavior. type ScalingDirection int const ( DirectionUnknown ScalingDirection = iota ScaleUpDirection ScaleDownDirection ) </ins> /* ResourceConsumer is a tool for testing. It helps create specified usage of CPU or memory (Warning: memory not supported) typical use case:

} } <del> func HPAStabilizationWindowScalingRule(stabilizationDuration int32) *autoscalingv2.HPAScalingRules { </del> <ins> func HPABehaviorWithScalingRuleInDirection(scalingDirection ScalingDirection, rule *autoscalingv2.HPAScalingRules) *autoscalingv2.HorizontalPodAutoscalerBehavior { var scaleUpRule, scaleDownRule *autoscalingv2.HPAScalingRules if scalingDirection == ScaleUpDirection { scaleUpRule = rule } if scalingDirection == ScaleDownDirection { scaleDownRule = rule } return HPABehaviorWithScaleUpAndDownRules(scaleUpRule, scaleDownRule) } func HPAScalingRuleWithStabilizationWindow(stabilizationDuration int32) *autoscalingv2.HPAScalingRules { </ins> return &autoscalingv2.HPAScalingRules{ StabilizationWindowSeconds: &stabilizationDuration, } } <del> func HPAPolicyDisabledScalingRule() *autoscalingv2.HPAScalingRules { </del> <ins> func HPAScalingRuleWithPolicyDisabled() *autoscalingv2.HPAScalingRules { </ins> disabledPolicy := autoscalingv2.DisabledPolicySelect return &autoscalingv2.HPAScalingRules{ SelectPolicy: &disabledPolicy, } } <ins> func HPAScalingRuleWithScalingPolicy(policyType autoscalingv2.HPAScalingPolicyType, value, periodSeconds int32) *autoscalingv2.HPAScalingRules { stabilizationWindowDisabledDuration := int32(0) selectPolicy := autoscalingv2.MaxChangePolicySelect return &autoscalingv2.HPAScalingRules{ Policies: []autoscalingv2.HPAScalingPolicy{ { Type:          policyType, Value:         value, PeriodSeconds: periodSeconds, }, }, SelectPolicy:               &selectPolicy, StabilizationWindowSeconds: &stabilizationWindowDisabledDuration, } } </ins> func HPABehaviorWithStabilizationWindows(upscaleStabilization, downscaleStabilization time.Duration) *autoscalingv2.HorizontalPodAutoscalerBehavior { <del> return HPABehaviorWithScaleUpAndDownRules( /*scaleUpRule=*/ HPAStabilizationWindowScalingRule(int32(upscaleStabilization.Seconds())), /*scaleDownRule=*/ HPAStabilizationWindowScalingRule(int32(downscaleStabilization.Seconds())), ) </del> <ins> scaleUpRule := HPAScalingRuleWithStabilizationWindow(int32(upscaleStabilization.Seconds())) scaleDownRule := HPAScalingRuleWithStabilizationWindow(int32(downscaleStabilization.Seconds())) return HPABehaviorWithScaleUpAndDownRules(scaleUpRule, scaleDownRule) } func HPABehaviorWithScaleDisabled(scalingDirection ScalingDirection) *autoscalingv2.HorizontalPodAutoscalerBehavior { scalingRule := HPAScalingRuleWithPolicyDisabled() return HPABehaviorWithScalingRuleInDirection(scalingDirection, scalingRule) </ins> } <del> func HPABehaviorWithScaleUpDisabled() *autoscalingv2.HorizontalPodAutoscalerBehavior { return HPABehaviorWithScaleUpAndDownRules( /*scaleUpRule=*/ HPAPolicyDisabledScalingRule(), /*scaleDownRule=*/ nil, ) </del> <ins> func HPABehaviorWithScaleLimitedByNumberOfPods(scalingDirection ScalingDirection, numberOfPods, periodSeconds int32) *autoscalingv2.HorizontalPodAutoscalerBehavior { scalingRule := HPAScalingRuleWithScalingPolicy(autoscalingv2.PodsScalingPolicy, numberOfPods, periodSeconds) return HPABehaviorWithScalingRuleInDirection(scalingDirection, scalingRule) </ins> } <del> func HPABehaviorWithScaleDownDisabled() *autoscalingv2.HorizontalPodAutoscalerBehavior { return HPABehaviorWithScaleUpAndDownRules( /*scaleUpRule=*/ nil, /*scaleDownRule=*/ HPAPolicyDisabledScalingRule(), ) </del> <ins> func HPABehaviorWithScaleLimitedByPercentage(scalingDirection ScalingDirection, percentage, periodSeconds int32) *autoscalingv2.HorizontalPodAutoscalerBehavior { scalingRule := HPAScalingRuleWithScalingPolicy(autoscalingv2.PercentScalingPolicy, percentage, periodSeconds) return HPABehaviorWithScalingRuleInDirection(scalingDirection, scalingRule) </ins> } func DeleteHPAWithBehavior(rc *ResourceConsumer, autoscalerName string) {