From e0d5e40cef92d301a9eb36d08182c7994fb2e3cb Mon Sep 17 00:00:00 2001 From: Basundhara Chakrabarty Date: Wed, 19 Aug 2026 06:01:43 +0000 Subject: [PATCH 1/5] dynamic_modules: allow native LB in dynamic_modules clusters Signed-off-by: Basundhara Chakrabarty --- .../clusters/dynamic_modules/cluster.cc | 28 +++-- .../dynamic_modules/sdk/rust/src/cluster.rs | 24 +++- .../dynamic_modules/sdk/rust/src/lib.rs | 4 +- .../dynamic_modules/sdk/rust/src/lib_test.rs | 2 +- .../extensions/clusters/dynamic_modules/BUILD | 3 + .../clusters/dynamic_modules/cluster_test.cc | 117 +++++++++++++++++- .../dynamic_modules/integration_test.cc | 59 +++++++++ .../rust/cluster_dynamic_metadata_test.rs | 6 +- .../rust/cluster_filter_state_test.rs | 12 +- .../rust/cluster_integration_test.rs | 101 +++++++++++---- 10 files changed, 306 insertions(+), 50 deletions(-) diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 768c5b87848b1..825f83d3443a0 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -903,12 +903,22 @@ DynamicModuleClusterFactory::createClusterWithConfig( const envoy::extensions::clusters::dynamic_modules::v3::ClusterConfig& proto_config, Upstream::ClusterFactoryContext& context) { - // Validate that CLUSTER_PROVIDED LB policy is used. - if (cluster.lb_policy() != envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED) { + // Validate that the LB policy is one supported by dynamic_modules clusters. + // CLUSTER_PROVIDED (module LB) is always supported. + // LEAST_REQUEST, ROUND_ROBIN, RANDOM (native factory LB) are supported. + // Thread-aware policies (ring hash, maglev) are not supported. + const auto policy = cluster.lb_policy(); + const bool is_module_lb = (policy == envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); + const bool is_native_lb = (policy == envoy::config::cluster::v3::Cluster::LEAST_REQUEST || + policy == envoy::config::cluster::v3::Cluster::ROUND_ROBIN || + policy == envoy::config::cluster::v3::Cluster::RANDOM); + + if (!is_module_lb && !is_native_lb) { return absl::InvalidArgumentError( fmt::format("cluster: LB policy {} is not valid for cluster type " - "'envoy.clusters.dynamic_modules'. Only 'CLUSTER_PROVIDED' is allowed.", - envoy::config::cluster::v3::Cluster::LbPolicy_Name(cluster.lb_policy()))); + "'envoy.clusters.dynamic_modules'. Supported policies are " + "CLUSTER_PROVIDED, LEAST_REQUEST, ROUND_ROBIN, and RANDOM.", + envoy::config::cluster::v3::Cluster::LbPolicy_Name(policy))); } Server::Configuration::ServerFactoryContext& server_context = context.serverFactoryContext(); @@ -952,9 +962,13 @@ DynamicModuleClusterFactory::createClusterWithConfig( cluster, std::move(config_or_error.value()), context, creation_status)); RETURN_IF_NOT_OK(creation_status); - // Create the thread-aware load balancer. - auto handle = std::make_shared(new_cluster); - auto lb = std::make_unique(handle); + // Create the thread-aware load balancer only if the module provides LB (CLUSTER_PROVIDED). + // For native LB policies, return nullptr so the cluster manager builds the native factory LB. + Upstream::ThreadAwareLoadBalancerPtr lb; + if (cluster.lb_policy() == envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED) { + auto handle = std::make_shared(new_cluster); + lb = std::make_unique(handle); + } return std::make_pair(std::move(new_cluster), std::move(lb)); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs index c6235a55ae1ae..093edb0372ebf 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs @@ -48,7 +48,13 @@ pub trait Cluster: Send + Sync { /// /// Each worker thread gets its own load balancer instance. The `envoy_lb` /// provides thread-local access to the cluster's host set. - fn new_load_balancer(&self, envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box; + /// + /// Return `Some(lb)` if this cluster implements host selection, or `None` if the cluster + /// only provides host discovery and Envoy should use its native load balancer. + /// When returning `None`, Envoy will use the standard load balancer factory based on + /// `lb_policy` + `common_lb_config` (e.g., zone-aware or locality-weighted routing). + /// The module's `choose_host` hook will never be called if this returns `None`. + fn new_load_balancer(&self, envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option>; /// Called on the main thread when a new event is scheduled via /// [`EnvoyClusterScheduler::commit`] for this [`Cluster`]. @@ -2560,6 +2566,9 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_destroy( /// Wrapper that pairs a module-side load balancer with the Envoy-side LB pointer. /// The `lb_envoy_ptr` is needed by [`ClusterLbContextRef::should_select_another_host`] to /// resolve host pointers from the priority set. +/// +/// If the module returns None from `new_load_balancer()`, this wrapper is not created and +/// a null pointer is returned, signaling to Envoy to use the native factory load balancer. struct ClusterLbWrapper { lb: Box, lb_envoy_ptr: abi::envoy_dynamic_module_type_cluster_lb_envoy_ptr, @@ -2578,9 +2587,16 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_cluster_lb_new( let cluster = cluster_module_ptr as *const *const dyn Cluster; let cluster = &**cluster; let envoy_lb = EnvoyClusterLoadBalancerImpl::new(lb_envoy_ptr); - let lb = cluster.new_load_balancer(&envoy_lb); - let wrapper = Box::new(ClusterLbWrapper { lb, lb_envoy_ptr }); - Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_cluster_lb_module_ptr + match cluster.new_load_balancer(&envoy_lb) { + Some(lb) => { + let wrapper = Box::new(ClusterLbWrapper { lb, lb_envoy_ptr }); + Box::into_raw(wrapper) as abi::envoy_dynamic_module_type_cluster_lb_module_ptr + }, + None => { + // Module does not provide a load balancer; return null so Envoy uses native LB. + std::ptr::null() + }, + } })) .unwrap_or_else(|panic| { crate::log_ffi_panic("envoy_dynamic_module_on_cluster_lb_new", panic); diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 19a0e7b42bc47..39feb2f6a1823 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -1420,8 +1420,8 @@ pub static NEW_CLUSTER_CONFIG_FUNCTION: OnceLock = Onc /// envoy_cluster.pre_init_complete(); /// } /// -/// fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { -/// Box::new(MyClusterLb {}) +/// fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { +/// Some(Box::new(MyClusterLb {})) /// } /// } /// diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 1d70a4bfe7153..5d880f1bf9a9c 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -6973,7 +6973,7 @@ fn test_cluster_callout_done_with_null_buffers_yields_none() { struct TestCluster; impl Cluster for TestCluster { fn on_init(&mut self, _envoy_cluster: &dyn EnvoyCluster) {} - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { unimplemented!("not exercised by this test") } diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index 9f454206966ab..65c84b11e3ebc 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -27,6 +27,9 @@ envoy_cc_test( "//source/extensions/clusters/dynamic_modules:cluster_lib", "//source/extensions/dynamic_modules:abi_impl", "//source/extensions/load_balancing_policies/cluster_provided:config", + "//source/extensions/load_balancing_policies/least_request:config", + "//source/extensions/load_balancing_policies/random:config", + "//source/extensions/load_balancing_policies/round_robin:config", "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", "//test/extensions/dynamic_modules:util", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index fd179273a48ed..54e40f72a026c 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -207,8 +207,52 @@ TEST_F(DynamicModuleClusterTest, CreationWithClusterConfig) { EXPECT_NE(nullptr, result->second); } -// Test that a non-CLUSTER_PROVIDED lb_policy is rejected. -TEST_F(DynamicModuleClusterTest, InvalidLbPolicy) { +// Test that CLUSTER_PROVIDED lb_policy returns a non-null thread-aware LB. +TEST_F(DynamicModuleClusterTest, ClusterProvidedLbPolicy) { + const std::string yaml = R"EOF( +name: test_cluster +connect_timeout: 0.25s +lb_policy: CLUSTER_PROVIDED +cluster_type: + name: envoy.clusters.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_modules.v3.ClusterConfig + dynamic_module_config: + name: cluster_no_op + cluster_name: test +)EOF"; + + auto result = createCluster(yaml); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // CLUSTER_PROVIDED should return a non-null thread-aware LB (module LB). + EXPECT_NE(nullptr, result->second); +} + +// Test that LEAST_REQUEST lb_policy is accepted and returns nullptr thread-aware LB. +TEST_F(DynamicModuleClusterTest, LeastRequestLbPolicy) { + const std::string yaml = R"EOF( +name: test_cluster +connect_timeout: 0.25s +lb_policy: LEAST_REQUEST +cluster_type: + name: envoy.clusters.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_modules.v3.ClusterConfig + dynamic_module_config: + name: cluster_no_op + cluster_name: test +)EOF"; + + auto result = createCluster(yaml); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // Native LB policies should return nullptr thread-aware LB. + EXPECT_EQ(nullptr, result->second); +} + +// Test that ROUND_ROBIN lb_policy is accepted and returns nullptr thread-aware LB. +TEST_F(DynamicModuleClusterTest, RoundRobinLbPolicy) { const std::string yaml = R"EOF( name: test_cluster connect_timeout: 0.25s @@ -223,7 +267,74 @@ lb_policy: ROUND_ROBIN )EOF"; auto result = createCluster(yaml); - ASSERT_THAT(result, HasStatusMessage(testing::HasSubstr("CLUSTER_PROVIDED"))); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // Native LB policies should return nullptr thread-aware LB. + EXPECT_EQ(nullptr, result->second); +} + +// Test that RANDOM lb_policy is accepted and returns nullptr thread-aware LB. +TEST_F(DynamicModuleClusterTest, RandomLbPolicy) { + const std::string yaml = R"EOF( +name: test_cluster +connect_timeout: 0.25s +lb_policy: RANDOM +cluster_type: + name: envoy.clusters.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_modules.v3.ClusterConfig + dynamic_module_config: + name: cluster_no_op + cluster_name: test +)EOF"; + + auto result = createCluster(yaml); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // Native LB policies should return nullptr thread-aware LB. + EXPECT_EQ(nullptr, result->second); +} + +// Test that thread-aware lb_policy like RING_HASH is rejected. +TEST_F(DynamicModuleClusterTest, RingHashLbPolicyRejected) { + const std::string yaml = R"EOF( +name: test_cluster +connect_timeout: 0.25s +lb_policy: RING_HASH +cluster_type: + name: envoy.clusters.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_modules.v3.ClusterConfig + dynamic_module_config: + name: cluster_no_op + cluster_name: test +)EOF"; + + auto result = createCluster(yaml); + ASSERT_FALSE(result.ok()); + EXPECT_THAT(result.status().message(), + testing::HasSubstr("not valid for cluster type 'envoy.clusters.dynamic_modules'")); +} + +// Test that thread-aware lb_policy like MAGLEV is rejected. +TEST_F(DynamicModuleClusterTest, MaglevLbPolicyRejected) { + const std::string yaml = R"EOF( +name: test_cluster +connect_timeout: 0.25s +lb_policy: MAGLEV +cluster_type: + name: envoy.clusters.dynamic_modules + typed_config: + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_modules.v3.ClusterConfig + dynamic_module_config: + name: cluster_no_op + cluster_name: test +)EOF"; + + auto result = createCluster(yaml); + ASSERT_FALSE(result.ok()); + EXPECT_THAT(result.status().message(), + testing::HasSubstr("not valid for cluster type 'envoy.clusters.dynamic_modules'")); } // Test that a missing module fails gracefully. diff --git a/test/extensions/clusters/dynamic_modules/integration_test.cc b/test/extensions/clusters/dynamic_modules/integration_test.cc index e3692ef021c9f..61566ab4737db 100644 --- a/test/extensions/clusters/dynamic_modules/integration_test.cc +++ b/test/extensions/clusters/dynamic_modules/integration_test.cc @@ -510,6 +510,65 @@ TEST_P(DynamicModuleClusterDynamicMetadataIntegrationTest, SetsDynamicMetadataDu EXPECT_EQ("1234 test_value", log); } +// ============================================================================= +// Native LB test: cluster with LEAST_REQUEST policy (native LB, not module LB). +// ============================================================================= + +class DynamicModuleClusterNativeLbIntegrationTest + : public testing::TestWithParam, + public HttpIntegrationTest { +public: + DynamicModuleClusterNativeLbIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} + + void initializeWithNativeLb() { + TestEnvironment::setEnvVar( + "ENVOY_DYNAMIC_MODULES_SEARCH_PATH", + TestEnvironment::runfilesPath("test/extensions/dynamic_modules/test_data/rust"), 1); + + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + const std::string upstream_address = fake_upstreams_[0]->localAddress()->asString(); + + cluster->set_name("cluster_0"); + // Use LEAST_REQUEST policy instead of CLUSTER_PROVIDED to test native LB. + cluster->set_lb_policy(envoy::config::cluster::v3::Cluster::LEAST_REQUEST); + cluster->clear_load_assignment(); + + envoy::extensions::clusters::dynamic_modules::v3::ClusterConfig dec_config; + dec_config.mutable_dynamic_module_config()->set_name("cluster_integration_test"); + dec_config.set_cluster_name("native_lb_test"); + + Protobuf::StringValue config_proto; + config_proto.set_value(upstream_address); + std::ignore = dec_config.mutable_cluster_config()->PackFrom(config_proto); + + cluster->mutable_cluster_type()->set_name("envoy.clusters.dynamic_modules"); + std::ignore = cluster->mutable_cluster_type()->mutable_typed_config()->PackFrom(dec_config); + }); + + HttpIntegrationTest::initialize(); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, DynamicModuleClusterNativeLbIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Verifies that a dynamic_modules cluster with native LB policy (LEAST_REQUEST) works correctly. +// The module's new_load_balancer returns None, so Envoy uses its factory LB instead of calling +// the module's choose_host hook. This tests that native LB policies are properly supported. +TEST_P(DynamicModuleClusterNativeLbIntegrationTest, NativeLbWithLeastRequestPolicy) { + initializeWithNativeLb(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + + auto response = + sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + } // namespace DynamicModules } // namespace Clusters } // namespace Extensions diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs index 381991dc3f3c9..8c92afcd413ad 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs @@ -69,10 +69,10 @@ impl Cluster for DynamicMetadataWriterCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(DynamicMetadataWriterLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(DynamicMetadataWriterLb { hosts: self.hosts.clone(), - }) + })) } } diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs index 1fb007a254660..2b188f8641308 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs @@ -128,10 +128,10 @@ impl Cluster for FilterStateReaderCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(FilterStateReaderLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(FilterStateReaderLb { hosts: self.hosts.clone(), - }) + })) } } @@ -200,10 +200,10 @@ impl Cluster for FilterStateWriterCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(FilterStateWriterLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(FilterStateWriterLb { hosts: self.hosts.clone(), - }) + })) } } diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs index ec3b87e7a6497..a181be0bfb236 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs @@ -89,6 +89,10 @@ fn new_cluster_config( metrics: envoy_cluster_metrics, })) }, + "native_lb_test" => Some(Box::new(NativeLbTestClusterConfig { + upstream_address: config_str.to_string(), + metrics: envoy_cluster_metrics, + })), _ => None, } } @@ -140,13 +144,13 @@ impl Cluster for SyncHostSelectionCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(SyncHostSelectionLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(SyncHostSelectionLb { hosts: self.hosts.clone(), index: AtomicUsize::new(0), counter_id: self.counter_id, metrics: self.metrics.clone(), - }) + })) } } @@ -207,10 +211,10 @@ impl Cluster for AsyncHostSelectionCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(AsyncHostSelectionLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(AsyncHostSelectionLb { hosts: self.hosts.clone(), - }) + })) } } @@ -297,11 +301,11 @@ impl Cluster for SchedulerHostUpdateCluster { scheduler.commit(ADD_HOST_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(SchedulerHostUpdateLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(SchedulerHostUpdateLb { hosts: self.hosts.clone(), membership_update_count: AtomicUsize::new(0), - }) + })) } fn on_scheduled(&self, envoy_cluster: &dyn EnvoyCluster, event_id: u64) { @@ -376,10 +380,10 @@ impl Cluster for LifecycleCallbacksCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(LifecycleCallbacksLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(LifecycleCallbacksLb { hosts: self.hosts.clone(), - }) + })) } fn on_server_initialized(&mut self, _envoy_cluster: &dyn EnvoyCluster) { @@ -472,10 +476,10 @@ impl Cluster for RunOnAllWorkersCluster { scheduler.commit(RUN_ON_ALL_WORKERS_TRIGGER_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(RunOnAllWorkersLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(RunOnAllWorkersLb { hosts: self.hosts.clone(), - }) + })) } fn on_scheduled(&self, envoy_cluster: &dyn EnvoyCluster, event_id: u64) { @@ -557,13 +561,13 @@ impl Cluster for WorkerLocalRebuildCluster { scheduler.commit(WORKER_LOCAL_REBUILD_ADD_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(WorkerLocalRebuildLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(WorkerLocalRebuildLb { hosts: Vec::new(), index: 0, counter_id: self.counter_id, metrics: self.metrics.clone(), - }) + })) } fn on_scheduled(&self, envoy_cluster: &dyn EnvoyCluster, event_id: u64) { @@ -671,13 +675,13 @@ impl Cluster for MemberUpdatePackedAddressCluster { scheduler.commit(PACKED_ADDRESS_ADD_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(MemberUpdatePackedAddressLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(MemberUpdatePackedAddressLb { hosts: Vec::new(), index: 0, counter_id: self.counter_id, metrics: self.metrics.clone(), - }) + })) } fn on_scheduled(&self, envoy_cluster: &dyn EnvoyCluster, event_id: u64) { @@ -806,14 +810,14 @@ impl Cluster for WorkerTimerCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Box { - Box::new(WorkerTimerLb { + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + Some(Box::new(WorkerTimerLb { hosts: self.hosts.clone(), timer: None, armed_id: self.armed_id, fired_id: self.fired_id, metrics: self.metrics.clone(), - }) + })) } } @@ -868,3 +872,52 @@ impl ClusterLb for WorkerTimerLb { timer.enable(std::time::Duration::from_millis(WORKER_TIMER_INTERVAL_MS)); } } + +// ============================================================================= +// Native LB test: module that only provides host discovery, not load balancing. +// ============================================================================= +// +// This module's new_load_balancer returns None, signaling to Envoy to use its native +// load balancer. This is used to test that native LB policies (LEAST_REQUEST, ROUND_ROBIN, +// RANDOM) work correctly with dynamic_modules clusters. + +struct NativeLbTestClusterConfig { + upstream_address: String, + metrics: Arc, +} + +impl ClusterConfig for NativeLbTestClusterConfig { + fn new_cluster(&self, _envoy_cluster: &dyn EnvoyCluster) -> Box { + let counter_id = self.metrics.define_counter("native_lb_requests").ok(); + Box::new(NativeLbTestCluster { + upstream_address: self.upstream_address.clone(), + hosts: Arc::new(Mutex::new(HostList(Vec::new()))), + counter_id, + metrics: self.metrics.clone(), + }) + } +} + +struct NativeLbTestCluster { + upstream_address: String, + hosts: SharedHostList, + counter_id: Option, + metrics: Arc, +} + +impl Cluster for NativeLbTestCluster { + fn on_init(&mut self, envoy_cluster: &dyn EnvoyCluster) { + let addresses = vec![self.upstream_address.clone()]; + let weights = vec![1u32]; + if let Some(host_ptrs) = envoy_cluster.add_hosts(&addresses, &weights) { + self.hosts.lock().unwrap().0 = host_ptrs; + } + envoy_cluster.pre_init_complete(); + } + + // Return None to signal that this module does not provide a load balancer. + // Envoy will use the native factory load balancer based on lb_policy. + fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + None + } +} From 52d4d08e443aef4f35ecf70201645e0014886d5a Mon Sep 17 00:00:00 2001 From: Basundhara Chakrabarty Date: Thu, 20 Aug 2026 22:32:40 +0000 Subject: [PATCH 2/5] dynamic_modules: verify native lb_policy bypasses module host selection Signed-off-by: Basundhara Chakrabarty --- .../dynamic_modules/integration_test.cc | 11 +++-- .../rust/cluster_integration_test.rs | 42 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/test/extensions/clusters/dynamic_modules/integration_test.cc b/test/extensions/clusters/dynamic_modules/integration_test.cc index 61566ab4737db..143ec9a9da2d2 100644 --- a/test/extensions/clusters/dynamic_modules/integration_test.cc +++ b/test/extensions/clusters/dynamic_modules/integration_test.cc @@ -554,9 +554,9 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, DynamicModuleClusterNativeLbIntegrationTest testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -// Verifies that a dynamic_modules cluster with native LB policy (LEAST_REQUEST) works correctly. -// The module's new_load_balancer returns None, so Envoy uses its factory LB instead of calling -// the module's choose_host hook. This tests that native LB policies are properly supported. +// A native lb_policy makes Envoy build its factory load balancer instead of the module's. The +// request routes to the upstream, and the module's choose_host must not run: the module provides +// a load balancer, but native_lb_requests stays 0 because Envoy never calls into it. TEST_P(DynamicModuleClusterNativeLbIntegrationTest, NativeLbWithLeastRequestPolicy) { initializeWithNativeLb(); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); @@ -567,6 +567,11 @@ TEST_P(DynamicModuleClusterNativeLbIntegrationTest, NativeLbWithLeastRequestPoli EXPECT_TRUE(upstream_request_->complete()); EXPECT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().getStatusValue()); + + // Native LB selected the host; the module's choose_host was never called, so its + // native_lb_requests counter is unincremented (absent or 0). + auto choose_host = test_server_->counter("dynamicmodulescustom.native_lb_requests"); + EXPECT_TRUE(choose_host == nullptr || choose_host->value() == 0); } } // namespace DynamicModules diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs index a181be0bfb236..417c59bb1e3c2 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs @@ -874,12 +874,12 @@ impl ClusterLb for WorkerTimerLb { } // ============================================================================= -// Native LB test: module that only provides host discovery, not load balancing. +// Native LB test: module provides a load balancer that must be bypassed. // ============================================================================= // -// This module's new_load_balancer returns None, signaling to Envoy to use its native -// load balancer. This is used to test that native LB policies (LEAST_REQUEST, ROUND_ROBIN, -// RANDOM) work correctly with dynamic_modules clusters. +// choose_host increments native_lb_requests. Tests configure a native lb_policy +// (LEAST_REQUEST, ROUND_ROBIN, RANDOM) so Envoy uses its factory LB; the module's +// choose_host must never run, so native_lb_requests stays 0. struct NativeLbTestClusterConfig { upstream_address: String, @@ -915,9 +915,37 @@ impl Cluster for NativeLbTestCluster { envoy_cluster.pre_init_complete(); } - // Return None to signal that this module does not provide a load balancer. - // Envoy will use the native factory load balancer based on lb_policy. fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { - None + Some(Box::new(NativeLbTestLb { + hosts: self.hosts.clone(), + index: AtomicUsize::new(0), + counter_id: self.counter_id, + metrics: self.metrics.clone(), + })) + } +} + +struct NativeLbTestLb { + hosts: SharedHostList, + index: AtomicUsize, + counter_id: Option, + metrics: Arc, +} + +impl ClusterLb for NativeLbTestLb { + fn choose_host( + &mut self, + _context: Option<&dyn ClusterLbContext>, + _async_completion: Box, + ) -> HostSelectionResult { + let hosts = self.hosts.lock().unwrap(); + if hosts.0.is_empty() { + return HostSelectionResult::NoHost; + } + let idx = self.index.fetch_add(1, Ordering::Relaxed) % hosts.0.len(); + if let Some(counter_id) = self.counter_id { + let _ = self.metrics.increment_counter(counter_id, 1); + } + HostSelectionResult::Selected(hosts.0[idx]) } } From e228dbe4d3dda917e5f3e79d3450efe2b8a47e4b Mon Sep 17 00:00:00 2001 From: Basundhara Chakrabarty Date: Fri, 21 Aug 2026 23:09:12 +0000 Subject: [PATCH 3/5] dynamic_modules: add changelog for native cluster load balancing Signed-off-by: Basundhara Chakrabarty --- .../dynamic_modules__native-load-balancing-clusters.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst diff --git a/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst new file mode 100644 index 0000000000000..386b65748e963 --- /dev/null +++ b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst @@ -0,0 +1,6 @@ +Dynamic module clusters (``envoy.clusters.dynamic_modules``) can now use +Envoy's built-in load balancers. In addition to ``CLUSTER_PROVIDED``, +``lb_policy`` may be set to ``LEAST_REQUEST``, ``ROUND_ROBIN``, or ``RANDOM``; +the module then supplies only host discovery and Envoy performs host selection +(including zone-aware and locality-weighted routing). Thread-aware policies +(ring hash, maglev) are not supported. From 30102eaaee0b9b0e5fa9c06a6369a3e6730882ae Mon Sep 17 00:00:00 2001 From: Basundhara Chakrabarty Date: Fri, 21 Aug 2026 23:17:28 +0000 Subject: [PATCH 4/5] dynamic_modules: correct changelog to not overclaim locality routing Signed-off-by: Basundhara Chakrabarty --- .../dynamic_modules__native-load-balancing-clusters.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst index 386b65748e963..91466f39a24ea 100644 --- a/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst +++ b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst @@ -1,6 +1,5 @@ Dynamic module clusters (``envoy.clusters.dynamic_modules``) can now use Envoy's built-in load balancers. In addition to ``CLUSTER_PROVIDED``, ``lb_policy`` may be set to ``LEAST_REQUEST``, ``ROUND_ROBIN``, or ``RANDOM``; -the module then supplies only host discovery and Envoy performs host selection -(including zone-aware and locality-weighted routing). Thread-aware policies -(ring hash, maglev) are not supported. +the module then supplies only host discovery and Envoy performs host selection. +Thread-aware policies (ring hash, maglev) are not supported. From ffab64749b6b9a8f7c47e4d4c70c66eedd50896d Mon Sep 17 00:00:00 2001 From: Basundhara Chakrabarty Date: Sat, 22 Aug 2026 00:17:15 +0000 Subject: [PATCH 5/5] dynamic_modules: support RING_HASH and MAGLEV cluster load balancing Signed-off-by: Basundhara Chakrabarty --- ...odules__native-load-balancing-clusters.rst | 6 +-- .../clusters/dynamic_modules/cluster.cc | 14 +++--- .../dynamic_modules/sdk/rust/src/cluster.rs | 5 ++- .../dynamic_modules/sdk/rust/src/lib_test.rs | 5 ++- .../extensions/clusters/dynamic_modules/BUILD | 2 + .../clusters/dynamic_modules/cluster_test.cc | 22 ++++----- .../dynamic_modules/integration_test.cc | 3 +- .../rust/cluster_dynamic_metadata_test.rs | 5 ++- .../rust/cluster_filter_state_test.rs | 10 ++++- .../rust/cluster_integration_test.rs | 45 +++++++++++++++---- 10 files changed, 82 insertions(+), 35 deletions(-) diff --git a/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst index 91466f39a24ea..5c34aa6def9e3 100644 --- a/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst +++ b/changelogs/current/new_features/dynamic_modules__native-load-balancing-clusters.rst @@ -1,5 +1,5 @@ Dynamic module clusters (``envoy.clusters.dynamic_modules``) can now use Envoy's built-in load balancers. In addition to ``CLUSTER_PROVIDED``, -``lb_policy`` may be set to ``LEAST_REQUEST``, ``ROUND_ROBIN``, or ``RANDOM``; -the module then supplies only host discovery and Envoy performs host selection. -Thread-aware policies (ring hash, maglev) are not supported. +``lb_policy`` may be set to ``LEAST_REQUEST``, ``ROUND_ROBIN``, ``RANDOM``, +``RING_HASH``, or ``MAGLEV``; the module then supplies only host discovery and +Envoy performs host selection. diff --git a/source/extensions/clusters/dynamic_modules/cluster.cc b/source/extensions/clusters/dynamic_modules/cluster.cc index 825f83d3443a0..a0aeb1ac0f899 100644 --- a/source/extensions/clusters/dynamic_modules/cluster.cc +++ b/source/extensions/clusters/dynamic_modules/cluster.cc @@ -903,21 +903,21 @@ DynamicModuleClusterFactory::createClusterWithConfig( const envoy::extensions::clusters::dynamic_modules::v3::ClusterConfig& proto_config, Upstream::ClusterFactoryContext& context) { - // Validate that the LB policy is one supported by dynamic_modules clusters. - // CLUSTER_PROVIDED (module LB) is always supported. - // LEAST_REQUEST, ROUND_ROBIN, RANDOM (native factory LB) are supported. - // Thread-aware policies (ring hash, maglev) are not supported. + // CLUSTER_PROVIDED uses the module's load balancer; the native policies use Envoy's factory + // load balancer, with the module supplying only host discovery. const auto policy = cluster.lb_policy(); const bool is_module_lb = (policy == envoy::config::cluster::v3::Cluster::CLUSTER_PROVIDED); const bool is_native_lb = (policy == envoy::config::cluster::v3::Cluster::LEAST_REQUEST || policy == envoy::config::cluster::v3::Cluster::ROUND_ROBIN || - policy == envoy::config::cluster::v3::Cluster::RANDOM); + policy == envoy::config::cluster::v3::Cluster::RANDOM || + policy == envoy::config::cluster::v3::Cluster::RING_HASH || + policy == envoy::config::cluster::v3::Cluster::MAGLEV); if (!is_module_lb && !is_native_lb) { return absl::InvalidArgumentError( fmt::format("cluster: LB policy {} is not valid for cluster type " - "'envoy.clusters.dynamic_modules'. Supported policies are " - "CLUSTER_PROVIDED, LEAST_REQUEST, ROUND_ROBIN, and RANDOM.", + "'envoy.clusters.dynamic_modules'. Supported policies are CLUSTER_PROVIDED, " + "LEAST_REQUEST, ROUND_ROBIN, RANDOM, RING_HASH, and MAGLEV.", envoy::config::cluster::v3::Cluster::LbPolicy_Name(policy))); } diff --git a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs index 093edb0372ebf..21a4f30488615 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/cluster.rs @@ -54,7 +54,10 @@ pub trait Cluster: Send + Sync { /// When returning `None`, Envoy will use the standard load balancer factory based on /// `lb_policy` + `common_lb_config` (e.g., zone-aware or locality-weighted routing). /// The module's `choose_host` hook will never be called if this returns `None`. - fn new_load_balancer(&self, envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option>; + fn new_load_balancer( + &self, + envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option>; /// Called on the main thread when a new event is scheduled via /// [`EnvoyClusterScheduler::commit`] for this [`Cluster`]. diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs index 5d880f1bf9a9c..81faea08aa041 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs @@ -6973,7 +6973,10 @@ fn test_cluster_callout_done_with_null_buffers_yields_none() { struct TestCluster; impl Cluster for TestCluster { fn on_init(&mut self, _envoy_cluster: &dyn EnvoyCluster) {} - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { unimplemented!("not exercised by this test") } diff --git a/test/extensions/clusters/dynamic_modules/BUILD b/test/extensions/clusters/dynamic_modules/BUILD index 65c84b11e3ebc..a11cd83590a14 100644 --- a/test/extensions/clusters/dynamic_modules/BUILD +++ b/test/extensions/clusters/dynamic_modules/BUILD @@ -28,7 +28,9 @@ envoy_cc_test( "//source/extensions/dynamic_modules:abi_impl", "//source/extensions/load_balancing_policies/cluster_provided:config", "//source/extensions/load_balancing_policies/least_request:config", + "//source/extensions/load_balancing_policies/maglev:config", "//source/extensions/load_balancing_policies/random:config", + "//source/extensions/load_balancing_policies/ring_hash:config", "//source/extensions/load_balancing_policies/round_robin:config", "//source/extensions/transport_sockets/raw_buffer:config", "//test/common/upstream:utility_lib", diff --git a/test/extensions/clusters/dynamic_modules/cluster_test.cc b/test/extensions/clusters/dynamic_modules/cluster_test.cc index 54e40f72a026c..0ee6914c2b65d 100644 --- a/test/extensions/clusters/dynamic_modules/cluster_test.cc +++ b/test/extensions/clusters/dynamic_modules/cluster_test.cc @@ -295,8 +295,8 @@ lb_policy: RANDOM EXPECT_EQ(nullptr, result->second); } -// Test that thread-aware lb_policy like RING_HASH is rejected. -TEST_F(DynamicModuleClusterTest, RingHashLbPolicyRejected) { +// Test that RING_HASH lb_policy is accepted and returns nullptr thread-aware LB. +TEST_F(DynamicModuleClusterTest, RingHashLbPolicy) { const std::string yaml = R"EOF( name: test_cluster connect_timeout: 0.25s @@ -311,13 +311,14 @@ lb_policy: RING_HASH )EOF"; auto result = createCluster(yaml); - ASSERT_FALSE(result.ok()); - EXPECT_THAT(result.status().message(), - testing::HasSubstr("not valid for cluster type 'envoy.clusters.dynamic_modules'")); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // Native LB policies should return nullptr thread-aware LB. + EXPECT_EQ(nullptr, result->second); } -// Test that thread-aware lb_policy like MAGLEV is rejected. -TEST_F(DynamicModuleClusterTest, MaglevLbPolicyRejected) { +// Test that MAGLEV lb_policy is accepted and returns nullptr thread-aware LB. +TEST_F(DynamicModuleClusterTest, MaglevLbPolicy) { const std::string yaml = R"EOF( name: test_cluster connect_timeout: 0.25s @@ -332,9 +333,10 @@ lb_policy: MAGLEV )EOF"; auto result = createCluster(yaml); - ASSERT_FALSE(result.ok()); - EXPECT_THAT(result.status().message(), - testing::HasSubstr("not valid for cluster type 'envoy.clusters.dynamic_modules'")); + ASSERT_TRUE(result.ok()) << result.status().message(); + EXPECT_NE(nullptr, result->first); + // Native LB policies should return nullptr thread-aware LB. + EXPECT_EQ(nullptr, result->second); } // Test that a missing module fails gracefully. diff --git a/test/extensions/clusters/dynamic_modules/integration_test.cc b/test/extensions/clusters/dynamic_modules/integration_test.cc index 143ec9a9da2d2..bfe80e4162907 100644 --- a/test/extensions/clusters/dynamic_modules/integration_test.cc +++ b/test/extensions/clusters/dynamic_modules/integration_test.cc @@ -518,7 +518,8 @@ class DynamicModuleClusterNativeLbIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { public: - DynamicModuleClusterNativeLbIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} + DynamicModuleClusterNativeLbIntegrationTest() + : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} void initializeWithNativeLb() { TestEnvironment::setEnvVar( diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs index 8c92afcd413ad..04253b519b8e8 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_dynamic_metadata_test.rs @@ -69,7 +69,10 @@ impl Cluster for DynamicMetadataWriterCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(DynamicMetadataWriterLb { hosts: self.hosts.clone(), })) diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs index 2b188f8641308..7ba5daa7633c7 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_filter_state_test.rs @@ -128,7 +128,10 @@ impl Cluster for FilterStateReaderCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(FilterStateReaderLb { hosts: self.hosts.clone(), })) @@ -200,7 +203,10 @@ impl Cluster for FilterStateWriterCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(FilterStateWriterLb { hosts: self.hosts.clone(), })) diff --git a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs index 417c59bb1e3c2..479dc20ada9da 100644 --- a/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/cluster_integration_test.rs @@ -144,7 +144,10 @@ impl Cluster for SyncHostSelectionCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(SyncHostSelectionLb { hosts: self.hosts.clone(), index: AtomicUsize::new(0), @@ -211,7 +214,10 @@ impl Cluster for AsyncHostSelectionCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(AsyncHostSelectionLb { hosts: self.hosts.clone(), })) @@ -301,7 +307,10 @@ impl Cluster for SchedulerHostUpdateCluster { scheduler.commit(ADD_HOST_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(SchedulerHostUpdateLb { hosts: self.hosts.clone(), membership_update_count: AtomicUsize::new(0), @@ -380,7 +389,10 @@ impl Cluster for LifecycleCallbacksCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(LifecycleCallbacksLb { hosts: self.hosts.clone(), })) @@ -476,7 +488,10 @@ impl Cluster for RunOnAllWorkersCluster { scheduler.commit(RUN_ON_ALL_WORKERS_TRIGGER_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(RunOnAllWorkersLb { hosts: self.hosts.clone(), })) @@ -561,7 +576,10 @@ impl Cluster for WorkerLocalRebuildCluster { scheduler.commit(WORKER_LOCAL_REBUILD_ADD_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(WorkerLocalRebuildLb { hosts: Vec::new(), index: 0, @@ -675,7 +693,10 @@ impl Cluster for MemberUpdatePackedAddressCluster { scheduler.commit(PACKED_ADDRESS_ADD_EVENT_ID); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(MemberUpdatePackedAddressLb { hosts: Vec::new(), index: 0, @@ -810,7 +831,10 @@ impl Cluster for WorkerTimerCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(WorkerTimerLb { hosts: self.hosts.clone(), timer: None, @@ -915,7 +939,10 @@ impl Cluster for NativeLbTestCluster { envoy_cluster.pre_init_complete(); } - fn new_load_balancer(&self, _envoy_lb: &dyn EnvoyClusterLoadBalancer) -> Option> { + fn new_load_balancer( + &self, + _envoy_lb: &dyn EnvoyClusterLoadBalancer, + ) -> Option> { Some(Box::new(NativeLbTestLb { hosts: self.hosts.clone(), index: AtomicUsize::new(0),