diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go index 9468331583..90d6c23c3f 100644 --- a/api/v1alpha1/backendtrafficpolicy_types.go +++ b/api/v1alpha1/backendtrafficpolicy_types.go @@ -46,6 +46,7 @@ type BackendTrafficPolicy struct { // +kubebuilder:validation:XValidation:rule="!has(self.compression) || !has(self.compressor)", message="either compression or compressor can be set, not both" // +kubebuilder:validation:XValidation:rule="!has(self.requestBuffer) || !has(self.httpUpgrade) || self.httpUpgrade.size() == 0", message="requestBuffer cannot be used together with httpUpgrade" // +kubebuilder:validation:XValidation:rule="!has(self.admissionControl) || ((!has(self.targetRef) || self.targetRef.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute']) && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute'])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute'])))", message="admissionControl can only be used with HTTPRoute, GRPCRoute, or Gateway targets" +// +kubebuilder:validation:XValidation:rule="!has(self.defaultChildMergeType) || ((!has(self.targetRef) || (self.targetRef.kind == 'Gateway' && !has(self.targetRef.sectionName))) && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == 'Gateway' && !has(ref.sectionName))) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind == 'Gateway')))", message="defaultChildMergeType can only be used with Gateway targets without a sectionName" type BackendTrafficPolicySpec struct { PolicyTargetReferences `json:",inline"` ClusterSettings `json:",inline"` @@ -60,6 +61,18 @@ type BackendTrafficPolicySpec struct { // +optional MergeType *MergeType `json:"mergeType,omitempty"` + // DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + // an xRoute under this policy's target) that do not set their own mergeType, so a child + // policy merges into this policy instead of replacing it. A child policy can opt out by + // setting mergeType to Replace. + // This field can only be set on policies targeting an entire Gateway. It is rejected on + // policies targeting a specific Listener (via sectionName), so the default is defined at a + // single top-level parent rather than at multiple intermediate parents. + // + // +kubebuilder:validation:Enum=StrategicMerge;JSONMerge + // +optional + DefaultChildMergeType *MergeType `json:"defaultChildMergeType,omitempty"` + // RateLimit allows the user to limit the number of incoming requests // to a predefined value based on attributes within the traffic flow. // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7245a49145..22f93b8567 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -812,6 +812,11 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec) *out = new(MergeType) **out = **in } + if in.DefaultChildMergeType != nil { + in, out := &in.DefaultChildMergeType, &out.DefaultChildMergeType + *out = new(MergeType) + **out = **in + } if in.RateLimit != nil { in, out := &in.RateLimit, &out.RateLimit *out = new(RateLimitSpec) diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index f18e9f8334..8e273c797d 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -513,6 +513,19 @@ spec: Note that when the suffix is not provided, the value is interpreted as bytes. x-kubernetes-int-or-string: true type: object + defaultChildMergeType: + description: |- + DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + an xRoute under this policy's target) that do not set their own mergeType, so a child + policy merges into this policy instead of replacing it. A child policy can opt out by + setting mergeType to Replace. + This field can only be set on policies targeting an entire Gateway. It is rejected on + policies targeting a specific Listener (via sectionName), so the default is defined at a + single top-level parent rather than at multiple intermediate parents. + enum: + - StrategicMerge + - JSONMerge + type: string dns: description: DNS includes dns resolution settings. properties: @@ -3399,6 +3412,13 @@ spec: || self.targetRefs.all(ref, ref.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])))' + - message: defaultChildMergeType can only be used with Gateway targets + without a sectionName + rule: '!has(self.defaultChildMergeType) || ((!has(self.targetRef) || + (self.targetRef.kind == ''Gateway'' && !has(self.targetRef.sectionName))) + && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == + ''Gateway'' && !has(ref.sectionName))) && (!has(self.targetSelectors) + || self.targetSelectors.all(sel, sel.kind == ''Gateway'')))' - message: predictivePercent in preconnect policy only works with RoundRobin or Random load balancers rule: '!((has(self.connection) && has(self.connection.preconnect) && diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 395dfccd57..202f6371c9 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -512,6 +512,19 @@ spec: Note that when the suffix is not provided, the value is interpreted as bytes. x-kubernetes-int-or-string: true type: object + defaultChildMergeType: + description: |- + DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + an xRoute under this policy's target) that do not set their own mergeType, so a child + policy merges into this policy instead of replacing it. A child policy can opt out by + setting mergeType to Replace. + This field can only be set on policies targeting an entire Gateway. It is rejected on + policies targeting a specific Listener (via sectionName), so the default is defined at a + single top-level parent rather than at multiple intermediate parents. + enum: + - StrategicMerge + - JSONMerge + type: string dns: description: DNS includes dns resolution settings. properties: @@ -3398,6 +3411,13 @@ spec: || self.targetRefs.all(ref, ref.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])))' + - message: defaultChildMergeType can only be used with Gateway targets + without a sectionName + rule: '!has(self.defaultChildMergeType) || ((!has(self.targetRef) || + (self.targetRef.kind == ''Gateway'' && !has(self.targetRef.sectionName))) + && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == + ''Gateway'' && !has(ref.sectionName))) && (!has(self.targetSelectors) + || self.targetSelectors.all(sel, sel.kind == ''Gateway'')))' - message: predictivePercent in preconnect policy only works with RoundRobin or Random load balancers rule: '!((has(self.connection) && has(self.connection.preconnect) && diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 933ba491fb..f4ed6e5173 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -494,7 +494,10 @@ func (t *Translator) processBackendTrafficPolicyForRoute( return } - if policy.Spec.MergeType == nil { + // A policy that sets mergeType: Replace explicitly opts out of merging, even when a parent + // policy configures defaultChildMergeType. + explicitReplace := policy.Spec.MergeType != nil && *policy.Spec.MergeType == egv1a1.Replace + if explicitReplace || (policy.Spec.MergeType == nil && !anyParentPolicyMergeDefault(parentRefCtxs, gatewayPolicyMap)) { // Set conditions for translation error if it got any if err := t.translateBackendTrafficPolicyForRoute(policy, targetedRoute, currTarget, xdsIR, nil, nil); err != nil { status.SetTranslationErrorForPolicyAncestors(&policy.Status, @@ -522,8 +525,24 @@ func (t *Translator) processBackendTrafficPolicyForRoute( NamespacedName: gwNN, } gwPolicy := gatewayPolicyMap[gwMapKey] - if gwPolicy == nil && listenerPolicy == nil { - // not found, fall back to the current policy + + // The closest parent policy is the one the child would merge into: the + // listener-level policy if it exists, otherwise the gateway-level policy. + parentPolicy := gwPolicy + if listenerPolicy != nil { + parentPolicy = listenerPolicy + } + + // The default-merge intent comes from the nearest ancestor that declares + // defaultChildMergeType (listener before gateway), independent of which parent + // the child merges into. This keeps a listener-level policy from suppressing the + // gateway-level default. + defaultMergeType := resolveDefaultChildMergeType(listenerPolicy, gwPolicy) + + // Resolve the effective mergeType: the policy's own value, or the resolved default. + mergeType := effectiveMergeType(policy, defaultMergeType) + if mergeType == nil || parentPolicy == nil { + // No merge for this gateway: apply the policy standalone. if err := t.translateBackendTrafficPolicyForRoute(policy, targetedRoute, currTarget, xdsIR, &gwNN, &listener.Name); err != nil { status.SetConditionForPolicyAncestor(&policy.Status, &ancestorRef, @@ -537,13 +556,16 @@ func (t *Translator) processBackendTrafficPolicyForRoute( continue } - parentPolicy := gwPolicy - if listenerPolicy != nil { - parentPolicy = listenerPolicy + // Carry the effective mergeType so a defaulted policy (no explicit mergeType) + // merges like an explicit one. policy is a deep copy; nothing is persisted. + mergePolicy := policy + if policy.Spec.MergeType == nil { + mergePolicy = policy.DeepCopy() + mergePolicy.Spec.MergeType = mergeType } // merge with parent policy if err := t.translateBackendTrafficPolicyForRouteWithMerge( - policy, parentPolicy, currTarget, gwNN, &listener.Name, + mergePolicy, parentPolicy, currTarget, gwNN, &listener.Name, targetedRoute, xdsIR, ); err != nil { status.SetConditionForPolicyAncestor(&policy.Status, @@ -836,7 +858,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( // Skip if not the gateway wanted continue } - t.applyTrafficFeatureToRoute(route, tf, errs, policy, target, x, policyTargetListener) + t.applyTrafficFeatureToRoute(route, tf, errs, policy, target, x, policyTargetGatewayNN, policyTargetListener) } return errs @@ -897,16 +919,29 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge( // should not happen. return nil } - t.applyTrafficFeatureToRoute(route, tf, errs, mergedPolicy, target, x, policyTargetListener) + t.applyTrafficFeatureToRoute(route, tf, errs, mergedPolicy, target, x, &policyTargetGatewayNN, policyTargetListener) return errs } +// listenerBelongsToGateway reports whether an IR listener (named "//
...") +// belongs to the given Gateway. In MergeGateways mode multiple Gateways share a single IR, so a +// route policy merged with a Gateway-specific parent must be scoped to that Gateway's listeners; +// otherwise the section name alone (e.g. "http") would also match sibling Gateways' listeners and +// the first applied policy would win for all of them. A nil gwNN means "no Gateway scoping". +func listenerBelongsToGateway(listenerName string, gwNN *types.NamespacedName) bool { + if gwNN == nil { + return true + } + return strings.HasPrefix(listenerName, gwNN.Namespace+"/"+gwNN.Name+"/") +} + func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, tf *ir.TrafficFeatures, errs error, policy *egv1a1.BackendTrafficPolicy, target policyTargetReferenceWithSectionName, x *ir.Xds, + policyTargetGatewayNN *types.NamespacedName, policyTargetListener *gwapiv1.SectionName, ) { routeStatName := "" @@ -916,6 +951,11 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, prefix := irRoutePrefix(route) for _, tcp := range x.TCP { + // In MergeGateways mode multiple Gateways share one IR, so scope to the target Gateway's + // listeners; otherwise the section name alone would match sibling Gateways' listeners. + if !listenerBelongsToGateway(tcp.Name, policyTargetGatewayNN) { + continue + } // if listenerName is not nil, only apply to the specific listener if policyTargetListener != nil && string(*policyTargetListener) != tcp.Metadata.SectionName { // Skip if not the listener wanted @@ -944,6 +984,9 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, } for _, udp := range x.UDP { + if !listenerBelongsToGateway(udp.Name, policyTargetGatewayNN) { + continue + } // if listenerName is not nil, only apply to the specific listener if policyTargetListener != nil && string(*policyTargetListener) != udp.Metadata.SectionName { // Skip if not the listener wanted @@ -966,6 +1009,9 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, routesWithDirectResponse := sets.New[string]() for _, http := range x.HTTP { + if !listenerBelongsToGateway(http.Name, policyTargetGatewayNN) { + continue + } // if listenerName is not nil, only apply to the specific listener if policyTargetListener != nil && string(*policyTargetListener) != http.Metadata.SectionName { // Skip if not the listener wanted @@ -1034,6 +1080,62 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, } } +// effectiveMergeType returns the mergeType to use when merging a route-level policy into its +// parent policy: the policy's own value if set (Replace meaning "do not merge"), otherwise the +// resolved defaultChildMergeType from the nearest ancestor that declares one. +func effectiveMergeType(policy *egv1a1.BackendTrafficPolicy, defaultMergeType *egv1a1.MergeType) *egv1a1.MergeType { + if policy.Spec.MergeType != nil { + if *policy.Spec.MergeType == egv1a1.Replace { + return nil + } + return policy.Spec.MergeType + } + return defaultMergeType +} + +// resolveDefaultChildMergeType returns the defaultChildMergeType declared by the nearest ancestor +// policy, checking from the closest parent to the furthest (listener before gateway). CEL restricts +// defaultChildMergeType to policies targeting an entire Gateway, so the merge target for a child +// under a listener may be the listener-level policy while the default intent is declared on the +// gateway-level policy. Resolving the default independently of the merge target keeps a +// listener-level policy from suppressing the gateway-level default. +func resolveDefaultChildMergeType(ancestors ...*egv1a1.BackendTrafficPolicy) *egv1a1.MergeType { + for _, p := range ancestors { + if p == nil || p.Spec.DefaultChildMergeType == nil { + continue + } + mergeType := p.Spec.DefaultChildMergeType + // Defense in depth: the CRD enum restricts DefaultChildMergeType to StrategicMerge/JSONMerge. + // The nearest declarer wins, so if its value is not a real merge, return nil rather than + // falling through, ensuring a stray value can never produce a "merged" status while actually + // replacing the parent. + if *mergeType != egv1a1.StrategicMerge && *mergeType != egv1a1.JSONMerge { + return nil + } + return mergeType + } + return nil +} + +// anyParentPolicyMergeDefault reports whether any parent policy (gateway- or listener-level) of +// the route's parent gateways sets defaultChildMergeType. +func anyParentPolicyMergeDefault(parentRefCtxs []*RouteParentContext, gatewayPolicyMap map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy) bool { + for _, p := range parentRefCtxs { + for _, l := range p.listeners { + gwNN := utils.NamespacedName(l.gateway.Gateway) + for _, key := range []NamespacedNameWithSection{ + {NamespacedName: gwNN, SectionName: l.Name}, + {NamespacedName: gwNN}, + } { + if pp := gatewayPolicyMap[key]; pp != nil && pp.Spec.DefaultChildMergeType != nil { + return true + } + } + } + } + return false +} + // mergeBackendTrafficPolicy merges route policy into gateway policy, returning the merged // policy and the per-field owners used to resolve references against the contributing // policy's namespace. diff --git a/internal/gatewayapi/backendtrafficpolicy_mergedefault_test.go b/internal/gatewayapi/backendtrafficpolicy_mergedefault_test.go new file mode 100644 index 0000000000..a793fc14e4 --- /dev/null +++ b/internal/gatewayapi/backendtrafficpolicy_mergedefault_test.go @@ -0,0 +1,200 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" +) + +func TestEffectiveMergeType(t *testing.T) { + strategic := egv1a1.StrategicMerge + jsonMerge := egv1a1.JSONMerge + replace := egv1a1.Replace + + child := func(mt *egv1a1.MergeType) *egv1a1.BackendTrafficPolicy { + return &egv1a1.BackendTrafficPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: "app", Name: "child"}, + Spec: egv1a1.BackendTrafficPolicySpec{MergeType: mt}, + } + } + + tests := []struct { + name string + pol *egv1a1.BackendTrafficPolicy + defaultMT *egv1a1.MergeType + want *egv1a1.MergeType + }{ + {"explicit value wins over default", child(&jsonMerge), &strategic, &jsonMerge}, + {"explicit replace opts out of default", child(&replace), &strategic, nil}, + {"default applied when unset", child(nil), &strategic, &strategic}, + {"no default stays nil", child(nil), nil, nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := effectiveMergeType(tt.pol, tt.defaultMT) + if tt.want == nil { + assert.Nil(t, got) + return + } + assert.NotNil(t, got) + assert.Equal(t, *tt.want, *got) + }) + } +} + +// TestResolveDefaultChildMergeType covers resolveDefaultChildMergeType, which returns the +// defaultChildMergeType declared by the nearest ancestor policy (closest first). This decouples +// the default-merge intent (which, after CEL restricts defaultChildMergeType to Gateway targets, +// is only declared on the Gateway-level policy) from the merge target (the closest parent config). +func TestResolveDefaultChildMergeType(t *testing.T) { + strategic := egv1a1.StrategicMerge + jsonMerge := egv1a1.JSONMerge + replace := egv1a1.Replace + + withDefault := func(mt *egv1a1.MergeType) *egv1a1.BackendTrafficPolicy { + return &egv1a1.BackendTrafficPolicy{Spec: egv1a1.BackendTrafficPolicySpec{DefaultChildMergeType: mt}} + } + + tests := []struct { + name string + ancestors []*egv1a1.BackendTrafficPolicy + want *egv1a1.MergeType + }{ + {"no ancestors", nil, nil}, + {"nil ancestor", []*egv1a1.BackendTrafficPolicy{nil}, nil}, + {"single ancestor with default", []*egv1a1.BackendTrafficPolicy{withDefault(&strategic)}, &strategic}, + {"single ancestor without default", []*egv1a1.BackendTrafficPolicy{withDefault(nil)}, nil}, + // Reading B: the closest parent (listener) declares no default, so resolution falls through + // to the gateway-level policy that does. + {"closest without default falls through", []*egv1a1.BackendTrafficPolicy{withDefault(nil), withDefault(&strategic)}, &strategic}, + {"closest declarer wins", []*egv1a1.BackendTrafficPolicy{withDefault(&jsonMerge), withDefault(&strategic)}, &jsonMerge}, + {"invalid nearest default is ignored", []*egv1a1.BackendTrafficPolicy{withDefault(&replace), withDefault(&strategic)}, nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := resolveDefaultChildMergeType(tt.ancestors...) + if tt.want == nil { + assert.Nil(t, got) + return + } + assert.NotNil(t, got) + assert.Equal(t, *tt.want, *got) + }) + } +} + +// TestAnyParentPolicyMergeDefault covers anyParentPolicyMergeDefault, which reports whether any +// gateway- or listener-level parent policy of the route's parent gateways sets +// defaultChildMergeType. +func TestAnyParentPolicyMergeDefault(t *testing.T) { + strategic := egv1a1.StrategicMerge + gwNN := types.NamespacedName{Namespace: "envoy-gateway", Name: "gw"} + + parentCtx := func(listenerName string) *RouteParentContext { + return &RouteParentContext{ + listeners: []*ListenerContext{{ + Listener: &gwapiv1.Listener{Name: gwapiv1.SectionName(listenerName)}, + gateway: &GatewayContext{Gateway: &gwapiv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{Namespace: gwNN.Namespace, Name: gwNN.Name}, + }}, + }}, + } + } + parentPolicy := func(defaultMT *egv1a1.MergeType) *egv1a1.BackendTrafficPolicy { + return &egv1a1.BackendTrafficPolicy{ + Spec: egv1a1.BackendTrafficPolicySpec{DefaultChildMergeType: defaultMT}, + } + } + + tests := []struct { + name string + parents []*RouteParentContext + policy map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy + want bool + }{ + {"no parents", nil, nil, false}, + {"no parent policies", []*RouteParentContext{parentCtx("http")}, nil, false}, + { + "gateway-level policy without default", + []*RouteParentContext{parentCtx("http")}, + map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy{ + {NamespacedName: gwNN}: parentPolicy(nil), + }, + false, + }, + { + "gateway-level policy with default", + []*RouteParentContext{parentCtx("http")}, + map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy{ + {NamespacedName: gwNN}: parentPolicy(&strategic), + }, + true, + }, + { + "listener-level policy with default", + []*RouteParentContext{parentCtx("http")}, + map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy{ + {NamespacedName: gwNN, SectionName: "http"}: parentPolicy(&strategic), + }, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, anyParentPolicyMergeDefault(tt.parents, tt.policy)) + }) + } +} + +// TestApplyTrafficFeatureToRoute_MergeGatewayScoping covers the MergeGateways scoping in +// applyTrafficFeatureToRoute for the TCP and UDP listener loops: a listener belonging to a +// different Gateway must be skipped, so a merged policy does not bleed across Gateways that +// share one IR. The HTTP path is covered by the merged-gateways golden fixture. +func TestApplyTrafficFeatureToRoute_MergeGatewayScoping(t *testing.T) { + tr := &Translator{} + gwNN := &types.NamespacedName{Namespace: "envoy-gateway", Name: "gw"} + policy := &egv1a1.BackendTrafficPolicy{} + target := policyTargetReferenceWithSectionName{} + + t.Run("tcp listener of another gateway is skipped", func(t *testing.T) { + route := &TCPRouteContext{TCPRoute: &gwapiv1.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "tcproute-1"}, + }} + sibling := &ir.TCPRoute{Destination: &ir.RouteDestination{Name: irRoutePrefix(route) + "rule/0"}} + x := &ir.Xds{TCP: []*ir.TCPListener{{ + CoreListenerDetails: ir.CoreListenerDetails{Name: "envoy-gateway/other-gw/tcp"}, + Routes: []*ir.TCPRoute{sibling}, + }}} + tr.applyTrafficFeatureToRoute(route, &ir.TrafficFeatures{CircuitBreaker: &ir.CircuitBreaker{}}, + nil, policy, target, x, gwNN, nil) + assert.Nil(t, sibling.CircuitBreaker, "route on a sibling Gateway's listener must be skipped") + }) + + t.Run("udp listener of another gateway is skipped", func(t *testing.T) { + route := &UDPRouteContext{UDPRoute: &gwapiv1.UDPRoute{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "udproute-1"}, + }} + sibling := &ir.UDPRoute{Destination: &ir.RouteDestination{Name: irRoutePrefix(route) + "rule/0"}} + x := &ir.Xds{UDP: []*ir.UDPListener{{ + CoreListenerDetails: ir.CoreListenerDetails{Name: "envoy-gateway/other-gw/udp"}, + Route: sibling, + }}} + tr.applyTrafficFeatureToRoute(route, &ir.TrafficFeatures{LoadBalancer: &ir.LoadBalancer{}}, + nil, policy, target, x, gwNN, nil) + assert.Nil(t, sibling.LoadBalancer, "route on a sibling Gateway's listener must be skipped") + }) +} diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.in.yaml new file mode 100644 index 0000000000..0f92993909 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.in.yaml @@ -0,0 +1,88 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + # Gateway-level policy declares defaultChildMergeType. It is the only place the default can be + # declared (CEL rejects it on listener targets). + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + defaultChildMergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 15s + httpUpgrade: + - type: websocket + # Listener-level policy is the closest parent config a child merges into. It sets no + # defaultChildMergeType, so under "closest declared default" resolution the child still inherits + # the gateway-level default and merges into this policy. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-listener + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + # Route-level policy with no mergeType: merges into the listener-level policy using the + # gateway-level defaultChildMergeType. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + connection: + bufferLimit: 100M diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.out.yaml new file mode 100644 index 0000000000..c5c1791b24 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-listener-parent.out.yaml @@ -0,0 +1,315 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-listener + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-listener + namespace: envoy-gateway + spec: + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway + namespace: envoy-gateway + spec: + defaultChildMergeType: StrategicMerge + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: 'This policy is being overridden by other backendTrafficPolicies + for these listeners: [http]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.in.yaml new file mode 100644 index 0000000000..e94a983ca9 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.in.yaml @@ -0,0 +1,102 @@ +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + mergeGateways: true +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 8080 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + - namespace: envoy-gateway + name: gateway-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + # Distinct parent policy per Gateway: gateway-1 sets maxConnections 1111, gateway-2 sets 2222. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + defaultChildMergeType: StrategicMerge + circuitBreaker: + maxConnections: 1111 + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + defaultChildMergeType: StrategicMerge + circuitBreaker: + maxConnections: 2222 + # Route-level policy with no mergeType: should merge into each Gateway's own parent, so the route + # on gateway-1 gets maxConnections 1111 and the route on gateway-2 gets 2222. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + connection: + bufferLimit: 100Mi diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.out.yaml new file mode 100644 index 0000000000..20e30b7e37 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-merged-gateways.out.yaml @@ -0,0 +1,447 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100Mi + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway-1 + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway-2 + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway-1 + namespace: envoy-gateway + spec: + circuitBreaker: + maxConnections: 1111 + defaultChildMergeType: StrategicMerge + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway-2 + namespace: envoy-gateway + spec: + circuitBreaker: + maxConnections: 2222 + defaultChildMergeType: StrategicMerge + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 8080 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway-class: + proxy: + config: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + logging: {} + mergeGateways: true + status: {} + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - name: envoy-gateway/gateway-2/http + ports: + - containerPort: 8080 + name: http-8080 + protocol: HTTP + servicePort: 8080 + metadata: + labels: + gateway.envoyproxy.io/owning-gatewayclass: envoy-gateway-class + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway-class + namespace: envoy-gateway-system +xdsIR: + envoy-gateway-class: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-class-3b1df594 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway-class + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-class-3b1df594 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway-class + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 104857600 + circuitBreaker: + maxConnections: 1111 + - address: 0.0.0.0 + externalPort: 8080 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 104857600 + circuitBreaker: + maxConnections: 2222 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.in.yaml new file mode 100644 index 0000000000..71e4b64bfb --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.in.yaml @@ -0,0 +1,105 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + # gateway-2's policy has no defaultChildMergeType, so the same route policy applies standalone here. + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + - namespace: envoy-gateway + name: gateway-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + defaultChildMergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + timeout: + tcp: + connectTimeout: 25s + httpUpgrade: + - type: websocket + # Route-level policy with no mergeType: merges into gateway-1's policy (which sets + # defaultChildMergeType), but applies standalone on gateway-2 (whose policy does not). + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.out.yaml new file mode 100644 index 0000000000..a058487b62 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-multi-parent.out.yaml @@ -0,0 +1,494 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway-1 + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway-1 + namespace: envoy-gateway + spec: + defaultChildMergeType: StrategicMerge + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway-2 + namespace: envoy-gateway + spec: + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + timeout: + tcp: + connectTimeout: 25s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being overridden by other backendTrafficPolicies + for these routes: [default/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system + envoy-gateway/gateway-2: + proxy: + listeners: + - name: envoy-gateway/gateway-2/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-2 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + envoy-gateway/gateway-2: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-2-4a0e4eb9 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-2 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-2-4a0e4eb9 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-2 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + timeout: + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.in.yaml new file mode 100644 index 0000000000..8c541daedd --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.in.yaml @@ -0,0 +1,73 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + defaultChildMergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + # Route-level policy with mergeType Replace: it opts out of the parent policy's + # defaultChildMergeType and replaces the gateway-level policy for this route. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + mergeType: Replace + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.out.yaml new file mode 100644 index 0000000000..d679696218 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype-replace-opt-out.out.yaml @@ -0,0 +1,265 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + mergeType: Replace + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway + namespace: envoy-gateway + spec: + defaultChildMergeType: StrategicMerge + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being overridden by other backendTrafficPolicies + for these routes: [default/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + timeout: + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.in.yaml new file mode 100644 index 0000000000..238da3ed5c --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.in.yaml @@ -0,0 +1,72 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + defaultChildMergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + # Route-level policy with no mergeType: the parent policy's defaultChildMergeType makes it + # merge into the gateway-level policy instead of replacing it. + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.out.yaml new file mode 100644 index 0000000000..38b910bb37 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-default-mergetype.out.yaml @@ -0,0 +1,274 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway + reason: Merged + status: "True" + type: Merged + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-for-gateway + namespace: envoy-gateway + spec: + defaultChildMergeType: StrategicMerge + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-for-route + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/release-notes/current/new_features/9334-backendtrafficpolicy-default-child-mergetype.md b/release-notes/current/new_features/9334-backendtrafficpolicy-default-child-mergetype.md new file mode 100644 index 0000000000..eec212a7b2 --- /dev/null +++ b/release-notes/current/new_features/9334-backendtrafficpolicy-default-child-mergetype.md @@ -0,0 +1 @@ +Added `defaultChildMergeType` to `BackendTrafficPolicy` for policies targeting a Gateway, so a route-level policy that doesn't set `mergeType` merges into the parent policy instead of replacing it. A child policy with an explicit `mergeType` keeps its own behavior. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index e5fcea8c43..46e2e2ca50 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -584,6 +584,7 @@ _Appears in:_ | `dns` | _[DNS](#dns)_ | false | | DNS includes dns resolution settings. | | `http2` | _[HTTP2Settings](#http2settings)_ | false | | HTTP2 provides HTTP/2 configuration for backend connections. | | `mergeType` | _[MergeType](#mergetype)_ | false | | MergeType determines how this configuration is merged with existing BackendTrafficPolicy
configurations targeting a parent resource. When set, this configuration will be merged
into a parent BackendTrafficPolicy (i.e. the one targeting a Gateway or Listener).
This field cannot be set when targeting a parent resource (Gateway).
If unset, no merging occurs, and only the most specific configuration takes effect. | +| `defaultChildMergeType` | _[MergeType](#mergetype)_ | false | | DefaultChildMergeType is the merge strategy applied to child policies (policies targeting
an xRoute under this policy's target) that do not set their own mergeType, so a child
policy merges into this policy instead of replacing it. A child policy can opt out by
setting mergeType to Replace.
This field can only be set on policies targeting an entire Gateway. It is rejected on
policies targeting a specific Listener (via sectionName), so the default is defined at a
single top-level parent rather than at multiple intermediate parents. | | `rateLimit` | _[RateLimitSpec](#ratelimitspec)_ | false | | RateLimit allows the user to limit the number of incoming requests
to a predefined value based on attributes within the traffic flow. | | `bandwidthLimit` | _[BandwidthLimitSpec](#bandwidthlimitspec)_ | false | | BandwidthLimit allows the user to limit the bandwidth of traffic
sent to and received from the backend. | | `faultInjection` | _[FaultInjection](#faultinjection)_ | false | | FaultInjection defines the fault injection policy to be applied. This configuration can be used to
inject delays and abort requests to mimic failure scenarios such as service failures and overloads | diff --git a/site/content/en/latest/concepts/gateway_api_extensions/backend-traffic-policy.md b/site/content/en/latest/concepts/gateway_api_extensions/backend-traffic-policy.md index daa6fa1abf..48549ff5ca 100644 --- a/site/content/en/latest/concepts/gateway_api_extensions/backend-traffic-policy.md +++ b/site/content/en/latest/concepts/gateway_api_extensions/backend-traffic-policy.md @@ -147,7 +147,7 @@ spec: In this example, `alpha-policy` would take precedence due to its earlier creation time, so the HTTPRoute would use `maxConnections: 30`. -When the `mergeType` field is unset, no merging occurs and only the most specific configuration takes effect. However, policies can be configured to merge with parent policies using the `mergeType` field (see [Policy Merging](#policy-merging) section below). +When the `mergeType` field is unset, no merging occurs and only the most specific configuration takes effect, unless the parent policy sets `defaultChildMergeType` (see [Defaulting mergeType via the parent policy](#defaulting-mergetype-via-the-parent-policy)). Policies can also opt into merging explicitly using the `mergeType` field (see [Policy Merging](#policy-merging) section below). ## Policy Merging @@ -214,10 +214,39 @@ spec: In this example, the route-level policy merges with the gateway-level policy, resulting in both rate limits being enforced: the global 100 requests/second abuse limit and the route-specific 5 requests/minute limit. +### Defaulting mergeType via the parent policy + +Setting `mergeType` on every route-level policy can be repetitive when an organization wants merging to be the norm. To make merging the default, set `defaultChildMergeType` on the parent (Gateway-targeting) BackendTrafficPolicy: + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: gateway-baseline +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + defaultChildMergeType: StrategicMerge + rateLimit: + # ... baseline configuration for all routes under this Gateway +``` + +With this configuration, a route-level BackendTrafficPolicy that does **not** set `mergeType` merges into this parent policy as if it had set `mergeType: StrategicMerge`. This keeps merge behavior discoverable from the Policy resources alone, following the pattern described in [GEP-713](https://gateway-api.sigs.k8s.io/geps/gep-713/#example-3-merged-specs). + +The effective merge strategy for a child policy is resolved in this order: + +1. The child policy's own `mergeType`, if set. `Replace` explicitly opts the child out of merging, so it replaces the parent even when the parent sets `defaultChildMergeType`. +2. Otherwise, the closest parent policy's `defaultChildMergeType` (the listener-level policy if one exists, else the gateway-level policy). +3. Otherwise, no merging occurs and only the most specific policy takes effect. + +Note that `defaultChildMergeType` accepts only `StrategicMerge` or `JSONMerge` (defaulting to a replace would have no effect), and it can only be set on policies targeting a Gateway. + ### Key Constraints - The `mergeType` field can only be set on policies targeting child resources (like HTTPRoute), not parent resources (like Gateway) -- When `mergeType` is unset, no merging occurs - only the most specific policy takes effect +- When `mergeType` is unset, no merging occurs - only the most specific policy takes effect - unless the parent policy sets a [defaultChildMergeType](#defaulting-mergetype-via-the-parent-policy) - The merged configuration combines both policies, enabling layered protection strategies ## Related Resources diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go index 58a319a840..47f7a41bb5 100644 --- a/test/cel-validation/backendtrafficpolicy_test.go +++ b/test/cel-validation/backendtrafficpolicy_test.go @@ -3778,6 +3778,79 @@ func TestBackendTrafficPolicyTarget(t *testing.T) { }, wantErrors: []string{}, }, + { + desc: "defaultChildMergeType is valid on gateway targets", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("Gateway"), + Name: gwapiv1.ObjectName("eg"), + }, + }, + }, + DefaultChildMergeType: new(egv1a1.StrategicMerge), + } + }, + wantErrors: []string{}, + }, + { + desc: "defaultChildMergeType is rejected on gateway listener (sectionName) targets", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("Gateway"), + Name: gwapiv1.ObjectName("eg"), + }, + SectionName: §ionName, + }, + }, + DefaultChildMergeType: new(egv1a1.StrategicMerge), + } + }, + wantErrors: []string{"defaultChildMergeType can only be used with Gateway targets"}, + }, + { + desc: "defaultChildMergeType is rejected on route targets", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("HTTPRoute"), + Name: gwapiv1.ObjectName("httpbin-route"), + }, + }, + }, + DefaultChildMergeType: new(egv1a1.StrategicMerge), + } + }, + wantErrors: []string{"defaultChildMergeType can only be used with Gateway targets"}, + }, + { + desc: "defaultChildMergeType Replace is rejected", + mutate: func(btp *egv1a1.BackendTrafficPolicy) { + btp.Spec = egv1a1.BackendTrafficPolicySpec{ + PolicyTargetReferences: egv1a1.PolicyTargetReferences{ + TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ + Group: gwapiv1.Group("gateway.networking.k8s.io"), + Kind: gwapiv1.Kind("Gateway"), + Name: gwapiv1.ObjectName("eg"), + }, + }, + }, + DefaultChildMergeType: new(egv1a1.Replace), + } + }, + wantErrors: []string{"Unsupported value: \"Replace\": supported values: \"StrategicMerge\", \"JSONMerge\""}, + }, } for _, tc := range cases { diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 3db681cbe8..14570fd961 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -25130,6 +25130,19 @@ spec: Note that when the suffix is not provided, the value is interpreted as bytes. x-kubernetes-int-or-string: true type: object + defaultChildMergeType: + description: |- + DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + an xRoute under this policy's target) that do not set their own mergeType, so a child + policy merges into this policy instead of replacing it. A child policy can opt out by + setting mergeType to Replace. + This field can only be set on policies targeting an entire Gateway. It is rejected on + policies targeting a specific Listener (via sectionName), so the default is defined at a + single top-level parent rather than at multiple intermediate parents. + enum: + - StrategicMerge + - JSONMerge + type: string dns: description: DNS includes dns resolution settings. properties: @@ -28016,6 +28029,13 @@ spec: || self.targetRefs.all(ref, ref.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])))' + - message: defaultChildMergeType can only be used with Gateway targets + without a sectionName + rule: '!has(self.defaultChildMergeType) || ((!has(self.targetRef) || + (self.targetRef.kind == ''Gateway'' && !has(self.targetRef.sectionName))) + && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == + ''Gateway'' && !has(ref.sectionName))) && (!has(self.targetSelectors) + || self.targetSelectors.all(sel, sel.kind == ''Gateway'')))' - message: predictivePercent in preconnect policy only works with RoundRobin or Random load balancers rule: '!((has(self.connection) && has(self.connection.preconnect) && diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 0849fd858a..c9d5ee9263 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -1068,6 +1068,19 @@ spec: Note that when the suffix is not provided, the value is interpreted as bytes. x-kubernetes-int-or-string: true type: object + defaultChildMergeType: + description: |- + DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + an xRoute under this policy's target) that do not set their own mergeType, so a child + policy merges into this policy instead of replacing it. A child policy can opt out by + setting mergeType to Replace. + This field can only be set on policies targeting an entire Gateway. It is rejected on + policies targeting a specific Listener (via sectionName), so the default is defined at a + single top-level parent rather than at multiple intermediate parents. + enum: + - StrategicMerge + - JSONMerge + type: string dns: description: DNS includes dns resolution settings. properties: @@ -3954,6 +3967,13 @@ spec: || self.targetRefs.all(ref, ref.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])))' + - message: defaultChildMergeType can only be used with Gateway targets + without a sectionName + rule: '!has(self.defaultChildMergeType) || ((!has(self.targetRef) || + (self.targetRef.kind == ''Gateway'' && !has(self.targetRef.sectionName))) + && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == + ''Gateway'' && !has(ref.sectionName))) && (!has(self.targetSelectors) + || self.targetSelectors.all(sel, sel.kind == ''Gateway'')))' - message: predictivePercent in preconnect policy only works with RoundRobin or Random load balancers rule: '!((has(self.connection) && has(self.connection.preconnect) && diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 813bedd5f9..234ec46b8b 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1068,6 +1068,19 @@ spec: Note that when the suffix is not provided, the value is interpreted as bytes. x-kubernetes-int-or-string: true type: object + defaultChildMergeType: + description: |- + DefaultChildMergeType is the merge strategy applied to child policies (policies targeting + an xRoute under this policy's target) that do not set their own mergeType, so a child + policy merges into this policy instead of replacing it. A child policy can opt out by + setting mergeType to Replace. + This field can only be set on policies targeting an entire Gateway. It is rejected on + policies targeting a specific Listener (via sectionName), so the default is defined at a + single top-level parent rather than at multiple intermediate parents. + enum: + - StrategicMerge + - JSONMerge + type: string dns: description: DNS includes dns resolution settings. properties: @@ -3954,6 +3967,13 @@ spec: || self.targetRefs.all(ref, ref.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in [''Gateway'', ''HTTPRoute'', ''GRPCRoute''])))' + - message: defaultChildMergeType can only be used with Gateway targets + without a sectionName + rule: '!has(self.defaultChildMergeType) || ((!has(self.targetRef) || + (self.targetRef.kind == ''Gateway'' && !has(self.targetRef.sectionName))) + && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind == + ''Gateway'' && !has(ref.sectionName))) && (!has(self.targetSelectors) + || self.targetSelectors.all(sel, sel.kind == ''Gateway'')))' - message: predictivePercent in preconnect policy only works with RoundRobin or Random load balancers rule: '!((has(self.connection) && has(self.connection.preconnect) &&