From 80806a73218e7d128bd25945f573c2a91316d1d3 Mon Sep 17 00:00:00 2001 From: xrwang <68765051+xrwang8@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:28:25 +0800 Subject: [PATCH] add `InitialCooldownPeriod` for ScaledObjects (#5478) Signed-off-by: xrwang <68765051+xrwang8@users.noreply.github.com> Signed-off-by: wangxingrui Co-authored-by: Jorge Turrado Ferrero Co-authored-by: Zbynek Roubalik --- CHANGELOG.md | 3 + apis/keda/v1alpha1/scaledobject_types.go | 2 + config/crd/bases/keda.sh_scaledobjects.yaml | 3 + pkg/scaling/executor/scale_scaledobjects.go | 12 +- .../initial_delay_cooldownperiod_test.go | 120 ++++++++++++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1de3e38b0..f62ba0515b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio - **General**: Provide capability to filter CloudEvents ([#3533](https://github.com/kedacore/keda/issues/3533)) - **NATS Scaler**: Add TLS authentication ([#2296](https://github.com/kedacore/keda/issues/2296)) +- **ScaledObject**: Ability to specify `initialCooldownPeriod` ([#5008](https://github.com/kedacore/keda/issues/5008)) + + #### Experimental diff --git a/apis/keda/v1alpha1/scaledobject_types.go b/apis/keda/v1alpha1/scaledobject_types.go index 70853774cd4..a806c3bd25c 100644 --- a/apis/keda/v1alpha1/scaledobject_types.go +++ b/apis/keda/v1alpha1/scaledobject_types.go @@ -100,6 +100,8 @@ type ScaledObjectSpec struct { Triggers []ScaleTriggers `json:"triggers"` // +optional Fallback *Fallback `json:"fallback,omitempty"` + // +optional + InitialCooldownPeriod int32 `json:"initialCooldownPeriod,omitempty"` } // Fallback is the spec for fallback options diff --git a/config/crd/bases/keda.sh_scaledobjects.yaml b/config/crd/bases/keda.sh_scaledobjects.yaml index f103979c8b0..8f03de32b18 100644 --- a/config/crd/bases/keda.sh_scaledobjects.yaml +++ b/config/crd/bases/keda.sh_scaledobjects.yaml @@ -238,6 +238,9 @@ spec: idleReplicaCount: format: int32 type: integer + initialCooldownPeriod: + format: int32 + type: integer maxReplicaCount: format: int32 type: integer diff --git a/pkg/scaling/executor/scale_scaledobjects.go b/pkg/scaling/executor/scale_scaledobjects.go index 0fb37f89136..f7f880afcde 100644 --- a/pkg/scaling/executor/scale_scaledobjects.go +++ b/pkg/scaling/executor/scale_scaledobjects.go @@ -253,12 +253,18 @@ func (e *scaleExecutor) scaleToZeroOrIdle(ctx context.Context, logger logr.Logge cooldownPeriod = time.Second * time.Duration(defaultCooldownPeriod) } + initialCooldownPeriod := time.Second * time.Duration(scaledObject.Spec.InitialCooldownPeriod) + + // If the ScaledObject was just created,CreationTimestamp is zero, set the CreationTimestamp to now + if scaledObject.ObjectMeta.CreationTimestamp.IsZero() { + scaledObject.ObjectMeta.CreationTimestamp = metav1.NewTime(time.Now()) + } + // LastActiveTime can be nil if the ScaleTarget was scaled outside of KEDA. // In this case we will ignore the cooldown period and scale it down - if scaledObject.Status.LastActiveTime == nil || - scaledObject.Status.LastActiveTime.Add(cooldownPeriod).Before(time.Now()) { + if (scaledObject.Status.LastActiveTime == nil && scaledObject.ObjectMeta.CreationTimestamp.Add(initialCooldownPeriod).Before(time.Now())) || (scaledObject.Status.LastActiveTime != nil && + scaledObject.Status.LastActiveTime.Add(cooldownPeriod).Before(time.Now())) { // or last time a trigger was active was > cooldown period, so scale in. - idleValue, scaleToReplicas := getIdleOrMinimumReplicaCount(scaledObject) currentReplicas, err := e.updateScaleOnScaleTarget(ctx, scaledObject, scale, scaleToReplicas) diff --git a/tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go b/tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go new file mode 100644 index 00000000000..bfc7f307b95 --- /dev/null +++ b/tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go @@ -0,0 +1,120 @@ +//go:build e2e +// +build e2e + +package initial_delay_cooldownperiod_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + . "github.com/kedacore/keda/v2/tests/helper" +) + +const ( + testName = "initial-delay-cooldownperiod-test" +) + +var ( + testNamespace = fmt.Sprintf("%s-ns", testName) + deploymentName = fmt.Sprintf("%s-deployment", testName) + scaledObjectName = fmt.Sprintf("%s-so", testName) + + now = time.Now().Local() + start = (now.Minute() + 10) % 60 + end = (start + 1) % 60 +) + +type templateData struct { + TestNamespace string + DeploymentName string + ScaledObjectName string + StartMin string + EndMin string +} + +const ( + deploymentTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.DeploymentName}} + namespace: {{.TestNamespace}} + labels: + deploy: {{.DeploymentName}} +spec: + replicas: 1 + selector: + matchLabels: + pod: {{.DeploymentName}} + template: + metadata: + labels: + pod: {{.DeploymentName}} + spec: + containers: + - name: nginx + image: 'nginxinc/nginx-unprivileged' +` + + scaledObjectTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: {{.ScaledObjectName}} + namespace: {{.TestNamespace}} +spec: + scaleTargetRef: + name: {{.DeploymentName}} + cooldownPeriod: 5 + minReplicaCount: 0 + initialCooldownPeriod: 120 + advanced: + horizontalPodAutoscalerConfig: + behavior: + scaleDown: + stabilizationWindowSeconds: 15 + triggers: + - type: cron + metadata: + timezone: Etc/UTC + start: {{.StartMin}} * * * * + end: {{.EndMin}} * * * * + desiredReplicas: '0' +` +) + +func TestScaler(t *testing.T) { + // setup + t.Log("--- setting up ---") + + // Create kubernetes resources + kc := GetKubernetesClient(t) + data, templates := getTemplateData() + + CreateKubernetesResources(t, kc, testNamespace, data, templates) + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, 1, 60, 1), + "replica count should be %d after 1 minute", 1) + t.Log("--- Waiting for some time to ensure deployment replica count doesn't change ---") + AssertReplicaCountNotChangeDuringTimePeriod(t, kc, deploymentName, testNamespace, 1, 90) + t.Log("--- scale to 0 replicas ---") + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, 0, 120, 1), + "replica count should be %d after 2 minute", 0) // Assert that the workload is scaled to zero after the initial cooldown period + + DeleteKubernetesResources(t, testNamespace, data, templates) +} +func getTemplateData() (templateData, []Template) { + return templateData{ + TestNamespace: testNamespace, + DeploymentName: deploymentName, + ScaledObjectName: scaledObjectName, + StartMin: strconv.Itoa(start), + EndMin: strconv.Itoa(end), + }, []Template{ + {Name: "deploymentTemplate", Config: deploymentTemplate}, + {Name: "scaledObjectTemplate", Config: scaledObjectTemplate}, + } +}