Skip to content

fix: apply QuotaPolicy changes to Envoy config without restart - #2766

Merged
aabchoo merged 13 commits into
theagentrouter:mainfrom
AyushSawant18588:fix/quota-policy-live-reconciliation
Oct 9, 2026
Merged

aabchoo merged 13 commits into
theagentrouter:mainfrom
AyushSawant18588:fix/quota-policy-live-reconciliation

Conversation

@AyushSawant18588

@AyushSawant18588 AyushSawant18588 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

When a QuotaPolicy CR was updated, the new configuration was not applied to the data plane until the AI Gateway controller and the Envoy proxy pod were restarted.

The root cause is that a QuotaPolicy feeds two config planes: the rate limit service config (the numeric limits, pushed over xDS) and the Envoy data-plane config (the rate limit filter, cluster, and per-route descriptors, injected by the extension server's PostTranslateModify). The extension server only runs when Envoy Gateway re-translates, which happens when a resource it watches changes. QuotaPolicy is not such a resource. The controller tried to force re-translation by re-reconciling the HTTPRoute, but the regenerated HTTPRoute was identical (its content does not depend on the QuotaPolicy), so the update was a no-op and Envoy Gateway never re-translated.

This change stamps a hash of the applicable QuotaPolicy specs onto the generated HTTPRoute as the aigateway.envoyproxy.io/quota-policy-hash annotation (mirroring the existing stampGatewayConfigHash approach for GatewayConfig). A QuotaPolicy create/update/delete now changes the annotation, making the HTTPRoute genuinely change, which forces Envoy Gateway to re-translate and re-run PostTranslateModify with the latest policy. The hash covers each policy's full spec, so both value-only changes (e.g. a limit bump) and structural changes (e.g. a new model or bucket rule) are picked up live.

This change also fixes QuotaPolicy deletion not propagating to routes in different namespace. Deleting a QuotaPolicy now notifies the referencing AIGatewayRoutes (via the finalizer callback, while TargetRefs are still available) so their generated HTTPRoutes are re-stamped and Envoy Gateway re-translates without the deleted policy, no controller/Envoy restart required. The quota-policy-hash computation also skips terminating policies (non-zero DeletionTimestamp), closing the issue where a cached, soon-to-be-deleted policy could otherwise leave the hash unchanged.

Unit tests cover the hash computation and annotation behavior, and an e2e test verifies the annotation changes when a QuotaPolicy is updated live (no restart).

Manually tested the changes end to end by deploying the updated AI Gateway controller and updating and deleting Quota Policy and verify the changes being translated properly in Envoy config

Co-authored-by: Vignesh Chaturvedi vigneshchaturvedi@gmail.com

@AyushSawant18588
AyushSawant18588 requested a review from a team as a code owner September 30, 2026 16:07
@netlify

netlify Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for theagentrouter ready!

Name Link
🔨 Latest commit a0214c6
🔍 Latest deploy log https://app.netlify.com/projects/theagentrouter/deploys/6ac927295b5dbd0008080029
😎 Deploy Preview https://deploy-preview-2766--theagentrouter.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.87179% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/controller/ai_gateway_route.go 93.02% 3 Missing ⚠️
internal/controller/quota_policy.go 96.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
@missBerg missBerg added bug Something isn't working area/quota Rate limiting, quota, token/cost accounting and attribution area/controller Controller and reconciliation labels Sep 30, 2026
Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
@gavrissh

gavrissh commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

@AyushSawant18588 could you fix the tests?

@vignesh-chaturvedi

Copy link
Copy Markdown
Contributor

One gap in the deletion change, which I ran into while working on the
overlapping #2770. With the cleanup removed from the not-found branch of
Reconcile, a QuotaPolicy that disappears without the finalizer callback keeps
its limits in the rate limit config until the controller restarts, because
configCache is only ever updated one policy at a time.

That can happen two ways:

  • The finalizer is stripped out of band, for example kubectl patch with
    finalizers: null on a stuck object. The controller then only sees not-found.
  • deleteQuotaPolicyConfig fails inside the callback. handleFinalizer logs and
    drops the callback's error and removes the finalizer anyway, so on main the
    not-found branch was the retry.

I checked the first case with a controller test that creates and reconciles a
policy, clears its finalizers, deletes it, and reconciles again. On this branch
the policy's entry is still in configCache afterwards. On main it is removed.
Happy to share the test.

Keeping deleteQuotaPolicyConfig in the not-found branch alongside the new
finalizer-time notification would close it, and since it is idempotent, running
it in both places is safe. If you also want routes refreshed in that case, the
namespace-wide notification in #2770 covers it without needing the targetRefs.

Comment thread internal/controller/ai_gateway_route.go
Comment thread internal/controller/quota_policy.go
Comment thread internal/controller/ai_gateway_route.go Outdated

@vignesh-chaturvedi vignesh-chaturvedi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for picking this up. I checked out ff1c427 and ran the controller tests:
everything passes, and putting the not-found branch back to a bare return fails
all three new deletion tests, so they cover the gap. LGTM for the deletion path.

It also covers the second case I raised. handleFinalizer drops the callback's
error and removes the finalizer anyway, so if the cleanup or the notification in
the callback fails, the not-found reconcile now retries both and requeues on
error. In a normal deletion both paths run, which is safe since each step is
idempotent.

Small nit, in line with aabchoo's helper suggestion on ai_gateway_route.go:
notifyAIGatewayRoutesForNamespace builds the index key with
fmt.Sprintf("%s.%s", ...) as well, so it could use the same helper.

Since notifyAIGatewayRoutesForNamespace and the three new deletion tests are
carried over from #2770, would you mind adding
Co-authored-by: Vignesh Chaturvedi <vigneshchaturvedi@gmail.com> to the PR
description so it ends up in the squash commit? Thanks!

…etRef updates

Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
@vignesh-chaturvedi

Copy link
Copy Markdown
Contributor

Thanks for adding the credit! One small thing: GitHub only links a co-author
when the trailer is on its own line at the very end of the commit message, in
Name <email> form, so the inline @vignesh-chaturvedi mention won't show up
as co-authorship after the squash. Could you move it to the last line of the
description, with a blank line before it, as:

Co-authored-by: Vignesh Chaturvedi <vigneshchaturvedi@gmail.com>

That's the same format earlier squash merges here use. Thanks again!

@AyushSawant18588

AyushSawant18588 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for adding the credit! One small thing: GitHub only links a co-author when the trailer is on its own line at the very end of the commit message, in Name <email> form, so the inline @vignesh-chaturvedi mention won't show up as co-authorship after the squash. Could you move it to the last line of the description, with a blank line before it, as:

Co-authored-by: Vignesh Chaturvedi <vigneshchaturvedi@gmail.com>

That's the same format earlier squash merges here use. Thanks again!

Done. Can you approve if PR looks good?

Signed-off-by: Ayush Sawant <ayush.sawant@nutanix.com>
Comment thread internal/controller/quota_policy.go
@aabchoo
aabchoo enabled auto-merge (squash) October 9, 2026 17:41
@aabchoo
aabchoo merged commit ffdb12e into theagentrouter:main Oct 9, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/controller Controller and reconciliation area/quota Rate limiting, quota, token/cost accounting and attribution bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants