diff --git a/CODEOWNERS b/CODEOWNERS index 925cbb03c6851..cf716e0eb61d1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -284,6 +284,11 @@ extensions/filters/http/oauth2 @derekargueta @snowp /*/extensions/path/rewrite/uri_template @alyssawilk @yanjunxiang-google # Dubbo codec /*/extensions/common/dubbo @wbpcode @lizan +# upstream load balancing policies +/*/extensions/load_balancing_policies/common @wbpcode @UNOWNED +/*/extensions/load_balancing_policies/least_request @wbpcode @UNOWNED +/*/extensions/load_balancing_policies/random @wbpcode @UNOWNED +/*/extensions/load_balancing_policies/round_robin @wbpcode @UNOWNED # Early header mutation /*/extensions/http/early_header_mutation/header_mutation @wbpcode @UNOWNED diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index e9438bd5f6d17..0a266e17e63de 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -1216,6 +1216,7 @@ message LoadBalancingPolicy { reserved "config", "name", "typed_config"; + // [#extension-category: envoy.load_balancing_policies] core.v3.TypedExtensionConfig typed_extension_config = 4; } diff --git a/api/envoy/extensions/load_balancing_policies/common/v3/common.proto b/api/envoy/extensions/load_balancing_policies/common/v3/common.proto index a19765f77b4b7..51520690a29ac 100644 --- a/api/envoy/extensions/load_balancing_policies/common/v3/common.proto +++ b/api/envoy/extensions/load_balancing_policies/common/v3/common.proto @@ -18,7 +18,6 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Common configuration for two or more load balancing policy extensions] -// [#not-implemented-hide:] message LocalityLbConfig { // Configuration for :ref:`zone aware routing diff --git a/api/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto b/api/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto index d6eb834acded1..87a379c669124 100644 --- a/api/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto +++ b/api/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto @@ -17,7 +17,7 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Least Request Load Balancing Policy] -// [#not-implemented-hide:] +// [#extension: envoy.load_balancing_policies.least_request] // This configuration allows the built-in LEAST_REQUEST LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview diff --git a/api/envoy/extensions/load_balancing_policies/random/v3/random.proto b/api/envoy/extensions/load_balancing_policies/random/v3/random.proto index 37efa7c13305c..1fc0a49049be9 100644 --- a/api/envoy/extensions/load_balancing_policies/random/v3/random.proto +++ b/api/envoy/extensions/load_balancing_policies/random/v3/random.proto @@ -13,7 +13,7 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Random Load Balancing Policy] -// [#not-implemented-hide:] +// [#extension: envoy.load_balancing_policies.random] // This configuration allows the built-in Random LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview diff --git a/api/envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto b/api/envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto index 01b8a62e002a4..63fe49e9b0bca 100644 --- a/api/envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto +++ b/api/envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto @@ -13,7 +13,7 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Round Robin Load Balancing Policy] -// [#not-implemented-hide:] +// [#extension: envoy.load_balancing_policies.round_robin] // This configuration allows the built-in ROUND_ROBIN LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 40abfa27ea6c3..855af3701416c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -206,6 +206,15 @@ new_features: - area: http change: | allowing the dynamic forward proxy cluster to :ref:`allow_coalesced_connections ` for HTTP/2 and HTTP/3 connections. +- area: upstream + change: | + added :ref:`least request extension ` to suppport the :ref:`load balancer policy `. +- area: upstream + change: | + added :ref:`random extension ` to suppport the :ref:`load balancer policy `. +- area: upstream + change: | + added :ref:`round robin extension ` to suppport the :ref:`load balancer policy `. - area: generic_proxy change: | added :ref:`generic rds support `. diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index eb22dff4415a9..10a4752b26401 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -42,3 +42,4 @@ Extensions upstream/upstream wasm/wasm watchdog/watchdog + load_balancing_policies/load_balancing_policies diff --git a/docs/root/api-v3/config/load_balancing_policies/load_balancing_policies.rst b/docs/root/api-v3/config/load_balancing_policies/load_balancing_policies.rst new file mode 100644 index 0000000000000..39133e7a38da9 --- /dev/null +++ b/docs/root/api-v3/config/load_balancing_policies/load_balancing_policies.rst @@ -0,0 +1,10 @@ +.. _envoy_v3_api_config_load_balancer_policies: + +Load balancing policies +======================= + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../extensions/load_balancing_policies/*/v3/* diff --git a/docs/root/intro/arch_overview/upstream/load_balancing_policies.rst b/docs/root/intro/arch_overview/upstream/load_balancing_policies.rst new file mode 100644 index 0000000000000..680860ec33b2b --- /dev/null +++ b/docs/root/intro/arch_overview/upstream/load_balancing_policies.rst @@ -0,0 +1,39 @@ +Load balancing policies +======================= + +Extendable load balancing policies could be configured for each cluster by +:ref:`load balancer policy `. +And the developer can implement a custom load balancing policy and configured it. + + +See built-in load balancing policies by :ref:`APIs `. + + +:ref:`Enum based load balancing policies ` +will be suupported for backward compatibility and marked as deprecated. The new extendable load +balancing policies should be used as a priority if the related policies are implemented. + + +Use :ref:`random load balancing policy ` +as an example: + +.. code-block:: yaml + + name: example_cluster + type: STRICT_DNS + connect_timeout: 0.25s + load_assignment: + cluster_name: example_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: example.com + port_value: 80 + load_balancing_policy: + policies: + - typed_extension_config: + name: envoy.load_balancing_policies.random + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.random.v3.Random diff --git a/docs/root/intro/arch_overview/upstream/upstream.rst b/docs/root/intro/arch_overview/upstream/upstream.rst index 2e7350917ea64..aaa72f3b6cde3 100644 --- a/docs/root/intro/arch_overview/upstream/upstream.rst +++ b/docs/root/intro/arch_overview/upstream/upstream.rst @@ -17,3 +17,4 @@ Upstream clusters circuit_breaking upstream_filters load_reporting_service + load_balancing_policies diff --git a/mobile/examples/objective-c/hello_world/ViewController.m b/mobile/examples/objective-c/hello_world/ViewController.m index 4d1e6619bc769..6002692a7d9f1 100644 --- a/mobile/examples/objective-c/hello_world/ViewController.m +++ b/mobile/examples/objective-c/hello_world/ViewController.m @@ -39,6 +39,7 @@ - (void)startEnvoy { NSLog(@"starting Envoy..."); EngineBuilder *builder = [[EngineBuilder alloc] init]; [builder addLogLevel:LogLevelDebug]; + [builder addGrpcStatsDomain:@"example.com"]; [builder enableDNSCache:YES]; [builder addKeyValueStoreWithName:@"reserved.platform_store" keyValueStore:NSUserDefaults.standardUserDefaults]; @@ -47,7 +48,7 @@ - (void)startEnvoy { }]; id engine = [builder build]; - NSLog(@"started Envoy, beginning requests..."); + NSLog(@"started Envoy, beginning requeststest.."); self.streamClient = [engine streamClient]; self.pulseClient = [engine pulseClient]; [self startRequests]; diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index b63cc77af4922..4a216ad5dc429 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -265,6 +265,10 @@ envoy_cc_library( "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_protos_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/common/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/least_request/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/random/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/round_robin/v3:pkg_cc_proto", ], ) diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index eb719f7244d6c..8c2fb90271dd1 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -77,6 +77,36 @@ bool hostWeightsAreEqual(const HostVector& hosts) { } // namespace +absl::optional +LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig( + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) { + + if (common_config.has_locality_weighted_lb_config()) { + envoy::extensions::load_balancing_policies::common::v3::LocalityLbConfig locality_lb_config; + locality_lb_config.mutable_locality_weighted_lb_config(); + return locality_lb_config; + } else if (common_config.has_zone_aware_lb_config()) { + envoy::extensions::load_balancing_policies::common::v3::LocalityLbConfig locality_lb_config; + auto& zone_aware_lb_config = *locality_lb_config.mutable_zone_aware_lb_config(); + + const auto& legacy_zone_aware_lb_config = common_config.zone_aware_lb_config(); + if (legacy_zone_aware_lb_config.has_routing_enabled()) { + *zone_aware_lb_config.mutable_routing_enabled() = + legacy_zone_aware_lb_config.routing_enabled(); + } + if (legacy_zone_aware_lb_config.has_min_cluster_size()) { + *zone_aware_lb_config.mutable_min_cluster_size() = + legacy_zone_aware_lb_config.min_cluster_size(); + } + zone_aware_lb_config.set_fail_traffic_on_panic( + legacy_zone_aware_lb_config.fail_traffic_on_panic()); + + return locality_lb_config; + } + + return {}; +} + std::pair LoadBalancerBase::choosePriority(uint64_t hash, const HealthyLoad& healthy_per_priority_load, const DegradedLoad& degraded_per_priority_load) { @@ -108,14 +138,11 @@ LoadBalancerBase::choosePriority(uint64_t hash, const HealthyLoad& healthy_per_p return {0, HostAvailability::Healthy}; } -LoadBalancerBase::LoadBalancerBase( - const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, - Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +LoadBalancerBase::LoadBalancerBase(const PrioritySet& priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + uint32_t healthy_panic_threshold) : stats_(stats), runtime_(runtime), random_(random), - default_healthy_panic_percent_(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( - common_config, healthy_panic_threshold, 100, 50)), - priority_set_(priority_set) { + default_healthy_panic_percent_(healthy_panic_threshold), priority_set_(priority_set) { for (auto& host_set : priority_set_.hostSetsPerPriority()) { recalculatePerPriorityState(host_set->priority(), priority_set_, per_priority_load_, per_priority_health_, per_priority_degraded_, total_healthy_hosts_); @@ -352,16 +379,23 @@ LoadBalancerBase::chooseHostSet(LoadBalancerContext* context, uint64_t hash) con ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, - Runtime::Loader& runtime, Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : LoadBalancerBase(priority_set, stats, runtime, random, common_config), + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const absl::optional locality_config) + : LoadBalancerBase(priority_set, stats, runtime, random, healthy_panic_threshold), local_priority_set_(local_priority_set), - locality_weighted_balancing_(common_config.has_locality_weighted_lb_config()), - routing_enabled_(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( - common_config.zone_aware_lb_config(), routing_enabled, 100, 100)), - min_cluster_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(common_config.zone_aware_lb_config(), - min_cluster_size, 6U)), - fail_traffic_on_panic_(common_config.zone_aware_lb_config().fail_traffic_on_panic()) { + locality_weighted_balancing_(locality_config.has_value() && + locality_config->has_locality_weighted_lb_config()), + routing_enabled_(locality_config.has_value() + ? PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + locality_config->zone_aware_lb_config(), routing_enabled, 100, 100) + : 100), + min_cluster_size_(locality_config.has_value() + ? PROTOBUF_GET_WRAPPED_OR_DEFAULT( + locality_config->zone_aware_lb_config(), min_cluster_size, 6U) + : 6U), + fail_traffic_on_panic_(locality_config.has_value() + ? locality_config->zone_aware_lb_config().fail_traffic_on_panic() + : false) { ASSERT(!priority_set.hostSetsPerPriority().empty()); resizePerPriorityState(); priority_update_cb_ = priority_set_.addPriorityUpdateCb( @@ -727,12 +761,11 @@ const HostVector& ZoneAwareLoadBalancerBase::hostSourceToHosts(HostsSource hosts EdfLoadBalancerBase::EdfLoadBalancerBase( const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, - Runtime::Loader& runtime, Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, - const absl::optional slow_start_config, - TimeSource& time_source) + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const absl::optional locality_config, + const absl::optional slow_start_config, TimeSource& time_source) : ZoneAwareLoadBalancerBase(priority_set, local_priority_set, stats, runtime, random, - common_config), + healthy_panic_threshold, locality_config), seed_(random_.random()), slow_start_window_(slow_start_config.has_value() ? std::chrono::milliseconds(DurationUtil::durationToMilliseconds( diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index 1c28cb327d7f8..97685a7102990 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -11,6 +11,13 @@ #include "envoy/common/callback.h" #include "envoy/common/random_generator.h" #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/load_balancing_policies/common/v3/common.pb.h" +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request.pb.h" +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request.pb.validate.h" +#include "envoy/extensions/load_balancing_policies/random/v3/random.pb.h" +#include "envoy/extensions/load_balancing_policies/random/v3/random.pb.validate.h" +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.pb.h" +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.pb.validate.h" #include "envoy/runtime/runtime.h" #include "envoy/upstream/load_balancer.h" #include "envoy/upstream/upstream.h" @@ -25,6 +32,53 @@ namespace Upstream { // Priority levels and localities are considered overprovisioned with this factor. static constexpr uint32_t kDefaultOverProvisioningFactor = 140; +class LoadBalancerConfigHelper { +public: + template + static absl::optional + slowStartConfigFromLegacyProto(const LegacyProto& proto_config) { + if (!proto_config.has_slow_start_config()) { + return {}; + } + + envoy::extensions::load_balancing_policies::common::v3::SlowStartConfig slow_start_config; + const auto& legacy_slow_start_config = proto_config.slow_start_config(); + if (legacy_slow_start_config.has_slow_start_window()) { + *slow_start_config.mutable_slow_start_window() = legacy_slow_start_config.slow_start_window(); + } + if (legacy_slow_start_config.has_aggression()) { + *slow_start_config.mutable_aggression() = legacy_slow_start_config.aggression(); + } + if (legacy_slow_start_config.has_min_weight_percent()) { + *slow_start_config.mutable_min_weight_percent() = + legacy_slow_start_config.min_weight_percent(); + } + return slow_start_config; + } + + template + static absl::optional + slowStartConfigFromProto(const Proto& proto_config) { + if (!proto_config.has_slow_start_config()) { + return {}; + } + return proto_config.slow_start_config(); + } + + static absl::optional + localityLbConfigFromCommonLbConfig( + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + + template + static absl::optional + localityLbConfigFromProto(const Proto& proto_config) { + if (!proto_config.has_locality_lb_config()) { + return {}; + } + return proto_config.locality_lb_config(); + } +}; + /** * Base class for all LB implementations. */ @@ -69,8 +123,7 @@ class LoadBalancerBase : public LoadBalancer { void recalculateLoadInTotalPanic(); LoadBalancerBase(const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, - Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + Random::RandomGenerator& random, uint32_t healthy_panic_threshold); // Choose host set randomly, based on the healthy_per_priority_load_ and // degraded_per_priority_load_. per_priority_load_ is consulted first, spilling over to @@ -194,14 +247,16 @@ class LoadBalancerContextBase : public LoadBalancerContext { */ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { public: + using LocalityLbConfig = envoy::extensions::load_balancing_policies::common::v3::LocalityLbConfig; + HostConstSharedPtr chooseHost(LoadBalancerContext* context) override; protected: // Both priority_set and local_priority_set if non-null must have at least one host set. - ZoneAwareLoadBalancerBase( - const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, - Runtime::Loader& runtime, Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + ZoneAwareLoadBalancerBase(const PrioritySet& priority_set, const PrioritySet* local_priority_set, + ClusterLbStats& stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const absl::optional locality_config); // When deciding which hosts to use on an LB decision, we need to know how to index into the // priority_set. This priority_set cursor is used by ZoneAwareLoadBalancerBase subclasses, e.g. @@ -404,12 +459,14 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase, Logger::Loggable { public: - EdfLoadBalancerBase( - const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, - Runtime::Loader& runtime, Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, - const absl::optional slow_start_cofig, - TimeSource& time_source); + using SlowStartConfig = envoy::extensions::load_balancing_policies::common::v3::SlowStartConfig; + + EdfLoadBalancerBase(const PrioritySet& priority_set, const PrioritySet* local_priority_set, + ClusterLbStats& stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const absl::optional locality_config, + const absl::optional slow_start_config, + TimeSource& time_source); // Upstream::ZoneAwareLoadBalancerBase HostConstSharedPtr peekAnotherHost(LoadBalancerContext* context) override; @@ -480,15 +537,31 @@ class RoundRobinLoadBalancer : public EdfLoadBalancerBase { round_robin_config, TimeSource& time_source) : EdfLoadBalancerBase( - priority_set, local_priority_set, stats, runtime, random, common_config, - (round_robin_config.has_value() && round_robin_config.value().has_slow_start_config()) - ? absl::optional( - round_robin_config.value().slow_start_config()) + priority_set, local_priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config), + round_robin_config.has_value() + ? LoadBalancerConfigHelper::slowStartConfigFromLegacyProto( + round_robin_config.value()) : absl::nullopt, time_source) { initialize(); } + RoundRobinLoadBalancer( + const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::round_robin::v3::RoundRobin& + round_robin_config, + TimeSource& time_source) + : EdfLoadBalancerBase( + priority_set, local_priority_set, stats, runtime, random, healthy_panic_threshold, + LoadBalancerConfigHelper::localityLbConfigFromProto(round_robin_config), + LoadBalancerConfigHelper::slowStartConfigFromProto(round_robin_config), time_source) { + initialize(); + } + private: void refreshHostSource(const HostsSource& source) override { // insert() is used here on purpose so that we don't overwrite the index if the host source @@ -558,11 +631,13 @@ class LeastRequestLoadBalancer : public EdfLoadBalancerBase { least_request_config, TimeSource& time_source) : EdfLoadBalancerBase( - priority_set, local_priority_set, stats, runtime, random, common_config, - (least_request_config.has_value() && - least_request_config.value().has_slow_start_config()) - ? absl::optional( - least_request_config.value().slow_start_config()) + priority_set, local_priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config), + least_request_config.has_value() + ? LoadBalancerConfigHelper::slowStartConfigFromLegacyProto( + least_request_config.value()) : absl::nullopt, time_source), choice_count_( @@ -577,6 +652,25 @@ class LeastRequestLoadBalancer : public EdfLoadBalancerBase { initialize(); } + LeastRequestLoadBalancer( + const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest& + least_request_config, + TimeSource& time_source) + : EdfLoadBalancerBase( + priority_set, local_priority_set, stats, runtime, random, healthy_panic_threshold, + LoadBalancerConfigHelper::localityLbConfigFromProto(least_request_config), + LoadBalancerConfigHelper::slowStartConfigFromProto(least_request_config), time_source), + choice_count_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(least_request_config, choice_count, 2)), + active_request_bias_runtime_( + least_request_config.has_active_request_bias() + ? absl::optional( + {least_request_config.active_request_bias(), runtime}) + : absl::nullopt) { + initialize(); + } + protected: void refresh(uint32_t priority) override { active_request_bias_ = active_request_bias_runtime_ != absl::nullopt @@ -654,8 +748,19 @@ class RandomLoadBalancer : public ZoneAwareLoadBalancerBase, ClusterLbStats& stats, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ZoneAwareLoadBalancerBase(priority_set, local_priority_set, stats, runtime, random, - common_config) {} + : ZoneAwareLoadBalancerBase( + priority_set, local_priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} + + RandomLoadBalancer( + const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::random::v3::Random& random_config) + : ZoneAwareLoadBalancerBase( + priority_set, local_priority_set, stats, runtime, random, healthy_panic_threshold, + LoadBalancerConfigHelper::localityLbConfigFromProto(random_config)) {} // Upstream::ZoneAwareLoadBalancerBase HostConstSharedPtr chooseHostOnce(LoadBalancerContext* context) override; diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h index 2cd2b78a17cb6..12dd97139c814 100644 --- a/source/common/upstream/thread_aware_lb_impl.h +++ b/source/common/upstream/thread_aware_lb_impl.h @@ -110,7 +110,9 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : LoadBalancerBase(priority_set, stats, runtime, random, common_config), + : LoadBalancerBase(priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50)), factory_(new LoadBalancerFactoryImpl(stats, random)) {} private: diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index 7d0a215d6e9ec..d71d5a558137e 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -95,7 +95,8 @@ class AggregateClusterLoadBalancer : public Upstream::LoadBalancer, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) : Upstream::LoadBalancerBase(priority_context.priority_set_, lb_stats, runtime, random, - common_config), + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50)), priority_context_(priority_context) {} // Upstream::LoadBalancer diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 4e094dcfc15db..eccab9f9b9715 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -393,6 +393,13 @@ EXTENSIONS = { "envoy.route.early_data_policy.default": "//source/extensions/early_data:default_early_data_policy_lib", # + # Load balancing policies for upstream + # + "envoy.load_balancing_policies.least_request": "//source/extensions/load_balancing_policies/least_request:config", + "envoy.load_balancing_policies.random": "//source/extensions/load_balancing_policies/random:config", + "envoy.load_balancing_policies.round_robin": "//source/extensions/load_balancing_policies/round_robin:config", + + # HTTP Early Header Mutation # "envoy.http.early_header_mutation.header_mutation": "//source/extensions/http/early_header_mutation/header_mutation:config", diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 1c6093744c4c8..31f424b44da77 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1348,10 +1348,31 @@ envoy.matching.custom_matchers.trie_matcher: status: alpha type_urls: - xds.type.matcher.v3.IPMatcher +envoy.load_balancing_policies.least_request: + categories: + - envoy.load_balancing_policies + security_posture: unknown + status: stable + type_urls: + - envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest +envoy.load_balancing_policies.random: + categories: + - envoy.load_balancing_policies + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable + type_urls: + - envoy.extensions.load_balancing_policies.random.v3.Random +envoy.load_balancing_policies.round_robin: + categories: + - envoy.load_balancing_policies + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable + type_urls: + - envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin envoy.http.early_header_mutation.header_mutation: categories: - envoy.http.early_header_mutation - security_posture: unknown + security_posture: robust_to_untrusted_downstream_and_upstream status: alpha type_urls: - envoy.extensions.http.early_header_mutation.header_mutation.v3.HeaderMutation diff --git a/source/extensions/load_balancing_policies/common/BUILD b/source/extensions/load_balancing_policies/common/BUILD new file mode 100644 index 0000000000000..4c045e2ec9de5 --- /dev/null +++ b/source/extensions/load_balancing_policies/common/BUILD @@ -0,0 +1,17 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "factory_base", + hdrs = ["factory_base.h"], + deps = [ + "//envoy/upstream:load_balancer_interface", + ], +) diff --git a/source/extensions/load_balancing_policies/common/factory_base.h b/source/extensions/load_balancing_policies/common/factory_base.h new file mode 100644 index 0000000000000..d532ec3d439f6 --- /dev/null +++ b/source/extensions/load_balancing_policies/common/factory_base.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include "envoy/upstream/load_balancer.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Common { + +template +class FactoryBase : public Upstream::TypedLoadBalancerFactory { +public: + FactoryBase(absl::string_view name) : name_(name) {} + + Upstream::ThreadAwareLoadBalancerPtr create(const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, + Envoy::Random::RandomGenerator& random, + TimeSource& time_source) override { + return std::make_unique( + std::make_shared(cluster_info, priority_set, runtime, random, time_source)); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return name_; } + +private: + class LbFactory : public Upstream::LoadBalancerFactory { + public: + LbFactory(const Upstream::ClusterInfo& cluster_info, const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, Envoy::Random::RandomGenerator& random, + TimeSource& time_source) + : cluster_info_(cluster_info), priority_set_(priority_set), runtime_(runtime), + random_(random), time_source_(time_source) {} + + Upstream::LoadBalancerPtr create() override { PANIC("not implemented"); } + Upstream::LoadBalancerPtr create(Upstream::LoadBalancerParams params) override { + return Impl()(params, cluster_info_, priority_set_, runtime_, random_, time_source_); + } + + public: + const Upstream::ClusterInfo& cluster_info_; + const Upstream::PrioritySet& priority_set_; + Runtime::Loader& runtime_; + Envoy::Random::RandomGenerator& random_; + TimeSource& time_source_; + }; + + class ThreadAwareLb : public Upstream::ThreadAwareLoadBalancer { + public: + ThreadAwareLb(Upstream::LoadBalancerFactorySharedPtr factory) : factory_(std::move(factory)) {} + + Upstream::LoadBalancerFactorySharedPtr factory() override { return factory_; } + void initialize() override {} + + private: + Upstream::LoadBalancerFactorySharedPtr factory_; + }; + + const std::string name_; +}; + +} // namespace Common +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/least_request/BUILD b/source/extensions/load_balancing_policies/least_request/BUILD new file mode 100644 index 0000000000000..5f1d06067544c --- /dev/null +++ b/source/extensions/load_balancing_policies/least_request/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + "//source/common/common:minimal_logger_lib", + "//source/common/upstream:load_balancer_lib", + "//source/extensions/load_balancing_policies/common:factory_base", + "@envoy_api//envoy/extensions/load_balancing_policies/least_request/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/load_balancing_policies/least_request/config.cc b/source/extensions/load_balancing_policies/least_request/config.cc new file mode 100644 index 0000000000000..d2dab339ec1fa --- /dev/null +++ b/source/extensions/load_balancing_policies/least_request/config.cc @@ -0,0 +1,43 @@ +#include "source/extensions/load_balancing_policies/least_request/config.h" + +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request.pb.h" + +#include "source/common/upstream/load_balancer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace LeastRequest { + +Upstream::LoadBalancerPtr LeastRequestCreator::operator()(Upstream::LoadBalancerParams params, + const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet&, + Runtime::Loader& runtime, + Random::RandomGenerator& random, + TimeSource& time_source) { + + const auto* typed_config = dynamic_cast< + const envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest*>( + cluster_info.loadBalancingPolicy().get()); + + // The load balancing policy configuration will be loaded and validated in the main thread when we + // load the cluster configuration. So we can assume the configuration is valid here. + ASSERT(typed_config != nullptr, + "Invalid load balancing policy configuration for least request load balancer"); + + return std::make_unique( + params.priority_set, params.local_priority_set, cluster_info.lbStats(), runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(cluster_info.lbConfig(), + healthy_panic_threshold, 100, 50), + *typed_config, time_source); +} + +/** + * Static registration for the Factory. @see RegisterFactory. + */ +REGISTER_FACTORY(Factory, Upstream::TypedLoadBalancerFactory); + +} // namespace LeastRequest +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/least_request/config.h b/source/extensions/load_balancing_policies/least_request/config.h new file mode 100644 index 0000000000000..e2e787936c283 --- /dev/null +++ b/source/extensions/load_balancing_policies/least_request/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request.pb.h" +#include "envoy/extensions/load_balancing_policies/least_request/v3/least_request.pb.validate.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/common/logger.h" +#include "source/extensions/load_balancing_policies/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace LeastRequest { + +struct LeastRequestCreator : public Logger::Loggable { + Upstream::LoadBalancerPtr operator()(Upstream::LoadBalancerParams params, + const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, Random::RandomGenerator& random, + TimeSource& time_source); +}; + +class Factory : public Common::FactoryBase< + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest, + LeastRequestCreator> { +public: + Factory() : FactoryBase("envoy.load_balancing_policies.least_request") {} +}; + +} // namespace LeastRequest +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/random/BUILD b/source/extensions/load_balancing_policies/random/BUILD new file mode 100644 index 0000000000000..a803dbb22f403 --- /dev/null +++ b/source/extensions/load_balancing_policies/random/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + "//source/common/common:minimal_logger_lib", + "//source/common/upstream:load_balancer_lib", + "//source/extensions/load_balancing_policies/common:factory_base", + "@envoy_api//envoy/extensions/load_balancing_policies/random/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/load_balancing_policies/random/config.cc b/source/extensions/load_balancing_policies/random/config.cc new file mode 100644 index 0000000000000..86d8394f32f7d --- /dev/null +++ b/source/extensions/load_balancing_policies/random/config.cc @@ -0,0 +1,43 @@ +#include "source/extensions/load_balancing_policies/random/config.h" + +#include "envoy/extensions/load_balancing_policies/random/v3/random.pb.h" + +#include "source/common/upstream/load_balancer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Random { + +Upstream::LoadBalancerPtr RandomCreator::operator()(Upstream::LoadBalancerParams params, + const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet&, + Runtime::Loader& runtime, + Envoy::Random::RandomGenerator& random, + TimeSource&) { + + const auto* typed_config = + dynamic_cast( + cluster_info.loadBalancingPolicy().get()); + + // The load balancing policy configuration will be loaded and validated in the main thread when we + // load the cluster configuration. So we can assume the configuration is valid here. + ASSERT(typed_config != nullptr, + "Invalid load balancing policy configuration for random load balancer"); + + return std::make_unique( + params.priority_set, params.local_priority_set, cluster_info.lbStats(), runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(cluster_info.lbConfig(), + healthy_panic_threshold, 100, 50), + *typed_config); +} + +/** + * Static registration for the Factory. @see RegisterFactory. + */ +REGISTER_FACTORY(Factory, Upstream::TypedLoadBalancerFactory); + +} // namespace Random +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/random/config.h b/source/extensions/load_balancing_policies/random/config.h new file mode 100644 index 0000000000000..b40754536a670 --- /dev/null +++ b/source/extensions/load_balancing_policies/random/config.h @@ -0,0 +1,32 @@ +#pragma once + +#include "envoy/extensions/load_balancing_policies/random/v3/random.pb.h" +#include "envoy/extensions/load_balancing_policies/random/v3/random.pb.validate.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/common/logger.h" +#include "source/extensions/load_balancing_policies/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Random { + +struct RandomCreator : public Logger::Loggable { + Upstream::LoadBalancerPtr + operator()(Upstream::LoadBalancerParams params, const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, Runtime::Loader& runtime, + Envoy::Random::RandomGenerator& random, TimeSource& time_source); +}; + +class Factory + : public Common::FactoryBase { +public: + Factory() : FactoryBase("envoy.load_balancing_policies.random") {} +}; + +} // namespace Random +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/round_robin/BUILD b/source/extensions/load_balancing_policies/round_robin/BUILD new file mode 100644 index 0000000000000..a047662218360 --- /dev/null +++ b/source/extensions/load_balancing_policies/round_robin/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + "//source/common/common:minimal_logger_lib", + "//source/common/upstream:load_balancer_lib", + "//source/extensions/load_balancing_policies/common:factory_base", + "@envoy_api//envoy/extensions/load_balancing_policies/round_robin/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/load_balancing_policies/round_robin/config.cc b/source/extensions/load_balancing_policies/round_robin/config.cc new file mode 100644 index 0000000000000..a55d47fa9c680 --- /dev/null +++ b/source/extensions/load_balancing_policies/round_robin/config.cc @@ -0,0 +1,43 @@ +#include "source/extensions/load_balancing_policies/round_robin/config.h" + +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.pb.h" + +#include "source/common/upstream/load_balancer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RoundRobin { + +Upstream::LoadBalancerPtr RoundRobinCreator::operator()(Upstream::LoadBalancerParams params, + const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet&, + Runtime::Loader& runtime, + Random::RandomGenerator& random, + TimeSource& time_source) { + + const auto* typed_config = + dynamic_cast( + cluster_info.loadBalancingPolicy().get()); + + // The load balancing policy configuration will be loaded and validated in the main thread when we + // load the cluster configuration. So we can assume the configuration is valid here. + ASSERT(typed_config != nullptr, + "Invalid load balancing policy configuration for least request load balancer"); + + return std::make_unique( + params.priority_set, params.local_priority_set, cluster_info.lbStats(), runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(cluster_info.lbConfig(), + healthy_panic_threshold, 100, 50), + *typed_config, time_source); +} + +/** + * Static registration for the Factory. @see RegisterFactory. + */ +REGISTER_FACTORY(Factory, Upstream::TypedLoadBalancerFactory); + +} // namespace RoundRobin +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/round_robin/config.h b/source/extensions/load_balancing_policies/round_robin/config.h new file mode 100644 index 0000000000000..b0138b86dc015 --- /dev/null +++ b/source/extensions/load_balancing_policies/round_robin/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.pb.h" +#include "envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.pb.validate.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/common/logger.h" +#include "source/extensions/load_balancing_policies/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RoundRobin { + +struct RoundRobinCreator : public Logger::Loggable { + Upstream::LoadBalancerPtr operator()(Upstream::LoadBalancerParams params, + const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, Random::RandomGenerator& random, + TimeSource& time_source); +}; + +class Factory : public Common::FactoryBase< + envoy::extensions::load_balancing_policies::round_robin::v3::RoundRobin, + RoundRobinCreator> { +public: + Factory() : FactoryBase("envoy.load_balancing_policies.round_robin") {} +}; + +} // namespace RoundRobin +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index ac565799e85e2..77ca220247b36 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -57,8 +57,11 @@ class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ZoneAwareLoadBalancerBase(priority_set, nullptr, lb_stats, runtime, random, common_config) { - } + : ZoneAwareLoadBalancerBase( + priority_set, nullptr, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} void runInvalidLocalitySourceType() { localitySourceType(static_cast(123)); } @@ -100,7 +103,9 @@ class TestLb : public LoadBalancerBase { TestLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : LoadBalancerBase(priority_set, lb_stats, runtime, random, common_config) {} + : LoadBalancerBase(priority_set, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50)) {} using LoadBalancerBase::chooseHostSet; using LoadBalancerBase::isInPanic; using LoadBalancerBase::percentageDegradedLoad; @@ -584,8 +589,11 @@ class TestZoneAwareLb : public ZoneAwareLoadBalancerBase { TestZoneAwareLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ZoneAwareLoadBalancerBase(priority_set, nullptr, lb_stats, runtime, random, common_config) { - } + : ZoneAwareLoadBalancerBase( + priority_set, nullptr, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { return choose_host_once_host_; diff --git a/test/extensions/load_balancing_policies/least_request/BUILD b/test/extensions/load_balancing_policies/least_request/BUILD new file mode 100644 index 0000000000000..74d75e12ea48d --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/BUILD @@ -0,0 +1,38 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.load_balancing_policies.least_request"], + deps = [ + "//source/extensions/load_balancing_policies/least_request:config", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:priority_set_mocks", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + srcs = ["integration_test.cc"], + extension_names = ["envoy.load_balancing_policies.least_request"], + deps = [ + "//source/common/protobuf", + "//source/extensions/load_balancing_policies/least_request:config", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/load_balancing_policies/least_request/config_test.cc b/test/extensions/load_balancing_policies/least_request/config_test.cc new file mode 100644 index 0000000000000..ef4fd506d0006 --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/config_test.cc @@ -0,0 +1,54 @@ +#include "envoy/config/core/v3/extension.pb.h" + +#include "source/extensions/load_balancing_policies/least_request/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace LeastRequest { +namespace { + +TEST(LeastRequestConfigTest, ValidateFail) { + NiceMock context; + NiceMock cluster_info; + NiceMock main_thread_priority_set; + NiceMock thread_local_priority_set; + + envoy::config::core::v3::TypedExtensionConfig config; + config.set_name("envoy.load_balancing_policies.least_request"); + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest config_msg; + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.least_request", factory.name()); + + auto messsage_ptr = factory.createEmptyConfigProto(); + EXPECT_CALL(cluster_info, loadBalancingPolicy()).WillOnce(testing::ReturnRef(messsage_ptr)); + + auto thread_aware_lb = + factory.create(cluster_info, main_thread_priority_set, context.runtime_loader_, + context.api_.random_, context.time_system_); + EXPECT_NE(nullptr, thread_aware_lb); + + thread_aware_lb->initialize(); + + auto thread_local_lb_factory = thread_aware_lb->factory(); + EXPECT_NE(nullptr, thread_local_lb_factory); + + auto thread_local_lb = thread_local_lb_factory->create({thread_local_priority_set, nullptr}); + EXPECT_NE(nullptr, thread_local_lb); + + EXPECT_DEATH(thread_local_lb_factory->create(), "not implemented"); +} + +} // namespace +} // namespace LeastRequest +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/least_request/integration_test.cc b/test/extensions/load_balancing_policies/least_request/integration_test.cc new file mode 100644 index 0000000000000..4feb0585c182b --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/integration_test.cc @@ -0,0 +1,105 @@ +#include +#include + +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" + +#include "source/common/common/base64.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/load_balancing_policies/least_request/config.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace LeastRequest { +namespace { + +class LeastRequestIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + LeastRequestIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + // Create 3 different upstream server for stateful session test. + setUpstreamCount(3); + + // Update endpoints of default cluster `cluster_0` to 3 different fake upstreams. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); + ASSERT(cluster_0->name() == "cluster_0"); + auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); + + const std::string endpoints_yaml = R"EOF( + lb_endpoints: + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + )EOF"; + + const std::string local_address = Network::Test::getLoopbackAddressString(GetParam()); + TestUtility::loadFromYaml( + fmt::format(endpoints_yaml, local_address, local_address, local_address), *endpoint); + + auto* policy = cluster_0->mutable_load_balancing_policy(); + + const std::string policy_yaml = R"EOF( + policies: + - typed_extension_config: + name: envoy.load_balancing_policies.least_request + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + )EOF"; + + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, LeastRequestIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(LeastRequestIntegrationTest, NormalLoadBalancing) { + initialize(); + + for (uint64_t i = 0; i < 8; i++) { + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "example.com"}}; + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + ASSERT(upstream_index.has_value()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + cleanupUpstreamAndDownstream(); + } +} + +} // namespace +} // namespace LeastRequest +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/random/BUILD b/test/extensions/load_balancing_policies/random/BUILD new file mode 100644 index 0000000000000..226c5fccb6b95 --- /dev/null +++ b/test/extensions/load_balancing_policies/random/BUILD @@ -0,0 +1,38 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.load_balancing_policies.random"], + deps = [ + "//source/extensions/load_balancing_policies/random:config", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:priority_set_mocks", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + srcs = ["integration_test.cc"], + extension_names = ["envoy.load_balancing_policies.random"], + deps = [ + "//source/common/protobuf", + "//source/extensions/load_balancing_policies/random:config", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/load_balancing_policies/random/config_test.cc b/test/extensions/load_balancing_policies/random/config_test.cc new file mode 100644 index 0000000000000..505855dde3d06 --- /dev/null +++ b/test/extensions/load_balancing_policies/random/config_test.cc @@ -0,0 +1,54 @@ +#include "envoy/config/core/v3/extension.pb.h" + +#include "source/extensions/load_balancing_policies/random/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Random { +namespace { + +TEST(RandomConfigTest, ValidateFail) { + NiceMock context; + NiceMock cluster_info; + NiceMock main_thread_priority_set; + NiceMock thread_local_priority_set; + + envoy::config::core::v3::TypedExtensionConfig config; + config.set_name("envoy.load_balancing_policies.random"); + envoy::extensions::load_balancing_policies::random::v3::Random config_msg; + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.random", factory.name()); + + auto messsage_ptr = factory.createEmptyConfigProto(); + EXPECT_CALL(cluster_info, loadBalancingPolicy()).WillOnce(testing::ReturnRef(messsage_ptr)); + + auto thread_aware_lb = + factory.create(cluster_info, main_thread_priority_set, context.runtime_loader_, + context.api_.random_, context.time_system_); + EXPECT_NE(nullptr, thread_aware_lb); + + thread_aware_lb->initialize(); + + auto thread_local_lb_factory = thread_aware_lb->factory(); + EXPECT_NE(nullptr, thread_local_lb_factory); + + auto thread_local_lb = thread_local_lb_factory->create({thread_local_priority_set, nullptr}); + EXPECT_NE(nullptr, thread_local_lb); + + EXPECT_DEATH(thread_local_lb_factory->create(), "not implemented"); +} + +} // namespace +} // namespace Random +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/random/integration_test.cc b/test/extensions/load_balancing_policies/random/integration_test.cc new file mode 100644 index 0000000000000..aae4a415c5460 --- /dev/null +++ b/test/extensions/load_balancing_policies/random/integration_test.cc @@ -0,0 +1,105 @@ +#include +#include + +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" + +#include "source/common/common/base64.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/load_balancing_policies/random/config.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Random { +namespace { + +class RandomIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + RandomIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + // Create 3 different upstream server for stateful session test. + setUpstreamCount(3); + + // Update endpoints of default cluster `cluster_0` to 3 different fake upstreams. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); + ASSERT(cluster_0->name() == "cluster_0"); + auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); + + const std::string endpoints_yaml = R"EOF( + lb_endpoints: + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + )EOF"; + + const std::string local_address = Network::Test::getLoopbackAddressString(GetParam()); + TestUtility::loadFromYaml( + fmt::format(endpoints_yaml, local_address, local_address, local_address), *endpoint); + + auto* policy = cluster_0->mutable_load_balancing_policy(); + + const std::string policy_yaml = R"EOF( + policies: + - typed_extension_config: + name: envoy.load_balancing_policies.random + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.random.v3.Random + )EOF"; + + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, RandomIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(RandomIntegrationTest, NormalLoadBalancing) { + initialize(); + + for (uint64_t i = 0; i < 8; i++) { + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "example.com"}}; + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + ASSERT(upstream_index.has_value()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + cleanupUpstreamAndDownstream(); + } +} + +} // namespace +} // namespace Random +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/round_robin/BUILD b/test/extensions/load_balancing_policies/round_robin/BUILD new file mode 100644 index 0000000000000..b52558c9667c5 --- /dev/null +++ b/test/extensions/load_balancing_policies/round_robin/BUILD @@ -0,0 +1,38 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.load_balancing_policies.round_robin"], + deps = [ + "//source/extensions/load_balancing_policies/round_robin:config", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:priority_set_mocks", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + srcs = ["integration_test.cc"], + extension_names = ["envoy.load_balancing_policies.round_robin"], + deps = [ + "//source/common/protobuf", + "//source/extensions/load_balancing_policies/round_robin:config", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/load_balancing_policies/round_robin/config_test.cc b/test/extensions/load_balancing_policies/round_robin/config_test.cc new file mode 100644 index 0000000000000..533218cb71df9 --- /dev/null +++ b/test/extensions/load_balancing_policies/round_robin/config_test.cc @@ -0,0 +1,54 @@ +#include "envoy/config/core/v3/extension.pb.h" + +#include "source/extensions/load_balancing_policies/round_robin/config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RoundRobin { +namespace { + +TEST(RoundRobinConfigTest, ValidateFail) { + NiceMock context; + NiceMock cluster_info; + NiceMock main_thread_priority_set; + NiceMock thread_local_priority_set; + + envoy::config::core::v3::TypedExtensionConfig config; + config.set_name("envoy.load_balancing_policies.round_robin"); + envoy::extensions::load_balancing_policies::round_robin::v3::RoundRobin config_msg; + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.round_robin", factory.name()); + + auto messsage_ptr = factory.createEmptyConfigProto(); + EXPECT_CALL(cluster_info, loadBalancingPolicy()).WillOnce(testing::ReturnRef(messsage_ptr)); + + auto thread_aware_lb = + factory.create(cluster_info, main_thread_priority_set, context.runtime_loader_, + context.api_.random_, context.time_system_); + EXPECT_NE(nullptr, thread_aware_lb); + + thread_aware_lb->initialize(); + + auto thread_local_lb_factory = thread_aware_lb->factory(); + EXPECT_NE(nullptr, thread_local_lb_factory); + + auto thread_local_lb = thread_local_lb_factory->create({thread_local_priority_set, nullptr}); + EXPECT_NE(nullptr, thread_local_lb); + + EXPECT_DEATH(thread_local_lb_factory->create(), "not implemented"); +} + +} // namespace +} // namespace RoundRobin +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/round_robin/integration_test.cc b/test/extensions/load_balancing_policies/round_robin/integration_test.cc new file mode 100644 index 0000000000000..ee9939fccded6 --- /dev/null +++ b/test/extensions/load_balancing_policies/round_robin/integration_test.cc @@ -0,0 +1,113 @@ +#include +#include + +#include "envoy/config/endpoint/v3/endpoint_components.pb.h" + +#include "source/common/common/base64.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/load_balancing_policies/round_robin/config.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RoundRobin { +namespace { + +class RoundRobinIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + RoundRobinIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + // Create 3 different upstream server for stateful session test. + setUpstreamCount(3); + + // Update endpoints of default cluster `cluster_0` to 3 different fake upstreams. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster_0 = bootstrap.mutable_static_resources()->mutable_clusters()->Mutable(0); + ASSERT(cluster_0->name() == "cluster_0"); + auto* endpoint = cluster_0->mutable_load_assignment()->mutable_endpoints()->Mutable(0); + + const std::string endpoints_yaml = R"EOF( + lb_endpoints: + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + - endpoint: + address: + socket_address: + address: {} + port_value: 0 + )EOF"; + + const std::string local_address = Network::Test::getLoopbackAddressString(GetParam()); + TestUtility::loadFromYaml( + fmt::format(endpoints_yaml, local_address, local_address, local_address), *endpoint); + + auto* policy = cluster_0->mutable_load_balancing_policy(); + + const std::string policy_yaml = R"EOF( + policies: + - typed_extension_config: + name: envoy.load_balancing_policies.round_robin + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin + )EOF"; + + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, RoundRobinIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(RoundRobinIntegrationTest, NormalLoadBalancing) { + initialize(); + + std::vector indexs; + + for (uint64_t i = 0; i < 8; i++) { + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/"}, {":scheme", "http"}, {":authority", "example.com"}}; + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + ASSERT(upstream_index.has_value()); + indexs.push_back(upstream_index.value()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + cleanupUpstreamAndDownstream(); + } + + for (uint64_t i = 2; i < 8; i++) { + EXPECT_NE(indexs[i], indexs[i - 1]); + EXPECT_NE(indexs[i - 1], indexs[i - 2]); + } +} + +} // namespace +} // namespace RoundRobin +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 5d121f170ae25..0a8f914de46ef 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -75,6 +75,8 @@ declare -a KNOWN_LOW_COVERAGE=( "source/server:93.3" # flaky: be careful adjusting. See https://github.com/envoyproxy/envoy/issues/15239 "source/server/admin:97.4" "source/server/admin:profiler-lib:83" +"source/extensions/load_balancing_policies:96" +"source/extensions/load_balancing_policies/common:94" # Death tests don't report LCOV "source/server/config_validation:80.8" "source/extensions/http/early_header_mutation:95.2" # Death tests don't report LCOV "source/extensions/http/early_header_mutation/header_mutation:95.2" # Death tests don't report LCOV diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index 3e079ba1058d0..3c7625be5530e 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -119,6 +119,7 @@ categories: - envoy.tls_handshakers - envoy.generic_proxy.filters - envoy.generic_proxy.codecs +- envoy.load_balancing_policies - envoy.http.early_header_mutation - envoy.http.custom_response