Skip to content

feat(policy-engine): reuse unchanged policy chains on xDS update#2741

Open
renuka-fernando wants to merge 2 commits into
wso2:mainfrom
renuka-fernando:get-pol-factory
Open

feat(policy-engine): reuse unchanged policy chains on xDS update#2741
renuka-fernando wants to merge 2 commits into
wso2:mainfrom
renuka-fernando:get-pol-factory

Conversation

@renuka-fernando

Copy link
Copy Markdown
Contributor

Purpose

The policy engine rebuilt every route's policy chain on every xDS update. Because xDS is State-of-the-World, any artifact change (deploy/redeploy/delete of any REST API, LLM proxy, etc.) pushes the full snapshot, and HandlePolicyChainUpdate unconditionally calls buildPolicyChainregistry.GetInstance → each policy's GetPolicy factory for every policy on every route. So redeploying one API re-instantiated the policy instances of all unrelated APIs, discarding any per-instance state (token caches, connection pools, rate-limit windows) and churning resources. This is what forces stateful policies such as backend-jwt to work around the behavior with a process-wide singleton cache.

Resolves #2740

Goals

Reconcile each xDS snapshot against what is already applied and rebuild only the routes whose configuration actually changed, so a redeploy of one API no longer re-runs other APIs' GetPolicy factories. State-of-the-World remains unchanged on the wire; the reconciliation is entirely policy-engine side.

Approach

  • Add a per-route lastApplied map (signature + built chain) on the singleton ResourceHandler; no lock needed since HandlePolicyChainUpdate runs serially on the single ADS goroutine.
  • Compute a stable, order-sensitive canonical-JSON signature of each route's behavioral config: RouteKey, the whole ordered Policies list, and APIId/APIName/APIVersion. Volatile Metadata fields (CreatedAt/UpdatedAt/ResourceVersion) and Context (not read by buildPolicyChain) are deliberately excluded so an unchanged route does not look changed every push.
  • On each snapshot: reuse the existing chain pointer when the signature matches (skipping GetPolicy), rebuild only changed/new routes, and drop routes absent from the snapshot. The whole route map is still applied atomically via ApplyWholeRoutesAndSensitiveValues.
  • Fail-safe: on any signature error, rebuild and do not record the route, so it re-evaluates next snapshot rather than risking a stale reuse. Only successfully-built chains ever enter lastApplied, so an invalid chain can never be reused.
  • Add per-route decision debug logs (REUSED/REBUILT/NEW/REMOVED) and per-snapshot reused/rebuilt/new/removed counts. Cross-reference comments keep buildPolicyChain and the signature projection in sync.

User stories

N/A

Documentation

N/A — internal policy-engine behavior change with no user-facing configuration or API surface change.

Automation tests

  • Unit tests

    internal/xdsclient/reconcile_test.go covers: the original-bug regression (redeploy one API, unrelated API's factory count stays 1 and its chain pointer is identical), reuse across a volatile-metadata bump, reorder-triggers-rebuild, removal drops the route, a full routeSignature field-sensitivity table (param add/remove/change, reorder, name/version/enabled/executionCondition, api id/name/version) with volatile fields asserted to NOT change the signature, and a reflection-based completeness guard that fails if a routeSignatureView field lacks a mutator.

  • Integration tests

    Existing gateway integration suite exercises deploy/redeploy/delete and policy execution. A follow-up backend-jwt reuse scenario (warm a token cache, redeploy an unrelated API, assert the cached token still serves) is recommended to lock in the intended cross-deploy reuse.

Security checks

Samples

N/A

Related PRs

N/A

Test environment

Go 1.26.5 on macOS (darwin/arm64). Unit tests via go test ./internal/xdsclient/...; go build ./... and go vet ./... clean for the policy-engine module.

xDS is State-of-the-World, so every artifact change pushes the full
snapshot and HandlePolicyChainUpdate rebuilt every route's chain,
re-running each policy's GetPolicy factory. Redeploying one API thus
re-instantiated every other API's policies, churning pools/state.

Reconcile each snapshot against the last-applied set:
- Hash each route's behavioral config (canonical JSON, order-sensitive,
  excluding volatile Metadata fields) and reuse the existing chain
  pointer when unchanged; rebuild only changed/new routes; drop removed.
- Fail-safe to rebuild on signature errors; add per-route decision
  debug logs and per-snapshot reused/rebuilt/new/removed counts.
- Add signature + reconciliation tests incl. a completeness guard.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@renuka-fernando, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6be0629f-a31b-41ba-ad6f-6db91261fe29

📥 Commits

Reviewing files that changed from the base of the PR and between edb2e45 and 066695c.

📒 Files selected for processing (2)
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/reconcile_test.go
📝 Walkthrough

Walkthrough

The xDS client now computes stable route signatures, reuses unchanged policy chains across snapshots, rebuilds changed or new routes, removes absent routes, and applies the reconciled set atomically. Tests cover reuse, rebuild triggers, removals, signature sensitivity, and signature-field completeness.

Changes

Route policy-chain reconciliation

Layer / File(s) Summary
Route signature contract
gateway/gateway-runtime/policy-engine/internal/xdsclient/signature.go, gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
Defines a stable SHA-256 signature over behavioral route and policy inputs, excluding volatile metadata.
Snapshot reconciliation
gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
Tracks applied routes, reuses unchanged chains, rebuilds changed or new routes, removes missing routes, and commits state after atomic application.
Reconciliation validation
gateway/gateway-runtime/policy-engine/internal/xdsclient/reconcile_test.go
Tests policy-chain reuse, rebuild conditions, route removal, signature sensitivity, and completeness of the signature view.

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

Sequence Diagram(s)

sequenceDiagram
  participant ADS
  participant HandlePolicyChainUpdate
  participant routeSignature
  participant buildPolicyChain
  participant Kernel
  ADS->>HandlePolicyChainUpdate: provide policy-chain snapshot
  HandlePolicyChainUpdate->>routeSignature: compute route signature
  alt unchanged route
    routeSignature-->>HandlePolicyChainUpdate: reuse applied PolicyChain
  else changed or new route
    routeSignature-->>HandlePolicyChainUpdate: return changed signature
    HandlePolicyChainUpdate->>buildPolicyChain: build policy chain
  end
  HandlePolicyChainUpdate->>Kernel: ApplyWholeRoutesAndSensitiveValues
  Kernel-->>HandlePolicyChainUpdate: apply result
Loading

Suggested reviewers: ashera96, pubudu538, thivindu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: reusing unchanged policy chains during xDS updates.
Description check ✅ Passed The description covers all required template sections and includes purpose, goals, approach, tests, security, and environment details.
Linked Issues check ✅ Passed The changes match #2740 by reusing unchanged routes, rebuilding changed/new ones, removing absent routes, and adding the expected tests.
Out of Scope Changes check ✅ Passed The added signature utility, reconciliation logic, and tests are all directly tied to the stated xDS reuse objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go`:
- Around line 241-248: Update the reconciliation logic around the removed-route
loop and the related handling near the second reported location to track route
keys present in the snapshot separately from successfully rebuilt entries in
chains. Use the snapshot-key set when determining removals, so routes that fail
validation or rebuilding are not logged or counted as REMOVED, and add a test
covering a failed rebuild.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 83c881c7-01cb-4c1c-86fd-e13143a7c20e

📥 Commits

Reviewing files that changed from the base of the PR and between db88a9d and edb2e45.

📒 Files selected for processing (3)
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/reconcile_test.go
  • gateway/gateway-runtime/policy-engine/internal/xdsclient/signature.go

Comment thread gateway/gateway-runtime/policy-engine/internal/xdsclient/handler.go
A route present in the snapshot but dropped due to a validation or
rebuild failure was logged and counted as REMOVED, because the check
tested membership in the successfully-built chains. Track the route
keys seen in the snapshot separately and base removal detection on
that set, so present-but-rejected routes (already reported by their
own warning/error log) are not double-reported.

Add a failed-rebuild test asserting the dropped route is not logged
or counted as REMOVED.

Addresses CodeRabbit review on wso2#2741.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rebuild only changed routes' policy chains on xDS updates

1 participant