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
59 changes: 55 additions & 4 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge(
policyTargetGatewayNN types.NamespacedName, policyTargetListener *gwapiv1.SectionName, route RouteContext,
xdsIR resource.XdsIRMap,
) error {
mergedPolicy, err := mergeBackendTrafficPolicy(policy, parentPolicy)
mergedPolicy, err := t.mergeBackendTrafficPolicy(policy, parentPolicy)
if err != nil {
return fmt.Errorf("error merging policies: %w", err)
}
Expand Down Expand Up @@ -842,14 +842,24 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext,
}
}

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

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

// buildTrafficFeatures builds IR traffic features from a BackendTrafficPolicy.
func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy) (*ir.TrafficFeatures, error) {
var (
rl *ir.RateLimit
Expand Down Expand Up @@ -1684,10 +1694,10 @@ func (t *Translator) getCustomResponseBody(
return binData, nil
}
default:
return nil, fmt.Errorf("can't find the key %s in the referenced configmap %s", ResponseBodyConfigMapKey, body.ValueRef.Name)
return nil, fmt.Errorf("can't find the key %s in the referenced configmap %s/%s", ResponseBodyConfigMapKey, policyNs, body.ValueRef.Name)
}
} else {
return nil, fmt.Errorf("can't find the referenced configmap %s", body.ValueRef.Name)
return nil, fmt.Errorf("can't find the referenced configmap %s/%s", policyNs, body.ValueRef.Name)
}
} else if body.Inline != nil {
inlineValue := []byte(*body.Inline)
Expand All @@ -1697,6 +1707,47 @@ 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 = ptr.To(egv1a1.ResponseValueTypeInline)
body.Inline = &inlineStr
body.ValueRef = nil
return nil
}

// 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
}
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 nil
}

func defaultResponseOverrideRuleName(policy *egv1a1.BackendTrafficPolicy, index int) string {
return fmt.Sprintf(
"%s/responseoverride/rule/%s",
Expand Down
3 changes: 1 addition & 2 deletions internal/gatewayapi/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,9 +893,8 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec
if hrf.Spec.DirectResponse != nil {
dr := &ir.CustomResponse{}
if hrf.Spec.DirectResponse.Body != nil {
body := hrf.Spec.DirectResponse.Body
var err error
if dr.Body, err = t.getCustomResponseBody(body, filterNs); err != nil {
if dr.Body, err = t.getCustomResponseBody(hrf.Spec.DirectResponse.Body, filterNs); err != nil {
return t.processInvalidHTTPFilter(string(extFilter.Kind), filterContext, err)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ backendTrafficPolicies:
sectionName: http
conditions:
- lastTransitionTime: null
message: 'ResponseOverride: can''t find the referenced configmap response-override-config-1.'
message: 'ResponseOverride: can''t find the referenced configmap default/response-override-config-1.'
reason: Invalid
status: "False"
type: Accepted
Expand Down Expand Up @@ -101,7 +101,7 @@ backendTrafficPolicies:
conditions:
- lastTransitionTime: null
message: 'ResponseOverride: can''t find the key response.body in the referenced
configmap response-override-config.'
configmap default/response-override-config.'
reason: Invalid
status: "False"
type: Accepted
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Tests merged BackendTrafficPolicies when ResponseOverride ConfigMap lives in the
# gateway (parent) policy's namespace. Gateway BTP is in envoy-gateway-system with
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nice !

# a ConfigMap reference; route BTP is in default with mergeType. After merge, the
# ConfigMap must be resolved in the parent policy's namespace.
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
configMaps:
- apiVersion: v1
kind: ConfigMap
metadata:
name: error-page
namespace: envoy-gateway-system
data:
response.body: |
error page contents here
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: 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