@@ -146,7 +146,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request
146
146
return ctrl.Result {}, nil
147
147
}
148
148
// Error reading the object - requeue the request.
149
- reqLogger .Error (err , "Failed to get ScaledObject" )
149
+ reqLogger .Error (err , "failed to get ScaledObject" )
150
150
return ctrl.Result {}, err
151
151
}
152
152
@@ -172,9 +172,9 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request
172
172
}
173
173
}
174
174
175
- // reconcile ScaledObject and set status appropriately
176
- msg , err := r .reconcileScaledObject (ctx , reqLogger , scaledObject )
177
175
conditions := scaledObject .Status .Conditions .DeepCopy ()
176
+ // reconcile ScaledObject and set status appropriately
177
+ msg , err := r .reconcileScaledObject (ctx , reqLogger , scaledObject , & conditions )
178
178
if err != nil {
179
179
reqLogger .Error (err , msg )
180
180
conditions .SetReadyCondition (metav1 .ConditionFalse , "ScaledObjectCheckFailed" , msg )
@@ -197,7 +197,31 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request
197
197
}
198
198
199
199
// reconcileScaledObject implements reconciler logic for ScaledObject
200
- func (r * ScaledObjectReconciler ) reconcileScaledObject (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject ) (string , error ) {
200
+ func (r * ScaledObjectReconciler ) reconcileScaledObject (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject , conditions * kedav1alpha1.Conditions ) (string , error ) {
201
+ // Check the presence of "autoscaling.keda.sh/paused-replicas" annotation on the scaledObject (since the presence of this annotation will pause
202
+ // autoscaling no matter what number of replicas is provided), and if so, stop the scale loop and delete the HPA on the scaled object.
203
+ _ , pausedAnnotationFound := scaledObject .GetAnnotations ()[kedacontrollerutil .PausedReplicasAnnotation ]
204
+ if pausedAnnotationFound {
205
+ if conditions .GetPausedCondition ().Status == metav1 .ConditionTrue {
206
+ return kedav1alpha1 .ScaledObjectConditionReadySuccessMessage , nil
207
+ }
208
+ if scaledObject .Status .PausedReplicaCount != nil {
209
+ msg := kedav1alpha1 .ScaledObjectConditionPausedMessage
210
+ if err := r .stopScaleLoop (ctx , logger , scaledObject ); err != nil {
211
+ msg = "failed to stop the scale loop for paused ScaledObject"
212
+ return msg , err
213
+ }
214
+ if deleted , err := r .ensureHPAForScaledObjectIsDeleted (ctx , logger , scaledObject ); ! deleted {
215
+ msg = "failed to delete HPA for paused ScaledObject"
216
+ return msg , err
217
+ }
218
+ conditions .SetPausedCondition (metav1 .ConditionTrue , kedav1alpha1 .ScaledObjectConditionPausedReason , msg )
219
+ return msg , nil
220
+ }
221
+ } else if conditions .GetPausedCondition ().Status == metav1 .ConditionTrue {
222
+ conditions .SetPausedCondition (metav1 .ConditionFalse , "ScaledObjectUnpaused" , "pause annotation removed for ScaledObject" )
223
+ }
224
+
201
225
// Check scale target Name is specified
202
226
if scaledObject .Spec .ScaleTargetRef .Name == "" {
203
227
err := fmt .Errorf ("ScaledObject.spec.scaleTargetRef.name is missing" )
@@ -207,7 +231,7 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg
207
231
// Check the label needed for Metrics servers is present on ScaledObject
208
232
err := r .ensureScaledObjectLabel (ctx , logger , scaledObject )
209
233
if err != nil {
210
- return "Failed to update ScaledObject with scaledObjectName label" , err
234
+ return "failed to update ScaledObject with scaledObjectName label" , err
211
235
}
212
236
213
237
// Check if resource targeted for scaling exists and exposes /scale subresource
@@ -229,7 +253,7 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg
229
253
// Create a new HPA or update existing one according to ScaledObject
230
254
newHPACreated , err := r .ensureHPAForScaledObjectExists (ctx , logger , scaledObject , & gvkr )
231
255
if err != nil {
232
- return "Failed to ensure HPA is correctly created for ScaledObject" , err
256
+ return "failed to ensure HPA is correctly created for ScaledObject" , err
233
257
}
234
258
scaleObjectSpecChanged := false
235
259
if ! newHPACreated {
@@ -238,17 +262,21 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg
238
262
// (we can omit this check if a new HPA was created, which fires new ScaleLoop anyway)
239
263
scaleObjectSpecChanged , err = r .scaledObjectGenerationChanged (logger , scaledObject )
240
264
if err != nil {
241
- return "Failed to check whether ScaledObject's Generation was changed" , err
265
+ return "failed to check whether ScaledObject's Generation was changed" , err
242
266
}
243
267
}
244
268
245
269
// Notify ScaleHandler if a new HPA was created or if ScaledObject was updated
246
270
if newHPACreated || scaleObjectSpecChanged {
247
271
if r .requestScaleLoop (ctx , logger , scaledObject ) != nil {
248
- return "Failed to start a new scale loop with scaling logic" , err
272
+ return "failed to start a new scale loop with scaling logic" , err
249
273
}
250
274
logger .Info ("Initializing Scaling logic according to ScaledObject Specification" )
251
275
}
276
+
277
+ if pausedAnnotationFound && conditions .GetPausedCondition ().Status != metav1 .ConditionTrue {
278
+ return "ScaledObject paused replicas are being scaled" , fmt .Errorf ("ScaledObject paused replicas are being scaled" )
279
+ }
252
280
return kedav1alpha1 .ScaledObjectConditionReadySuccessMessage , nil
253
281
}
254
282
@@ -273,7 +301,7 @@ func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, lo
273
301
func (r * ScaledObjectReconciler ) checkTargetResourceIsScalable (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject ) (kedav1alpha1.GroupVersionKindResource , error ) {
274
302
gvkr , err := kedav1alpha1 .ParseGVKR (r .restMapper , scaledObject .Spec .ScaleTargetRef .APIVersion , scaledObject .Spec .ScaleTargetRef .Kind )
275
303
if err != nil {
276
- logger .Error (err , "Failed to parse Group, Version, Kind, Resource" , "apiVersion" , scaledObject .Spec .ScaleTargetRef .APIVersion , "kind" , scaledObject .Spec .ScaleTargetRef .Kind )
304
+ logger .Error (err , "failed to parse Group, Version, Kind, Resource" , "apiVersion" , scaledObject .Spec .ScaleTargetRef .APIVersion , "kind" , scaledObject .Spec .ScaleTargetRef .Kind )
277
305
return gvkr , err
278
306
}
279
307
gvkString := gvkr .GVKString ()
@@ -299,11 +327,11 @@ func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Conte
299
327
unstruct .SetGroupVersionKind (gvkr .GroupVersionKind ())
300
328
if err := r .Client .Get (ctx , client.ObjectKey {Namespace : scaledObject .Namespace , Name : scaledObject .Spec .ScaleTargetRef .Name }, unstruct ); err != nil {
301
329
// resource doesn't exist
302
- logger .Error (err , "Target resource doesn't exist" , "resource" , gvkString , "name" , scaledObject .Spec .ScaleTargetRef .Name )
330
+ logger .Error (err , "target resource doesn't exist" , "resource" , gvkString , "name" , scaledObject .Spec .ScaleTargetRef .Name )
303
331
return gvkr , err
304
332
}
305
333
// resource exist but doesn't expose /scale subresource
306
- logger .Error (errScale , "Target resource doesn't expose /scale subresource" , "resource" , gvkString , "name" , scaledObject .Spec .ScaleTargetRef .Name )
334
+ logger .Error (errScale , "target resource doesn't expose /scale subresource" , "resource" , gvkString , "name" , scaledObject .Spec .ScaleTargetRef .Name )
307
335
return gvkr , errScale
308
336
}
309
337
isScalableCache .Store (gr .String (), true )
@@ -395,12 +423,7 @@ func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *k
395
423
396
424
// ensureHPAForScaledObjectExists ensures that in cluster exist up-to-date HPA for specified ScaledObject, returns true if a new HPA was created
397
425
func (r * ScaledObjectReconciler ) ensureHPAForScaledObjectExists (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject , gvkr * kedav1alpha1.GroupVersionKindResource ) (bool , error ) {
398
- var hpaName string
399
- if scaledObject .Status .HpaName != "" {
400
- hpaName = scaledObject .Status .HpaName
401
- } else {
402
- hpaName = getHPAName (scaledObject )
403
- }
426
+ hpaName := getHPANameOnEnsure (scaledObject )
404
427
foundHpa := & autoscalingv2.HorizontalPodAutoscaler {}
405
428
// Check if HPA for this ScaledObject already exists
406
429
err := r .Client .Get (ctx , types.NamespacedName {Name : hpaName , Namespace : scaledObject .Namespace }, foundHpa )
@@ -414,7 +437,7 @@ func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Cont
414
437
// new HPA created successfully -> notify Reconcile function so it could fire a new ScaleLoop
415
438
return true , nil
416
439
} else if err != nil {
417
- logger .Error (err , "Failed to get HPA from cluster" )
440
+ logger .Error (err , "failed to get HPA from cluster" )
418
441
return false , err
419
442
}
420
443
@@ -431,13 +454,40 @@ func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Cont
431
454
// HPA was found -> let's check if we need to update it
432
455
err = r .updateHPAIfNeeded (ctx , logger , scaledObject , foundHpa , gvkr )
433
456
if err != nil {
434
- logger .Error (err , "Failed to check HPA for possible update" )
457
+ logger .Error (err , "failed to check HPA for possible update" )
435
458
return false , err
436
459
}
437
460
438
461
return false , nil
439
462
}
440
463
464
+ // ensureHPAForScaledObjectIsDeleted ensures that in cluster any HPA for specified ScaledObject is deleted, returns true if no HPA exists
465
+ func (r * ScaledObjectReconciler ) ensureHPAForScaledObjectIsDeleted (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject ) (bool , error ) {
466
+ hpaName := getHPANameOnEnsure (scaledObject )
467
+ foundHpa := & autoscalingv2.HorizontalPodAutoscaler {}
468
+ // Check if HPA for this ScaledObject already exists
469
+ err := r .Client .Get (ctx , types.NamespacedName {Name : hpaName , Namespace : scaledObject .Namespace }, foundHpa )
470
+ if err != nil && errors .IsNotFound (err ) {
471
+ return true , nil
472
+ } else if err != nil {
473
+ logger .Error (err , "failed to get HPA from cluster" )
474
+ return false , err
475
+ }
476
+
477
+ if err := r .deleteHPA (ctx , logger , scaledObject , foundHpa ); err != nil {
478
+ logger .Error (err , "failed to delete HPA from cluster" )
479
+ return false , err
480
+ }
481
+ return true , nil
482
+ }
483
+
484
+ func getHPANameOnEnsure (scaledObject * kedav1alpha1.ScaledObject ) string {
485
+ if scaledObject .Status .HpaName != "" {
486
+ return scaledObject .Status .HpaName
487
+ }
488
+ return getHPAName (scaledObject )
489
+ }
490
+
441
491
func isHpaRenamed (scaledObject * kedav1alpha1.ScaledObject , foundHpa * autoscalingv2.HorizontalPodAutoscaler ) bool {
442
492
// if HPA name defined in SO -> check if equals to the found HPA
443
493
if scaledObject .Spec .Advanced != nil && scaledObject .Spec .Advanced .HorizontalPodAutoscalerConfig != nil && scaledObject .Spec .Advanced .HorizontalPodAutoscalerConfig .Name != "" {
@@ -453,7 +503,7 @@ func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger lo
453
503
454
504
key , err := cache .MetaNamespaceKeyFunc (scaledObject )
455
505
if err != nil {
456
- logger .Error (err , "Error getting key for scaledObject" )
506
+ logger .Error (err , "error getting key for scaledObject" )
457
507
return err
458
508
}
459
509
@@ -471,7 +521,7 @@ func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger lo
471
521
func (r * ScaledObjectReconciler ) stopScaleLoop (ctx context.Context , logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject ) error {
472
522
key , err := cache .MetaNamespaceKeyFunc (scaledObject )
473
523
if err != nil {
474
- logger .Error (err , "Error getting key for scaledObject" )
524
+ logger .Error (err , "error getting key for scaledObject" )
475
525
return err
476
526
}
477
527
@@ -487,7 +537,7 @@ func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr.
487
537
func (r * ScaledObjectReconciler ) scaledObjectGenerationChanged (logger logr.Logger , scaledObject * kedav1alpha1.ScaledObject ) (bool , error ) {
488
538
key , err := cache .MetaNamespaceKeyFunc (scaledObject )
489
539
if err != nil {
490
- logger .Error (err , "Error getting key for scaledObject" )
540
+ logger .Error (err , "error getting key for scaledObject" )
491
541
return true , err
492
542
}
493
543
0 commit comments