Skip to content

Commit 105b4ac

Browse files
authored
dynamic_modules: add cluster lifecycle events to bootstrap (#43950)
## Description This PR added server lifecycle callbacks including `on_cluster_server_initialized`, `on_cluster_drain_started`, and `on_cluster_shutdown` to the dynamic module cluster extension, enabling modules to respond to server initialization, drain, and shutdown events. --- **Commit Message:** dynamic_modules: added server lifecycle callbacks for dynamic module clusters **Additional Description:** Added server lifecycle callbacks to the dynamic module cluster extension. **Risk Level:** Low **Testing:** Added Tests **Docs Changes:** Added **Release Notes:** N/A Signed-off-by: Rohit Agrawal <rohit.agrawal@databricks.com>
1 parent 986cb16 commit 105b4ac

32 files changed

+1201
-0
lines changed

source/extensions/bootstrap/dynamic_modules/abi_impl.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,4 +575,12 @@ bool envoy_dynamic_module_callback_bootstrap_extension_remove_admin_handler(
575575
return admin->removeHandler(prefix_str);
576576
}
577577

578+
// -------------------- Cluster Lifecycle Callbacks --------------------
579+
580+
bool envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle(
581+
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr) {
582+
auto* config = static_cast<DynamicModuleBootstrapExtensionConfig*>(extension_config_envoy_ptr);
583+
return config->enableClusterLifecycle();
584+
}
585+
578586
} // extern "C"

source/extensions/bootstrap/dynamic_modules/extension_config.cc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,36 @@ void DynamicModuleBootstrapExtensionConfig::signalInitComplete() {
4848
"traffic");
4949
}
5050

51+
bool DynamicModuleBootstrapExtensionConfig::enableClusterLifecycle() {
52+
if (cluster_lifecycle_enabled_) {
53+
return false;
54+
}
55+
cluster_lifecycle_enabled_ = true;
56+
cluster_update_callbacks_handle_ =
57+
context_.clusterManager().addThreadLocalClusterUpdateCallbacks(*this);
58+
// Register a shutdown callback to release the handle before the underlying TLS data is
59+
// destroyed. The TLS shutdown happens in terminate() after ShutdownExit callbacks fire.
60+
cluster_lifecycle_shutdown_handle_ = context_.lifecycleNotifier().registerCallback(
61+
Server::ServerLifecycleNotifier::Stage::ShutdownExit,
62+
[this]() { cluster_update_callbacks_handle_.reset(); });
63+
return true;
64+
}
65+
66+
void DynamicModuleBootstrapExtensionConfig::onClusterAddOrUpdate(
67+
absl::string_view cluster_name, Upstream::ThreadLocalClusterCommand&) {
68+
if (in_module_config_ != nullptr && on_bootstrap_extension_cluster_add_or_update_ != nullptr) {
69+
on_bootstrap_extension_cluster_add_or_update_(thisAsVoidPtr(), in_module_config_,
70+
{cluster_name.data(), cluster_name.size()});
71+
}
72+
}
73+
74+
void DynamicModuleBootstrapExtensionConfig::onClusterRemoval(const std::string& cluster_name) {
75+
if (in_module_config_ != nullptr && on_bootstrap_extension_cluster_removal_ != nullptr) {
76+
on_bootstrap_extension_cluster_removal_(thisAsVoidPtr(), in_module_config_,
77+
{cluster_name.data(), cluster_name.size()});
78+
}
79+
}
80+
5181
void DynamicModuleBootstrapExtensionConfig::onScheduled(uint64_t event_id) {
5282
if (in_module_config_ != nullptr && on_bootstrap_extension_config_scheduled_ != nullptr) {
5383
on_bootstrap_extension_config_scheduled_(thisAsVoidPtr(), in_module_config_, event_id);
@@ -247,6 +277,20 @@ newDynamicModuleBootstrapExtensionConfig(
247277
return on_admin_request.status();
248278
}
249279

280+
auto on_cluster_add_or_update =
281+
dynamic_module->getFunctionPointer<OnBootstrapExtensionClusterAddOrUpdateType>(
282+
"envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update");
283+
if (!on_cluster_add_or_update.ok()) {
284+
return on_cluster_add_or_update.status();
285+
}
286+
287+
auto on_cluster_removal =
288+
dynamic_module->getFunctionPointer<OnBootstrapExtensionClusterRemovalType>(
289+
"envoy_dynamic_module_on_bootstrap_extension_cluster_removal");
290+
if (!on_cluster_removal.ok()) {
291+
return on_cluster_removal.status();
292+
}
293+
250294
auto config = std::make_shared<DynamicModuleBootstrapExtensionConfig>(
251295
extension_name, extension_config, metrics_namespace, std::move(dynamic_module),
252296
main_thread_dispatcher, context, stats_store);
@@ -276,6 +320,8 @@ newDynamicModuleBootstrapExtensionConfig(
276320
config->on_bootstrap_extension_http_callout_done_ = on_http_callout_done.value();
277321
config->on_bootstrap_extension_timer_fired_ = on_timer_fired.value();
278322
config->on_bootstrap_extension_admin_request_ = on_admin_request.value();
323+
config->on_bootstrap_extension_cluster_add_or_update_ = on_cluster_add_or_update.value();
324+
config->on_bootstrap_extension_cluster_removal_ = on_cluster_removal.value();
279325

280326
return config;
281327
}

source/extensions/bootstrap/dynamic_modules/extension_config.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "envoy/stats/scope.h"
1010
#include "envoy/stats/stats.h"
1111
#include "envoy/stats/store.h"
12+
#include "envoy/upstream/cluster_manager.h"
1213

1314
#include "source/common/common/logger.h"
1415
#include "source/common/http/message_impl.h"
@@ -49,6 +50,10 @@ using OnBootstrapExtensionTimerFiredType =
4950
decltype(&envoy_dynamic_module_on_bootstrap_extension_timer_fired);
5051
using OnBootstrapExtensionAdminRequestType =
5152
decltype(&envoy_dynamic_module_on_bootstrap_extension_admin_request);
53+
using OnBootstrapExtensionClusterAddOrUpdateType =
54+
decltype(&envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update);
55+
using OnBootstrapExtensionClusterRemovalType =
56+
decltype(&envoy_dynamic_module_on_bootstrap_extension_cluster_removal);
5257

5358
class DynamicModuleBootstrapExtension;
5459

@@ -58,6 +63,7 @@ class DynamicModuleBootstrapExtension;
5863
*/
5964
class DynamicModuleBootstrapExtensionConfig
6065
: public std::enable_shared_from_this<DynamicModuleBootstrapExtensionConfig>,
66+
public Upstream::ClusterUpdateCallbacks,
6167
public Logger::Loggable<Logger::Id::dynamic_modules> {
6268
public:
6369
/**
@@ -112,6 +118,23 @@ class DynamicModuleBootstrapExtensionConfig
112118
*/
113119
void signalInitComplete();
114120

121+
/**
122+
* Enables cluster lifecycle event notifications. When enabled, the module will receive
123+
* on_bootstrap_extension_cluster_add_or_update and on_bootstrap_extension_cluster_removal
124+
* callbacks when clusters are added, updated, or removed.
125+
*
126+
* This must be called on the main thread after the server is initialized, since the
127+
* ClusterManager is not available during bootstrap extension creation.
128+
*
129+
* @return true if the callbacks were successfully registered, false if already registered.
130+
*/
131+
bool enableClusterLifecycle();
132+
133+
// Upstream::ClusterUpdateCallbacks
134+
void onClusterAddOrUpdate(absl::string_view cluster_name,
135+
Upstream::ThreadLocalClusterCommand& get_cluster) override;
136+
void onClusterRemoval(const std::string& cluster_name) override;
137+
115138
// The corresponding in-module configuration.
116139
envoy_dynamic_module_type_bootstrap_extension_config_module_ptr in_module_config_ = nullptr;
117140

@@ -130,6 +153,9 @@ class DynamicModuleBootstrapExtensionConfig
130153
OnBootstrapExtensionHttpCalloutDoneType on_bootstrap_extension_http_callout_done_ = nullptr;
131154
OnBootstrapExtensionTimerFiredType on_bootstrap_extension_timer_fired_ = nullptr;
132155
OnBootstrapExtensionAdminRequestType on_bootstrap_extension_admin_request_ = nullptr;
156+
OnBootstrapExtensionClusterAddOrUpdateType on_bootstrap_extension_cluster_add_or_update_ =
157+
nullptr;
158+
OnBootstrapExtensionClusterRemovalType on_bootstrap_extension_cluster_removal_ = nullptr;
133159

134160
// The dynamic module.
135161
Extensions::DynamicModules::DynamicModulePtr dynamic_module_;
@@ -374,6 +400,14 @@ class DynamicModuleBootstrapExtensionConfig
374400
std::vector<ModuleGaugeVecHandle> gauge_vecs_;
375401
std::vector<ModuleHistogramHandle> histograms_;
376402
std::vector<ModuleHistogramVecHandle> histogram_vecs_;
403+
404+
// Cluster lifecycle callback handle. Set when the module enables cluster lifecycle events
405+
// via enableClusterLifecycle(). Reset during shutdown to avoid use-after-free since the
406+
// underlying TLS data is destroyed before the config.
407+
Upstream::ClusterUpdateCallbacksHandlePtr cluster_update_callbacks_handle_;
408+
// Handle for the shutdown lifecycle callback that cleans up cluster_update_callbacks_handle_.
409+
Server::ServerLifecycleNotifier::HandlePtr cluster_lifecycle_shutdown_handle_;
410+
bool cluster_lifecycle_enabled_ = false;
377411
};
378412

379413
using DynamicModuleBootstrapExtensionConfigSharedPtr =

source/extensions/dynamic_modules/abi/abi.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7326,6 +7326,44 @@ void envoy_dynamic_module_on_bootstrap_extension_timer_fired(
73267326
envoy_dynamic_module_type_bootstrap_extension_config_module_ptr extension_config_module_ptr,
73277327
envoy_dynamic_module_type_bootstrap_extension_timer_module_ptr timer_ptr);
73287328

7329+
/**
7330+
* envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update is called when a cluster is
7331+
* added to or updated in the ClusterManager.
7332+
*
7333+
* This is only called if the module has opted in to receiving cluster lifecycle events via
7334+
* envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle. The callback is
7335+
* registered on the main thread and invoked on the main thread.
7336+
*
7337+
* @param extension_config_envoy_ptr is the pointer to the DynamicModuleBootstrapExtensionConfig
7338+
* object.
7339+
* @param extension_config_module_ptr is the pointer to the in-module bootstrap extension
7340+
* configuration created by envoy_dynamic_module_on_bootstrap_extension_config_new.
7341+
* @param cluster_name is the name of the cluster that was added or updated.
7342+
*/
7343+
void envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update(
7344+
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr,
7345+
envoy_dynamic_module_type_bootstrap_extension_config_module_ptr extension_config_module_ptr,
7346+
envoy_dynamic_module_type_envoy_buffer cluster_name);
7347+
7348+
/**
7349+
* envoy_dynamic_module_on_bootstrap_extension_cluster_removal is called when a cluster is
7350+
* removed from the ClusterManager.
7351+
*
7352+
* This is only called if the module has opted in to receiving cluster lifecycle events via
7353+
* envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle. The callback is
7354+
* registered on the main thread and invoked on the main thread.
7355+
*
7356+
* @param extension_config_envoy_ptr is the pointer to the DynamicModuleBootstrapExtensionConfig
7357+
* object.
7358+
* @param extension_config_module_ptr is the pointer to the in-module bootstrap extension
7359+
* configuration created by envoy_dynamic_module_on_bootstrap_extension_config_new.
7360+
* @param cluster_name is the name of the cluster that was removed.
7361+
*/
7362+
void envoy_dynamic_module_on_bootstrap_extension_cluster_removal(
7363+
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr,
7364+
envoy_dynamic_module_type_bootstrap_extension_config_module_ptr extension_config_module_ptr,
7365+
envoy_dynamic_module_type_envoy_buffer cluster_name);
7366+
73297367
// =============================================================================
73307368
// Bootstrap Extension Callbacks
73317369
// =============================================================================
@@ -7893,6 +7931,27 @@ bool envoy_dynamic_module_callback_bootstrap_extension_remove_admin_handler(
78937931
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr,
78947932
envoy_dynamic_module_type_module_buffer path_prefix);
78957933

7934+
// -------------------- Bootstrap Extension Callbacks - Cluster Lifecycle --------------------
7935+
7936+
/**
7937+
* envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle is called by the
7938+
* module to opt in to receiving cluster lifecycle events
7939+
* (envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update and
7940+
* envoy_dynamic_module_on_bootstrap_extension_cluster_removal).
7941+
*
7942+
* This must be called on the main thread, typically during or after
7943+
* envoy_dynamic_module_on_bootstrap_extension_server_initialized, since the ClusterManager is
7944+
* not available until that point.
7945+
*
7946+
* This should be called at most once. Subsequent calls are no-ops and return false.
7947+
*
7948+
* @param extension_config_envoy_ptr is the pointer to the DynamicModuleBootstrapExtensionConfig
7949+
* object.
7950+
* @return true if the cluster lifecycle callbacks were successfully registered, false otherwise.
7951+
*/
7952+
bool envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle(
7953+
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr extension_config_envoy_ptr);
7954+
78967955
// =============================================================================
78977956
// Common Host Types (shared by cluster and standalone load balancer extensions)
78987957
// =============================================================================

source/extensions/dynamic_modules/abi_impl.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,14 @@ __attribute__((weak)) void envoy_dynamic_module_callback_bootstrap_extension_tim
355355
"not implemented in this context");
356356
}
357357

358+
__attribute__((weak)) bool
359+
envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle(
360+
envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr) {
361+
IS_ENVOY_BUG("envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle: "
362+
"not implemented in this context");
363+
return false;
364+
}
365+
358366
// ---------------------- Cluster extension callbacks ------------------------
359367
// These are weak symbols that provide default stub implementations. The actual implementations
360368
// are provided in the cluster extension abi_impl.cc when the cluster extension is used.

source/extensions/dynamic_modules/sdk/rust/src/bootstrap.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ pub trait EnvoyBootstrapExtensionConfig {
219219
///
220220
/// This must be called on the main thread.
221221
fn remove_admin_handler(&self, path_prefix: &str) -> bool;
222+
223+
/// Enable cluster lifecycle event notifications. When enabled, the module will receive
224+
/// [`BootstrapExtensionConfig::on_cluster_add_or_update`] and
225+
/// [`BootstrapExtensionConfig::on_cluster_removal`] callbacks when clusters are added, updated,
226+
/// or removed from the ClusterManager.
227+
///
228+
/// This must be called on the main thread, typically during or after `on_server_initialized`,
229+
/// since the ClusterManager is not available until that point.
230+
///
231+
/// This should be called at most once. Subsequent calls are no-ops and return `false`.
232+
fn enable_cluster_lifecycle(&self) -> bool;
222233
}
223234

224235
/// EnvoyBootstrapExtension is the Envoy-side bootstrap extension.
@@ -332,6 +343,36 @@ pub trait BootstrapExtensionConfig: Send + Sync {
332343
) -> (u32, String) {
333344
(404, String::new())
334345
}
346+
347+
/// This is called when a cluster is added to or updated in the ClusterManager.
348+
///
349+
/// This is only called if the module has opted in via
350+
/// [`EnvoyBootstrapExtensionConfig::enable_cluster_lifecycle`]. The callback is invoked on the
351+
/// main thread.
352+
///
353+
/// * `envoy_extension_config` can be used to interact with the underlying Envoy config object.
354+
/// * `cluster_name` is the name of the cluster that was added or updated.
355+
fn on_cluster_add_or_update(
356+
&self,
357+
_envoy_extension_config: &mut dyn EnvoyBootstrapExtensionConfig,
358+
_cluster_name: &str,
359+
) {
360+
}
361+
362+
/// This is called when a cluster is removed from the ClusterManager.
363+
///
364+
/// This is only called if the module has opted in via
365+
/// [`EnvoyBootstrapExtensionConfig::enable_cluster_lifecycle`]. The callback is invoked on the
366+
/// main thread.
367+
///
368+
/// * `envoy_extension_config` can be used to interact with the underlying Envoy config object.
369+
/// * `cluster_name` is the name of the cluster that was removed.
370+
fn on_cluster_removal(
371+
&self,
372+
_envoy_extension_config: &mut dyn EnvoyBootstrapExtensionConfig,
373+
_cluster_name: &str,
374+
) {
375+
}
335376
}
336377

337378
/// A completion callback that must be invoked exactly once to signal that an asynchronous
@@ -1026,6 +1067,12 @@ impl EnvoyBootstrapExtensionConfig for EnvoyBootstrapExtensionConfigImpl {
10261067
)
10271068
}
10281069
}
1070+
1071+
fn enable_cluster_lifecycle(&self) -> bool {
1072+
unsafe {
1073+
abi::envoy_dynamic_module_callback_bootstrap_extension_enable_cluster_lifecycle(self.raw)
1074+
}
1075+
}
10291076
}
10301077

10311078
// Implementation of EnvoyBootstrapExtension
@@ -1433,3 +1480,59 @@ pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_admin_reque
14331480

14341481
status_code
14351482
}
1483+
1484+
/// Event hook called by Envoy when a cluster is added to or updated in the ClusterManager.
1485+
///
1486+
/// # Safety
1487+
///
1488+
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
1489+
/// by the Envoy dynamic module ABI.
1490+
#[no_mangle]
1491+
pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update(
1492+
envoy_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr,
1493+
extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr,
1494+
cluster_name: abi::envoy_dynamic_module_type_envoy_buffer,
1495+
) {
1496+
let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig;
1497+
let extension_config = unsafe { &**extension_config };
1498+
1499+
let cluster_name_str = unsafe {
1500+
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
1501+
cluster_name.ptr as *const u8,
1502+
cluster_name.length,
1503+
))
1504+
};
1505+
1506+
extension_config.on_cluster_add_or_update(
1507+
&mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr),
1508+
cluster_name_str,
1509+
);
1510+
}
1511+
1512+
/// Event hook called by Envoy when a cluster is removed from the ClusterManager.
1513+
///
1514+
/// # Safety
1515+
///
1516+
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
1517+
/// by the Envoy dynamic module ABI.
1518+
#[no_mangle]
1519+
pub unsafe extern "C" fn envoy_dynamic_module_on_bootstrap_extension_cluster_removal(
1520+
envoy_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_envoy_ptr,
1521+
extension_config_ptr: abi::envoy_dynamic_module_type_bootstrap_extension_config_module_ptr,
1522+
cluster_name: abi::envoy_dynamic_module_type_envoy_buffer,
1523+
) {
1524+
let extension_config = extension_config_ptr as *const *const dyn BootstrapExtensionConfig;
1525+
let extension_config = unsafe { &**extension_config };
1526+
1527+
let cluster_name_str = unsafe {
1528+
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
1529+
cluster_name.ptr as *const u8,
1530+
cluster_name.length,
1531+
))
1532+
};
1533+
1534+
extension_config.on_cluster_removal(
1535+
&mut EnvoyBootstrapExtensionConfigImpl::new(envoy_ptr),
1536+
cluster_name_str,
1537+
);
1538+
}

0 commit comments

Comments
 (0)