diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go
index 8e407a02b7c..7981a2bfc19 100644
--- a/apis/projectcontour/v1/httpproxy.go
+++ b/apis/projectcontour/v1/httpproxy.go
@@ -1299,6 +1299,49 @@ type LoadBalancerPolicy struct {
// list of hash policies is empty after validation, the load balancing
// strategy will fall back to the default `RoundRobin`.
RequestHashPolicies []RequestHashPolicy `json:"requestHashPolicies,omitempty"`
+
+ // ClientSideWeightedRoundRobinPolicy contains configuration for a client side WRR LB policy.
+ ClientSideWeightedRoundRobinPolicy *LoadBalancerPolicyClientSideWeightedRoundRobinPolicy `json:"clientSideWeightedRoundRobinPolicy,omitempty"`
+}
+
+// LoadBalancerPolicyClientSideWeightedRoundRobinPolicy holds configuration for a client side wrr lb policy.
+// For default values, constraints and behavior patterns refer to the envoy doc.
+type LoadBalancerPolicyClientSideWeightedRoundRobinPolicy struct {
+ // Whether to enable out-of-band utilization reporting collection from the endpoints.
+ EnableOOBLoadReport *bool `json:"enableOobLoadReport,omitempty"`
+
+ // Load reporting interval to request from the server. Note that the
+ // server may not provide reports as frequently as the client requests.
+ // Used only when enable_oob_load_report is true.
+ OOBReportingPeriod string `json:"oobReportingPeriod,omitempty"`
+
+ // A given endpoint must report load metrics continuously for at least
+ // this long before the endpoint weight will be used. This avoids
+ // churn when the set of endpoint addresses changes. Takes effect
+ // both immediately after we establish a connection to an endpoint and
+ // after weight_expiration_period has caused us to stop using the most
+ // recent load metrics.
+ BlackoutPeriod string `json:"blackoutPeriod,omitempty"`
+
+ // If a given endpoint has not reported load metrics in this long,
+ // then we stop using the reported weight. This ensures that we do
+ // not continue to use very stale weights. Once we stop using a stale
+ // value, if we later start seeing fresh reports again, the
+ // blackout_period applies.
+ WeightExpirationPeriod string `json:"weightExpirationPeriod,omitempty"`
+
+ // How often endpoint weights are recalculated.
+ WeightUpdatePeriod string `json:"weightUpdatePeriod,omitempty"`
+
+ // The multiplier used to adjust endpoint weights with the error rate
+ // calculated as eps/qps.
+ ErrorUtilizationPenalty string `json:"errorUtilizationPenalty,omitempty"`
+
+ // By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ // If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ // For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ // If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ MetricNamesForComputingUtilization []string `json:"metricNamesForComputingUtilization,omitempty"`
}
// HeadersPolicy defines how headers are managed during forwarding.
diff --git a/apis/projectcontour/v1/zz_generated.deepcopy.go b/apis/projectcontour/v1/zz_generated.deepcopy.go
index 30cd9f1cbea..ac3ddc1c998 100644
--- a/apis/projectcontour/v1/zz_generated.deepcopy.go
+++ b/apis/projectcontour/v1/zz_generated.deepcopy.go
@@ -713,6 +713,11 @@ func (in *LoadBalancerPolicy) DeepCopyInto(out *LoadBalancerPolicy) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
+ if in.ClientSideWeightedRoundRobinPolicy != nil {
+ in, out := &in.ClientSideWeightedRoundRobinPolicy, &out.ClientSideWeightedRoundRobinPolicy
+ *out = new(LoadBalancerPolicyClientSideWeightedRoundRobinPolicy)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerPolicy.
@@ -725,6 +730,31 @@ func (in *LoadBalancerPolicy) DeepCopy() *LoadBalancerPolicy {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LoadBalancerPolicyClientSideWeightedRoundRobinPolicy) DeepCopyInto(out *LoadBalancerPolicyClientSideWeightedRoundRobinPolicy) {
+ *out = *in
+ if in.EnableOOBLoadReport != nil {
+ in, out := &in.EnableOOBLoadReport, &out.EnableOOBLoadReport
+ *out = new(bool)
+ **out = **in
+ }
+ if in.MetricNamesForComputingUtilization != nil {
+ in, out := &in.MetricNamesForComputingUtilization, &out.MetricNamesForComputingUtilization
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerPolicyClientSideWeightedRoundRobinPolicy.
+func (in *LoadBalancerPolicyClientSideWeightedRoundRobinPolicy) DeepCopy() *LoadBalancerPolicyClientSideWeightedRoundRobinPolicy {
+ if in == nil {
+ return nil
+ }
+ out := new(LoadBalancerPolicyClientSideWeightedRoundRobinPolicy)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalRateLimitPolicy) DeepCopyInto(out *LocalRateLimitPolicy) {
*out = *in
diff --git a/changelogs/unreleased/6999-anton-kuklin-minor.md b/changelogs/unreleased/6999-anton-kuklin-minor.md
new file mode 100644
index 00000000000..dc64885b7d5
--- /dev/null
+++ b/changelogs/unreleased/6999-anton-kuklin-minor.md
@@ -0,0 +1 @@
+Adds support for `ClientSideWeightedRoundRobin` LB policy in HTTPProxy and Extension service.
\ No newline at end of file
diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml
index 0a74efa2c4c..9ca5a044562 100644
--- a/examples/contour/01-crds.yaml
+++ b/examples/contour/01-crds.yaml
@@ -5188,6 +5188,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization reporting
+ collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -6245,6 +6294,55 @@ spec:
loadBalancerPolicy:
description: The load balancing policy for this route.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains
+ configuration for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -7146,6 +7244,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml
index 05e8e836804..fa75c496800 100644
--- a/examples/render/contour-deployment.yaml
+++ b/examples/render/contour-deployment.yaml
@@ -5403,6 +5403,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization reporting
+ collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -6460,6 +6509,55 @@ spec:
loadBalancerPolicy:
description: The load balancing policy for this route.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains
+ configuration for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -7361,6 +7459,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml
index 55bc6547ab9..b16a0841cbc 100644
--- a/examples/render/contour-gateway-provisioner.yaml
+++ b/examples/render/contour-gateway-provisioner.yaml
@@ -5199,6 +5199,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization reporting
+ collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -6256,6 +6305,55 @@ spec:
loadBalancerPolicy:
description: The load balancing policy for this route.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains
+ configuration for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -7157,6 +7255,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index 11be69f1f67..7e324c42945 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -5224,6 +5224,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization reporting
+ collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -6281,6 +6330,55 @@ spec:
loadBalancerPolicy:
description: The load balancing policy for this route.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains
+ configuration for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -7182,6 +7280,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index 0b27d0a0fa1..60541fc610d 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -5403,6 +5403,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization reporting
+ collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -6460,6 +6509,55 @@ spec:
loadBalancerPolicy:
description: The load balancing policy for this route.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains
+ configuration for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
@@ -7361,6 +7459,55 @@ spec:
`Cookie` and `RequestHash` load balancing strategies cannot be used
here.
properties:
+ clientSideWeightedRoundRobinPolicy:
+ description: ClientSideWeightedRoundRobinPolicy contains configuration
+ for a client side WRR LB policy.
+ properties:
+ blackoutPeriod:
+ description: |-
+ A given endpoint must report load metrics continuously for at least
+ this long before the endpoint weight will be used. This avoids
+ churn when the set of endpoint addresses changes. Takes effect
+ both immediately after we establish a connection to an endpoint and
+ after weight_expiration_period has caused us to stop using the most
+ recent load metrics.
+ type: string
+ enableOobLoadReport:
+ description: Whether to enable out-of-band utilization
+ reporting collection from the endpoints.
+ type: boolean
+ errorUtilizationPenalty:
+ description: |-
+ The multiplier used to adjust endpoint weights with the error rate
+ calculated as eps/qps.
+ type: string
+ metricNamesForComputingUtilization:
+ description: |-
+ By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ items:
+ type: string
+ type: array
+ oobReportingPeriod:
+ description: |-
+ Load reporting interval to request from the server. Note that the
+ server may not provide reports as frequently as the client requests.
+ Used only when enable_oob_load_report is true.
+ type: string
+ weightExpirationPeriod:
+ description: |-
+ If a given endpoint has not reported load metrics in this long,
+ then we stop using the reported weight. This ensures that we do
+ not continue to use very stale weights. Once we stop using a stale
+ value, if we later start seeing fresh reports again, the
+ blackout_period applies.
+ type: string
+ weightUpdatePeriod:
+ description: How often endpoint weights are recalculated.
+ type: string
+ type: object
requestHashPolicies:
description: |-
RequestHashPolicies contains a list of hash policies to apply when the
diff --git a/internal/dag/dag.go b/internal/dag/dag.go
index 0e75755abfd..5ba48d7b919 100644
--- a/internal/dag/dag.go
+++ b/internal/dag/dag.go
@@ -997,6 +997,9 @@ type Cluster struct {
// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#enum-config-cluster-v3-cluster-lbpolicy
LoadBalancerPolicy string
+ // LoadBalancerPolicyConfig holds LB strategy specific configurations.
+ LoadBalancerPolicyConfig *LoadBalancerPolicyConfig
+
// Cluster http health check policy
*HTTPHealthCheckPolicy
@@ -1227,6 +1230,9 @@ type ExtensionCluster struct {
// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#enum-config-cluster-v3-cluster-lbpolicy
LoadBalancerPolicy string
+ // LoadBalancerPolicyConfig holds LB strategy specific configurations.
+ LoadBalancerPolicyConfig *LoadBalancerPolicyConfig
+
// RouteTimeoutPolicy specifies how to handle timeouts to this extension.
RouteTimeoutPolicy RouteTimeoutPolicy
@@ -1306,3 +1312,49 @@ type CircuitBreakers struct {
// that Envoy will allow to each individual host in a cluster.
PerHostMaxConnections uint32
}
+
+// LoadBalancerPolicyConfig holds configuration specific for each underlying lb strategy.
+type LoadBalancerPolicyConfig struct {
+ // ClientSideWeightedRoundRobin holds configuration parameters used for client side WRR.
+ ClientSideWeightedRoundRobin *LoadBalancerPolicyConfigClientSideWeightedRoundRobin
+}
+
+// LoadBalancerPolicyConfigClientSideWeightedRoundRobin holds configuration for a client side wrr lb policy.
+// For default values, constraints and behavior patterns refer to the envoy doc.
+type LoadBalancerPolicyConfigClientSideWeightedRoundRobin struct {
+ // Whether to enable out-of-band utilization reporting collection from the endpoints.
+ EnableOOBLoadReport *bool
+
+ // Load reporting interval to request from the server. Note that the
+ // server may not provide reports as frequently as the client requests.
+ // Used only when enable_oob_load_report is true.
+ OOBReportingPeriod *time.Duration
+
+ // A given endpoint must report load metrics continuously for at least
+ // this long before the endpoint weight will be used. This avoids
+ // churn when the set of endpoint addresses changes. Takes effect
+ // both immediately after we establish a connection to an endpoint and
+ // after weight_expiration_period has caused us to stop using the most
+ // recent load metrics.
+ BlackoutPeriod *time.Duration
+
+ // If a given endpoint has not reported load metrics in this long,
+ // then we stop using the reported weight. This ensures that we do
+ // not continue to use very stale weights. Once we stop using a stale
+ // value, if we later start seeing fresh reports again, the
+ // blackout_period applies.
+ WeightExpirationPeriod *time.Duration
+
+ // How often endpoint weights are recalculated.
+ WeightUpdatePeriod *time.Duration
+
+ // The multiplier used to adjust endpoint weights with the error rate
+ // calculated as eps/qps.
+ ErrorUtilizationPenalty *float32
+
+ // By default, endpoint weight is computed based on the :ref:`application_utilization ` field reported by the endpoint.
+ // If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+ // For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:`named_metrics ` field.
+ // If none of the specified metrics are present in the load report, then :ref:`cpu_utilization ` is used instead.
+ MetricNamesForComputingUtilization []string
+}
diff --git a/internal/dag/extension_processor.go b/internal/dag/extension_processor.go
index 8d03f60a360..ccde28c7a65 100644
--- a/internal/dag/extension_processor.go
+++ b/internal/dag/extension_processor.go
@@ -156,6 +156,13 @@ func (p *ExtensionServiceProcessor) buildExtensionService(
}
extension.LoadBalancerPolicy = lbPolicy
+ lbPolicyConfig, err := loadBalancerPolicyConfig(lbPolicy, ext.Spec.LoadBalancerPolicy)
+ if err != nil {
+ validCondition.AddErrorf(contour_v1.ConditionTypeSpecError, "LoadBalancerPolicyInvalid",
+ "loadBalancerPolicy processing failed with error: %s", err)
+ }
+ extension.LoadBalancerPolicyConfig = lbPolicyConfig
+
// Timeouts are specified above the cluster (e.g.
// in the ext_authz filter). The ext_authz filter
// doesn't have an idle timeout (only a request
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index 8a61bc5f132..d7de2542396 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -1035,9 +1035,16 @@ func (p *HTTPProxyProcessor) computeRoutes(
}
}
+ lbPolicyConfig, err := loadBalancerPolicyConfig(lbPolicy, route.LoadBalancerPolicy)
+ if err != nil {
+ validCond.AddErrorf(contour_v1.ConditionTypeRouteError, "LoadBalancerPolicyInvalid",
+ "loadBalancerPolicy processing failed with error: %s", err)
+ }
+
c := &Cluster{
Upstream: s,
LoadBalancerPolicy: lbPolicy,
+ LoadBalancerPolicyConfig: lbPolicyConfig,
Weight: uint32(service.Weight), //nolint:gosec // disable G115
HTTPHealthCheckPolicy: healthPolicy,
UpstreamValidation: uv,
@@ -1196,7 +1203,7 @@ func (p *HTTPProxyProcessor) processHTTPProxyTCPProxy(validCond *contour_v1.Deta
lbPolicy := loadBalancerPolicy(tcpproxy.LoadBalancerPolicy)
switch lbPolicy {
- case LoadBalancerPolicyCookie, LoadBalancerPolicyRequestHash:
+ case LoadBalancerPolicyCookie, LoadBalancerPolicyRequestHash, LoadBalancerPolicyClientSideWeightedRoundRobin:
validCond.AddWarningf(contour_v1.ConditionTypeTCPProxyError, "IgnoredField",
"ignoring field %q; %s load balancer policy is not supported for TCPProxies",
"Spec.TCPProxy.LoadBalancerPolicy", lbPolicy)
diff --git a/internal/dag/policy.go b/internal/dag/policy.go
index fd94c1749c6..bc94f75c9ab 100644
--- a/internal/dag/policy.go
+++ b/internal/dag/policy.go
@@ -18,6 +18,7 @@ import (
"fmt"
"net/http"
"regexp"
+ "strconv"
"strings"
"time"
@@ -55,6 +56,10 @@ const (
// LoadBalancerPolicyRequestHash denotes request attribute hashing is used
// to make load balancing decisions.
LoadBalancerPolicyRequestHash = "RequestHash"
+
+ // LoadBalancerPolicyClientSideWeightedRoundRobin denotes load balancing will be performed based on
+ // ORCA metrics returned in responses.
+ LoadBalancerPolicyClientSideWeightedRoundRobin = "ClientSideWeightedRoundRobin"
)
// match "%REQ()%"
@@ -538,7 +543,7 @@ func loadBalancerPolicy(lbp *contour_v1.LoadBalancerPolicy) string {
return ""
}
switch lbp.Strategy {
- case LoadBalancerPolicyWeightedLeastRequest, LoadBalancerPolicyRandom, LoadBalancerPolicyCookie, LoadBalancerPolicyRequestHash:
+ case LoadBalancerPolicyWeightedLeastRequest, LoadBalancerPolicyRandom, LoadBalancerPolicyCookie, LoadBalancerPolicyRequestHash, LoadBalancerPolicyClientSideWeightedRoundRobin:
return lbp.Strategy
default:
return ""
@@ -836,3 +841,64 @@ func serviceCircuitBreakerPolicy(s *Service, cb *contour_v1alpha1.CircuitBreaker
return s
}
+
+func loadBalancerPolicyConfig(strategy string, policy *contour_v1.LoadBalancerPolicy) (*LoadBalancerPolicyConfig, error) {
+ if policy == nil {
+ return nil, nil
+ }
+ switch strategy {
+ case LoadBalancerPolicyClientSideWeightedRoundRobin:
+ if policy.ClientSideWeightedRoundRobinPolicy == nil {
+ return nil, nil
+ }
+ oobReportingPeriod, err := parseDurationPtr(policy.ClientSideWeightedRoundRobinPolicy.OOBReportingPeriod)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing OOBReportingPeriod: %v", err)
+ }
+ blackoutPeriod, err := parseDurationPtr(policy.ClientSideWeightedRoundRobinPolicy.BlackoutPeriod)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing blackout period: %v", err)
+ }
+ weightExpirationPeriod, err := parseDurationPtr(policy.ClientSideWeightedRoundRobinPolicy.WeightExpirationPeriod)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing weight expiration period: %v", err)
+ }
+ weightUpdatePeriod, err := parseDurationPtr(policy.ClientSideWeightedRoundRobinPolicy.WeightUpdatePeriod)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing weight update period: %v", err)
+ }
+ var errorUtilizationPenalty *float32
+ if policy.ClientSideWeightedRoundRobinPolicy.ErrorUtilizationPenalty != "" {
+ fPenalty, err := strconv.ParseFloat(policy.ClientSideWeightedRoundRobinPolicy.ErrorUtilizationPenalty, 32)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing error utilization penalty: %v", err)
+ }
+ fPenalty32 := float32(fPenalty)
+ errorUtilizationPenalty = &fPenalty32
+ }
+ return &LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: &LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ EnableOOBLoadReport: policy.ClientSideWeightedRoundRobinPolicy.EnableOOBLoadReport,
+ OOBReportingPeriod: oobReportingPeriod,
+ BlackoutPeriod: blackoutPeriod,
+ WeightExpirationPeriod: weightExpirationPeriod,
+ WeightUpdatePeriod: weightUpdatePeriod,
+ ErrorUtilizationPenalty: errorUtilizationPenalty,
+ MetricNamesForComputingUtilization: policy.ClientSideWeightedRoundRobinPolicy.MetricNamesForComputingUtilization,
+ },
+ }, nil
+ default:
+ return nil, nil
+ }
+}
+
+func parseDurationPtr(val string) (*time.Duration, error) {
+ if val == "" {
+ return nil, nil
+ }
+ duration, err := time.ParseDuration(val)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing duration: %v", err)
+ }
+ return &duration, nil
+}
diff --git a/internal/dag/policy_test.go b/internal/dag/policy_test.go
index ab006db9b57..0e3f0404704 100644
--- a/internal/dag/policy_test.go
+++ b/internal/dag/policy_test.go
@@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
networking_v1 "k8s.io/api/networking/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/utils/ptr"
contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
@@ -324,6 +325,12 @@ func TestLoadBalancerPolicy(t *testing.T) {
},
want: "RequestHash",
},
+ "ClientSideWeightedRoundRobin": {
+ lbp: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ },
+ want: "ClientSideWeightedRoundRobin",
+ },
"unknown": {
lbp: &contour_v1.LoadBalancerPolicy{
Strategy: "please",
@@ -1477,3 +1484,146 @@ func TestHeadersPolicyRoute(t *testing.T) {
})
}
}
+
+func TestLoadBalancerPolicyConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ strategy string
+ policy *contour_v1.LoadBalancerPolicy
+ expectedConfig *LoadBalancerPolicyConfig
+ expectedErr bool
+ }{
+ {
+ name: "nil policy",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: nil,
+ expectedConfig: nil,
+ expectedErr: false,
+ },
+ {
+ name: "unhandled strategy",
+ strategy: "unhandled strategy",
+ policy: &contour_v1.LoadBalancerPolicy{},
+ expectedConfig: nil,
+ expectedErr: false,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: nil config",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: nil,
+ },
+ expectedConfig: nil,
+ expectedErr: false,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: empty config",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{},
+ },
+ expectedConfig: &LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: &LoadBalancerPolicyConfigClientSideWeightedRoundRobin{},
+ },
+ expectedErr: false,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: invalid oobReportingPeriod",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ OOBReportingPeriod: "invalid value",
+ },
+ },
+ expectedConfig: nil,
+ expectedErr: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: invalid blackoutPeriod",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ BlackoutPeriod: "invalid value",
+ },
+ },
+ expectedConfig: nil,
+ expectedErr: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: invalid weightExpirationPeriod",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ WeightExpirationPeriod: "invalid value",
+ },
+ },
+ expectedConfig: nil,
+ expectedErr: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: invalid weightUpdatePeriod",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ WeightUpdatePeriod: "invalid value",
+ },
+ },
+ expectedConfig: nil,
+ expectedErr: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: invalid errorUtilizationPenalty",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ ErrorUtilizationPenalty: "invalid value",
+ },
+ },
+ expectedConfig: nil,
+ expectedErr: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: full valid config",
+ strategy: "ClientSideWeightedRoundRobin",
+ policy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{
+ EnableOOBLoadReport: ptr.To(true),
+ OOBReportingPeriod: "30s",
+ BlackoutPeriod: "40s",
+ WeightExpirationPeriod: "50s",
+ WeightUpdatePeriod: "1m",
+ ErrorUtilizationPenalty: "0.5",
+ MetricNamesForComputingUtilization: []string{"extra_name"},
+ },
+ },
+ expectedConfig: &LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: &LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ EnableOOBLoadReport: ptr.To(true),
+ OOBReportingPeriod: ptr.To(30 * time.Second),
+ BlackoutPeriod: ptr.To(40 * time.Second),
+ WeightExpirationPeriod: ptr.To(50 * time.Second),
+ WeightUpdatePeriod: ptr.To(1 * time.Minute),
+ ErrorUtilizationPenalty: ptr.To(float32(0.5)),
+ MetricNamesForComputingUtilization: []string{"extra_name"},
+ },
+ },
+ expectedErr: false,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ config, err := loadBalancerPolicyConfig(tc.strategy, tc.policy)
+ assert.Equal(t, tc.expectedConfig, config)
+ assert.Equal(t, tc.expectedErr, err != nil)
+ })
+ }
+}
diff --git a/internal/envoy/v3/cluster.go b/internal/envoy/v3/cluster.go
index 4717b966f8b..8cf6fb3810e 100644
--- a/internal/envoy/v3/cluster.go
+++ b/internal/envoy/v3/cluster.go
@@ -20,6 +20,8 @@ import (
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+ client_side_weighted_round_robin_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3"
+ round_robin_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3"
envoy_upstream_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"google.golang.org/protobuf/types/known/anypb"
@@ -49,7 +51,12 @@ func (e *EnvoyGen) Cluster(c *dag.Cluster) *envoy_config_cluster_v3.Cluster {
cluster.Name = envoy.Clustername(c)
cluster.AltStatName = envoy.AltStatName(service)
- cluster.LbPolicy = lbPolicy(c.LoadBalancerPolicy)
+
+ var isLoadBalancingPolicy bool
+ cluster.LoadBalancingPolicy, isLoadBalancingPolicy = loadBalancingPolicy(c.LoadBalancerPolicy, c.LoadBalancerPolicyConfig)
+ if !isLoadBalancingPolicy {
+ cluster.LbPolicy = lbPolicy(c.LoadBalancerPolicy)
+ }
cluster.HealthChecks = edshealthcheck(c)
cluster.DnsLookupFamily = parseDNSLookupFamily(c.DNSLookupFamily)
@@ -151,7 +158,11 @@ func (e *EnvoyGen) ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_clu
// to produce a stable, readable name.
cluster.AltStatName = strings.ReplaceAll(cluster.Name, "/", "_")
- cluster.LbPolicy = lbPolicy(ext.LoadBalancerPolicy)
+ var isLoadBalancingPolicy bool
+ cluster.LoadBalancingPolicy, isLoadBalancingPolicy = loadBalancingPolicy(ext.LoadBalancerPolicy, ext.LoadBalancerPolicyConfig)
+ if !isLoadBalancingPolicy {
+ cluster.LbPolicy = lbPolicy(ext.LoadBalancerPolicy)
+ }
// Cluster will be discovered via EDS.
cluster.ClusterDiscoveryType = ClusterDiscoveryType(envoy_config_cluster_v3.Cluster_EDS)
@@ -254,6 +265,65 @@ func lbPolicy(strategy string) envoy_config_cluster_v3.Cluster_LbPolicy {
}
}
+func loadBalancingPolicy(strategy string, config *dag.LoadBalancerPolicyConfig) (*envoy_config_cluster_v3.LoadBalancingPolicy, bool) {
+ var mainPolicy *envoy_config_cluster_v3.LoadBalancingPolicy_Policy
+ switch strategy {
+ case dag.LoadBalancerPolicyClientSideWeightedRoundRobin:
+ var lbConfig *dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin
+ if config != nil {
+ lbConfig = config.ClientSideWeightedRoundRobin
+ }
+ mainPolicy = &envoy_config_cluster_v3.LoadBalancingPolicy_Policy{
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.client_side_weighted_round_robin",
+ TypedConfig: protobuf.MustMarshalAny(loadBalancerPolicyConfigClientSideWeightedRoundRobinToPB(lbConfig)),
+ },
+ }
+ default:
+ return nil, false
+ }
+
+ fallbackPolicy := &envoy_config_cluster_v3.LoadBalancingPolicy_Policy{
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.round_robin",
+ TypedConfig: protobuf.MustMarshalAny(&round_robin_v3.RoundRobin{}),
+ },
+ }
+
+ return &envoy_config_cluster_v3.LoadBalancingPolicy{
+ Policies: []*envoy_config_cluster_v3.LoadBalancingPolicy_Policy{mainPolicy, fallbackPolicy},
+ }, true
+}
+
+func loadBalancerPolicyConfigClientSideWeightedRoundRobinToPB(config *dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin) *client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin {
+ pb := &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{}
+ if config == nil {
+ return pb
+ }
+ if config.EnableOOBLoadReport != nil {
+ pb.EnableOobLoadReport = wrapperspb.Bool(*config.EnableOOBLoadReport)
+ }
+ if config.OOBReportingPeriod != nil {
+ pb.OobReportingPeriod = durationpb.New(*config.OOBReportingPeriod)
+ }
+ if config.BlackoutPeriod != nil {
+ pb.BlackoutPeriod = durationpb.New(*config.BlackoutPeriod)
+ }
+ if config.WeightExpirationPeriod != nil {
+ pb.WeightExpirationPeriod = durationpb.New(*config.WeightExpirationPeriod)
+ }
+ if config.WeightUpdatePeriod != nil {
+ pb.WeightUpdatePeriod = durationpb.New(*config.WeightUpdatePeriod)
+ }
+ if config.ErrorUtilizationPenalty != nil {
+ pb.ErrorUtilizationPenalty = wrapperspb.Float(*config.ErrorUtilizationPenalty)
+ }
+ if len(config.MetricNamesForComputingUtilization) > 0 {
+ pb.MetricNamesForComputingUtilization = config.MetricNamesForComputingUtilization
+ }
+ return pb
+}
+
func edshealthcheck(c *dag.Cluster) []*envoy_config_core_v3.HealthCheck {
if c.HTTPHealthCheckPolicy == nil && c.TCPHealthCheckPolicy == nil {
return nil
diff --git a/internal/envoy/v3/cluster_test.go b/internal/envoy/v3/cluster_test.go
index ba37a8e7ef4..74784ead076 100644
--- a/internal/envoy/v3/cluster_test.go
+++ b/internal/envoy/v3/cluster_test.go
@@ -20,6 +20,8 @@ import (
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
+ client_side_weighted_round_robin_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3"
+ round_robin_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3"
envoy_upstream_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/stretchr/testify/assert"
@@ -864,6 +866,51 @@ func TestCluster(t *testing.T) {
},
},
},
+ "cluster with client side wrr lb policy": {
+ cluster: &dag.Cluster{
+ Upstream: service(s1),
+ LoadBalancerPolicy: dag.LoadBalancerPolicyClientSideWeightedRoundRobin,
+ LoadBalancerPolicyConfig: &dag.LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ MetricNamesForComputingUtilization: []string{
+ "extra_field",
+ },
+ },
+ },
+ },
+ want: &envoy_config_cluster_v3.Cluster{
+ Name: "default/kuard/443/2401dd8c4c",
+ AltStatName: "default_kuard_443",
+ ClusterDiscoveryType: ClusterDiscoveryType(envoy_config_cluster_v3.Cluster_EDS),
+ EdsClusterConfig: &envoy_config_cluster_v3.Cluster_EdsClusterConfig{
+ EdsConfig: edsConfig,
+ ServiceName: "default/kuard/http",
+ },
+ LoadBalancingPolicy: &envoy_config_cluster_v3.LoadBalancingPolicy{
+ Policies: []*envoy_config_cluster_v3.LoadBalancingPolicy_Policy{
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.client_side_weighted_round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ MetricNamesForComputingUtilization: []string{
+ "extra_field",
+ },
+ }),
+ },
+ },
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &round_robin_v3.RoundRobin{},
+ ),
+ },
+ },
+ },
+ },
+ },
+ },
}
for name, tc := range tests {
@@ -1241,6 +1288,176 @@ func TestClusterCommonLBConfig(t *testing.T) {
assert.Equal(t, want, got)
}
+func TestLoadBalancingPolicy(t *testing.T) {
+ tests := []struct {
+ name string
+ strategy string
+ config *dag.LoadBalancerPolicyConfig
+ expectedPolicy *envoy_config_cluster_v3.LoadBalancingPolicy
+ expectedOk bool
+ }{
+ {
+ name: "unhandled strategy",
+ strategy: "unhandled strategy",
+ config: nil,
+ expectedPolicy: nil,
+ expectedOk: false,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: nil policy",
+ strategy: "ClientSideWeightedRoundRobin",
+ config: &dag.LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: nil,
+ },
+ expectedPolicy: &envoy_config_cluster_v3.LoadBalancingPolicy{
+ Policies: []*envoy_config_cluster_v3.LoadBalancingPolicy_Policy{
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.client_side_weighted_round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{},
+ ),
+ },
+ },
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &round_robin_v3.RoundRobin{},
+ ),
+ },
+ },
+ },
+ },
+ expectedOk: true,
+ },
+ {
+ name: "ClientSideWeightedRoundRobin: non empty policy",
+ strategy: "ClientSideWeightedRoundRobin",
+ config: &dag.LoadBalancerPolicyConfig{
+ ClientSideWeightedRoundRobin: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ EnableOOBLoadReport: ptr.To(true),
+ },
+ },
+ expectedPolicy: &envoy_config_cluster_v3.LoadBalancingPolicy{
+ Policies: []*envoy_config_cluster_v3.LoadBalancingPolicy_Policy{
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.client_side_weighted_round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ EnableOobLoadReport: wrapperspb.Bool(true),
+ },
+ ),
+ },
+ },
+ {
+ TypedExtensionConfig: &envoy_config_core_v3.TypedExtensionConfig{
+ Name: "envoy.load_balancing_policies.round_robin",
+ TypedConfig: protobuf.MustMarshalAny(
+ &round_robin_v3.RoundRobin{},
+ ),
+ },
+ },
+ },
+ },
+ expectedOk: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ policy, ok := loadBalancingPolicy(tt.strategy, tt.config)
+ assert.Equal(t, tt.expectedPolicy, policy)
+ assert.Equal(t, tt.expectedOk, ok)
+ })
+ }
+}
+
+func TestLoadBalancerPolicyConfigClientSideWeightedRoundRobinToPB(t *testing.T) {
+ tests := []struct {
+ name string
+ config *dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin
+ expectedWRR *client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin
+ }{
+ {
+ name: "nil config",
+ config: nil,
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{},
+ },
+ {
+ name: "non empty EnableOOBLoadReport",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ EnableOOBLoadReport: ptr.To(true),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ EnableOobLoadReport: wrapperspb.Bool(true),
+ },
+ },
+ {
+ name: "non empty OOBReportingPeriod",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ OOBReportingPeriod: ptr.To(30 * time.Second),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ OobReportingPeriod: durationpb.New(30 * time.Second),
+ },
+ },
+ {
+ name: "non empty BlackoutPeriod",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ BlackoutPeriod: ptr.To(40 * time.Second),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ BlackoutPeriod: durationpb.New(40 * time.Second),
+ },
+ },
+ {
+ name: "non empty WeightExpirationPeriod",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ WeightExpirationPeriod: ptr.To(50 * time.Second),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ WeightExpirationPeriod: durationpb.New(50 * time.Second),
+ },
+ },
+ {
+ name: "non empty WeightUpdatePeriod",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ WeightUpdatePeriod: ptr.To(time.Minute),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ WeightUpdatePeriod: durationpb.New(time.Minute),
+ },
+ },
+ {
+ name: "non empty ErrorUtilizationPenalty",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ ErrorUtilizationPenalty: proto.Float32(0.5),
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ ErrorUtilizationPenalty: wrapperspb.Float(0.5),
+ },
+ },
+ {
+ name: "non empty MetricNamesForComputingUtilization",
+ config: &dag.LoadBalancerPolicyConfigClientSideWeightedRoundRobin{
+ MetricNamesForComputingUtilization: []string{"extra_field"},
+ },
+ expectedWRR: &client_side_weighted_round_robin_v3.ClientSideWeightedRoundRobin{
+ MetricNamesForComputingUtilization: []string{"extra_field"},
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ wrr := loadBalancerPolicyConfigClientSideWeightedRoundRobinToPB(tt.config)
+ assert.Equal(t, tt.expectedWRR, wrr)
+ })
+ }
+}
+
func service(s *core_v1.Service, protocols ...string) *dag.Service {
protocol := ""
if len(protocols) > 0 {
diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html
index 76cad6d8b2e..3da16cb2f67 100644
--- a/site/content/docs/main/config/api-reference.html
+++ b/site/content/docs/main/config/api-reference.html
@@ -2500,6 +2500,139 @@ LoadBalancerPolicy
strategy will fall back to the default RoundRobin.
+
+
+clientSideWeightedRoundRobinPolicy
+
+
+
+LoadBalancerPolicyClientSideWeightedRoundRobinPolicy
+
+
+ |
+
+ ClientSideWeightedRoundRobinPolicy contains configuration for a client side WRR LB policy.
+ |
+
+
+
+LoadBalancerPolicyClientSideWeightedRoundRobinPolicy
+
+
+(Appears on:
+LoadBalancerPolicy)
+
+
+
LoadBalancerPolicyClientSideWeightedRoundRobinPolicy holds configuration for a client side wrr lb policy.
+For default values, constraints and behavior patterns refer to the envoy doc.
+
+
+
+
+| Field |
+Description |
+
+
+
+
+
+enableOobLoadReport
+
+
+bool
+
+ |
+
+ Whether to enable out-of-band utilization reporting collection from the endpoints.
+ |
+
+
+
+oobReportingPeriod
+
+
+string
+
+ |
+
+ Load reporting interval to request from the server. Note that the
+server may not provide reports as frequently as the client requests.
+Used only when enable_oob_load_report is true.
+ |
+
+
+
+blackoutPeriod
+
+
+string
+
+ |
+
+ A given endpoint must report load metrics continuously for at least
+this long before the endpoint weight will be used. This avoids
+churn when the set of endpoint addresses changes. Takes effect
+both immediately after we establish a connection to an endpoint and
+after weight_expiration_period has caused us to stop using the most
+recent load metrics.
+ |
+
+
+
+weightExpirationPeriod
+
+
+string
+
+ |
+
+ If a given endpoint has not reported load metrics in this long,
+then we stop using the reported weight. This ensures that we do
+not continue to use very stale weights. Once we stop using a stale
+value, if we later start seeing fresh reports again, the
+blackout_period applies.
+ |
+
+
+
+weightUpdatePeriod
+
+
+string
+
+ |
+
+ How often endpoint weights are recalculated.
+ |
+
+
+
+errorUtilizationPenalty
+
+
+string
+
+ |
+
+ The multiplier used to adjust endpoint weights with the error rate
+calculated as eps/qps.
+ |
+
+
+
+metricNamesForComputingUtilization
+
+
+[]string
+
+ |
+
+ By default, endpoint weight is computed based on the :ref:application_utilization <envoy_v3_api_field_.xds.data.orca.v3.OrcaLoadReport.application_utilization> field reported by the endpoint.
+If that field is not set, then utilization will instead be computed by taking the max of the values of the metrics specified here.
+For map fields in the ORCA proto, the string will be of the form “.“. For example, the string “named_metrics.foo“ will mean to look for the key “foo“ in the ORCA :ref:named_metrics <envoy_v3_api_field_.xds.data.orca.v3.OrcaLoadReport.named_metrics> field.
+If none of the specified metrics are present in the load report, then :ref:cpu_utilization <envoy_v3_api_field_.xds.data.orca.v3.OrcaLoadReport.cpu_utilization> is used instead.
+ |
+
LocalRateLimitPolicy
diff --git a/site/content/docs/main/config/request-routing.md b/site/content/docs/main/config/request-routing.md
index 19ef5386e86..86cb491c4fc 100644
--- a/site/content/docs/main/config/request-routing.md
+++ b/site/content/docs/main/config/request-routing.md
@@ -351,6 +351,7 @@ The following list are the options available to choose from:
- `Random`: The random strategy selects a random healthy Endpoints.
- `RequestHash`: The request hashing strategy allows for load balancing based on request attributes. An upstream Endpoint is selected based on the hash of an element of a request. For example, requests that contain a consistent value in an HTTP request header will be routed to the same upstream Endpoint. Currently, only hashing of HTTP request headers, query parameters and the source IP of a request is supported.
- `Cookie`: The cookie load balancing strategy is similar to the request hash strategy and is a convenience feature to implement session affinity, as described below.
+- `ClientSideWeightedRoundRobin`: The weighted round-robin strategy, based on the ORCA metrics reported in responses.
More information on the load balancing strategy can be found in [Envoy's documentation][7].
@@ -460,6 +461,29 @@ spec:
parameterName: param2
```
+ClientSideWeightedRoundRobin
+```yaml
+# httpproxy-client-side-wrr.yaml
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+ name: client-side-wrr
+ namespace: default
+spec:
+ virtualhost:
+ fqdn: client-side-wrr.com
+ routes:
+ - conditions:
+ - prefix: /
+ services:
+ - name: client-side-wrr-service
+ port: 8080
+ protocol: h2c
+ loadBalancerPolicy:
+ strategy: ClientSideWeightedRoundRobin
+ clientSideWeightedRoundRobinPolicy:
+ blackoutPeriod: '1m'
+```
## Session Affinity
Session affinity, also known as _sticky sessions_, is a load balancing strategy whereby a sequence of requests from a single client are consistently routed to the same application backend.
diff --git a/test/e2e/httpproxy/grpc_test.go b/test/e2e/httpproxy/grpc_test.go
index 95daa84cc45..074901acc36 100644
--- a/test/e2e/httpproxy/grpc_test.go
+++ b/test/e2e/httpproxy/grpc_test.go
@@ -253,3 +253,68 @@ func parseGRPCWebResponse(body []byte) grpcWebResponse {
return response
}
+
+func testGRPCClientSideWRR(namespace string) {
+ Specify("requests to a gRPC service with envoy client side WRR work without weights", func() {
+ t := f.T()
+
+ f.Fixtures.GRPC.Deploy(namespace, "grpc-echo")
+
+ p := &contour_v1.HTTPProxy{
+ ObjectMeta: meta_v1.ObjectMeta{
+ Namespace: namespace,
+ Name: "grpc-cswrr",
+ },
+ Spec: contour_v1.HTTPProxySpec{
+ VirtualHost: &contour_v1.VirtualHost{
+ Fqdn: "grpc-cswrr.projectcontour.io",
+ },
+ Routes: []contour_v1.Route{
+ {
+ Services: []contour_v1.Service{
+ {
+ Name: "grpc-echo",
+ Port: 9000,
+ Protocol: ptr.To("h2c"),
+ },
+ },
+ LoadBalancerPolicy: &contour_v1.LoadBalancerPolicy{
+ Strategy: "ClientSideWeightedRoundRobin",
+ ClientSideWeightedRoundRobinPolicy: &contour_v1.LoadBalancerPolicyClientSideWeightedRoundRobinPolicy{},
+ },
+ },
+ },
+ },
+ }
+ require.True(f.T(), f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid))
+
+ addr := strings.TrimPrefix(f.HTTP.HTTPURLBase, "http://")
+ retryOpts := []grpc_retry.CallOption{
+ // Retry if Envoy returns unavailable, the upstream
+ // may not be healthy yet.
+ // Also retry if we get the unimplemented status, see:
+ // https://github.com/projectcontour/contour/issues/4707
+ // Also retry unauthenticated to accommodate eventual consistency
+ // after a global ExtAuth test.
+ grpc_retry.WithCodes(codes.Unavailable, codes.Unimplemented, codes.Unauthenticated),
+ grpc_retry.WithBackoff(grpc_retry.BackoffExponential(time.Millisecond * 10)),
+ grpc_retry.WithMax(20),
+ }
+ conn, err := grpc.NewClient(addr,
+ grpc.WithAuthority(p.Spec.VirtualHost.Fqdn),
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)),
+ )
+ require.NoError(t, err)
+ defer conn.Close()
+
+ // Give significant leeway for retries to complete with exponential backoff.
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
+ defer cancel()
+ client := yages.NewEchoClient(conn)
+ resp, err := client.Ping(ctx, &yages.Empty{})
+
+ require.NoErrorf(t, err, "gRPC error code %d", status.Code(err))
+ require.Equal(t, "pong", resp.Text)
+ })
+}
diff --git a/test/e2e/httpproxy/httpproxy_test.go b/test/e2e/httpproxy/httpproxy_test.go
index 0a838a2294a..adeccfa64f1 100644
--- a/test/e2e/httpproxy/httpproxy_test.go
+++ b/test/e2e/httpproxy/httpproxy_test.go
@@ -854,6 +854,8 @@ descriptors:
f.NamespacedTest("grpc-upstream-plaintext", testGRPCServicePlaintext)
f.NamespacedTest("grpc-web", testGRPCWeb)
+
+ f.NamespacedTest("grpc-cswrr", testGRPCClientSideWRR)
})
Context("global external auth", func() {