From 601218d718f05bc8714bc4cf5e62e7a62fb759a9 Mon Sep 17 00:00:00 2001
From: Clayton Gonsalves <101868649+clayton-gonsalves@users.noreply.github.com>
Date: Thu, 25 Jul 2024 20:43:41 +0200
Subject: [PATCH] Add circuit breaker support for extension services (#6539)
Closes #6537.
Signed-off-by: Clayton Gonsalves
---
apis/projectcontour/v1alpha1/contourconfig.go | 8 +-
.../v1alpha1/extensionservice.go | 5 +
.../v1alpha1/zz_generated.deepcopy.go | 37 +-
.../6539-clayton-gonsalves-minor.md | 6 +
cmd/contour/serve.go | 10 +-
cmd/contour/serve_test.go | 2 +-
cmd/contour/servecontext_test.go | 4 +-
examples/contour/01-crds.yaml | 45 +++
examples/render/contour-deployment.yaml | 45 +++
.../render/contour-gateway-provisioner.yaml | 45 +++
examples/render/contour-gateway.yaml | 45 +++
examples/render/contour.yaml | 45 +++
internal/dag/accessors.go | 16 +-
internal/dag/builder_test.go | 12 +-
internal/dag/dag.go | 47 +--
internal/dag/extension_processor.go | 23 ++
internal/dag/gatewayapi_processor.go | 2 +-
internal/dag/httpproxy_processor.go | 2 +-
internal/dag/ingress_processor.go | 2 +-
internal/dag/policy.go | 22 +-
internal/dag/policy_test.go | 68 ++--
internal/envoy/v3/cluster.go | 36 +-
internal/envoy/v3/cluster_test.go | 20 +-
internal/featuretests/v3/cluster_test.go | 8 +-
.../featuretests/v3/extensionservice_test.go | 340 +++++++++++++++++-
internal/xdscache/v3/cluster_test.go | 2 +-
pkg/config/parameters.go | 2 +-
.../docs/main/config/api-reference.html | 190 ++++++----
28 files changed, 884 insertions(+), 205 deletions(-)
create mode 100644 changelogs/unreleased/6539-clayton-gonsalves-minor.md
diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go
index 05545e30493..0af7a50e08a 100644
--- a/apis/projectcontour/v1alpha1/contourconfig.go
+++ b/apis/projectcontour/v1alpha1/contourconfig.go
@@ -107,7 +107,7 @@ const (
EnvoyServerType XDSServerType = "envoy"
)
-type GlobalCircuitBreakerDefaults struct {
+type CircuitBreakers struct {
// The maximum number of connections that a single Envoy instance allows to the Kubernetes Service; defaults to 1024.
// +optional
MaxConnections uint32 `json:"maxConnections,omitempty" yaml:"max-connections,omitempty"`
@@ -120,6 +120,10 @@ type GlobalCircuitBreakerDefaults struct {
// The maximum number of parallel retries a single Envoy instance allows to the Kubernetes Service; defaults to 3.
// +optional
MaxRetries uint32 `json:"maxRetries,omitempty" yaml:"max-retries,omitempty"`
+
+ // PerHostMaxConnections is the maximum number of connections
+ // that Envoy will allow to each individual host in a cluster.
+ PerHostMaxConnections uint32 `json:"perHostMaxConnections,omitempty" yaml:"per-host-max-connections,omitempty"`
}
// XDSServerConfig holds the config for the Contour xDS server.
@@ -707,7 +711,7 @@ type ClusterParameters struct {
// If defined, this will be used as the default for all services.
//
// +optional
- GlobalCircuitBreakerDefaults *GlobalCircuitBreakerDefaults `json:"circuitBreakers,omitempty"`
+ GlobalCircuitBreakerDefaults *CircuitBreakers `json:"circuitBreakers,omitempty"`
// UpstreamTLS contains the TLS policy parameters for upstream connections
//
diff --git a/apis/projectcontour/v1alpha1/extensionservice.go b/apis/projectcontour/v1alpha1/extensionservice.go
index 20c53058b54..c074de546e7 100644
--- a/apis/projectcontour/v1alpha1/extensionservice.go
+++ b/apis/projectcontour/v1alpha1/extensionservice.go
@@ -104,6 +104,11 @@ type ExtensionServiceSpec struct {
// +optional
// +kubebuilder:validation:Enum=v3
ProtocolVersion ExtensionProtocolVersion `json:"protocolVersion,omitempty"`
+
+ // CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ // If defined this overrides the global circuit breaker budget.
+ // +optional
+ CircuitBreakerPolicy *CircuitBreakers `json:"circuitBreakerPolicy,omitempty"`
}
// ExtensionServiceStatus defines the observed state of an
diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go
index eaa1576bfce..394c22dff6e 100644
--- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go
@@ -47,6 +47,21 @@ func (in AccessLogJSONFields) DeepCopy() AccessLogJSONFields {
return *out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CircuitBreakers) DeepCopyInto(out *CircuitBreakers) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CircuitBreakers.
+func (in *CircuitBreakers) DeepCopy() *CircuitBreakers {
+ if in == nil {
+ return nil
+ }
+ out := new(CircuitBreakers)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterParameters) DeepCopyInto(out *ClusterParameters) {
*out = *in
@@ -62,7 +77,7 @@ func (in *ClusterParameters) DeepCopyInto(out *ClusterParameters) {
}
if in.GlobalCircuitBreakerDefaults != nil {
in, out := &in.GlobalCircuitBreakerDefaults, &out.GlobalCircuitBreakerDefaults
- *out = new(GlobalCircuitBreakerDefaults)
+ *out = new(CircuitBreakers)
**out = **in
}
if in.UpstreamTLS != nil {
@@ -813,6 +828,11 @@ func (in *ExtensionServiceSpec) DeepCopyInto(out *ExtensionServiceSpec) {
*out = new(v1.TimeoutPolicy)
**out = **in
}
+ if in.CircuitBreakerPolicy != nil {
+ in, out := &in.CircuitBreakerPolicy, &out.CircuitBreakerPolicy
+ *out = new(CircuitBreakers)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionServiceSpec.
@@ -897,21 +917,6 @@ func (in *GatewayConfig) DeepCopy() *GatewayConfig {
return out
}
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GlobalCircuitBreakerDefaults) DeepCopyInto(out *GlobalCircuitBreakerDefaults) {
- *out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalCircuitBreakerDefaults.
-func (in *GlobalCircuitBreakerDefaults) DeepCopy() *GlobalCircuitBreakerDefaults {
- if in == nil {
- return nil
- }
- out := new(GlobalCircuitBreakerDefaults)
- in.DeepCopyInto(out)
- return out
-}
-
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HTTPProxyConfig) DeepCopyInto(out *HTTPProxyConfig) {
*out = *in
diff --git a/changelogs/unreleased/6539-clayton-gonsalves-minor.md b/changelogs/unreleased/6539-clayton-gonsalves-minor.md
new file mode 100644
index 00000000000..6601ec2203d
--- /dev/null
+++ b/changelogs/unreleased/6539-clayton-gonsalves-minor.md
@@ -0,0 +1,6 @@
+## Add Circuit Breaker support for Extension Services
+
+This change enables the user to configure the Circuit breakers for extension services either via the global Contour config or on an individual Extension Service.
+
+**NOTE**: The `PerHostMaxConnections` is now also configurable via the global settings.
+
diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go
index 375edb0033b..1c48f9fce88 100644
--- a/cmd/contour/serve.go
+++ b/cmd/contour/serve.go
@@ -1068,7 +1068,7 @@ type dagBuilderConfig struct {
maxRequestsPerConnection *uint32
perConnectionBufferLimitBytes *uint32
globalRateLimitService *contour_v1alpha1.RateLimitServiceConfig
- globalCircuitBreakerDefaults *contour_v1alpha1.GlobalCircuitBreakerDefaults
+ globalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers
upstreamTLS *dag.UpstreamTLS
}
@@ -1145,10 +1145,10 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder {
&dag.ExtensionServiceProcessor{
// Note that ExtensionService does not support ExternalName, if it does get added,
// need to bring EnableExternalNameService in here too.
- FieldLogger: s.log.WithField("context", "ExtensionServiceProcessor"),
- ClientCertificate: dbc.clientCert,
- ConnectTimeout: dbc.connectTimeout,
- UpstreamTLS: dbc.upstreamTLS,
+ FieldLogger: s.log.WithField("context", "ExtensionServiceProcessor"),
+ ClientCertificate: dbc.clientCert,
+ ConnectTimeout: dbc.connectTimeout,
+ GlobalCircuitBreakerDefaults: dbc.globalCircuitBreakerDefaults,
},
&dag.HTTPProxyProcessor{
EnableExternalNameService: dbc.enableExternalNameService,
diff --git a/cmd/contour/serve_test.go b/cmd/contour/serve_test.go
index 248e9bc17c4..f7846e5dffb 100644
--- a/cmd/contour/serve_test.go
+++ b/cmd/contour/serve_test.go
@@ -125,7 +125,7 @@ func TestGetDAGBuilder(t *testing.T) {
})
t.Run("GlobalCircuitBreakerDefaults specified for all processors", func(t *testing.T) {
- g := contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ g := contour_v1alpha1.CircuitBreakers{
MaxConnections: 100,
}
diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go
index 766a2f0af83..cf205d81a46 100644
--- a/cmd/contour/servecontext_test.go
+++ b/cmd/contour/servecontext_test.go
@@ -767,7 +767,7 @@ func TestConvertServeContext(t *testing.T) {
},
"global circuit breaker defaults": {
getServeContext: func(ctx *serveContext) *serveContext {
- ctx.Config.Cluster.GlobalCircuitBreakerDefaults = &contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ ctx.Config.Cluster.GlobalCircuitBreakerDefaults = &contour_v1alpha1.CircuitBreakers{
MaxConnections: 4,
MaxPendingRequests: 5,
MaxRequests: 6,
@@ -776,7 +776,7 @@ func TestConvertServeContext(t *testing.T) {
return ctx
},
getContourConfiguration: func(cfg contour_v1alpha1.ContourConfigurationSpec) contour_v1alpha1.ContourConfigurationSpec {
- cfg.Envoy.Cluster.GlobalCircuitBreakerDefaults = &contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ cfg.Envoy.Cluster.GlobalCircuitBreakerDefaults = &contour_v1alpha1.CircuitBreakers{
MaxConnections: 4,
MaxPendingRequests: 5,
MaxRequests: 6,
diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml
index bef93fe89c9..0beece5bc51 100644
--- a/examples/contour/01-crds.yaml
+++ b/examples/contour/01-crds.yaml
@@ -120,6 +120,12 @@ spec:
defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -3896,6 +3902,12 @@ spec:
Service; defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -5061,6 +5073,39 @@ spec:
description: ExtensionServiceSpec defines the desired state of an ExtensionService
resource.
properties:
+ circuitBreakerPolicy:
+ description: |-
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ If defined this overrides the global circuit breaker budget.
+ properties:
+ maxConnections:
+ description: The maximum number of connections that a single Envoy
+ instance allows to the Kubernetes Service; defaults to 1024.
+ format: int32
+ type: integer
+ maxPendingRequests:
+ description: The maximum number of pending requests that a single
+ Envoy instance allows to the Kubernetes Service; defaults to
+ 1024.
+ format: int32
+ type: integer
+ maxRequests:
+ description: The maximum parallel requests a single Envoy instance
+ allows to the Kubernetes Service; defaults to 1024
+ format: int32
+ type: integer
+ maxRetries:
+ description: The maximum number of parallel retries a single Envoy
+ instance allows to the Kubernetes Service; defaults to 3.
+ format: int32
+ type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
+ type: object
loadBalancerPolicy:
description: |-
The policy for load balancing GRPC service requests. Note that the
diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml
index edfe5756ea6..1e085adae27 100644
--- a/examples/render/contour-deployment.yaml
+++ b/examples/render/contour-deployment.yaml
@@ -340,6 +340,12 @@ spec:
defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -4116,6 +4122,12 @@ spec:
Service; defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -5281,6 +5293,39 @@ spec:
description: ExtensionServiceSpec defines the desired state of an ExtensionService
resource.
properties:
+ circuitBreakerPolicy:
+ description: |-
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ If defined this overrides the global circuit breaker budget.
+ properties:
+ maxConnections:
+ description: The maximum number of connections that a single Envoy
+ instance allows to the Kubernetes Service; defaults to 1024.
+ format: int32
+ type: integer
+ maxPendingRequests:
+ description: The maximum number of pending requests that a single
+ Envoy instance allows to the Kubernetes Service; defaults to
+ 1024.
+ format: int32
+ type: integer
+ maxRequests:
+ description: The maximum parallel requests a single Envoy instance
+ allows to the Kubernetes Service; defaults to 1024
+ format: int32
+ type: integer
+ maxRetries:
+ description: The maximum number of parallel retries a single Envoy
+ instance allows to the Kubernetes Service; defaults to 3.
+ format: int32
+ type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
+ type: object
loadBalancerPolicy:
description: |-
The policy for load balancing GRPC service requests. Note that the
diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml
index 327714592e0..b3633a2e0cd 100644
--- a/examples/render/contour-gateway-provisioner.yaml
+++ b/examples/render/contour-gateway-provisioner.yaml
@@ -131,6 +131,12 @@ spec:
defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -3907,6 +3913,12 @@ spec:
Service; defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -5072,6 +5084,39 @@ spec:
description: ExtensionServiceSpec defines the desired state of an ExtensionService
resource.
properties:
+ circuitBreakerPolicy:
+ description: |-
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ If defined this overrides the global circuit breaker budget.
+ properties:
+ maxConnections:
+ description: The maximum number of connections that a single Envoy
+ instance allows to the Kubernetes Service; defaults to 1024.
+ format: int32
+ type: integer
+ maxPendingRequests:
+ description: The maximum number of pending requests that a single
+ Envoy instance allows to the Kubernetes Service; defaults to
+ 1024.
+ format: int32
+ type: integer
+ maxRequests:
+ description: The maximum parallel requests a single Envoy instance
+ allows to the Kubernetes Service; defaults to 1024
+ format: int32
+ type: integer
+ maxRetries:
+ description: The maximum number of parallel retries a single Envoy
+ instance allows to the Kubernetes Service; defaults to 3.
+ format: int32
+ type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
+ type: object
loadBalancerPolicy:
description: |-
The policy for load balancing GRPC service requests. Note that the
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index 067df730e44..0db31e989d5 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -156,6 +156,12 @@ spec:
defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -3932,6 +3938,12 @@ spec:
Service; defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -5097,6 +5109,39 @@ spec:
description: ExtensionServiceSpec defines the desired state of an ExtensionService
resource.
properties:
+ circuitBreakerPolicy:
+ description: |-
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ If defined this overrides the global circuit breaker budget.
+ properties:
+ maxConnections:
+ description: The maximum number of connections that a single Envoy
+ instance allows to the Kubernetes Service; defaults to 1024.
+ format: int32
+ type: integer
+ maxPendingRequests:
+ description: The maximum number of pending requests that a single
+ Envoy instance allows to the Kubernetes Service; defaults to
+ 1024.
+ format: int32
+ type: integer
+ maxRequests:
+ description: The maximum parallel requests a single Envoy instance
+ allows to the Kubernetes Service; defaults to 1024
+ format: int32
+ type: integer
+ maxRetries:
+ description: The maximum number of parallel retries a single Envoy
+ instance allows to the Kubernetes Service; defaults to 3.
+ format: int32
+ type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
+ type: object
loadBalancerPolicy:
description: |-
The policy for load balancing GRPC service requests. Note that the
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index 49cd4749a20..d78bac68c7b 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -340,6 +340,12 @@ spec:
defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -4116,6 +4122,12 @@ spec:
Service; defaults to 3.
format: int32
type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
type: object
dnsLookupFamily:
description: |-
@@ -5281,6 +5293,39 @@ spec:
description: ExtensionServiceSpec defines the desired state of an ExtensionService
resource.
properties:
+ circuitBreakerPolicy:
+ description: |-
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+ If defined this overrides the global circuit breaker budget.
+ properties:
+ maxConnections:
+ description: The maximum number of connections that a single Envoy
+ instance allows to the Kubernetes Service; defaults to 1024.
+ format: int32
+ type: integer
+ maxPendingRequests:
+ description: The maximum number of pending requests that a single
+ Envoy instance allows to the Kubernetes Service; defaults to
+ 1024.
+ format: int32
+ type: integer
+ maxRequests:
+ description: The maximum parallel requests a single Envoy instance
+ allows to the Kubernetes Service; defaults to 1024
+ format: int32
+ type: integer
+ maxRetries:
+ description: The maximum number of parallel retries a single Envoy
+ instance allows to the Kubernetes Service; defaults to 3.
+ format: int32
+ type: integer
+ perHostMaxConnections:
+ description: |-
+ PerHostMaxConnections is the maximum number of connections
+ that Envoy will allow to each individual host in a cluster.
+ format: int32
+ type: integer
+ type: object
loadBalancerPolicy:
description: |-
The policy for load balancing GRPC service requests. Note that the
diff --git a/internal/dag/accessors.go b/internal/dag/accessors.go
index 20a990b3433..c039d28ccba 100644
--- a/internal/dag/accessors.go
+++ b/internal/dag/accessors.go
@@ -63,13 +63,15 @@ func (d *DAG) EnsureService(meta types.NamespacedName, port, healthPort int, cac
HealthPort: healthSvcPort,
Weight: 1,
},
- Protocol: upstreamProtocol(svc, svcPort),
- MaxConnections: annotation.MaxConnections(svc),
- MaxPendingRequests: annotation.MaxPendingRequests(svc),
- MaxRequests: annotation.MaxRequests(svc),
- MaxRetries: annotation.MaxRetries(svc),
- PerHostMaxConnections: annotation.PerHostMaxConnections(svc),
- ExternalName: externalName(svc),
+ Protocol: upstreamProtocol(svc, svcPort),
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: annotation.MaxConnections(svc),
+ MaxPendingRequests: annotation.MaxPendingRequests(svc),
+ MaxRequests: annotation.MaxRequests(svc),
+ PerHostMaxConnections: annotation.PerHostMaxConnections(svc),
+ MaxRetries: annotation.MaxRetries(svc),
+ },
+ ExternalName: externalName(svc),
}, nil
}
diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go
index 98f4525b890..70f9ee3ed0f 100644
--- a/internal/dag/builder_test.go
+++ b/internal/dag/builder_test.go
@@ -10640,11 +10640,13 @@ func TestDAGInsert(t *testing.T) {
ServicePort: s1b.Spec.Ports[0],
HealthPort: s1b.Spec.Ports[0],
},
- MaxConnections: 9000,
- MaxPendingRequests: 4096,
- MaxRequests: 404,
- MaxRetries: 7,
- PerHostMaxConnections: 45,
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: 9000,
+ MaxPendingRequests: 4096,
+ MaxRequests: 404,
+ MaxRetries: 7,
+ PerHostMaxConnections: 45,
+ },
}),
),
),
diff --git a/internal/dag/dag.go b/internal/dag/dag.go
index 9c67318e0c8..8ec08fea717 100644
--- a/internal/dag/dag.go
+++ b/internal/dag/dag.go
@@ -971,26 +971,7 @@ type Service struct {
Protocol string
// Circuit breaking limits
-
- // Max connections is maximum number of connections
- // that Envoy will make to the upstream cluster.
- MaxConnections uint32
-
- // MaxPendingRequests is maximum number of pending
- // requests that Envoy will allow to the upstream cluster.
- MaxPendingRequests uint32
-
- // MaxRequests is the maximum number of parallel requests that
- // Envoy will make to the upstream cluster.
- MaxRequests uint32
-
- // MaxRetries is the maximum number of parallel retries that
- // Envoy will allow to the upstream cluster.
- MaxRetries uint32
-
- // PerHostMaxConnections is the maximum number of connections
- // that Envoy will allow to each individual host in a cluster.
- PerHostMaxConnections uint32
+ CircuitBreakers CircuitBreakers
// ExternalName is an optional field referencing a dns entry for Service type "ExternalName"
ExternalName string
@@ -1261,6 +1242,9 @@ type ExtensionCluster struct {
// UpstreamTLS contains the TLS version and cipher suite configurations for upstream connections
UpstreamTLS *UpstreamTLS
+
+ // Circuit breaking limits
+ CircuitBreakers CircuitBreakers
}
const singleDNSLabelWildcardRegex = "^[a-z0-9]([-a-z0-9]*[a-z0-9])?"
@@ -1299,3 +1283,26 @@ type UpstreamTLS struct {
MaximumProtocolVersion string
CipherSuites []string
}
+
+// CircuitBreakers holds configuration for circuit breakers.
+type CircuitBreakers struct {
+ // Max connections is maximum number of connections
+ // that Envoy will make to the upstream cluster.
+ MaxConnections uint32
+
+ // MaxPendingRequests is maximum number of pending
+ // requests that Envoy will allow to the upstream cluster.
+ MaxPendingRequests uint32
+
+ // MaxRequests is the maximum number of parallel requests that
+ // Envoy will make to the upstream cluster.
+ MaxRequests uint32
+
+ // MaxRetries is the maximum number of parallel retries that
+ // Envoy will allow to the upstream cluster.
+ MaxRetries uint32
+
+ // PerHostMaxConnections is the maximum number of connections
+ // that Envoy will allow to each individual host in a cluster.
+ PerHostMaxConnections uint32
+}
diff --git a/internal/dag/extension_processor.go b/internal/dag/extension_processor.go
index d954f89d300..8d03f60a360 100644
--- a/internal/dag/extension_processor.go
+++ b/internal/dag/extension_processor.go
@@ -44,6 +44,9 @@ type ExtensionServiceProcessor struct {
// UpstreamTLS defines the TLS settings like min/max version
// and cipher suites for upstream connections.
UpstreamTLS *UpstreamTLS
+
+ // GlobalCircuitBreakerDefaults defines global circuit breaker defaults.
+ GlobalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers
}
var _ Processor = &ExtensionServiceProcessor{}
@@ -122,6 +125,26 @@ func (p *ExtensionServiceProcessor) buildExtensionService(
UpstreamTLS: p.UpstreamTLS,
}
+ if p.GlobalCircuitBreakerDefaults != nil {
+ extension.CircuitBreakers = CircuitBreakers{
+ MaxConnections: p.GlobalCircuitBreakerDefaults.MaxConnections,
+ MaxPendingRequests: p.GlobalCircuitBreakerDefaults.MaxPendingRequests,
+ MaxRequests: p.GlobalCircuitBreakerDefaults.MaxRequests,
+ MaxRetries: p.GlobalCircuitBreakerDefaults.MaxRetries,
+ PerHostMaxConnections: p.GlobalCircuitBreakerDefaults.PerHostMaxConnections,
+ }
+ }
+
+ if ext.Spec.CircuitBreakerPolicy != nil {
+ extension.CircuitBreakers = CircuitBreakers{
+ MaxConnections: ext.Spec.CircuitBreakerPolicy.MaxConnections,
+ MaxPendingRequests: ext.Spec.CircuitBreakerPolicy.MaxPendingRequests,
+ MaxRequests: ext.Spec.CircuitBreakerPolicy.MaxRequests,
+ MaxRetries: ext.Spec.CircuitBreakerPolicy.MaxRetries,
+ PerHostMaxConnections: ext.Spec.CircuitBreakerPolicy.PerHostMaxConnections,
+ }
+ }
+
lbPolicy := loadBalancerPolicy(ext.Spec.LoadBalancerPolicy)
switch lbPolicy {
case LoadBalancerPolicyCookie, LoadBalancerPolicyRequestHash:
diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go
index 638ded0a939..5dd9c1deb6e 100644
--- a/internal/dag/gatewayapi_processor.go
+++ b/internal/dag/gatewayapi_processor.go
@@ -76,7 +76,7 @@ type GatewayAPIProcessor struct {
SetSourceMetadataOnRoutes bool
// GlobalCircuitBreakerDefaults defines global circuit breaker defaults.
- GlobalCircuitBreakerDefaults *contour_v1alpha1.GlobalCircuitBreakerDefaults
+ GlobalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers
// UpstreamTLS defines the TLS settings like min/max version
// and cipher suites for upstream connections.
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index 7dac1510315..083fd741353 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -114,7 +114,7 @@ type HTTPProxyProcessor struct {
SetSourceMetadataOnRoutes bool
// GlobalCircuitBreakerDefaults defines global circuit breaker defaults.
- GlobalCircuitBreakerDefaults *contour_v1alpha1.GlobalCircuitBreakerDefaults
+ GlobalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers
// UpstreamTLS defines the TLS settings like min/max version
// and cipher suites for upstream connections.
diff --git a/internal/dag/ingress_processor.go b/internal/dag/ingress_processor.go
index d5dd05e5bed..63639e22712 100644
--- a/internal/dag/ingress_processor.go
+++ b/internal/dag/ingress_processor.go
@@ -68,7 +68,7 @@ type IngressProcessor struct {
SetSourceMetadataOnRoutes bool
// GlobalCircuitBreakerDefaults defines global circuit breaker defaults.
- GlobalCircuitBreakerDefaults *contour_v1alpha1.GlobalCircuitBreakerDefaults
+ GlobalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers
// UpstreamTLS defines the TLS settings like min/max version
// and cipher suites for upstream connections.
diff --git a/internal/dag/policy.go b/internal/dag/policy.go
index 9360d641c1e..720d959e534 100644
--- a/internal/dag/policy.go
+++ b/internal/dag/policy.go
@@ -808,25 +808,29 @@ func loadBalancerRequestHashPolicies(lbp *contour_v1.LoadBalancerPolicy, validCo
}
}
-func serviceCircuitBreakerPolicy(s *Service, cb *contour_v1alpha1.GlobalCircuitBreakerDefaults) *Service {
+func serviceCircuitBreakerPolicy(s *Service, cb *contour_v1alpha1.CircuitBreakers) *Service {
if s == nil {
return nil
}
- if s.MaxConnections == 0 && cb != nil {
- s.MaxConnections = cb.MaxConnections
+ if s.CircuitBreakers.MaxConnections == 0 && cb != nil {
+ s.CircuitBreakers.MaxConnections = cb.MaxConnections
}
- if s.MaxPendingRequests == 0 && cb != nil {
- s.MaxPendingRequests = cb.MaxPendingRequests
+ if s.CircuitBreakers.MaxPendingRequests == 0 && cb != nil {
+ s.CircuitBreakers.MaxPendingRequests = cb.MaxPendingRequests
}
- if s.MaxRequests == 0 && cb != nil {
- s.MaxRequests = cb.MaxRequests
+ if s.CircuitBreakers.MaxRequests == 0 && cb != nil {
+ s.CircuitBreakers.MaxRequests = cb.MaxRequests
}
- if s.MaxRetries == 0 && cb != nil {
- s.MaxRetries = cb.MaxRetries
+ if s.CircuitBreakers.MaxRetries == 0 && cb != nil {
+ s.CircuitBreakers.MaxRetries = cb.MaxRetries
+ }
+
+ if s.CircuitBreakers.PerHostMaxConnections == 0 && cb != nil {
+ s.CircuitBreakers.PerHostMaxConnections = cb.PerHostMaxConnections
}
return s
diff --git a/internal/dag/policy_test.go b/internal/dag/policy_test.go
index 62301bbb1d6..ab006db9b57 100644
--- a/internal/dag/policy_test.go
+++ b/internal/dag/policy_test.go
@@ -1276,7 +1276,7 @@ func TestValidateHeaderAlteration(t *testing.T) {
func TestServiceCircuitBreakerPolicy(t *testing.T) {
tests := map[string]struct {
in *Service
- globalDefault *contour_v1alpha1.GlobalCircuitBreakerDefaults
+ globalDefault *contour_v1alpha1.CircuitBreakers
want *Service
}{
"service is nil and globalDefault is nil": {
@@ -1286,51 +1286,65 @@ func TestServiceCircuitBreakerPolicy(t *testing.T) {
},
"service is nil and globalDefault is not nil": {
in: nil,
- globalDefault: &contour_v1alpha1.GlobalCircuitBreakerDefaults{},
+ globalDefault: &contour_v1alpha1.CircuitBreakers{},
want: nil,
},
"service is not nil and globalDefault is nil": {
in: &Service{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
- MaxRetries: 13,
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ MaxRetries: 13,
+ PerHostMaxConnections: 23,
+ },
},
globalDefault: nil,
want: &Service{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
- MaxRetries: 13,
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ MaxRetries: 13,
+ PerHostMaxConnections: 23,
+ },
},
},
"service is not set but global is": {
in: &Service{},
- globalDefault: &contour_v1alpha1.GlobalCircuitBreakerDefaults{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
- MaxRetries: 13,
+ globalDefault: &contour_v1alpha1.CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ MaxRetries: 13,
+ PerHostMaxConnections: 23,
},
want: &Service{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
- MaxRetries: 13,
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ MaxRetries: 13,
+ PerHostMaxConnections: 23,
+ },
},
},
"service is not set but global is partial": {
in: &Service{},
- globalDefault: &contour_v1alpha1.GlobalCircuitBreakerDefaults{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
+ globalDefault: &contour_v1alpha1.CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ PerHostMaxConnections: 23,
},
want: &Service{
- MaxConnections: 42,
- MaxPendingRequests: 73,
- MaxRequests: 89,
- MaxRetries: 0,
+ CircuitBreakers: CircuitBreakers{
+ MaxConnections: 42,
+ MaxPendingRequests: 73,
+ MaxRequests: 89,
+ MaxRetries: 0,
+ PerHostMaxConnections: 23,
+ },
},
},
}
diff --git a/internal/envoy/v3/cluster.go b/internal/envoy/v3/cluster.go
index 40fe15afd52..24346c48421 100644
--- a/internal/envoy/v3/cluster.go
+++ b/internal/envoy/v3/cluster.go
@@ -79,21 +79,7 @@ func Cluster(c *dag.Cluster) *envoy_config_cluster_v3.Cluster {
cluster.IgnoreHealthOnHostRemoval = true
}
- if envoy.AnyPositive(service.MaxConnections, service.MaxPendingRequests, service.MaxRequests, service.MaxRetries, service.PerHostMaxConnections) {
- cluster.CircuitBreakers = &envoy_config_cluster_v3.CircuitBreakers{
- Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
- MaxConnections: protobuf.UInt32OrNil(service.MaxConnections),
- MaxPendingRequests: protobuf.UInt32OrNil(service.MaxPendingRequests),
- MaxRequests: protobuf.UInt32OrNil(service.MaxRequests),
- MaxRetries: protobuf.UInt32OrNil(service.MaxRetries),
- TrackRemaining: true,
- }},
- PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
- MaxConnections: protobuf.UInt32OrNil(service.PerHostMaxConnections),
- TrackRemaining: true,
- }},
- }
- }
+ applyCircuitBreakers(cluster, service.CircuitBreakers)
httpVersion := HTTPVersionAuto
switch c.Protocol {
@@ -198,9 +184,29 @@ func ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_cluster_v3.Cluste
}
cluster.TypedExtensionProtocolOptions = protocolOptions(http2Version, ext.ClusterTimeoutPolicy.IdleConnectionTimeout, nil)
+ applyCircuitBreakers(cluster, ext.CircuitBreakers)
+
return cluster
}
+func applyCircuitBreakers(cluster *envoy_config_cluster_v3.Cluster, settings dag.CircuitBreakers) {
+ if envoy.AnyPositive(settings.MaxConnections, settings.MaxPendingRequests, settings.MaxRequests, settings.MaxRetries, settings.PerHostMaxConnections) {
+ cluster.CircuitBreakers = &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: protobuf.UInt32OrNil(settings.MaxConnections),
+ MaxPendingRequests: protobuf.UInt32OrNil(settings.MaxPendingRequests),
+ MaxRequests: protobuf.UInt32OrNil(settings.MaxRequests),
+ MaxRetries: protobuf.UInt32OrNil(settings.MaxRetries),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: protobuf.UInt32OrNil(settings.PerHostMaxConnections),
+ TrackRemaining: true,
+ }},
+ }
+ }
+}
+
// DNSNameCluster builds a envoy_config_cluster_v3.Cluster for the given *dag.DNSNameCluster.
func DNSNameCluster(c *dag.DNSNameCluster) *envoy_config_cluster_v3.Cluster {
cluster := clusterDefaults()
diff --git a/internal/envoy/v3/cluster_test.go b/internal/envoy/v3/cluster_test.go
index ff6bf79a012..cc9f64bcec5 100644
--- a/internal/envoy/v3/cluster_test.go
+++ b/internal/envoy/v3/cluster_test.go
@@ -406,7 +406,9 @@ func TestCluster(t *testing.T) {
"projectcontour.io/max-connections": {
cluster: &dag.Cluster{
Upstream: &dag.Service{
- MaxConnections: 9000,
+ CircuitBreakers: dag.CircuitBreakers{
+ MaxConnections: 9000,
+ },
Weighted: dag.WeightedService{
Weight: 1,
ServiceName: s1.Name,
@@ -438,7 +440,9 @@ func TestCluster(t *testing.T) {
"projectcontour.io/max-pending-requests": {
cluster: &dag.Cluster{
Upstream: &dag.Service{
- MaxPendingRequests: 4096,
+ CircuitBreakers: dag.CircuitBreakers{
+ MaxPendingRequests: 4096,
+ },
Weighted: dag.WeightedService{
Weight: 1,
ServiceName: s1.Name,
@@ -470,7 +474,9 @@ func TestCluster(t *testing.T) {
"projectcontour.io/max-requests": {
cluster: &dag.Cluster{
Upstream: &dag.Service{
- MaxRequests: 404,
+ CircuitBreakers: dag.CircuitBreakers{
+ MaxRequests: 404,
+ },
Weighted: dag.WeightedService{
Weight: 1,
ServiceName: s1.Name,
@@ -502,7 +508,9 @@ func TestCluster(t *testing.T) {
"projectcontour.io/max-retries": {
cluster: &dag.Cluster{
Upstream: &dag.Service{
- MaxRetries: 7,
+ CircuitBreakers: dag.CircuitBreakers{
+ MaxRetries: 7,
+ },
Weighted: dag.WeightedService{
Weight: 1,
ServiceName: s1.Name,
@@ -534,7 +542,9 @@ func TestCluster(t *testing.T) {
"projectcontour.io/per-host-max-connections": {
cluster: &dag.Cluster{
Upstream: &dag.Service{
- PerHostMaxConnections: 45,
+ CircuitBreakers: dag.CircuitBreakers{
+ PerHostMaxConnections: 45,
+ },
Weighted: dag.WeightedService{
Weight: 1,
ServiceName: s1.Name,
diff --git a/internal/featuretests/v3/cluster_test.go b/internal/featuretests/v3/cluster_test.go
index ff4eb77c07d..2680585ba4b 100644
--- a/internal/featuretests/v3/cluster_test.go
+++ b/internal/featuretests/v3/cluster_test.go
@@ -391,7 +391,7 @@ func TestCDSResourceFiltering(t *testing.T) {
})
}
-func circuitBreakerGlobalOpt(t *testing.T, g *contour_v1alpha1.GlobalCircuitBreakerDefaults) func(*dag.Builder) {
+func circuitBreakerGlobalOpt(t *testing.T, g *contour_v1alpha1.CircuitBreakers) func(*dag.Builder) {
return func(b *dag.Builder) {
log := fixture.NewTestLogger(t)
log.SetLevel(logrus.DebugLevel)
@@ -414,7 +414,7 @@ func circuitBreakerGlobalOpt(t *testing.T, g *contour_v1alpha1.GlobalCircuitBrea
}
func TestClusterCircuitbreakerAnnotationsIngress(t *testing.T) {
- g := &contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ g := &contour_v1alpha1.CircuitBreakers{
MaxConnections: 13,
MaxPendingRequests: 14,
MaxRequests: 15,
@@ -549,7 +549,7 @@ func TestClusterCircuitbreakerAnnotationsIngress(t *testing.T) {
}
func TestClusterCircuitbreakerAnnotationsHTTPProxy(t *testing.T) {
- g := &contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ g := &contour_v1alpha1.CircuitBreakers{
MaxConnections: 13,
MaxPendingRequests: 14,
MaxRequests: 15,
@@ -692,7 +692,7 @@ func TestClusterCircuitbreakerAnnotationsHTTPProxy(t *testing.T) {
}
func TestClusterCircuitbreakerAnnotationsGateway(t *testing.T) {
- g := &contour_v1alpha1.GlobalCircuitBreakerDefaults{
+ g := &contour_v1alpha1.CircuitBreakers{
MaxConnections: 13,
MaxPendingRequests: 14,
MaxRequests: 15,
diff --git a/internal/featuretests/v3/extensionservice_test.go b/internal/featuretests/v3/extensionservice_test.go
index 93314db9505..b341882e4d7 100644
--- a/internal/featuretests/v3/extensionservice_test.go
+++ b/internal/featuretests/v3/extensionservice_test.go
@@ -23,11 +23,13 @@ import (
envoy_transport_socket_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
+ "google.golang.org/protobuf/types/known/wrapperspb"
core_v1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
+ "github.com/projectcontour/contour/internal/dag"
envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3"
"github.com/projectcontour/contour/internal/featuretests"
"github.com/projectcontour/contour/internal/fixture"
@@ -402,23 +404,341 @@ func extInvalidLoadBalancerPolicy(t *testing.T, rh ResourceEventHandlerWrapper,
})
}
+func extCircuitBreakers(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) {
+ ext := &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "Cookie",
+ },
+ CircuitBreakerPolicy: &contour_v1alpha1.CircuitBreakers{
+ MaxConnections: 10000,
+ MaxPendingRequests: 1048,
+ MaxRequests: 494,
+ MaxRetries: 10,
+ PerHostMaxConnections: 1,
+ },
+ },
+ }
+
+ rh.OnAdd(ext)
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ CircuitBreakers: &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(10000),
+ MaxPendingRequests: wrapperspb.UInt32(1048),
+ MaxRequests: wrapperspb.UInt32(494),
+ MaxRetries: wrapperspb.UInt32(10),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(1),
+ TrackRemaining: true,
+ }},
+ },
+ },
+ ),
+ ),
+ })
+
+ rh.OnUpdate(ext, &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "RequestHash",
+ },
+ },
+ })
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ },
+ ),
+ ),
+ })
+}
+
+func extGlobalCircuitBreakers(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) {
+ ext := &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "Cookie",
+ },
+ },
+ }
+
+ rh.OnAdd(ext)
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ CircuitBreakers: &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(20000),
+ MaxPendingRequests: wrapperspb.UInt32(2048),
+ MaxRequests: wrapperspb.UInt32(294),
+ MaxRetries: wrapperspb.UInt32(20),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(10),
+ TrackRemaining: true,
+ }},
+ },
+ },
+ ),
+ ),
+ })
+
+ rh.OnUpdate(ext, &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "RequestHash",
+ },
+ },
+ })
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ CircuitBreakers: &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(20000),
+ MaxPendingRequests: wrapperspb.UInt32(2048),
+ MaxRequests: wrapperspb.UInt32(294),
+ MaxRetries: wrapperspb.UInt32(20),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(10),
+ TrackRemaining: true,
+ }},
+ },
+ },
+ ),
+ ),
+ })
+}
+
+func overrideExtGlobalCircuitBreakers(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) {
+ ext := &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "Cookie",
+ },
+ CircuitBreakerPolicy: &contour_v1alpha1.CircuitBreakers{
+ MaxConnections: 30000,
+ MaxPendingRequests: 3048,
+ MaxRequests: 394,
+ MaxRetries: 30,
+ PerHostMaxConnections: 30,
+ },
+ },
+ }
+
+ rh.OnAdd(ext)
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ CircuitBreakers: &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(30000),
+ MaxPendingRequests: wrapperspb.UInt32(3048),
+ MaxRequests: wrapperspb.UInt32(394),
+ MaxRetries: wrapperspb.UInt32(30),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(30),
+ TrackRemaining: true,
+ }},
+ },
+ },
+ ),
+ ),
+ })
+
+ rh.OnUpdate(ext, &contour_v1alpha1.ExtensionService{
+ ObjectMeta: fixture.ObjectMeta("ns/ext"),
+ Spec: contour_v1alpha1.ExtensionServiceSpec{
+ Services: []contour_v1alpha1.ExtensionServiceTarget{
+ {Name: "svc1", Port: 8081},
+ {Name: "svc2", Port: 8082},
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "RequestHash",
+ },
+ },
+ })
+
+ c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{
+ TypeUrl: clusterType,
+ Resources: resources(t,
+ DefaultCluster(
+ // Default load balancer policy should be set as we were passed
+ // an invalid value, we can assert we get a basic cluster.
+ h2cCluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")),
+ &envoy_config_cluster_v3.Cluster{
+ TransportSocket: envoy_v3.UpstreamTLSTransportSocket(
+ &envoy_transport_socket_tls_v3.UpstreamTlsContext{
+ CommonTlsContext: &envoy_transport_socket_tls_v3.CommonTlsContext{
+ AlpnProtocols: []string{"h2"},
+ },
+ },
+ ),
+ CircuitBreakers: &envoy_config_cluster_v3.CircuitBreakers{
+ Thresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(20000),
+ MaxPendingRequests: wrapperspb.UInt32(2048),
+ MaxRequests: wrapperspb.UInt32(294),
+ MaxRetries: wrapperspb.UInt32(20),
+ TrackRemaining: true,
+ }},
+ PerHostThresholds: []*envoy_config_cluster_v3.CircuitBreakers_Thresholds{{
+ MaxConnections: wrapperspb.UInt32(10),
+ TrackRemaining: true,
+ }},
+ },
+ },
+ ),
+ ),
+ })
+}
+
func TestExtensionService(t *testing.T) {
subtests := map[string]func(*testing.T, ResourceEventHandlerWrapper, *Contour){
- "Basic": extBasic,
- "Cleartext": extCleartext,
- "UpstreamValidation": extUpstreamValidation,
- "ExternalName": extExternalName,
- "IdleConnectionTimeout": extIdleConnectionTimeout,
- "MissingService": extMissingService,
- "InconsistentProto": extInconsistentProto,
- "InvalidTimeout": extInvalidTimeout,
- "InvalidLoadBalancerPolicy": extInvalidLoadBalancerPolicy,
+ "Basic": extBasic,
+ "Cleartext": extCleartext,
+ "UpstreamValidation": extUpstreamValidation,
+ "ExternalName": extExternalName,
+ "IdleConnectionTimeout": extIdleConnectionTimeout,
+ "MissingService": extMissingService,
+ "InconsistentProto": extInconsistentProto,
+ "InvalidTimeout": extInvalidTimeout,
+ "InvalidLoadBalancerPolicy": extInvalidLoadBalancerPolicy,
+ "CircuitBreakers": extCircuitBreakers,
+ "GlobalCircuitBreakers": extGlobalCircuitBreakers,
+ "OverrideGlobalCircuitBreakers": overrideExtGlobalCircuitBreakers,
}
for n, f := range subtests {
f := f
t.Run(n, func(t *testing.T) {
- rh, c, done := setup(t)
+ var (
+ rh ResourceEventHandlerWrapper
+ c *Contour
+ done func()
+ )
+
+ switch n {
+ case "GlobalCircuitBreakers", "OverrideGlobalCircuitBreakers":
+ rh, c, done = setup(t,
+ func(b *dag.Builder) {
+ for _, processor := range b.Processors {
+ if extensionProcessor, ok := processor.(*dag.ExtensionServiceProcessor); ok {
+ extensionProcessor.GlobalCircuitBreakerDefaults = &contour_v1alpha1.CircuitBreakers{
+ MaxConnections: 20000,
+ MaxPendingRequests: 2048,
+ MaxRequests: 294,
+ MaxRetries: 20,
+ PerHostMaxConnections: 10,
+ }
+ }
+ }
+ })
+
+ default:
+ rh, c, done = setup(t)
+ }
+
defer done()
// Add common test fixtures.
diff --git a/internal/xdscache/v3/cluster_test.go b/internal/xdscache/v3/cluster_test.go
index 585c3bfea57..b370948c6e8 100644
--- a/internal/xdscache/v3/cluster_test.go
+++ b/internal/xdscache/v3/cluster_test.go
@@ -724,7 +724,7 @@ func TestClusterVisit(t *testing.T) {
},
),
},
- "circuitbreaker annotations": {
+ "CircuitBreakers annotations": {
objs: []any{
&networking_v1.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go
index 51a1ba3eec8..04931c8d349 100644
--- a/pkg/config/parameters.go
+++ b/pkg/config/parameters.go
@@ -443,7 +443,7 @@ type ClusterParameters struct {
// GlobalCircuitBreakerDefaults holds configurable global defaults for the circuit breakers.
//
// +optional
- GlobalCircuitBreakerDefaults *contour_v1alpha1.GlobalCircuitBreakerDefaults `yaml:"circuit-breakers,omitempty"`
+ GlobalCircuitBreakerDefaults *contour_v1alpha1.CircuitBreakers `yaml:"circuit-breakers,omitempty"`
// UpstreamTLS contains the TLS policy parameters for upstream connections
UpstreamTLS ProtocolParameters `yaml:"upstream-tls,omitempty"`
diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html
index 8dba46b5e33..57bc87795fd 100644
--- a/site/content/docs/main/config/api-reference.html
+++ b/site/content/docs/main/config/api-reference.html
@@ -5522,6 +5522,22 @@ ExtensionService
protocol options will be available in future.
+
+
+circuitBreakerPolicy
+
+
+
+CircuitBreakers
+
+
+ |
+
+(Optional)
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+If defined this overrides the global circuit breaker budget.
+ |
+
@@ -5610,6 +5626,90 @@ AccessLogType
+CircuitBreakers
+
+
+(Appears on:
+ClusterParameters,
+ExtensionServiceSpec)
+
+
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+maxConnections
+
+
+uint32
+
+ |
+
+(Optional)
+ The maximum number of connections that a single Envoy instance allows to the Kubernetes Service; defaults to 1024.
+ |
+
+
+
+maxPendingRequests
+
+
+uint32
+
+ |
+
+(Optional)
+ The maximum number of pending requests that a single Envoy instance allows to the Kubernetes Service; defaults to 1024.
+ |
+
+
+
+maxRequests
+
+
+uint32
+
+ |
+
+(Optional)
+ The maximum parallel requests a single Envoy instance allows to the Kubernetes Service; defaults to 1024
+ |
+
+
+
+maxRetries
+
+
+uint32
+
+ |
+
+(Optional)
+ The maximum number of parallel retries a single Envoy instance allows to the Kubernetes Service; defaults to 3.
+ |
+
+
+
+perHostMaxConnections
+
+
+uint32
+
+ |
+
+ PerHostMaxConnections is the maximum number of connections
+that Envoy will allow to each individual host in a cluster.
+ |
+
+
+
ClusterDNSFamilyType
(string
alias)
@@ -5723,8 +5823,8 @@
ClusterParameters
circuitBreakers
-
-GlobalCircuitBreakerDefaults
+
+CircuitBreakers
@@ -7529,6 +7629,22 @@ ExtensionServiceSpec
protocol options will be available in future.
+
+
+circuitBreakerPolicy
+
+
+
+CircuitBreakers
+
+
+ |
+
+(Optional)
+ CircuitBreakerPolicy specifies the circuit breaker budget across the extension service.
+If defined this overrides the global circuit breaker budget.
+ |
+
ExtensionServiceStatus
@@ -7671,76 +7787,6 @@ GatewayConfig
-GlobalCircuitBreakerDefaults
-
-
-(Appears on:
-ClusterParameters)
-
-
-
-
-
-
-Field |
-Description |
-
-
-
-
-
-maxConnections
-
-
-uint32
-
- |
-
-(Optional)
- The maximum number of connections that a single Envoy instance allows to the Kubernetes Service; defaults to 1024.
- |
-
-
-
-maxPendingRequests
-
-
-uint32
-
- |
-
-(Optional)
- The maximum number of pending requests that a single Envoy instance allows to the Kubernetes Service; defaults to 1024.
- |
-
-
-
-maxRequests
-
-
-uint32
-
- |
-
-(Optional)
- The maximum parallel requests a single Envoy instance allows to the Kubernetes Service; defaults to 1024
- |
-
-
-
-maxRetries
-
-
-uint32
-
- |
-
-(Optional)
- The maximum number of parallel retries a single Envoy instance allows to the Kubernetes Service; defaults to 3.
- |
-
-
-
HTTPProxyConfig