Skip to content

ROSAENG-62134: Add rate-limit backoff guard for fleet mode HTTP 429 - #264

Open
redhat-chai-bot wants to merge 3 commits into
openshift:masterfrom
redhat-chai-bot:fix/rate-limit-backoff-ROSAENG-62134
Open

ROSAENG-62134: Add rate-limit backoff guard for fleet mode HTTP 429#264
redhat-chai-bot wants to merge 3 commits into
openshift:masterfrom
redhat-chai-bot:fix/rate-limit-backoff-ROSAENG-62134

Conversation

@redhat-chai-bot

@redhat-chai-bot redhat-chai-bot commented Aug 4, 2026

Copy link
Copy Markdown

Summary

When the OCM service log API returns HTTP 429 (rate limit exceeded), ocm-agent fleet mode enters an infinite retry loop. The restoreNotificationStatus() function rolls back lastTransitionTime after every failed send, causing canSendNotification() to return true on every reconciliation cycle (~15 min). This generates ~24 wasted API requests per hour, per affected cluster, indefinitely.

Fixes ROSAENG-62134

Changes

1. pkg/ocm/ocm.go — Typed RateLimitError on 429

  • Added RateLimitError struct with Error() and Unwrap() methods
  • Modified SendServiceLog to return &RateLimitError{...} when the OCM API responds with HTTP 429, in both the SDK error path and the unexpected-status path

2. pkg/handlers/webhookrhobsreceiver.go — Rate-limit-aware backoff guard

  • Added in-memory sync.Map (rateLimitBackoffs) keyed by notificationName:clusterID to track when 429 was last received
  • Added rateLimitRetryInterval constant (30 minutes)
  • Pre-send guard: In processAlert(), before proceeding to the send path, checks the backoff map. If within the 30-minute window, logs and returns nil (skipping the send)
  • Error handler: Uses errors.As() to detect RateLimitError, stores time.Now() in the backoff map, and logs a warning. Existing restoreNotificationStatus() and metrics calls are preserved
  • Success path: Clears any backoff entry after a successful send

Resulting Behavior

Scenario Before After
429 received Retry every ~15 min forever Retry every ~30-45 min (backoff window)
Rate limit clears Still retrying every 15 min Next retry succeeds, 24h ResendWait kicks in
Pod restart during backoff N/A One extra failed attempt, then backoff re-established

Design Notes

  • restoreNotificationStatus() is still called on 429 — this keeps FiringNotificationSentCount accurate
  • The in-memory sync.Map is ephemeral — on pod restart one extra failed attempt occurs, then the backoff is re-established (self-healing)
  • No changes to canSendNotification() — the backoff is an early guard in processAlert, keeping concerns separated
  • No interface changes — SendServiceLog still returns error, so mock generation is unaffected

AI-generated. Review for accuracy.

@ravitri requested in Slack thread

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of OCM API rate limits.
    • Alerts are temporarily paused for affected hosted clusters after a rate-limit response, preventing repeated failed attempts.
    • Alert delivery automatically resumes after the backoff period or once a successful send clears the rate-limit state.
    • Rate-limit errors are now identified consistently across API responses and delivery failures.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Aug 4, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 4, 2026

Copy link
Copy Markdown

@redhat-chai-bot: This pull request references ROSAENG-62134 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

When the OCM service log API returns HTTP 429 (rate limit exceeded), ocm-agent fleet mode enters an infinite retry loop. The restoreNotificationStatus() function rolls back lastTransitionTime after every failed send, causing canSendNotification() to return true on every reconciliation cycle (~15 min). This generates ~24 wasted API requests per hour, per affected cluster, indefinitely.

Fixes ROSAENG-62134

Changes

1. pkg/ocm/ocm.go — Typed RateLimitError on 429

  • Added RateLimitError struct with Error() and Unwrap() methods
  • Modified SendServiceLog to return &RateLimitError{...} when the OCM API responds with HTTP 429, in both the SDK error path and the unexpected-status path

2. pkg/handlers/webhookrhobsreceiver.go — Rate-limit-aware backoff guard

  • Added in-memory sync.Map (rateLimitBackoffs) keyed by notificationName:clusterID to track when 429 was last received
  • Added rateLimitRetryInterval constant (30 minutes)
  • Pre-send guard: In processAlert(), before proceeding to the send path, checks the backoff map. If within the 30-minute window, logs and returns nil (skipping the send)
  • Error handler: Uses errors.As() to detect RateLimitError, stores time.Now() in the backoff map, and logs a warning. Existing restoreNotificationStatus() and metrics calls are preserved
  • Success path: Clears any backoff entry after a successful send

Resulting Behavior

Scenario Before After
429 received Retry every ~15 min forever Retry every ~30-45 min (backoff window)
Rate limit clears Still retrying every 15 min Next retry succeeds, 24h ResendWait kicks in
Pod restart during backoff N/A One extra failed attempt, then backoff re-established

Design Notes

  • restoreNotificationStatus() is still called on 429 — this keeps FiringNotificationSentCount accurate
  • The in-memory sync.Map is ephemeral — on pod restart one extra failed attempt occurs, then the backoff is re-established (self-healing)
  • No changes to canSendNotification() — the backoff is an early guard in processAlert, keeping concerns separated
  • No interface changes — SendServiceLog still returns error, so mock generation is unaffected

AI-generated. Review for accuracy.

@ravitri requested in Slack thread

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested review from Tafhim and chamalabey August 4, 2026 01:43
@openshift-ci

openshift-ci Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: redhat-chai-bot
Once this PR has been reviewed and has the lgtm label, please assign ravitri for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@redhat-chai-bot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c597825f-b750-4b66-ae8a-ed3334f81e11

📥 Commits

Reviewing files that changed from the base of the PR and between d7f6acc and 7a10962.

📒 Files selected for processing (4)
  • pkg/handlers/webhookrhobsreceiver.go
  • pkg/handlers/webhookrhobsreceiver_test.go
  • pkg/ocm/ocm.go
  • pkg/ocm/ocm_test.go

Walkthrough

Changes

Rate-limit backoff

Layer / File(s) Summary
Typed OCM rate-limit errors
pkg/ocm/ocm.go
SendServiceLog returns RateLimitError when OCM responds with HTTP 429. The error wraps the original error.
Notification backoff tracking
pkg/handlers/webhookrhobsreceiver.go
The receiver tracks backoff per notification and hosted cluster, skips firing alerts for 30 minutes, records rate-limit failures, and clears backoff after successful sends.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebhookReceiver
  participant SendServiceLog
  participant OCMAPI
  participant BackoffStore

  WebhookReceiver->>BackoffStore: Check notification and cluster backoff
  BackoffStore-->>WebhookReceiver: Active or expired backoff
  WebhookReceiver->>SendServiceLog: Send firing notification
  SendServiceLog->>OCMAPI: Submit service log
  OCMAPI-->>SendServiceLog: HTTP 429 or success
  SendServiceLog-->>WebhookReceiver: RateLimitError or success
  WebhookReceiver->>BackoffStore: Record or clear backoff
Loading

Possibly related issues

  • openshift/ocm-agent-operator#319 — The PR adds typed HTTP 429 detection and rate-limit backoff handling described by the issue.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error The new warning logs write alert _id as hosted_cluster_id; this is a customer cluster identifier and may expose customer data in logs. Remove the raw cluster ID from warning fields or use an approved redacted value; keep rate-limit logs limited to non-sensitive identifiers.
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the HTTP 429 rate-limit backoff change for fleet mode.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The PR changes only two production Go files; no *_test.go files or Ginkgo test declarations were added or modified.
Test Structure And Quality ✅ Passed The pull request changes only production Go files; it adds no Ginkgo tests or test setup, waits, resources, or assertions to review.
Microshift Test Compatibility ✅ Passed The commit changes only pkg/handlers/webhookrhobsreceiver.go and pkg/ocm/ocm.go; it adds no Ginkgo tests or test files requiring MicroShift API compatibility review.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The patch changes only two production Go files (+54 lines); it adds no Ginkgo e2e tests or multi-node assumptions, so SNO compatibility review is not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed The commit modifies only OCM error handling and webhook backoff code; it adds no manifests, controllers, replicas, affinity, node selectors, tolerations, or topology constraints.
Ote Binary Stdout Contract ✅ Passed The PR changes only OCM and handler code; added lines contain no stdout writes, OTE entrypoints, suite setup, klog calls, or output redirection changes.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The PR adds no Ginkgo e2e tests; its diff changes only pkg/handlers/webhookrhobsreceiver.go and pkg/ocm/ocm.go.
No-Weak-Crypto ✅ Passed The PR adds only HTTP 429 error handling and backoff logic; changed-line and repository scans found no MD5, SHA1, DES, RC4, Blowfish, ECB, or custom crypto usage.
Container-Privileges ✅ Passed The PR changes only Go files. No changed manifest contains privileged, hostPID, hostNetwork, hostIPC, SYS_ADMIN, or enabled allowPrivilegeEscalation settings.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

… 429

When the OCM API responds with HTTP 429 (Too Many Requests) during
service log sends, ocm-agent was retrying indefinitely on every
AlertManager webhook delivery, creating a retry storm.

This change:
- Adds a RateLimitError type in pkg/ocm that wraps 429 responses from
  SendServiceLog, allowing callers to distinguish rate limits from
  other errors.
- Adds an in-memory per-notification:cluster backoff map in the RHOBS
  webhook handler. On a 429 response the timestamp is recorded and
  subsequent firing alerts for that pair are silently skipped for 30
  minutes, breaking the retry loop.
- Clears the backoff entry on a successful send so normal operation
  resumes immediately once the rate limit lifts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@redhat-chai-bot
redhat-chai-bot force-pushed the fix/rate-limit-backoff-ROSAENG-62134 branch from d7f6acc to 4d92ada Compare August 4, 2026 01:48

@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: 3

🧹 Nitpick comments (1)
pkg/handlers/webhookrhobsreceiver.go (1)

46-51: 🩺 Stability & Availability | 🔵 Trivial

Confirm the deployment scope for rateLimitBackoffs.

rateLimitBackoffs is process-local. If the receiver runs with multiple replicas, a 429 under notification:clusterID in one replica will not suppress retries in another. Confirm single-replica scope is intended, or make the backoff shared.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/handlers/webhookrhobsreceiver.go` around lines 46 - 51, Confirm that the
process-local rateLimitBackoffs in the webhook receiver is only intended for
single-replica deployments; if multiple replicas are supported, replace the
sync.Map-based tracking with shared backoff storage so a 429 for the same
notification:clusterID suppresses retries across replicas.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/handlers/webhookrhobsreceiver.go`:
- Around line 396-399: Remove the "hosted_cluster_id" field from the Warn
logging calls around the rate-limit backoff handling, including both occurrences
associated with the alert notification. Preserve the notification name and
existing warning message while ensuring no customer resource identifier is
logged.
- Around line 391-403: Protect the entire rate-limit backoff state transition
with one shared mutex: the expired-entry check/deletion in processAlert, the 429
timestamp recording, and the successful-request cleanup. Update the logic around
rateLimitBackoffs and the related branches near the existing cleanup so a
request cannot delete or clear a newer timestamp stored by another request;
alternatively, condition cleanup on removing the exact loaded value.

In `@pkg/ocm/ocm.go`:
- Around line 258-260: Update the HTTP 429 branch in the OCM send flow to
preserve the original send error for errors.As traversal. Return a
RateLimitError whose Err is the original err without replacing it with a
non-wrapping fmt.Errorf, while retaining the existing rate-limit detection.

---

Nitpick comments:
In `@pkg/handlers/webhookrhobsreceiver.go`:
- Around line 46-51: Confirm that the process-local rateLimitBackoffs in the
webhook receiver is only intended for single-replica deployments; if multiple
replicas are supported, replace the sync.Map-based tracking with shared backoff
storage so a 429 for the same notification:clusterID suppresses retries across
replicas.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 05fd039e-21d8-439e-a7d7-8bc39e52cfd3

📥 Commits

Reviewing files that changed from the base of the PR and between f50872a and d7f6acc.

📒 Files selected for processing (2)
  • pkg/handlers/webhookrhobsreceiver.go
  • pkg/ocm/ocm.go

Comment thread pkg/handlers/webhookrhobsreceiver.go
Comment thread pkg/handlers/webhookrhobsreceiver.go
Comment thread pkg/ocm/ocm.go
@codecov-commenter

codecov-commenter commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.21%. Comparing base (f50872a) to head (7a10962).

Files with missing lines Patch % Lines
pkg/ocm/ocm.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #264      +/-   ##
==========================================
+ Coverage   55.67%   56.21%   +0.54%     
==========================================
  Files          23       23              
  Lines        1895     1923      +28     
==========================================
+ Hits         1055     1081      +26     
- Misses        785      786       +1     
- Partials       55       56       +1     
Files with missing lines Coverage Δ
pkg/handlers/webhookrhobsreceiver.go 91.63% <100.00%> (+0.72%) ⬆️
pkg/ocm/ocm.go 89.03% <75.00%> (-0.77%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Adds unit tests for both the RateLimitError type and the rate-limit
backoff behavior in the fleet webhook handler:

- RateLimitError: Error(), Unwrap(), errors.As() traversal
- SendServiceLog: returns *RateLimitError on 429, plain error on 500
- processAlert: backoff map populated on 429, subsequent alerts
  skipped within window, normal operation after expiry, entry cleared
  on success

Also addresses CodeRabbit review feedback:
- Remove hosted_cluster_id from warning logs (customer data concern)
- Use %w instead of %v to preserve original error in RateLimitError
- Remove eager Delete of expired entries (race-safe: stale expired
  entries are harmless since the pre-send check only blocks within
  the 30-minute window)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ravitri

ravitri commented Aug 4, 2026

Copy link
Copy Markdown
Member

Addresses openshift/ocm-agent-operator#319

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@redhat-chai-bot

Copy link
Copy Markdown
Author

/retest ci/prow/images


AI-generated. Review for accuracy.

@redhat-chai-bot

Copy link
Copy Markdown
Author

/retest pull-ci-openshift-ocm-agent-master-images


AI-generated. Review for accuracy.

@redhat-chai-bot

Copy link
Copy Markdown
Author

/test images


AI-generated. Review for accuracy.

@openshift-ci

openshift-ci Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@redhat-chai-bot: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants