Repository: envoyproxy/gateway

Issue:
Support per_host_thresholds for Circuit Breakers
*Description*:
>Describe the desired behavior, what scenario it enables and how it
would be used.

I'd like to configure Circuit Breakers under `per_host_thresholds` so I can set connection limits per-host.

[optional *Relevant Links*:]
- Envoy docs: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/circuit_breaking
- Envoy proto: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/circuit_breaker.proto

Discussion:

Solution patch:
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index d230e2a4c..2d4c4ab5b 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -77,7 +77,6 @@ type BackendTrafficPolicySpec struct {
 	// If not specified, the default upgrade configuration(websocket) will be used.
 	//
 	// +optional
-	// +notImplementedHide
 	HTTPUpgrade []*ProtocolUpgradeConfig `json:"httpUpgrade,omitempty"`
 }
 
@@ -87,9 +86,8 @@ type ProtocolUpgradeConfig struct {
 	//
 	// +kubebuilder:validation:Required
 	Type string `json:"type"`
-	// Disabled indicates whether the upgrade is disabled.
-	// +optional
-	Disabled *bool `json:"disabled"`
+
+	// TODO: support more options for CONNECT
 }
 
 // BackendTrafficPolicyList contains a list of BackendTrafficPolicy resources.
diff --git a/api/v1alpha1/circuitbreaker_types.go b/api/v1alpha1/circuitbreaker_types.go
index c9147379a..dfe99710a 100644
--- a/api/v1alpha1/circuitbreaker_types.go
+++ b/api/v1alpha1/circuitbreaker_types.go
@@ -46,4 +46,20 @@ type CircuitBreaker struct {
 	// +kubebuilder:validation:Maximum=4294967295
 	// +optional
 	MaxRequestsPerConnection *int64 `json:"maxRequestsPerConnection,omitempty"`
+
+	// PerEndpoint defines Circuit Breakers that will apply per-endpoint for an upstream cluster
+	//
+	// +optional
+	PerEndpoint *PerEndpointCircuitBreakers `json:"perEndpoint,omitempty"`
+}
+
+// PerEndpointCircuitBreakers defines Circuit Breakers that will apply per-endpoint for an upstream cluster
+type PerEndpointCircuitBreakers struct {
+	// MaxConnections configures the maximum number of connections that Envoy will establish per-endpoint to the referenced backend defined within a xRoute rule.
+	//
+	// +kubebuilder:validation:Minimum=0
+	// +kubebuilder:validation:Maximum=4294967295
+	// +kubebuilder:default=1024
+	// +optional
+	MaxConnections *int64 `json:"maxConnections,omitempty"`
 }
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 771ef318b..0b44bb424 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -633,7 +633,7 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
 			if (*in)[i] != nil {
 				in, out := &(*in)[i], &(*out)[i]
 				*out = new(ProtocolUpgradeConfig)
-				(*in).DeepCopyInto(*out)
+				**out = **in
 			}
 		}
 	}
@@ -773,6 +773,11 @@ func (in *CircuitBreaker) DeepCopyInto(out *CircuitBreaker) {
 		*out = new(int64)
 		**out = **in
 	}
+	if in.PerEndpoint != nil {
+		in, out := &in.PerEndpoint, &out.PerEndpoint
+		*out = new(PerEndpointCircuitBreakers)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CircuitBreaker.
@@ -4365,6 +4370,26 @@ func (in *PathSettings) DeepCopy() *PathSettings {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PerEndpointCircuitBreakers) DeepCopyInto(out *PerEndpointCircuitBreakers) {
+	*out = *in
+	if in.MaxConnections != nil {
+		in, out := &in.MaxConnections, &out.MaxConnections
+		*out = new(int64)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PerEndpointCircuitBreakers.
+func (in *PerEndpointCircuitBreakers) DeepCopy() *PerEndpointCircuitBreakers {
+	if in == nil {
+		return nil
+	}
+	out := new(PerEndpointCircuitBreakers)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PerRetryPolicy) DeepCopyInto(out *PerRetryPolicy) {
 	*out = *in
@@ -4484,11 +4509,6 @@ func (in *ProcessingModeOptions) DeepCopy() *ProcessingModeOptions {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ProtocolUpgradeConfig) DeepCopyInto(out *ProtocolUpgradeConfig) {
 	*out = *in
-	if in.Disabled != nil {
-		in, out := &in.Disabled, &out.Disabled
-		*out = new(bool)
-		**out = **in
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProtocolUpgradeConfig.
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 025e18e2f..10a5fa38b 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -98,6 +98,20 @@ spec:
                     maximum: 4294967295
                     minimum: 0
                     type: integer
+                  perEndpoint:
+                    description: PerEndpoint defines Circuit Breakers that will apply
+                      per-endpoint for an upstream cluster
+                    properties:
+                      maxConnections:
+                        default: 1024
+                        description: MaxConnections configures the maximum number
+                          of connections that Envoy will establish per-endpoint to
+                          the referenced backend defined within a xRoute rule.
+                        format: int64
+                        maximum: 4294967295
+                        minimum: 0
+                        type: integer
+                    type: object
                 type: object
               compression:
                 description: The compression config for the http streams.
@@ -538,9 +552,6 @@ spec:
                   If not specified, the default upgrade configuration(websocket) will be used.
                 items:
                   properties:
-                    disabled:
-                      description: Disabled indicates whether the upgrade is disabled.
-                      type: boolean
                     type:
                       description: |-
                         Type is the case-insensitive type of protocol upgrade.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
index f1ea85024..f7977a661 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
@@ -268,6 +268,21 @@ spec:
                               maximum: 4294967295
                               minimum: 0
                               type: integer
+                            perEndpoint:
+                              description: PerEndpoint defines Circuit Breakers that
+                                will apply per-endpoint for an upstream cluster
+                              properties:
+                                maxConnections:
+                                  default: 1024
+                                  description: MaxConnections configures the maximum
+                                    number of connections that Envoy will establish
+                                    per-endpoint to the referenced backend defined
+                                    within a xRoute rule.
+                                  format: int64
+                                  maximum: 4294967295
+                                  minimum: 0
+                                  type: integer
+                              type: object
                           type: object
                         connection:
                           description: Connection includes backend connection settings.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
index b0c4d78f1..f9ae3cf8b 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -10724,6 +10724,23 @@ spec:
                                                 maximum: 4294967295
                                                 minimum: 0
                                                 type: integer
+                                              perEndpoint:
+                                                description: PerEndpoint defines Circuit
+                                                  Breakers that will apply per-endpoint
+                                                  for an upstream cluster
+                                                properties:
+                                                  maxConnections:
+                                                    default: 1024
+                                                    description: MaxConnections configures
+                                                      the maximum number of connections
+                                                      that Envoy will establish per-endpoint
+                                                      to the referenced backend defined
+                                                      within a xRoute rule.
+                                                    format: int64
+                                                    maximum: 4294967295
+                                                    minimum: 0
+                                                    type: integer
+                                                type: object
                                             type: object
                                           connection:
                                             description: Connection includes backend
@@ -11722,6 +11739,23 @@ spec:
                                                 maximum: 4294967295
                                                 minimum: 0
                                                 type: integer
+                                              perEndpoint:
+                                                description: PerEndpoint defines Circuit
+                                                  Breakers that will apply per-endpoint
+                                                  for an upstream cluster
+                                                properties:
+                                                  maxConnections:
+                                                    default: 1024
+                                                    description: MaxConnections configures
+                                                      the maximum number of connections
+                                                      that Envoy will establish per-endpoint
+                                                      to the referenced backend defined
+                                                      within a xRoute rule.
+                                                    format: int64
+                                                    maximum: 4294967295
+                                                    minimum: 0
+                                                    type: integer
+                                                type: object
                                             type: object
                                           connection:
                                             description: Connection includes backend
@@ -12814,6 +12848,23 @@ spec:
                                           maximum: 4294967295
                                           minimum: 0
                                           type: integer
+                                        perEndpoint:
+                                          description: PerEndpoint defines Circuit
+                                            Breakers that will apply per-endpoint
+                                            for an upstream cluster
+                                          properties:
+                                            maxConnections:
+                                              default: 1024
+                                              description: MaxConnections configures
+                                                the maximum number of connections
+                                                that Envoy will establish per-endpoint
+                                                to the referenced backend defined
+                                                within a xRoute rule.
+                                              format: int64
+                                              maximum: 4294967295
+                                              minimum: 0
+                                              type: integer
+                                          type: object
                                       type: object
                                     connection:
                                       description: Connection includes backend connection
@@ -13820,6 +13871,22 @@ spec:
                                     maximum: 4294967295
                                     minimum: 0
                                     type: integer
+                                  perEndpoint:
+                                    description: PerEndpoint defines Circuit Breakers
+                                      that will apply per-endpoint for an upstream
+                                      cluster
+                                    properties:
+                                      maxConnections:
+                                        default: 1024
+                                        description: MaxConnections configures the
+                                          maximum number of connections that Envoy
+                                          will establish per-endpoint to the referenced
+                                          backend defined within a xRoute rule.
+                                        format: int64
+                                        maximum: 4294967295
+                                        minimum: 0
+                                        type: integer
+                                    type: object
                                 type: object
                               connection:
                                 description: Connection includes backend connection
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
index 368d53dd1..4188b7a41 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
@@ -761,6 +761,21 @@ spec:
                                 maximum: 4294967295
                                 minimum: 0
                                 type: integer
+                              perEndpoint:
+                                description: PerEndpoint defines Circuit Breakers
+                                  that will apply per-endpoint for an upstream cluster
+                                properties:
+                                  maxConnections:
+                                    default: 1024
+                                    description: MaxConnections configures the maximum
+                                      number of connections that Envoy will establish
+                                      per-endpoint to the referenced backend defined
+                                      within a xRoute rule.
+                                    format: int64
+                                    maximum: 4294967295
+                                    minimum: 0
+                                    type: integer
+                                type: object
                             type: object
                           connection:
                             description: Connection includes backend connection settings.
@@ -1663,6 +1678,21 @@ spec:
                                 maximum: 4294967295
                                 minimum: 0
                                 type: integer
+                              perEndpoint:
+                                description: PerEndpoint defines Circuit Breakers
+                                  that will apply per-endpoint for an upstream cluster
+                                properties:
+                                  maxConnections:
+                                    default: 1024
+                                    description: MaxConnections configures the maximum
+                                      number of connections that Envoy will establish
+                                      per-endpoint to the referenced backend defined
+                                      within a xRoute rule.
+                                    format: int64
+                                    maximum: 4294967295
+                                    minimum: 0
+                                    type: integer
+                                type: object
                             type: object
                           connection:
                             description: Connection includes backend connection settings.
@@ -2693,6 +2723,22 @@ spec:
                                       maximum: 4294967295
                                       minimum: 0
                                       type: integer
+                                    perEndpoint:
+                                      description: PerEndpoint defines Circuit Breakers
+                                        that will apply per-endpoint for an upstream
+                                        cluster
+                                      properties:
+                                        maxConnections:
+                                          default: 1024
+                                          description: MaxConnections configures the
+                                            maximum number of connections that Envoy
+                                            will establish per-endpoint to the referenced
+                                            backend defined within a xRoute rule.
+                                          format: int64
+                                          maximum: 4294967295
+                                          minimum: 0
+                                          type: integer
+                                      type: object
                                   type: object
                                 connection:
                                   description: Connection includes backend connection
@@ -3736,6 +3782,21 @@ spec:
                                 maximum: 4294967295
                                 minimum: 0
                                 type: integer
+                              perEndpoint:
+                                description: PerEndpoint defines Circuit Breakers
+                                  that will apply per-endpoint for an upstream cluster
+                                properties:
+                                  maxConnections:
+                                    default: 1024
+                                    description: MaxConnections configures the maximum
+                                      number of connections that Envoy will establish
+                                      per-endpoint to the referenced backend defined
+                                      within a xRoute rule.
+                                    format: int64
+                                    maximum: 4294967295
+                                    minimum: 0
+                                    type: integer
+                                type: object
                             type: object
                           connection:
                             description: Connection includes backend connection settings.
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 4d3c95c49..ca72ac828 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -292,21 +292,22 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 	resources *resource.Resources,
 ) error {
 	var (
-		rl        *ir.RateLimit
-		lb        *ir.LoadBalancer
-		pp        *ir.ProxyProtocol
-		hc        *ir.HealthCheck
-		cb        *ir.CircuitBreaker
-		fi        *ir.FaultInjection
-		to        *ir.Timeout
-		ka        *ir.TCPKeepalive
-		rt        *ir.Retry
-		bc        *ir.BackendConnection
-		ds        *ir.DNS
-		h2        *ir.HTTP2Settings
-		ro        *ir.ResponseOverride
-		cp        []*ir.Compression
-		err, errs error
+		rl          *ir.RateLimit
+		lb          *ir.LoadBalancer
+		pp          *ir.ProxyProtocol
+		hc          *ir.HealthCheck
+		cb          *ir.CircuitBreaker
+		fi          *ir.FaultInjection
+		to          *ir.Timeout
+		ka          *ir.TCPKeepalive
+		rt          *ir.Retry
+		bc          *ir.BackendConnection
+		ds          *ir.DNS
+		h2          *ir.HTTP2Settings
+		ro          *ir.ResponseOverride
+		cp          []*ir.Compression
+		httpUpgrade []string
+		err, errs   error
 	)
 
 	// Build IR
@@ -359,6 +360,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 		errs = errors.Join(errs, err)
 	}
 	cp = buildCompression(policy.Spec.Compression)
+	httpUpgrade = buildHTTPProtocolUpgradeConfig(policy.Spec.HTTPUpgrade)
 
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
@@ -423,6 +425,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 						Timeout:           to,
 						ResponseOverride:  ro,
 						Compression:       cp,
+						HTTPUpgrade:       httpUpgrade,
 					}
 
 					// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -440,27 +443,25 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 }
 
 func (t *Translator) translateBackendTrafficPolicyForGateway(
-	policy *egv1a1.BackendTrafficPolicy,
-	target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
-	gateway *GatewayContext,
-	xdsIR resource.XdsIRMap,
-	resources *resource.Resources,
+	policy *egv1a1.BackendTrafficPolicy, target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
+	gateway *GatewayContext, xdsIR resource.XdsIRMap, resources *resource.Resources,
 ) error {
 	var (
-		rl        *ir.RateLimit
-		lb        *ir.LoadBalancer
-		pp        *ir.ProxyProtocol
-		hc        *ir.HealthCheck
-		cb        *ir.CircuitBreaker
-		fi        *ir.FaultInjection
-		ct        *ir.Timeout
-		ka        *ir.TCPKeepalive
-		rt        *ir.Retry
-		ds        *ir.DNS
-		h2        *ir.HTTP2Settings
-		ro        *ir.ResponseOverride
-		cp        []*ir.Compression
-		err, errs error
+		rl          *ir.RateLimit
+		lb          *ir.LoadBalancer
+		pp          *ir.ProxyProtocol
+		hc          *ir.HealthCheck
+		cb          *ir.CircuitBreaker
+		fi          *ir.FaultInjection
+		ct          *ir.Timeout
+		ka          *ir.TCPKeepalive
+		rt          *ir.Retry
+		ds          *ir.DNS
+		h2          *ir.HTTP2Settings
+		ro          *ir.ResponseOverride
+		cp          []*ir.Compression
+		httpUpgrade []string
+		err, errs   error
 	)
 
 	// Build IR
@@ -506,6 +507,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(
 		errs = errors.Join(errs, err)
 	}
 	cp = buildCompression(policy.Spec.Compression)
+	httpUpgrade = buildHTTPProtocolUpgradeConfig(policy.Spec.HTTPUpgrade)
 
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
@@ -591,6 +593,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(
 				DNS:              ds,
 				ResponseOverride: ro,
 				Compression:      cp,
+				HTTPUpgrade:      httpUpgrade,
 			}
 
 			// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -971,3 +974,16 @@ func buildCompression(compression []*egv1a1.Compression) []*ir.Compression {
 
 	return irCompression
 }
+
+func buildHTTPProtocolUpgradeConfig(cfgs []*egv1a1.ProtocolUpgradeConfig) []string {
+	if len(cfgs) == 0 {
+		return nil
+	}
+
+	result := make([]string, 0, len(cfgs))
+	for _, cfg := range cfgs {
+		result = append(result, cfg.Type)
+	}
+
+	return result
+}
diff --git a/internal/gatewayapi/clustersettings.go b/internal/gatewayapi/clustersettings.go
index cc54c3ae2..01b543ae9 100644
--- a/internal/gatewayapi/clustersettings.go
+++ b/internal/gatewayapi/clustersettings.go
@@ -259,6 +259,15 @@ func buildCircuitBreaker(policy egv1a1.ClusterSettings) (*ir.CircuitBreaker, err
 			}
 		}
 
+		if pcb.PerEndpoint != nil {
+			perEndpoint := &ir.PerEndpointCircuitBreakers{}
+			if pcb.PerEndpoint.MaxConnections != nil {
+				if ui32, ok := int64ToUint32(*pcb.PerEndpoint.MaxConnections); ok {
+					perEndpoint.MaxConnections = &ui32
+				}
+			}
+			cb.PerEndpoint = perEndpoint
+		}
 	}
 
 	return cb, nil
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 15a8e28c8..9986a0240 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -828,6 +828,8 @@ type TrafficFeatures struct {
 	ResponseOverride *ResponseOverride `json:"responseOverride,omitempty" yaml:"responseOverride,omitempty"`
 	// Compression settings for HTTP Response
 	Compression []*Compression `json:"compression,omitempty" yaml:"compression,omitempty"`
+	// HTTPUpgrade defines the schema for upgrading the HTTP protocol.
+	HTTPUpgrade []string `json:"httpUpgrade,omitempty" yaml:"httpUpgrade,omitempty"`
 }
 
 func (b *TrafficFeatures) Validate() error {
@@ -2403,6 +2405,16 @@ type CircuitBreaker struct {
 
 	// The maximum number of parallel retries that Envoy will make.
 	MaxParallelRetries *uint32 `json:"maxParallelRetries,omitempty" yaml:"maxParallelRetries,omitempty"`
+
+	// PerEndpoint defines per-endpoint Circuit Breakers
+	PerEndpoint *PerEndpointCircuitBreakers `json:"perEndpoint,omitempty"`
+}
+
+// PerEndpointCircuitBreakers defines the per-endpoint Circuit Breaker configuration.
+// +k8s:deepcopy-gen=true
+type PerEndpointCircuitBreakers struct {
+	// MaxConnections configures the maximum number of connections that Envoy will establish per-endpoint to the referenced backend defined within a xRoute rule.
+	MaxConnections *uint32 `json:"maxConnections,omitempty"`
 }
 
 // HealthCheck defines health check settings
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index c2ae3f040..f249e7a3e 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -471,6 +471,11 @@ func (in *CircuitBreaker) DeepCopyInto(out *CircuitBreaker) {
 		*out = new(uint32)
 		**out = **in
 	}
+	if in.PerEndpoint != nil {
+		in, out := &in.PerEndpoint, &out.PerEndpoint
+		*out = new(PerEndpointCircuitBreakers)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CircuitBreaker.
@@ -2425,6 +2430,26 @@ func (in *PathSettings) DeepCopy() *PathSettings {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PerEndpointCircuitBreakers) DeepCopyInto(out *PerEndpointCircuitBreakers) {
+	*out = *in
+	if in.MaxConnections != nil {
+		in, out := &in.MaxConnections, &out.MaxConnections
+		*out = new(uint32)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PerEndpointCircuitBreakers.
+func (in *PerEndpointCircuitBreakers) DeepCopy() *PerEndpointCircuitBreakers {
+	if in == nil {
+		return nil
+	}
+	out := new(PerEndpointCircuitBreakers)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PerRetryPolicy) DeepCopyInto(out *PerRetryPolicy) {
 	*out = *in
@@ -3658,6 +3683,11 @@ func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
 			}
 		}
 	}
+	if in.HTTPUpgrade != nil {
+		in, out := &in.HTTPUpgrade, &out.HTTPUpgrade
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficFeatures.
diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go
index b3b8368ef..5c268ef0b 100644
--- a/internal/xds/translator/cluster.go
+++ b/internal/xds/translator/cluster.go
@@ -414,6 +414,7 @@ func buildXdsClusterCircuitBreaker(circuitBreaker *ir.CircuitBreaker) *clusterv3
 			Value: uint32(1024),
 		},
 	}
+	cbtPerEndpoint := []*clusterv3.CircuitBreakers_Thresholds{}
 
 	if circuitBreaker != nil {
 		if circuitBreaker.MaxConnections != nil {
@@ -439,10 +440,23 @@ func buildXdsClusterCircuitBreaker(circuitBreaker *ir.CircuitBreaker) *clusterv3
 				Value: *circuitBreaker.MaxParallelRetries,
 			}
 		}
+
+		if circuitBreaker.PerEndpoint != nil {
+			if circuitBreaker.PerEndpoint.MaxConnections != nil {
+				cbtPerEndpoint = []*clusterv3.CircuitBreakers_Thresholds{
+					{
+						MaxConnections: &wrapperspb.UInt32Value{
+							Value: *circuitBreaker.PerEndpoint.MaxConnections,
+						},
+					},
+				}
+			}
+		}
 	}
 
 	ecb := &clusterv3.CircuitBreakers{
-		Thresholds: []*clusterv3.CircuitBreakers_Thresholds{cbt},
+		Thresholds:        []*clusterv3.CircuitBreakers_Thresholds{cbt},
+		PerHostThresholds: cbtPerEndpoint,
 	}
 
 	return ecb
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 2bff31761..722e1aa91 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -26,8 +26,18 @@ const (
 	retryDefaultRetryOn             = "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes"
 	retryDefaultRetriableStatusCode = 503
 	retryDefaultNumRetries          = 2
+
+	websocketUpgradeType = "websocket"
 )
 
+// Allow websocket upgrades for HTTP 1.1
+// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
+var defaultUpgradeConfig = []*routev3.RouteAction_UpgradeConfig{
+	{
+		UpgradeType: websocketUpgradeType,
+	},
+}
+
 func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 	router := &routev3.Route{
 		Name:     httpRoute.Name,
@@ -61,13 +71,7 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 		}
 
 		if !httpRoute.IsHTTP2 {
-			// Allow websocket upgrades for HTTP 1.1
-			// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
-			routeAction.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{
-				{
-					UpgradeType: "websocket",
-				},
-			}
+			routeAction.UpgradeConfigs = buildUpgradeConfig(httpRoute.Traffic)
 		}
 
 		router.Action = &routev3.Route_Route{Route: routeAction}
@@ -80,13 +84,7 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 			routeAction.RequestMirrorPolicies = buildXdsRequestMirrorPolicies(httpRoute.Mirrors)
 		}
 		if !httpRoute.IsHTTP2 {
-			// Allow websocket upgrades for HTTP 1.1
-			// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
-			routeAction.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{
-				{
-					UpgradeType: "websocket",
-				},
-			}
+			routeAction.UpgradeConfigs = buildUpgradeConfig(httpRoute.Traffic)
 		}
 		router.Action = &routev3.Route_Route{Route: routeAction}
 	}
@@ -122,6 +120,21 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 	return router, nil
 }
 
+func buildUpgradeConfig(trafficFeatures *ir.TrafficFeatures) []*routev3.RouteAction_UpgradeConfig {
+	if trafficFeatures == nil || trafficFeatures.HTTPUpgrade == nil {
+		return defaultUpgradeConfig
+	}
+
+	upgradeConfigs := make([]*routev3.RouteAction_UpgradeConfig, 0, len(trafficFeatures.HTTPUpgrade))
+	for _, protocol := range trafficFeatures.HTTPUpgrade {
+		upgradeConfigs = append(upgradeConfigs, &routev3.RouteAction_UpgradeConfig{
+			UpgradeType: protocol,
+		})
+	}
+
+	return upgradeConfigs
+}
+
 func buildXdsRouteMatch(pathMatch *ir.StringMatch, headerMatches []*ir.StringMatch, queryParamMatches []*ir.StringMatch) *routev3.RouteMatch {
 	outMatch := &routev3.RouteMatch{}
 
@@ -236,7 +249,7 @@ func buildXdsRouteAction(backendWeights *ir.BackendWeights, settings []*ir.Desti
 }
 
 func buildXdsWeightedRouteAction(backendWeights *ir.BackendWeights, settings []*ir.DestinationSetting) *routev3.RouteAction {
-	weightedClusters := []*routev3.WeightedCluster_ClusterWeight{}
+	weightedClusters := make([]*routev3.WeightedCluster_ClusterWeight, 0, len(settings))
 	if backendWeights.Invalid > 0 {
 		invalidCluster := &routev3.WeightedCluster_ClusterWeight{
 			Name:   "invalid-backend-cluster",
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index b7c4d81af..87ee5adc9 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -22,6 +22,7 @@ new features: |
   Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth
   Added support for HTTP Methods and Headers based authorization in SecurityPolicy
   Added support for zone aware routing
+  Added support for per-host circuit breaker thresholds
 
 bug fixes: |
   Fix traffic splitting when filters are attached to the backendRef.



Test patch:
diff --git a/internal/gatewayapi/backendtrafficpolicy_test.go b/internal/gatewayapi/backendtrafficpolicy_test.go
index e104f3b0b..c03796f57 100644
--- a/internal/gatewayapi/backendtrafficpolicy_test.go
+++ b/internal/gatewayapi/backendtrafficpolicy_test.go
@@ -132,3 +132,57 @@ func Test_translateRateLimitCost(t *testing.T) {
 		})
 	}
 }
+
+func TestBuildHTTPProtocolUpgradeConfig(t *testing.T) {
+	cases := []struct {
+		name     string
+		cfgs     []*egv1a1.ProtocolUpgradeConfig
+		expected []string
+	}{
+		{
+			name:     "empty",
+			cfgs:     nil,
+			expected: nil,
+		},
+		{
+			name: "spdy",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "spdy/3.1",
+				},
+			},
+			expected: []string{"spdy/3.1"},
+		},
+		{
+			name: "websockets-spdy",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "websockets",
+				},
+				{
+					Type: "spdy/3.1",
+				},
+			},
+			expected: []string{"websockets", "spdy/3.1"},
+		},
+		{
+			name: "spdy-websockets",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "spdy/3.1",
+				},
+				{
+					Type: "websockets",
+				},
+			},
+			expected: []string{"spdy/3.1", "websockets"},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := buildHTTPProtocolUpgradeConfig(tc.cfgs)
+			require.Equal(t, tc.expected, got)
+		})
+	}
+}
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml
new file mode 100644
index 000000000..72650c5dc
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml
@@ -0,0 +1,48 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      httpUpgrade:
+        - type: "spdy/3.1"
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml
new file mode 100644
index 000000000..dcb4b20e0
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml
@@ -0,0 +1,175 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    httpUpgrade:
+    - type: spdy/3.1
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            name: httproute/default/httproute-1/rule/0/backend/0
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+          - spdy/3.1
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml
new file mode 100644
index 000000000..230d2358d
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml
@@ -0,0 +1,49 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      httpUpgrade:
+        - type: websocket
+        - type: spdy/3.1
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml
new file mode 100644
index 000000000..ea1d4d6a6
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml
@@ -0,0 +1,177 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    httpUpgrade:
+    - type: websocket
+    - type: spdy/3.1
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            name: httproute/default/httproute-1/rule/0/backend/0
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+          - websocket
+          - spdy/3.1
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/xds/translator/route_test.go b/internal/xds/translator/route_test.go
index 3d7d6a972..817426c97 100644
--- a/internal/xds/translator/route_test.go
+++ b/internal/xds/translator/route_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
-func Test_buildHashPolicy(t *testing.T) {
+func TestBuildHashPolicy(t *testing.T) {
 	tests := []struct {
 		name      string
 		httpRoute *ir.HTTPRoute
@@ -102,3 +102,72 @@ func Test_buildHashPolicy(t *testing.T) {
 		})
 	}
 }
+
+func TestBuildUpgradeConfig(t *testing.T) {
+	cases := []struct {
+		name           string
+		trafficFeature *ir.TrafficFeatures
+		expected       []*routev3.RouteAction_UpgradeConfig
+	}{
+		{
+			name:           "default",
+			trafficFeature: nil,
+			expected:       defaultUpgradeConfig,
+		},
+		{
+			name: "empty",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: nil,
+			},
+			expected: defaultUpgradeConfig,
+		},
+		{
+			name: "spdy",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"spdy/3.1"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "spdy/3.1",
+				},
+			},
+		},
+		{
+			name: "spdy-websocket",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"spdy/3.1", "websocket"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "spdy/3.1",
+				},
+				{
+					UpgradeType: "websocket",
+				},
+			},
+		},
+		{
+			name: "websocket-spdy",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"websocket", "spdy/3.1"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "websocket",
+				},
+				{
+					UpgradeType: "spdy/3.1",
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := buildUpgradeConfig(tc.trafficFeature)
+			if !reflect.DeepEqual(got, tc.expected) {
+				t.Errorf("buildUpgradeConfig() got = %v, want %v", got, tc.expected)
+			}
+		})
+	}
+}
diff --git a/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml b/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
index 7819bbdb4..f47bea642 100644
--- a/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/circuit-breaker.yaml
@@ -17,6 +17,8 @@ http:
         maxParallelRequests: 1
         maxParallelRetries: 2
         maxRequestsPerConnection: 10
+        perEndpoint:
+          maxConnections: 1
     destination:
       name: "first-route-dest"
       settings:
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml
new file mode 100644
index 000000000..ce44231bd
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml
@@ -0,0 +1,40 @@
+http:
+  - address: 0.0.0.0
+    hostnames:
+      - "*"
+    isHTTP2: false
+    metadata:
+      kind: Gateway
+      name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    name: envoy-gateway/gateway-1/http
+    path:
+      escapedSlashesAction: UnescapeAndRedirect
+      mergeSlashes: true
+    port: 10080
+    routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+            - addressType: IP
+              endpoints:
+                - host: 7.7.7.7
+                  port: 8080
+              protocol: HTTP
+              weight: 1
+              name: httproute/default/httproute-1/rule/0/backend/0
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+            - "spdy/3.1"
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml
new file mode 100644
index 000000000..8bac62745
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml
@@ -0,0 +1,41 @@
+http:
+  - address: 0.0.0.0
+    hostnames:
+      - "*"
+    isHTTP2: false
+    metadata:
+      kind: Gateway
+      name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    name: envoy-gateway/gateway-1/http
+    path:
+      escapedSlashesAction: UnescapeAndRedirect
+      mergeSlashes: true
+    port: 10080
+    routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+            - addressType: IP
+              endpoints:
+                - host: 7.7.7.7
+                  port: 8080
+              protocol: HTTP
+              weight: 1
+              name: httproute/default/httproute-1/rule/0/backend/0
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+            - "websocket"
+            - "spdy/3.1"
diff --git a/internal/xds/translator/testdata/out/xds-ir/circuit-breaker.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/circuit-breaker.clusters.yaml
index 1767baed5..0084cd8b4 100644
--- a/internal/xds/translator/testdata/out/xds-ir/circuit-breaker.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/circuit-breaker.clusters.yaml
@@ -1,4 +1,6 @@
 - circuitBreakers:
+    perHostThresholds:
+    - maxConnections: 1
     thresholds:
     - maxConnections: 1
       maxPendingRequests: 1
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml
new file mode 100644
index 000000000..c24d059ee
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml
new file mode 100644
index 000000000..09426a317
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml
@@ -0,0 +1,34 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml
new file mode 100644
index 000000000..f447ef4e5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml
@@ -0,0 +1,29 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: spdy/3.1
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml
new file mode 100644
index 000000000..c24d059ee
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml
new file mode 100644
index 000000000..09426a317
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml
@@ -0,0 +1,34 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml
new file mode 100644
index 000000000..20c2467f5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml
@@ -0,0 +1,30 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+        - upgradeType: spdy/3.1
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index e5889126e..83049ad2c 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -487,6 +487,7 @@ _Appears in:_
 | `useClientProtocol` | _boolean_ |  false  |  | UseClientProtocol configures Envoy to prefer sending requests to backends using<br />the same HTTP protocol that the incoming request used. Defaults to false, which means<br />that Envoy will use the protocol indicated by the attached BackendRef. |
 | `compression` | _[Compression](#compression) array_ |  false  |  | The compression config for the http streams. |
 | `responseOverride` | _[ResponseOverride](#responseoverride) array_ |  false  |  | ResponseOverride defines the configuration to override specific responses with a custom one.<br />If multiple configurations are specified, the first one to match wins. |
+| `httpUpgrade` | _[ProtocolUpgradeConfig](#protocolupgradeconfig) array_ |  false  |  | HTTPUpgrade defines the configuration for HTTP protocol upgrades.<br />If not specified, the default upgrade configuration(websocket) will be used. |
 
 
 #### BasicAuth
@@ -596,6 +597,7 @@ _Appears in:_
 | `maxParallelRequests` | _integer_ |  false  | 1024 | The maximum number of parallel requests that Envoy will make to the referenced backend defined within a xRoute rule. |
 | `maxParallelRetries` | _integer_ |  false  | 1024 | The maximum number of parallel retries that Envoy will make to the referenced backend defined within a xRoute rule. |
 | `maxRequestsPerConnection` | _integer_ |  false  |  | The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule.<br />Default: unlimited. |
+| `perEndpoint` | _[PerEndpointCircuitBreakers](#perendpointcircuitbreakers)_ |  false  |  | PerEndpoint defines Circuit Breakers that will apply per-endpoint for an upstream cluster |
 
 
 #### ClaimToHeader
@@ -3140,6 +3142,20 @@ _Appears in:_
 | `disableMergeSlashes` | _boolean_ |  false  |  | DisableMergeSlashes allows disabling the default configuration of merging adjacent<br />slashes in the path.<br />Note that slash merging is not part of the HTTP spec and is provided for convenience. |
 
 
+#### PerEndpointCircuitBreakers
+
+
+
+PerEndpointCircuitBreakers defines Circuit Breakers that will apply per-endpoint for an upstream cluster
+
+_Appears in:_
+- [CircuitBreaker](#circuitbreaker)
+
+| Field | Type | Required | Default | Description |
+| ---   | ---  | ---      | ---     | ---         |
+| `maxConnections` | _integer_ |  false  | 1024 | MaxConnections configures the maximum number of connections that Envoy will establish per-endpoint to the referenced backend defined within a xRoute rule. |
+
+
 #### PerRetryPolicy
 
 
@@ -3221,7 +3237,6 @@ _Appears in:_
 | Field | Type | Required | Default | Description |
 | ---   | ---  | ---      | ---     | ---         |
 | `type` | _string_ |  true  |  | Type is the case-insensitive type of protocol upgrade.<br />e.g. `websocket`, `CONNECT`, `spdy/3.1` etc. |
-| `disabled` | _boolean_ |  false  |  | Disabled indicates whether the upgrade is disabled. |
 
 
 #### ProviderType