Skip to content

Commit b38239d

Browse files
arybolovlevmhofstetter
authored andcommitted
gateway-api: unify Gateway API listener parentRef matching
Extract duplicated route-to-listener parentRef matching into `parentRefsMatchListener`. HTTP and L4 routes already considered both sectionName and port, while GRPC and TLS routes only checked sectionName. Use the shared helper for HTTP, GRPC, TLS, TCP, and UDP routes so all route types consistently filter listener matches by both fields. Add test coverage for the shared helper and TLS parentRef port filtering. Signed-off-by: Aleksandr Rybolovlev <aleksandr.rybolovlev@proton.me>
1 parent 60c1985 commit b38239d

6 files changed

Lines changed: 191 additions & 80 deletions

File tree

operator/pkg/model/ingestion/gateway.go

Lines changed: 12 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -343,47 +343,7 @@ func toHTTPRoutes(log *slog.Logger,
343343
) []model.HTTPRoute {
344344
var httpRoutes []model.HTTPRoute
345345
for _, r := range input {
346-
listenerIsParent := false
347-
// Check parents to see if r can attach to them.
348-
// We have to consider _both_ SectionName and Port
349-
for _, parent := range r.Spec.ParentRefs {
350-
// First, if both SectionName and Port are unset, attach
351-
if parent.SectionName == nil && parent.Port == nil {
352-
listenerIsParent = true
353-
break
354-
}
355-
356-
// Then, if SectionName is set, check combinations with Port.
357-
if parent.SectionName != nil {
358-
if *parent.SectionName != listener.Name {
359-
// If SectionName is set but not equal, no other settings
360-
// matter, so check the next parent.
361-
continue
362-
}
363-
364-
if parent.Port != nil && *parent.Port != listener.Port {
365-
// If SectionName is set and equal, but Port is set and _unequal_,
366-
continue
367-
}
368-
369-
listenerIsParent = true
370-
break
371-
}
372-
373-
if parent.Port != nil {
374-
if *parent.Port != listener.Port {
375-
// If Port is set but not equal, no other settings
376-
// matter, check the next parent.
377-
continue
378-
}
379-
380-
listenerIsParent = true
381-
break
382-
}
383-
384-
}
385-
386-
if !listenerIsParent {
346+
if !parentRefsMatchListener(r.Spec.ParentRefs, listener) {
387347
continue
388348
}
389349

@@ -761,14 +721,7 @@ func toGRPCRoutes(listener gatewayv1beta1.Listener,
761721
) []model.HTTPRoute {
762722
var grpcRoutes []model.HTTPRoute
763723
for _, r := range input {
764-
isListener := false
765-
for _, parent := range r.Spec.ParentRefs {
766-
if parent.SectionName == nil || *parent.SectionName == listener.Name {
767-
isListener = true
768-
break
769-
}
770-
}
771-
if !isListener {
724+
if !parentRefsMatchListener(r.Spec.ParentRefs, listener) {
772725
continue
773726
}
774727

@@ -912,14 +865,7 @@ func extractGRPCRoutes(hostnames []string, grpcr gatewayv1.GRPCRoute, services [
912865
func toTLSRoutes(listener gatewayv1beta1.Listener, gatewayNamespace string, namespaceLabels helpers.NamespaceLabelIndex, namespacesPreFiltered bool, listenerHostnamesByProtocol map[gatewayv1.ProtocolType][]string, input []gatewayv1.TLSRoute, services []corev1.Service, serviceImports []mcsapiv1beta1.ServiceImport, grants []gatewayv1.ReferenceGrant) []model.TLSPassthroughRoute {
913866
var tlsRoutes []model.TLSPassthroughRoute
914867
for _, r := range input {
915-
isListener := false
916-
for _, parent := range r.Spec.ParentRefs {
917-
if parent.SectionName == nil || *parent.SectionName == listener.Name {
918-
isListener = true
919-
break
920-
}
921-
}
922-
if !isListener {
868+
if !parentRefsMatchListener(r.Spec.ParentRefs, listener) {
923869
continue
924870
}
925871

@@ -972,32 +918,18 @@ func toTLSRoutes(listener gatewayv1beta1.Listener, gatewayNamespace string, name
972918
return tlsRoutes
973919
}
974920

975-
// l4RouteAttachesToListener reports whether a TCP/UDP route with the given
976-
// parentRefs attaches to the listener, mirroring the sectionName/port matching
977-
// rules used by HTTP/TLS routes.
978-
func l4RouteAttachesToListener(parentRefs []gatewayv1.ParentReference, listener gatewayv1beta1.Listener) bool {
921+
func parentRefsMatchListener(parentRefs []gatewayv1.ParentReference, listener gatewayv1.Listener) bool {
979922
for _, parent := range parentRefs {
980-
if parent.SectionName == nil && parent.Port == nil {
981-
return true
923+
if parent.SectionName != nil && *parent.SectionName != listener.Name {
924+
continue
982925
}
983-
984-
if parent.SectionName != nil {
985-
if *parent.SectionName != listener.Name {
986-
continue
987-
}
988-
if parent.Port != nil && *parent.Port != listener.Port {
989-
continue
990-
}
991-
return true
926+
if parent.Port != nil && *parent.Port != listener.Port {
927+
continue
992928
}
993929

994-
if parent.Port != nil {
995-
if *parent.Port != listener.Port {
996-
continue
997-
}
998-
return true
999-
}
930+
return true
1000931
}
932+
1001933
return false
1002934
}
1003935

@@ -1037,7 +969,7 @@ func toTCPRoutes(listener gatewayv1beta1.Listener,
1037969
if !namespacesPreFiltered && !helpers.IsListenerNamespaceAllowed(listener, r.GetNamespace(), gatewayNamespace, namespaceLabels) {
1038970
continue
1039971
}
1040-
if l4RouteAttachesToListener(r.Spec.ParentRefs, listener) {
972+
if parentRefsMatchListener(r.Spec.ParentRefs, listener) {
1041973
attached = append(attached, r)
1042974
}
1043975
}
@@ -1096,7 +1028,7 @@ func toUDPRoutes(listener gatewayv1beta1.Listener,
10961028
if !namespacesPreFiltered && !helpers.IsListenerNamespaceAllowed(listener, r.GetNamespace(), gatewayNamespace, namespaceLabels) {
10971029
continue
10981030
}
1099-
if l4RouteAttachesToListener(r.Spec.ParentRefs, listener) {
1031+
if parentRefsMatchListener(r.Spec.ParentRefs, listener) {
11001032
attached = append(attached, r)
11011033
}
11021034
}

operator/pkg/model/ingestion/gateway_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,120 @@ func TestTLSGatewayAPIFiltersRoutesByListenerAllowedNamespaces(t *testing.T) {
469469
assert.Equal(t, "podinfo", m.TLSPassthrough[1].Routes[0].Backends[0].Name)
470470
}
471471

472+
func TestParentRefsMatchListener(t *testing.T) {
473+
listener := gatewayv1.Listener{
474+
Name: "http-listener",
475+
Port: 80,
476+
}
477+
478+
tests := []struct {
479+
name string
480+
parentRefs []gatewayv1.ParentReference
481+
want bool
482+
}{
483+
{
484+
name: "both fields nil matches listener",
485+
parentRefs: []gatewayv1.ParentReference{
486+
{SectionName: nil, Port: nil},
487+
},
488+
want: true,
489+
},
490+
{
491+
name: "sectionName matches and port is nil",
492+
parentRefs: []gatewayv1.ParentReference{
493+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: nil},
494+
},
495+
want: true,
496+
},
497+
{
498+
name: "sectionName mismatches and port is nil",
499+
parentRefs: []gatewayv1.ParentReference{
500+
{SectionName: ptr.To[gatewayv1.SectionName]("wrong-listener"), Port: nil},
501+
},
502+
want: false,
503+
},
504+
{
505+
name: "sectionName is nil and port matches",
506+
parentRefs: []gatewayv1.ParentReference{
507+
{SectionName: nil, Port: ptr.To[gatewayv1.PortNumber](80)},
508+
},
509+
want: true,
510+
},
511+
{
512+
name: "sectionName is nil and port mismatches",
513+
parentRefs: []gatewayv1.ParentReference{
514+
{SectionName: nil, Port: ptr.To[gatewayv1.PortNumber](443)},
515+
},
516+
want: false,
517+
},
518+
{
519+
name: "sectionName and port match",
520+
parentRefs: []gatewayv1.ParentReference{
521+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: ptr.To[gatewayv1.PortNumber](80)},
522+
},
523+
want: true,
524+
},
525+
{
526+
name: "sectionName matches and port mismatches",
527+
parentRefs: []gatewayv1.ParentReference{
528+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: ptr.To[gatewayv1.PortNumber](443)},
529+
},
530+
want: false,
531+
},
532+
{
533+
name: "sectionName mismatches and port matches",
534+
parentRefs: []gatewayv1.ParentReference{
535+
{SectionName: ptr.To[gatewayv1.SectionName]("wrong-listener"), Port: ptr.To[gatewayv1.PortNumber](80)},
536+
},
537+
want: false,
538+
},
539+
{
540+
name: "multiple parentRefs and second one matches",
541+
parentRefs: []gatewayv1.ParentReference{
542+
{SectionName: ptr.To[gatewayv1.SectionName]("wrong-listener"), Port: ptr.To[gatewayv1.PortNumber](443)},
543+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: ptr.To[gatewayv1.PortNumber](80)},
544+
},
545+
want: true,
546+
},
547+
{
548+
name: "multiple parentRefs and later port-only ref matches",
549+
parentRefs: []gatewayv1.ParentReference{
550+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: ptr.To[gatewayv1.PortNumber](443)},
551+
{SectionName: nil, Port: ptr.To[gatewayv1.PortNumber](80)},
552+
},
553+
want: true,
554+
},
555+
{
556+
name: "multiple parentRefs and none match",
557+
parentRefs: []gatewayv1.ParentReference{
558+
{SectionName: ptr.To[gatewayv1.SectionName]("wrong-listener"), Port: ptr.To[gatewayv1.PortNumber](80)},
559+
{SectionName: ptr.To[gatewayv1.SectionName]("http-listener"), Port: ptr.To[gatewayv1.PortNumber](443)},
560+
},
561+
want: false,
562+
},
563+
{
564+
name: "empty parentRefs list",
565+
parentRefs: []gatewayv1.ParentReference{},
566+
want: false,
567+
},
568+
}
569+
570+
for _, tt := range tests {
571+
t.Run(tt.name, func(t *testing.T) {
572+
got := parentRefsMatchListener(tt.parentRefs, listener)
573+
assert.Equal(t, tt.want, got)
574+
})
575+
}
576+
}
577+
472578
func TestTLSGatewayAPI(t *testing.T) {
473579
tests := map[string]struct{}{
474580
"basic tls http": {},
475581
"Conformance/TLSRouteSimpleSameNamespace": {},
476582
"Conformance/TLSRouteHostnameIntersection": {},
477583
"mixed protocol listeners TLSRoute": {},
478584
"tls weighted backends": {},
585+
"tls route parent ref filter": {},
479586
}
480587

481588
for name := range tests {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: gateway.networking.k8s.io/v1
2+
kind: Gateway
3+
metadata:
4+
creationTimestamp: null
5+
name: my-gateway
6+
namespace: default
7+
spec:
8+
gatewayClassName: ""
9+
listeners:
10+
- hostname: example.com
11+
name: tls-443
12+
port: 443
13+
protocol: TLS
14+
tls:
15+
mode: Passthrough
16+
- hostname: example.com
17+
name: tls-8443
18+
port: 8443
19+
protocol: TLS
20+
tls:
21+
mode: Passthrough
22+
status: {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- metadata:
2+
creationTimestamp: null
3+
name: my-service
4+
namespace: default
5+
spec: {}
6+
status:
7+
loadBalancer: {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
- metadata:
2+
creationTimestamp: null
3+
name: tls
4+
namespace: default
5+
spec:
6+
hostnames:
7+
- example.com
8+
parentRefs:
9+
- name: my-gateway
10+
namespace: default
11+
port: 443
12+
rules:
13+
- backendRefs:
14+
- name: my-service
15+
port: 443
16+
status:
17+
parents: null
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
- hostname: example.com
2+
name: tls-443
3+
port: 443
4+
routes:
5+
- backends:
6+
- name: my-service
7+
namespace: default
8+
port:
9+
port: 443
10+
hostnames:
11+
- example.com
12+
sources:
13+
- group: gateway.networking.k8s.io
14+
kind: Gateway
15+
name: my-gateway
16+
namespace: default
17+
version: v1
18+
- hostname: example.com
19+
name: tls-8443
20+
port: 8443
21+
sources:
22+
- group: gateway.networking.k8s.io
23+
kind: Gateway
24+
name: my-gateway
25+
namespace: default
26+
version: v1

0 commit comments

Comments
 (0)