Skip to content

feat: support defaultChildMergeType for BackendTrafficPolicy - #9374

Open
H34rtUnd3rB14d3 wants to merge 10 commits into
envoyproxy:mainfrom
H34rtUnd3rB14d3:feat/btp-mergetype-envoyproxy
Open

feat: support defaultChildMergeType for BackendTrafficPolicy#9374
H34rtUnd3rB14d3 wants to merge 10 commits into
envoyproxy:mainfrom
H34rtUnd3rB14d3:feat/btp-mergetype-envoyproxy

Conversation

@H34rtUnd3rB14d3

@H34rtUnd3rB14d3 H34rtUnd3rB14d3 commented Jun 29, 2026

Copy link
Copy Markdown

What this PR does / why we need it:

Right now, if a route-level BackendTrafficPolicy doesn't set mergeType, it replaces the
gateway- or listener-level policy instead of merging into it. So if you keep a baseline policy on
the Gateway and add route-level policies on top, you have to remember to put mergeType on every
single one of them, or the baseline silently gets dropped for that route.

This PR lets the parent policy declare that default once, following GEP-713:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: eg
  defaultChildMergeType: StrategicMerge   # or JSONMerge
  # ... baseline configuration for all routes under this Gateway

With that in place, a route-level policy that doesn't set its own mergeType picks up the default
and merges into its parent. A few rules that keep it predictable:

  • An explicit mergeType on the child always wins; mergeType: Replace is an explicit opt-out.

  • defaultChildMergeType may only be set on a policy targeting an entire Gateway. It is rejected by CEL on Listener-scoped targets (sectionName) and on ListenerSet targets, so the default is declared at a single top-level parent instead of at several intermediate ones.

  • The default and the merge target are resolved separately: a child merges into the closest parent policy in its attachment hierarchy (a Listener- or ListenerSet-level policy if one exists, otherwise the Gateway-level policy), while the default strategy always comes from the Gateway-level policy. This keeps a Listener-level policy from silently suppressing the Gateway's default.

  • defaultChildMergeType only accepts StrategicMerge or JSONMerge — the field's enum rejects Replace, since defaulting to a replace wouldn't do anything.

Nothing is written to the policy object — the default is resolved at translation time, so
kubectl get btp -o yaml still shows no mergeType. The effect is visible in the policy
status (Merged / Overridden) and in the generated Envoy config. When no parent sets
defaultChildMergeType, behavior is exactly the same as today, so this is fully backward compatible.

Implementation is in internal/gatewayapi/backendtrafficpolicy.go (resolveDefaultChildMergeType /
effectiveMergeType / anyParentPolicyMergeDefault); no new webhook, CRD, or controller configuration.

Tests: unit tests for the resolution helpers, golden translator fixtures covering the basic merge,
Replace opt-out, multi-parent, MergeGateways scoping, and the Gateway-default-with-Listener-parent
case, plus CEL validation for the field and its target restrictions. Verified end to end in a Kind
cluster (live CEL rejection, policy statuses, and the merged Envoy cluster config, including a control
test confirming the merge stops when the default is removed).

Which issue(s) this PR fixes:

Fixes #9334


PR Checklist

  • Authorship & ownership: Coding agents / AI assistants are welcome, but I have reviewed every change, understand how and why it works, can explain and maintain it, and take full responsibility for this PR. I have not submitted generated output I do not understand.
  • DCO: All commits are signed off (git commit -s). See DCO: Sign your work.
  • API agreed first: If this PR contains API changes (changes under /api), the API was discussed and agreed before the implementation. The API change can be in a separate PR, or in the same PR, but the API must be agreed before implementation. N/A if this PR does not contain API changes.
  • Required checks pass: make generate gen-check, make lint, and the unit-test/coverage build pass. (Flaky e2e failures are not considered breakages, but gen-check, lint, and coverage MUST pass.)
  • Tests added/updated: New/changed code is covered by appropriate tests. N/A if this PR does not contain code changes.
  • Docs: User-facing changes update the docs, either in this PR or a follow-up PR. N/A if this PR does not contain user-facing changes.
  • Release notes: For any non-trivial change, added a release-note fragment under release-notes/current/<section>/<pr-number>-<slug>.md (see release-notes/current/README.md for sections and naming). N/A if this PR does not contain non-trivial changes.
  • Generated files committed: Ran make gen-check and committed the result if API/helm charts/modules changed.
  • Scope & compatibility: The PR is reasonably scoped (no unrelated changes) and preserves backward compatibility, or any breaking change is called out above and documented in release-notes/current/breaking_changes/.
  • Codex review: Requested a Codex review and addressed all of its comments.
  • Copilot review: Requested a Copilot review and addressed all of its comments.

@H34rtUnd3rB14d3
H34rtUnd3rB14d3 requested a review from a team as a code owner June 29, 2026 12:30
@netlify

netlify Bot commented Jun 29, 2026

Copy link
Copy Markdown

Deploy Preview for cerulean-figolla-1f9435 ready!

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

@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: e22e13f6f6

ℹ️ 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".

}

if policy.Spec.MergeType == nil {
if policy.Spec.MergeType == nil && !t.anyGatewayMergeDefault(policy, parentRefCtxs) {

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 Keep defaulted merges scoped to the Gateway

When defaultMergeType sends an otherwise-unset policy into the per-listener merge path, MergeGateways mode makes t.IRKey(gwNN) the same for every Gateway and the later route application filters only by listener sectionName. For a route attached to two Gateways that both have a listener named http but different parent BackendTrafficPolicies, the first merged parent policy is applied to both listeners and the second is skipped because the route traffic is already set. Include the Gateway identity when applying defaulted merged route policies in merged-Gateway IR.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

Comment thread api/v1alpha1/envoyproxy_types.go Outdated
Comment on lines +230 to +232
// +kubebuilder:validation:Enum=StrategicMerge;JSONMerge
// +optional
DefaultMergeType *MergeType `json:"defaultMergeType,omitempty"`

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 Validate defaultMergeType outside the CRD path

The enum marker protects EnvoyProxy CRs, but this same EnvoyProxySpec is also used as the EnvoyGateway default spec, which is loaded through ValidateEnvoyGateway rather than CRD validation. A config such as envoyProxy.backendTrafficPolicy.defaultMergeType: Replace is therefore accepted and effectiveMergeType will use Replace, causing the child policy to replace the parent while statuses record it as merged. Add runtime validation for the EnvoyGateway/default-spec path as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

Comment thread api/v1alpha1/envoyproxy_types.go Outdated
@H34rtUnd3rB14d3
H34rtUnd3rB14d3 requested a review from zirain June 30, 2026 09:10
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.35%. Comparing base (61f6772) to head (27fe91e).
⚠️ Report is 13 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9374      +/-   ##
==========================================
- Coverage   76.36%   76.35%   -0.01%     
==========================================
  Files         261      261              
  Lines       44537    44592      +55     
==========================================
+ Hits        34011    34050      +39     
- Misses       8279     8294      +15     
- Partials     2247     2248       +1     

☔ 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.

Comment thread api/v1alpha1/envoyproxy_types.go Outdated
// BackendTrafficPolicy defines defaults applied to BackendTrafficPolicy resources
// attached to Gateways that use this EnvoyProxy.
// +optional
BackendTrafficPolicy *PolicyDefaults `json:"backendTrafficPolicy,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we add some things like following for better extension?

policyMergeType:
  backendTrafficPolicy:
  securitryPolicy: #we could implament this in the future.

cc @envoyproxy/gateway-maintainers WDYT?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

IMHO, if we decide to do that, I'd name the parent policyDefaults rather than policyMergeType, since each entry also carries excludeLabel and may grow beyond merge settings later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

policyDefaults sounds good to me, cc @envoyproxy/gateway-maintainers

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I propose this:

policyDefaults:
  backendTrafficPolicy:
    mergeType: StrategicMerge
    mergeExcludeLabel: gateway.envoyproxy.io/skip-merge-default
    foo: bar # additional BTP-specific default
  securityPolicy:
    mergeType: StrategicMerge
    mergeExcludeLabel: gateway.envoyproxy.io/skip-merge-default

and these structs:

type PolicyDefaults struct {
    BackendTrafficPolicy *BackendTrafficPolicyDefaults `json:"backendTrafficPolicy,omitempty"`
    // SecurityPolicy *SecurityPolicyDefaults `json:"securityPolicy,omitempty"`  // future
}

// Per-policy and extensible: BTP-only defaults live here alongside the shared merge settings.
type BackendTrafficPolicyDefaults struct {
    MergeSettings `json:",inline"`
}

type MergeSettings struct {
    MergeType         *MergeType `json:"mergeType,omitempty"`
    MergeExcludeLabel *string    `json:"mergeExcludeLabel,omitempty"`
}

This way every policy can reuse MergeSettings while still having its own policy-specific defaults, and we don't need to nest the settings any deeper.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sound goo to me, can you join the meeting and discuss with other maintainers?
cc @envoyproxy/gateway-maintainers

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

+1 - done. defaultChildMergeType is now accepted only on whole-Gateway targets (no sectionName) and rejected on Listener-scoped targets via CEL, with a test.

The Gateway-level default still applies to all children, including routes under a Listener.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @arkodg could you take a look at the API shape here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

However, supporting a different defaultChildMergeType at different parent levels adds complexity and becomes confusing without a clear benefit. I'd restrict it to the top parent policy only and reject it on any intermediate parent.

sounds good to me! wait for comments from other maintainers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Friendly ping @arkodg. Just checking if there are any updates on the API review. Happy to address any feedback if needed.

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.

this warrants a longer & wider discussion, hey @zhaohuabing lets bring this into a future community meeting

A route-level BackendTrafficPolicy that leaves mergeType unset currently replaces its parent policy rather than merging into it. That means anyone who wants merging has to remember to set mergeType on every policy.

This adds a backendTrafficPolicy section to the EnvoyProxy API with a defaultMergeType field. When a route-level policy doesn't set its own mergeType, it picks up this default and merges into the parent instead of replacing it. An explicit mergeType still wins, an optional excludeLabel lets a policy opt out, and policies in the control-plane namespace are left untouched. defaultMergeType only accepts StrategicMerge or JSONMerge.

Fixes envoyproxy#9334

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
Rename the EnvoyProxy policy-defaults type from BackendTrafficPolicyDefaults
to a reusable PolicyDefaults, so other xPolicies can adopt the same shape.
The backendTrafficPolicy field is unchanged; only the type name changes.

Also fixes two review findings:

- Validate defaultMergeType on the EnvoyGateway default EnvoyProxySpec.
  That spec is loaded via config (ValidateEnvoyGateway), not CRD admission,
  so the enum does not apply and a value like Replace could slip through.
  effectiveMergeType also defensively ignores a non-merge value, so a stray
  default can never report a "merged" status while actually replacing.

- Scope a defaulted merged route policy to its own Gateway's listeners. In
  MergeGateways mode all Gateways share one IR keyed by section name, so a
  route attached to two Gateways with different parent policies received the
  first Gateway's merge on both. applyTrafficFeatureToRoute now filters
  listeners by the target Gateway. Adds a merged-gateways golden fixture.

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
Add a unit test for applyTrafficFeatureToRoute exercising the TCP and UDP listener skip branches when a route policy targets a different Gateway under the shared MergeGateways IR. The HTTP path is already covered by the merged-gateways golden fixture; this brings the new scoping code to full patch coverage.

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
…ergeSettings

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
@H34rtUnd3rB14d3
H34rtUnd3rB14d3 force-pushed the feat/btp-mergetype-envoyproxy branch from e942762 to 0e3e12d Compare July 9, 2026 10:39
Replace the EnvoyProxy policyDefaults API with a defaultChildMergeType
field on Gateway-targeting BackendTrafficPolicies, following GEP-713.
A child policy without its own mergeType merges into the closest parent
policy using its defaultChildMergeType; an explicit mergeType (including
Replace, allowed by a separate change) always wins.

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
@H34rtUnd3rB14d3
H34rtUnd3rB14d3 force-pushed the feat/btp-mergetype-envoyproxy branch from 0d3188c to 0d61eab Compare July 20, 2026 15:43
Reject defaultChildMergeType on Listener (sectionName) targets via CEL so the child merge default is declared only at the top-level Gateway. The default that children inherit always comes from the Gateway-level policy

Add CEL, unit, and golden test coverage.

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
@H34rtUnd3rB14d3
H34rtUnd3rB14d3 force-pushed the feat/btp-mergetype-envoyproxy branch from 0d61eab to 72c5161 Compare July 20, 2026 15:54
…voyproxy

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>

# Conflicts:
#	internal/gatewayapi/backendtrafficpolicy.go
…voyproxy

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>

# Conflicts:
#	api/v1alpha1/backendtrafficpolicy_types.go
#	charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
#	charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
#	internal/gatewayapi/backendtrafficpolicy.go
#	site/content/en/latest/api/extension_types.md
#	site/content/en/latest/concepts/gateway_api_extensions/backend-traffic-policy.md
#	test/helm/gateway-crds-helm/all.out.yaml
#	test/helm/gateway-crds-helm/e2e.out.yaml
#	test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@H34rtUnd3rB14d3 H34rtUnd3rB14d3 changed the title feat: support default mergeType for BackendTrafficPolicy via EnvoyProxy feat: support defaultChildMergeType for BackendTrafficPolicy Jul 30, 2026
@zhaohuabing
zhaohuabing requested review from a team, arkodg and guydc August 5, 2026 14:09
@zirain zirain added this to the v1.10.0-rc.1 Release milestone Aug 25, 2026
…voyproxy

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
The deduplication index treats a route policy with mergeType unset as replacing its parent, which no longer implies a replace once the parent sets defaultChildMergeType. Pin the resulting behavior in a golden fixture so a change on either side surfaces as a diff.

Signed-off-by: Maksim Kuchkovskiy <K.Maksim.E@yandex.ru>
@H34rtUnd3rB14d3

Copy link
Copy Markdown
Author

Noticed a small interaction with MergeBackends while rebasing on main: the dedup index treats mergeType unset as "replaces parent", which isn't quite true anymore when the parent sets defaultChildMergeType. The result is just a missed dedup (the route gets its own cluster even though its effective settings match), not a wrong config. Added a golden fixture to pin the current behavior. If you think there's a better way to handle this, I'm happy to adjust the implementation.

@zhaohuabing

Copy link
Copy Markdown
Member

Hi @H34rtUnd3rB14d3 feel free to add this to the community agenda and join the meeting to discuss.

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.

feat: optional cluster-wide default for BackendTrafficPolicy mergeType

5 participants