@@ -559,7 +559,8 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
559559 for _ , listener := range parentRefCtx .listeners {
560560 irListener := xdsIR [irKey ].GetHTTPListener (irListenerName (listener ))
561561 if irListener != nil {
562- for _ , r := range irListener .Routes {
562+ var fallbackRoutes []* ir.HTTPRoute
563+ for i , r := range irListener .Routes {
563564 // If specified the sectionName must match route rule from ir route metadata.
564565 if target .SectionName != nil && string (* target .SectionName ) != r .Metadata .SectionName {
565566 continue
@@ -600,9 +601,16 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
600601 Luas : luas ,
601602 DynamicModules : dynamicModules ,
602603 }
604+ if extensionsUsePercentage (r .EnvoyExtensions ) {
605+ fallback := irListener .Routes [i ].DeepCopy ()
606+ fallback .EnvoyExtensions = & ir.EnvoyExtensionFeatures {}
607+ fallback .Name = r .Name + "/fallback"
608+ fallbackRoutes = append (fallbackRoutes , fallback )
609+ }
603610 }
604611 }
605612 }
613+ appendFallbackRoutes (irListener , fallbackRoutes )
606614 }
607615 }
608616 }
@@ -670,7 +678,8 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
670678
671679 // A Policy targeting the specific scope(xRoute rule, xRoute, Gateway listener) wins over a policy
672680 // targeting a lesser specific scope(Gateway).
673- for _ , r := range http .Routes {
681+ var fallbackRoutes []* ir.HTTPRoute
682+ for i , r := range http .Routes {
674683 // if already set - there's a specific level policy, so skip
675684 if r .EnvoyExtensions != nil {
676685 continue
@@ -703,8 +712,15 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
703712 Luas : luas ,
704713 DynamicModules : dynamicModules ,
705714 }
715+ if extensionsUsePercentage (r .EnvoyExtensions ) {
716+ fallback := http .Routes [i ].DeepCopy ()
717+ fallback .EnvoyExtensions = & ir.EnvoyExtensionFeatures {}
718+ fallback .Name = r .Name + "/fallback"
719+ fallbackRoutes = append (fallbackRoutes , fallback )
720+ }
706721 }
707722 }
723+ appendFallbackRoutes (http , fallbackRoutes )
708724 }
709725 if len (routesWithDirectResponse ) > 0 {
710726 t .Logger .Info ("setting 500 direct response in routes due to errors in EnvoyExtensionPolicy" ,
@@ -764,8 +780,9 @@ func (t *Translator) buildLua(
764780 return nil , fmt .Errorf ("validation failed for lua body in policy with name %v: %w" , name , err )
765781 }
766782 return & ir.Lua {
767- Name : name ,
768- Code : luaCode ,
783+ Name : name ,
784+ Code : luaCode ,
785+ Percentage : lua .Percentage ,
769786 }, nil
770787}
771788
@@ -808,7 +825,8 @@ func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resource
808825 extProcIRList := make ([]ir.ExtProc , 0 , len (policy .Spec .ExtProc ))
809826
810827 hasFailClose := false
811- for idx , ep := range policy .Spec .ExtProc {
828+ for idx := range policy .Spec .ExtProc {
829+ ep := & policy .Spec .ExtProc [idx ]
812830 name := irConfigNameForExtProc (policy , idx )
813831 extProcIR , err := t .buildExtProc (name , policy , ep , idx , resources , gtwCtx )
814832 if err != nil {
@@ -831,7 +849,7 @@ func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resource
831849func (t * Translator ) buildExtProc (
832850 name string ,
833851 policy * egv1a1.EnvoyExtensionPolicy ,
834- extProc egv1a1.ExtProc ,
852+ extProc * egv1a1.ExtProc ,
835853 extProcIdx int ,
836854 resources * resource.Resources ,
837855 gtwCtx * GatewayContext ,
@@ -869,6 +887,7 @@ func (t *Translator) buildExtProc(
869887 Destination : * rd ,
870888 Traffic : traffic ,
871889 Authority : authority ,
890+ Percentage : extProc .Percentage ,
872891 }
873892
874893 if extProc .MessageTimeout != nil {
@@ -1148,12 +1167,13 @@ func (t *Translator) buildWasm(
11481167 wasmName = * config .Name
11491168 }
11501169 wasmIR := & ir.Wasm {
1151- Name : name ,
1152- RootID : config .RootID ,
1153- WasmName : wasmName ,
1154- Config : config .Config ,
1155- FailOpen : failOpen ,
1156- Code : code ,
1170+ Name : name ,
1171+ RootID : config .RootID ,
1172+ WasmName : wasmName ,
1173+ Config : config .Config ,
1174+ FailOpen : failOpen ,
1175+ Code : code ,
1176+ Percentage : config .Percentage ,
11571177 }
11581178
11591179 if config .Env != nil && len (config .Env .HostKeys ) > 0 {
@@ -1268,6 +1288,51 @@ func (t *Translator) buildDynamicModules(
12681288 return dmIRList , errs
12691289}
12701290
1291+ // appendFallbackRoutes inserts each fallback route immediately after its
1292+ // corresponding main route (identified by name prefix) so that Envoy evaluates
1293+ // the main route (with RuntimeFraction) before the fallback.
1294+ func appendFallbackRoutes (irListener * ir.HTTPListener , fallbacks []* ir.HTTPRoute ) {
1295+ if len (fallbacks ) == 0 {
1296+ return
1297+ }
1298+ fallbackByMain := make (map [string ]* ir.HTTPRoute , len (fallbacks ))
1299+ for _ , fb := range fallbacks {
1300+ mainName := strings .TrimSuffix (fb .Name , "/fallback" )
1301+ fallbackByMain [mainName ] = fb
1302+ }
1303+
1304+ newRoutes := make ([]* ir.HTTPRoute , 0 , len (irListener .Routes )+ len (fallbacks ))
1305+ for _ , r := range irListener .Routes {
1306+ newRoutes = append (newRoutes , r )
1307+ if fb , ok := fallbackByMain [r .Name ]; ok {
1308+ newRoutes = append (newRoutes , fb )
1309+ }
1310+ }
1311+ irListener .Routes = newRoutes
1312+ }
1313+
1314+ func extensionsUsePercentage (exts * ir.EnvoyExtensionFeatures ) bool {
1315+ if exts == nil {
1316+ return false
1317+ }
1318+ for i := range exts .ExtProcs {
1319+ if exts .ExtProcs [i ].Percentage != nil {
1320+ return true
1321+ }
1322+ }
1323+ for i := range exts .Wasms {
1324+ if exts .Wasms [i ].Percentage != nil {
1325+ return true
1326+ }
1327+ }
1328+ for i := range exts .Luas {
1329+ if exts .Luas [i ].Percentage != nil {
1330+ return true
1331+ }
1332+ }
1333+ return false
1334+ }
1335+
12711336// envoyExtensionPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
12721337// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
12731338func envoyExtensionPolicyCopiesWithStatusDeepCopy (policies []* egv1a1.EnvoyExtensionPolicy ) []* egv1a1.EnvoyExtensionPolicy {
0 commit comments