Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/router/scoped_rds.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/router/scoped_rds.h"
2
3
#include <memory>
4
5
#include "envoy/admin/v3/config_dump.pb.h"
6
#include "envoy/config/core/v3/config_source.pb.h"
7
#include "envoy/config/route/v3/scoped_route.pb.h"
8
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"
9
#include "envoy/service/discovery/v3/discovery.pb.h"
10
11
#include "source/common/common/assert.h"
12
#include "source/common/common/cleanup.h"
13
#include "source/common/common/logger.h"
14
#include "source/common/common/utility.h"
15
#include "source/common/config/api_version.h"
16
#include "source/common/config/resource_name.h"
17
#include "source/common/config/xds_resource.h"
18
#include "source/common/init/manager_impl.h"
19
#include "source/common/init/watcher_impl.h"
20
#include "source/common/protobuf/utility.h"
21
#include "source/common/router/rds_impl.h"
22
#include "source/common/router/scoped_config_impl.h"
23
24
#include "absl/strings/str_join.h"
25
26
// Types are deeply nested under Envoy::Config::ConfigProvider; use 'using-directives' across all
27
// ConfigProvider related types for consistency.
28
using Envoy::Config::ConfigProvider;
29
using Envoy::Config::ConfigProviderInstanceType;
30
using Envoy::Config::ConfigProviderManager;
31
using Envoy::Config::ConfigProviderPtr;
32
using Envoy::Config::ScopedResume;
33
34
namespace Envoy {
35
namespace Router {
36
namespace ScopedRoutesConfigProviderUtil {
37
ConfigProviderPtr create(
38
    const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
39
        config,
40
    Server::Configuration::ServerFactoryContext& factory_context, Init::Manager& init_manager,
41
270
    const std::string& stat_prefix, ConfigProviderManager& scoped_routes_config_provider_manager) {
42
270
  ASSERT(config.route_specifier_case() ==
43
270
         envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager::
44
270
             RouteSpecifierCase::kScopedRoutes);
45
270
  switch (config.scoped_routes().config_specifier_case()) {
46
223
  case envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes::
47
223
      ConfigSpecifierCase::kScopedRouteConfigurationsList: {
48
223
    const envoy::extensions::filters::network::http_connection_manager::v3::
49
223
        ScopedRouteConfigurationsList& scoped_route_list =
50
223
            config.scoped_routes().scoped_route_configurations_list();
51
223
    return scoped_routes_config_provider_manager.createStaticConfigProvider(
52
223
        RepeatedPtrUtil::convertToConstMessagePtrContainer<
53
223
            envoy::config::route::v3::ScopedRouteConfiguration,
54
223
            ProtobufTypes::ConstMessagePtrVector>(scoped_route_list.scoped_route_configurations()),
55
223
        factory_context,
56
223
        ScopedRoutesConfigProviderManagerOptArg(config.scoped_routes().name(),
57
223
                                                config.scoped_routes().rds_config_source()));
58
0
  }
59
47
  case envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes::
60
47
      ConfigSpecifierCase::kScopedRds:
61
47
    return scoped_routes_config_provider_manager.createXdsConfigProvider(
62
47
        config.scoped_routes().scoped_rds(), factory_context, init_manager, stat_prefix,
63
47
        ScopedRoutesConfigProviderManagerOptArg(config.scoped_routes().name(),
64
47
                                                config.scoped_routes().rds_config_source()));
65
0
  case envoy::extensions::filters::network::http_connection_manager::v3::ScopedRoutes::
66
0
      ConfigSpecifierCase::CONFIG_SPECIFIER_NOT_SET:
67
0
    PANIC("not implemented");
68
270
  }
69
0
  PANIC_DUE_TO_CORRUPT_ENUM;
70
0
}
71
72
ScopeKeyBuilderPtr createScopeKeyBuilder(
73
    const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
74
205
        config) {
75
205
  ASSERT(config.route_specifier_case() ==
76
205
         envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager::
77
205
             RouteSpecifierCase::kScopedRoutes);
78
205
  auto scope_key_builder = config.scoped_routes().scope_key_builder();
79
205
  return std::make_unique<ScopeKeyBuilderImpl>(std::move(scope_key_builder));
80
205
}
81
82
} // namespace ScopedRoutesConfigProviderUtil
83
84
namespace {
85
86
std::vector<ScopedRouteInfoConstSharedPtr>
87
makeScopedRouteInfos(ProtobufTypes::ConstMessagePtrVector&& config_protos,
88
                     Server::Configuration::ServerFactoryContext& factory_context,
89
223
                     ScopedRoutesConfigProviderManager& config_provider_manager) {
90
223
  std::vector<ScopedRouteInfoConstSharedPtr> scopes;
91
3.32k
  for (std::unique_ptr<const Protobuf::Message>& config_proto : config_protos) {
92
3.32k
    auto scoped_route_config =
93
3.32k
        MessageUtil::downcastAndValidate<const envoy::config::route::v3::ScopedRouteConfiguration&>(
94
3.32k
            *config_proto, factory_context.messageValidationContext().staticValidationVisitor());
95
3.32k
    if (!scoped_route_config.route_configuration_name().empty()) {
96
1
      throw EnvoyException("Fetching routes via RDS (route_configuration_name) is not supported "
97
1
                           "with inline scoped routes.");
98
1
    }
99
3.32k
    if (!scoped_route_config.has_route_configuration()) {
100
2
      throw EnvoyException("You must specify a route_configuration with inline scoped routes.");
101
2
    }
102
3.32k
    RouteConfigProviderPtr route_config_provider =
103
3.32k
        config_provider_manager.routeConfigProviderManager().createStaticRouteConfigProvider(
104
3.32k
            scoped_route_config.route_configuration(), factory_context,
105
3.32k
            factory_context.messageValidationContext().staticValidationVisitor());
106
3.32k
    scopes.push_back(std::make_shared<const ScopedRouteInfo>(scoped_route_config,
107
3.32k
                                                             route_config_provider->configCast()));
108
3.32k
  }
109
110
220
  return scopes;
111
223
}
112
113
} // namespace
114
115
InlineScopedRoutesConfigProvider::InlineScopedRoutesConfigProvider(
116
    ProtobufTypes::ConstMessagePtrVector&& config_protos, std::string name,
117
    Server::Configuration::ServerFactoryContext& factory_context,
118
    ScopedRoutesConfigProviderManager& config_provider_manager,
119
    envoy::config::core::v3::ConfigSource rds_config_source)
120
    : Envoy::Config::ImmutableConfigProviderBase(factory_context, config_provider_manager,
121
                                                 ConfigProviderInstanceType::Inline,
122
                                                 ConfigProvider::ApiType::Delta),
123
      name_(std::move(name)),
124
      scopes_(
125
          makeScopedRouteInfos(std::move(config_protos), factory_context, config_provider_manager)),
126
      config_(std::make_shared<ScopedConfigImpl>(scopes_)),
127
223
      rds_config_source_(std::move(rds_config_source)) {}
128
129
ScopedRdsConfigSubscription::ScopedRdsConfigSubscription(
130
    const envoy::extensions::filters::network::http_connection_manager::v3::ScopedRds& scoped_rds,
131
    const uint64_t manager_identifier, const std::string& name,
132
    Server::Configuration::ServerFactoryContext& factory_context, const std::string& stat_prefix,
133
    envoy::config::core::v3::ConfigSource rds_config_source,
134
    RouteConfigProviderManager& route_config_provider_manager,
135
    ScopedRoutesConfigProviderManager& config_provider_manager)
136
    : DeltaConfigSubscriptionInstance("SRDS", manager_identifier, config_provider_manager,
137
                                      factory_context),
138
      Envoy::Config::SubscriptionBase<envoy::config::route::v3::ScopedRouteConfiguration>(
139
          factory_context.messageValidationContext().dynamicValidationVisitor(), "name"),
140
      factory_context_(factory_context), name_(name),
141
      scope_(factory_context.scope().createScope(stat_prefix + "scoped_rds." + name + ".")),
142
      stats_({ALL_SCOPED_RDS_STATS(POOL_COUNTER(*scope_), POOL_GAUGE(*scope_))}),
143
      rds_config_source_(std::move(rds_config_source)), stat_prefix_(stat_prefix),
144
44
      route_config_provider_manager_(route_config_provider_manager) {
145
44
  const auto resource_name = getResourceName();
146
44
  if (scoped_rds.srds_resources_locator().empty()) {
147
43
    subscription_ = THROW_OR_RETURN_VALUE(
148
43
        factory_context.clusterManager().subscriptionFactory().subscriptionFromConfigSource(
149
43
            scoped_rds.scoped_rds_config_source(), Grpc::Common::typeUrl(resource_name), *scope_,
150
43
            *this, resource_decoder_, {}),
151
43
        Envoy::Config::SubscriptionPtr);
152
43
  } else {
153
1
    const auto srds_resources_locator = THROW_OR_RETURN_VALUE(
154
1
        Envoy::Config::XdsResourceIdentifier::decodeUrl(scoped_rds.srds_resources_locator()),
155
1
        xds::core::v3::ResourceLocator);
156
1
    subscription_ = THROW_OR_RETURN_VALUE(
157
1
        factory_context.clusterManager().subscriptionFactory().collectionSubscriptionFromUrl(
158
1
            srds_resources_locator, scoped_rds.scoped_rds_config_source(), resource_name, *scope_,
159
1
            *this, resource_decoder_),
160
1
        Envoy::Config::SubscriptionPtr);
161
1
  }
162
163
  // TODO(tony612): consider not using the callback here.
164
44
  initialize([]() -> Envoy::Config::ConfigProvider::ConfigConstSharedPtr {
165
43
    return std::make_shared<ScopedConfigImpl>();
166
43
  });
167
44
}
168
169
// Constructor for RdsRouteConfigProviderHelper when scope is eager loading.
170
// Initialize RdsRouteConfigProvider by default.
171
ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::RdsRouteConfigProviderHelper(
172
    ScopedRdsConfigSubscription& parent, std::string scope_name,
173
    envoy::extensions::filters::network::http_connection_manager::v3::Rds& rds,
174
    Init::Manager& init_manager)
175
0
    : parent_(parent), scope_name_(scope_name), on_demand_(false) {
176
0
  initRdsConfigProvider(rds, init_manager);
177
0
}
178
179
// Constructor for RdsRouteConfigProviderHelper when scope is on demand.
180
// Leave the RdsRouteConfigProvider uninitialized.
181
ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::RdsRouteConfigProviderHelper(
182
    ScopedRdsConfigSubscription& parent, std::string scope_name)
183
0
    : parent_(parent), scope_name_(scope_name), on_demand_(true) {
184
0
  parent_.stats_.on_demand_scopes_.inc();
185
0
}
186
187
// When on demand callback is received from main thread, there are 4 cases.
188
// 1. Scope is not found, post a scope not found callback back to worker thread.
189
// 2. Scope is found but route provider has not been initialized, create route provider.
190
// 3. After route provider has been initialized, if RouteConfiguration has been fetched,
191
// post scope found callback to worker thread.
192
// 4. After route provider has been initialized, if RouteConfiguration is null,
193
// cache the callback and wait for RouteConfiguration to come.
194
void ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::addOnDemandUpdateCallback(
195
0
    std::function<void()> callback) {
196
  // If RouteConfiguration has been initialized, run the callback to continue in filter chain,
197
  // otherwise cache it and wait for the route table to be initialized. If RouteConfiguration hasn't
198
  // been initialized, routeConfig() return a shared_ptr to NullConfigImpl. The name of
199
  // NullConfigImpl is an empty string.
200
0
  if (route_provider_ != nullptr && !routeConfig()->name().empty()) {
201
0
    callback();
202
0
    return;
203
0
  }
204
0
  on_demand_update_callbacks_.push_back(callback);
205
  // Initialize the rds provider if it has not been initialized. There is potential race here
206
  // because other worker threads may also post callback to on demand update the RouteConfiguration
207
  // associated with this scope. If rds provider has been initialized, just wait for
208
  // RouteConfiguration to be updated.
209
0
  maybeInitRdsConfigProvider();
210
0
}
211
212
0
void ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::runOnDemandUpdateCallback() {
213
0
  for (auto& callback : on_demand_update_callbacks_) {
214
0
    callback();
215
0
  }
216
0
  on_demand_update_callbacks_.clear();
217
0
}
218
219
void ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::initRdsConfigProvider(
220
    envoy::extensions::filters::network::http_connection_manager::v3::Rds& rds,
221
0
    Init::Manager& init_manager) {
222
0
  route_provider_ = std::dynamic_pointer_cast<RdsRouteConfigProviderImpl>(
223
0
      parent_.route_config_provider_manager_.createRdsRouteConfigProvider(
224
0
          rds, parent_.factory_context_, parent_.stat_prefix_, init_manager));
225
226
0
  rds_update_callback_handle_ = route_provider_->subscription().addUpdateCallback([this]() {
227
    // Subscribe to RDS update.
228
0
    parent_.onRdsConfigUpdate(scope_name_, route_provider_->configCast());
229
0
    return absl::OkStatus();
230
0
  });
231
0
  parent_.stats_.active_scopes_.inc();
232
0
}
233
234
0
void ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::maybeInitRdsConfigProvider() {
235
  // If the route provider have been initialized, return and wait for rds config update.
236
0
  if (route_provider_ != nullptr) {
237
0
    return;
238
0
  }
239
240
  // Create a init_manager to create a rds provider.
241
  // No transitive warming dependency here because only on demand update reach this point.
242
0
  Init::ManagerImpl srds_init_mgr("SRDS on demand init manager.");
243
0
  Cleanup srds_initialization_continuation([this, &srds_init_mgr] {
244
0
    Init::WatcherImpl noop_watcher(
245
0
        fmt::format("SRDS on demand ConfigUpdate watcher: {}", scope_name_),
246
0
        []() { /*Do nothing.*/ });
247
0
    srds_init_mgr.initialize(noop_watcher);
248
0
  });
249
  // Create route provider.
250
0
  envoy::extensions::filters::network::http_connection_manager::v3::Rds rds;
251
0
  rds.mutable_config_source()->MergeFrom(parent_.rds_config_source_);
252
0
  rds.set_route_config_name(
253
0
      parent_.scoped_route_map_[scope_name_]->configProto().route_configuration_name());
254
0
  initRdsConfigProvider(rds, srds_init_mgr);
255
0
  ENVOY_LOG(debug, fmt::format("Scope on demand update: {}", scope_name_));
256
  // If RouteConfiguration hasn't been initialized, routeConfig() return a shared_ptr to
257
  // NullConfigImpl. The name of NullConfigImpl is an empty string.
258
0
  if (routeConfig()->name().empty()) {
259
0
    return;
260
0
  }
261
  // If RouteConfiguration has been initialized, apply update to all the threads.
262
0
  parent_.onRdsConfigUpdate(scope_name_, route_provider_->configCast());
263
0
}
264
265
absl::StatusOr<bool> ScopedRdsConfigSubscription::addOrUpdateScopes(
266
    const std::vector<Envoy::Config::DecodedResourceRef>& resources, Init::Manager& init_manager,
267
0
    const std::string& version_info) {
268
0
  bool any_applied = false;
269
0
  envoy::extensions::filters::network::http_connection_manager::v3::Rds rds;
270
0
  rds.mutable_config_source()->MergeFrom(rds_config_source_);
271
0
  std::vector<ScopedRouteInfoConstSharedPtr> updated_scopes;
272
0
  for (const auto& resource : resources) {
273
    // Explicit copy so that we can std::move later.
274
0
    envoy::config::route::v3::ScopedRouteConfiguration scoped_route_config =
275
0
        dynamic_cast<const envoy::config::route::v3::ScopedRouteConfiguration&>(
276
0
            resource.get().resource());
277
0
    if (scoped_route_config.route_configuration_name().empty()) {
278
0
      return absl::InvalidArgumentError("route_configuration_name is empty.");
279
0
    }
280
0
    const std::string scope_name = scoped_route_config.name();
281
0
    if (const auto& scope_info_iter = scoped_route_map_.find(scope_name);
282
0
        scope_info_iter != scoped_route_map_.end() &&
283
0
        scope_info_iter->second->configHash() == MessageUtil::hash(scoped_route_config)) {
284
0
      continue;
285
0
    }
286
0
    rds.set_route_config_name(scoped_route_config.route_configuration_name());
287
0
    std::unique_ptr<RdsRouteConfigProviderHelper> rds_config_provider_helper;
288
0
    std::shared_ptr<ScopedRouteInfo> scoped_route_info = nullptr;
289
0
    if (scoped_route_config.on_demand() == false) {
290
      // For default scopes, create a rds helper with rds provider initialized.
291
0
      rds_config_provider_helper =
292
0
          std::make_unique<RdsRouteConfigProviderHelper>(*this, scope_name, rds, init_manager);
293
0
      scoped_route_info = std::make_shared<ScopedRouteInfo>(
294
0
          std::move(scoped_route_config), rds_config_provider_helper->routeConfig());
295
0
    } else {
296
      // For on demand scopes, create a rds helper with rds provider uninitialized.
297
0
      rds_config_provider_helper =
298
0
          std::make_unique<RdsRouteConfigProviderHelper>(*this, scope_name);
299
      // scope_route_info->routeConfig() will be nullptr, because RouteConfiguration is not loaded.
300
0
      scoped_route_info =
301
0
          std::make_shared<ScopedRouteInfo>(std::move(scoped_route_config), nullptr);
302
0
    }
303
0
    route_provider_by_scope_[scope_name] = std::move(rds_config_provider_helper);
304
0
    scope_name_by_hash_[scoped_route_info->scopeKey().hash()] = scoped_route_info->scopeName();
305
0
    scoped_route_map_[scoped_route_info->scopeName()] = scoped_route_info;
306
0
    updated_scopes.push_back(scoped_route_info);
307
0
    any_applied = true;
308
0
    ENVOY_LOG(debug, "srds: queueing add/update of scoped_route '{}', version: {}",
309
0
              scoped_route_info->scopeName(), version_info);
310
0
  }
311
312
  // scoped_route_info of both eager loading and on demand scopes will be propagated to work
313
  // threads. Upon a scoped RouteConfiguration miss, if the scope exists, an on demand update
314
  // callback will be posted to main thread.
315
0
  if (!updated_scopes.empty()) {
316
0
    applyConfigUpdate([updated_scopes](ConfigProvider::ConfigConstSharedPtr config)
317
0
                          -> ConfigProvider::ConfigConstSharedPtr {
318
0
      auto* thread_local_scoped_config =
319
0
          const_cast<ScopedConfigImpl*>(static_cast<const ScopedConfigImpl*>(config.get()));
320
0
      thread_local_scoped_config->addOrUpdateRoutingScopes(updated_scopes);
321
0
      return config;
322
0
    });
323
0
  }
324
0
  return any_applied;
325
0
}
326
327
std::list<ScopedRdsConfigSubscription::RdsRouteConfigProviderHelperPtr>
328
ScopedRdsConfigSubscription::removeScopes(
329
0
    const Protobuf::RepeatedPtrField<std::string>& scope_names, const std::string& version_info) {
330
0
  std::list<ScopedRdsConfigSubscription::RdsRouteConfigProviderHelperPtr>
331
0
      to_be_removed_rds_providers;
332
0
  std::vector<std::string> removed_scope_names;
333
0
  for (const auto& scope_name : scope_names) {
334
0
    auto iter = scoped_route_map_.find(scope_name);
335
0
    if (iter != scoped_route_map_.end()) {
336
0
      auto rds_config_provider_helper_iter = route_provider_by_scope_.find(scope_name);
337
0
      if (rds_config_provider_helper_iter != route_provider_by_scope_.end()) {
338
0
        to_be_removed_rds_providers.emplace_back(
339
0
            std::move(rds_config_provider_helper_iter->second));
340
0
        route_provider_by_scope_.erase(rds_config_provider_helper_iter);
341
0
      }
342
0
      ASSERT(scope_name_by_hash_.find(iter->second->scopeKey().hash()) !=
343
0
             scope_name_by_hash_.end());
344
0
      scope_name_by_hash_.erase(iter->second->scopeKey().hash());
345
0
      scoped_route_map_.erase(iter);
346
0
      removed_scope_names.push_back(scope_name);
347
0
      ENVOY_LOG(debug, "srds: queueing removal of scoped route '{}', version: {}", scope_name,
348
0
                version_info);
349
0
    }
350
0
  }
351
0
  if (!removed_scope_names.empty()) {
352
0
    applyConfigUpdate([removed_scope_names](ConfigProvider::ConfigConstSharedPtr config)
353
0
                          -> ConfigProvider::ConfigConstSharedPtr {
354
0
      auto* thread_local_scoped_config =
355
0
          const_cast<ScopedConfigImpl*>(static_cast<const ScopedConfigImpl*>(config.get()));
356
0
      thread_local_scoped_config->removeRoutingScopes(removed_scope_names);
357
0
      return config;
358
0
    });
359
0
  }
360
0
  return to_be_removed_rds_providers;
361
0
}
362
363
absl::Status ScopedRdsConfigSubscription::onConfigUpdate(
364
    const std::vector<Envoy::Config::DecodedResourceRef>& added_resources,
365
    const Protobuf::RepeatedPtrField<std::string>& removed_resources,
366
0
    const std::string& version_info) {
367
  // NOTE: deletes are done before adds/updates.
368
0
  absl::flat_hash_map<std::string, ScopedRouteInfoConstSharedPtr> to_be_removed_scopes;
369
  // Destruction of resume_rds will lift the floodgate for new RDS subscriptions.
370
  // Note in the case of partial acceptance, accepted RDS subscriptions should be started
371
  // despite of any error.
372
0
  ScopedResume resume_rds;
373
  // If new route config sources come after the local init manager's initialize() been
374
  // called, the init manager can't accept new targets. Instead we use a local override which will
375
  // start new subscriptions but not wait on them to be ready.
376
0
  std::unique_ptr<Init::ManagerImpl> srds_init_mgr;
377
  // NOTE: This should be defined after srds_init_mgr and resume_rds, as it depends on the
378
  // srds_init_mgr, and we want a single RDS discovery request to be sent to management
379
  // server.
380
0
  std::unique_ptr<Cleanup> srds_initialization_continuation;
381
0
  ASSERT(localInitManager().state() > Init::Manager::State::Uninitialized);
382
0
  const auto type_url = Envoy::Config::getTypeUrl<envoy::config::route::v3::RouteConfiguration>();
383
  // Pause RDS to not send a burst of RDS requests until we start all the new subscriptions.
384
  // In the case that localInitManager is uninitialized, RDS is already paused
385
  // either by Server init or LDS init.
386
0
  if (factory_context_.clusterManager().adsMux()) {
387
0
    resume_rds = factory_context_.clusterManager().adsMux()->pause(type_url);
388
0
  }
389
  // if local init manager is initialized, the parent init manager may have gone away.
390
0
  if (localInitManager().state() == Init::Manager::State::Initialized) {
391
0
    srds_init_mgr =
392
0
        std::make_unique<Init::ManagerImpl>(fmt::format("SRDS {}:{}", name_, version_info));
393
0
    srds_initialization_continuation =
394
0
        std::make_unique<Cleanup>([this, &srds_init_mgr, version_info] {
395
          // For new RDS subscriptions created after listener warming up, we don't wait for them to
396
          // warm up.
397
0
          Init::WatcherImpl noop_watcher(
398
              // Note: we just throw it away.
399
0
              fmt::format("SRDS ConfigUpdate watcher {}:{}", name_, version_info),
400
0
              []() { /*Do nothing.*/ });
401
0
          srds_init_mgr->initialize(noop_watcher);
402
0
        });
403
0
  }
404
405
0
  std::string exception_msg;
406
0
  Protobuf::RepeatedPtrField<std::string> clean_removed_resources =
407
0
      detectUpdateConflictAndCleanupRemoved(added_resources, removed_resources, exception_msg);
408
0
  if (!exception_msg.empty()) {
409
0
    return absl::InvalidArgumentError(
410
0
        fmt::format("Error adding/updating scoped route(s): {}", exception_msg));
411
0
  }
412
413
  // Do not delete RDS config providers just yet, in case the to be deleted RDS subscriptions could
414
  // be reused by some to be added scopes.
415
0
  std::list<ScopedRdsConfigSubscription::RdsRouteConfigProviderHelperPtr>
416
0
      to_be_removed_rds_providers = removeScopes(clean_removed_resources, version_info);
417
418
0
  auto status_or_applied = addOrUpdateScopes(
419
0
      added_resources, (srds_init_mgr == nullptr ? localInitManager() : *srds_init_mgr),
420
0
      version_info);
421
0
  if (!status_or_applied.status().ok()) {
422
0
    return status_or_applied.status();
423
0
  }
424
0
  const bool any_applied = status_or_applied.value();
425
0
  const auto status = ConfigSubscriptionCommonBase::onConfigUpdate();
426
0
  if (!status.ok()) {
427
0
    return status;
428
0
  }
429
0
  if (any_applied || !to_be_removed_rds_providers.empty()) {
430
0
    setLastConfigInfo(absl::optional<LastConfigInfo>({absl::nullopt, version_info}));
431
0
  }
432
0
  stats_.all_scopes_.set(scoped_route_map_.size());
433
0
  stats_.config_reload_.inc();
434
0
  stats_.config_reload_time_ms_.set(DateUtil::nowToMilliseconds(factory_context_.timeSource()));
435
0
  return absl::OkStatus();
436
0
}
437
438
void ScopedRdsConfigSubscription::onRdsConfigUpdate(const std::string& scope_name,
439
0
                                                    ConfigConstSharedPtr new_rds_config) {
440
0
  auto iter = scoped_route_map_.find(scope_name);
441
0
  ASSERT(iter != scoped_route_map_.end(),
442
0
         fmt::format("trying to update route config for non-existing scope {}", scope_name));
443
0
  auto new_scoped_route_info = std::make_shared<ScopedRouteInfo>(
444
0
      envoy::config::route::v3::ScopedRouteConfiguration(iter->second->configProto()),
445
0
      std::move(new_rds_config));
446
0
  scoped_route_map_[new_scoped_route_info->scopeName()] = new_scoped_route_info;
447
0
  applyConfigUpdate([new_scoped_route_info](ConfigProvider::ConfigConstSharedPtr config)
448
0
                        -> ConfigProvider::ConfigConstSharedPtr {
449
0
    auto* thread_local_scoped_config =
450
0
        const_cast<ScopedConfigImpl*>(static_cast<const ScopedConfigImpl*>(config.get()));
451
0
    thread_local_scoped_config->addOrUpdateRoutingScopes({new_scoped_route_info});
452
0
    return config;
453
0
  });
454
  // The data plane may wait for the route configuration to come back.
455
0
  route_provider_by_scope_[scope_name]->runOnDemandUpdateCallback();
456
0
}
457
458
// TODO(stevenzzzz): see issue #7508, consider generalizing this function as it overlaps with
459
// CdsApiImpl::onConfigUpdate.
460
absl::Status ScopedRdsConfigSubscription::onConfigUpdate(
461
    const std::vector<Envoy::Config::DecodedResourceRef>& resources,
462
0
    const std::string& version_info) {
463
0
  Protobuf::RepeatedPtrField<std::string> to_remove_repeated;
464
0
  for (const auto& scoped_route : scoped_route_map_) {
465
0
    *to_remove_repeated.Add() = scoped_route.first;
466
0
  }
467
0
  return onConfigUpdate(resources, to_remove_repeated, version_info);
468
0
}
469
470
Protobuf::RepeatedPtrField<std::string>
471
ScopedRdsConfigSubscription::detectUpdateConflictAndCleanupRemoved(
472
    const std::vector<Envoy::Config::DecodedResourceRef>& resources,
473
0
    const Protobuf::RepeatedPtrField<std::string>& removed_resources, std::string& exception_msg) {
474
0
  Protobuf::RepeatedPtrField<std::string> clean_removed_resources;
475
  // All the scope names to be removed or updated.
476
0
  absl::flat_hash_set<std::string> updated_or_removed_scopes;
477
0
  for (const std::string& removed_resource : removed_resources) {
478
0
    updated_or_removed_scopes.insert(removed_resource);
479
0
  }
480
0
  for (const auto& resource : resources) {
481
0
    const auto& scoped_route =
482
0
        dynamic_cast<const envoy::config::route::v3::ScopedRouteConfiguration&>(
483
0
            resource.get().resource());
484
0
    updated_or_removed_scopes.insert(scoped_route.name());
485
0
  }
486
487
0
  absl::flat_hash_map<uint64_t, std::string> scope_name_by_hash = scope_name_by_hash_;
488
0
  absl::erase_if(scope_name_by_hash, [&updated_or_removed_scopes](const auto& key_name) {
489
0
    auto const& [key, name] = key_name;
490
0
    UNREFERENCED_PARAMETER(key);
491
0
    return updated_or_removed_scopes.contains(name);
492
0
  });
493
0
  absl::flat_hash_map<std::string, envoy::config::route::v3::ScopedRouteConfiguration>
494
0
      scoped_routes;
495
0
  for (const auto& resource : resources) {
496
    // Throws (thus rejects all) on any error.
497
0
    const auto& scoped_route =
498
0
        dynamic_cast<const envoy::config::route::v3::ScopedRouteConfiguration&>(
499
0
            resource.get().resource());
500
0
    const std::string& scope_name = scoped_route.name();
501
0
    auto scope_config_inserted = scoped_routes.try_emplace(scope_name, std::move(scoped_route));
502
0
    if (!scope_config_inserted.second) {
503
0
      exception_msg = fmt::format("duplicate scoped route configuration '{}' found", scope_name);
504
0
      return clean_removed_resources;
505
0
    }
506
0
    envoy::config::route::v3::ScopedRouteConfiguration scoped_route_config =
507
0
        scope_config_inserted.first->second;
508
0
    const uint64_t key_fingerprint =
509
0
        ScopedRouteInfo(std::move(scoped_route_config), nullptr).scopeKey().hash();
510
0
    if (!scope_name_by_hash.try_emplace(key_fingerprint, scope_name).second) {
511
0
      exception_msg =
512
0
          fmt::format("scope key conflict found, first scope is '{}', second scope is '{}'",
513
0
                      scope_name_by_hash[key_fingerprint], scope_name);
514
0
      return clean_removed_resources;
515
0
    }
516
0
  }
517
518
  // only remove resources that is not going to be updated.
519
0
  for (const std::string& removed_resource : removed_resources) {
520
0
    if (!scoped_routes.contains(removed_resource)) {
521
0
      *clean_removed_resources.Add() = removed_resource;
522
0
    }
523
0
  }
524
0
  return clean_removed_resources;
525
0
}
526
527
void ScopedRdsConfigSubscription::onDemandRdsUpdate(
528
    std::shared_ptr<Router::ScopeKey> scope_key, Event::Dispatcher& thread_local_dispatcher,
529
    Http::RouteConfigUpdatedCallback&& route_config_updated_cb,
530
0
    std::weak_ptr<Envoy::Config::ConfigSubscriptionCommonBase> weak_subscription) {
531
0
  factory_context_.mainThreadDispatcher().post([this, &thread_local_dispatcher, scope_key,
532
0
                                                route_config_updated_cb, weak_subscription]() {
533
    // If the subscription has been destroyed, return immediately.
534
0
    if (!weak_subscription.lock()) {
535
0
      thread_local_dispatcher.post([route_config_updated_cb] { route_config_updated_cb(false); });
536
0
      return;
537
0
    }
538
539
0
    auto iter = scope_name_by_hash_.find(scope_key->hash());
540
    // Return to filter chain if we can't find the scope.
541
    // The scope may have been destroyed when callback reach the main thread.
542
0
    if (iter == scope_name_by_hash_.end()) {
543
0
      thread_local_dispatcher.post([route_config_updated_cb] { route_config_updated_cb(false); });
544
0
      return;
545
0
    }
546
    // Wrap the thread local dispatcher inside the callback.
547
0
    std::function<void()> thread_local_updated_callback = [route_config_updated_cb,
548
0
                                                           &thread_local_dispatcher]() {
549
0
      thread_local_dispatcher.post([route_config_updated_cb] { route_config_updated_cb(true); });
550
0
    };
551
0
    std::string scope_name = iter->second;
552
    // On demand initialization inside main thread.
553
0
    route_provider_by_scope_[scope_name]->addOnDemandUpdateCallback(thread_local_updated_callback);
554
0
  });
555
0
}
556
557
ScopedRdsConfigProvider::ScopedRdsConfigProvider(
558
    ScopedRdsConfigSubscriptionSharedPtr&& subscription)
559
46
    : MutableConfigProviderCommonBase(std::move(subscription), ConfigProvider::ApiType::Delta) {}
560
561
ProtobufTypes::MessagePtr
562
0
ScopedRoutesConfigProviderManager::dumpConfigs(const Matchers::StringMatcher& name_matcher) const {
563
0
  auto config_dump = std::make_unique<envoy::admin::v3::ScopedRoutesConfigDump>();
564
0
  for (const auto& element : configSubscriptions()) {
565
0
    auto subscription = element.second.lock();
566
0
    ASSERT(subscription);
567
568
0
    if (subscription->configInfo()) {
569
0
      auto* dynamic_config = config_dump->mutable_dynamic_scoped_route_configs()->Add();
570
0
      dynamic_config->set_version_info(subscription->configInfo().value().last_config_version_);
571
0
      const ScopedRdsConfigSubscription* typed_subscription =
572
0
          static_cast<ScopedRdsConfigSubscription*>(subscription.get());
573
0
      dynamic_config->set_name(typed_subscription->name());
574
0
      const ScopedRouteMap& scoped_route_map = typed_subscription->scopedRouteMap();
575
0
      for (const auto& it : scoped_route_map) {
576
0
        if (!name_matcher.match(it.second->configProto().name())) {
577
0
          continue;
578
0
        }
579
0
        dynamic_config->mutable_scoped_route_configs()->Add()->PackFrom(it.second->configProto());
580
0
      }
581
0
      TimestampUtil::systemClockToTimestamp(subscription->lastUpdated(),
582
0
                                            *dynamic_config->mutable_last_updated());
583
0
    }
584
0
  }
585
586
0
  for (const auto& provider : immutableConfigProviders(ConfigProviderInstanceType::Inline)) {
587
0
    const auto protos_info =
588
0
        provider->configProtoInfoVector<envoy::config::route::v3::ScopedRouteConfiguration>();
589
0
    ASSERT(protos_info != absl::nullopt);
590
0
    auto* inline_config = config_dump->mutable_inline_scoped_route_configs()->Add();
591
0
    inline_config->set_name(static_cast<InlineScopedRoutesConfigProvider*>(provider)->name());
592
0
    for (const auto& config_proto : protos_info.value().config_protos_) {
593
0
      if (!name_matcher.match(config_proto->name())) {
594
0
        continue;
595
0
      }
596
0
      inline_config->mutable_scoped_route_configs()->Add()->PackFrom(*config_proto);
597
0
    }
598
0
    TimestampUtil::systemClockToTimestamp(provider->lastUpdated(),
599
0
                                          *inline_config->mutable_last_updated());
600
0
  }
601
602
0
  return config_dump;
603
0
}
604
605
ConfigProviderPtr ScopedRoutesConfigProviderManager::createXdsConfigProvider(
606
    const Protobuf::Message& config_source_proto,
607
    Server::Configuration::ServerFactoryContext& factory_context, Init::Manager& init_manager,
608
47
    const std::string& stat_prefix, const ConfigProviderManager::OptionalArg& optarg) {
609
47
  const auto& typed_optarg = static_cast<const ScopedRoutesConfigProviderManagerOptArg&>(optarg);
610
47
  ScopedRdsConfigSubscriptionSharedPtr subscription =
611
47
      ConfigProviderManagerImplBase::getSubscription<ScopedRdsConfigSubscription>(
612
47
          config_source_proto, init_manager,
613
47
          [&config_source_proto, &factory_context, &stat_prefix,
614
47
           &typed_optarg](const uint64_t manager_identifier,
615
47
                          ConfigProviderManagerImplBase& config_provider_manager)
616
47
              -> Envoy::Config::ConfigSubscriptionCommonBaseSharedPtr {
617
44
            const auto& scoped_rds_config_source = dynamic_cast<
618
44
                const envoy::extensions::filters::network::http_connection_manager::v3::ScopedRds&>(
619
44
                config_source_proto);
620
44
            return std::make_shared<ScopedRdsConfigSubscription>(
621
44
                scoped_rds_config_source, manager_identifier, typed_optarg.scoped_routes_name_,
622
44
                factory_context, stat_prefix, typed_optarg.rds_config_source_,
623
44
                static_cast<ScopedRoutesConfigProviderManager&>(config_provider_manager)
624
44
                    .routeConfigProviderManager(),
625
44
                static_cast<ScopedRoutesConfigProviderManager&>(config_provider_manager));
626
44
          });
627
628
47
  return std::make_unique<ScopedRdsConfigProvider>(std::move(subscription));
629
47
}
630
631
ConfigProviderPtr ScopedRoutesConfigProviderManager::createStaticConfigProvider(
632
    ProtobufTypes::ConstMessagePtrVector&& config_protos,
633
    Server::Configuration::ServerFactoryContext& factory_context,
634
223
    const ConfigProviderManager::OptionalArg& optarg) {
635
223
  const auto& typed_optarg = static_cast<const ScopedRoutesConfigProviderManagerOptArg&>(optarg);
636
223
  return std::make_unique<InlineScopedRoutesConfigProvider>(
637
223
      std::move(config_protos), typed_optarg.scoped_routes_name_, factory_context, *this,
638
223
      typed_optarg.rds_config_source_);
639
223
}
640
641
REGISTER_FACTORY(SrdsFactoryDefault, SrdsFactory);
642
643
} // namespace Router
644
} // namespace Envoy