Skip to content

feat: add L4RoutePolicy CRD for attaching stream plugins to Gateway API L4 routes#416

Open
AlinsRan wants to merge 4 commits into
masterfrom
feat/l4routepolicy
Open

feat: add L4RoutePolicy CRD for attaching stream plugins to Gateway API L4 routes#416
AlinsRan wants to merge 4 commits into
masterfrom
feat/l4routepolicy

Conversation

@AlinsRan
Copy link
Copy Markdown
Contributor

@AlinsRan AlinsRan commented May 14, 2026

Summary

Closes #403

Add a new L4RoutePolicy custom resource that enables attaching APISIX stream plugins to Gateway API L4 routes (TCPRoute, UDPRoute, TLSRoute). This follows the Gateway API Policy Attachment pattern (GEP-713), consistent with the existing BackendTrafficPolicy and HTTPRoutePolicy CRDs.

Motivation

AIPSIX supports attaching stream plugins (limit-conn, ip-restriction, mqtt-proxy, syslog, traffic-split, etc.) to stream_routes. However, the controller had no mechanism to express this through Gateway API resources.

Changes

New CRD: L4RoutePolicy

apiVersion: apisix.apache.org/v1alpha1
kind: L4RoutePolicy
metadata:
  name: tcp-rate-limit
  namespace: default
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: TCPRoute   # also UDPRoute / TLSRoute
      name: my-tcp-route
  plugins:
    - name: limit-conn
      config:
        conn: 100
        burst: 50
        default_conn_delay: 0.1
        key_type: "var"
        key: "remote_addr"
    - name: ip-restriction
      config:
        whitelist:
          - "192.168.1.0/24"

Implementation details

  • Plugin attachment level: plugins are attached to service.Plugins (one service per L4 rule), giving per-rule granularity and avoiding duplication in TLS multi-SNI scenarios
  • Conflict resolution: when multiple policies target the same route, the oldest (creationTimestamp + namespace/name tie-break) wins; losers receive Accepted=False, Reason=Conflicted
  • Lifecycle: L4 route controllers watch L4RoutePolicy changes and trigger reconciliation; ancestor status entries are cleaned up on route deletion
  • LocalPolicyTargetReferenceWithSectionName is used in the spec to preserve the option for future per-rule targeting via sectionName

Files changed

File Change
api/v1alpha1/l4routepolicy_types.go New CRD type
api/v1alpha1/zz_generated.deepcopy.go DeepCopy methods
internal/provider/provider.go Add L4RoutePolicies to TranslateContext
internal/controller/indexer/indexer.go Register L4RoutePolicy indexer
internal/controller/policies.go ProcessL4RoutePolicy, updateL4RoutePolicyStatusOnDeleting
internal/adc/translator/policies.go AttachL4RoutePolicyPlugins helper
internal/adc/translator/{tcp,udp,tls}route.go Call plugin attachment
internal/controller/{tcp,udp,tls}route_controller.go Watch + reconcile + deletion cleanup
internal/manager/controllers.go RBAC markers
internal/adc/translator/l4routepolicy_test.go Unit tests

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced L4RoutePolicy resource for attaching plugins to TCP, UDP, and TLS routes with conflict resolution and status tracking.
    • Enabled dynamic plugin configuration on layer 4 routes with policy acceptance and conflict conditions.
  • Documentation

    • Added API reference documentation for L4RoutePolicy specifications.
  • Tests

    • Added comprehensive unit and end-to-end tests validating L4RoutePolicy plugin attachment across route types.

Review Change Stack

Copilot AI review requested due to automatic review settings May 14, 2026 04:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces L4RoutePolicy, a new Gateway API policy resource enabling plugins to be attached to TCP, UDP, and TLS routes. The implementation includes API types and CRD manifest, translator logic to merge policies into services, controller watches for automatic reconciliation, and deterministic policy conflict resolution using creation time ordering.

Changes

L4RoutePolicy Stream Plugin Support

Layer / File(s) Summary
L4RoutePolicy API types and CRD manifest
api/v1alpha1/l4routepolicy_types.go, api/v1alpha1/zz_generated.deepcopy.go, config/crd/bases/apisix.apache.org_l4routepolicies.yaml
L4RoutePolicy resource with spec.targetRefs (validated to TCPRoute/UDPRoute/TLSRoute), spec.plugins, and status.ancestors[] for per-route acceptance/conflict conditions; includes generated deepcopy methods and full OpenAPI schema.
RBAC, manager, provider, and indexing setup
config/rbac/role.yaml, internal/manager/controllers.go, internal/provider/provider.go, internal/controller/indexer/indexer.go
Grants controller get;list;watch on l4routepolicies and get;update on status; adds TranslateContext cache for winning policies; indexes L4RoutePolicy by targetRefs to enable reverse lookups.
Policy plugin attachment logic and translator integration
internal/adc/translator/policies.go, internal/adc/translator/tcproute.go, internal/adc/translator/tlsroute.go, internal/adc/translator/udproute.go, internal/adc/translator/l4routepolicy_test.go, internal/adc/translator/l4route_test.go
AttachL4RoutePolicyPlugins matches policies by route namespace/name/kind and merges plugin configs into service plugins; each translator calls attachment before appending service; unit tests validate matching logic for all route types.
Controller watches, reconciliation, and policy conflict resolution
internal/controller/policies.go, internal/controller/tcproute_controller.go, internal/controller/tlsroute_controller.go, internal/controller/udproute_controller.go
All three route controllers watch L4RoutePolicy changes; ProcessL4RoutePolicy lists conflicting policies and selects winner by earliest CreationTimestamp with namespace/name tie-break; deletion clears ancestor status; indexed targetRefs enqueue affected routes on policy changes.
E2E test framework, scaffold helper, and API documentation
test/e2e/framework/assertion.go, test/e2e/scaffold/k8s.go, test/e2e/gatewayapi/tcproute.go, docs/en/latest/reference/api-reference.md
Assertion helpers poll L4RoutePolicy status ancestors for condition matches; scaffold ApplyL4RoutePolicy applies YAML and waits for acceptance; E2E test applies ip-restriction policy to block TCP traffic, verifies blockage, and confirms restoration after deletion; docs describe L4RoutePolicy, L4RoutePolicySpec, and plugin fields.

Sequence Diagram

sequenceDiagram
  participant Client
  participant TCPRouteReconciler
  participant FieldIndex
  participant ProcessL4RoutePolicy as ProcessL4RoutePolicy
  participant Translator
  participant AttachL4RoutePolicyPlugins as AttachL4RoutePolicyPlugins
  Client->>TCPRouteReconciler: Create/Update L4RoutePolicy targeting TCPRoute
  TCPRouteReconciler->>FieldIndex: L4RoutePolicy indexed by targetRefs
  FieldIndex->>TCPRouteReconciler: enqueue affected TCPRoute reconciliation requests
  TCPRouteReconciler->>TCPRouteReconciler: Reconcile TCPRoute
  TCPRouteReconciler->>ProcessL4RoutePolicy: list indexed L4RoutePolicy
  ProcessL4RoutePolicy->>ProcessL4RoutePolicy: resolve conflicts by CreationTimestamp
  ProcessL4RoutePolicy->>ProcessL4RoutePolicy: store winning policy in TranslateContext
  ProcessL4RoutePolicy->>ProcessL4RoutePolicy: set ancestor accepted/conflicted status
  TCPRouteReconciler->>Translator: translate with updated context
  Translator->>AttachL4RoutePolicyPlugins: attach winning policy plugins to service
  AttachL4RoutePolicyPlugins->>AttachL4RoutePolicyPlugins: unmarshal plugin configs and merge
  Translator->>TCPRouteReconciler: service with attached plugins
  TCPRouteReconciler->>Client: apply configuration to APISIX
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error Missing group validation in mappers (cross-resource access risk), context.Background() loses cancellation, ancestor status clearing corrupts shared policy state. Add group check in mappers; pass reconcile context to ProcessL4RoutePolicy; filter status clearing to deleted route only.
E2e Test Quality Review ⚠️ Warning E2E tests incomplete: TCPRoute only, missing UDP/TLS. Context usage bug in ProcessL4RoutePolicy (context.Background()). Ancestor status cleanup corrupts shared policy usage. Add UDP/TLS E2E tests. Replace context.Background() with request context. Fix status cleanup to scope only to deleted route's ancestors.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a new L4RoutePolicy CRD for attaching stream plugins to Gateway API L4 routes.
Linked Issues check ✅ Passed The PR implements all coding objectives from issue #403: L4RoutePolicy CRD for attaching stream plugins to TCPRoute/UDPRoute/TLSRoute, conflict resolution, lifecycle management, and translator integration.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing L4RoutePolicy functionality. The license header rewrite in api/v2/zz_generated.deepcopy.go appears to be an autogenerated artifact and is not a meaningful code change.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/l4routepolicy

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new L4RoutePolicy CRD and controller/translator plumbing to attach APISIX stream (L4) plugins to Gateway API L4 routes (TCPRoute, UDPRoute, TLSRoute) using the Policy Attachment pattern.

Changes:

  • Introduces api/v1alpha1.L4RoutePolicy type (plus deepcopy) and extends TranslateContext to carry selected policies.
  • Indexes L4RoutePolicy.spec.targetRefs, watches L4RoutePolicy from L4 route controllers, resolves conflicts deterministically, and updates policy status.
  • Attaches matched policy plugins onto translated L4 “service” objects; includes unit tests for the attachment helper.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
api/v1alpha1/l4routepolicy_types.go New CRD Go types + kubebuilder validations/markers for L4RoutePolicy.
api/v1alpha1/zz_generated.deepcopy.go Generated DeepCopy methods for the new CRD types.
internal/provider/provider.go Adds L4RoutePolicies storage to translation context.
internal/manager/controllers.go Adds RBAC markers for l4routepolicies and /status.
internal/controller/indexer/indexer.go Registers field indexer for L4RoutePolicy.spec.targetRefs.
internal/controller/policies.go Implements ProcessL4RoutePolicy and deletion-time status cleanup.
internal/controller/tcproute_controller.go Watches L4RoutePolicy, triggers reconcile, and calls ProcessL4RoutePolicy.
internal/controller/udproute_controller.go Same as above for UDPRoute.
internal/controller/tlsroute_controller.go Same as above for TLSRoute.
internal/adc/translator/policies.go Adds AttachL4RoutePolicyPlugins helper and JSON config unmarshalling.
internal/adc/translator/tcproute.go Calls plugin attachment during TCPRoute translation.
internal/adc/translator/udproute.go Calls plugin attachment during UDPRoute translation.
internal/adc/translator/tlsroute.go Calls plugin attachment during TLSRoute translation.
internal/adc/translator/l4routepolicy_test.go Unit tests for L4RoutePolicy plugin attachment behavior.
Files not reviewed (1)
  • api/v1alpha1/zz_generated.deepcopy.go: Language not supported

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

Comment on lines +261 to +298
winner := list.Items[0].DeepCopy()
tctx.L4RoutePolicies[types.NamespacedName{Namespace: winner.Namespace, Name: winner.Name}] = winner

for i := range list.Items {
policy := list.Items[i]
var condition metav1.Condition
if i == 0 {
condition = metav1.Condition{
Type: string(gatewayv1alpha2.PolicyConditionAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: policy.GetGeneration(),
LastTransitionTime: metav1.Now(),
Reason: string(gatewayv1alpha2.PolicyReasonAccepted),
Message: "Policy has been accepted",
}
} else {
condition = metav1.Condition{
Type: string(gatewayv1alpha2.PolicyConditionAccepted),
Status: metav1.ConditionFalse,
ObservedGeneration: policy.GetGeneration(),
LastTransitionTime: metav1.Now(),
Reason: string(gatewayv1alpha2.PolicyReasonConflicted),
Message: fmt.Sprintf("Conflicts with L4RoutePolicy %s/%s which was created earlier", winner.Namespace, winner.Name),
}
}

if updated := SetAncestors(&policy.Status, tctx.RouteParentRefs, condition); updated {
policyCopy := policy.DeepCopy()
tctx.StatusUpdaters = append(tctx.StatusUpdaters, status.Update{
NamespacedName: utils.NamespacedName(policyCopy),
Resource: policyCopy,
Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
cp := obj.(*v1alpha1.L4RoutePolicy).DeepCopy()
cp.Status = policyCopy.Status
return cp
}),
})
}
Comment on lines +302 to +329
// updateL4RoutePolicyStatusOnDeleting clears ancestor status entries for L4RoutePolicy
// resources that target the deleted route.
func updateL4RoutePolicyStatusOnDeleting(ctx context.Context, c client.Client, updater status.Updater, log logr.Logger, nn types.NamespacedName, routeKind string) {
var list v1alpha1.L4RoutePolicyList
key := indexer.GenIndexKeyWithGK(gatewayv1alpha2.GroupName, routeKind, nn.Namespace, nn.Name)
if err := c.List(ctx, &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
log.Error(err, "failed to list L4RoutePolicy on route deletion", "namespace", nn.Namespace, "name", nn.Name)
return
}
for _, policy := range list.Items {
updateL4RoutePolicyDeleteAncestors(updater, policy)
}
}

func updateL4RoutePolicyDeleteAncestors(updater status.Updater, policy v1alpha1.L4RoutePolicy) {
if len(policy.Status.Ancestors) == 0 {
return
}
policy.Status.Ancestors = nil
updater.Update(status.Update{
NamespacedName: utils.NamespacedName(&policy),
Resource: policy.DeepCopy(),
Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
cp := obj.(*v1alpha1.L4RoutePolicy).DeepCopy()
cp.Status = policy.Status
return cp
}),
})
//
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:rule="self.all(r, r.kind == 'TCPRoute' || r.kind == 'UDPRoute' || r.kind == 'TLSRoute')",message="targetRefs kind must be TCPRoute, UDPRoute, or TLSRoute"
Comment on lines +520 to +527
for _, ref := range policy.Spec.TargetRefs {
if string(ref.Kind) != KindTCPRoute {
continue
}
nn := k8stypes.NamespacedName{
Namespace: policy.Namespace,
Name: string(ref.Name),
}
Comment on lines +520 to +527
for _, ref := range policy.Spec.TargetRefs {
if string(ref.Kind) != KindUDPRoute {
continue
}
nn := k8stypes.NamespacedName{
Namespace: policy.Namespace,
Name: string(ref.Name),
}
Comment on lines +520 to +527
for _, ref := range policy.Spec.TargetRefs {
if string(ref.Kind) != types.KindTLSRoute {
continue
}
nn := k8stypes.NamespacedName{
Namespace: policy.Namespace,
Name: string(ref.Name),
}
Comment on lines +42 to +68
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// L4RoutePolicy defines plugin configuration for Gateway API L4 routes (TCPRoute, UDPRoute, TLSRoute).
// It follows the Gateway API Policy Attachment pattern and attaches APISIX stream plugins
// to the targeted L4 route resources.
type L4RoutePolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec defines the desired state of L4RoutePolicy.
Spec L4RoutePolicySpec `json:"spec,omitempty"`
Status PolicyStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// L4RoutePolicyList contains a list of L4RoutePolicy.
type L4RoutePolicyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []L4RoutePolicy `json:"items"`
}

func init() {
SchemeBuilder.Register(&L4RoutePolicy{}, &L4RoutePolicyList{})
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/controller/policies.go (1)

233-300: ⚡ Quick win

Use tctx instead of context.Background() for consistency.

Line 241 uses context.Background() while other similar code in this file (e.g., line 111) passes tctx directly as the context. Since TranslateContext embeds context.Context, use tctx for consistency with the rest of the codebase.

♻️ Proposed fix
-	if err := c.List(context.Background(), &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
+	if err := c.List(tctx, &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
🤖 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 `@internal/controller/policies.go` around lines 233 - 300, In
ProcessL4RoutePolicy replace the call that uses context.Background() when
listing policies with the TranslateContext value (tctx) so the c.List call uses
tctx as the context; update the c.List invocation (the call that passes
client.MatchingFields{indexer.PolicyTargetRefs: key}) to pass tctx
(TranslateContext) instead of context.Background() to be consistent with other
handlers that use tctx as the request context.
🤖 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 `@internal/controller/indexer/indexer.go`:
- Around line 522-532: The file fails gofmt/formatting check; run gofmt (or
goimports if preferred) on the file and reformat the function
setupL4RoutePolicyIndexer and the other affected blocks (around the 900-914
region) so that spacing, imports, and indentation conform to gofmt rules; ensure
the signatures and calls for mgr.GetFieldIndexer().IndexField,
v1alpha1.L4RoutePolicy{}, PolicyTargetRefs, and L4RoutePolicyIndexFunc remain
unchanged except for whitespace/formatting, then stage and commit the formatted
file to unblock CI.

---

Nitpick comments:
In `@internal/controller/policies.go`:
- Around line 233-300: In ProcessL4RoutePolicy replace the call that uses
context.Background() when listing policies with the TranslateContext value
(tctx) so the c.List call uses tctx as the context; update the c.List invocation
(the call that passes client.MatchingFields{indexer.PolicyTargetRefs: key}) to
pass tctx (TranslateContext) instead of context.Background() to be consistent
with other handlers that use tctx as the request context.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48e8aad9-1b5e-4ec8-9a48-c6e3e7b6c9d2

📥 Commits

Reviewing files that changed from the base of the PR and between 382cc0f and 242ce9c.

📒 Files selected for processing (14)
  • api/v1alpha1/l4routepolicy_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • internal/adc/translator/l4routepolicy_test.go
  • internal/adc/translator/policies.go
  • internal/adc/translator/tcproute.go
  • internal/adc/translator/tlsroute.go
  • internal/adc/translator/udproute.go
  • internal/controller/indexer/indexer.go
  • internal/controller/policies.go
  • internal/controller/tcproute_controller.go
  • internal/controller/tlsroute_controller.go
  • internal/controller/udproute_controller.go
  • internal/manager/controllers.go
  • internal/provider/provider.go

Comment thread internal/controller/indexer/indexer.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/adc/translator/l4route_test.go (1)

259-260: 💤 Low value

Assertion does not verify the stated intent.

The comment says "plugins should be on service level, not duplicated" but the assertion only checks that there's exactly one service. To verify plugins aren't duplicated across stream routes, consider asserting that individual stream routes don't have plugins attached (if that's how the structure works), or remove the misleading comment.

Suggested clarification
-				// Plugins are on the service, not duplicated per stream route
-				assert.Len(t, result.Services, 1, "plugins should be on service level, not duplicated")
+				// Verify single service with plugins (plugins attached at service level)
+				assert.Len(t, result.Services, 1, "expected exactly one service")
🤖 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 `@internal/adc/translator/l4route_test.go` around lines 259 - 260, The test
comment says "plugins should be on service level, not duplicated" but the
assertion assert.Len(t, result.Services, 1) only verifies the number of
services; update the test in l4route_test.go to actually assert plugin
placement: either remove or revise the misleading comment, and add checks such
as asserting that each stream route (e.g., elements in
result.Services[0].StreamRoutes or the route objects returned by the translator)
has no Plugins (assert.Empty(t, route.Plugins) for each route) or assert that
only result.Services[0].Plugins contains the plugins and that none of the
individual routes contain duplicates; keep the existing assert.Len if still
relevant but add the plugin-level assertions to verify the intended behavior.
🤖 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.

Nitpick comments:
In `@internal/adc/translator/l4route_test.go`:
- Around line 259-260: The test comment says "plugins should be on service
level, not duplicated" but the assertion assert.Len(t, result.Services, 1) only
verifies the number of services; update the test in l4route_test.go to actually
assert plugin placement: either remove or revise the misleading comment, and add
checks such as asserting that each stream route (e.g., elements in
result.Services[0].StreamRoutes or the route objects returned by the translator)
has no Plugins (assert.Empty(t, route.Plugins) for each route) or assert that
only result.Services[0].Plugins contains the plugins and that none of the
individual routes contain duplicates; keep the existing assert.Len if still
relevant but add the plugin-level assertions to verify the intended behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1a4a092-ab61-46e7-b78a-ed7d9431361d

📥 Commits

Reviewing files that changed from the base of the PR and between 242ce9c and 261bfc9.

📒 Files selected for processing (9)
  • api/v2/zz_generated.deepcopy.go
  • config/crd/bases/apisix.apache.org_l4routepolicies.yaml
  • config/rbac/role.yaml
  • internal/adc/translator/apisixconsumer_test.go
  • internal/adc/translator/l4route_test.go
  • internal/controller/indexer/indexer.go
  • internal/controller/tcproute_controller.go
  • internal/controller/tlsroute_controller.go
  • internal/controller/udproute_controller.go
✅ Files skipped from review due to trivial changes (2)
  • api/v2/zz_generated.deepcopy.go
  • config/rbac/role.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/controller/tlsroute_controller.go
  • internal/controller/indexer/indexer.go
  • internal/controller/tcproute_controller.go
  • internal/controller/udproute_controller.go

Copilot AI review requested due to automatic review settings May 15, 2026 00:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.

Files not reviewed (2)
  • api/v1alpha1/zz_generated.deepcopy.go: Language not supported
  • api/v2/zz_generated.deepcopy.go: Language not supported

Comment on lines +316 to +330
func updateL4RoutePolicyDeleteAncestors(updater status.Updater, policy v1alpha1.L4RoutePolicy) {
if len(policy.Status.Ancestors) == 0 {
return
}
policy.Status.Ancestors = nil
updater.Update(status.Update{
NamespacedName: utils.NamespacedName(&policy),
Resource: policy.DeepCopy(),
Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
cp := obj.(*v1alpha1.L4RoutePolicy).DeepCopy()
cp.Status = policy.Status
return cp
}),
})
}
) {
var list v1alpha1.L4RoutePolicyList
key := indexer.GenIndexKeyWithGK(gatewayv1alpha2.GroupName, routeKind, routeNamespace, routeName)
if err := c.List(context.Background(), &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
//
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:validation:XValidation:rule="self.all(r, r.kind == 'TCPRoute' || r.kind == 'UDPRoute' || r.kind == 'TLSRoute')",message="targetRefs kind must be TCPRoute, UDPRoute, or TLSRoute"
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

conformance test report - apisix mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-05-25T00:16:41Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
  contact: null
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    result: success
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 0
  name: GATEWAY-GRPC
  summary: Core tests succeeded.
- core:
    failedTests:
    - HTTPRouteInvalidBackendRefUnknownKind
    result: failure
    skippedTests:
    - HTTPRouteHTTPSListener
    statistics:
      Failed: 1
      Passed: 31
      Skipped: 1
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 11
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - GatewayHTTPListenerIsolation
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
  name: GATEWAY-HTTP
  summary: Core tests failed with 1 test failures. Extended tests partially succeeded
    with 1 test skips.
- core:
    failedTests:
    - TLSRouteInvalidReferenceGrant
    result: failure
    skippedTests:
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 1
      Passed: 9
      Skipped: 1
  name: GATEWAY-TLS
  summary: Core tests failed with 1 test failures.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

conformance test report - apisix-standalone mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-05-25T00:17:26Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
  contact: null
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    result: success
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 0
  name: GATEWAY-GRPC
  summary: Core tests succeeded.
- core:
    failedTests:
    - HTTPRouteInvalidBackendRefUnknownKind
    result: failure
    skippedTests:
    - HTTPRouteHTTPSListener
    statistics:
      Failed: 1
      Passed: 31
      Skipped: 1
  extended:
    failedTests:
    - HTTPRouteBackendProtocolWebSocket
    result: failure
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 1
      Passed: 10
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - GatewayHTTPListenerIsolation
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
  name: GATEWAY-HTTP
  summary: Core tests failed with 1 test failures. Extended tests failed with 1 test
    failures.
- core:
    failedTests:
    - TLSRouteInvalidReferenceGrant
    result: failure
    skippedTests:
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 1
      Passed: 9
      Skipped: 1
  name: GATEWAY-TLS
  summary: Core tests failed with 1 test failures.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

conformance test report

apiVersion: gateway.networking.k8s.io/v1
date: "2026-05-25T00:28:11Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.3.0
implementation:
  contact: null
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    failedTests:
    - TLSRouteInvalidReferenceGrant
    - TLSRouteSimpleSameNamespace
    result: failure
    statistics:
      Failed: 2
      Passed: 9
      Skipped: 0
  name: GATEWAY-TLS
  summary: Core tests failed with 2 test failures.
- core:
    failedTests:
    - GRPCExactMethodMatching
    - GRPCRouteHeaderMatching
    - GRPCRouteListenerHostnameMatching
    result: failure
    statistics:
      Failed: 3
      Passed: 9
      Skipped: 0
  name: GATEWAY-GRPC
  summary: Core tests failed with 3 test failures.
- core:
    result: partial
    skippedTests:
    - HTTPRouteHTTPSListener
    statistics:
      Failed: 0
      Passed: 32
      Skipped: 1
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 11
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - GatewayHTTPListenerIsolation
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
  name: GATEWAY-HTTP
  summary: Core tests partially succeeded with 1 test skips. Extended tests partially
    succeeded with 1 test skips.

AlinsRan and others added 4 commits May 25, 2026 07:58
…PI L4 routes

Add a new L4RoutePolicy custom resource that enables attaching APISIX stream
plugins to Gateway API L4 routes (TCPRoute, UDPRoute, TLSRoute). This follows
the Gateway API Policy Attachment pattern (GEP-713) consistent with the existing
BackendTrafficPolicy and HTTPRoutePolicy CRDs.

Changes:
- Add L4RoutePolicy CRD type (api/v1alpha1/l4routepolicy_types.go)
  - targetRefs support TCPRoute, UDPRoute, TLSRoute (validated via CEL rule)
  - plugins list reuses the existing Plugin type (name + config)
  - LocalPolicyTargetReferenceWithSectionName for future per-rule targeting
- Add DeepCopy methods in zz_generated.deepcopy.go
- Add L4RoutePolicies to TranslateContext in provider.go
- Register L4RoutePolicy indexer (by group+kind+namespace+name) in indexer.go
- Add ProcessL4RoutePolicy in policies.go with deterministic conflict resolution
  (oldest creationTimestamp wins; tie-break by namespace/name; losers get
  Accepted=False, Reason=Conflicted)
- Add updateL4RoutePolicyStatusOnDeleting for route deletion cleanup
- Add AttachL4RoutePolicyPlugins translator helper; plugins are attached at the
  service level (one service per L4 rule) to avoid duplication in TLS multi-SNI
- Wire up TCPRoute, UDPRoute, TLSRoute controllers:
  - Watch L4RoutePolicy and enqueue affected routes
  - Call ProcessL4RoutePolicy during reconcile
  - Clear policy ancestor status on route deletion
- Add RBAC markers for l4routepolicies resources
- Add unit tests for AttachL4RoutePolicyPlugins

Fixes: #403

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Generate config/crd/bases/apisix.apache.org_l4routepolicies.yaml via make manifests
- Regenerate config/rbac/role.yaml with l4routepolicies RBAC entries
- Regenerate api/v2/zz_generated.deepcopy.go via make generate
- Fix pre-existing go vet error in apisixconsumer_test.go: AuthParameter is a pointer field
- Fix import ordering in indexer.go (goimports-reviser)
- Fix prealloc lint warnings in tcproute/udproute/tlsroute controllers
- Add translator-level tests: TestTranslateTCPRouteWithL4RoutePolicy,
  TestTranslateUDPRouteWithL4RoutePolicy, TestTranslateTLSRouteWithL4RoutePolicy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add L4RoutePolicyMustHaveCondition and PollUntilL4RoutePolicyHaveStatus
  framework helpers (mirroring HTTPRoutePolicy pattern)
- Add ApplyL4RoutePolicy scaffold helper
- Add e2e test 'L4RoutePolicy blocks traffic via ip-restriction plugin':
  1. Create TCPRoute, verify traffic works
  2. Apply L4RoutePolicy with ip-restriction blacklist 0.0.0.0/0
  3. Verify traffic is blocked
  4. Delete L4RoutePolicy, verify traffic recovers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run 'make generate-crd-docs' to add L4RoutePolicy and L4RoutePolicySpec
entries to the auto-generated CRD reference documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 25, 2026 00:01
@AlinsRan AlinsRan force-pushed the feat/l4routepolicy branch from 69bacc9 to 6105dde Compare May 25, 2026 00:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 4 comments.

Files not reviewed (2)
  • api/v1alpha1/zz_generated.deepcopy.go: Language not supported
  • api/v2/zz_generated.deepcopy.go: Language not supported

Comment on lines +305 to +330
var list v1alpha1.L4RoutePolicyList
key := indexer.GenIndexKeyWithGK(gatewayv1alpha2.GroupName, routeKind, nn.Namespace, nn.Name)
if err := c.List(ctx, &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
log.Error(err, "failed to list L4RoutePolicy on route deletion", "namespace", nn.Namespace, "name", nn.Name)
return
}
for _, policy := range list.Items {
updateL4RoutePolicyDeleteAncestors(updater, policy)
}
}

func updateL4RoutePolicyDeleteAncestors(updater status.Updater, policy v1alpha1.L4RoutePolicy) {
if len(policy.Status.Ancestors) == 0 {
return
}
policy.Status.Ancestors = nil
updater.Update(status.Update{
NamespacedName: utils.NamespacedName(&policy),
Resource: policy.DeepCopy(),
Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
cp := obj.(*v1alpha1.L4RoutePolicy).DeepCopy()
cp.Status = policy.Status
return cp
}),
})
}
Comment on lines +245 to +248
if len(list.Items) == 0 {
return
}

Comment on lines +191 to +203
for _, ref := range policy.Spec.TargetRefs {
if string(ref.Group) != gatewayv1alpha2.GroupName {
continue
}
if string(ref.Kind) != routeKind {
continue
}
if string(ref.Name) != routeName {
continue
}
t.mergeL4PolicyPlugins(policy, plugins)
return
}
Comment on lines +208 to +217
for _, plugin := range policy.Spec.Plugins {
cfg := make(map[string]any)
if len(plugin.Config.Raw) > 0 {
if err := json.Unmarshal(plugin.Config.Raw, &cfg); err != nil {
t.Log.Error(err, "failed to unmarshal L4RoutePolicy plugin config", "plugin", plugin.Name, "policy", policy.Name)
continue
}
}
plugins[plugin.Name] = cfg
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@internal/controller/policies.go`:
- Around line 241-243: The call to c.List is using context.Background() which
loses reconcile cancellation/deadline; change it to use the request-scoped
context (the tctx used elsewhere in this controller) so the list call
participates in reconciliation timeouts. Update the c.List invocation that
queries indexer.PolicyTargetRefs with key (and returns into list) to pass the
reconcile context (e.g., tctx or tctx.Context()) instead of
context.Background(), keeping the same MatchingFields and log behavior
referencing routeNamespace, routeName, and routeKind.

In `@internal/controller/tcproute_controller.go`:
- Around line 520-523: The loop over policy.Spec.TargetRefs only filters by
KindTCPRoute and can match resources from other API groups; update the check in
the tcproute reconciliation to require both string(ref.Kind) == KindTCPRoute and
ref.Group == gatewayv1alpha2.GroupName (or equivalently verify ref.Group matches
the expected gateway group) before treating the ref as a TCPRoute target. Locate
the loop handling policy.Spec.TargetRefs in
internal/controller/tcproute_controller.go and add the group equality check
alongside the existing KindTCPRoute check (ensuring you import/qualify
gatewayv1alpha2.GroupName if needed).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6fa55db2-4273-4db6-8b8a-42599e5fca4f

📥 Commits

Reviewing files that changed from the base of the PR and between 69bacc9 and 6105dde.

📒 Files selected for processing (22)
  • api/v1alpha1/l4routepolicy_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • api/v2/zz_generated.deepcopy.go
  • config/crd/bases/apisix.apache.org_l4routepolicies.yaml
  • config/rbac/role.yaml
  • docs/en/latest/reference/api-reference.md
  • internal/adc/translator/l4route_test.go
  • internal/adc/translator/l4routepolicy_test.go
  • internal/adc/translator/policies.go
  • internal/adc/translator/tcproute.go
  • internal/adc/translator/tlsroute.go
  • internal/adc/translator/udproute.go
  • internal/controller/indexer/indexer.go
  • internal/controller/policies.go
  • internal/controller/tcproute_controller.go
  • internal/controller/tlsroute_controller.go
  • internal/controller/udproute_controller.go
  • internal/manager/controllers.go
  • internal/provider/provider.go
  • test/e2e/framework/assertion.go
  • test/e2e/gatewayapi/tcproute.go
  • test/e2e/scaffold/k8s.go
✅ Files skipped from review due to trivial changes (2)
  • docs/en/latest/reference/api-reference.md
  • api/v1alpha1/zz_generated.deepcopy.go

Comment on lines +241 to +243
if err := c.List(context.Background(), &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
log.Error(err, "failed to list L4RoutePolicy", "namespace", routeNamespace, "name", routeName, "kind", routeKind)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid context.Background() for reconcile-scoped API calls.

c.List here bypasses the reconcile context, so cancellation/deadline propagation is lost. Use the request-scoped context (e.g., from tctx) consistently like other controller list calls.

🤖 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 `@internal/controller/policies.go` around lines 241 - 243, The call to c.List
is using context.Background() which loses reconcile cancellation/deadline;
change it to use the request-scoped context (the tctx used elsewhere in this
controller) so the list call participates in reconciliation timeouts. Update the
c.List invocation that queries indexer.PolicyTargetRefs with key (and returns
into list) to pass the reconcile context (e.g., tctx or tctx.Context()) instead
of context.Background(), keeping the same MatchingFields and log behavior
referencing routeNamespace, routeName, and routeKind.

Comment on lines +316 to +321
func updateL4RoutePolicyDeleteAncestors(updater status.Updater, policy v1alpha1.L4RoutePolicy) {
if len(policy.Status.Ancestors) == 0 {
return
}
policy.Status.Ancestors = nil
updater.Update(status.Update{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not clear all ancestor statuses on single-route deletion.

policy.Status.Ancestors = nil removes status for all attachments, including still-valid targets when one L4RoutePolicy references multiple routes. This can corrupt status for shared policy usage; cleanup should be scoped to the deleted route’s affected ancestors or gated on “no remaining dependents”.

As per coding guidelines, "When resources are shared between multiple higher-level entities, verify that deletion or modification operations on one entity do not corrupt or unexpectedly affect shared underlying resources; check for other dependents before cascade delete operations".

Comment on lines +520 to +523
for _, ref := range policy.Spec.TargetRefs {
if string(ref.Kind) != KindTCPRoute {
continue
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Filter L4RoutePolicy targets by both group and kind.

The mapper only checks KindTCPRoute; it should also require ref.Group == gatewayv1alpha2.GroupName to avoid reconciling routes for unrelated API groups that reuse the same kind string.

As per coding guidelines, "Resource access must be scoped to the correct parent entity; validate that child resources (subscription, upstream, route, credential) belong to the requested parent before access or modification".

🤖 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 `@internal/controller/tcproute_controller.go` around lines 520 - 523, The loop
over policy.Spec.TargetRefs only filters by KindTCPRoute and can match resources
from other API groups; update the check in the tcproute reconciliation to
require both string(ref.Kind) == KindTCPRoute and ref.Group ==
gatewayv1alpha2.GroupName (or equivalently verify ref.Group matches the expected
gateway group) before treating the ref as a TCPRoute target. Locate the loop
handling policy.Spec.TargetRefs in internal/controller/tcproute_controller.go
and add the group equality check alongside the existing KindTCPRoute check
(ensuring you import/qualify gatewayv1alpha2.GroupName if needed).

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.

enhancement: support attaching L4 plugins with Gateway API routes

2 participants