diff --git a/api/v1alpha1/admission_control.go b/api/v1alpha1/admission_control.go
new file mode 100644
index 00000000000..46ecd44fa96
--- /dev/null
+++ b/api/v1alpha1/admission_control.go
@@ -0,0 +1,110 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// AdmissionControl defines the admission control policy to be applied.
+// This configuration probabilistically rejects requests based on the success rate
+// of previous requests in a configurable sliding time window.
+type AdmissionControl struct {
+ // Enabled enables or disables the admission control filter.
+ // Defaults to true if not specified.
+ //
+ // +optional
+ Enabled *bool `json:"enabled,omitempty"`
+
+ // SamplingWindow defines the time window over which request success rates are calculated.
+ // Defaults to 60s if not specified.
+ //
+ // +optional
+ SamplingWindow *metav1.Duration `json:"samplingWindow,omitempty"`
+
+ // SuccessRateThreshold defines the lowest request success rate at which the filter
+ // will not reject requests. The value should be in the range [0.0, 1.0].
+ // Defaults to 0.95 (95%) if not specified.
+ //
+ // +optional
+ // +kubebuilder:validation:Minimum=0.0
+ // +kubebuilder:validation:Maximum=1.0
+ SuccessRateThreshold *float64 `json:"successRateThreshold,omitempty"`
+
+ // Aggression controls the rejection probability curve. A value of 1.0 means a linear
+ // increase in rejection probability as the success rate decreases. Higher values
+ // result in more aggressive rejection at higher success rates.
+ // Defaults to 1.0 if not specified.
+ //
+ // +optional
+ // +kubebuilder:validation:Minimum=0.0
+ Aggression *float64 `json:"aggression,omitempty"`
+
+ // RPSThreshold defines the minimum requests per second below which requests will
+ // pass through the filter without rejection. Defaults to 1 if not specified.
+ //
+ // +optional
+ // +kubebuilder:validation:Minimum=0
+ RPSThreshold *uint32 `json:"rpsThreshold,omitempty"`
+
+ // MaxRejectionProbability represents the upper limit of the rejection probability.
+ // The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified.
+ //
+ // +optional
+ // +kubebuilder:validation:Minimum=0.0
+ // +kubebuilder:validation:Maximum=1.0
+ MaxRejectionProbability *float64 `json:"maxRejectionProbability,omitempty"`
+
+ // SuccessCriteria defines what constitutes a successful request for both HTTP and gRPC.
+ //
+ // +optional
+ SuccessCriteria *AdmissionControlSuccessCriteria `json:"successCriteria,omitempty"`
+}
+
+// AdmissionControlSuccessCriteria defines the criteria for determining successful requests.
+type AdmissionControlSuccessCriteria struct {
+ // HTTP defines success criteria for HTTP requests.
+ //
+ // +optional
+ HTTP *HTTPSuccessCriteria `json:"http,omitempty"`
+
+ // GRPC defines success criteria for gRPC requests.
+ //
+ // +optional
+ GRPC *GRPCSuccessCriteria `json:"grpc,omitempty"`
+}
+
+// HTTPSuccessCriteria defines success criteria for HTTP requests.
+type HTTPSuccessCriteria struct {
+ // HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ // Each range is inclusive on both ends.
+ //
+ // +optional
+ HTTPSuccessStatus []HTTPStatusRange `json:"httpSuccessStatus,omitempty"`
+}
+
+// HTTPStatusRange defines a range of HTTP status codes.
+type HTTPStatusRange struct {
+ // Start is the inclusive start of the status code range (100-600).
+ //
+ // +kubebuilder:validation:Minimum=100
+ // +kubebuilder:validation:Maximum=600
+ Start int32 `json:"start"`
+
+ // End is the inclusive end of the status code range (100-600).
+ //
+ // +kubebuilder:validation:Minimum=100
+ // +kubebuilder:validation:Maximum=600
+ End int32 `json:"end"`
+}
+
+// GRPCSuccessCriteria defines success criteria for gRPC requests.
+type GRPCSuccessCriteria struct {
+ // GRPCSuccessStatus defines gRPC status codes that are considered successful.
+ //
+ // +optional
+ GRPCSuccessStatus []int32 `json:"grpcSuccessStatus,omitempty"`
+}
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index a762e597c60..36a25b8c6f4 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -66,6 +66,12 @@ type BackendTrafficPolicySpec struct {
// +optional
FaultInjection *FaultInjection `json:"faultInjection,omitempty"`
+ // AdmissionControl defines the admission control policy to be applied. This configuration
+ // probabilistically rejects requests based on the success rate of previous requests in a
+ // configurable sliding time window.
+ // +optional
+ AdmissionControl *AdmissionControl `json:"admissionControl,omitempty"`
+
// UseClientProtocol configures Envoy to prefer sending requests to backends using
// the same HTTP protocol that the incoming request used. Defaults to false, which means
// that Envoy will use the protocol indicated by the attached BackendRef.
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index cd4933d948b..5d6f0ebcd5e 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -246,6 +246,9 @@ const (
// EnvoyFilterFault defines the Envoy HTTP fault filter.
EnvoyFilterFault EnvoyFilter = "envoy.filters.http.fault"
+ // EnvoyFilterAdmissionControl defines the Envoy HTTP admission control filter.
+ EnvoyFilterAdmissionControl EnvoyFilter = "envoy.filters.http.admission_control"
+
// EnvoyFilterCORS defines the Envoy HTTP CORS filter.
EnvoyFilterCORS EnvoyFilter = "envoy.filters.http.cors"
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index d5661561773..b0583ac483b 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -17,7 +17,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
- "sigs.k8s.io/gateway-api/apis/v1"
+ apisv1 "sigs.k8s.io/gateway-api/apis/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -81,7 +81,7 @@ func (in *APIKeyAuth) DeepCopyInto(out *APIKeyAuth) {
*out = *in
if in.CredentialRefs != nil {
in, out := &in.CredentialRefs, &out.CredentialRefs
- *out = make([]v1.SecretObjectReference, len(*in))
+ *out = make([]apisv1.SecretObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -124,17 +124,17 @@ func (in *ActiveHealthCheck) DeepCopyInto(out *ActiveHealthCheck) {
*out = *in
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.InitialJitter != nil {
in, out := &in.InitialJitter, &out.InitialJitter
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.UnhealthyThreshold != nil {
@@ -199,6 +199,81 @@ func (in *ActiveHealthCheckPayload) DeepCopy() *ActiveHealthCheckPayload {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdmissionControl) DeepCopyInto(out *AdmissionControl) {
+ *out = *in
+ if in.Enabled != nil {
+ in, out := &in.Enabled, &out.Enabled
+ *out = new(bool)
+ **out = **in
+ }
+ if in.SamplingWindow != nil {
+ in, out := &in.SamplingWindow, &out.SamplingWindow
+ *out = new(v1.Duration)
+ **out = **in
+ }
+ if in.SuccessRateThreshold != nil {
+ in, out := &in.SuccessRateThreshold, &out.SuccessRateThreshold
+ *out = new(float64)
+ **out = **in
+ }
+ if in.Aggression != nil {
+ in, out := &in.Aggression, &out.Aggression
+ *out = new(float64)
+ **out = **in
+ }
+ if in.RPSThreshold != nil {
+ in, out := &in.RPSThreshold, &out.RPSThreshold
+ *out = new(uint32)
+ **out = **in
+ }
+ if in.MaxRejectionProbability != nil {
+ in, out := &in.MaxRejectionProbability, &out.MaxRejectionProbability
+ *out = new(float64)
+ **out = **in
+ }
+ if in.SuccessCriteria != nil {
+ in, out := &in.SuccessCriteria, &out.SuccessCriteria
+ *out = new(AdmissionControlSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionControl.
+func (in *AdmissionControl) DeepCopy() *AdmissionControl {
+ if in == nil {
+ return nil
+ }
+ out := new(AdmissionControl)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdmissionControlSuccessCriteria) DeepCopyInto(out *AdmissionControlSuccessCriteria) {
+ *out = *in
+ if in.HTTP != nil {
+ in, out := &in.HTTP, &out.HTTP
+ *out = new(HTTPSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.GRPC != nil {
+ in, out := &in.GRPC, &out.GRPC
+ *out = new(GRPCSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionControlSuccessCriteria.
+func (in *AdmissionControlSuccessCriteria) DeepCopy() *AdmissionControlSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(AdmissionControlSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Authorization) DeepCopyInto(out *Authorization) {
*out = *in
@@ -277,12 +352,12 @@ func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) {
*out = *in
if in.BaseInterval != nil {
in, out := &in.BaseInterval, &out.BaseInterval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxInterval != nil {
in, out := &in.MaxInterval, &out.MaxInterval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -329,7 +404,7 @@ func (in *BackendCluster) DeepCopyInto(out *BackendCluster) {
*out = *in
if in.BackendRef != nil {
in, out := &in.BackendRef, &out.BackendRef
- *out = new(v1.BackendObjectReference)
+ *out = new(apisv1.BackendObjectReference)
(*in).DeepCopyInto(*out)
}
if in.BackendRefs != nil {
@@ -551,7 +626,7 @@ func (in *BackendStatus) DeepCopyInto(out *BackendStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
- *out = make([]metav1.Condition, len(*in))
+ *out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -573,7 +648,7 @@ func (in *BackendTLSConfig) DeepCopyInto(out *BackendTLSConfig) {
*out = *in
if in.ClientCertificateRef != nil {
in, out := &in.ClientCertificateRef, &out.ClientCertificateRef
- *out = new(v1.SecretObjectReference)
+ *out = new(apisv1.SecretObjectReference)
(*in).DeepCopyInto(*out)
}
in.TLSSettings.DeepCopyInto(&out.TLSSettings)
@@ -594,12 +669,12 @@ func (in *BackendTLSSettings) DeepCopyInto(out *BackendTLSSettings) {
*out = *in
if in.CACertificateRefs != nil {
in, out := &in.CACertificateRefs, &out.CACertificateRefs
- *out = make([]v1.LocalObjectReference, len(*in))
+ *out = make([]apisv1.LocalObjectReference, len(*in))
copy(*out, *in)
}
if in.WellKnownCACertificates != nil {
in, out := &in.WellKnownCACertificates, &out.WellKnownCACertificates
- *out = new(v1.WellKnownCACertificatesType)
+ *out = new(apisv1.WellKnownCACertificatesType)
**out = **in
}
if in.InsecureSkipVerify != nil {
@@ -609,7 +684,7 @@ func (in *BackendTLSSettings) DeepCopyInto(out *BackendTLSSettings) {
}
if in.SNI != nil {
in, out := &in.SNI, &out.SNI
- *out = new(v1.PreciseHostname)
+ *out = new(apisv1.PreciseHostname)
**out = **in
}
if in.BackendTLSConfig != nil {
@@ -733,6 +808,11 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
*out = new(FaultInjection)
(*in).DeepCopyInto(*out)
}
+ if in.AdmissionControl != nil {
+ in, out := &in.AdmissionControl, &out.AdmissionControl
+ *out = new(AdmissionControl)
+ (*in).DeepCopyInto(*out)
+ }
if in.UseClientProtocol != nil {
in, out := &in.UseClientProtocol, &out.UseClientProtocol
*out = new(bool)
@@ -880,7 +960,7 @@ func (in *CORS) DeepCopyInto(out *CORS) {
}
if in.MaxAge != nil {
in, out := &in.MaxAge, &out.MaxAge
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.AllowCredentials != nil {
@@ -1216,7 +1296,7 @@ func (in *ClientValidationContext) DeepCopyInto(out *ClientValidationContext) {
*out = *in
if in.CACertificateRefs != nil {
in, out := &in.CACertificateRefs, &out.CACertificateRefs
- *out = make([]v1.SecretObjectReference, len(*in))
+ *out = make([]apisv1.SecretObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -1393,12 +1473,12 @@ func (in *ConnectionLimit) DeepCopyInto(out *ConnectionLimit) {
*out = *in
if in.CloseDelay != nil {
in, out := &in.CloseDelay, &out.CloseDelay
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxConnectionDuration != nil {
in, out := &in.MaxConnectionDuration, &out.MaxConnectionDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxRequestsPerConnection != nil {
@@ -1408,7 +1488,7 @@ func (in *ConnectionLimit) DeepCopyInto(out *ConnectionLimit) {
}
if in.MaxStreamDuration != nil {
in, out := &in.MaxStreamDuration, &out.MaxStreamDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -1480,7 +1560,7 @@ func (in *Cookie) DeepCopyInto(out *Cookie) {
*out = *in
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Attributes != nil {
@@ -1507,7 +1587,7 @@ func (in *CrlContext) DeepCopyInto(out *CrlContext) {
*out = *in
if in.Refs != nil {
in, out := &in.Refs, &out.Refs
- *out = make([]v1.SecretObjectReference, len(*in))
+ *out = make([]apisv1.SecretObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -1559,17 +1639,17 @@ func (in *CustomRedirect) DeepCopyInto(out *CustomRedirect) {
}
if in.Hostname != nil {
in, out := &in.Hostname, &out.Hostname
- *out = new(v1.PreciseHostname)
+ *out = new(apisv1.PreciseHostname)
**out = **in
}
if in.Path != nil {
in, out := &in.Path, &out.Path
- *out = new(v1.HTTPPathModifier)
+ *out = new(apisv1.HTTPPathModifier)
(*in).DeepCopyInto(*out)
}
if in.Port != nil {
in, out := &in.Port, &out.Port
- *out = new(v1.PortNumber)
+ *out = new(apisv1.PortNumber)
**out = **in
}
if in.StatusCode != nil {
@@ -1609,7 +1689,7 @@ func (in *CustomResponse) DeepCopyInto(out *CustomResponse) {
}
if in.Header != nil {
in, out := &in.Header, &out.Header
- *out = new(v1.HTTPHeaderFilter)
+ *out = new(apisv1.HTTPHeaderFilter)
(*in).DeepCopyInto(*out)
}
}
@@ -1639,7 +1719,7 @@ func (in *CustomResponseBody) DeepCopyInto(out *CustomResponseBody) {
}
if in.ValueRef != nil {
in, out := &in.ValueRef, &out.ValueRef
- *out = new(v1.LocalObjectReference)
+ *out = new(apisv1.LocalObjectReference)
**out = **in
}
}
@@ -1711,7 +1791,7 @@ func (in *DNS) DeepCopyInto(out *DNS) {
*out = *in
if in.DNSRefreshRate != nil {
in, out := &in.DNSRefreshRate, &out.DNSRefreshRate
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.RespectDNSTTL != nil {
@@ -2100,7 +2180,7 @@ func (in *EnvoyGatewayKubernetesProvider) DeepCopyInto(out *EnvoyGatewayKubernet
}
if in.CacheSyncPeriod != nil {
in, out := &in.CacheSyncPeriod, &out.CacheSyncPeriod
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -2194,12 +2274,12 @@ func (in *EnvoyGatewayOpenTelemetrySink) DeepCopyInto(out *EnvoyGatewayOpenTelem
*out = *in
if in.ExportInterval != nil {
in, out := &in.ExportInterval, &out.ExportInterval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.ExportTimeout != nil {
in, out := &in.ExportTimeout, &out.ExportTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -2754,7 +2834,7 @@ func (in *ExtAuth) DeepCopyInto(out *ExtAuth) {
}
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.FailOpen != nil {
@@ -2785,7 +2865,7 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) {
in.BackendCluster.DeepCopyInto(&out.BackendCluster)
if in.MessageTimeout != nil {
in, out := &in.MessageTimeout, &out.MessageTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.FailOpen != nil {
@@ -2981,17 +3061,17 @@ func (in *ExtensionServiceRetry) DeepCopyInto(out *ExtensionServiceRetry) {
}
if in.InitialBackoff != nil {
in, out := &in.InitialBackoff, &out.InitialBackoff
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxBackoff != nil {
in, out := &in.MaxBackoff, &out.MaxBackoff
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.BackoffMultiplier != nil {
in, out := &in.BackoffMultiplier, &out.BackoffMultiplier
- *out = new(v1.Fraction)
+ *out = new(apisv1.Fraction)
(*in).DeepCopyInto(*out)
}
if in.RetryableStatusCodes != nil {
@@ -3017,7 +3097,7 @@ func (in *ExtensionTLS) DeepCopyInto(out *ExtensionTLS) {
in.CertificateRef.DeepCopyInto(&out.CertificateRef)
if in.ClientCertificateRef != nil {
in, out := &in.ClientCertificateRef, &out.ClientCertificateRef
- *out = new(v1.SecretObjectReference)
+ *out = new(apisv1.SecretObjectReference)
(*in).DeepCopyInto(*out)
}
}
@@ -3137,7 +3217,7 @@ func (in *FaultInjectionDelay) DeepCopyInto(out *FaultInjectionDelay) {
*out = *in
if in.FixedDelay != nil {
in, out := &in.FixedDelay, &out.FixedDelay
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Percentage != nil {
@@ -3253,6 +3333,26 @@ func (in *GRPCExtAuthService) DeepCopy() *GRPCExtAuthService {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GRPCSuccessCriteria) DeepCopyInto(out *GRPCSuccessCriteria) {
+ *out = *in
+ if in.GRPCSuccessStatus != nil {
+ in, out := &in.GRPCSuccessStatus, &out.GRPCSuccessStatus
+ *out = make([]int32, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCSuccessCriteria.
+func (in *GRPCSuccessCriteria) DeepCopy() *GRPCSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(GRPCSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Gateway) DeepCopyInto(out *Gateway) {
*out = *in
@@ -3465,17 +3565,17 @@ func (in *HTTPClientTimeout) DeepCopyInto(out *HTTPClientTimeout) {
*out = *in
if in.RequestReceivedTimeout != nil {
in, out := &in.RequestReceivedTimeout, &out.RequestReceivedTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.IdleTimeout != nil {
in, out := &in.IdleTimeout, &out.IdleTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.StreamIdleTimeout != nil {
in, out := &in.StreamIdleTimeout, &out.StreamIdleTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -3536,7 +3636,7 @@ func (in *HTTPDirectResponseFilter) DeepCopyInto(out *HTTPDirectResponseFilter)
}
if in.Header != nil {
in, out := &in.Header, &out.Header
- *out = new(v1.HTTPHeaderFilter)
+ *out = new(apisv1.HTTPHeaderFilter)
(*in).DeepCopyInto(*out)
}
}
@@ -3582,12 +3682,12 @@ func (in *HTTPHeaderFilter) DeepCopyInto(out *HTTPHeaderFilter) {
*out = *in
if in.Set != nil {
in, out := &in.Set, &out.Set
- *out = make([]v1.HTTPHeader, len(*in))
+ *out = make([]apisv1.HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Add != nil {
in, out := &in.Add, &out.Add
- *out = make([]v1.HTTPHeader, len(*in))
+ *out = make([]apisv1.HTTPHeader, len(*in))
copy(*out, *in)
}
if in.Remove != nil {
@@ -3735,27 +3835,62 @@ func (in *HTTPRouteFilterSpec) DeepCopy() *HTTPRouteFilterSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPStatusRange) DeepCopyInto(out *HTTPStatusRange) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPStatusRange.
+func (in *HTTPStatusRange) DeepCopy() *HTTPStatusRange {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPStatusRange)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPSuccessCriteria) DeepCopyInto(out *HTTPSuccessCriteria) {
+ *out = *in
+ if in.HTTPSuccessStatus != nil {
+ in, out := &in.HTTPSuccessStatus, &out.HTTPSuccessStatus
+ *out = make([]HTTPStatusRange, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPSuccessCriteria.
+func (in *HTTPSuccessCriteria) DeepCopy() *HTTPSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPTimeout) DeepCopyInto(out *HTTPTimeout) {
*out = *in
if in.ConnectionIdleTimeout != nil {
in, out := &in.ConnectionIdleTimeout, &out.ConnectionIdleTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxConnectionDuration != nil {
in, out := &in.MaxConnectionDuration, &out.MaxConnectionDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.RequestTimeout != nil {
in, out := &in.RequestTimeout, &out.RequestTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxStreamDuration != nil {
in, out := &in.MaxStreamDuration, &out.MaxStreamDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -3990,7 +4125,7 @@ func (in *ImageWasmCodeSource) DeepCopyInto(out *ImageWasmCodeSource) {
}
if in.PullSecretRef != nil {
in, out := &in.PullSecretRef, &out.PullSecretRef
- *out = new(v1.SecretObjectReference)
+ *out = new(apisv1.SecretObjectReference)
(*in).DeepCopyInto(*out)
}
if in.TLS != nil {
@@ -4717,7 +4852,7 @@ func (in *KubernetesWatchMode) DeepCopyInto(out *KubernetesWatchMode) {
}
if in.NamespaceSelector != nil {
in, out := &in.NamespaceSelector, &out.NamespaceSelector
- *out = new(metav1.LabelSelector)
+ *out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
}
@@ -4737,17 +4872,17 @@ func (in *LeaderElection) DeepCopyInto(out *LeaderElection) {
*out = *in
if in.LeaseDuration != nil {
in, out := &in.LeaseDuration, &out.LeaseDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.RenewDeadline != nil {
in, out := &in.RenewDeadline, &out.RenewDeadline
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.RetryPeriod != nil {
in, out := &in.RetryPeriod, &out.RetryPeriod
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Disable != nil {
@@ -4852,7 +4987,7 @@ func (in *LocalJWKS) DeepCopyInto(out *LocalJWKS) {
}
if in.ValueRef != nil {
in, out := &in.ValueRef, &out.ValueRef
- *out = new(v1.LocalObjectReference)
+ *out = new(apisv1.LocalObjectReference)
**out = **in
}
}
@@ -4899,7 +5034,7 @@ func (in *Lua) DeepCopyInto(out *Lua) {
}
if in.ValueRef != nil {
in, out := &in.ValueRef, &out.ValueRef
- *out = new(v1.LocalObjectReference)
+ *out = new(apisv1.LocalObjectReference)
**out = **in
}
}
@@ -4945,7 +5080,7 @@ func (in *OIDC) DeepCopyInto(out *OIDC) {
}
if in.ClientIDRef != nil {
in, out := &in.ClientIDRef, &out.ClientIDRef
- *out = new(v1.SecretObjectReference)
+ *out = new(apisv1.SecretObjectReference)
(*in).DeepCopyInto(*out)
}
in.ClientSecret.DeepCopyInto(&out.ClientSecret)
@@ -4996,7 +5131,7 @@ func (in *OIDC) DeepCopyInto(out *OIDC) {
}
if in.DefaultTokenTTL != nil {
in, out := &in.DefaultTokenTTL, &out.DefaultTokenTTL
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.RefreshToken != nil {
@@ -5006,12 +5141,12 @@ func (in *OIDC) DeepCopyInto(out *OIDC) {
}
if in.DefaultRefreshTokenTTL != nil {
in, out := &in.DefaultRefreshTokenTTL, &out.DefaultRefreshTokenTTL
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.CSRFTokenTTL != nil {
in, out := &in.CSRFTokenTTL, &out.CSRFTokenTTL
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.DisableTokenEncryption != nil {
@@ -5183,7 +5318,7 @@ func (in *Operation) DeepCopyInto(out *Operation) {
*out = *in
if in.Methods != nil {
in, out := &in.Methods, &out.Methods
- *out = make([]v1.HTTPMethod, len(*in))
+ *out = make([]apisv1.HTTPMethod, len(*in))
copy(*out, *in)
}
}
@@ -5224,7 +5359,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) {
}
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.ConsecutiveLocalOriginFailures != nil {
@@ -5244,7 +5379,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) {
}
if in.BaseEjectionTime != nil {
in, out := &in.BaseEjectionTime, &out.BaseEjectionTime
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxEjectionPercent != nil {
@@ -5274,7 +5409,7 @@ func (in *PathMatch) DeepCopyInto(out *PathMatch) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
- *out = new(v1.PathMatchType)
+ *out = new(apisv1.PathMatchType)
**out = **in
}
if in.Invert != nil {
@@ -5344,7 +5479,7 @@ func (in *PerRetryPolicy) DeepCopyInto(out *PerRetryPolicy) {
*out = *in
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.BackOff != nil {
@@ -5369,12 +5504,12 @@ func (in *PolicyTargetReferences) DeepCopyInto(out *PolicyTargetReferences) {
*out = *in
if in.TargetRef != nil {
in, out := &in.TargetRef, &out.TargetRef
- *out = new(v1.LocalPolicyTargetReferenceWithSectionName)
+ *out = new(apisv1.LocalPolicyTargetReferenceWithSectionName)
(*in).DeepCopyInto(*out)
}
if in.TargetRefs != nil {
in, out := &in.TargetRefs, &out.TargetRefs
- *out = make([]v1.LocalPolicyTargetReferenceWithSectionName, len(*in))
+ *out = make([]apisv1.LocalPolicyTargetReferenceWithSectionName, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -5895,7 +6030,7 @@ func (in *ProxyTracing) DeepCopyInto(out *ProxyTracing) {
}
if in.SamplingFraction != nil {
in, out := &in.SamplingFraction, &out.SamplingFraction
- *out = new(v1.Fraction)
+ *out = new(apisv1.Fraction)
(*in).DeepCopyInto(*out)
}
if in.CustomTags != nil {
@@ -5939,7 +6074,7 @@ func (in *RateLimit) DeepCopyInto(out *RateLimit) {
in.Backend.DeepCopyInto(&out.Backend)
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Telemetry != nil {
@@ -6291,7 +6426,7 @@ func (in *RedisTLSSettings) DeepCopyInto(out *RedisTLSSettings) {
*out = *in
if in.CertificateRef != nil {
in, out := &in.CertificateRef, &out.CertificateRef
- *out = new(v1.SecretObjectReference)
+ *out = new(apisv1.SecretObjectReference)
(*in).DeepCopyInto(*out)
}
}
@@ -6312,7 +6447,7 @@ func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) {
in.BackendCluster.DeepCopyInto(&out.BackendCluster)
if in.CacheDuration != nil {
in, out := &in.CacheDuration, &out.CacheDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -6689,12 +6824,12 @@ func (in *ShutdownConfig) DeepCopyInto(out *ShutdownConfig) {
*out = *in
if in.DrainTimeout != nil {
in, out := &in.DrainTimeout, &out.DrainTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MinDrainDuration != nil {
in, out := &in.MinDrainDuration, &out.MinDrainDuration
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -6734,7 +6869,7 @@ func (in *SlowStart) DeepCopyInto(out *SlowStart) {
*out = *in
if in.Window != nil {
in, out := &in.Window, &out.Window
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -6944,7 +7079,7 @@ func (in *TCPClientTimeout) DeepCopyInto(out *TCPClientTimeout) {
*out = *in
if in.IdleTimeout != nil {
in, out := &in.IdleTimeout, &out.IdleTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -6969,12 +7104,12 @@ func (in *TCPKeepalive) DeepCopyInto(out *TCPKeepalive) {
}
if in.IdleTime != nil {
in, out := &in.IdleTime, &out.IdleTime
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -6994,7 +7129,7 @@ func (in *TCPTimeout) DeepCopyInto(out *TCPTimeout) {
*out = *in
if in.ConnectTimeout != nil {
in, out := &in.ConnectTimeout, &out.ConnectTimeout
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
@@ -7059,7 +7194,7 @@ func (in *TargetSelector) DeepCopyInto(out *TargetSelector) {
*out = *in
if in.Group != nil {
in, out := &in.Group, &out.Group
- *out = new(v1.Group)
+ *out = new(apisv1.Group)
**out = **in
}
if in.MatchLabels != nil {
@@ -7071,7 +7206,7 @@ func (in *TargetSelector) DeepCopyInto(out *TargetSelector) {
}
if in.MatchExpressions != nil {
in, out := &in.MatchExpressions, &out.MatchExpressions
- *out = make([]metav1.LabelSelectorRequirement, len(*in))
+ *out = make([]v1.LabelSelectorRequirement, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -7118,7 +7253,7 @@ func (in *Tracing) DeepCopyInto(out *Tracing) {
*out = *in
if in.SamplingFraction != nil {
in, out := &in.SamplingFraction, &out.SamplingFraction
- *out = new(v1.Fraction)
+ *out = new(apisv1.Fraction)
(*in).DeepCopyInto(*out)
}
if in.CustomTags != nil {
@@ -7333,12 +7468,12 @@ func (in *XDSServer) DeepCopyInto(out *XDSServer) {
*out = *in
if in.MaxConnectionAge != nil {
in, out := &in.MaxConnectionAge, &out.MaxConnectionAge
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
if in.MaxConnectionAgeGrace != nil {
in, out := &in.MaxConnectionAgeGrace, &out.MaxConnectionAgeGrace
- *out = new(v1.Duration)
+ *out = new(apisv1.Duration)
**out = **in
}
}
diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 8ad379ea307..4447a560fa9 100644
--- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -50,6 +50,100 @@ spec:
spec:
description: spec defines the desired state of BackendTrafficPolicy.
properties:
+ admissionControl:
+ description: |-
+ AdmissionControl defines the admission control policy to be applied. This configuration
+ probabilistically rejects requests based on the success rate of previous requests in a
+ configurable sliding time window.
+ properties:
+ aggression:
+ description: |-
+ Aggression controls the rejection probability curve. A value of 1.0 means a linear
+ increase in rejection probability as the success rate decreases. Higher values
+ result in more aggressive rejection at higher success rates.
+ Defaults to 1.0 if not specified.
+ minimum: 0
+ type: number
+ enabled:
+ description: |-
+ Enabled enables or disables the admission control filter.
+ Defaults to true if not specified.
+ type: boolean
+ maxRejectionProbability:
+ description: |-
+ MaxRejectionProbability represents the upper limit of the rejection probability.
+ The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ rpsThreshold:
+ description: |-
+ RPSThreshold defines the minimum requests per second below which requests will
+ pass through the filter without rejection. Defaults to 1 if not specified.
+ format: int32
+ minimum: 0
+ type: integer
+ samplingWindow:
+ description: |-
+ SamplingWindow defines the time window over which request success rates are calculated.
+ Defaults to 60s if not specified.
+ type: string
+ successCriteria:
+ description: SuccessCriteria defines what constitutes a successful
+ request for both HTTP and gRPC.
+ properties:
+ grpc:
+ description: GRPC defines success criteria for gRPC requests.
+ properties:
+ grpcSuccessStatus:
+ description: GRPCSuccessStatus defines gRPC status codes
+ that are considered successful.
+ items:
+ format: int32
+ type: integer
+ type: array
+ type: object
+ http:
+ description: HTTP defines success criteria for HTTP requests.
+ properties:
+ httpSuccessStatus:
+ description: |-
+ HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ Each range is inclusive on both ends.
+ items:
+ description: HTTPStatusRange defines a range of HTTP
+ status codes.
+ properties:
+ end:
+ description: End is the inclusive end of the status
+ code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ start:
+ description: Start is the inclusive start of the
+ status code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ required:
+ - end
+ - start
+ type: object
+ type: array
+ type: object
+ type: object
+ successRateThreshold:
+ description: |-
+ SuccessRateThreshold defines the lowest request success rate at which the filter
+ will not reject requests. The value should be in the range [0.0, 1.0].
+ Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ type: object
circuitBreaker:
description: |-
Circuit Breaker settings for the upstream connections and requests.
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 5944352f6f8..09529e311d0 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -49,6 +49,100 @@ spec:
spec:
description: spec defines the desired state of BackendTrafficPolicy.
properties:
+ admissionControl:
+ description: |-
+ AdmissionControl defines the admission control policy to be applied. This configuration
+ probabilistically rejects requests based on the success rate of previous requests in a
+ configurable sliding time window.
+ properties:
+ aggression:
+ description: |-
+ Aggression controls the rejection probability curve. A value of 1.0 means a linear
+ increase in rejection probability as the success rate decreases. Higher values
+ result in more aggressive rejection at higher success rates.
+ Defaults to 1.0 if not specified.
+ minimum: 0
+ type: number
+ enabled:
+ description: |-
+ Enabled enables or disables the admission control filter.
+ Defaults to true if not specified.
+ type: boolean
+ maxRejectionProbability:
+ description: |-
+ MaxRejectionProbability represents the upper limit of the rejection probability.
+ The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ rpsThreshold:
+ description: |-
+ RPSThreshold defines the minimum requests per second below which requests will
+ pass through the filter without rejection. Defaults to 1 if not specified.
+ format: int32
+ minimum: 0
+ type: integer
+ samplingWindow:
+ description: |-
+ SamplingWindow defines the time window over which request success rates are calculated.
+ Defaults to 60s if not specified.
+ type: string
+ successCriteria:
+ description: SuccessCriteria defines what constitutes a successful
+ request for both HTTP and gRPC.
+ properties:
+ grpc:
+ description: GRPC defines success criteria for gRPC requests.
+ properties:
+ grpcSuccessStatus:
+ description: GRPCSuccessStatus defines gRPC status codes
+ that are considered successful.
+ items:
+ format: int32
+ type: integer
+ type: array
+ type: object
+ http:
+ description: HTTP defines success criteria for HTTP requests.
+ properties:
+ httpSuccessStatus:
+ description: |-
+ HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ Each range is inclusive on both ends.
+ items:
+ description: HTTPStatusRange defines a range of HTTP
+ status codes.
+ properties:
+ end:
+ description: End is the inclusive end of the status
+ code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ start:
+ description: Start is the inclusive start of the
+ status code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ required:
+ - end
+ - start
+ type: object
+ type: array
+ type: object
+ type: object
+ successRateThreshold:
+ description: |-
+ SuccessRateThreshold defines the lowest request success rate at which the filter
+ will not reject requests. The value should be in the range [0.0, 1.0].
+ Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ type: object
circuitBreaker:
description: |-
Circuit Breaker settings for the upstream connections and requests.
diff --git a/examples/kubernetes/admission-control.yaml b/examples/kubernetes/admission-control.yaml
new file mode 100644
index 00000000000..2750e9182f2
--- /dev/null
+++ b/examples/kubernetes/admission-control.yaml
@@ -0,0 +1,64 @@
+# Copyright Envoy Gateway Authors
+# SPDX-License-Identifier: Apache-2.0
+# The full text of the Apache license is available in the LICENSE file at
+# the root of the repo.
+
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+ name: admission-control-policy
+ namespace: default
+spec:
+ targetRef:
+ group: gateway.networking.k8s.io
+ kind: HTTPRoute
+ name: example-route
+ namespace: default
+ admissionControl:
+ enabled: true
+ samplingWindow: 30s
+ srThreshold: 0.95
+ aggression: 1.0
+ rpsThreshold: 5.0
+ maxRejectionProbability: 0.8
+ successCriteria:
+ http:
+ httpSuccessStatus:
+ - start: 200
+ end: 299
+ grpc:
+ grpcSuccessStatus:
+ - start: 0
+ end: 0
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: example-route
+ namespace: default
+spec:
+ parentRefs:
+ - name: eg
+ namespace: default
+ hostnames:
+ - "example.com"
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ backendRefs:
+ - name: backend-service
+ port: 80
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: backend-service
+ namespace: default
+spec:
+ ports:
+ - port: 80
+ targetPort: 8080
+ selector:
+ app: backend
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 2dfef92eeaa..0dd1cab3104 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -834,6 +834,7 @@ func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy, r
hc *ir.HealthCheck
cb *ir.CircuitBreaker
fi *ir.FaultInjection
+ ac *ir.AdmissionControl
to *ir.Timeout
ka *ir.TCPKeepalive
rt *ir.Retry
@@ -866,6 +867,9 @@ func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy, r
if policy.Spec.FaultInjection != nil {
fi = t.buildFaultInjection(policy)
}
+ if policy.Spec.AdmissionControl != nil {
+ ac = t.buildAdmissionControl(policy)
+ }
if ka, err = buildTCPKeepAlive(&policy.Spec.ClusterSettings); err != nil {
err = perr.WithMessage(err, "TCPKeepalive")
errs = errors.Join(errs, err)
@@ -918,6 +922,7 @@ func (t *Translator) buildTrafficFeatures(policy *egv1a1.BackendTrafficPolicy, r
HealthCheck: hc,
CircuitBreaker: cb,
FaultInjection: fi,
+ AdmissionControl: ac,
TCPKeepalive: ka,
Retry: rt,
BackendConnection: bc,
@@ -1386,6 +1391,43 @@ func (t *Translator) buildFaultInjection(policy *egv1a1.BackendTrafficPolicy) *i
return fi
}
+func (t *Translator) buildAdmissionControl(policy *egv1a1.BackendTrafficPolicy) *ir.AdmissionControl {
+ if policy.Spec.AdmissionControl == nil {
+ return nil
+ }
+
+ ac := &ir.AdmissionControl{
+ Enabled: policy.Spec.AdmissionControl.Enabled,
+ SamplingWindow: policy.Spec.AdmissionControl.SamplingWindow,
+ SuccessRateThreshold: policy.Spec.AdmissionControl.SuccessRateThreshold,
+ Aggression: policy.Spec.AdmissionControl.Aggression,
+ RPSThreshold: policy.Spec.AdmissionControl.RPSThreshold,
+ MaxRejectionProbability: policy.Spec.AdmissionControl.MaxRejectionProbability,
+ }
+
+ if policy.Spec.AdmissionControl.SuccessCriteria != nil {
+ ac.SuccessCriteria = &ir.AdmissionControlSuccessCriteria{}
+
+ if policy.Spec.AdmissionControl.SuccessCriteria.HTTP != nil {
+ ac.SuccessCriteria.HTTP = &ir.HTTPSuccessCriteria{}
+ for _, statusRange := range policy.Spec.AdmissionControl.SuccessCriteria.HTTP.HTTPSuccessStatus {
+ ac.SuccessCriteria.HTTP.HTTPSuccessStatus = append(ac.SuccessCriteria.HTTP.HTTPSuccessStatus, ir.HTTPStatusRange{
+ Start: statusRange.Start,
+ End: statusRange.End,
+ })
+ }
+ }
+
+ if policy.Spec.AdmissionControl.SuccessCriteria.GRPC != nil {
+ ac.SuccessCriteria.GRPC = &ir.GRPCSuccessCriteria{
+ GRPCSuccessStatus: policy.Spec.AdmissionControl.SuccessCriteria.GRPC.GRPCSuccessStatus,
+ }
+ }
+ }
+
+ return ac
+}
+
func makeIrStatusSet(in []egv1a1.HTTPStatus) []ir.HTTPStatus {
statusSet := sets.NewInt()
for _, r := range in {
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index bd60f26505f..55f4afb7e1b 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -907,6 +907,8 @@ type TrafficFeatures struct {
HealthCheck *HealthCheck `json:"healthCheck,omitempty" yaml:"healthCheck,omitempty"`
// FaultInjection defines the schema for injecting faults into HTTP requests.
FaultInjection *FaultInjection `json:"faultInjection,omitempty" yaml:"faultInjection,omitempty"`
+ // AdmissionControl defines the schema for admission control based on success rate.
+ AdmissionControl *AdmissionControl `json:"admissionControl,omitempty" yaml:"admissionControl,omitempty"`
// Circuit Breaker Settings
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
// Request and connection timeout settings
@@ -1465,6 +1467,64 @@ type FaultInjectionAbort struct {
Percentage *float32 `json:"percentage,omitempty" yaml:"percentage,omitempty"`
}
+// AdmissionControl defines the schema for admission control based on success rate.
+//
+// +k8s:deepcopy-gen=true
+type AdmissionControl struct {
+ // Enabled enables or disables the admission control filter.
+ Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
+ // SamplingWindow defines the time window over which request success rates are calculated.
+ SamplingWindow *metav1.Duration `json:"samplingWindow,omitempty" yaml:"samplingWindow,omitempty"`
+ // SuccessRateThreshold defines the lowest request success rate at which the filter
+ // will not reject requests. The value should be in the range [0.0, 1.0].
+ SuccessRateThreshold *float64 `json:"successRateThreshold,omitempty" yaml:"successRateThreshold,omitempty"`
+ // Aggression controls the rejection probability curve.
+ Aggression *float64 `json:"aggression,omitempty" yaml:"aggression,omitempty"`
+ // RPSThreshold defines the minimum requests per second below which requests will
+ // pass through the filter without rejection.
+ RPSThreshold *uint32 `json:"rpsThreshold,omitempty" yaml:"rpsThreshold,omitempty"`
+ // MaxRejectionProbability represents the upper limit of the rejection probability.
+ MaxRejectionProbability *float64 `json:"maxRejectionProbability,omitempty" yaml:"maxRejectionProbability,omitempty"`
+ // SuccessCriteria defines what constitutes a successful request for both HTTP and gRPC.
+ SuccessCriteria *AdmissionControlSuccessCriteria `json:"successCriteria,omitempty" yaml:"successCriteria,omitempty"`
+}
+
+// AdmissionControlSuccessCriteria defines the criteria for determining successful requests.
+//
+// +k8s:deepcopy-gen=true
+type AdmissionControlSuccessCriteria struct {
+ // HTTP defines success criteria for HTTP requests.
+ HTTP *HTTPSuccessCriteria `json:"http,omitempty" yaml:"http,omitempty"`
+ // GRPC defines success criteria for gRPC requests.
+ GRPC *GRPCSuccessCriteria `json:"grpc,omitempty" yaml:"grpc,omitempty"`
+}
+
+// HTTPSuccessCriteria defines success criteria for HTTP requests.
+//
+// +k8s:deepcopy-gen=true
+type HTTPSuccessCriteria struct {
+ // HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ HTTPSuccessStatus []HTTPStatusRange `json:"httpSuccessStatus,omitempty" yaml:"httpSuccessStatus,omitempty"`
+}
+
+// HTTPStatusRange defines a range of HTTP status codes.
+//
+// +k8s:deepcopy-gen=true
+type HTTPStatusRange struct {
+ // Start is the inclusive start of the status code range.
+ Start int32 `json:"start" yaml:"start"`
+ // End is the inclusive end of the status code range.
+ End int32 `json:"end" yaml:"end"`
+}
+
+// GRPCSuccessCriteria defines success criteria for gRPC requests.
+//
+// +k8s:deepcopy-gen=true
+type GRPCSuccessCriteria struct {
+ // GRPCSuccessStatus defines gRPC status codes that are considered successful.
+ GRPCSuccessStatus []int32 `json:"grpcSuccessStatus,omitempty" yaml:"grpcSuccessStatus,omitempty"`
+}
+
// MirrorPolicy specifies a destination to mirror traffic in addition
// to the original destination
//
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index a7186f23f4e..25c09c8afc4 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -296,6 +296,81 @@ func (in *AddHeader) DeepCopy() *AddHeader {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdmissionControl) DeepCopyInto(out *AdmissionControl) {
+ *out = *in
+ if in.Enabled != nil {
+ in, out := &in.Enabled, &out.Enabled
+ *out = new(bool)
+ **out = **in
+ }
+ if in.SamplingWindow != nil {
+ in, out := &in.SamplingWindow, &out.SamplingWindow
+ *out = new(v1.Duration)
+ **out = **in
+ }
+ if in.SuccessRateThreshold != nil {
+ in, out := &in.SuccessRateThreshold, &out.SuccessRateThreshold
+ *out = new(float64)
+ **out = **in
+ }
+ if in.Aggression != nil {
+ in, out := &in.Aggression, &out.Aggression
+ *out = new(float64)
+ **out = **in
+ }
+ if in.RPSThreshold != nil {
+ in, out := &in.RPSThreshold, &out.RPSThreshold
+ *out = new(uint32)
+ **out = **in
+ }
+ if in.MaxRejectionProbability != nil {
+ in, out := &in.MaxRejectionProbability, &out.MaxRejectionProbability
+ *out = new(float64)
+ **out = **in
+ }
+ if in.SuccessCriteria != nil {
+ in, out := &in.SuccessCriteria, &out.SuccessCriteria
+ *out = new(AdmissionControlSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionControl.
+func (in *AdmissionControl) DeepCopy() *AdmissionControl {
+ if in == nil {
+ return nil
+ }
+ out := new(AdmissionControl)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdmissionControlSuccessCriteria) DeepCopyInto(out *AdmissionControlSuccessCriteria) {
+ *out = *in
+ if in.HTTP != nil {
+ in, out := &in.HTTP, &out.HTTP
+ *out = new(HTTPSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.GRPC != nil {
+ in, out := &in.GRPC, &out.GRPC
+ *out = new(GRPCSuccessCriteria)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionControlSuccessCriteria.
+func (in *AdmissionControlSuccessCriteria) DeepCopy() *AdmissionControlSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(AdmissionControlSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Authorization) DeepCopyInto(out *Authorization) {
*out = *in
@@ -1469,6 +1544,26 @@ func (in *GRPCHealthChecker) DeepCopy() *GRPCHealthChecker {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GRPCSuccessCriteria) DeepCopyInto(out *GRPCSuccessCriteria) {
+ *out = *in
+ if in.GRPCSuccessStatus != nil {
+ in, out := &in.GRPCSuccessStatus, &out.GRPCSuccessStatus
+ *out = make([]int32, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCSuccessCriteria.
+func (in *GRPCSuccessCriteria) DeepCopy() *GRPCSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(GRPCSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalRateLimit) DeepCopyInto(out *GlobalRateLimit) {
*out = *in
@@ -1982,6 +2077,41 @@ func (in *HTTPRoute) DeepCopy() *HTTPRoute {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPStatusRange) DeepCopyInto(out *HTTPStatusRange) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPStatusRange.
+func (in *HTTPStatusRange) DeepCopy() *HTTPStatusRange {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPStatusRange)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPSuccessCriteria) DeepCopyInto(out *HTTPSuccessCriteria) {
+ *out = *in
+ if in.HTTPSuccessStatus != nil {
+ in, out := &in.HTTPSuccessStatus, &out.HTTPSuccessStatus
+ *out = make([]HTTPStatusRange, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPSuccessCriteria.
+func (in *HTTPSuccessCriteria) DeepCopy() *HTTPSuccessCriteria {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPSuccessCriteria)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPTimeout) DeepCopyInto(out *HTTPTimeout) {
*out = *in
@@ -4229,6 +4359,11 @@ func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
*out = new(FaultInjection)
(*in).DeepCopyInto(*out)
}
+ if in.AdmissionControl != nil {
+ in, out := &in.AdmissionControl, &out.AdmissionControl
+ *out = new(AdmissionControl)
+ (*in).DeepCopyInto(*out)
+ }
if in.CircuitBreaker != nil {
in, out := &in.CircuitBreaker, &out.CircuitBreaker
*out = new(CircuitBreaker)
diff --git a/internal/xds/translator/admission_control.go b/internal/xds/translator/admission_control.go
new file mode 100644
index 00000000000..2d2afed3510
--- /dev/null
+++ b/internal/xds/translator/admission_control.go
@@ -0,0 +1,252 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package translator
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+ routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
+ admissioncontrolv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/admission_control/v3"
+ hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+ typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
+ "google.golang.org/protobuf/types/known/anypb"
+ "google.golang.org/protobuf/types/known/durationpb"
+ "google.golang.org/protobuf/types/known/wrapperspb"
+
+ egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+ "github.com/envoyproxy/gateway/internal/ir"
+ "github.com/envoyproxy/gateway/internal/utils/proto"
+ "github.com/envoyproxy/gateway/internal/xds/types"
+)
+
+func init() {
+ registerHTTPFilter(&admissionControl{})
+}
+
+type admissionControl struct{}
+
+var _ httpFilter = &admissionControl{}
+
+// patchHCM builds and appends the admission control filter to the HTTP Connection Manager
+// if applicable, and it does not already exist.
+func (*admissionControl) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error {
+ if mgr == nil {
+ return errors.New("hcm is nil")
+ }
+
+ if irListener == nil {
+ return errors.New("ir listener is nil")
+ }
+
+ if !listenerContainsAdmissionControl(irListener) {
+ return nil
+ }
+
+ // Return early if the admission control filter already exists.
+ for _, existingFilter := range mgr.HttpFilters {
+ if existingFilter.Name == string(egv1a1.EnvoyFilterAdmissionControl) {
+ return nil
+ }
+ }
+
+ admissionControlFilter, err := buildHCMAdmissionControlFilter()
+ if err != nil {
+ return err
+ }
+ mgr.HttpFilters = append(mgr.HttpFilters, admissionControlFilter)
+
+ return nil
+}
+
+// buildHCMAdmissionControlFilter returns a basic admission control HTTP filter.
+func buildHCMAdmissionControlFilter() (*hcmv3.HttpFilter, error) {
+ // Create a basic admission control configuration
+ admissionControlProto := &admissioncontrolv3.AdmissionControl{}
+
+ admissionControlAny, err := proto.ToAnyWithValidation(admissionControlProto)
+ if err != nil {
+ return nil, err
+ }
+
+ return &hcmv3.HttpFilter{
+ Name: string(egv1a1.EnvoyFilterAdmissionControl),
+ ConfigType: &hcmv3.HttpFilter_TypedConfig{
+ TypedConfig: admissionControlAny,
+ },
+ }, nil
+}
+
+// patchRoute patches the provided route with the admission control config if applicable.
+func (*admissionControl) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, httpListener *ir.HTTPListener) error {
+ if route == nil || irRoute == nil {
+ return nil
+ }
+
+ // Check if admission control is configured for this route
+ if irRoute.Traffic == nil || irRoute.Traffic.AdmissionControl == nil {
+ return nil
+ }
+
+ admissionControlConfig := irRoute.Traffic.AdmissionControl
+
+ // Skip if admission control is explicitly disabled
+ if admissionControlConfig.Enabled != nil && !*admissionControlConfig.Enabled {
+ return nil
+ }
+
+ // Build the admission control configuration
+ routeCfgProto, err := buildAdmissionControlConfig(admissionControlConfig)
+ if err != nil {
+ return err
+ }
+
+ // Add the admission control filter to the route
+ if route.TypedPerFilterConfig == nil {
+ route.TypedPerFilterConfig = make(map[string]*anypb.Any)
+ }
+
+ routeCfgAny, err := proto.ToAnyWithValidation(routeCfgProto)
+ if err != nil {
+ return err
+ }
+
+ route.TypedPerFilterConfig[string(egv1a1.EnvoyFilterAdmissionControl)] = routeCfgAny
+
+ return nil
+}
+
+// buildAdmissionControlConfig builds the admission control configuration from the IR.
+func buildAdmissionControlConfig(admissionControl *ir.AdmissionControl) (*admissioncontrolv3.AdmissionControl, error) {
+ if admissionControl == nil {
+ return nil, errors.New("admissionControl cannot be nil")
+ }
+
+ config := &admissioncontrolv3.AdmissionControl{}
+
+ // Set enabled (defaults to true if not specified)
+ enabled := true
+ if admissionControl.Enabled != nil {
+ enabled = *admissionControl.Enabled
+ }
+ config.Enabled = &corev3.RuntimeFeatureFlag{
+ DefaultValue: &wrapperspb.BoolValue{Value: enabled},
+ }
+
+ // Set sampling window (defaults to 60s if not specified)
+ samplingWindow := "60s"
+ if admissionControl.SamplingWindow != nil {
+ samplingWindow = admissionControl.SamplingWindow.Duration.String()
+ }
+ duration, err := parseDuration(samplingWindow)
+ if err != nil {
+ return nil, fmt.Errorf("invalid samplingWindow: %w", err)
+ }
+ config.SamplingWindow = durationpb.New(duration)
+
+ // Set success rate threshold (defaults to 0.95 if not specified)
+ // Note: srThreshold is in range [0.0, 1.0], but Percent expects [0.0, 100.0]
+ srThreshold := 0.95
+ if admissionControl.SuccessRateThreshold != nil {
+ srThreshold = *admissionControl.SuccessRateThreshold
+ }
+ config.SrThreshold = &corev3.RuntimePercent{
+ DefaultValue: &typev3.Percent{Value: srThreshold * 100.0},
+ }
+
+ // Set aggression (defaults to 1.0 if not specified)
+ aggression := 1.0
+ if admissionControl.Aggression != nil {
+ aggression = *admissionControl.Aggression
+ }
+ config.Aggression = &corev3.RuntimeDouble{
+ DefaultValue: aggression,
+ }
+
+ // Set RPS threshold (defaults to 1 if not specified)
+ rpsThreshold := uint32(1)
+ if admissionControl.RPSThreshold != nil {
+ rpsThreshold = *admissionControl.RPSThreshold
+ }
+ config.RpsThreshold = &corev3.RuntimeUInt32{
+ DefaultValue: rpsThreshold,
+ }
+
+ // Set max rejection probability (defaults to 0.95 if not specified)
+ // Note: maxRejectionProbability is in range [0.0, 1.0], but Percent expects [0.0, 100.0]
+ maxRejectionProbability := 0.95
+ if admissionControl.MaxRejectionProbability != nil {
+ maxRejectionProbability = *admissionControl.MaxRejectionProbability
+ }
+ config.MaxRejectionProbability = &corev3.RuntimePercent{
+ DefaultValue: &typev3.Percent{Value: maxRejectionProbability * 100.0},
+ }
+
+ // Set success criteria (part of EvaluationCriteria oneof)
+ if admissionControl.SuccessCriteria != nil {
+ successCriteria := &admissioncontrolv3.AdmissionControl_SuccessCriteria{}
+
+ // HTTP success criteria
+ if admissionControl.SuccessCriteria.HTTP != nil && len(admissionControl.SuccessCriteria.HTTP.HTTPSuccessStatus) > 0 {
+ httpCriteria := &admissioncontrolv3.AdmissionControl_SuccessCriteria_HttpCriteria{}
+ for _, statusRange := range admissionControl.SuccessCriteria.HTTP.HTTPSuccessStatus {
+ httpCriteria.HttpSuccessStatus = append(httpCriteria.HttpSuccessStatus, &typev3.Int32Range{
+ Start: statusRange.Start,
+ End: statusRange.End,
+ })
+ }
+ successCriteria.HttpCriteria = httpCriteria
+ }
+
+ // gRPC success criteria
+ if admissionControl.SuccessCriteria.GRPC != nil && len(admissionControl.SuccessCriteria.GRPC.GRPCSuccessStatus) > 0 {
+ grpcCriteria := &admissioncontrolv3.AdmissionControl_SuccessCriteria_GrpcCriteria{}
+ for _, status := range admissionControl.SuccessCriteria.GRPC.GRPCSuccessStatus {
+ grpcCriteria.GrpcSuccessStatus = append(grpcCriteria.GrpcSuccessStatus, uint32(status))
+ }
+ successCriteria.GrpcCriteria = grpcCriteria
+ }
+
+ // Set as EvaluationCriteria (oneof field)
+ config.EvaluationCriteria = &admissioncontrolv3.AdmissionControl_SuccessCriteria_{
+ SuccessCriteria: successCriteria,
+ }
+ }
+
+ return config, nil
+}
+
+// parseDuration parses a duration string and returns a time.Duration.
+func parseDuration(s string) (time.Duration, error) {
+ return time.ParseDuration(s)
+}
+
+// patchResources adds all the other needed resources referenced by this filter.
+func (*admissionControl) patchResources(tCtx *types.ResourceVersionTable, routes []*ir.HTTPRoute) error {
+ // Admission control filter doesn't require additional resources
+ return nil
+}
+
+// listenerContainsAdmissionControl returns true if the provided listener contains
+// any route with admission control configured.
+func listenerContainsAdmissionControl(irListener *ir.HTTPListener) bool {
+ if irListener == nil {
+ return false
+ }
+
+ for _, route := range irListener.Routes {
+ if route.Traffic != nil && route.Traffic.AdmissionControl != nil {
+ // Check if enabled (defaults to true)
+ if route.Traffic.AdmissionControl.Enabled == nil || *route.Traffic.AdmissionControl.Enabled {
+ return true
+ }
+ }
+ }
+
+ return false
+}
diff --git a/internal/xds/translator/admission_control_test.go b/internal/xds/translator/admission_control_test.go
new file mode 100644
index 00000000000..da5a26c77b4
--- /dev/null
+++ b/internal/xds/translator/admission_control_test.go
@@ -0,0 +1,95 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package translator
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/envoyproxy/gateway/internal/ir"
+)
+
+func TestAdmissionControlFilter(t *testing.T) {
+ tests := []struct {
+ name string
+ listener *ir.HTTPListener
+ want bool
+ }{
+ {
+ name: "listener with admission control",
+ listener: &ir.HTTPListener{
+ Routes: []*ir.HTTPRoute{
+ {
+ Traffic: &ir.TrafficFeatures{
+ AdmissionControl: &ir.AdmissionControl{
+ Enabled: func() *bool { b := true; return &b }(),
+ },
+ },
+ },
+ },
+ },
+ want: true,
+ },
+ {
+ name: "listener without admission control",
+ listener: &ir.HTTPListener{
+ Routes: []*ir.HTTPRoute{
+ {
+ Traffic: &ir.TrafficFeatures{},
+ },
+ },
+ },
+ want: false,
+ },
+ {
+ name: "nil listener",
+ listener: nil,
+ want: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := listenerContainsAdmissionControl(tt.listener)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func TestBuildAdmissionControlConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ config *ir.AdmissionControl
+ wantErr bool
+ }{
+ {
+ name: "valid admission control config",
+ config: &ir.AdmissionControl{
+ Enabled: func() *bool { b := true; return &b }(),
+ },
+ wantErr: false,
+ },
+ {
+ name: "nil config",
+ config: nil,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := buildAdmissionControlConfig(tt.config)
+ if tt.wantErr {
+ require.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+ assert.NotNil(t, got)
+ })
+ }
+}
diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go
index dfcf73bf60f..478c5242fb9 100644
--- a/internal/xds/translator/httpfilters.go
+++ b/internal/xds/translator/httpfilters.go
@@ -102,27 +102,29 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
order = 0
case isFilterType(filter, egv1a1.EnvoyFilterFault):
order = 1
- case isFilterType(filter, egv1a1.EnvoyFilterCORS):
+ case isFilterType(filter, egv1a1.EnvoyFilterAdmissionControl):
order = 2
+ case isFilterType(filter, egv1a1.EnvoyFilterCORS):
+ order = 3
case isFilterType(filter, egv1a1.EnvoyFilterHeaderMutation):
// Ensure header mutation run before ext auth which might consume the header.
- order = 3
- case isFilterType(filter, egv1a1.EnvoyFilterExtAuthz):
order = 4
- case isFilterType(filter, egv1a1.EnvoyFilterAPIKeyAuth):
+ case isFilterType(filter, egv1a1.EnvoyFilterExtAuthz):
order = 5
- case isFilterType(filter, egv1a1.EnvoyFilterBasicAuth):
+ case isFilterType(filter, egv1a1.EnvoyFilterAPIKeyAuth):
order = 6
- case isFilterType(filter, egv1a1.EnvoyFilterOAuth2):
+ case isFilterType(filter, egv1a1.EnvoyFilterBasicAuth):
order = 7
- case isFilterType(filter, egv1a1.EnvoyFilterJWTAuthn):
+ case isFilterType(filter, egv1a1.EnvoyFilterOAuth2):
order = 8
- case isFilterType(filter, egv1a1.EnvoyFilterSessionPersistence):
+ case isFilterType(filter, egv1a1.EnvoyFilterJWTAuthn):
order = 9
- case isFilterType(filter, egv1a1.EnvoyFilterBuffer):
+ case isFilterType(filter, egv1a1.EnvoyFilterSessionPersistence):
order = 10
+ case isFilterType(filter, egv1a1.EnvoyFilterBuffer):
+ order = 11
case isFilterType(filter, egv1a1.EnvoyFilterLua):
- order = 11 + mustGetFilterIndex(filter.Name)
+ order = 12 + mustGetFilterIndex(filter.Name)
case isFilterType(filter, egv1a1.EnvoyFilterExtProc):
order = 100 + mustGetFilterIndex(filter.Name)
case isFilterType(filter, egv1a1.EnvoyFilterWasm):
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 5b47c218d71..057ca53babb 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -188,6 +188,43 @@ _Appears in:_
| `GRPC` | ActiveHealthCheckerTypeGRPC defines the GRPC type of health checking.
|
+#### AdmissionControl
+
+
+
+AdmissionControl defines the admission control policy to be applied.
+This configuration probabilistically rejects requests based on the success rate
+of previous requests in a configurable sliding time window.
+
+_Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `enabled` | _boolean_ | false | | Enabled enables or disables the admission control filter.
Defaults to true if not specified. |
+| `samplingWindow` | _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | false | | SamplingWindow defines the time window over which request success rates are calculated.
Defaults to 60s if not specified. |
+| `successRateThreshold` | _float_ | false | | SuccessRateThreshold defines the lowest request success rate at which the filter
will not reject requests. The value should be in the range [0.0, 1.0].
Defaults to 0.95 (95%) if not specified. |
+| `aggression` | _float_ | false | | Aggression controls the rejection probability curve. A value of 1.0 means a linear
increase in rejection probability as the success rate decreases. Higher values
result in more aggressive rejection at higher success rates.
Defaults to 1.0 if not specified. |
+| `rpsThreshold` | _integer_ | false | | RPSThreshold defines the minimum requests per second below which requests will
pass through the filter without rejection. Defaults to 1 if not specified. |
+| `maxRejectionProbability` | _float_ | false | | MaxRejectionProbability represents the upper limit of the rejection probability.
The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified. |
+| `successCriteria` | _[AdmissionControlSuccessCriteria](#admissioncontrolsuccesscriteria)_ | false | | SuccessCriteria defines what constitutes a successful request for both HTTP and gRPC. |
+
+
+#### AdmissionControlSuccessCriteria
+
+
+
+AdmissionControlSuccessCriteria defines the criteria for determining successful requests.
+
+_Appears in:_
+- [AdmissionControl](#admissioncontrol)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `http` | _[HTTPSuccessCriteria](#httpsuccesscriteria)_ | false | | HTTP defines success criteria for HTTP requests. |
+| `grpc` | _[GRPCSuccessCriteria](#grpcsuccesscriteria)_ | false | | GRPC defines success criteria for gRPC requests. |
+
+
#### AppProtocolType
_Underlying type:_ _string_
@@ -541,6 +578,7 @@ _Appears in:_
| `mergeType` | _[MergeType](#mergetype)_ | false | | MergeType determines how this configuration is merged with existing BackendTrafficPolicy
configurations targeting a parent resource. When set, this configuration will be merged
into a parent BackendTrafficPolicy (i.e. the one targeting a Gateway or Listener).
This field cannot be set when targeting a parent resource (Gateway).
If unset, no merging occurs, and only the most specific configuration takes effect. |
| `rateLimit` | _[RateLimitSpec](#ratelimitspec)_ | false | | RateLimit allows the user to limit the number of incoming requests
to a predefined value based on attributes within the traffic flow. |
| `faultInjection` | _[FaultInjection](#faultinjection)_ | false | | FaultInjection defines the fault injection policy to be applied. This configuration can be used to
inject delays and abort requests to mimic failure scenarios such as service failures and overloads |
+| `admissionControl` | _[AdmissionControl](#admissioncontrol)_ | false | | AdmissionControl defines the admission control policy to be applied. This configuration
probabilistically rejects requests based on the success rate of previous requests in a
configurable sliding time window. |
| `useClientProtocol` | _boolean_ | false | | UseClientProtocol configures Envoy to prefer sending requests to backends using
the same HTTP protocol that the incoming request used. Defaults to false, which means
that Envoy will use the protocol indicated by the attached BackendRef. |
| `compression` | _[Compression](#compression) array_ | false | | The compression config for the http streams.
Deprecated: Use Compressor instead. |
| `compressor` | _[Compression](#compression) array_ | false | | The compressor config for the http streams.
This provides more granular control over compression configuration.
Order matters: The first compressor in the list is preferred when q-values in Accept-Encoding are equal. |
@@ -1258,6 +1296,7 @@ _Appears in:_
| ----- | ----------- |
| `envoy.filters.http.health_check` | EnvoyFilterHealthCheck defines the Envoy HTTP health check filter.
|
| `envoy.filters.http.fault` | EnvoyFilterFault defines the Envoy HTTP fault filter.
|
+| `envoy.filters.http.admission_control` | EnvoyFilterAdmissionControl defines the Envoy HTTP admission control filter.
|
| `envoy.filters.http.cors` | EnvoyFilterCORS defines the Envoy HTTP CORS filter.
|
| `envoy.filters.http.ext_authz` | EnvoyFilterExtAuthz defines the Envoy HTTP external authorization filter.
|
| `envoy.filters.http.api_key_auth` | EnvoyFilterAPIKeyAuth defines the Envoy HTTP api key authentication filter.
|
@@ -2179,6 +2218,20 @@ _Appears in:_
| `backendSettings` | _[ClusterSettings](#clustersettings)_ | false | | BackendSettings holds configuration for managing the connection
to the backend. |
+#### GRPCSuccessCriteria
+
+
+
+GRPCSuccessCriteria defines success criteria for gRPC requests.
+
+_Appears in:_
+- [AdmissionControlSuccessCriteria](#admissioncontrolsuccesscriteria)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `grpcSuccessStatus` | _integer array_ | false | | GRPCSuccessStatus defines gRPC status codes that are considered successful. |
+
+
#### Gateway
@@ -2512,6 +2565,35 @@ _Appears in:_
+#### HTTPStatusRange
+
+
+
+HTTPStatusRange defines a range of HTTP status codes.
+
+_Appears in:_
+- [HTTPSuccessCriteria](#httpsuccesscriteria)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `start` | _integer_ | true | | Start is the inclusive start of the status code range (100-600). |
+| `end` | _integer_ | true | | End is the inclusive end of the status code range (100-600). |
+
+
+#### HTTPSuccessCriteria
+
+
+
+HTTPSuccessCriteria defines success criteria for HTTP requests.
+
+_Appears in:_
+- [AdmissionControlSuccessCriteria](#admissioncontrolsuccesscriteria)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `httpSuccessStatus` | _[HTTPStatusRange](#httpstatusrange) array_ | false | | HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
Each range is inclusive on both ends. |
+
+
#### HTTPTimeout
diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml
index 18d794c24a3..c266c0f7122 100644
--- a/test/helm/gateway-crds-helm/all.out.yaml
+++ b/test/helm/gateway-crds-helm/all.out.yaml
@@ -21183,6 +21183,100 @@ spec:
spec:
description: spec defines the desired state of BackendTrafficPolicy.
properties:
+ admissionControl:
+ description: |-
+ AdmissionControl defines the admission control policy to be applied. This configuration
+ probabilistically rejects requests based on the success rate of previous requests in a
+ configurable sliding time window.
+ properties:
+ aggression:
+ description: |-
+ Aggression controls the rejection probability curve. A value of 1.0 means a linear
+ increase in rejection probability as the success rate decreases. Higher values
+ result in more aggressive rejection at higher success rates.
+ Defaults to 1.0 if not specified.
+ minimum: 0
+ type: number
+ enabled:
+ description: |-
+ Enabled enables or disables the admission control filter.
+ Defaults to true if not specified.
+ type: boolean
+ maxRejectionProbability:
+ description: |-
+ MaxRejectionProbability represents the upper limit of the rejection probability.
+ The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ rpsThreshold:
+ description: |-
+ RPSThreshold defines the minimum requests per second below which requests will
+ pass through the filter without rejection. Defaults to 1 if not specified.
+ format: int32
+ minimum: 0
+ type: integer
+ samplingWindow:
+ description: |-
+ SamplingWindow defines the time window over which request success rates are calculated.
+ Defaults to 60s if not specified.
+ type: string
+ successCriteria:
+ description: SuccessCriteria defines what constitutes a successful
+ request for both HTTP and gRPC.
+ properties:
+ grpc:
+ description: GRPC defines success criteria for gRPC requests.
+ properties:
+ grpcSuccessStatus:
+ description: GRPCSuccessStatus defines gRPC status codes
+ that are considered successful.
+ items:
+ format: int32
+ type: integer
+ type: array
+ type: object
+ http:
+ description: HTTP defines success criteria for HTTP requests.
+ properties:
+ httpSuccessStatus:
+ description: |-
+ HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ Each range is inclusive on both ends.
+ items:
+ description: HTTPStatusRange defines a range of HTTP
+ status codes.
+ properties:
+ end:
+ description: End is the inclusive end of the status
+ code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ start:
+ description: Start is the inclusive start of the
+ status code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ required:
+ - end
+ - start
+ type: object
+ type: array
+ type: object
+ type: object
+ successRateThreshold:
+ description: |-
+ SuccessRateThreshold defines the lowest request success rate at which the filter
+ will not reject requests. The value should be in the range [0.0, 1.0].
+ Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ type: object
circuitBreaker:
description: |-
Circuit Breaker settings for the upstream connections and requests.
diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
index e4a2010020e..4bcf469bb7f 100644
--- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
+++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@@ -527,6 +527,100 @@ spec:
spec:
description: spec defines the desired state of BackendTrafficPolicy.
properties:
+ admissionControl:
+ description: |-
+ AdmissionControl defines the admission control policy to be applied. This configuration
+ probabilistically rejects requests based on the success rate of previous requests in a
+ configurable sliding time window.
+ properties:
+ aggression:
+ description: |-
+ Aggression controls the rejection probability curve. A value of 1.0 means a linear
+ increase in rejection probability as the success rate decreases. Higher values
+ result in more aggressive rejection at higher success rates.
+ Defaults to 1.0 if not specified.
+ minimum: 0
+ type: number
+ enabled:
+ description: |-
+ Enabled enables or disables the admission control filter.
+ Defaults to true if not specified.
+ type: boolean
+ maxRejectionProbability:
+ description: |-
+ MaxRejectionProbability represents the upper limit of the rejection probability.
+ The value should be in the range [0.0, 1.0]. Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ rpsThreshold:
+ description: |-
+ RPSThreshold defines the minimum requests per second below which requests will
+ pass through the filter without rejection. Defaults to 1 if not specified.
+ format: int32
+ minimum: 0
+ type: integer
+ samplingWindow:
+ description: |-
+ SamplingWindow defines the time window over which request success rates are calculated.
+ Defaults to 60s if not specified.
+ type: string
+ successCriteria:
+ description: SuccessCriteria defines what constitutes a successful
+ request for both HTTP and gRPC.
+ properties:
+ grpc:
+ description: GRPC defines success criteria for gRPC requests.
+ properties:
+ grpcSuccessStatus:
+ description: GRPCSuccessStatus defines gRPC status codes
+ that are considered successful.
+ items:
+ format: int32
+ type: integer
+ type: array
+ type: object
+ http:
+ description: HTTP defines success criteria for HTTP requests.
+ properties:
+ httpSuccessStatus:
+ description: |-
+ HTTPSuccessStatus defines ranges of HTTP status codes that are considered successful.
+ Each range is inclusive on both ends.
+ items:
+ description: HTTPStatusRange defines a range of HTTP
+ status codes.
+ properties:
+ end:
+ description: End is the inclusive end of the status
+ code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ start:
+ description: Start is the inclusive start of the
+ status code range (100-600).
+ format: int32
+ maximum: 600
+ minimum: 100
+ type: integer
+ required:
+ - end
+ - start
+ type: object
+ type: array
+ type: object
+ type: object
+ successRateThreshold:
+ description: |-
+ SuccessRateThreshold defines the lowest request success rate at which the filter
+ will not reject requests. The value should be in the range [0.0, 1.0].
+ Defaults to 0.95 (95%) if not specified.
+ maximum: 1
+ minimum: 0
+ type: number
+ type: object
circuitBreaker:
description: |-
Circuit Breaker settings for the upstream connections and requests.
diff --git a/tools/make/lint.mk b/tools/make/lint.mk
index 2f1a417b21d..c6a5c06e92e 100644
--- a/tools/make/lint.mk
+++ b/tools/make/lint.mk
@@ -53,7 +53,7 @@ lint.codespell: $(tools/codespell)
# one shell, this is because we want the ::remove-matcher lines to get
# printed whether or not it finds complaints.
@PS4=; set -e; { \
- if test -n "$$GITHUB_ACTION"; then \
+ if test -n "$${GITHUB_ACTION:-}"; then \
printf '::add-matcher::$(CURDIR)/tools/linter/codespell/matcher.json\n'; \
trap "printf '::remove-matcher owner=codespell-matcher-default::\n::remove-matcher owner=codespell-matcher-specified::\n'" EXIT; \
fi; \