Skip to content

General: Keep a trigger that fails metric spec discovery in the HPA - #8198

Open
rickbrouwer wants to merge 5 commits into
kedacore:mainfrom
rickbrouwer:issue-8168
Open

rickbrouwer wants to merge 5 commits into
kedacore:mainfrom
rickbrouwer:issue-8168

Conversation

@rickbrouwer

@rickbrouwer rickbrouwer commented Sep 17, 2026

Copy link
Copy Markdown
Member

This PR is mainly meant as a proposal, to review the solution direction before investing in polish.

When an external scaler is unreachable, discovery returns nothing for that trigger and the partial result is persisted. The trigger is dropped from the HPA and from status.externalMetricNames, which erases its health status and breaks the metric name lookup the push handler uses, so activations keep being dropped after the scaler recovers.

This keeps the trigger in place instead:

  • the scalers cache remembers the last successful metric specs per trigger and reuses them when discovery fails, so the trigger stays in the HPA
  • an incomplete discovery result is no longer persisted, the reconcile fails and retries
  • a push activation with an unresolvable metric name asks for a reconcile instead of being dropped

Once the trigger keeps its metric spec, the failing value query reaches the existing fallback path, so failureThreshold and behavior now apply to an unreachable external scaler like they do to any other trigger. Before this we never got that far, because without a spec the trigger was gone before its value was ever queried.
Configuring fallback stays optional: without it the trigger reports an error and the HPA holds its current scale.

Checklist

Fixes #8168

Signed-off-by: Rick Brouwer <rickbrouwer@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: kedacore/keda/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: d9d87ea1-8845-47af-b2a9-266a2696f0a2

📥 Commits

Reviewing files that changed from the base of the PR and between 7dbcec8 and 3acb98e.

📒 Files selected for processing (6)
  • controllers/keda/hpa_test.go
  • pkg/scaling/cache/scalers_cache.go
  • pkg/scaling/cache/scalers_cache_test.go
  • pkg/scaling/scale_handler.go
  • pkg/scaling/scale_handler_test.go
  • tests/internals/partial_metric_discovery/partial_metric_discovery_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • kedacore/keda-docs (manual)
  • kedacore/charts (manual)

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change adds generation-scoped last-known metric-specification fallback and explicit incomplete-discovery errors. HPA and push-scaler reconciliation preserve usable metadata during transient failures. Tests cover discovery failure, recovery, persistence, activation, and fallback scaling.

Changes

Partial metric discovery recovery

Layer / File(s) Summary
Metric specification resolution
pkg/scaling/cache/scalers_cache.go, pkg/scaling/cache/scalers_cache_test.go
ScalersCache stores last-known specifications, returns errors for incomplete discovery, retries refresh, and preserves specifications across supported cache replacements.
Reconciliation integration
controllers/keda/..., pkg/scaling/scale_handler.go, pkg/scaling/*_test.go
HPA reconciliation rejects incomplete discovery before updating metrics or status. Push scalers request reconciliation when metric names are unresolved. Tests verify retained metrics, status, generation handling, and cleanup.
End-to-end recovery validation
tests/internals/partial_metric_discovery/partial_metric_discovery_test.go
The test verifies persisted metric names during scaler unavailability, activation after recovery, and configured fallback scaling.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant ExternalScaler
  participant ScalersCache
  participant HPAController
  participant ScaleHandler
  participant ScaledObject
  participant TargetDeployment
  ExternalScaler-->>ScalersCache: return metric specifications
  ScalersCache->>HPAController: provide live or last-known specifications
  HPAController->>ScaledObject: retain metric names during incomplete discovery
  ScaleHandler->>ScaledObject: request reconciliation for unresolved push metrics
  ExternalScaler-->>ScalersCache: become unreachable
  ScalersCache-->>HPAController: return partial specifications and an error
  ExternalScaler->>TargetDeployment: recover and provide activation
  TargetDeployment-->>HPAController: scale after recovery
Loading

Suggested reviewers: wozniakjan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the metric discovery failure, last-known spec reuse, reconciliation behavior, push activation recovery, and test coverage.
Title check ✅ Passed The title clearly summarizes the main change: retaining triggers in the HPA when metric-spec discovery fails.
Linked Issues check ✅ Passed The changes satisfy the coding requirements in #8168. ScalersCache stores and reuses last successful metric specifications by generation, returns errors for incomplete discovery, and preserves usabl…
Out of Scope Changes check ✅ Passed The production changes implement the discovery fallback, HPA protection, status preservation, cache recovery, and push reconciliation required by #8168. The test changes verify those behaviors. No unr…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@snyk-io

snyk-io Bot commented Sep 17, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
🔚 Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

Copy link
Copy Markdown

Thank you for your contribution! 🙏

Please understand that we will do our best to review your PR and give you feedback as soon as possible, but please bear with us if it takes a little longer as expected.

While you are waiting, make sure to:

  • Update the documentation, if needed
  • Add unit & e2e tests for your changes
  • GitHub checks are passing
  • Is the DCO check failing? Here is how you can fix DCO issues

Once the initial tests are successful, a KEDA member will ensure that the e2e tests are run. Once the e2e tests have been successfully completed, the PR may be merged at a later date. Please be patient.

Learn more about our contribution guide.

@keda-automation
keda-automation requested review from a team September 17, 2026 15:43
@rickbrouwer

rickbrouwer commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

/run-e2e partial_metric_discovery_test
Update: You can check the progress here

passed tests: 1
Execution of tests/internals/partial_metric_discovery/partial_metric_discovery_test.go, has passed after "one" attempts
failed tests: 0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 00780298-4496-41fb-b412-e6b0d097d364

📥 Commits

Reviewing files that changed from the base of the PR and between ea2613e and cbf208b.

📒 Files selected for processing (8)
  • controllers/keda/hpa.go
  • controllers/keda/hpa_test.go
  • controllers/keda/scaledobject_controller_test.go
  • pkg/scaling/cache/scalers_cache.go
  • pkg/scaling/cache/scalers_cache_test.go
  • pkg/scaling/scale_handler.go
  • pkg/scaling/scale_handler_test.go
  • tests/internals/partial_metric_discovery/partial_metric_discovery_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • kedacore/keda-docs (manual)
  • kedacore/charts (manual)

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread controllers/keda/hpa_test.go
Signed-off-by: Rick Brouwer <rickbrouwer@gmail.com>
@rickbrouwer

rickbrouwer commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

/run-e2e internals|external*
Update: You can check the progress here

passed tests: 43
Execution of tests/internals/force_activation/force_activation_test.go, has passed after "one" attempts
Execution of tests/internals/eventemitter/azureeventgridtopic/azureeventgridtopic_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/accurate_scaling_strategy/accurate_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_so/external_scaler_so_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_validation/scaled_job_validation_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_object_validation/scaled_object_validation_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_sj/external_scaler_sj_test.go, has passed after "one" attempts
Execution of tests/internals/replicaset_scale/replicaset_scale_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_authentication_validation/trigger_authentication_validation_test.go, has passed after "one" attempts
Execution of tests/internals/subresource_scale/subresource_scale_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in/pause_scale_in_test.go, has passed after "one" attempts
Execution of tests/internals/custom_hpa_name/custom_hpa_name_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler/external_push_scaler_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/eager_scaling_strategy/eager_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/internals/value_metric_type/value_metric_type_test.go, has passed after "one" attempts
Execution of tests/internals/global_custom_ca/global_custom_ca_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler_old_proto/external_push_scaler_old_proto_test.go, has passed after "one" attempts
Execution of tests/internals/status_update/status_update_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_conditions/scaled_job_conditions_test.go, has passed after "one" attempts
Execution of tests/internals/file_based_auth/file_based_auth_test.go, has passed after "one" attempts
Execution of tests/internals/idle_replicas/idle_replicas_test.go, has passed after "one" attempts
Execution of tests/internals/min_replica_sj/min_replica_sj_test.go, has passed after "one" attempts
Execution of tests/internals/cache_metrics/cache_metrics_test.go, has passed after "one" attempts
Execution of tests/internals/events/events_test.go, has passed after "one" attempts
Execution of tests/internals/restore_original/restore_original_test.go, has passed after "one" attempts
Execution of tests/internals/replica_update_so/replica_update_so_test.go, has passed after "one" attempts
Execution of tests/internals/partial_metric_discovery/partial_metric_discovery_test.go, has passed after "one" attempts
Execution of tests/internals/cloudevent_source/cloudevent_source_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject/pause_scaledobject_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_out/pause_scale_out_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in_restore/pause_scale_in_restore_test.go, has passed after "one" attempts
Execution of tests/internals/update_ta/update_ta_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_update_so/trigger_update_so_test.go, has passed after "one" attempts
Execution of tests/secret-providers/aws_identity_external_id/aws_identity_external_id_test.go, has passed after "one" attempts
Execution of tests/internals/polling_irrelevant_so/polling_irrelevant_so_test.go, has passed after "one" attempts
Execution of tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledjob/pause_scaledjob_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_modifiers/scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/fallback_scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/polling_cooldown_so/polling_cooldown_so_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject_explicitly/pause_scaledobject_explicitly_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/deployments/fallback_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/rollouts/fallback_test.go, has passed after "one" attempts
failed tests: 0

@rickbrouwer

rickbrouwer commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

/run-e2e partial_metric_discovery_test
Update: You can check the progress here

passed tests: 1
Execution of tests/internals/partial_metric_discovery/partial_metric_discovery_test.go, has passed after "one" attempts
failed tests: 0

@rickbrouwer

rickbrouwer commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

/run-e2e internals|external*
Update: You can check the progress here

passed tests: 43
Execution of tests/internals/status_update/status_update_test.go, has passed after "one" attempts
Execution of tests/internals/eventemitter/azureeventgridtopic/azureeventgridtopic_test.go, has passed after "one" attempts
Execution of tests/internals/idle_replicas/idle_replicas_test.go, has passed after "one" attempts
Execution of tests/internals/min_replica_sj/min_replica_sj_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler_old_proto/external_push_scaler_old_proto_test.go, has passed after "one" attempts
Execution of tests/internals/restore_original/restore_original_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_so/external_scaler_so_test.go, has passed after "one" attempts
Execution of tests/internals/file_based_auth/file_based_auth_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_validation/scaled_job_validation_test.go, has passed after "one" attempts
Execution of tests/internals/subresource_scale/subresource_scale_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/eager_scaling_strategy/eager_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_object_validation/scaled_object_validation_test.go, has passed after "one" attempts
Execution of tests/internals/force_activation/force_activation_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler/external_push_scaler_test.go, has passed after "one" attempts
Execution of tests/internals/value_metric_type/value_metric_type_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject/pause_scaledobject_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in/pause_scale_in_test.go, has passed after "two" attempts
Execution of tests/internals/replicaset_scale/replicaset_scale_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_authentication_validation/trigger_authentication_validation_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_sj/external_scaler_sj_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in_restore/pause_scale_in_restore_test.go, has passed after "two" attempts
Execution of tests/internals/events/events_test.go, has passed after "one" attempts
Execution of tests/secret-providers/aws_identity_external_id/aws_identity_external_id_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_conditions/scaled_job_conditions_test.go, has passed after "one" attempts
Execution of tests/internals/global_custom_ca/global_custom_ca_test.go, has passed after "one" attempts
Execution of tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go, has passed after "one" attempts
Execution of tests/internals/custom_hpa_name/custom_hpa_name_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/accurate_scaling_strategy/accurate_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/internals/cache_metrics/cache_metrics_test.go, has passed after "one" attempts
Execution of tests/internals/update_ta/update_ta_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_out/pause_scale_out_test.go, has passed after "one" attempts
Execution of tests/internals/cloudevent_source/cloudevent_source_test.go, has passed after "one" attempts
Execution of tests/internals/replica_update_so/replica_update_so_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_update_so/trigger_update_so_test.go, has passed after "one" attempts
Execution of tests/internals/partial_metric_discovery/partial_metric_discovery_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledjob/pause_scaledjob_test.go, has passed after "one" attempts
Execution of tests/internals/polling_irrelevant_so/polling_irrelevant_so_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_modifiers/scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/fallback_scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/polling_cooldown_so/polling_cooldown_so_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject_explicitly/pause_scaledobject_explicitly_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/deployments/fallback_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/rollouts/fallback_test.go, has passed after "one" attempts
failed tests: 0

Signed-off-by: Rick Brouwer <rickbrouwer@gmail.com>
@rickbrouwer

rickbrouwer commented Sep 20, 2026

Copy link
Copy Markdown
Member Author

/run-e2e internals|external*
Update: You can check the progress here

passed tests: 43
Execution of tests/internals/status_update/status_update_test.go, has passed after "one" attempts
Execution of tests/internals/min_replica_sj/min_replica_sj_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_object_validation/scaled_object_validation_test.go, has passed after "one" attempts
Execution of tests/internals/restore_original/restore_original_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_so/external_scaler_so_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler_old_proto/external_push_scaler_old_proto_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_validation/scaled_job_validation_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/accurate_scaling_strategy/accurate_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/internals/idle_replicas/idle_replicas_test.go, has passed after "one" attempts
Execution of tests/scalers/external_scaler_sj/external_scaler_sj_test.go, has passed after "one" attempts
Execution of tests/internals/subresource_scale/subresource_scale_test.go, has passed after "one" attempts
Execution of tests/internals/custom_hpa_name/custom_hpa_name_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_authentication_validation/trigger_authentication_validation_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_out/pause_scale_out_test.go, has passed after "one" attempts
Execution of tests/scalers/external_push_scaler/external_push_scaler_test.go, has passed after "one" attempts
Execution of tests/internals/file_based_auth/file_based_auth_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_strategies/eager_scaling_strategy/eager_scaling_strategy_test.go, has passed after "one" attempts
Execution of tests/internals/scaled_job_conditions/scaled_job_conditions_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject/pause_scaledobject_test.go, has passed after "one" attempts
Execution of tests/internals/trigger_update_so/trigger_update_so_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in_restore/pause_scale_in_restore_test.go, has passed after "two" attempts
Execution of tests/internals/replica_update_so/replica_update_so_test.go, has passed after "one" attempts
Execution of tests/internals/update_ta/update_ta_test.go, has passed after "one" attempts
Execution of tests/secret-providers/aws_identity_external_id/aws_identity_external_id_test.go, has passed after "one" attempts
Execution of tests/internals/eventemitter/azureeventgridtopic/azureeventgridtopic_test.go, has passed after "one" attempts
Execution of tests/internals/replicaset_scale/replicaset_scale_test.go, has passed after "one" attempts
Execution of tests/internals/force_activation/force_activation_test.go, has passed after "one" attempts
Execution of tests/internals/initial_delay_cooldownperiod/initial_delay_cooldownperiod_test.go, has passed after "one" attempts
Execution of tests/internals/events/events_test.go, has passed after "one" attempts
Execution of tests/internals/cache_metrics/cache_metrics_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scale_in/pause_scale_in_test.go, has passed after "one" attempts
Execution of tests/internals/value_metric_type/value_metric_type_test.go, has passed after "one" attempts
Execution of tests/internals/global_custom_ca/global_custom_ca_test.go, has passed after "one" attempts
Execution of tests/internals/scaling_modifiers/scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/cloudevent_source/cloudevent_source_test.go, has passed after "one" attempts
Execution of tests/internals/partial_metric_discovery/partial_metric_discovery_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/fallback_scaling_modifiers_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledjob/pause_scaledjob_test.go, has passed after "one" attempts
Execution of tests/internals/polling_cooldown_so/polling_cooldown_so_test.go, has passed after "one" attempts
Execution of tests/internals/polling_irrelevant_so/polling_irrelevant_so_test.go, has passed after "one" attempts
Execution of tests/internals/pause_scaledobject_explicitly/pause_scaledobject_explicitly_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/deployments/fallback_test.go, has passed after "one" attempts
Execution of tests/internals/fallback/rollouts/fallback_test.go, has passed after "one" attempts
failed tests: 0

@rickbrouwer
rickbrouwer marked this pull request as ready for review September 20, 2026 15:35

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

External Push Scaler: Partial metric discovery drops activation signals after endpoint recovery

1 participant