@@ -224,6 +224,32 @@ type rewriteConfig struct {
224
224
MainRewrite string
225
225
}
226
226
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 ] = 100.0
237
+ continue
238
+ }
239
+
240
+ percentage := * mirrorFilter .Percent
241
+
242
+ if _ , exists := mirrorTargets [* mirrorFilter .Target ]; ! exists || percentage > mirrorTargets [* mirrorFilter .Target ] {
243
+ mirrorTargets [* mirrorFilter .Target ] = percentage // set a higher percentage if it exists
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ return mirrorTargets
251
+ }
252
+
227
253
type httpMatchPairs map [string ][]routeMatch
228
254
229
255
func createLocations (
@@ -239,6 +265,8 @@ func createLocations(
239
265
var rootPathExists bool
240
266
var grpcServer bool
241
267
268
+ mirrorPathToPercentage := extractMirrorTargetsWithPercentages (server .PathRules )
269
+
242
270
for pathRuleIdx , rule := range server .PathRules {
243
271
matches := make ([]routeMatch , 0 , len (rule .MatchRules ))
244
272
@@ -250,6 +278,11 @@ func createLocations(
250
278
grpcServer = true
251
279
}
252
280
281
+ mirrorPercentage , exists := mirrorPathToPercentage [rule .Path ]
282
+ if ! exists {
283
+ mirrorPercentage = - 1
284
+ }
285
+
253
286
extLocations := initializeExternalLocations (rule , pathsAndTypes )
254
287
for i := range extLocations {
255
288
extLocations [i ].Includes = createIncludesFromPolicyGenerateResult (
@@ -267,6 +300,7 @@ func createLocations(
267
300
rule .Path ,
268
301
rule .GRPC ,
269
302
keepAliveCheck ,
303
+ mirrorPercentage ,
270
304
)
271
305
}
272
306
@@ -290,6 +324,7 @@ func createLocations(
290
324
rule .Path ,
291
325
rule .GRPC ,
292
326
keepAliveCheck ,
327
+ mirrorPercentage ,
293
328
)
294
329
295
330
internalLocations = append (internalLocations , intLocation )
@@ -427,31 +462,59 @@ func updateLocation(
427
462
path string ,
428
463
grpc bool ,
429
464
keepAliveCheck keepAliveChecker ,
465
+ mirrorPercentage float64 ,
430
466
) http.Location {
431
467
if filters .InvalidFilter != nil {
432
468
location .Return = & http.Return {Code : http .StatusInternalServerError }
433
469
return location
434
470
}
435
471
472
+ location = updateLocationMirrorRoute (location , path , grpc )
473
+ location .Includes = append (location .Includes , createIncludesFromLocationSnippetsFilters (filters .SnippetsFilters )... )
474
+
475
+ if filters .RequestRedirect != nil {
476
+ return updateLocationRedirectFilter (location , filters .RequestRedirect , listenerPort , path )
477
+ }
478
+
479
+ location = updateLocationRewriteFilter (location , filters .RequestURLRewrite , path )
480
+ location = updateLocationMirrorFilters (location , filters .RequestMirrors , path , mirrorPercentage )
481
+ location = updateLocationProxySettings (location , matchRule , grpc , keepAliveCheck )
482
+
483
+ return location
484
+ }
485
+
486
+ func updateLocationMirrorRoute (location http.Location , path string , grpc bool ) http.Location {
436
487
if strings .HasPrefix (path , http .InternalMirrorRoutePathPrefix ) {
437
488
location .Type = http .InternalLocationType
438
489
if grpc {
439
490
location .Rewrites = []string {"^ $request_uri break" }
440
491
}
441
492
}
442
493
443
- location .Includes = append (location .Includes , createIncludesFromLocationSnippetsFilters (filters .SnippetsFilters )... )
494
+ return location
495
+ }
444
496
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
497
+ func updateLocationRedirectFilter (
498
+ location http.Location ,
499
+ redirectFilter * dataplane.HTTPRequestRedirectFilter ,
500
+ listenerPort int32 ,
501
+ path string ,
502
+ ) http.Location {
503
+ ret , rewrite := createReturnAndRewriteConfigForRedirectFilter (redirectFilter , listenerPort , path )
504
+ if rewrite .MainRewrite != "" {
505
+ location .Rewrites = append (location .Rewrites , rewrite .MainRewrite )
452
506
}
507
+ location .Return = ret
453
508
454
- rewrites := createRewritesValForRewriteFilter (filters .RequestURLRewrite , path )
509
+ return location
510
+ }
511
+
512
+ func updateLocationRewriteFilter (
513
+ location http.Location ,
514
+ rewriteFilter * dataplane.HTTPURLRewriteFilter ,
515
+ path string ,
516
+ ) http.Location {
517
+ rewrites := createRewritesValForRewriteFilter (rewriteFilter , path )
455
518
if rewrites != nil {
456
519
if location .Type == http .InternalLocationType && rewrites .InternalRewrite != "" {
457
520
location .Rewrites = append (location .Rewrites , rewrites .InternalRewrite )
@@ -461,12 +524,42 @@ func updateLocation(
461
524
}
462
525
}
463
526
464
- for _ , filter := range filters .RequestMirrors {
527
+ return location
528
+ }
529
+
530
+ func updateLocationMirrorFilters (
531
+ location http.Location ,
532
+ mirrorFilters []* dataplane.HTTPRequestMirrorFilter ,
533
+ path string ,
534
+ mirrorPercentage float64 ,
535
+ ) http.Location {
536
+ for _ , filter := range mirrorFilters {
465
537
if filter .Target != nil {
466
538
location .MirrorPaths = append (location .MirrorPaths , * filter .Target )
467
539
}
468
540
}
469
541
542
+ if location .MirrorPaths != nil {
543
+ location .MirrorPaths = deduplicateStrings (location .MirrorPaths )
544
+ }
545
+
546
+ // if the mirrorPercentage is 100.0 or negative (we set it to negative when there is no mirror filter because 0.0
547
+ // is valid), the split clients variable is not generated, and we want to let all the traffic get mirrored.
548
+ if mirrorPercentage != 100.0 && mirrorPercentage >= 0.0 {
549
+ location .MirrorSplitClientsVariableName = convertSplitClientVariableName (
550
+ fmt .Sprintf ("%s_%.2f" , path , mirrorPercentage ),
551
+ )
552
+ }
553
+
554
+ return location
555
+ }
556
+
557
+ func updateLocationProxySettings (
558
+ location http.Location ,
559
+ matchRule dataplane.MatchRule ,
560
+ grpc bool ,
561
+ keepAliveCheck keepAliveChecker ,
562
+ ) http.Location {
470
563
extraHeaders := make ([]http.Header , 0 , 3 )
471
564
if grpc {
472
565
extraHeaders = append (extraHeaders , grpcAuthorityHeader )
@@ -504,11 +597,21 @@ func updateLocations(
504
597
path string ,
505
598
grpc bool ,
506
599
keepAliveCheck keepAliveChecker ,
600
+ mirrorPercentage float64 ,
507
601
) []http.Location {
508
602
updatedLocations := make ([]http.Location , len (buildLocations ))
509
603
510
604
for i , loc := range buildLocations {
511
- updatedLocations [i ] = updateLocation (filters , loc , matchRule , listenerPort , path , grpc , keepAliveCheck )
605
+ updatedLocations [i ] = updateLocation (
606
+ filters ,
607
+ loc ,
608
+ matchRule ,
609
+ listenerPort ,
610
+ path ,
611
+ grpc ,
612
+ keepAliveCheck ,
613
+ mirrorPercentage ,
614
+ )
512
615
}
513
616
514
617
return updatedLocations
@@ -962,3 +1065,18 @@ func getConnectionHeader(keepAliveCheck keepAliveChecker, backends []dataplane.B
962
1065
963
1066
return httpConnectionHeader
964
1067
}
1068
+
1069
+ // deduplicateStrings removes duplicate strings from a slice while preserving order.
1070
+ func deduplicateStrings (content []string ) []string {
1071
+ seen := make (map [string ]bool )
1072
+ result := make ([]string , 0 , len (content ))
1073
+
1074
+ for _ , str := range content {
1075
+ if ! seen [str ] {
1076
+ seen [str ] = true
1077
+ result = append (result , str )
1078
+ }
1079
+ }
1080
+
1081
+ return result
1082
+ }
0 commit comments