1
#include "source/extensions/bootstrap/dynamic_modules/extension_config.h"
2

            
3
#include "source/common/common/assert.h"
4

            
5
#include "absl/strings/str_cat.h"
6

            
7
namespace Envoy {
8
namespace Extensions {
9
namespace Bootstrap {
10
namespace DynamicModules {
11

            
12
DynamicModuleBootstrapExtensionConfig::DynamicModuleBootstrapExtensionConfig(
13
    const absl::string_view extension_name, const absl::string_view extension_config,
14
    const absl::string_view metrics_namespace,
15
    Extensions::DynamicModules::DynamicModulePtr dynamic_module,
16
    Event::Dispatcher& main_thread_dispatcher, Server::Configuration::ServerFactoryContext& context,
17
    Stats::Store& stats_store)
18
71
    : dynamic_module_(std::move(dynamic_module)), main_thread_dispatcher_(main_thread_dispatcher),
19
71
      context_(context), stats_store_(stats_store),
20
71
      stats_scope_(stats_store.createScope(absl::StrCat(metrics_namespace, "."))),
21
71
      stat_name_pool_(stats_scope_->symbolTable()) {
22
71
  ASSERT(dynamic_module_ != nullptr);
23
71
  ASSERT(extension_name.data() != nullptr);
24
71
  ASSERT(extension_config.data() != nullptr);
25
71
}
26

            
27
71
DynamicModuleBootstrapExtensionConfig::~DynamicModuleBootstrapExtensionConfig() {
28
  // Cancel any pending HTTP callouts before destroying the config.
29
71
  for (auto& callout : http_callouts_) {
30
    if (callout.second->request_ != nullptr) {
31
      callout.second->request_->cancel();
32
    }
33
  }
34
71
  http_callouts_.clear();
35

            
36
71
  if (in_module_config_ != nullptr && on_bootstrap_extension_config_destroy_ != nullptr) {
37
67
    on_bootstrap_extension_config_destroy_(in_module_config_);
38
67
  }
39
71
}
40

            
41
69
void DynamicModuleBootstrapExtensionConfig::signalInitComplete() {
42
69
  if (init_target_ == nullptr) {
43
    IS_ENVOY_BUG("dynamic modules: signal_init_complete called but no init target registered");
44
    return;
45
  }
46
69
  init_target_->ready();
47
69
  ENVOY_LOG(debug, "dynamic modules: init target signaled complete, Envoy may start accepting "
48
69
                   "traffic");
49
69
}
50

            
51
3
bool DynamicModuleBootstrapExtensionConfig::enableClusterLifecycle() {
52
3
  if (cluster_lifecycle_enabled_) {
53
1
    return false;
54
1
  }
55
2
  cluster_lifecycle_enabled_ = true;
56
2
  cluster_update_callbacks_handle_ =
57
2
      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
2
  cluster_lifecycle_shutdown_handle_ = context_.lifecycleNotifier().registerCallback(
61
2
      Server::ServerLifecycleNotifier::Stage::ShutdownExit,
62
2
      [this]() { cluster_update_callbacks_handle_.reset(); });
63
2
  return true;
64
3
}
65

            
66
void DynamicModuleBootstrapExtensionConfig::onClusterAddOrUpdate(
67
1
    absl::string_view cluster_name, Upstream::ThreadLocalClusterCommand&) {
68
1
  if (in_module_config_ != nullptr && on_bootstrap_extension_cluster_add_or_update_ != nullptr) {
69
1
    on_bootstrap_extension_cluster_add_or_update_(thisAsVoidPtr(), in_module_config_,
70
1
                                                  {cluster_name.data(), cluster_name.size()});
71
1
  }
72
1
}
73

            
74
1
void DynamicModuleBootstrapExtensionConfig::onClusterRemoval(const std::string& cluster_name) {
75
1
  if (in_module_config_ != nullptr && on_bootstrap_extension_cluster_removal_ != nullptr) {
76
1
    on_bootstrap_extension_cluster_removal_(thisAsVoidPtr(), in_module_config_,
77
1
                                            {cluster_name.data(), cluster_name.size()});
78
1
  }
79
1
}
80

            
81
4
bool DynamicModuleBootstrapExtensionConfig::enableListenerLifecycle() {
82
4
  if (listener_lifecycle_enabled_) {
83
1
    return false;
84
1
  }
85
3
  if (listener_manager_ == nullptr) {
86
1
    ENVOY_LOG(error, "cannot enable listener lifecycle before server is initialized");
87
1
    return false;
88
1
  }
89
2
  listener_lifecycle_enabled_ = true;
90
2
  listener_update_callbacks_handle_ = listener_manager_->addListenerUpdateCallbacks(*this);
91
  // Register a shutdown callback to release the handle before the ListenerManager is destroyed.
92
2
  listener_lifecycle_shutdown_handle_ = context_.lifecycleNotifier().registerCallback(
93
2
      Server::ServerLifecycleNotifier::Stage::ShutdownExit,
94
2
      [this]() { listener_update_callbacks_handle_.reset(); });
95
2
  return true;
96
3
}
97

            
98
void DynamicModuleBootstrapExtensionConfig::onListenerAddOrUpdate(absl::string_view listener_name,
99
1
                                                                  const Network::ListenerConfig&) {
100
1
  if (in_module_config_ != nullptr && on_bootstrap_extension_listener_add_or_update_ != nullptr) {
101
1
    on_bootstrap_extension_listener_add_or_update_(thisAsVoidPtr(), in_module_config_,
102
1
                                                   {listener_name.data(), listener_name.size()});
103
1
  }
104
1
}
105

            
106
1
void DynamicModuleBootstrapExtensionConfig::onListenerRemoval(const std::string& listener_name) {
107
1
  if (in_module_config_ != nullptr && on_bootstrap_extension_listener_removal_ != nullptr) {
108
1
    on_bootstrap_extension_listener_removal_(thisAsVoidPtr(), in_module_config_,
109
1
                                             {listener_name.data(), listener_name.size()});
110
1
  }
111
1
}
112

            
113
6
void DynamicModuleBootstrapExtensionConfig::onScheduled(uint64_t event_id) {
114
6
  if (in_module_config_ != nullptr && on_bootstrap_extension_config_scheduled_ != nullptr) {
115
6
    on_bootstrap_extension_config_scheduled_(thisAsVoidPtr(), in_module_config_, event_id);
116
6
  }
117
6
}
118

            
119
envoy_dynamic_module_type_http_callout_init_result
120
DynamicModuleBootstrapExtensionConfig::sendHttpCallout(uint64_t* callout_id_out,
121
                                                       absl::string_view cluster_name,
122
                                                       Http::RequestMessagePtr&& message,
123
8
                                                       uint64_t timeout_milliseconds) {
124
  // Access cluster manager lazily since it's not available during bootstrap extension creation.
125
8
  Upstream::ThreadLocalCluster* cluster =
126
8
      context_.clusterManager().getThreadLocalCluster(cluster_name);
127
8
  if (!cluster) {
128
1
    return envoy_dynamic_module_type_http_callout_init_result_ClusterNotFound;
129
1
  }
130
7
  Http::AsyncClient::RequestOptions options;
131
7
  options.setTimeout(std::chrono::milliseconds(timeout_milliseconds));
132

            
133
  // Prepare the callback and the ID.
134
7
  const uint64_t callout_id = getNextCalloutId();
135
7
  auto http_callout_callback =
136
7
      std::make_unique<DynamicModuleBootstrapExtensionConfig::HttpCalloutCallback>(
137
7
          shared_from_this(), callout_id);
138
7
  DynamicModuleBootstrapExtensionConfig::HttpCalloutCallback& callback = *http_callout_callback;
139

            
140
7
  auto request = cluster->httpAsyncClient().send(std::move(message), callback, options);
141
7
  if (!request) {
142
1
    return envoy_dynamic_module_type_http_callout_init_result_CannotCreateRequest;
143
1
  }
144

            
145
  // Register the callout.
146
6
  callback.request_ = request;
147
6
  http_callouts_.emplace(callout_id, std::move(http_callout_callback));
148
6
  *callout_id_out = callout_id;
149

            
150
6
  return envoy_dynamic_module_type_http_callout_init_result_Success;
151
7
}
152

            
153
void DynamicModuleBootstrapExtensionConfig::HttpCalloutCallback::onSuccess(
154
3
    const Http::AsyncClient::Request&, Http::ResponseMessagePtr&& response) {
155
  // Move the config and callout id to the local scope since
156
  // on_bootstrap_extension_http_callout_done_ might result in operations that affect this
157
  // callback's lifetime.
158
3
  DynamicModuleBootstrapExtensionConfigSharedPtr config = std::move(config_);
159
3
  uint64_t callout_id = callout_id_;
160

            
161
  // Check if the config still has the in-module config.
162
3
  if (!config->in_module_config_) {
163
1
    config->http_callouts_.erase(callout_id);
164
1
    return;
165
1
  }
166

            
167
2
  absl::InlinedVector<envoy_dynamic_module_type_envoy_http_header, 16> headers_vector;
168
2
  headers_vector.reserve(response->headers().size());
169
2
  response->headers().iterate([&headers_vector](
170
3
                                  const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate {
171
3
    headers_vector.emplace_back(envoy_dynamic_module_type_envoy_http_header{
172
3
        const_cast<char*>(header.key().getStringView().data()), header.key().getStringView().size(),
173
3
        const_cast<char*>(header.value().getStringView().data()),
174
3
        header.value().getStringView().size()});
175
3
    return Http::HeaderMap::Iterate::Continue;
176
3
  });
177

            
178
2
  Envoy::Buffer::RawSliceVector body = response->body().getRawSlices(std::nullopt);
179
2
  config->on_bootstrap_extension_http_callout_done_(
180
2
      config->thisAsVoidPtr(), config->in_module_config_, callout_id,
181
2
      envoy_dynamic_module_type_http_callout_result_Success, headers_vector.data(),
182
2
      headers_vector.size(), reinterpret_cast<envoy_dynamic_module_type_envoy_buffer*>(body.data()),
183
2
      body.size());
184
  // Clean up the callout.
185
2
  config->http_callouts_.erase(callout_id);
186
2
}
187

            
188
void DynamicModuleBootstrapExtensionConfig::HttpCalloutCallback::onFailure(
189
3
    const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason reason) {
190
  // Move the config and callout id to the local scope since
191
  // on_bootstrap_extension_http_callout_done_ might result in operations that affect this
192
  // callback's lifetime.
193
3
  DynamicModuleBootstrapExtensionConfigSharedPtr config = std::move(config_);
194
3
  const uint64_t callout_id = callout_id_;
195

            
196
  // Check if the config still has the in-module config.
197
3
  if (!config->in_module_config_) {
198
1
    config->http_callouts_.erase(callout_id);
199
1
    return;
200
1
  }
201

            
202
  // request_ is not null if the callout is actually sent to the upstream cluster.
203
  // This allows us to avoid inlined calls to onFailure() method (which results in a reentrant to
204
  // the modules) when the async client immediately fails the callout.
205
2
  if (request_) {
206
2
    envoy_dynamic_module_type_http_callout_result result;
207
2
    switch (reason) {
208
1
    case Http::AsyncClient::FailureReason::Reset:
209
1
      result = envoy_dynamic_module_type_http_callout_result_Reset;
210
1
      break;
211
1
    case Http::AsyncClient::FailureReason::ExceedResponseBufferLimit:
212
1
      result = envoy_dynamic_module_type_http_callout_result_ExceedResponseBufferLimit;
213
1
      break;
214
2
    }
215
2
    config->on_bootstrap_extension_http_callout_done_(config->thisAsVoidPtr(),
216
2
                                                      config->in_module_config_, callout_id, result,
217
2
                                                      nullptr, 0, nullptr, 0);
218
2
  }
219

            
220
  // Clean up the callout.
221
2
  config->http_callouts_.erase(callout_id);
222
2
}
223

            
224
absl::StatusOr<DynamicModuleBootstrapExtensionConfigSharedPtr>
225
newDynamicModuleBootstrapExtensionConfig(
226
    const absl::string_view extension_name, const absl::string_view extension_config,
227
    const absl::string_view metrics_namespace,
228
    Extensions::DynamicModules::DynamicModulePtr dynamic_module,
229
    Event::Dispatcher& main_thread_dispatcher, Server::Configuration::ServerFactoryContext& context,
230
87
    Stats::Store& stats_store) {
231

            
232
  // Resolve the required symbols from the dynamic module.
233
87
  auto constructor =
234
87
      dynamic_module
235
87
          ->getFunctionPointer<decltype(&envoy_dynamic_module_on_bootstrap_extension_config_new)>(
236
87
              "envoy_dynamic_module_on_bootstrap_extension_config_new");
237
87
  if (!constructor.ok()) {
238
1
    return constructor.status();
239
1
  }
240

            
241
86
  auto on_config_destroy =
242
86
      dynamic_module->getFunctionPointer<OnBootstrapExtensionConfigDestroyType>(
243
86
          "envoy_dynamic_module_on_bootstrap_extension_config_destroy");
244
86
  if (!on_config_destroy.ok()) {
245
1
    return on_config_destroy.status();
246
1
  }
247

            
248
85
  auto on_extension_new = dynamic_module->getFunctionPointer<OnBootstrapExtensionNewType>(
249
85
      "envoy_dynamic_module_on_bootstrap_extension_new");
250
85
  if (!on_extension_new.ok()) {
251
1
    return on_extension_new.status();
252
1
  }
253

            
254
84
  auto on_server_initialized =
255
84
      dynamic_module->getFunctionPointer<OnBootstrapExtensionServerInitializedType>(
256
84
          "envoy_dynamic_module_on_bootstrap_extension_server_initialized");
257
84
  if (!on_server_initialized.ok()) {
258
1
    return on_server_initialized.status();
259
1
  }
260

            
261
83
  auto on_worker_thread_initialized =
262
83
      dynamic_module->getFunctionPointer<OnBootstrapExtensionWorkerThreadInitializedType>(
263
83
          "envoy_dynamic_module_on_bootstrap_extension_worker_thread_initialized");
264
83
  if (!on_worker_thread_initialized.ok()) {
265
1
    return on_worker_thread_initialized.status();
266
1
  }
267

            
268
82
  auto on_extension_destroy = dynamic_module->getFunctionPointer<OnBootstrapExtensionDestroyType>(
269
82
      "envoy_dynamic_module_on_bootstrap_extension_destroy");
270
82
  if (!on_extension_destroy.ok()) {
271
1
    return on_extension_destroy.status();
272
1
  }
273

            
274
81
  auto on_drain_started = dynamic_module->getFunctionPointer<OnBootstrapExtensionDrainStartedType>(
275
81
      "envoy_dynamic_module_on_bootstrap_extension_drain_started");
276
81
  if (!on_drain_started.ok()) {
277
1
    return on_drain_started.status();
278
1
  }
279

            
280
80
  auto on_shutdown = dynamic_module->getFunctionPointer<OnBootstrapExtensionShutdownType>(
281
80
      "envoy_dynamic_module_on_bootstrap_extension_shutdown");
282
80
  if (!on_shutdown.ok()) {
283
1
    return on_shutdown.status();
284
1
  }
285

            
286
79
  auto on_config_scheduled =
287
79
      dynamic_module->getFunctionPointer<OnBootstrapExtensionConfigScheduledType>(
288
79
          "envoy_dynamic_module_on_bootstrap_extension_config_scheduled");
289
79
  if (!on_config_scheduled.ok()) {
290
1
    return on_config_scheduled.status();
291
1
  }
292

            
293
78
  auto on_http_callout_done =
294
78
      dynamic_module->getFunctionPointer<OnBootstrapExtensionHttpCalloutDoneType>(
295
78
          "envoy_dynamic_module_on_bootstrap_extension_http_callout_done");
296
78
  if (!on_http_callout_done.ok()) {
297
1
    return on_http_callout_done.status();
298
1
  }
299

            
300
77
  auto on_timer_fired = dynamic_module->getFunctionPointer<OnBootstrapExtensionTimerFiredType>(
301
77
      "envoy_dynamic_module_on_bootstrap_extension_timer_fired");
302
77
  if (!on_timer_fired.ok()) {
303
1
    return on_timer_fired.status();
304
1
  }
305

            
306
76
  auto on_admin_request = dynamic_module->getFunctionPointer<OnBootstrapExtensionAdminRequestType>(
307
76
      "envoy_dynamic_module_on_bootstrap_extension_admin_request");
308
76
  if (!on_admin_request.ok()) {
309
1
    return on_admin_request.status();
310
1
  }
311

            
312
75
  auto on_cluster_add_or_update =
313
75
      dynamic_module->getFunctionPointer<OnBootstrapExtensionClusterAddOrUpdateType>(
314
75
          "envoy_dynamic_module_on_bootstrap_extension_cluster_add_or_update");
315
75
  if (!on_cluster_add_or_update.ok()) {
316
1
    return on_cluster_add_or_update.status();
317
1
  }
318

            
319
74
  auto on_cluster_removal =
320
74
      dynamic_module->getFunctionPointer<OnBootstrapExtensionClusterRemovalType>(
321
74
          "envoy_dynamic_module_on_bootstrap_extension_cluster_removal");
322
74
  if (!on_cluster_removal.ok()) {
323
1
    return on_cluster_removal.status();
324
1
  }
325

            
326
73
  auto on_listener_add_or_update =
327
73
      dynamic_module->getFunctionPointer<OnBootstrapExtensionListenerAddOrUpdateType>(
328
73
          "envoy_dynamic_module_on_bootstrap_extension_listener_add_or_update");
329
73
  if (!on_listener_add_or_update.ok()) {
330
1
    return on_listener_add_or_update.status();
331
1
  }
332

            
333
72
  auto on_listener_removal =
334
72
      dynamic_module->getFunctionPointer<OnBootstrapExtensionListenerRemovalType>(
335
72
          "envoy_dynamic_module_on_bootstrap_extension_listener_removal");
336
72
  if (!on_listener_removal.ok()) {
337
1
    return on_listener_removal.status();
338
1
  }
339

            
340
71
  auto config = std::make_shared<DynamicModuleBootstrapExtensionConfig>(
341
71
      extension_name, extension_config, metrics_namespace, std::move(dynamic_module),
342
71
      main_thread_dispatcher, context, stats_store);
343

            
344
  // Always register an init target so that Envoy blocks traffic until the module signals readiness.
345
  // This must happen before calling the module constructor so the module can call
346
  // signal_init_complete during config creation.
347
71
  config->init_target_ = std::make_unique<Init::TargetImpl>("dynamic_modules_bootstrap", []() {});
348
71
  context.initManager().add(*config->init_target_);
349

            
350
71
  const void* extension_config_module_ptr = (*constructor.value())(
351
71
      static_cast<void*>(config.get()), {extension_name.data(), extension_name.size()},
352
71
      {extension_config.data(), extension_config.size()});
353
71
  if (extension_config_module_ptr == nullptr) {
354
2
    return absl::InvalidArgumentError("Failed to initialize dynamic module");
355
2
  }
356

            
357
69
  config->in_module_config_ = extension_config_module_ptr;
358
69
  config->on_bootstrap_extension_config_destroy_ = on_config_destroy.value();
359
69
  config->on_bootstrap_extension_new_ = on_extension_new.value();
360
69
  config->on_bootstrap_extension_server_initialized_ = on_server_initialized.value();
361
69
  config->on_bootstrap_extension_worker_thread_initialized_ = on_worker_thread_initialized.value();
362
69
  config->on_bootstrap_extension_destroy_ = on_extension_destroy.value();
363
69
  config->on_bootstrap_extension_drain_started_ = on_drain_started.value();
364
69
  config->on_bootstrap_extension_shutdown_ = on_shutdown.value();
365
69
  config->on_bootstrap_extension_config_scheduled_ = on_config_scheduled.value();
366
69
  config->on_bootstrap_extension_http_callout_done_ = on_http_callout_done.value();
367
69
  config->on_bootstrap_extension_timer_fired_ = on_timer_fired.value();
368
69
  config->on_bootstrap_extension_admin_request_ = on_admin_request.value();
369
69
  config->on_bootstrap_extension_cluster_add_or_update_ = on_cluster_add_or_update.value();
370
69
  config->on_bootstrap_extension_cluster_removal_ = on_cluster_removal.value();
371
69
  config->on_bootstrap_extension_listener_add_or_update_ = on_listener_add_or_update.value();
372
69
  config->on_bootstrap_extension_listener_removal_ = on_listener_removal.value();
373

            
374
69
  return config;
375
71
}
376

            
377
} // namespace DynamicModules
378
} // namespace Bootstrap
379
} // namespace Extensions
380
} // namespace Envoy