Skip to content

feat: add optional name field to RateLimitRule for improved observability#9466

Open
Aias00 wants to merge 4 commits into
envoyproxy:mainfrom
Aias00:worktree-fix-issue-9151
Open

feat: add optional name field to RateLimitRule for improved observability#9466
Aias00 wants to merge 4 commits into
envoyproxy:mainfrom
Aias00:worktree-fix-issue-9151

Conversation

@Aias00

@Aias00 Aias00 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements the enhancement requested in #9151, adding an optional name field to RateLimitRule for improved observability and debugging.

Problem: Current global rate limit keys use array indices (e.g., envoy-gateway-system/gateway/rule/0), which are:

  • Not meaningful in metrics and dashboards
  • Unstable when rule ordering changes

Solution: Allow users to provide meaningful names for rate limit rules.

Changes

API Changes

  • Add optional name field to RateLimitRule struct
  • Validation:
    • Pattern: [a-zA-Z0-9._-]+ (alphanumeric, dots, underscores, hyphens)
    • MaxLength: 64 characters
    • CEL validation: Cannot be purely numeric (at field level)
    • CEL validation: Must be unique within a policy (at Rules array level)

IR Translation

  • When name is set: <policy-ns>/<policy-name>/rule/<rule-name>
  • When name is not set: <policy-ns>/<policy-name>/rule/<rule-index> (existing behavior)

Testing

  • Gateway API translation tests for named rules (including shared rules)
  • XDS translator tests
  • CEL validation tests covering:
    • Valid named rules
    • Duplicate names rejection
    • Purely numeric names rejection

Example Usage

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: policy-for-route
spec:
  rateLimit:
    type: Global
    global:
      rules:
      - name: basic-limit  # Meaningful stable identifier
        clientSelectors:
        - headers:
          - name: x-user-id
            value: one
        limit:
          requests: 10
          unit: Minute
      - name: premium-limit
        clientSelectors:
        - headers:
          - name: x-user-id
            value: premium
        limit:
          requests: 100
          unit: Minute
      - clientSelectors:  # No name, uses index
        - sourceCIDR:
            type: Distinct
            value: 192.168.0.0/16
        limit:
          requests: 50
          unit: Hour

Generated keys:

  • default/policy-for-route/rule/basic-limit (uses provided name)
  • default/policy-for-route/rule/premium-limit (uses provided name)
  • default/policy-for-route/rule/2 (uses index, no name provided)

Benefits

  1. Improved Observability: Meaningful keys in metrics, dashboards, and alerts
  2. Stability: Keys unchanged when rule ordering changes
  3. Backward Compatible: Optional field, existing configurations continue to work
  4. No Breaking Changes: Default behavior preserved when name is not provided

Test Results

✅ All rate limit related tests pass
✅ CEL validation tests pass
✅ Gateway API translation tests pass
✅ XDS translator tests pass

Resolves: #9151

@Aias00
Aias00 requested a review from a team as a code owner July 9, 2026 23:47
Copilot AI review requested due to automatic review settings July 9, 2026 23:47
@netlify

netlify Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy Preview for cerulean-figolla-1f9435 ready!

Name Link
🔨 Latest commit 5d5c7d3
🔍 Latest deploy log https://app.netlify.com/projects/cerulean-figolla-1f9435/deploys/6a5f6ea0d3db8800077f50d4
😎 Deploy Preview https://deploy-preview-9466--cerulean-figolla-1f9435.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.

Copilot AI 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.

Pull request overview

Adds an optional, user-defined name to RateLimitRule to produce stable, human-meaningful rate limit keys (improving observability) while preserving the existing index-based key format when name is unset.

Changes:

  • Introduces RateLimitRule.name with validation (pattern/length, “not purely numeric”, and per-policy uniqueness via CEL).
  • Updates BackendTrafficPolicy IR translation to use <ns>/<policy>/rule/<name> when provided (else <rule-index>).
  • Adds/updates CEL validation tests and Gateway API / xDS translator golden testdata, plus release note and generated artifacts.

Reviewed changes

Copilot reviewed 13 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/cel-validation/backendtrafficpolicy_test.go Adds CEL validation coverage for named rules (valid, duplicate, numeric-only, invalid chars).
site/content/en/latest/api/extension_types.md Documents the new name field on RateLimitRule in the rendered API docs.
release-notes/current/new_features/9151-named-ratelimit-rules-backendtrafficpolicy.md Release note fragment for the new optional rule name behavior.
internal/xds/translator/testdata/out/ratelimit-config/global-shared-named-rule.yaml Golden output verifying descriptor keys include the named rule.
internal/xds/translator/testdata/out/ratelimit-config/global-shared-named-rule.routes.yaml Golden output verifying route rate limit actions use the named rule key.
internal/xds/translator/testdata/in/ratelimit-config/global-shared-named-rule.yaml Test input covering shared named-rule behavior around /rule/ handling.
internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml Golden output ensuring merges preserve the new name field.
internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml Golden output ensuring JSON merge preserves the new name field.
internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-rule-names.out.yaml Golden output showing mixed named/unnamed rule key generation.
internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-rule-names.in.yaml Test input for multiple named global rules plus an unnamed fallback.
internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-named-rule.out.yaml Golden output for named shared + named non-shared + unnamed rules.
internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit-named-rule.in.yaml Test input for named shared/non-shared rules and unnamed fallback.
internal/gatewayapi/helpers.go Updates helper to format rule keys using optional rule name.
internal/gatewayapi/backendtrafficpolicy.go Uses the optional rule name when setting IR rule Name for local/global rate limits.
charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml Updates generated CRD schema to include name + validations.
charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml Updates generated CRD schema to include name + validations.
api/v1alpha1/zz_generated.deepcopy.go Ensures RateLimitRule.Name is deep-copied.
api/v1alpha1/ratelimit_types.go Adds RateLimitRule.Name and related kubebuilder + CEL validations.
Files not reviewed (1)
  • api/v1alpha1/zz_generated.deepcopy.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | _string_ | false | | Name is a user-facing name for this rule that can be used for debugging<br />and observability. When set, the rate limit key will include this name,<br />making it easier to identify in metrics and dashboards.<br />The name must be unique within a policy and should be a stable identifier<br />that won't change when the rule order changes.<br />When name is set, the rate limit key format becomes:<br /><policy-namespace>/<policy-name>/rule/<rule-name><br />When name is not set, the format remains:<br /><policy-namespace>/<policy-name>/rule/<rule-index> |

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79454224d9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-index>
irRules[i].Name = irRuleName(policy.Namespace, policy.Name, i)
// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-name-or-index>
irRules[i].Name = irRuleName(policy.Namespace, policy.Name, i, rule.Name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include rule names in non-shared global descriptors

For the default global case (shared unset/false), this IR name is never used by the xDS/RLS config: I checked internal/xds/translator/ratelimit.go, where buildRouteRateLimits and addRateLimitDescriptor still choose getRouteDescriptor(route.Name) plus getRouteRuleDescriptor(domainRuleIdx, ...). A policy like the added example with name: basic-limit but no shared: true therefore still emits rule-0-* descriptors and will churn counters/metrics when rules are reordered. Please thread the named key into the non-shared descriptor path, or restrict the API/docs to the shared-only case.

Useful? React with 👍 / 👎.

// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-index>
irRule.Name = irRuleName(policy.Namespace, policy.Name, i)
// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-name-or-index>
irRule.Name = irRuleName(policy.Namespace, policy.Name, i, rule.Name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Either honor local rule names or reject them

This accepts spec.rateLimit.local.rules[].name and stores it in the IR, but I checked internal/xds/translator/local_ratelimit.go and local descriptor/action construction never reads rule.Name; it uses the loop index rIdx for all rule-<n>-* keys. Users can now configure a valid local rule name that has no observable effect and still gets index-based descriptor churn after reordering. Please either use the name in local descriptor generation or disallow/document the field for local rate limits.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.55%. Comparing base (2874485) to head (5d5c7d3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9466      +/-   ##
==========================================
- Coverage   75.59%   75.55%   -0.04%     
==========================================
  Files         252      252              
  Lines       41713    41715       +2     
==========================================
- Hits        31531    31519      -12     
- Misses       8057     8067      +10     
- Partials     2125     2129       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Aias00
Aias00 force-pushed the worktree-fix-issue-9151 branch from 9c984e1 to 793215e Compare July 10, 2026 12:48
@arkodg
arkodg requested a review from zirain July 18, 2026 17:58
Aias00 and others added 4 commits July 21, 2026 21:05
…lity

This enhancement allows users to provide meaningful names for rate limit rules,
making rate limit keys more identifiable in metrics and dashboards.

API Changes:
- Add optional 'name' field to RateLimitRule with validation
- Pattern: [a-zA-Z0-9._-]+ (alphanumeric, dots, underscores, hyphens)
- Max length: 64 characters
- Cannot be purely numeric (CEL validation at field level)
- Must be unique within a policy (CEL validation at Rules array level)

IR Translation:
- When name is set: <policy-ns>/<policy-name>/rule/<rule-name>
- When name is not set: <policy-ns>/<policy-name>/rule/<rule-index> (existing behavior)

Testing:
- Add test case for named rules (including shared rules)
- Add CEL validation tests for valid names, duplicates, and numeric names
- All existing tests continue to pass

Benefits:
- Improved observability: meaningful keys in metrics/dashboards
- Stability: keys unchanged when rule ordering changes
- Backward compatible: optional field preserves existing behavior

Resolves: envoyproxy#9151

Signed-off-by: liuhy <liuhongyu@apache.org>
Co-Authored-By: Claude <noreply@anthropic.com>
The release note for envoyproxy#9151 previously mentioned only the global
rate-limit metric key, but the change to irRuleName affects both the
local and global rate-limit rule keys. Update the note to reflect the
actual scope.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: liuhy <liuhongyu@apache.org>
The CEL validation test used ptr.To from k8s.io/utils/ptr, which is
forbidden by forbidigo (use new() builtin or a typed helper instead).
Replace the 7 ptr.To(...) call sites with a small file-local ptrString
helper that matches the existing SectionNamePtr/ObjectNamePtr convention,
and drop the now-unused ptr import.

The new ratelimit-rule-names testdata YAML was missing a trailing
newline, which fails yamllint's new-line-at-end-of-file rule (part of
make lint / gen-check). Add the trailing newline.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: liuhy <liuhongyu@apache.org>
Regenerate the gateway-crds-helm golden snapshots so they reflect the
new optional RateLimitRule.name schema and its CEL validations
(non-numeric name, unique names within global/local rule slices) added
in api/v1alpha1/ratelimit_types.go. The charts CRD sources were already
regenerated; only the test/helm/gateway-crds-helm/*.out.yaml snapshots
were stale, causing gen-check (helm-template) to fail.

Signed-off-by: liuhy <liuhongyu@apache.org>
Co-Authored-By: Claude <noreply@anthropic.com>
@zirain
zirain force-pushed the worktree-fix-issue-9151 branch from f3a6953 to 5d5c7d3 Compare July 21, 2026 13:05
@zirain

zirain commented Jul 21, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d5c7d36ae

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-index>
irRule.Name = irRuleName(policy.Namespace, policy.Name, i)
// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-name-or-index>
irRule.Name = irRuleName(policy.Namespace, policy.Name, i, rule.Name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wire local rule names into local descriptors

When a local rule has spec.rateLimit.local.rules[].name, this line copies the name into IR, but the local xDS path never consumes ir.RateLimitRule.Name: buildRouteLocalRateLimits still passes the loop index rIdx into every build*LocalRateLimit* helper, which emits rule-<index>-... descriptor keys (internal/xds/translator/local_ratelimit.go:209-233,269-287). As a result accepted local names do not change local rate-limit keys/metrics, despite the API and release note promising both local and global rule keys use the provided name; either wire the name into those descriptors or reject/document local names as unsupported.

Useful? React with 👍 / 👎.

// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-index>
irRules[i].Name = irRuleName(policy.Namespace, policy.Name, i)
// Set the Name field as <policy-ns>/<policy-name>/rule/<rule-name-or-index>
irRules[i].Name = irRuleName(policy.Namespace, policy.Name, i, rule.Name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wire non-shared names into global descriptors

For a global rule without shared: true, setting this IR name still has no effect on the generated RLS keys: buildRouteRateLimits and addRateLimitDescriptor choose getRouteDescriptor(route.Name) for non-shared rules and then derive match keys from getRouteRuleDescriptor(domainRuleIdx, ...) (internal/xds/translator/ratelimit.go:260-298,759-764,856-866). Thus a normal named rule such as by-org still produces route/rule-1-match-* descriptors and loses the stable user name in Envoy/RLS metrics; please use rule.Name in the non-shared descriptor/action path or narrow the API/docs to shared rules.

Useful? React with 👍 / 👎.

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.

Improved global rate limiting metric keys

3 participants