Skip to content

Commit 92aa64a

Browse files
committed
Check all route types vs only HTTPRoute
Signed-off-by: jukie <isaac.wilson514@gmail.com>
1 parent a45a09a commit 92aa64a

4 files changed

Lines changed: 140 additions & 51 deletions

File tree

internal/gatewayapi/route.go

Lines changed: 119 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"net"
12+
"sort"
1213
"strconv"
1314
"strings"
1415
"time"
@@ -179,7 +180,7 @@ func (t *Translator) processHTTPRouteParentRefs(httpRoute *HTTPRouteContext, res
179180
"Resolved all the Object references for the Route",
180181
)
181182
}
182-
hasHostnameIntersection, hasOverlap := t.processHTTPRouteParentRefListener(httpRoute, routeRoutes, parentRef, xdsIR)
183+
hasHostnameIntersection := t.processHTTPRouteParentRefListener(httpRoute, routeRoutes, parentRef, xdsIR)
183184
if !hasHostnameIntersection {
184185
routeStatus := GetRouteStatus(httpRoute)
185186
status.SetRouteStatusCondition(routeStatus,
@@ -192,18 +193,6 @@ func (t *Translator) processHTTPRouteParentRefs(httpRoute *HTTPRouteContext, res
192193
)
193194
}
194195

195-
if hasOverlap {
196-
routeStatus := GetRouteStatus(httpRoute)
197-
status.SetRouteStatusCondition(routeStatus,
198-
parentRef.routeParentStatusIdx,
199-
httpRoute.GetGeneration(),
200-
gwapiv1.RouteConditionResolvedRefs,
201-
metav1.ConditionFalse,
202-
status.RouteReasonOverlap,
203-
"Overlapping match conditions with another HTTPRoute on the same listener and hostname",
204-
)
205-
}
206-
207196
// Skip parent refs that did not accept the route
208197
if parentRef.HasCondition(httpRoute, gwapiv1.RouteConditionAccepted, metav1.ConditionFalse) {
209198
continue
@@ -920,7 +909,7 @@ func (t *Translator) processGRPCRouteParentRefs(grpcRoute *GRPCRouteContext, res
920909
if parentRef.HasCondition(grpcRoute, gwapiv1.RouteConditionAccepted, metav1.ConditionFalse) {
921910
continue
922911
}
923-
hasHostnameIntersection, hasOverlap := t.processHTTPRouteParentRefListener(grpcRoute, routeRoutes, parentRef, xdsIR)
912+
hasHostnameIntersection := t.processHTTPRouteParentRefListener(grpcRoute, routeRoutes, parentRef, xdsIR)
924913
if !hasHostnameIntersection {
925914
routeStatus := GetRouteStatus(grpcRoute)
926915
status.SetRouteStatusCondition(routeStatus,
@@ -933,18 +922,6 @@ func (t *Translator) processGRPCRouteParentRefs(grpcRoute *GRPCRouteContext, res
933922
)
934923
}
935924

936-
if hasOverlap {
937-
routeStatus := GetRouteStatus(grpcRoute)
938-
status.SetRouteStatusCondition(routeStatus,
939-
parentRef.routeParentStatusIdx,
940-
grpcRoute.GetGeneration(),
941-
gwapiv1.RouteConditionResolvedRefs,
942-
metav1.ConditionFalse,
943-
status.RouteReasonOverlap,
944-
"Overlapping match conditions with another GRPCRoute on the same listener and hostname",
945-
)
946-
}
947-
948925
// If no negative conditions have been set, the route is considered "Accepted=True".
949926
if parentRef.GRPCRoute != nil &&
950927
len(parentRef.GRPCRoute.Status.Parents[parentRef.routeParentStatusIdx].Conditions) == 0 {
@@ -1269,10 +1246,9 @@ func (t *Translator) processGRPCRouteMethodRegularExpression(method *gwapiv1.GRP
12691246
}
12701247
}
12711248

1272-
func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, routeRoutes []*ir.HTTPRoute, parentRef *RouteParentContext, xdsIR resource.XdsIRMap) (bool, bool) {
1249+
func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, routeRoutes []*ir.HTTPRoute, parentRef *RouteParentContext, xdsIR resource.XdsIRMap) bool {
12731250
// need to check hostname intersection if there are listeners
12741251
hasHostnameIntersection := len(parentRef.listeners) == 0
1275-
var hasOverlap bool
12761252

12771253
for _, listener := range parentRef.listeners {
12781254
hosts := computeHosts(GetHostnames(route), listener)
@@ -1328,31 +1304,130 @@ func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, route
13281304
irListener.GRPC.EnableGRPCStats = ptr.To(true)
13291305
}
13301306

1331-
// Check for overlapping route matches before adding new routes.
1332-
// Two routes overlap when they are from different route resources
1333-
// but have identical match conditions on the same hostname.
1334-
routePrefix := irRoutePrefix(route)
1335-
for _, newRoute := range perHostRoutes {
1336-
for _, existingRoute := range irListener.Routes {
1337-
if strings.HasPrefix(existingRoute.Name, routePrefix) {
1338-
// Same route resource, skip.
1307+
irListener.Routes = append(irListener.Routes, perHostRoutes...)
1308+
}
1309+
}
1310+
1311+
return hasHostnameIntersection
1312+
}
1313+
1314+
// checkRouteOverlaps detects overlapping route matches across all IR listeners
1315+
// and sets a warning Overlap condition on the affected HTTPRoutes.
1316+
// This runs once after all routes are processed, checking each listener's routes
1317+
// for duplicates from different HTTPRoute resources.
1318+
// routeKey returns a "namespace/name" key for a route resource metadata.
1319+
func routeKey(namespace, name string) string {
1320+
return namespace + "/" + name
1321+
}
1322+
1323+
// checkRouteOverlaps detects overlapping route matches across all IR listeners
1324+
// and sets a warning Overlap condition on the affected HTTPRoutes and GRPCRoutes.
1325+
// This runs once after all routes are processed, rather than inside the per-route
1326+
// processing loop where it would execute N times.
1327+
func (t *Translator) checkRouteOverlaps(httpRoutes []*HTTPRouteContext, grpcRoutes []*GRPCRouteContext, xdsIR resource.XdsIRMap) {
1328+
// Build a combined lookup from "namespace/name" to RouteContext and its ParentRefs.
1329+
type routeInfo struct {
1330+
route RouteContext
1331+
parentRefs map[gwapiv1.ParentReference]*RouteParentContext
1332+
}
1333+
routeByKey := make(map[string]*routeInfo, len(httpRoutes)+len(grpcRoutes))
1334+
for _, hr := range httpRoutes {
1335+
routeByKey[routeKey(hr.GetNamespace(), hr.GetName())] = &routeInfo{route: hr, parentRefs: hr.ParentRefs}
1336+
}
1337+
for _, gr := range grpcRoutes {
1338+
routeByKey[routeKey(gr.GetNamespace(), gr.GetName())] = &routeInfo{route: gr, parentRefs: gr.ParentRefs}
1339+
}
1340+
1341+
// overlaps tracks per IR listener which routes overlap with which others.
1342+
// Key: IR listener name -> route key -> set of conflicting route keys.
1343+
type listenerOverlaps map[string]map[string]struct{}
1344+
overlaps := make(map[string]listenerOverlaps)
1345+
1346+
for _, xds := range xdsIR {
1347+
for _, httpListener := range xds.HTTP {
1348+
routes := httpListener.Routes
1349+
for i := 0; i < len(routes); i++ {
1350+
if routes[i].Metadata == nil {
1351+
continue
1352+
}
1353+
aKey := routeKey(routes[i].Metadata.Namespace, routes[i].Metadata.Name)
1354+
for j := i + 1; j < len(routes); j++ {
1355+
if routes[j].Metadata == nil {
13391356
continue
13401357
}
1341-
if routeMatchesOverlap(existingRoute, newRoute) {
1342-
hasOverlap = true
1343-
break
1358+
bKey := routeKey(routes[j].Metadata.Namespace, routes[j].Metadata.Name)
1359+
if aKey == bKey {
1360+
continue
1361+
}
1362+
if routeMatchesOverlap(routes[i], routes[j]) {
1363+
if overlaps[httpListener.Name] == nil {
1364+
overlaps[httpListener.Name] = make(listenerOverlaps)
1365+
}
1366+
lo := overlaps[httpListener.Name]
1367+
if lo[aKey] == nil {
1368+
lo[aKey] = make(map[string]struct{})
1369+
}
1370+
if lo[bKey] == nil {
1371+
lo[bKey] = make(map[string]struct{})
1372+
}
1373+
lo[aKey][bKey] = struct{}{}
1374+
lo[bKey][aKey] = struct{}{}
13441375
}
13451376
}
1346-
if hasOverlap {
1347-
break
1377+
}
1378+
}
1379+
}
1380+
1381+
// Set the Overlap warning condition only on parentRefs whose listeners
1382+
// match an IR listener where the overlap was detected.
1383+
for rKey, info := range routeByKey {
1384+
// Collect all conflicts for this route across the parentRefs that have overlaps.
1385+
for _, parentRef := range info.parentRefs {
1386+
var conflicts map[string]struct{}
1387+
for _, listener := range parentRef.listeners {
1388+
irKey := t.getIRKey(listener.gateway.Gateway)
1389+
irListener := xdsIR[irKey].GetHTTPListener(irListenerName(listener))
1390+
if irListener == nil {
1391+
continue
1392+
}
1393+
lo, ok := overlaps[irListener.Name]
1394+
if !ok {
1395+
continue
1396+
}
1397+
routeConflicts, ok := lo[rKey]
1398+
if !ok {
1399+
continue
13481400
}
1401+
if conflicts == nil {
1402+
conflicts = make(map[string]struct{})
1403+
}
1404+
for c := range routeConflicts {
1405+
conflicts[c] = struct{}{}
1406+
}
1407+
}
1408+
if len(conflicts) == 0 {
1409+
continue
13491410
}
13501411

1351-
irListener.Routes = append(irListener.Routes, perHostRoutes...)
1412+
conflictNames := make([]string, 0, len(conflicts))
1413+
for name := range conflicts {
1414+
conflictNames = append(conflictNames, name)
1415+
}
1416+
sort.Strings(conflictNames)
1417+
1418+
msg := fmt.Sprintf("Overlapping match conditions with route(s): %s", strings.Join(conflictNames, ", "))
1419+
1420+
routeStatus := GetRouteStatus(info.route)
1421+
status.SetRouteStatusCondition(routeStatus,
1422+
parentRef.routeParentStatusIdx,
1423+
info.route.GetGeneration(),
1424+
status.RouteConditionOverlap,
1425+
metav1.ConditionTrue,
1426+
status.RouteReasonOverlap,
1427+
msg,
1428+
)
13521429
}
13531430
}
1354-
1355-
return hasHostnameIntersection, hasOverlap
13561431
}
13571432

13581433
// routeMatchesOverlap checks if two IR HTTP routes have identical match conditions,

internal/gatewayapi/status/error.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ const (
3131
RouteReasonInvalidAddress gwapiv1.RouteConditionReason = "InvalidAddress"
3232
RouteReasonEndpointsNotFound gwapiv1.RouteConditionReason = "EndpointsNotFound"
3333

34-
// Route overlap reason
35-
RouteReasonOverlap gwapiv1.RouteConditionReason = "Overlap"
34+
// Route overlap reason and condition type
35+
RouteReasonOverlap gwapiv1.RouteConditionReason = "Overlap"
36+
RouteConditionOverlap gwapiv1.RouteConditionType = "Overlap"
3637

3738
// Network configuration related condition types
3839
RouteConditionBackendsAvailable gwapiv1.RouteConditionType = "BackendsAvailable"

internal/gatewayapi/testdata/httproute-overlapping-matches.out.yaml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ httpRoutes:
6969
reason: ResolvedRefs
7070
status: "True"
7171
type: ResolvedRefs
72+
- lastTransitionTime: null
73+
message: 'Overlapping match conditions with route(s): default/httproute-2'
74+
reason: Overlap
75+
status: "True"
76+
type: Overlap
7277
controllerName: gateway.envoyproxy.io/gatewayclass-controller
7378
parentRef:
7479
name: gateway-1
@@ -99,11 +104,15 @@ httpRoutes:
99104
status: "True"
100105
type: Accepted
101106
- lastTransitionTime: null
102-
message: Overlapping match conditions with another HTTPRoute on the same listener
103-
and hostname
104-
reason: Overlap
105-
status: "False"
107+
message: Resolved all the Object references for the Route
108+
reason: ResolvedRefs
109+
status: "True"
106110
type: ResolvedRefs
111+
- lastTransitionTime: null
112+
message: 'Overlapping match conditions with route(s): default/httproute-1'
113+
reason: Overlap
114+
status: "True"
115+
type: Overlap
107116
controllerName: gateway.envoyproxy.io/gatewayclass-controller
108117
parentRef:
109118
name: gateway-1
@@ -158,7 +167,6 @@ xdsIR:
158167
externalPort: 80
159168
hostnames:
160169
- '*'
161-
isHTTP2: false
162170
metadata:
163171
kind: Gateway
164172
name: gateway-1

internal/gatewayapi/translator.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,11 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult,
286286
// Process all relevant GRPCRoutes.
287287
grpcRoutes := t.ProcessGRPCRoutes(resources.GRPCRoutes, acceptedGateways, resources, xdsIR)
288288

289+
// Check for overlapping route matches across all listeners after all HTTP and
290+
// GRPC routes have been processed. This runs once rather than inside the
291+
// per-route processing loop where it would execute N times.
292+
t.checkRouteOverlaps(httpRoutes, grpcRoutes, xdsIR)
293+
289294
// Process all relevant TLSRoutes.
290295
tlsRoutes := t.ProcessTLSRoutes(resources.TLSRoutes, acceptedGateways, resources, xdsIR)
291296

0 commit comments

Comments
 (0)