diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go
index 72382d699f1..9e9a97dec17 100644
--- a/api/v1alpha1/ratelimit_types.go
+++ b/api/v1alpha1/ratelimit_types.go
@@ -62,6 +62,7 @@ type LocalRateLimit struct {
//
// +optional
// +kubebuilder:validation:MaxItems=16
+ // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.responseHitsAddend))", message="responseHitsAddend is not supported for Local Rate Limits"
Rules []RateLimitRule `json:"rules"`
}
@@ -91,6 +92,64 @@ type RateLimitRule struct {
// 429 HTTP status code is sent back to the client when
// the selected requests have reached the limit.
Limit RateLimitValue `json:"limit"`
+ // RequestHitsAddend specifies the number to reduce the rate limit counters
+ // on the request path. If the addend is not specified, the default behavior
+ // is to reduce the rate limit counters by 1.
+ //
+ // When Envoy receives a request that matches the rule, it tries to reduce the
+ // rate limit counters by the specified number. If the counter doesn't have
+ // enough capacity, the request is rate limited.
+ //
+ // +optional
+ // +notImplementedHide
+ RequestHitsAddend *RateLimitHitsAddend `json:"requestHitsAddend,omitempty"`
+ // ResponseHitsAddend specifies the number to reduce the rate limit counters
+ // after the response is sent back to the client or the request stream is closed.
+ //
+ // The addend is used to reduce the rate limit counters for the matching requests.
+ // Since the reduction happens after the request stream is complete, the rate limit
+ // won't be enforced for the current request, but for the subsequent matching requests.
+ //
+ // This is optional and if not specified, the rate limit counters are not reduced
+ // on the response path.
+ //
+ // Currently, this is only supported for HTTP Global Rate Limits.
+ //
+ // +optional
+ // +notImplementedHide
+ ResponseHitsAddend *RateLimitHitsAddend `json:"responseHitsAddend,omitempty"`
+}
+
+// RateLimitHitsAddend specifies where the Envoy retrieves the number to reduce the rate limit counters.
+//
+// By default, Envoy looks up the addend from the `envoy.ratelimit.hits_addend` filter metadata.
+// If there's no such metadata or the number stored in the metadata is invalid, it will use the default
+// usage number of 1.
+//
+// This default behavior can be overridden by specifying exactly one of the fields in this RateLimitUsage.
+// If either of the fields is not specified, Envoy will use the default behavior described above.
+//
+// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#config-route-v3-ratelimit-hitsaddend
+// for more information.
+//
+// +kubebuilder:validation:XValidation:rule="!(has(self.number) && has(self.format))",message="only one of number or format can be specified"
+type RateLimitHitsAddend struct {
+ // Number specifies the fixed usage number to reduce the rate limit counters.
+ //
+ // +optional
+ // +notImplementedHide
+ Number *uint64 `json:"number,omitempty"`
+ // Format specifies the format of the usage number. See the Envoy documentation for the supported format which
+ // is the same as the access log format:
+ // https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format
+ //
+ // For example `%DYNAMIC_METADATA(com.test.my_filter:test_key)%"` will retrieve the usage number from the
+ // `com.test.my_filter` filter metadata namespace with the key `test_key`.
+ // Another example is `%BYTES_RECEIVED%` which will retrieve the usage number from the bytes received by the client.
+ //
+ // +optional
+ // +notImplementedHide
+ Format *string `json:"format,omitempty"`
}
// RateLimitSelectCondition specifies the attributes within the traffic flow that can
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index dbc28e6aca2..011f7104ce6 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -4570,6 +4570,31 @@ func (in *RateLimitDatabaseBackend) DeepCopy() *RateLimitDatabaseBackend {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RateLimitHitsAddend) DeepCopyInto(out *RateLimitHitsAddend) {
+ *out = *in
+ if in.Number != nil {
+ in, out := &in.Number, &out.Number
+ *out = new(uint64)
+ **out = **in
+ }
+ if in.Format != nil {
+ in, out := &in.Format, &out.Format
+ *out = new(string)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitHitsAddend.
+func (in *RateLimitHitsAddend) DeepCopy() *RateLimitHitsAddend {
+ if in == nil {
+ return nil
+ }
+ out := new(RateLimitHitsAddend)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RateLimitMetrics) DeepCopyInto(out *RateLimitMetrics) {
*out = *in
@@ -4636,6 +4661,16 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) {
}
}
out.Limit = in.Limit
+ if in.RequestHitsAddend != nil {
+ in, out := &in.RequestHitsAddend, &out.RequestHitsAddend
+ *out = new(RateLimitHitsAddend)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.ResponseHitsAddend != nil {
+ in, out := &in.ResponseHitsAddend, &out.ResponseHitsAddend
+ *out = new(RateLimitHitsAddend)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index f9fb0f329dd..757623ef616 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -777,6 +777,68 @@ spec:
- requests
- unit
type: object
+ requestHitsAddend:
+ description: |-
+ RequestHitsAddend specifies the number to reduce the rate limit counters
+ on the request path. If the addend is not specified, the default behavior
+ is to reduce the rate limit counters by 1.
+
+ When Envoy receives a request that matches the rule, it tries to reduce the
+ rate limit counters by the specified number. If the counter doesn't have
+ enough capacity, the request is rate limited.
+ properties:
+ format:
+ description: |-
+ Format specifies the format of the usage number. See the Envoy documentation for the supported format which
+ is the same as the access log format:
+ https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format
+
+ For example `%DYNAMIC_METADATA(com.test.my_filter:test_key)%"` will retrieve the usage number from the
+ `com.test.my_filter` filter metadata namespace with the key `test_key`.
+ Another example is `%BYTES_RECEIVED%` which will retrieve the usage number from the bytes received by the client.
+ type: string
+ number:
+ description: Number specifies the fixed usage number
+ to reduce the rate limit counters.
+ format: int64
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: only one of number or format can be specified
+ rule: '!(has(self.number) && has(self.format))'
+ responseHitsAddend:
+ description: |-
+ ResponseHitsAddend specifies the number to reduce the rate limit counters
+ after the response is sent back to the client or the request stream is closed.
+
+ The addend is used to reduce the rate limit counters for the matching requests.
+ Since the reduction happens after the request stream is complete, the rate limit
+ won't be enforced for the current request, but for the subsequent matching requests.
+
+ This is optional and if not specified, the rate limit counters are not reduced
+ on the response path.
+
+ Currently, this is only supported for HTTP Global Rate Limits.
+ properties:
+ format:
+ description: |-
+ Format specifies the format of the usage number. See the Envoy documentation for the supported format which
+ is the same as the access log format:
+ https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format
+
+ For example `%DYNAMIC_METADATA(com.test.my_filter:test_key)%"` will retrieve the usage number from the
+ `com.test.my_filter` filter metadata namespace with the key `test_key`.
+ Another example is `%BYTES_RECEIVED%` which will retrieve the usage number from the bytes received by the client.
+ type: string
+ number:
+ description: Number specifies the fixed usage number
+ to reduce the rate limit counters.
+ format: int64
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: only one of number or format can be specified
+ rule: '!(has(self.number) && has(self.format))'
required:
- limit
type: object
@@ -912,11 +974,77 @@ spec:
- requests
- unit
type: object
+ requestHitsAddend:
+ description: |-
+ RequestHitsAddend specifies the number to reduce the rate limit counters
+ on the request path. If the addend is not specified, the default behavior
+ is to reduce the rate limit counters by 1.
+
+ When Envoy receives a request that matches the rule, it tries to reduce the
+ rate limit counters by the specified number. If the counter doesn't have
+ enough capacity, the request is rate limited.
+ properties:
+ format:
+ description: |-
+ Format specifies the format of the usage number. See the Envoy documentation for the supported format which
+ is the same as the access log format:
+ https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format
+
+ For example `%DYNAMIC_METADATA(com.test.my_filter:test_key)%"` will retrieve the usage number from the
+ `com.test.my_filter` filter metadata namespace with the key `test_key`.
+ Another example is `%BYTES_RECEIVED%` which will retrieve the usage number from the bytes received by the client.
+ type: string
+ number:
+ description: Number specifies the fixed usage number
+ to reduce the rate limit counters.
+ format: int64
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: only one of number or format can be specified
+ rule: '!(has(self.number) && has(self.format))'
+ responseHitsAddend:
+ description: |-
+ ResponseHitsAddend specifies the number to reduce the rate limit counters
+ after the response is sent back to the client or the request stream is closed.
+
+ The addend is used to reduce the rate limit counters for the matching requests.
+ Since the reduction happens after the request stream is complete, the rate limit
+ won't be enforced for the current request, but for the subsequent matching requests.
+
+ This is optional and if not specified, the rate limit counters are not reduced
+ on the response path.
+
+ Currently, this is only supported for HTTP Global Rate Limits.
+ properties:
+ format:
+ description: |-
+ Format specifies the format of the usage number. See the Envoy documentation for the supported format which
+ is the same as the access log format:
+ https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format
+
+ For example `%DYNAMIC_METADATA(com.test.my_filter:test_key)%"` will retrieve the usage number from the
+ `com.test.my_filter` filter metadata namespace with the key `test_key`.
+ Another example is `%BYTES_RECEIVED%` which will retrieve the usage number from the bytes received by the client.
+ type: string
+ number:
+ description: Number specifies the fixed usage number
+ to reduce the rate limit counters.
+ format: int64
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: only one of number or format can be specified
+ rule: '!(has(self.number) && has(self.format))'
required:
- limit
type: object
maxItems: 16
type: array
+ x-kubernetes-validations:
+ - message: responseHitsAddend is not supported for Local Rate
+ Limits
+ rule: self.all(foo, !has(foo.responseHitsAddend))
type: object
type:
description: |-
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 5119d756646..dae16269797 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -3352,6 +3352,32 @@ _Appears in:_
| `Redis` | RedisBackendType uses a redis database for the rate limit service.
|
+#### RateLimitHitsAddend
+
+
+
+RateLimitHitsAddend specifies where the Envoy retrieves the number to reduce the rate limit counters.
+
+
+By default, Envoy looks up the addend from the `envoy.ratelimit.hits_addend` filter metadata.
+If there's no such metadata or the number stored in the metadata is invalid, it will use the default
+usage number of 1.
+
+
+This default behavior can be overridden by specifying exactly one of the fields in this RateLimitUsage.
+If either of the fields is not specified, Envoy will use the default behavior described above.
+
+
+See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#config-route-v3-ratelimit-hitsaddend
+for more information.
+
+_Appears in:_
+- [RateLimitRule](#ratelimitrule)
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+
+
#### RateLimitMetrics
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index 5119d756646..dae16269797 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -3352,6 +3352,32 @@ _Appears in:_
| `Redis` | RedisBackendType uses a redis database for the rate limit service.
|
+#### RateLimitHitsAddend
+
+
+
+RateLimitHitsAddend specifies where the Envoy retrieves the number to reduce the rate limit counters.
+
+
+By default, Envoy looks up the addend from the `envoy.ratelimit.hits_addend` filter metadata.
+If there's no such metadata or the number stored in the metadata is invalid, it will use the default
+usage number of 1.
+
+
+This default behavior can be overridden by specifying exactly one of the fields in this RateLimitUsage.
+If either of the fields is not specified, Envoy will use the default behavior described above.
+
+
+See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#config-route-v3-ratelimit-hitsaddend
+for more information.
+
+_Appears in:_
+- [RateLimitRule](#ratelimitrule)
+
+| Field | Type | Required | Description |
+| --- | --- | --- | --- |
+
+
#### RateLimitMetrics
diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go
index d5e6a1b2d1f..7b54c4cca69 100644
--- a/test/cel-validation/backendtrafficpolicy_test.go
+++ b/test/cel-validation/backendtrafficpolicy_test.go
@@ -1502,6 +1502,126 @@ func TestBackendTrafficPolicyTarget(t *testing.T) {
"only ConfigMap is supported for ValueRe",
},
},
+ {
+ desc: "valid Global rate limit rules with request and response hit addends",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ rules := []egv1a1.RateLimitRule{
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // Default values for RequestHitsAddend and ResponseHitsAddend.
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{},
+ RequestHitsAddend: &egv1a1.RateLimitHitsAddend{},
+ },
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // Only ResponseHitsAddend is set.
+ RequestHitsAddend: &egv1a1.RateLimitHitsAddend{},
+ },
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // Only RequestHitsAddend is set.
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{},
+ },
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // Both RequestHitsAddend and ResponseHitsAddend are set with values.
+ RequestHitsAddend: &egv1a1.RateLimitHitsAddend{Number: ptr.To[uint64](200)},
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{Number: ptr.To[uint64](200)},
+ },
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // Both RequestHitsAddend and ResponseHitsAddend are set with formats.
+ RequestHitsAddend: &egv1a1.RateLimitHitsAddend{Format: ptr.To[string]("%DYNAMIC_METADATA(com.test.my_filter:test_key)%")},
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{Format: ptr.To[string]("%DYNAMIC_METADATA(com.test.my_filter:test_key)%")},
+ },
+ }
+
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+ Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1a2.Kind("Gateway"),
+ Name: gwapiv1a2.ObjectName("eg"),
+ },
+ },
+ },
+ RateLimit: &egv1a1.RateLimitSpec{
+ Type: egv1a1.GlobalRateLimitType,
+ Global: &egv1a1.GlobalRateLimit{
+ Rules: rules,
+ },
+ },
+ }
+ },
+ wantErrors: []string{},
+ },
+ {
+ desc: "invalid Global rate limit rules with request and response hit addends specifying both number and format fields",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+ Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1a2.Kind("Gateway"),
+ Name: gwapiv1a2.ObjectName("eg"),
+ },
+ },
+ },
+ RateLimit: &egv1a1.RateLimitSpec{
+ Type: egv1a1.GlobalRateLimitType,
+ Global: &egv1a1.GlobalRateLimit{
+ Rules: []egv1a1.RateLimitRule{
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ RequestHitsAddend: &egv1a1.RateLimitHitsAddend{
+ Format: ptr.To[string]("foo"),
+ Number: ptr.To[uint64](200),
+ },
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{
+ Format: ptr.To[string]("bar"),
+ Number: ptr.To[uint64](200),
+ },
+ },
+ },
+ },
+ },
+ }
+ },
+ wantErrors: []string{
+ `only one of number or format can be specified, spec.rateLimit.global.rules[0].responseHitsAddend`,
+ },
+ },
+ {
+ desc: "invalid count of local rate limit rules specifying ResponseHitsAddend",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+ Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1a2.Kind("Gateway"),
+ Name: gwapiv1a2.ObjectName("eg"),
+ },
+ },
+ },
+ RateLimit: &egv1a1.RateLimitSpec{
+ Type: egv1a1.GlobalRateLimitType,
+ Local: &egv1a1.LocalRateLimit{
+ Rules: []egv1a1.RateLimitRule{
+ {
+ Limit: egv1a1.RateLimitValue{Requests: 10, Unit: "Minute"},
+ // This is not supported for LocalRateLimit.
+ ResponseHitsAddend: &egv1a1.RateLimitHitsAddend{},
+ },
+ },
+ },
+ },
+ }
+ },
+ wantErrors: []string{`responseHitsAddend is not supported for Local Rate Limits`},
+ },
}
for _, tc := range cases {