Skip to content

Commit

Permalink
added hpa update predicate to reconcile a scaledobject only if hpa sp…
Browse files Browse the repository at this point in the history
…ec or label is changed
  • Loading branch information
deefreak committed Dec 13, 2023
1 parent cd22b79 commit c1011c6
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
3 changes: 1 addition & 2 deletions controllers/keda/scaledobject_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,11 @@ func (r *ScaledObjectReconciler) SetupWithManager(mgr ctrl.Manager, options cont
predicate.GenerationChangedPredicate{},
),
)).
// Trigger a reconcile only when the HPA spec,label or annotation changes.
// Trigger a reconcile only when the HPA spec,label or annotation changes.
// Ignore updates to HPA status
Owns(&autoscalingv2.HorizontalPodAutoscaler{}, builder.WithPredicates(
predicate.Or(
predicate.LabelChangedPredicate{},
predicate.AnnotationChangedPredicate{},
kedacontrollerutil.HPASpecChangedPredicate{},
))).
Complete(r)
Expand Down
131 changes: 131 additions & 0 deletions controllers/keda/scaledobject_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,137 @@ var _ = Describe("ScaledObjectController", func() {
return k8sClient.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("keda-hpa-%s", soName), Namespace: "default"}, hpa)
}).Should(HaveOccurred())
})

// Fix issue 5281
It("reconciles scaledobject when hpa spec is changed", func() {
var (
deploymentName = "hpa-spec-change"
soName = "so-" + deploymentName
min int32 = 1
max int32 = 5
newMin int32 = 2
newMax int32 = 6
pollingInterVal int32 = 1
)

err := k8sClient.Create(context.Background(), generateDeployment(deploymentName))
Expect(err).ToNot(HaveOccurred())

// Create the ScaledObject without specifying name.
so := &kedav1alpha1.ScaledObject{
ObjectMeta: metav1.ObjectMeta{
Name: soName,
Namespace: "default",
},
Spec: kedav1alpha1.ScaledObjectSpec{
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
Name: deploymentName,
},
MinReplicaCount: &min,
MaxReplicaCount: &max,
PollingInterval: &pollingInterVal,
Advanced: &kedav1alpha1.AdvancedConfig{
HorizontalPodAutoscalerConfig: &kedav1alpha1.HorizontalPodAutoscalerConfig{},
},
Triggers: []kedav1alpha1.ScaleTriggers{
{
Type: "cron",
Metadata: map[string]string{
"timezone": "UTC",
"start": "0 * * * *",
"end": "1 * * * *",
"desiredReplicas": "1",
},
},
},
},
}
err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())

// And validate that hpa is created.
hpa := &autoscalingv2.HorizontalPodAutoscaler{}
Eventually(func() error {
return k8sClient.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("keda-hpa-%s", soName), Namespace: "default"}, hpa)
}).ShouldNot(HaveOccurred())

// Change hpa spec and update
hpa.Spec.MinReplicas = &newMin
hpa.Spec.MaxReplicas = newMax
err = k8sClient.Update(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

// scaledobject should be reconciled and hpa spec should match with scaledobject spec
Eventually(func() bool {
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("keda-hpa-%s", soName), Namespace: "default"}, hpa)
if err != nil {
return false
}
return *hpa.Spec.MinReplicas == min && hpa.Spec.MaxReplicas == max
}).Should(BeTrue())
})

It("reconciles scaledobject and creates hpa when child hpa is deleted", func() {
var (
deploymentName = "hpa-deleted"
soName = "so-" + deploymentName
min int32 = 1
max int32 = 5
pollingInterVal int32 = 1
)

err := k8sClient.Create(context.Background(), generateDeployment(deploymentName))
Expect(err).ToNot(HaveOccurred())

// Create the ScaledObject without specifying name.
so := &kedav1alpha1.ScaledObject{
ObjectMeta: metav1.ObjectMeta{
Name: soName,
Namespace: "default",
},
Spec: kedav1alpha1.ScaledObjectSpec{
ScaleTargetRef: &kedav1alpha1.ScaleTarget{
Name: deploymentName,
},
MinReplicaCount: &min,
MaxReplicaCount: &max,
PollingInterval: &pollingInterVal,
Advanced: &kedav1alpha1.AdvancedConfig{
HorizontalPodAutoscalerConfig: &kedav1alpha1.HorizontalPodAutoscalerConfig{},
},
Triggers: []kedav1alpha1.ScaleTriggers{
{
Type: "cron",
Metadata: map[string]string{
"timezone": "UTC",
"start": "0 * * * *",
"end": "1 * * * *",
"desiredReplicas": "1",
},
},
},
},
}
err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())

// And validate that hpa is created.
hpa := &autoscalingv2.HorizontalPodAutoscaler{}
Eventually(func() error {
return k8sClient.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("keda-hpa-%s", soName), Namespace: "default"}, hpa)
}).ShouldNot(HaveOccurred())

// Delete the child hpa
err = k8sClient.Delete(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

// scaledobject should be reconciled and again the corresponding hpa should be created
Eventually(func() error {
return k8sClient.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("keda-hpa-%s", soName), Namespace: "default"}, hpa)

}).Should(BeNil())
})

})

func generateDeployment(name string) *appsv1.Deployment {
Expand Down
4 changes: 2 additions & 2 deletions controllers/keda/util/predicate.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package util

import (
autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/apimachinery/pkg/api/equality"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"

kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
"k8s.io/apimachinery/pkg/api/equality"
)

type PausedReplicasPredicate struct {
Expand Down

0 comments on commit c1011c6

Please sign in to comment.