Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 43 additions & 59 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
policyTargetGatewayNN *types.NamespacedName,
policyTargetListener *gwapiv1.SectionName,
) error {
tf, errs := t.buildTrafficFeatures(policy)
tf, errs := t.buildTrafficFeatures(policy, nil)
if tf == nil {
// should not happen
return nil
Expand All @@ -848,13 +848,13 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge(
policyTargetGatewayNN types.NamespacedName, policyTargetListener *gwapiv1.SectionName, route RouteContext,
xdsIR resource.XdsIRMap,
) error {
mergedPolicy, err := t.mergeBackendTrafficPolicy(policy, parentPolicy)
mergedPolicy, owners, err := t.mergeBackendTrafficPolicy(policy, parentPolicy)
if err != nil {
return fmt.Errorf("error merging policies: %w", err)
}

// Build traffic features from the merged policy
tf, errs := t.buildTrafficFeatures(mergedPolicy)
tf, errs := t.buildTrafficFeatures(mergedPolicy, owners)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return merged feature build errors

After this refactor, a missing or invalid ConfigMap in an inherited parent responseOverride is reported in errs from this call instead of being returned by mergeBackendTrafficPolicy; in the merge path errs is only passed to applyTrafficFeatureToRoute and the function later returns nil, so the caller records the route policy as Accepted/Merged instead of setting an Invalid condition. This affects merged BackendTrafficPolicies that inherit a parent responseOverride with a broken ValueRef; please propagate errs after applying the direct response so the user-visible status still reports the invalid reference.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed and added a test

if tf == nil {
// should not happen
return nil
Expand All @@ -869,8 +869,8 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge(
// 2. Only gateway policy has rate limits - preserve gateway policy's rule names
// 3. Only route policy has rate limits - use route policy's rule names (default behavior)
if policy.Spec.RateLimit != nil && parentPolicy.Spec.RateLimit != nil {
tfGW, _ := t.buildTrafficFeatures(parentPolicy)
tfRoute, _ := t.buildTrafficFeatures(policy)
tfGW, _ := t.buildTrafficFeatures(parentPolicy, nil)
tfRoute, _ := t.buildTrafficFeatures(policy, nil)

if tfGW != nil && tfRoute != nil &&
tfGW.RateLimit != nil && tfRoute.RateLimit != nil {
Expand All @@ -884,7 +884,7 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge(
}
} else if policy.Spec.RateLimit == nil && parentPolicy.Spec.RateLimit != nil {
// Case 2: Only gateway policy has rate limits - preserve gateway policy's rule names
tfGW, _ := t.buildTrafficFeatures(parentPolicy)
tfGW, _ := t.buildTrafficFeatures(parentPolicy, nil)
if tfGW != nil && tfGW.RateLimit != nil {
// Use the gateway policy's rate limit with its original rule names
tf.RateLimit = tfGW.RateLimit
Expand All @@ -899,7 +899,7 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge(
}
t.applyTrafficFeatureToRoute(route, tf, errs, mergedPolicy, target, x, policyTargetListener)

return nil
return errs
}

func (t *Translator) applyTrafficFeatureToRoute(route RouteContext,
Expand Down Expand Up @@ -1034,25 +1034,25 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext,
}
}

// mergeBackendTrafficPolicy merges route policy into gateway policy.
func (t *Translator) mergeBackendTrafficPolicy(routePolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, error) {
// 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.
func (t *Translator) mergeBackendTrafficPolicy(routePolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, *backendTrafficPolicyOwners, error) {
if routePolicy.Spec.MergeType == nil || gwPolicy == nil {
return routePolicy, nil
return routePolicy, nil, nil
}

// Resolve LocalObjectReferences to inline content in the policies before merge so the merge operates on concrete values.
if err := t.resolveLocalObjectRefsInPolicy(gwPolicy); err != nil {
return nil, err
}
if err := t.resolveLocalObjectRefsInPolicy(routePolicy); err != nil {
return nil, err
mergedPolicy, err := utils.Merge(gwPolicy, routePolicy, *routePolicy.Spec.MergeType)
if err != nil {
return nil, nil, err
}

return utils.Merge(gwPolicy, routePolicy, *routePolicy.Spec.MergeType)
return mergedPolicy, buildBackendTrafficPolicyOwners(routePolicy, gwPolicy), nil
}

// buildTrafficFeatures builds IR traffic features from a BackendTrafficPolicy.
func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy) (*ir.TrafficFeatures, error) {
// buildTrafficFeatures builds IR traffic features from a BackendTrafficPolicy. owners is
// the per-field owners for a merged policy, or nil to resolve references against the
// policy's own namespace.
func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy, owners *backendTrafficPolicyOwners) (*ir.TrafficFeatures, error) {
var (
rl *ir.RateLimit
bl *ir.BandwidthLimit
Expand Down Expand Up @@ -1128,7 +1128,7 @@ func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy) (
errs = errors.Join(errs, err)
}

if ro, err = t.buildResponseOverride(policy); err != nil {
if ro, err = t.buildResponseOverride(policy, owners); err != nil {
err = perr.WithMessage(err, "ResponseOverride")
errs = errors.Join(errs, err)
}
Expand Down Expand Up @@ -1211,7 +1211,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(
policy *egv1a1.BackendTrafficPolicy, target policyTargetReferenceWithSectionName,
gateway *GatewayContext, xdsIR resource.XdsIRMap,
) error {
tf, errs := t.buildTrafficFeatures(policy)
tf, errs := t.buildTrafficFeatures(policy, nil)
if tf == nil {
// should not happen
return errs
Expand Down Expand Up @@ -1904,11 +1904,17 @@ func buildRequestBuffer(spec *egv1a1.RequestBuffer) (*ir.RequestBuffer, error) {
}, nil
}

func (t *Translator) buildResponseOverride(policy *egv1a1.BackendTrafficPolicy) (*ir.ResponseOverride, error) {
func (t *Translator) buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, owners *backendTrafficPolicyOwners) (*ir.ResponseOverride, error) {
if len(policy.Spec.ResponseOverride) == 0 {
return nil, nil
}

// Resolve body ValueRefs against the owner's namespace, falling back to the policy's own.
responseOverrideNs := policy.Namespace
if owners != nil && owners.responseOverride != nil {
responseOverrideNs = owners.responseOverride.Namespace
}

rules := make([]ir.ResponseOverrideRule, 0, len(policy.Spec.ResponseOverride))
for index, ro := range policy.Spec.ResponseOverride {
match := ir.CustomResponseMatch{
Expand Down Expand Up @@ -1966,7 +1972,7 @@ func (t *Translator) buildResponseOverride(policy *egv1a1.BackendTrafficPolicy)
}

var err error
response.Body, err = t.getCustomResponseBody(ro.Response.Body, policy.Namespace)
response.Body, err = t.getCustomResponseBody(ro.Response.Body, responseOverrideNs)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2042,45 +2048,23 @@ func (t *Translator) getCustomResponseBody(
return nil, nil
}

// resolveCustomResponseBodyRefToInline resolves a ValueRef in body to inline content using the given namespace.
// It mutates body in place: replaces Type and ValueRef with Inline content. No-op if body is nil or already Inline.
func (t *Translator) resolveCustomResponseBodyRefToInline(body *egv1a1.CustomResponseBody, policyNs string) error {
if body == nil {
return nil
}
if body.Type == nil || *body.Type != egv1a1.ResponseValueTypeValueRef || body.ValueRef == nil {
return nil
}
data, err := t.getCustomResponseBody(body, policyNs)
if err != nil {
return err
}
inlineStr := string(data)
t.Logger.Info("resolved custom response body ref to inline before merge",
"namespace", policyNs,
"ref", body.ValueRef.Name,
)
body.Type = new(egv1a1.ResponseValueTypeInline)
body.Inline = &inlineStr
body.ValueRef = nil
return nil
// backendTrafficPolicyOwners records which policy (route or parent) contributed each
// merged field that references other objects, so references resolve against the owner's
// namespace. Mirrors the field-owner pattern used for SecurityPolicy.
type backendTrafficPolicyOwners struct {
responseOverride *egv1a1.BackendTrafficPolicy
}

// resolveLocalObjectRefsInPolicy resolves LocalObjectReferences to inline content in the given policy (mutates in place).
// Currently handles ResponseOverride body ValueRefs; may be extended for other refs BackendTrafficPolicy supports.
func (t *Translator) resolveLocalObjectRefsInPolicy(policy *egv1a1.BackendTrafficPolicy) error {
if policy == nil || len(policy.Spec.ResponseOverride) == 0 {
return nil
// buildBackendTrafficPolicyOwners picks the owner of each merged field: the route policy
// when it sets the field, otherwise the parent.
func buildBackendTrafficPolicyOwners(route, parent *egv1a1.BackendTrafficPolicy) *backendTrafficPolicyOwners {
responseOverrideOwner := parent
if len(route.Spec.ResponseOverride) > 0 {
responseOverrideOwner = route
}
policyNs := policy.Namespace
for _, ro := range policy.Spec.ResponseOverride {
if ro != nil && ro.Response != nil && ro.Response.Body != nil {
if err := t.resolveCustomResponseBodyRefToInline(ro.Response.Body, policyNs); err != nil {
return err
}
}
return &backendTrafficPolicyOwners{
responseOverride: responseOverrideOwner,
}
return nil
}

func sourceFromAPI(s *egv1a1.ResponseOverrideSource) egv1a1.ResponseOverrideSource {
Expand Down
6 changes: 3 additions & 3 deletions internal/gatewayapi/backendtrafficpolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func TestBuildTrafficFeaturesRejectsRequestBufferWithHTTPUpgrade(t *testing.T) {
},
}

tf, err := tr.buildTrafficFeatures(policy)
tf, err := tr.buildTrafficFeatures(policy, nil)
require.ErrorContains(t, err, "RequestBuffer: requestBuffer cannot be used together with httpUpgrade")
require.NotNil(t, tf)
})
Expand All @@ -238,10 +238,10 @@ func TestBuildTrafficFeaturesRejectsRequestBufferWithHTTPUpgrade(t *testing.T) {
},
}

mergedPolicy, err := tr.mergeBackendTrafficPolicy(routePolicy, parentPolicy)
mergedPolicy, owners, err := tr.mergeBackendTrafficPolicy(routePolicy, parentPolicy)
require.NoError(t, err)

tf, err := tr.buildTrafficFeatures(mergedPolicy)
tf, err := tr.buildTrafficFeatures(mergedPolicy, owners)
require.ErrorContains(t, err, "RequestBuffer: requestBuffer cannot be used together with httpUpgrade")
require.NotNil(t, tf)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ backendTrafficPolicies:
type: Range
response:
body:
inline: |
error page contents here
type: Inline
type: ValueRef
valueRef:
group: ""
kind: ConfigMap
name: error-page
contentType: text/html
targetRefs:
- group: gateway.networking.k8s.io
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Tests merged BackendTrafficPolicies when the ResponseOverride ValueRef inherited from
# the gateway (parent) policy points at a ConfigMap that does not exist. The route BTP
# has mergeType and inherits the parent responseOverride; resolution must fail and the
# route policy must report an Invalid condition (not Merged).
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway-system
name: my-gateway
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
backendTrafficPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: my-gateway-error-response
namespace: envoy-gateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: my-gateway
responseOverride:
- match:
statusCodes:
- type: Range
range:
start: 502
end: 504
response:
contentType: "text/html"
body:
type: ValueRef
valueRef:
group: ""
kind: ConfigMap
name: missing-error-page
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: my-app-rate-limit
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: my-app
mergeType: StrategicMerge
rateLimit:
local:
rules:
- clientSelectors:
- sourceCIDR:
type: Distinct
value: 0.0.0.0/0
limit:
requests: 200
unit: Minute
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: default
spec:
hostnames:
- myapp.example.com
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: my-gateway
namespace: envoy-gateway-system
rules:
- backendRefs:
- group: ""
kind: Service
name: service-1
port: 8080
weight: 1
matches:
- path:
type: PathPrefix
value: /
Loading
Loading