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,
0 commit comments