@@ -224,6 +224,33 @@ 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 ] = 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
+
227
254
type httpMatchPairs map [string ][]routeMatch
228
255
229
256
func createLocations (
@@ -239,6 +266,8 @@ func createLocations(
239
266
var rootPathExists bool
240
267
var grpcServer bool
241
268
269
+ mirrorPathToPercentage := extractMirrorTargetsWithPercentages (server .PathRules )
270
+
242
271
for pathRuleIdx , rule := range server .PathRules {
243
272
matches := make ([]routeMatch , 0 , len (rule .MatchRules ))
244
273
@@ -250,6 +279,8 @@ func createLocations(
250
279
grpcServer = true
251
280
}
252
281
282
+ mirrorPercentage := mirrorPathToPercentage [rule .Path ]
283
+
253
284
extLocations := initializeExternalLocations (rule , pathsAndTypes )
254
285
for i := range extLocations {
255
286
extLocations [i ].Includes = createIncludesFromPolicyGenerateResult (
@@ -260,13 +291,12 @@ func createLocations(
260
291
if ! needsInternalLocations (rule ) {
261
292
for _ , r := range rule .MatchRules {
262
293
extLocations = updateLocations (
263
- r .Filters ,
264
- extLocations ,
265
294
r ,
295
+ rule ,
296
+ extLocations ,
266
297
server .Port ,
267
- rule .Path ,
268
- rule .GRPC ,
269
298
keepAliveCheck ,
299
+ mirrorPercentage ,
270
300
)
271
301
}
272
302
@@ -283,13 +313,12 @@ func createLocations(
283
313
)
284
314
285
315
intLocation = updateLocation (
286
- r .Filters ,
287
- intLocation ,
288
316
r ,
317
+ rule ,
318
+ intLocation ,
289
319
server .Port ,
290
- rule .Path ,
291
- rule .GRPC ,
292
320
keepAliveCheck ,
321
+ mirrorPercentage ,
293
322
)
294
323
295
324
internalLocations = append (internalLocations , intLocation )
@@ -420,38 +449,68 @@ func initializeInternalLocation(
420
449
421
450
// updateLocation updates a location with any relevant configurations, like proxy_pass, filters, tls settings, etc.
422
451
func updateLocation (
423
- filters dataplane.HTTPFilters ,
424
- location http.Location ,
425
452
matchRule dataplane.MatchRule ,
453
+ pathRule dataplane.PathRule ,
454
+ location http.Location ,
426
455
listenerPort int32 ,
427
- path string ,
428
- grpc bool ,
429
456
keepAliveCheck keepAliveChecker ,
457
+ mirrorPercentage * float64 ,
430
458
) http.Location {
459
+ filters := matchRule .Filters
460
+ path := pathRule .Path
461
+ grpc := pathRule .GRPC
462
+
431
463
if filters .InvalidFilter != nil {
432
464
location .Return = & http.Return {Code : http .StatusInternalServerError }
433
465
return location
434
466
}
435
467
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 {
436
483
if strings .HasPrefix (path , http .InternalMirrorRoutePathPrefix ) {
437
484
location .Type = http .InternalLocationType
438
485
if grpc {
439
486
location .Rewrites = []string {"^ $request_uri break" }
440
487
}
441
488
}
442
489
443
- location .Includes = append (location .Includes , createIncludesFromLocationSnippetsFilters (filters .SnippetsFilters )... )
490
+ return location
491
+ }
444
492
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 )
452
502
}
503
+ location .Return = ret
504
+
505
+ return location
506
+ }
453
507
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 )
455
514
if rewrites != nil {
456
515
if location .Type == http .InternalLocationType && rewrites .InternalRewrite != "" {
457
516
location .Rewrites = append (location .Rewrites , rewrites .InternalRewrite )
@@ -461,12 +520,42 @@ func updateLocation(
461
520
}
462
521
}
463
522
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 {
465
533
if filter .Target != nil {
466
534
location .MirrorPaths = append (location .MirrorPaths , * filter .Target )
467
535
}
468
536
}
469
537
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 {
470
559
extraHeaders := make ([]http.Header , 0 , 3 )
471
560
if grpc {
472
561
extraHeaders = append (extraHeaders , grpcAuthorityHeader )
@@ -497,18 +586,24 @@ func updateLocation(
497
586
// updateLocations updates the existing locations with any relevant configurations, like proxy_pass,
498
587
// filters, tls settings, etc.
499
588
func updateLocations (
500
- filters dataplane.HTTPFilters ,
501
- buildLocations []http.Location ,
502
589
matchRule dataplane.MatchRule ,
590
+ pathRule dataplane.PathRule ,
591
+ buildLocations []http.Location ,
503
592
listenerPort int32 ,
504
- path string ,
505
- grpc bool ,
506
593
keepAliveCheck keepAliveChecker ,
594
+ mirrorPercentage * float64 ,
507
595
) []http.Location {
508
596
updatedLocations := make ([]http.Location , len (buildLocations ))
509
597
510
598
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
+ )
512
607
}
513
608
514
609
return updatedLocations
@@ -962,3 +1057,18 @@ func getConnectionHeader(keepAliveCheck keepAliveChecker, backends []dataplane.B
962
1057
963
1058
return httpConnectionHeader
964
1059
}
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