diff --git a/CODEOWNERS b/CODEOWNERS index cf716e0eb61d1..53b6ed9caf63f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -289,6 +289,8 @@ extensions/filters/http/oauth2 @derekargueta @snowp /*/extensions/load_balancing_policies/least_request @wbpcode @UNOWNED /*/extensions/load_balancing_policies/random @wbpcode @UNOWNED /*/extensions/load_balancing_policies/round_robin @wbpcode @UNOWNED +/*/extensions/load_balancing_policies/ring_hash @wbpcode @UNOWNED +/*/extensions/load_balancing_policies/maglev @wbpcode @UNOWNED # Early header mutation /*/extensions/http/early_header_mutation/header_mutation @wbpcode @UNOWNED diff --git a/api/envoy/extensions/load_balancing_policies/maglev/v3/maglev.proto b/api/envoy/extensions/load_balancing_policies/maglev/v3/maglev.proto index d34920a7b59e9..91457f5abfea6 100644 --- a/api/envoy/extensions/load_balancing_policies/maglev/v3/maglev.proto +++ b/api/envoy/extensions/load_balancing_policies/maglev/v3/maglev.proto @@ -16,7 +16,7 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Maglev Load Balancing Policy] -// [#not-implemented-hide:] +// [#extension: envoy.load_balancing_policies.maglev] // This configuration allows the built-in Maglev LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview @@ -30,4 +30,7 @@ message Maglev { // Common configuration for hashing-based load balancing policies. common.v3.ConsistentHashingLbConfig consistent_hashing_lb_config = 2; + + // Enable locality weighted load balancing for maglev lb explicitly. + common.v3.LocalityLbConfig.LocalityWeightedLbConfig locality_weighted_lb_config = 3; } diff --git a/api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto b/api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto index fa1da5fa60ece..ce29a0ac1789f 100644 --- a/api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto +++ b/api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto @@ -17,12 +17,12 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/loa option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Ring Hash Load Balancing Policy] -// [#not-implemented-hide:] +// [#extension: envoy.load_balancing_policies.ring_hash] // This configuration allows the built-in RING_HASH LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview // ` for more information. -// [#next-free-field: 7] +// [#next-free-field: 8] message RingHash { // The hash function used to hash hosts onto the ketama ring. enum HashFunction { @@ -90,4 +90,7 @@ message RingHash { // Common configuration for hashing-based load balancing policies. common.v3.ConsistentHashingLbConfig consistent_hashing_lb_config = 6; + + // Enable locality weighted load balancing for ring hash lb explicitly. + common.v3.LocalityLbConfig.LocalityWeightedLbConfig locality_weighted_lb_config = 7; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9c0cadffb0124..7d8e884934e0e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -17,5 +17,11 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: upstream + change: | + added :ref:`ring hash extension ` to suppport the :ref:`load balancer policy `. +- area: upstream + change: | + added :ref:`maglev extension ` to suppport the :ref:`load balancer policy `. deprecated: diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 1b2f76a6c9042..87ccc0beb4abd 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -409,6 +409,7 @@ envoy_cc_library( ":thread_aware_lb_lib", ":upstream_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/maglev/v3:pkg_cc_proto", ], ) @@ -423,6 +424,7 @@ envoy_cc_library( ":thread_aware_lb_lib", "//source/common/common:minimal_logger_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/load_balancing_policies/ring_hash/v3:pkg_cc_proto", ], ) diff --git a/source/common/upstream/load_balancer_factory_base.h b/source/common/upstream/load_balancer_factory_base.h index ce3fa56eabac7..6eaa658db5552 100644 --- a/source/common/upstream/load_balancer_factory_base.h +++ b/source/common/upstream/load_balancer_factory_base.h @@ -13,11 +13,13 @@ namespace Upstream { * TODO: provide a ThreadLocalLoadBalancer construct to abstract away thread-awareness from load * balancing extensions that don't require it. */ -class TypedLoadBalancerFactoryBase : public TypedLoadBalancerFactory { +template class TypedLoadBalancerFactoryBase : public TypedLoadBalancerFactory { public: // Upstream::TypedLoadBalancerFactory std::string name() const override { return name_; } + ProtobufTypes::MessagePtr createEmptyConfigProto() override { return std::make_unique(); } + protected: TypedLoadBalancerFactoryBase(const std::string& name) : name_(name) {} diff --git a/source/common/upstream/maglev_lb.cc b/source/common/upstream/maglev_lb.cc index 627b6e8925feb..98fb4718d8d12 100644 --- a/source/common/upstream/maglev_lb.cc +++ b/source/common/upstream/maglev_lb.cc @@ -97,7 +97,10 @@ MaglevLoadBalancer::MaglevLoadBalancer( Runtime::Loader& runtime, Random::RandomGenerator& random, OptRef config, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, common_config), + : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50), + common_config.has_locality_weighted_lb_config()), scope_(scope.createScope("maglev_lb.")), stats_(generateStats(*scope_)), table_size_(config ? PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.ref(), table_size, MaglevTable::DefaultTableSize) @@ -115,6 +118,28 @@ MaglevLoadBalancer::MaglevLoadBalancer( } } +MaglevLoadBalancer::MaglevLoadBalancer( + const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::maglev::v3::Maglev& config) + : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, healthy_panic_threshold, + config.has_locality_weighted_lb_config()), + scope_(scope.createScope("maglev_lb.")), stats_(generateStats(*scope_)), + table_size_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, table_size, MaglevTable::DefaultTableSize)), + use_hostname_for_hashing_( + config.has_consistent_hashing_lb_config() + ? config.consistent_hashing_lb_config().use_hostname_for_hashing() + : false), + hash_balance_factor_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.consistent_hashing_lb_config(), + hash_balance_factor, 0)) { + ENVOY_LOG(debug, "maglev table size: {}", table_size_); + // The table size must be prime number. + if (!Primes::isPrime(table_size_)) { + throw EnvoyException("The table size of maglev must be prime number"); + } +} + MaglevLoadBalancerStats MaglevLoadBalancer::generateStats(Stats::Scope& scope) { return {ALL_MAGLEV_LOAD_BALANCER_STATS(POOL_GAUGE(scope))}; } diff --git a/source/common/upstream/maglev_lb.h b/source/common/upstream/maglev_lb.h index ddd052e4eb99e..9b4a0b4196d39 100644 --- a/source/common/upstream/maglev_lb.h +++ b/source/common/upstream/maglev_lb.h @@ -2,6 +2,8 @@ #include "envoy/common/random_generator.h" #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.h" +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.validate.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -76,6 +78,11 @@ class MaglevLoadBalancer : public ThreadAwareLoadBalancerBase, OptRef config, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + MaglevLoadBalancer(const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, + uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::maglev::v3::Maglev& config); + const MaglevLoadBalancerStats& stats() const { return stats_; } uint64_t tableSize() const { return table_size_; } diff --git a/source/common/upstream/ring_hash_lb.cc b/source/common/upstream/ring_hash_lb.cc index fdcd861ed3869..8413d037fedc3 100644 --- a/source/common/upstream/ring_hash_lb.cc +++ b/source/common/upstream/ring_hash_lb.cc @@ -21,7 +21,10 @@ RingHashLoadBalancer::RingHashLoadBalancer( Runtime::Loader& runtime, Random::RandomGenerator& random, OptRef config, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, common_config), + : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50), + common_config.has_locality_weighted_lb_config()), scope_(scope.createScope("ring_hash_lb.")), stats_(generateStats(*scope_)), min_ring_size_(config.has_value() ? PROTOBUF_GET_WRAPPED_OR_DEFAULT( config.ref(), minimum_ring_size, DefaultMinRingSize) @@ -46,6 +49,34 @@ RingHashLoadBalancer::RingHashLoadBalancer( } } +RingHashLoadBalancer::RingHashLoadBalancer( + const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::ring_hash::v3::RingHash& config) + : ThreadAwareLoadBalancerBase(priority_set, stats, runtime, random, healthy_panic_threshold, + config.has_locality_weighted_lb_config()), + scope_(scope.createScope("ring_hash_lb.")), stats_(generateStats(*scope_)), + min_ring_size_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, minimum_ring_size, DefaultMinRingSize)), + max_ring_size_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, maximum_ring_size, DefaultMaxRingSize)), + hash_function_(static_cast(config.hash_function())), + use_hostname_for_hashing_( + config.has_consistent_hashing_lb_config() + ? config.consistent_hashing_lb_config().use_hostname_for_hashing() + : config.use_hostname_for_hashing()), + hash_balance_factor_(config.has_consistent_hashing_lb_config() + ? PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config.consistent_hashing_lb_config(), hash_balance_factor, 0) + : PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, hash_balance_factor, 0)) { + // It's important to do any config validation here, rather than deferring to Ring's ctor, + // because any exceptions thrown here will be caught and handled properly. + if (min_ring_size_ > max_ring_size_) { + throw EnvoyException(fmt::format("ring hash: minimum_ring_size ({}) > maximum_ring_size ({})", + min_ring_size_, max_ring_size_)); + } +} + RingHashLoadBalancerStats RingHashLoadBalancer::generateStats(Stats::Scope& scope) { return {ALL_RING_HASH_LOAD_BALANCER_STATS(POOL_GAUGE(scope))}; } diff --git a/source/common/upstream/ring_hash_lb.h b/source/common/upstream/ring_hash_lb.h index 52b0c2ad02f79..421753b52586e 100644 --- a/source/common/upstream/ring_hash_lb.h +++ b/source/common/upstream/ring_hash_lb.h @@ -3,6 +3,8 @@ #include #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.pb.h" +#include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.pb.validate.h" #include "envoy/runtime/runtime.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -45,6 +47,11 @@ class RingHashLoadBalancer : public ThreadAwareLoadBalancerBase, OptRef config, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + RingHashLoadBalancer( + const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, uint32_t healthy_panic_threshold, + const envoy::extensions::load_balancing_policies::ring_hash::v3::RingHash& config); + const RingHashLoadBalancerStats& stats() const { return stats_; } private: diff --git a/source/common/upstream/thread_aware_lb_impl.cc b/source/common/upstream/thread_aware_lb_impl.cc index a2d7e17c8cac0..c3f1c07fc9613 100644 --- a/source/common/upstream/thread_aware_lb_impl.cc +++ b/source/common/upstream/thread_aware_lb_impl.cc @@ -70,8 +70,10 @@ void normalizeLocalityWeights(const HostsPerLocality& hosts_per_locality, void normalizeWeights(const HostSet& host_set, bool in_panic, NormalizedHostWeightVector& normalized_host_weights, - double& min_normalized_weight, double& max_normalized_weight) { - if (host_set.localityWeights() == nullptr || host_set.localityWeights()->empty()) { + double& min_normalized_weight, double& max_normalized_weight, + bool locality_weighted_balancing) { + if (!locality_weighted_balancing || host_set.localityWeights() == nullptr || + host_set.localityWeights()->empty()) { // If we're not dealing with locality weights, just normalize weights for the flat set of hosts. const auto& hosts = in_panic ? host_set.hosts() : host_set.healthyHosts(); normalizeHostWeights(hosts, 1.0, normalized_host_weights, min_normalized_weight, @@ -121,7 +123,7 @@ void ThreadAwareLoadBalancerBase::refresh() { double min_normalized_weight = 1.0; double max_normalized_weight = 0.0; normalizeWeights(*host_set, per_priority_state->global_panic_, normalized_host_weights, - min_normalized_weight, max_normalized_weight); + min_normalized_weight, max_normalized_weight, locality_weighted_balancing_); per_priority_state->current_lb_ = createLoadBalancer( std::move(normalized_host_weights), min_normalized_weight, max_normalized_weight); } diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h index 12dd97139c814..8aca6b819d370 100644 --- a/source/common/upstream/thread_aware_lb_impl.h +++ b/source/common/upstream/thread_aware_lb_impl.h @@ -106,14 +106,12 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL } protected: - ThreadAwareLoadBalancerBase( - 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, - PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( - common_config, healthy_panic_threshold, 100, 50)), - factory_(new LoadBalancerFactoryImpl(stats, random)) {} + ThreadAwareLoadBalancerBase(const PrioritySet& priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + uint32_t healthy_panic_threshold, bool locality_weighted_balancing) + : LoadBalancerBase(priority_set, stats, runtime, random, healthy_panic_threshold), + factory_(new LoadBalancerFactoryImpl(stats, random)), + locality_weighted_balancing_(locality_weighted_balancing) {} private: struct PerPriorityState { @@ -171,6 +169,7 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL void refresh(); std::shared_ptr factory_; + const bool locality_weighted_balancing_{}; Common::CallbackHandlePtr priority_update_cb_; }; diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index eccab9f9b9715..4af0ef6aa3f8c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -398,7 +398,8 @@ EXTENSIONS = { "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", - + "envoy.load_balancing_policies.maglev": "//source/extensions/load_balancing_policies/maglev:config", + "envoy.load_balancing_policies.ring_hash": "//source/extensions/load_balancing_policies/ring_hash:config", # HTTP Early Header Mutation # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 31f424b44da77..9df9e313191d3 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1369,6 +1369,20 @@ envoy.load_balancing_policies.round_robin: status: stable type_urls: - envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin +envoy.load_balancing_policies.ring_hash: + categories: + - envoy.load_balancing_policies + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable + type_urls: + - envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash +envoy.load_balancing_policies.maglev: + categories: + - envoy.load_balancing_policies + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable + type_urls: + - envoy.extensions.load_balancing_policies.maglev.v3.Maglev envoy.http.early_header_mutation.header_mutation: categories: - envoy.http.early_header_mutation diff --git a/source/extensions/load_balancing_policies/maglev/BUILD b/source/extensions/load_balancing_policies/maglev/BUILD new file mode 100644 index 0000000000000..a3f88251c56c0 --- /dev/null +++ b/source/extensions/load_balancing_policies/maglev/BUILD @@ -0,0 +1,22 @@ +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_factory_base_lib", + "//source/common/upstream:load_balancer_lib", + "//source/common/upstream:maglev_lb_lib", + "@envoy_api//envoy/extensions/load_balancing_policies/maglev/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/load_balancing_policies/maglev/config.cc b/source/extensions/load_balancing_policies/maglev/config.cc new file mode 100644 index 0000000000000..494b5bd7b61d1 --- /dev/null +++ b/source/extensions/load_balancing_policies/maglev/config.cc @@ -0,0 +1,41 @@ +#include "source/extensions/load_balancing_policies/maglev/config.h" + +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.h" + +#include "source/common/upstream/maglev_lb.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Maglev { + +Upstream::ThreadAwareLoadBalancerPtr Factory::create(const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, + 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 maglev load balancer"); + + return std::make_unique( + priority_set, cluster_info.lbStats(), cluster_info.statsScope(), runtime, random, + static_cast(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 Maglev +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/maglev/config.h b/source/extensions/load_balancing_policies/maglev/config.h new file mode 100644 index 0000000000000..2c072367f31eb --- /dev/null +++ b/source/extensions/load_balancing_policies/maglev/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.h" +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.validate.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/upstream/load_balancer_factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Maglev { + +class Factory : public Upstream::TypedLoadBalancerFactoryBase< + envoy::extensions::load_balancing_policies::maglev::v3::Maglev> { +public: + Factory() : TypedLoadBalancerFactoryBase("envoy.load_balancing_policies.maglev") {} + + Upstream::ThreadAwareLoadBalancerPtr create(const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, + Random::RandomGenerator& random, + TimeSource& time_source) override; +}; + +} // namespace Maglev +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/ring_hash/BUILD b/source/extensions/load_balancing_policies/ring_hash/BUILD new file mode 100644 index 0000000000000..03bf3e916424d --- /dev/null +++ b/source/extensions/load_balancing_policies/ring_hash/BUILD @@ -0,0 +1,22 @@ +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_factory_base_lib", + "//source/common/upstream:load_balancer_lib", + "//source/common/upstream:ring_hash_lb_lib", + "@envoy_api//envoy/extensions/load_balancing_policies/ring_hash/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/load_balancing_policies/ring_hash/config.cc b/source/extensions/load_balancing_policies/ring_hash/config.cc new file mode 100644 index 0000000000000..ef6f5a4d45ef3 --- /dev/null +++ b/source/extensions/load_balancing_policies/ring_hash/config.cc @@ -0,0 +1,39 @@ +#include "source/extensions/load_balancing_policies/ring_hash/config.h" + +#include "source/common/upstream/ring_hash_lb.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RingHash { + +Upstream::ThreadAwareLoadBalancerPtr Factory::create(const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, + 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 ring hash load balancer"); + + return std::make_unique( + priority_set, cluster_info.lbStats(), cluster_info.statsScope(), 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 RingHash +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/load_balancing_policies/ring_hash/config.h b/source/extensions/load_balancing_policies/ring_hash/config.h new file mode 100644 index 0000000000000..4e4a8c0e61cd2 --- /dev/null +++ b/source/extensions/load_balancing_policies/ring_hash/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.pb.h" +#include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.pb.validate.h" +#include "envoy/upstream/load_balancer.h" + +#include "source/common/upstream/load_balancer_factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RingHash { + +class Factory : public Upstream::TypedLoadBalancerFactoryBase< + envoy::extensions::load_balancing_policies::ring_hash::v3::RingHash> { +public: + Factory() : TypedLoadBalancerFactoryBase("envoy.load_balancing_policies.ring_hash") {} + + Upstream::ThreadAwareLoadBalancerPtr create(const Upstream::ClusterInfo& cluster_info, + const Upstream::PrioritySet& priority_set, + Runtime::Loader& runtime, + Random::RandomGenerator& random, + TimeSource& time_source) override; +}; + +} // namespace RingHash +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/common/upstream/maglev_lb_test.cc b/test/common/upstream/maglev_lb_test.cc index dc0b675c3d708..2a98d9484a160 100644 --- a/test/common/upstream/maglev_lb_test.cc +++ b/test/common/upstream/maglev_lb_test.cc @@ -58,10 +58,14 @@ class MaglevLoadBalancerTest : public Event::TestUsingSimulatedTime, public test common_config_); } - void init(uint64_t table_size) { + void init(uint64_t table_size, bool locality_weighted_balancing = false) { config_ = envoy::config::cluster::v3::Cluster::MaglevLbConfig(); config_.value().mutable_table_size()->set_value(table_size); + if (locality_weighted_balancing) { + common_config_.mutable_locality_weighted_lb_config(); + } + createLb(); lb_->initialize(); } @@ -317,7 +321,7 @@ TEST_F(MaglevLoadBalancerTest, LocalityWeightedSameLocalityWeights) { LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1}}; host_set_.locality_weights_ = locality_weights; host_set_.runCallbacks({}, {}); - init(17); + init(17, true); EXPECT_EQ(8, lb_->stats().min_entries_per_host_.value()); EXPECT_EQ(9, lb_->stats().max_entries_per_host_.value()); @@ -361,7 +365,7 @@ TEST_F(MaglevLoadBalancerTest, LocalityWeightedDifferentLocalityWeights) { LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{8, 0, 2}}; host_set_.locality_weights_ = locality_weights; host_set_.runCallbacks({}, {}); - init(17); + init(17, true); EXPECT_EQ(4, lb_->stats().min_entries_per_host_.value()); EXPECT_EQ(13, lb_->stats().max_entries_per_host_.value()); @@ -401,7 +405,7 @@ TEST_F(MaglevLoadBalancerTest, LocalityWeightedAllZeroLocalityWeights) { LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{0}}; host_set_.locality_weights_ = locality_weights; host_set_.runCallbacks({}, {}); - init(17); + init(17, true); LoadBalancerPtr lb = lb_->factory()->create(); TestLoadBalancerContext context(0); EXPECT_EQ(nullptr, lb->chooseHost(&context)); @@ -419,7 +423,7 @@ TEST_F(MaglevLoadBalancerTest, LocalityWeightedGlobalPanic) { LocalityWeightsConstSharedPtr locality_weights{new LocalityWeights{1, 1}}; host_set_.locality_weights_ = locality_weights; host_set_.runCallbacks({}, {}); - init(17); + init(17, true); EXPECT_EQ(8, lb_->stats().min_entries_per_host_.value()); EXPECT_EQ(9, lb_->stats().max_entries_per_host_.value()); @@ -465,7 +469,7 @@ TEST_F(MaglevLoadBalancerTest, LocalityWeightedLopsided) { host_set_.healthy_hosts_per_locality_ = host_set_.hosts_per_locality_; host_set_.locality_weights_ = makeLocalityWeights({127, 1}); host_set_.runCallbacks({}, {}); - init(MaglevTable::DefaultTableSize); + init(MaglevTable::DefaultTableSize, true); EXPECT_EQ(1, lb_->stats().min_entries_per_host_.value()); EXPECT_EQ(MaglevTable::DefaultTableSize - 1023, lb_->stats().max_entries_per_host_.value()); diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc index f062bbe77ae72..6a9ae5f813638 100644 --- a/test/common/upstream/ring_hash_lb_test.cc +++ b/test/common/upstream/ring_hash_lb_test.cc @@ -61,7 +61,11 @@ class RingHashLoadBalancerTest : public Event::TestUsingSimulatedTime, RingHashLoadBalancerTest() : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, *stats_store_.rootScope()) {} - void init() { + void init(bool locality_weighted_balancing = false) { + if (locality_weighted_balancing) { + common_config_.mutable_locality_weighted_lb_config(); + } + lb_ = std::make_unique( priority_set_, stats_, *stats_store_.rootScope(), runtime_, random_, config_.has_value() @@ -639,7 +643,7 @@ TEST_P(RingHashLoadBalancerTest, ZeroLocalityWeights) { hostSet().locality_weights_ = makeLocalityWeights({0, 0}); hostSet().runCallbacks({}, {}); - init(); + init(true); EXPECT_EQ(nullptr, lb_->factory()->create()->chooseHost(nullptr)); } @@ -661,7 +665,7 @@ TEST_P(RingHashLoadBalancerTest, LocalityWeightedTinyRing) { config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); config_.value().mutable_minimum_ring_size()->set_value(6); config_.value().mutable_maximum_ring_size()->set_value(6); - init(); + init(true); EXPECT_EQ(6, lb_->stats().size_.value()); EXPECT_EQ(1, lb_->stats().min_hashes_per_host_.value()); EXPECT_EQ(3, lb_->stats().max_hashes_per_host_.value()); @@ -694,7 +698,7 @@ TEST_P(RingHashLoadBalancerTest, LocalityWeightedLargeRing) { config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); config_.value().mutable_minimum_ring_size()->set_value(6144); - init(); + init(true); EXPECT_EQ(6144, lb_->stats().size_.value()); EXPECT_EQ(1024, lb_->stats().min_hashes_per_host_.value()); EXPECT_EQ(3072, lb_->stats().max_hashes_per_host_.value()); @@ -733,7 +737,7 @@ TEST_P(RingHashLoadBalancerTest, HostAndLocalityWeightedTinyRing) { config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); config_.value().mutable_minimum_ring_size()->set_value(9); config_.value().mutable_maximum_ring_size()->set_value(9); - init(); + init(true); EXPECT_EQ(9, lb_->stats().size_.value()); EXPECT_EQ(1, lb_->stats().min_hashes_per_host_.value()); EXPECT_EQ(4, lb_->stats().max_hashes_per_host_.value()); @@ -769,7 +773,7 @@ TEST_P(RingHashLoadBalancerTest, HostAndLocalityWeightedLargeRing) { config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); config_.value().mutable_minimum_ring_size()->set_value(9216); - init(); + init(true); EXPECT_EQ(9216, lb_->stats().size_.value()); EXPECT_EQ(1024, lb_->stats().min_hashes_per_host_.value()); EXPECT_EQ(4096, lb_->stats().max_hashes_per_host_.value()); @@ -879,7 +883,7 @@ TEST_P(RingHashLoadBalancerTest, LopsidedWeightSmallScale) { config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); config_.value().mutable_minimum_ring_size()->set_value(1024); config_.value().mutable_maximum_ring_size()->set_value(1024); - init(); + init(true); EXPECT_EQ(1024, lb_->stats().size_.value()); EXPECT_EQ(0, lb_->stats().min_hashes_per_host_.value()); // Host :0, from the heavy-but-sparse locality, should have 1016 out of the 1024 entries on the diff --git a/test/extensions/load_balancing_policies/maglev/BUILD b/test/extensions/load_balancing_policies/maglev/BUILD new file mode 100644 index 0000000000000..c2e88e03252e3 --- /dev/null +++ b/test/extensions/load_balancing_policies/maglev/BUILD @@ -0,0 +1,39 @@ +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.maglev"], + deps = [ + "//source/extensions/load_balancing_policies/maglev: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_api//envoy/extensions/load_balancing_policies/maglev/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + srcs = ["integration_test.cc"], + extension_names = ["envoy.load_balancing_policies.maglev"], + deps = [ + "//source/common/protobuf", + "//source/extensions/load_balancing_policies/maglev: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/maglev/config_test.cc b/test/extensions/load_balancing_policies/maglev/config_test.cc new file mode 100644 index 0000000000000..733ed14a3b4b2 --- /dev/null +++ b/test/extensions/load_balancing_policies/maglev/config_test.cc @@ -0,0 +1,80 @@ +#include "envoy/config/core/v3/extension.pb.h" +#include "envoy/extensions/load_balancing_policies/maglev/v3/maglev.pb.h" + +#include "source/extensions/load_balancing_policies/maglev/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 Maglev { +namespace { + +TEST(MaglevConfigTest, Validate) { + 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.maglev"); + envoy::extensions::load_balancing_policies::maglev::v3::Maglev config_msg; + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.maglev", 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_NE(nullptr, thread_local_lb_factory->create()); + } + + { + envoy::config::core::v3::TypedExtensionConfig config; + config.set_name("envoy.load_balancing_policies.maglev"); + envoy::extensions::load_balancing_policies::maglev::v3::Maglev config_msg; + config_msg.mutable_table_size()->set_value(4); + + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.maglev", factory.name()); + + auto messsage_ptr = factory.createEmptyConfigProto(); + messsage_ptr->MergeFrom(config_msg); + + EXPECT_CALL(cluster_info, loadBalancingPolicy()).WillOnce(testing::ReturnRef(messsage_ptr)); + + EXPECT_THROW_WITH_MESSAGE(factory.create(cluster_info, main_thread_priority_set, + context.runtime_loader_, context.api_.random_, + context.time_system_), + EnvoyException, "The table size of maglev must be prime number"); + } +} + +} // namespace +} // namespace Maglev +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/maglev/integration_test.cc b/test/extensions/load_balancing_policies/maglev/integration_test.cc new file mode 100644 index 0000000000000..1ed99b69eefd8 --- /dev/null +++ b/test/extensions/load_balancing_policies/maglev/integration_test.cc @@ -0,0 +1,127 @@ +#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/maglev/config.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace Maglev { +namespace { + +class MaglevIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + MaglevIntegrationTest() : 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.maglev + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.maglev.v3.Maglev + )EOF"; + + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->add_hash_policy() + ->mutable_header() + ->set_header_name("x-hash"); + }); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, MaglevIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(MaglevIntegrationTest, NormalLoadBalancing) { + initialize(); + + absl::optional unique_upstream_index; + + 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"}, + {"x-hash", "hash"}, + }; + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + ASSERT(upstream_index.has_value()); + + if (unique_upstream_index.has_value()) { + EXPECT_EQ(unique_upstream_index.value(), upstream_index.value()); + } else { + unique_upstream_index = upstream_index.value(); + } + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + cleanupUpstreamAndDownstream(); + } +} + +} // namespace +} // namespace Maglev +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/ring_hash/BUILD b/test/extensions/load_balancing_policies/ring_hash/BUILD new file mode 100644 index 0000000000000..b3a195482f82c --- /dev/null +++ b/test/extensions/load_balancing_policies/ring_hash/BUILD @@ -0,0 +1,39 @@ +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.ring_hash"], + deps = [ + "//source/extensions/load_balancing_policies/ring_hash: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_api//envoy/extensions/load_balancing_policies/ring_hash/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "integration_test", + srcs = ["integration_test.cc"], + extension_names = ["envoy.load_balancing_policies.ring_hash"], + deps = [ + "//source/common/protobuf", + "//source/extensions/load_balancing_policies/ring_hash: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/ring_hash/config_test.cc b/test/extensions/load_balancing_policies/ring_hash/config_test.cc new file mode 100644 index 0000000000000..7f2034f76fd33 --- /dev/null +++ b/test/extensions/load_balancing_policies/ring_hash/config_test.cc @@ -0,0 +1,80 @@ +#include "envoy/config/core/v3/extension.pb.h" +#include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.pb.h" + +#include "source/extensions/load_balancing_policies/ring_hash/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 RingHash { +namespace { + +TEST(RingHashConfigTest, Validate) { + 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.ring_hash"); + envoy::extensions::load_balancing_policies::ring_hash::v3::RingHash config_msg; + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.ring_hash", 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_NE(nullptr, thread_local_lb_factory->create()); + } + + { + envoy::config::core::v3::TypedExtensionConfig config; + config.set_name("envoy.load_balancing_policies.ring_hash"); + envoy::extensions::load_balancing_policies::ring_hash::v3::RingHash config_msg; + config_msg.mutable_minimum_ring_size()->set_value(4); + config_msg.mutable_maximum_ring_size()->set_value(2); + + config.mutable_typed_config()->PackFrom(config_msg); + + auto& factory = Config::Utility::getAndCheckFactory(config); + EXPECT_EQ("envoy.load_balancing_policies.ring_hash", factory.name()); + + auto messsage_ptr = factory.createEmptyConfigProto(); + messsage_ptr->MergeFrom(config_msg); + + EXPECT_CALL(cluster_info, loadBalancingPolicy()).WillOnce(testing::ReturnRef(messsage_ptr)); + + EXPECT_THROW_WITH_MESSAGE( + factory.create(cluster_info, main_thread_priority_set, context.runtime_loader_, + context.api_.random_, context.time_system_), + EnvoyException, "ring hash: minimum_ring_size (4) > maximum_ring_size (2)"); + } +} + +} // namespace +} // namespace RingHash +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/ring_hash/integration_test.cc b/test/extensions/load_balancing_policies/ring_hash/integration_test.cc new file mode 100644 index 0000000000000..f42a875f53f2a --- /dev/null +++ b/test/extensions/load_balancing_policies/ring_hash/integration_test.cc @@ -0,0 +1,127 @@ +#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/ring_hash/config.h" + +#include "test/integration/http_integration.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace LoadBalancingPolices { +namespace RingHash { +namespace { + +class RingHashIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + RingHashIntegrationTest() : 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.ring_hash + typed_config: + "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash + )EOF"; + + TestUtility::loadFromYaml(policy_yaml, *policy); + }); + + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->add_hash_policy() + ->mutable_header() + ->set_header_name("x-hash"); + }); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, RingHashIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(RingHashIntegrationTest, NormalLoadBalancing) { + initialize(); + + absl::optional unique_upstream_index; + + 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"}, + {"x-hash", "hash"}, + }; + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2}); + ASSERT(upstream_index.has_value()); + + if (unique_upstream_index.has_value()) { + EXPECT_EQ(unique_upstream_index.value(), upstream_index.value()); + } else { + unique_upstream_index = upstream_index.value(); + } + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + cleanupUpstreamAndDownstream(); + } +} + +} // namespace +} // namespace RingHash +} // namespace LoadBalancingPolices +} // namespace Extensions +} // namespace Envoy diff --git a/test/integration/load_balancers/custom_lb_policy.h b/test/integration/load_balancers/custom_lb_policy.h index e166472248bd3..a6d4ed239174e 100644 --- a/test/integration/load_balancers/custom_lb_policy.h +++ b/test/integration/load_balancers/custom_lb_policy.h @@ -55,14 +55,11 @@ class ThreadAwareLbImpl : public Upstream::ThreadAwareLoadBalancer { const Upstream::HostSharedPtr host_; }; -class CustomLbFactory : public Upstream::TypedLoadBalancerFactoryBase { +class CustomLbFactory : public Upstream::TypedLoadBalancerFactoryBase< + ::test::integration::custom_lb::CustomLbConfig> { public: CustomLbFactory() : TypedLoadBalancerFactoryBase("envoy.load_balancers.custom_lb") {} - Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return Envoy::ProtobufTypes::MessagePtr{new ::test::integration::custom_lb::CustomLbConfig()}; - } - Upstream::ThreadAwareLoadBalancerPtr create(const Upstream::ClusterInfo&, const Upstream::PrioritySet&, Runtime::Loader&, Random::RandomGenerator&, TimeSource&) override {