Skip to content

Commit 44058a5

Browse files
committed
Add request header filter support for gRPC
1 parent d3a4846 commit 44058a5

File tree

7 files changed

+334
-263
lines changed

7 files changed

+334
-263
lines changed

internal/mode/static/state/graph/grpcroute.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,26 +73,24 @@ func processGRPCRouteRules(
7373
validator validation.HTTPFieldsValidator,
7474
) (rules []RouteRule, atLeastOneValid bool, allRulesErrs field.ErrorList) {
7575
rules = make([]RouteRule, len(specRules))
76-
validFilters := true
7776

7877
for i, rule := range specRules {
7978
rulePath := field.NewPath("spec").Child("rules").Index(i)
8079

8180
var allErrs field.ErrorList
82-
8381
var matchesErrs field.ErrorList
82+
var filtersErrs field.ErrorList
83+
8484
for j, match := range rule.Matches {
8585
matchPath := rulePath.Child("matches").Index(j)
8686
matchesErrs = append(matchesErrs, validateGRPCMatch(validator, match, matchPath)...)
8787
}
8888

8989
if len(rule.Filters) > 0 {
90-
filterPath := rulePath.Child("filters")
91-
allErrs = append(
92-
allErrs,
93-
field.NotSupported(filterPath, rule.Filters, []string{"gRPC filters are not yet supported"}),
94-
)
95-
validFilters = false
90+
for j, filter := range rule.Filters {
91+
filterPath := rulePath.Child("filters").Index(j)
92+
filtersErrs = append(filtersErrs, validateGRPCFilter(validator, filter, filterPath)...)
93+
}
9694
}
9795

9896
backendRefs := make([]RouteBackendRef, 0, len(rule.BackendRefs))
@@ -114,17 +112,20 @@ func processGRPCRouteRules(
114112
}
115113

116114
allErrs = append(allErrs, matchesErrs...)
115+
allErrs = append(allErrs, filtersErrs...)
117116
allRulesErrs = append(allRulesErrs, allErrs...)
118117

119118
if len(allErrs) == 0 {
120119
atLeastOneValid = true
121120
}
122121

122+
validFilters := len(filtersErrs) == 0
123+
123124
rules[i] = RouteRule{
124125
ValidMatches: len(matchesErrs) == 0,
125126
ValidFilters: validFilters,
126127
Matches: convertGRPCMatches(rule.Matches),
127-
Filters: nil,
128+
Filters: convertGRPCFilters(rule.Filters, validFilters),
128129
RouteBackendRefs: backendRefs,
129130
}
130131
}
@@ -234,3 +235,47 @@ func validateGRPCMethodMatch(
234235
}
235236
return allErrs
236237
}
238+
239+
func validateGRPCFilter(
240+
validator validation.HTTPFieldsValidator,
241+
filter v1alpha2.GRPCRouteFilter,
242+
filterPath *field.Path,
243+
) field.ErrorList {
244+
var allErrs field.ErrorList
245+
246+
switch filter.Type {
247+
case v1alpha2.GRPCRouteFilterRequestHeaderModifier:
248+
return validateFilterHeaderModifier(validator, filter.RequestHeaderModifier, filterPath)
249+
default:
250+
valErr := field.NotSupported(
251+
filterPath.Child("type"),
252+
filter.Type,
253+
[]string{
254+
string(v1alpha2.GRPCRouteFilterRequestHeaderModifier),
255+
},
256+
)
257+
allErrs = append(allErrs, valErr)
258+
return allErrs
259+
}
260+
}
261+
262+
func convertGRPCFilters(filters []v1alpha2.GRPCRouteFilter, validFilters bool) []v1.HTTPRouteFilter {
263+
// validation has already been done, don't process the filters if they are invalid
264+
if !validFilters || len(filters) == 0 {
265+
return nil
266+
}
267+
httpFilters := make([]v1.HTTPRouteFilter, 0, len(filters))
268+
for _, filter := range filters {
269+
switch filter.Type {
270+
case v1alpha2.GRPCRouteFilterRequestHeaderModifier:
271+
httpRequestHeaderFilter := v1.HTTPRouteFilter{
272+
Type: v1.HTTPRouteFilterRequestHeaderModifier,
273+
RequestHeaderModifier: filter.RequestHeaderModifier,
274+
}
275+
httpFilters = append(httpFilters, httpRequestHeaderFilter)
276+
default:
277+
continue
278+
}
279+
}
280+
return httpFilters
281+
}

internal/mode/static/state/graph/grpcroute_test.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func TestBuildGRPCRoute(t *testing.T) {
223223

224224
grInvalidFilterRule.Filters = []v1alpha2.GRPCRouteFilter{
225225
{
226-
Type: "RequestHeaderModifier",
226+
Type: "RequestMirror",
227227
},
228228
}
229229

@@ -234,6 +234,24 @@ func TestBuildGRPCRoute(t *testing.T) {
234234
[]v1alpha2.GRPCRouteRule{grInvalidFilterRule},
235235
)
236236

237+
grValidFilterRule := createGRPCMethodMatch("myService", "myMethod", "Exact")
238+
239+
grValidFilterRule.Filters = []v1alpha2.GRPCRouteFilter{
240+
{
241+
Type: "RequestHeaderModifier",
242+
RequestHeaderModifier: &v1.HTTPHeaderFilter{
243+
Remove: []string{"header"},
244+
},
245+
},
246+
}
247+
248+
grValidFilter := createGRPCRoute(
249+
"gr",
250+
gatewayNsName.Name,
251+
"example.com",
252+
[]v1alpha2.GRPCRouteRule{grValidFilterRule},
253+
)
254+
237255
createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator {
238256
v := &validationfakes.FakeHTTPFieldsValidator{}
239257
v.ValidateMethodInMatchReturns(true, nil)
@@ -310,6 +328,36 @@ func TestBuildGRPCRoute(t *testing.T) {
310328
},
311329
name: "valid rule with empty match",
312330
},
331+
{
332+
validator: createAllValidValidator(),
333+
gr: grValidFilter,
334+
expected: &L7Route{
335+
RouteType: RouteTypeGRPC,
336+
Source: grValidFilter,
337+
ParentRefs: []ParentRef{
338+
{
339+
Idx: 0,
340+
Gateway: gatewayNsName,
341+
SectionName: grValidFilter.Spec.ParentRefs[0].SectionName,
342+
},
343+
},
344+
Valid: true,
345+
Attachable: true,
346+
Spec: L7RouteSpec{
347+
Hostnames: grValidFilter.Spec.Hostnames,
348+
Rules: []RouteRule{
349+
{
350+
ValidMatches: true,
351+
ValidFilters: true,
352+
Matches: convertGRPCMatches(grValidFilter.Spec.Rules[0].Matches),
353+
RouteBackendRefs: []RouteBackendRef{},
354+
Filters: convertGRPCFilters(grValidFilter.Spec.Rules[0].Filters, true),
355+
},
356+
},
357+
},
358+
},
359+
name: "valid rule with filter",
360+
},
313361
{
314362
validator: createAllValidValidator(),
315363
gr: grInvalidMatchesEmptyMethodFields,
@@ -522,10 +570,8 @@ func TestBuildGRPCRoute(t *testing.T) {
522570
},
523571
Conditions: []conditions.Condition{
524572
staticConds.NewRouteUnsupportedValue(
525-
`All rules are invalid: spec.rules[0].filters: Unsupported value: []v1alpha2.GRPCRouteFilter{v1alpha2.` +
526-
`GRPCRouteFilter{Type:"RequestHeaderModifier", RequestHeaderModifier:(*v1.HTTPHeaderFilter)(nil), ` +
527-
`ResponseHeaderModifier:(*v1.HTTPHeaderFilter)(nil), RequestMirror:(*v1.HTTPRequestMirrorFilter)(nil), ` +
528-
`ExtensionRef:(*v1.LocalObjectReference)(nil)}}: supported values: "gRPC filters are not yet supported"`,
573+
`All rules are invalid: spec.rules[0].filters[0].type: ` +
574+
`Unsupported value: "RequestMirror": supported values: "RequestHeaderModifier"`,
529575
),
530576
},
531577
Spec: L7RouteSpec{

internal/mode/static/state/graph/httproute.go

Lines changed: 1 addition & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package graph
22

33
import (
44
"fmt"
5-
"strings"
65

76
"k8s.io/apimachinery/pkg/types"
87
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -247,7 +246,7 @@ func validateFilter(
247246
case v1.HTTPRouteFilterURLRewrite:
248247
return validateFilterRewrite(validator, filter, filterPath)
249248
case v1.HTTPRouteFilterRequestHeaderModifier:
250-
return validateFilterHeaderModifier(validator, filter, filterPath)
249+
return validateFilterHeaderModifier(validator, filter.RequestHeaderModifier, filterPath)
251250
default:
252251
valErr := field.NotSupported(
253252
filterPath.Child("type"),
@@ -355,107 +354,3 @@ func validateFilterRewrite(
355354

356355
return allErrs
357356
}
358-
359-
func validateFilterHeaderModifier(
360-
validator validation.HTTPFieldsValidator,
361-
filter v1.HTTPRouteFilter,
362-
filterPath *field.Path,
363-
) field.ErrorList {
364-
headerModifier := filter.RequestHeaderModifier
365-
366-
headerModifierPath := filterPath.Child("requestHeaderModifier")
367-
368-
if headerModifier == nil {
369-
return field.ErrorList{field.Required(headerModifierPath, "requestHeaderModifier cannot be nil")}
370-
}
371-
372-
return validateFilterHeaderModifierFields(validator, headerModifier, headerModifierPath)
373-
}
374-
375-
func validateFilterHeaderModifierFields(
376-
validator validation.HTTPFieldsValidator,
377-
headerModifier *v1.HTTPHeaderFilter,
378-
headerModifierPath *field.Path,
379-
) field.ErrorList {
380-
var allErrs field.ErrorList
381-
382-
// Ensure that the header names are case-insensitive unique
383-
allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique(
384-
headerModifier.Add,
385-
headerModifierPath.Child("add"))...,
386-
)
387-
allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique(
388-
headerModifier.Set,
389-
headerModifierPath.Child("set"))...,
390-
)
391-
allErrs = append(allErrs, validateRequestHeaderStringCaseInsensitiveUnique(
392-
headerModifier.Remove,
393-
headerModifierPath.Child("remove"))...,
394-
)
395-
396-
for _, h := range headerModifier.Add {
397-
if err := validator.ValidateRequestHeaderName(string(h.Name)); err != nil {
398-
valErr := field.Invalid(headerModifierPath.Child("add"), h, err.Error())
399-
allErrs = append(allErrs, valErr)
400-
}
401-
if err := validator.ValidateRequestHeaderValue(h.Value); err != nil {
402-
valErr := field.Invalid(headerModifierPath.Child("add"), h, err.Error())
403-
allErrs = append(allErrs, valErr)
404-
}
405-
}
406-
for _, h := range headerModifier.Set {
407-
if err := validator.ValidateRequestHeaderName(string(h.Name)); err != nil {
408-
valErr := field.Invalid(headerModifierPath.Child("set"), h, err.Error())
409-
allErrs = append(allErrs, valErr)
410-
}
411-
if err := validator.ValidateRequestHeaderValue(h.Value); err != nil {
412-
valErr := field.Invalid(headerModifierPath.Child("set"), h, err.Error())
413-
allErrs = append(allErrs, valErr)
414-
}
415-
}
416-
for _, h := range headerModifier.Remove {
417-
if err := validator.ValidateRequestHeaderName(h); err != nil {
418-
valErr := field.Invalid(headerModifierPath.Child("remove"), h, err.Error())
419-
allErrs = append(allErrs, valErr)
420-
}
421-
}
422-
423-
return allErrs
424-
}
425-
426-
func validateRequestHeadersCaseInsensitiveUnique(
427-
headers []v1.HTTPHeader,
428-
path *field.Path,
429-
) field.ErrorList {
430-
var allErrs field.ErrorList
431-
432-
seen := make(map[string]struct{})
433-
434-
for _, h := range headers {
435-
name := strings.ToLower(string(h.Name))
436-
if _, exists := seen[name]; exists {
437-
valErr := field.Invalid(path, h, "header name is not unique")
438-
allErrs = append(allErrs, valErr)
439-
}
440-
seen[name] = struct{}{}
441-
}
442-
443-
return allErrs
444-
}
445-
446-
func validateRequestHeaderStringCaseInsensitiveUnique(headers []string, path *field.Path) field.ErrorList {
447-
var allErrs field.ErrorList
448-
449-
seen := make(map[string]struct{})
450-
451-
for _, h := range headers {
452-
name := strings.ToLower(h)
453-
if _, exists := seen[name]; exists {
454-
valErr := field.Invalid(path, h, "header name is not unique")
455-
allErrs = append(allErrs, valErr)
456-
}
457-
seen[name] = struct{}{}
458-
}
459-
460-
return allErrs
461-
}

0 commit comments

Comments
 (0)