Skip to content

fix: honor BackendTrafficPolicy targetRefs.sectionName for Service ports#2796

Merged
AlinsRan merged 4 commits into
apache:masterfrom
AlinsRan:oss/backend-traffic-policy-section-name
Jun 23, 2026
Merged

fix: honor BackendTrafficPolicy targetRefs.sectionName for Service ports#2796
AlinsRan merged 4 commits into
apache:masterfrom
AlinsRan:oss/backend-traffic-policy-section-name

Conversation

@AlinsRan

@AlinsRan AlinsRan commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What this PR does

AttachBackendTrafficPolicyToUpstream selected the BackendTrafficPolicy to apply by matching only targetRef.Name, ignoring sectionName (and group/kind). As a result:

  • a policy intended for a single named Service port was applied to the whole Service (all ports), and
  • a policy targeting a different resource kind with a colliding name (e.g. a ServiceImport) could attach to a Service backend.

For a Service target, the Gateway API interprets sectionName as the port name. This PR makes both attachment and conflict detection honor it:

  • a targetRef without sectionName still applies to the whole Service (unchanged);
  • a targetRef with sectionName attaches only to the backend whose resolved Service port name matches it; a port-specific targetRef takes precedence over a whole-Service one;
  • targetRef group/kind are matched against the backend ref (Gateway API defaults: empty group, Service kind) before matching by name;
  • per Gateway API semantics, a sectionName that cannot be resolved does not attach;
  • conflict detection (ProcessBackendTrafficPolicy) now scopes the conflict key by sectionName, so a port-scoped policy and a whole-Service policy (or two different-section policies) on the same Service can both be Accepted instead of one being marked Conflicted.

To resolve a backend's port name, the attachment function now also receives the translate context's Services and maps the backend ref port number to the Service port name. All call sites (httproute / grpcroute / ingress / tcproute / tlsroute / udproute) are updated.

Example / Reproduction

A Service that exposes two named ports backed by the same workload:

apiVersion: v1
kind: Service
metadata:
  name: httpbin
spec:
  selector: { app: httpbin }
  ports:
  - { name: http,    port: 80,   targetPort: 80 }
  - { name: http-v2, port: 8080, targetPort: 80 }

One HTTPRoute whose two rules send /get to port 80 and /headers to port 8080 (same Service, different ports):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: httpbin }
spec:
  parentRefs: [{ name: apisix }]
  hostnames: ["httpbin.org"]
  rules:
  - matches: [{ path: { type: Exact, value: /get } }]
    backendRefs: [{ name: httpbin, port: 80 }]
  - matches: [{ path: { type: Exact, value: /headers } }]
    backendRefs: [{ name: httpbin, port: 8080 }]

Two policies — one scoped to the http-v2 port via sectionName, one targeting the whole Service:

apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata: { name: per-port }
spec:
  targetRefs:
  - { name: httpbin, kind: Service, group: "", sectionName: http-v2 }
  passHost: rewrite
  upstreamHost: section.http-v2.example.com
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata: { name: whole }
spec:
  targetRefs:
  - { name: httpbin, kind: Service, group: "" }
  passHost: rewrite
  upstreamHost: whole.service.example.com

Expected result — both policies are accepted, and each port gets its own host:

# port 8080 -> the per-port policy wins over the whole-Service one
$ curl -s http://<gateway>/headers -H 'Host: httpbin.org' | grep -i host
"Host": "section.http-v2.example.com"

# port 80 -> only the whole-Service policy applies
$ curl -s http://<gateway>/get -H 'Host: httpbin.org' | grep -i host
"Host": "whole.service.example.com"

# both policies are Accepted (neither is Conflicted)
$ kubectl get backendtrafficpolicy per-port whole \
    -o jsonpath='{.items[*].status.ancestors[*].conditions[?(@.type=="Accepted")].status}'
True True

Before this fix: the per-port policy leaked to port 80 as well (name-only match ignored sectionName), and the whole policy was rejected with status.conditions[Accepted]=False, reason=Conflicted ("Unable to target Service ... it conflicts with another BackendTrafficPolicy"), so port 80 received the wrong upstream host.

Tests

  • Unit tests for the matching logic: sectionName match / mismatch (no attach) / no-sectionName whole-Service / port-specific precedence / cross-kind rejection.
  • E2E (test/e2e/crds/v1alpha1/backendtrafficpolicy.go, Section Name): the example above — asserts the http-v2 port uses the scoped policy while port 80 falls back to the whole-Service policy.

go build, go vet, and the translator unit tests pass.

AlinsRan added 4 commits June 22, 2026 14:00
AttachBackendTrafficPolicyToUpstream selected the policy to apply by
matching only targetRef.Name, ignoring sectionName (and group/kind). As a
result a BackendTrafficPolicy intended for a single named Service port was
applied to the whole Service, and a policy targeting a different resource
kind with a colliding name could attach to a Service backend.

For a Service target the Gateway API interprets sectionName as the port
name. Attachment now:
- matches targetRef group/kind against the backend ref (Gateway API
  defaults: empty group, Service kind) before matching by name;
- when sectionName is set, attaches only to the backend whose resolved
  Service port name matches it, and a port-specific targetRef takes
  precedence over a whole-Service one;
- per Gateway API semantics, a sectionName that cannot be resolved does
  not attach.

Add unit tests for the matching logic and an e2e test asserting a
sectionName-scoped policy attaches to exactly the matched per-port
upstream in both directions.
Use a single HTTPRoute with two rules to the same Service on different
ports (/get->80, /headers->8080), plus a sectionName-scoped policy (http-v2)
and a whole-Service policy. Assert the http-v2 port uses the scoped policy
(precedence over the whole-Service one) while port 80 falls back to the
whole-Service policy.
The conflict key only included the target Service (namespace/name/kind), so
two BackendTrafficPolicies targeting the same Service were always treated as
conflicting even when scoped to different ports via sectionName. Include
sectionName in the key so a port-scoped policy and a whole-Service policy
(or two different-section policies) can both be accepted.
@AlinsRan AlinsRan merged commit d029b96 into apache:master Jun 23, 2026
26 checks passed
@AlinsRan AlinsRan deleted the oss/backend-traffic-policy-section-name branch June 23, 2026 01:01
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.

3 participants