Skip to content

Commit 50ba850

Browse files
committed
Add EnvoyExtensionPolicy runtime fraction support
Signed-off-by: Adam Buran <aburan28@gmail.com>
1 parent cab6837 commit 50ba850

22 files changed

Lines changed: 765 additions & 12 deletions

File tree

api/v1alpha1/ext_proc_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ type ExtProc struct {
106106
//
107107
// +optional
108108
Metadata *ExtProcMetadata `json:"metadata,omitempty"`
109+
110+
// Percentage controls what percentage of requests are sent through this ExtProc extension.
111+
// When not specified, 100% of requests are sent through the extension.
112+
//
113+
// +optional
114+
Percentage *gwapiv1.Fraction `json:"percentage,omitempty"`
109115
}
110116

111117
// ExtProcMetadata defines options related to the sending and receiving of dynamic metadata to and from the

api/v1alpha1/lua_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,10 @@ type Lua struct {
4545
// +optional
4646
// +unionMember
4747
ValueRef *gwapiv1.LocalObjectReference `json:"valueRef,omitempty"`
48+
49+
// Percentage controls what percentage of requests are sent through this Lua extension.
50+
// When not specified, 100% of requests are sent through the extension.
51+
//
52+
// +optional
53+
Percentage *gwapiv1.Fraction `json:"percentage,omitempty"`
4854
}

api/v1alpha1/wasm_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ type Wasm struct {
6767
// Env configures the environment for the Wasm extension
6868
// +optional
6969
Env *WasmEnv `json:"env,omitempty"`
70+
71+
// Percentage controls what percentage of requests are sent through this Wasm extension.
72+
// When not specified, 100% of requests are sent through the extension.
73+
//
74+
// +optional
75+
Percentage *gwapiv1.Fraction `json:"percentage,omitempty"`
7076
}
7177

7278
// WasmCodeSource defines the source of the Wasm code.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,26 @@ spec:
15091509
Envoy HTTP filter namespaces
15101510
rule: self.all(f, !f.startsWith('envoy.filters.http'))
15111511
type: object
1512+
percentage:
1513+
description: |-
1514+
Percentage controls what percentage of requests are sent through this ExtProc extension.
1515+
When not specified, 100% of requests are sent through the extension.
1516+
properties:
1517+
denominator:
1518+
default: 100
1519+
format: int32
1520+
minimum: 1
1521+
type: integer
1522+
numerator:
1523+
format: int32
1524+
minimum: 0
1525+
type: integer
1526+
required:
1527+
- numerator
1528+
type: object
1529+
x-kubernetes-validations:
1530+
- message: numerator must be less than or equal to denominator
1531+
rule: self.numerator <= self.denominator
15121532
processingMode:
15131533
description: |-
15141534
ProcessingMode defines how request and response body is processed
@@ -1603,6 +1623,26 @@ spec:
16031623
inline:
16041624
description: Inline contains the source code as an inline string.
16051625
type: string
1626+
percentage:
1627+
description: |-
1628+
Percentage controls what percentage of requests are sent through this Lua extension.
1629+
When not specified, 100% of requests are sent through the extension.
1630+
properties:
1631+
denominator:
1632+
default: 100
1633+
format: int32
1634+
minimum: 1
1635+
type: integer
1636+
numerator:
1637+
format: int32
1638+
minimum: 0
1639+
type: integer
1640+
required:
1641+
- numerator
1642+
type: object
1643+
x-kubernetes-validations:
1644+
- message: numerator must be less than or equal to denominator
1645+
rule: self.numerator <= self.denominator
16061646
type:
16071647
default: Inline
16081648
description: |-
@@ -2109,6 +2149,26 @@ spec:
21092149
It's also used for logging/debugging.
21102150
If not specified, EG will generate a unique name for the Wasm extension.
21112151
type: string
2152+
percentage:
2153+
description: |-
2154+
Percentage controls what percentage of requests are sent through this Wasm extension.
2155+
When not specified, 100% of requests are sent through the extension.
2156+
properties:
2157+
denominator:
2158+
default: 100
2159+
format: int32
2160+
minimum: 1
2161+
type: integer
2162+
numerator:
2163+
format: int32
2164+
minimum: 0
2165+
type: integer
2166+
required:
2167+
- numerator
2168+
type: object
2169+
x-kubernetes-validations:
2170+
- message: numerator must be less than or equal to denominator
2171+
rule: self.numerator <= self.denominator
21122172
rootID:
21132173
description: |-
21142174
RootID is a unique ID for a set of extensions in a VM which will share a

charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,26 @@ spec:
15081508
Envoy HTTP filter namespaces
15091509
rule: self.all(f, !f.startsWith('envoy.filters.http'))
15101510
type: object
1511+
percentage:
1512+
description: |-
1513+
Percentage controls what percentage of requests are sent through this ExtProc extension.
1514+
When not specified, 100% of requests are sent through the extension.
1515+
properties:
1516+
denominator:
1517+
default: 100
1518+
format: int32
1519+
minimum: 1
1520+
type: integer
1521+
numerator:
1522+
format: int32
1523+
minimum: 0
1524+
type: integer
1525+
required:
1526+
- numerator
1527+
type: object
1528+
x-kubernetes-validations:
1529+
- message: numerator must be less than or equal to denominator
1530+
rule: self.numerator <= self.denominator
15111531
processingMode:
15121532
description: |-
15131533
ProcessingMode defines how request and response body is processed
@@ -1602,6 +1622,26 @@ spec:
16021622
inline:
16031623
description: Inline contains the source code as an inline string.
16041624
type: string
1625+
percentage:
1626+
description: |-
1627+
Percentage controls what percentage of requests are sent through this Lua extension.
1628+
When not specified, 100% of requests are sent through the extension.
1629+
properties:
1630+
denominator:
1631+
default: 100
1632+
format: int32
1633+
minimum: 1
1634+
type: integer
1635+
numerator:
1636+
format: int32
1637+
minimum: 0
1638+
type: integer
1639+
required:
1640+
- numerator
1641+
type: object
1642+
x-kubernetes-validations:
1643+
- message: numerator must be less than or equal to denominator
1644+
rule: self.numerator <= self.denominator
16051645
type:
16061646
default: Inline
16071647
description: |-
@@ -2108,6 +2148,26 @@ spec:
21082148
It's also used for logging/debugging.
21092149
If not specified, EG will generate a unique name for the Wasm extension.
21102150
type: string
2151+
percentage:
2152+
description: |-
2153+
Percentage controls what percentage of requests are sent through this Wasm extension.
2154+
When not specified, 100% of requests are sent through the extension.
2155+
properties:
2156+
denominator:
2157+
default: 100
2158+
format: int32
2159+
minimum: 1
2160+
type: integer
2161+
numerator:
2162+
format: int32
2163+
minimum: 0
2164+
type: integer
2165+
required:
2166+
- numerator
2167+
type: object
2168+
x-kubernetes-validations:
2169+
- message: numerator must be less than or equal to denominator
2170+
rule: self.numerator <= self.denominator
21112171
rootID:
21122172
description: |-
21132173
RootID is a unique ID for a set of extensions in a VM which will share a

internal/gatewayapi/envoyextensionpolicy.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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
831849
func (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.
12731338
func envoyExtensionPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyExtensionPolicy) []*egv1a1.EnvoyExtensionPolicy {

0 commit comments

Comments
 (0)