Skip to content

Commit

Permalink
Change to CurrentReplicasIfHigher and CurrentReplicasIfLower
Browse files Browse the repository at this point in the history
Signed-off-by: Rick Brouwer <[email protected]>
  • Loading branch information
rickbrouwer committed Jan 25, 2025
1 parent 6fbcb56 commit 92c47d0
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 26 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio

### New

- **General**: Add fallback `behavior` with option `useCurrentReplicasAsMinimum` to use current number of replicas ([#6450](https://github.com/kedacore/keda/issues/6450))
- **General**: Add Fallback option `behavior` for dynamic fallback calculation ([#6450](https://github.com/kedacore/keda/issues/6450))
- **General**: Enable OpenSSF Scorecard to enhance security practices across the project ([#5913](https://github.com/kedacore/keda/issues/5913))
- **General**: Introduce new NSQ scaler ([#3281](https://github.com/kedacore/keda/issues/3281))
- **General**: Operator flag to control patching of webhook resources certificates ([#6184](https://github.com/kedacore/keda/issues/6184))
Expand Down
9 changes: 5 additions & 4 deletions apis/keda/v1alpha1/scaledobject_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ const ScaledObjectTransferHpaOwnershipAnnotation = "scaledobject.keda.sh/transfe
const ValidationsHpaOwnershipAnnotation = "validations.keda.sh/hpa-ownership"
const PausedReplicasAnnotation = "autoscaling.keda.sh/paused-replicas"
const PausedAnnotation = "autoscaling.keda.sh/paused"
const FallbackBehaviorStatic = "static"
const FallbackBehaviorUseCurrentReplicasAsMin = "useCurrentReplicasAsMinimum"
const FallbackBehaviorStatic = "Static"
const FallbackBehaviorCurrentReplicasIfHigher = "CurrentReplicasIfHigher"
const FallbackBehaviorCurrentReplicasIfLower = "CurrentReplicasIfLower"

// HealthStatus is the status for a ScaledObject's health
type HealthStatus struct {
Expand Down Expand Up @@ -112,8 +113,8 @@ type Fallback struct {
FailureThreshold int32 `json:"failureThreshold"`
Replicas int32 `json:"replicas"`
// +optional
// +kubebuilder:default=static
// +kubebuilder:validation:Enum=static;useCurrentReplicasAsMinimum
// +kubebuilder:default=Static
// +kubebuilder:validation:Enum=Static;CurrentReplicasIfHigher;CurrentReplicasIfLower
Behavior string `json:"behavior,omitempty"`
}

Expand Down
7 changes: 4 additions & 3 deletions config/crd/bases/keda.sh_scaledobjects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ spec:
description: Fallback is the spec for fallback options
properties:
behavior:
default: static
default: Static
enum:
- static
- useCurrentReplicasAsMinimum
- Static
- CurrentReplicasIfHigher
- CurrentReplicasIfLower
type: string
failureThreshold:
format: int32
Expand Down
9 changes: 8 additions & 1 deletion pkg/fallback/fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,20 @@ func doFallback(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpe
switch fallbackBehavior {
case kedav1alpha1.FallbackBehaviorStatic:
replicas = fallbackReplicas
case kedav1alpha1.FallbackBehaviorUseCurrentReplicasAsMin:
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfHigher:
currentReplicasCount := int64(currentReplicas)
if currentReplicasCount > fallbackReplicas {
replicas = currentReplicasCount
} else {
replicas = fallbackReplicas
}
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfLower:
currentReplicasCount := int64(currentReplicas)
if currentReplicasCount < fallbackReplicas {
replicas = currentReplicasCount
} else {
replicas = fallbackReplicas
}
default:
replicas = fallbackReplicas
}
Expand Down
16 changes: 8 additions & 8 deletions pkg/fallback/fallback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func TestFallback(t *testing.T) {

var _ = Describe("fallback", func() {
var (
client *mock_client.MockClient
scaler *mock_scalers.MockScaler
client *mock_client.MockClient
scaler *mock_scalers.MockScaler
scaleClient *mock_scale.MockScalesGetter
ctrl *gomock.Controller
ctrl *gomock.Controller
)

BeforeEach(func() {
Expand Down Expand Up @@ -375,10 +375,10 @@ var _ = Describe("fallback", func() {
Expect(condition.IsTrue()).Should(BeFalse())
})

It("should use fallback replicas when current replicas is lower", func() {
It("should use fallback replicas when current replicas is lower when behavior is 'CurrentReplicasIfHigher'", func() {
scaler.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Eq(metricName)).Return(nil, false, errors.New("some error"))
startingNumberOfFailures := int32(3)
behavior := "useCurrentReplicasAsMinimum"
behavior := "CurrentReplicasIfHigher"

so := buildScaledObject(
&kedav1alpha1.Fallback{
Expand Down Expand Up @@ -409,10 +409,10 @@ var _ = Describe("fallback", func() {
Expect(value).Should(Equal(expectedValue))
})

It("should ignore current replicas when behavior is 'static'", func() {
It("should ignore current replicas when behavior is 'Static'", func() {
scaler.EXPECT().GetMetricsAndActivity(gomock.Any(), gomock.Eq(metricName)).Return(nil, false, errors.New("some error"))
startingNumberOfFailures := int32(3)
behavior := "static"
behavior := "Static"

so := buildScaledObject(
&kedav1alpha1.Fallback{
Expand Down Expand Up @@ -455,7 +455,7 @@ func mockScaleAndDeployment(
mockScaleInterface := mock_scale.NewMockScaleInterface(ctrl)
scale := &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
Name: "myapp",
Namespace: "default",
},
Spec: autoscalingv1.ScaleSpec{
Expand Down
6 changes: 5 additions & 1 deletion pkg/scaling/executor/scale_scaledobjects.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,14 @@ func (e *scaleExecutor) doFallbackScaling(ctx context.Context, scaledObject *ked
switch fallbackBehavior {
case kedav1alpha1.FallbackBehaviorStatic:
// no specifc action needed
case kedav1alpha1.FallbackBehaviorUseCurrentReplicasAsMin:
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfHigher:
if currentReplicas > fallbackReplicas {
fallbackReplicas = currentReplicas
}
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfLower:
if currentReplicas < fallbackReplicas {
fallbackReplicas = currentReplicas
}
}

_, err := e.updateScaleOnScaleTarget(ctx, scaledObject, currentScale, fallbackReplicas)
Expand Down
84 changes: 76 additions & 8 deletions pkg/scaling/executor/scale_scaledobjects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,8 @@ func TestEventWitTriggerInfo(t *testing.T) {
assert.Equal(t, "Normal KEDAScaleTargetActivated Scaled namespace/name from 2 to 5, triggered by testTrigger", eventstring)
}

// Behavior is UseCurrentReplicasAsMinimum and current replicas is higher than fallback replicas
func TestScaleToFallbackWithCurrentReplicasAsMinimum(t *testing.T) {
// Behavior is 'CurrentReplicasIfHigher' and current replicas is higher than fallback replicas
func TestBehaviorCurrentReplicasIfHigherWithCurrentReplicasIsHigher(t *testing.T) {
ctrl := gomock.NewController(t)
client := mock_client.NewMockClient(ctrl)
recorder := record.NewFakeRecorder(1)
Expand All @@ -534,7 +534,7 @@ func TestScaleToFallbackWithCurrentReplicasAsMinimum(t *testing.T) {

scaleExecutor := NewScaleExecutor(client, mockScaleClient, nil, recorder)

behavior := "useCurrentReplicasAsMinimum"
behavior := "CurrentReplicasIfHigher"
scaledObject := v1alpha1.ScaledObject{
ObjectMeta: v1.ObjectMeta{
Name: "name",
Expand Down Expand Up @@ -590,8 +590,8 @@ func TestScaleToFallbackWithCurrentReplicasAsMinimum(t *testing.T) {
assert.Equal(t, true, condition.IsTrue())
}

// Behavior is UseCurrentReplicasAsMinimum and is true and current replicas is lower than fallback replicas
func TestScaleToFallbackIgnoringLowerCurrentReplicas(t *testing.T) {
// Behavior is 'CurrentReplicasIfLower' and current replicas is higher than fallback replicas
func TestBehaviorCurrentReplicasIfLowerWithCurrentReplicasIsHigher(t *testing.T) {
ctrl := gomock.NewController(t)
client := mock_client.NewMockClient(ctrl)
recorder := record.NewFakeRecorder(1)
Expand All @@ -601,7 +601,75 @@ func TestScaleToFallbackIgnoringLowerCurrentReplicas(t *testing.T) {

scaleExecutor := NewScaleExecutor(client, mockScaleClient, nil, recorder)

behavior := "useCurrentReplicasAsMinimum"
behavior := "CurrentReplicasIfLower"
scaledObject := v1alpha1.ScaledObject{
ObjectMeta: v1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ScaledObjectSpec{
ScaleTargetRef: &v1alpha1.ScaleTarget{
Name: "name",
},
Fallback: &v1alpha1.Fallback{
FailureThreshold: 3,
Replicas: 5,
Behavior: behavior,
},
},
Status: v1alpha1.ScaledObjectStatus{
ScaleTargetGVKR: &v1alpha1.GroupVersionKindResource{
Group: "apps",
Kind: "Deployment",
},
},
}

scaledObject.Status.Conditions = *v1alpha1.GetInitializedConditions()

// Current replicas is higher than fallback replicas
currentReplicas := int32(8)

client.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).SetArg(2, appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Replicas: &currentReplicas,
},
})

scale := &autoscalingv1.Scale{
Spec: autoscalingv1.ScaleSpec{
Replicas: currentReplicas,
},
}

mockScaleClient.EXPECT().Scales(gomock.Any()).Return(mockScaleInterface).Times(2)
mockScaleInterface.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(scale, nil)
mockScaleInterface.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Eq(scale), gomock.Any())

client.EXPECT().Status().Times(2).Return(statusWriter)
statusWriter.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()).Times(2)

scaleExecutor.RequestScale(context.Background(), &scaledObject, false, true, &ScaleExecutorOptions{})

// Should use fallback replicas as it's higher than current replicas
assert.Equal(t, int32(5), scale.Spec.Replicas)
condition := scaledObject.Status.Conditions.GetFallbackCondition()
assert.Equal(t, true, condition.IsTrue())
}


// Behavior is 'CurrentReplicasIfHigher' and current replicas is lower than fallback replicas
func TestBehaviorCurrentReplicasIfHigherWithCurrentReplicasisLower(t *testing.T) {
ctrl := gomock.NewController(t)
client := mock_client.NewMockClient(ctrl)
recorder := record.NewFakeRecorder(1)
mockScaleClient := mock_scale.NewMockScalesGetter(ctrl)
mockScaleInterface := mock_scale.NewMockScaleInterface(ctrl)
statusWriter := mock_client.NewMockStatusWriter(ctrl)

scaleExecutor := NewScaleExecutor(client, mockScaleClient, nil, recorder)

behavior := "CurrentReplicasIfHigher"
scaledObject := v1alpha1.ScaledObject{
ObjectMeta: v1.ObjectMeta{
Name: "name",
Expand Down Expand Up @@ -658,7 +726,7 @@ func TestScaleToFallbackIgnoringLowerCurrentReplicas(t *testing.T) {
}

// Behavior is Static and current replicas is higher than fallback replicas
func TestScaleToFallbackWithoutCurrentReplicasAsMinimum(t *testing.T) {
func TestBehaviorStaticWithCurrentReplicasisHigher(t *testing.T) {
ctrl := gomock.NewController(t)
client := mock_client.NewMockClient(ctrl)
recorder := record.NewFakeRecorder(1)
Expand All @@ -668,7 +736,7 @@ func TestScaleToFallbackWithoutCurrentReplicasAsMinimum(t *testing.T) {

scaleExecutor := NewScaleExecutor(client, mockScaleClient, nil, recorder)

behavior := "static"
behavior := "Static"
scaledObject := v1alpha1.ScaledObject{
ObjectMeta: v1.ObjectMeta{
Name: "name",
Expand Down

0 comments on commit 92c47d0

Please sign in to comment.