Skip to content

Commit

Permalink
Clear scalers cache correctly both in Operator and Metrics Server (#2564
Browse files Browse the repository at this point in the history
)

Signed-off-by: Zbynek Roubalik <[email protected]>
  • Loading branch information
zroubalik authored Jan 27, 2022
1 parent 7669f0f commit 6b72e85
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 26 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
### Improvements

- **General:** `keda-operator` Cluster Role: add `list` and `watch` access to service accounts ([#2406](https://github.com/kedacore/keda/pull/2406))|([#2410](https://github.com/kedacore/keda/pull/2410))
- **General:** Delete the cache entry when a ScaledObject is deleted ([#2408](https://github.com/kedacore/keda/pull/2408))
- **General:** Delete the cache entry when a ScaledObject is deleted ([#2564](https://github.com/kedacore/keda/pull/2564))
- **General:** Sign KEDA images published on GitHub Container Registry ([#2501](https://github.com/kedacore/keda/pull/2501))|([#2502](https://github.com/kedacore/keda/pull/2502))|([#2504](https://github.com/kedacore/keda/pull/2504))
- **Azure Pipelines Scaler:** support `poolName` or `poolID` validation ([#2370](https://github.com/kedacore/keda/pull/2370))
- **Graphite Scaler:** use the latest datapoint returned, not the earliest ([#2365](https://github.com/kedacore/keda/pull/2365))
Expand Down
26 changes: 18 additions & 8 deletions controllers/keda/metrics_adapter_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ func (r *MetricsScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue

r.removeFromCache(req.NamespacedName.String())
return ctrl.Result{}, nil
err := r.ScaleHandler.ClearScalersCache(ctx, scaledObject)
if err != nil {
reqLogger.Error(err, "error clearing scalers cache")
}
r.removeFromMetricsCache(req.NamespacedName.String())
return ctrl.Result{}, err
}
// Error reading the object - requeue the request.
reqLogger.Error(err, "Failed to get ScaledObject")
Expand All @@ -70,8 +73,12 @@ func (r *MetricsScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.
// indicated by the deletion timestamp being set.
// This depends on the preexisting finalizer setup in ScaledObjectController.
if scaledObject.GetDeletionTimestamp() != nil {
r.removeFromCache(req.NamespacedName.String())
return ctrl.Result{}, nil
err := r.ScaleHandler.ClearScalersCache(ctx, scaledObject)
if err != nil {
reqLogger.Error(err, "error clearing scalers cache")
}
r.removeFromMetricsCache(req.NamespacedName.String())
return ctrl.Result{}, err
}

reqLogger.V(1).Info("Reconciling ScaledObject", "externalMetricNames", scaledObject.Status.ExternalMetricNames)
Expand All @@ -82,8 +89,11 @@ func (r *MetricsScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.
}

r.addToMetricsCache(req.NamespacedName.String(), scaledObject.Status.ExternalMetricNames)
r.ScaleHandler.ClearScalersCache(ctx, req.Name, req.Namespace)
return ctrl.Result{}, nil
err = r.ScaleHandler.ClearScalersCache(ctx, scaledObject)
if err != nil {
reqLogger.Error(err, "error clearing scalers cache")
}
return ctrl.Result{}, err
}

func (r *MetricsScaledObjectReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
Expand All @@ -105,7 +115,7 @@ func (r *MetricsScaledObjectReconciler) addToMetricsCache(namespacedName string,
(*r.ExternalMetricsInfo) = extMetrics
}

func (r *MetricsScaledObjectReconciler) removeFromCache(namespacedName string) {
func (r *MetricsScaledObjectReconciler) removeFromMetricsCache(namespacedName string) {
scaledObjectsMetricsLock.Lock()
defer scaledObjectsMetricsLock.Unlock()
delete(scaledObjectsMetrics, namespacedName)
Expand Down
10 changes: 6 additions & 4 deletions pkg/mock/mock_scaling/mock_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 17 additions & 7 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string,
logger.V(1).Info("KEDA Metrics Server received request for external metrics", "namespace", namespace, "metric name", info.Metric, "metricSelector", metricSelector.String())
selector, err := labels.ConvertSelectorToLabelsMap(metricSelector.String())
if err != nil {
logger.Error(err, "Error converting Selector to Labels Map")
logger.Error(err, "error converting Selector to Labels Map")
return nil, err
}

Expand All @@ -96,16 +96,15 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string,

scaledObject := &scaledObjects.Items[0]
var matchingMetrics []external_metrics.ExternalMetricValue
cache, err := p.scaleHandler.GetScalersCache(ctx, scaledObject)
if err != nil {
return nil, err
}

cache, err := p.scaleHandler.GetScalersCache(ctx, scaledObject)
metricsServer.RecordScalerObjectError(scaledObject.Namespace, scaledObject.Name, err)
if err != nil {
return nil, fmt.Errorf("error when getting scalers %s", err)
}

scalerError := false

for scalerIndex, scaler := range cache.GetScalers() {
metricSpecs := scaler.GetMetricSpecForScaling(ctx)
scalerName := strings.Replace(fmt.Sprintf("%T", scaler), "*scalers.", "", 1)
Expand All @@ -121,6 +120,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string,
metrics, err = p.getMetricsWithFallback(ctx, metrics, err, info.Metric, scaledObject, metricSpec)

if err != nil {
scalerError = true
logger.Error(err, "error getting metric for scaler", "scaledObject.Namespace", scaledObject.Namespace, "scaledObject.Name", scaledObject.Name, "scaler", scaler)
} else {
for _, metric := range metrics {
Expand All @@ -134,8 +134,18 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string,
}
}

// invalidate the cache for the ScaledObject, if we hit an error in any scaler
// in this case we try to build all scalers (and resolve all secrets/creds) again in the next call
if scalerError {
err := p.scaleHandler.ClearScalersCache(ctx, scaledObject)
if err != nil {
logger.Error(err, "error clearing scalers cache")
}
logger.V(1).Info("scaler error encountered, clearing scaler cache")
}

if len(matchingMetrics) == 0 {
return nil, fmt.Errorf("No matching metrics found for " + info.Metric)
return nil, fmt.Errorf("no matching metrics found for " + info.Metric)
}

return &external_metrics.ExternalMetricValueList{
Expand Down Expand Up @@ -164,7 +174,7 @@ func (p *KedaProvider) GetMetricByName(ctx context.Context, name types.Namespace
// GetMetricBySelector fetches a particular metric for a set of objects matching
// the given label selector. The namespace will be empty if the metric is root-scoped.
func (p *KedaProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
logger.V(0).Info("Received request for custom metric", "groupresource", info.GroupResource.String(), "namespace", namespace, "metric name", info.Metric, "selector", selector.String())
logger.V(0).Info("Received request for custom metric, which is not supported by this adapter", "groupresource", info.GroupResource.String(), "namespace", namespace, "metric name", info.Metric, "selector", selector.String())
return nil, apiErrors.NewServiceUnavailable("not implemented yet")
}

Expand Down
25 changes: 19 additions & 6 deletions pkg/scaling/scale_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package scaling
import (
"context"
"fmt"
"strings"
"sync"
"time"

Expand All @@ -46,7 +45,7 @@ type ScaleHandler interface {
HandleScalableObject(ctx context.Context, scalableObject interface{}) error
DeleteScalableObject(ctx context.Context, scalableObject interface{}) error
GetScalersCache(ctx context.Context, scalableObject interface{}) (*cache.ScalersCache, error)
ClearScalersCache(ctx context.Context, name, namespace string)
ClearScalersCache(ctx context.Context, scalableObject interface{}) error
}

type scaleHandler struct {
Expand Down Expand Up @@ -126,7 +125,10 @@ func (h *scaleHandler) DeleteScalableObject(ctx context.Context, scalableObject
cancel()
}
h.scaleLoopContexts.Delete(key)
delete(h.scalerCaches, key)
err := h.ClearScalersCache(ctx, scalableObject)
if err != nil {
h.logger.Error(err, "error clearing scalers cache")
}
h.recorder.Event(withTriggers, corev1.EventTypeNormal, eventreason.KEDAScalersStopped, "Stopped scalers watch")
} else {
h.logger.V(1).Info("ScaleObject was not found in controller cache", "key", key)
Expand All @@ -151,7 +153,10 @@ func (h *scaleHandler) startScaleLoop(ctx context.Context, withTriggers *kedav1a
tmr.Stop()
case <-ctx.Done():
logger.V(1).Info("Context canceled")
h.ClearScalersCache(ctx, withTriggers.Name, withTriggers.Namespace)
err := h.ClearScalersCache(ctx, scalableObject)
if err != nil {
logger.Error(err, "error clearing scalers cache")
}
tmr.Stop()
return
}
Expand Down Expand Up @@ -201,15 +206,23 @@ func (h *scaleHandler) GetScalersCache(ctx context.Context, scalableObject inter
return h.scalerCaches[key], nil
}

func (h *scaleHandler) ClearScalersCache(ctx context.Context, name, namespace string) {
func (h *scaleHandler) ClearScalersCache(ctx context.Context, scalableObject interface{}) error {
withTriggers, err := asDuckWithTriggers(scalableObject)
if err != nil {
return err
}

key := withTriggers.GenerateIdenitifier()

h.lock.Lock()
defer h.lock.Unlock()

key := strings.ToLower(fmt.Sprintf("%s.%s", name, namespace))
if cache, ok := h.scalerCaches[key]; ok {
cache.Close(ctx)
delete(h.scalerCaches, key)
}

return nil
}

func (h *scaleHandler) startPushScalers(ctx context.Context, withTriggers *kedav1alpha1.WithTriggers, scalableObject interface{}, scalingMutex sync.Locker) {
Expand Down

0 comments on commit 6b72e85

Please sign in to comment.