@@ -224,6 +224,33 @@ type rewriteConfig struct {
224224 MainRewrite string
225225}
226226
227+ // extractMirrorTargetsWithPercentages extracts mirror targets and their percentages from path rules.
228+ func extractMirrorTargetsWithPercentages (pathRules []dataplane.PathRule ) map [string ]* float64 {
229+ mirrorTargets := make (map [string ]* float64 )
230+
231+ for _ , rule := range pathRules {
232+ for _ , matchRule := range rule .MatchRules {
233+ for _ , mirrorFilter := range matchRule .Filters .RequestMirrors {
234+ if mirrorFilter .Target != nil {
235+ if mirrorFilter .Percent == nil {
236+ mirrorTargets [* mirrorFilter .Target ] = helpers .GetPointer (100.0 )
237+ continue
238+ }
239+
240+ percentage := mirrorFilter .Percent
241+
242+ if _ , exists := mirrorTargets [* mirrorFilter .Target ]; ! exists ||
243+ * percentage > * mirrorTargets [* mirrorFilter .Target ] {
244+ mirrorTargets [* mirrorFilter .Target ] = percentage // set a higher percentage if it exists
245+ }
246+ }
247+ }
248+ }
249+ }
250+
251+ return mirrorTargets
252+ }
253+
227254type httpMatchPairs map [string ][]routeMatch
228255
229256func createLocations (
@@ -239,6 +266,8 @@ func createLocations(
239266 var rootPathExists bool
240267 var grpcServer bool
241268
269+ mirrorPathToPercentage := extractMirrorTargetsWithPercentages (server .PathRules )
270+
242271 for pathRuleIdx , rule := range server .PathRules {
243272 matches := make ([]routeMatch , 0 , len (rule .MatchRules ))
244273
@@ -250,6 +279,8 @@ func createLocations(
250279 grpcServer = true
251280 }
252281
282+ mirrorPercentage := mirrorPathToPercentage [rule .Path ]
283+
253284 extLocations := initializeExternalLocations (rule , pathsAndTypes )
254285 for i := range extLocations {
255286 extLocations [i ].Includes = createIncludesFromPolicyGenerateResult (
@@ -260,13 +291,12 @@ func createLocations(
260291 if ! needsInternalLocations (rule ) {
261292 for _ , r := range rule .MatchRules {
262293 extLocations = updateLocations (
263- r .Filters ,
264- extLocations ,
265294 r ,
295+ rule ,
296+ extLocations ,
266297 server .Port ,
267- rule .Path ,
268- rule .GRPC ,
269298 keepAliveCheck ,
299+ mirrorPercentage ,
270300 )
271301 }
272302
@@ -283,13 +313,12 @@ func createLocations(
283313 )
284314
285315 intLocation = updateLocation (
286- r .Filters ,
287- intLocation ,
288316 r ,
317+ rule ,
318+ intLocation ,
289319 server .Port ,
290- rule .Path ,
291- rule .GRPC ,
292320 keepAliveCheck ,
321+ mirrorPercentage ,
293322 )
294323
295324 internalLocations = append (internalLocations , intLocation )
@@ -420,38 +449,68 @@ func initializeInternalLocation(
420449
421450// updateLocation updates a location with any relevant configurations, like proxy_pass, filters, tls settings, etc.
422451func updateLocation (
423- filters dataplane.HTTPFilters ,
424- location http.Location ,
425452 matchRule dataplane.MatchRule ,
453+ pathRule dataplane.PathRule ,
454+ location http.Location ,
426455 listenerPort int32 ,
427- path string ,
428- grpc bool ,
429456 keepAliveCheck keepAliveChecker ,
457+ mirrorPercentage * float64 ,
430458) http.Location {
459+ filters := matchRule .Filters
460+ path := pathRule .Path
461+ grpc := pathRule .GRPC
462+
431463 if filters .InvalidFilter != nil {
432464 location .Return = & http.Return {Code : http .StatusInternalServerError }
433465 return location
434466 }
435467
468+ location = updateLocationMirrorRoute (location , path , grpc )
469+ location .Includes = append (location .Includes , createIncludesFromLocationSnippetsFilters (filters .SnippetsFilters )... )
470+
471+ if filters .RequestRedirect != nil {
472+ return updateLocationRedirectFilter (location , filters .RequestRedirect , listenerPort , path )
473+ }
474+
475+ location = updateLocationRewriteFilter (location , filters .RequestURLRewrite , path )
476+ location = updateLocationMirrorFilters (location , filters .RequestMirrors , path , mirrorPercentage )
477+ location = updateLocationProxySettings (location , matchRule , grpc , keepAliveCheck )
478+
479+ return location
480+ }
481+
482+ func updateLocationMirrorRoute (location http.Location , path string , grpc bool ) http.Location {
436483 if strings .HasPrefix (path , http .InternalMirrorRoutePathPrefix ) {
437484 location .Type = http .InternalLocationType
438485 if grpc {
439486 location .Rewrites = []string {"^ $request_uri break" }
440487 }
441488 }
442489
443- location .Includes = append (location .Includes , createIncludesFromLocationSnippetsFilters (filters .SnippetsFilters )... )
490+ return location
491+ }
444492
445- if filters .RequestRedirect != nil {
446- ret , rewrite := createReturnAndRewriteConfigForRedirectFilter (filters .RequestRedirect , listenerPort , path )
447- if rewrite .MainRewrite != "" {
448- location .Rewrites = append (location .Rewrites , rewrite .MainRewrite )
449- }
450- location .Return = ret
451- return location
493+ func updateLocationRedirectFilter (
494+ location http.Location ,
495+ redirectFilter * dataplane.HTTPRequestRedirectFilter ,
496+ listenerPort int32 ,
497+ path string ,
498+ ) http.Location {
499+ ret , rewrite := createReturnAndRewriteConfigForRedirectFilter (redirectFilter , listenerPort , path )
500+ if rewrite .MainRewrite != "" {
501+ location .Rewrites = append (location .Rewrites , rewrite .MainRewrite )
452502 }
503+ location .Return = ret
504+
505+ return location
506+ }
453507
454- rewrites := createRewritesValForRewriteFilter (filters .RequestURLRewrite , path )
508+ func updateLocationRewriteFilter (
509+ location http.Location ,
510+ rewriteFilter * dataplane.HTTPURLRewriteFilter ,
511+ path string ,
512+ ) http.Location {
513+ rewrites := createRewritesValForRewriteFilter (rewriteFilter , path )
455514 if rewrites != nil {
456515 if location .Type == http .InternalLocationType && rewrites .InternalRewrite != "" {
457516 location .Rewrites = append (location .Rewrites , rewrites .InternalRewrite )
@@ -461,12 +520,42 @@ func updateLocation(
461520 }
462521 }
463522
464- for _ , filter := range filters .RequestMirrors {
523+ return location
524+ }
525+
526+ func updateLocationMirrorFilters (
527+ location http.Location ,
528+ mirrorFilters []* dataplane.HTTPRequestMirrorFilter ,
529+ path string ,
530+ mirrorPercentage * float64 ,
531+ ) http.Location {
532+ for _ , filter := range mirrorFilters {
465533 if filter .Target != nil {
466534 location .MirrorPaths = append (location .MirrorPaths , * filter .Target )
467535 }
468536 }
469537
538+ if location .MirrorPaths != nil {
539+ location .MirrorPaths = deduplicateStrings (location .MirrorPaths )
540+ }
541+
542+ // if mirrorPercentage is nil (no mirror filter configured) or 100.0, the split clients variable is not generated,
543+ // and we let all traffic get mirrored.
544+ if mirrorPercentage != nil && * mirrorPercentage != 100.0 {
545+ location .MirrorSplitClientsVariableName = convertSplitClientVariableName (
546+ fmt .Sprintf ("%s_%.2f" , path , * mirrorPercentage ),
547+ )
548+ }
549+
550+ return location
551+ }
552+
553+ func updateLocationProxySettings (
554+ location http.Location ,
555+ matchRule dataplane.MatchRule ,
556+ grpc bool ,
557+ keepAliveCheck keepAliveChecker ,
558+ ) http.Location {
470559 extraHeaders := make ([]http.Header , 0 , 3 )
471560 if grpc {
472561 extraHeaders = append (extraHeaders , grpcAuthorityHeader )
@@ -497,18 +586,24 @@ func updateLocation(
497586// updateLocations updates the existing locations with any relevant configurations, like proxy_pass,
498587// filters, tls settings, etc.
499588func updateLocations (
500- filters dataplane.HTTPFilters ,
501- buildLocations []http.Location ,
502589 matchRule dataplane.MatchRule ,
590+ pathRule dataplane.PathRule ,
591+ buildLocations []http.Location ,
503592 listenerPort int32 ,
504- path string ,
505- grpc bool ,
506593 keepAliveCheck keepAliveChecker ,
594+ mirrorPercentage * float64 ,
507595) []http.Location {
508596 updatedLocations := make ([]http.Location , len (buildLocations ))
509597
510598 for i , loc := range buildLocations {
511- updatedLocations [i ] = updateLocation (filters , loc , matchRule , listenerPort , path , grpc , keepAliveCheck )
599+ updatedLocations [i ] = updateLocation (
600+ matchRule ,
601+ pathRule ,
602+ loc ,
603+ listenerPort ,
604+ keepAliveCheck ,
605+ mirrorPercentage ,
606+ )
512607 }
513608
514609 return updatedLocations
@@ -962,3 +1057,18 @@ func getConnectionHeader(keepAliveCheck keepAliveChecker, backends []dataplane.B
9621057
9631058 return httpConnectionHeader
9641059}
1060+
1061+ // deduplicateStrings removes duplicate strings from a slice while preserving order.
1062+ func deduplicateStrings (content []string ) []string {
1063+ seen := make (map [string ]struct {})
1064+ result := make ([]string , 0 , len (content ))
1065+
1066+ for _ , str := range content {
1067+ if _ , exists := seen [str ]; ! exists {
1068+ seen [str ] = struct {}{}
1069+ result = append (result , str )
1070+ }
1071+ }
1072+
1073+ return result
1074+ }
0 commit comments