-
Notifications
You must be signed in to change notification settings - Fork 5.3k
config: Scoped RDS (PR 1/4): Introduce {static,dynamic} config provider framework #5243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6c744cd
1bccfaf
66e4c45
02b5e97
09a0846
acf19cf
c0366a7
4b1bef4
550723c
2c79e9c
854c2f2
e67b5ee
821aab9
ca583a0
15c9d19
f69eb74
ba44ad9
142d9c1
5c71955
7441143
a36a3e8
bb34fdd
ccc244a
6d7cba8
9cf7f27
58ac876
bb990fa
b49127f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "envoy/common/time.h" | ||
|
|
||
| #include "common/protobuf/protobuf.h" | ||
|
|
||
| #include "absl/types/optional.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Config { | ||
|
|
||
| /** | ||
| * A provider for configuration obtained statically (via static resources in the bootstrap config), | ||
| * inline with a higher level resource or dynamically via xDS APIs. | ||
| * | ||
| * The ConfigProvider is an abstraction layer which higher level components such as the | ||
| * HttpConnectionManager, Listener, etc can leverage to interface with Envoy's configuration | ||
| * mechanisms. Implementations of this interface build upon lower level abstractions such as | ||
| * Envoy::Config::Subscription and Envoy::Config::SubscriptionCallbacks. | ||
| * | ||
| * The interface exposed below allows xDS providers to share the underlying config protos and | ||
| * resulting config implementations (i.e., the ConfigProvider::Config); this enables linear memory | ||
| * scaling based on the size of the configuration set, regardless of the number of threads/workers. | ||
| * | ||
| * Use config() to obtain a shared_ptr to the implementation of the config, and configProtoInfo() to | ||
| * obtain a reference to the underlying config proto and version (applicable only to dynamic config | ||
| * providers). | ||
| */ | ||
| class ConfigProvider { | ||
| public: | ||
| /** | ||
| * The "implementation" of the configuration. | ||
| * Use config() to obtain a typed object that corresponds to the specific configuration | ||
| * represented by this abstract type. | ||
| */ | ||
| class Config { | ||
| public: | ||
| virtual ~Config() = default; | ||
| }; | ||
| using ConfigConstSharedPtr = std::shared_ptr<const Config>; | ||
|
|
||
| /** | ||
| * Stores the config proto as well as the associated version. | ||
| */ | ||
| template <typename P> struct ConfigProtoInfo { | ||
| const P& config_proto_; | ||
|
|
||
| // Only populated by dynamic config providers. | ||
| std::string version_; | ||
| }; | ||
|
|
||
| virtual ~ConfigProvider() = default; | ||
|
|
||
| /** | ||
| * Returns a ConfigProtoInfo associated with the provider. | ||
| * @return absl::optional<ConfigProtoInfo<P>> an optional ConfigProtoInfo; the value is set when a | ||
| * config is available. | ||
| */ | ||
| template <typename P> absl::optional<ConfigProtoInfo<P>> configProtoInfo() const { | ||
| static_assert(std::is_base_of<Protobuf::Message, P>::value, | ||
| "Proto type must derive from Protobuf::Message"); | ||
|
|
||
| const auto* config_proto = dynamic_cast<const P*>(getConfigProto()); | ||
| if (config_proto == nullptr) { | ||
| return absl::nullopt; | ||
| } | ||
| return ConfigProtoInfo<P>{*config_proto, getConfigVersion()}; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the Config corresponding to the provider. | ||
| * @return std::shared_ptr<const C> a shared pointer to the Config. | ||
| */ | ||
| template <typename C> std::shared_ptr<const C> config() const { | ||
AndresGuedez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| static_assert(std::is_base_of<Config, C>::value, | ||
| "Config type must derive from ConfigProvider::Config"); | ||
|
|
||
| return std::dynamic_pointer_cast<const C>(getConfig()); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the timestamp associated with the last update to the Config. | ||
| * @return SystemTime the timestamp corresponding to the last config update. | ||
| */ | ||
| virtual SystemTime lastUpdated() const PURE; | ||
|
|
||
| protected: | ||
| /** | ||
| * Returns the config proto associated with the provider. | ||
| * @return Protobuf::Message* the config proto corresponding to the Config instantiated by the | ||
| * provider. | ||
| */ | ||
| virtual const Protobuf::Message* getConfigProto() const PURE; | ||
|
|
||
| /** | ||
| * Returns the config version associated with the provider. | ||
| * @return std::string the config version. | ||
| */ | ||
| virtual std::string getConfigVersion() const PURE; | ||
|
|
||
| /** | ||
| * Returns the config implementation associated with the provider. | ||
| * @return ConfigConstSharedPtr the config as the base type. | ||
| */ | ||
| virtual ConfigConstSharedPtr getConfig() const PURE; | ||
| }; | ||
|
|
||
| using ConfigProviderPtr = std::unique_ptr<ConfigProvider>; | ||
|
|
||
| } // namespace Config | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #pragma once | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "envoy/config/config_provider.h" | ||
| #include "envoy/server/filter_config.h" | ||
|
|
||
| #include "common/protobuf/protobuf.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Config { | ||
|
|
||
| /** | ||
| * A ConfigProvider manager which instantiates static and dynamic (xDS) providers. | ||
lizan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * ConfigProvider objects are owned by the caller of the | ||
| * createXdsConfigProvider()/createStaticConfigProvider() functions. The ConfigProviderManager holds | ||
| * raw pointers to those objects. | ||
AndresGuedez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * Configuration implementations returned by ConfigProvider::config() are immutable, which allows | ||
| * them to share the underlying objects such as config protos and subscriptions (for dynamic | ||
| * providers) without synchronization related performance penalties. This enables linear memory | ||
| * growth based on the size of the configuration set, regardless of the number of threads/objects | ||
| * that must hold a reference/pointer to them. | ||
| */ | ||
| class ConfigProviderManager { | ||
| public: | ||
| virtual ~ConfigProviderManager() = default; | ||
|
|
||
| /** | ||
| * Returns a dynamic ConfigProvider which receives configuration via an xDS API. | ||
| * A shared ownership model is used, such that the underlying subscription, config proto | ||
| * and Config are shared amongst all providers relying on the same config source. | ||
| * @param config_source_proto supplies the proto containing the xDS API configuration. | ||
| * @param factory_context is the context to use for the provider. | ||
| * @param stat_prefix supplies the prefix to use for statistics. | ||
| * @return ConfigProviderPtr a newly allocated dynamic config provider which shares underlying | ||
| * data structures with other dynamic providers configured with the same | ||
| * API source. | ||
| */ | ||
| virtual ConfigProviderPtr | ||
| createXdsConfigProvider(const Protobuf::Message& config_source_proto, | ||
AndresGuedez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Server::Configuration::FactoryContext& factory_context, | ||
| const std::string& stat_prefix) PURE; | ||
|
|
||
| /** | ||
| * Returns a ConfigProvider associated with a statically specified configuration. | ||
| * @param config_proto supplies the configuration proto. | ||
| * @param factory_context is the context to use for the provider. | ||
| * @return ConfigProviderPtr a newly allocated static config provider. | ||
| */ | ||
| virtual ConfigProviderPtr | ||
| createStaticConfigProvider(const Protobuf::Message& config_proto, | ||
| Server::Configuration::FactoryContext& factory_context) PURE; | ||
| }; | ||
|
|
||
| } // namespace Config | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| #include "common/config/config_provider_impl.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Config { | ||
|
|
||
| ImmutableConfigProviderImplBase::ImmutableConfigProviderImplBase( | ||
| Server::Configuration::FactoryContext& factory_context, | ||
| ConfigProviderManagerImplBase& config_provider_manager, ConfigProviderInstanceType type) | ||
| : last_updated_(factory_context.timeSource().systemTime()), | ||
| config_provider_manager_(config_provider_manager), type_(type) { | ||
| config_provider_manager_.bindImmutableConfigProvider(this); | ||
| } | ||
|
|
||
| ImmutableConfigProviderImplBase::~ImmutableConfigProviderImplBase() { | ||
| config_provider_manager_.unbindImmutableConfigProvider(this); | ||
| } | ||
|
|
||
| ConfigSubscriptionInstanceBase::~ConfigSubscriptionInstanceBase() { | ||
| runInitializeCallbackIfAny(); | ||
| config_provider_manager_.unbindSubscription(manager_identifier_); | ||
| } | ||
|
|
||
| void ConfigSubscriptionInstanceBase::runInitializeCallbackIfAny() { | ||
| if (initialize_callback_) { | ||
| initialize_callback_(); | ||
| initialize_callback_ = nullptr; | ||
| } | ||
| } | ||
|
|
||
| bool ConfigSubscriptionInstanceBase::checkAndApplyConfig(const Protobuf::Message& config_proto, | ||
| const std::string& config_name, | ||
| const std::string& version_info) { | ||
| const uint64_t new_hash = MessageUtil::hash(config_proto); | ||
| if (config_info_ && config_info_.value().last_config_hash_ == new_hash) { | ||
| return false; | ||
| } | ||
|
|
||
| config_info_ = {new_hash, version_info}; | ||
| ENVOY_LOG(debug, "{}: loading new configuration: config_name={} hash={}", name_, config_name, | ||
| new_hash); | ||
|
|
||
| ASSERT(!mutable_config_providers_.empty()); | ||
| ConfigProvider::ConfigConstSharedPtr new_config; | ||
| for (auto* provider : mutable_config_providers_) { | ||
| // All bound mutable config providers must be of the same type (see the ASSERT... in | ||
| // bindConfigProvider()). | ||
| // This makes it safe to call any of the provider's onConfigProtoUpdate() to get a new config | ||
| // impl, which can then be passed to all providers. | ||
| if (new_config == nullptr) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop looks wrong based on a quick glance; the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These dynamic providers are all instances of the same class, and they share the underlying ConfigSubscriptionInstance on which this function is taking effect. I will add the comments and assert()s.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments and an ASSERT to bindConfigProvider(). The assertion is tricky due to the lack of concrete type information in these functions, so PTAL and let me know your thoughts on my approach using RTTI. |
||
| if ((new_config = provider->onConfigProtoUpdate(config_proto)) == nullptr) { | ||
| return false; | ||
| } | ||
| } | ||
| provider->onConfigUpdate(new_config); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| void ConfigSubscriptionInstanceBase::bindConfigProvider(MutableConfigProviderImplBase* provider) { | ||
| // All config providers bound to a ConfigSubscriptionInstanceBase must be of the same concrete | ||
| // type; this is assumed by checkAndApplyConfig() and is verified by the assertion below. | ||
| // NOTE: an inlined statement ASSERT() triggers a potentially evaluated expression warning from | ||
lizan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // clang due to `typeid(**mutable_config_providers_.begin())`. To avoid this, we use a lambda to | ||
| // separate the first mutable provider dereference from the typeid() statement. | ||
| ASSERT([&]() { | ||
| if (!mutable_config_providers_.empty()) { | ||
| const auto& first_provider = **mutable_config_providers_.begin(); | ||
| return typeid(*provider) == typeid(first_provider); | ||
| } | ||
| return true; | ||
| }()); | ||
| mutable_config_providers_.insert(provider); | ||
| } | ||
|
|
||
| ConfigProviderManagerImplBase::ConfigProviderManagerImplBase(Server::Admin& admin, | ||
| const std::string& config_name) { | ||
| config_tracker_entry_ = | ||
| admin.getConfigTracker().add(config_name, [this] { return dumpConfigs(); }); | ||
| // ConfigTracker keys must be unique. We are asserting that no one has stolen the key | ||
| // from us, since the returned entry will be nullptr if the key already exists. | ||
| RELEASE_ASSERT(config_tracker_entry_, ""); | ||
| } | ||
|
|
||
| const ConfigProviderManagerImplBase::ConfigProviderSet& | ||
| ConfigProviderManagerImplBase::immutableConfigProviders(ConfigProviderInstanceType type) const { | ||
| static ConfigProviderSet empty_set; | ||
| ConfigProviderMap::const_iterator it; | ||
| if ((it = immutable_config_providers_map_.find(type)) == immutable_config_providers_map_.end()) { | ||
| return empty_set; | ||
| } | ||
|
|
||
| return *it->second; | ||
| } | ||
|
|
||
| void ConfigProviderManagerImplBase::bindImmutableConfigProvider( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, the new naming scheme is also a bit confusing, since the config object that is provided is always immutable, but the fact that a new config can be swapped in is being assigned the mutability characteristic. IDK if it's worth bike shedding further on this, but just raising this as a potential reader confusion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added to the file level comment more details regarding the mutability property and how it applies in this context. I am open to considering other names but this is the best I have come up with given the constraints pointed out by @lizan related to naming conventions already in place for the concrete classes. |
||
| ImmutableConfigProviderImplBase* provider) { | ||
| ASSERT(provider->type() == ConfigProviderInstanceType::Static || | ||
| provider->type() == ConfigProviderInstanceType::Inline); | ||
| ConfigProviderMap::iterator it; | ||
| if ((it = immutable_config_providers_map_.find(provider->type())) == | ||
| immutable_config_providers_map_.end()) { | ||
| immutable_config_providers_map_.insert(std::make_pair( | ||
| provider->type(), | ||
| std::make_unique<ConfigProviderSet>(std::initializer_list<ConfigProvider*>({provider})))); | ||
| } else { | ||
| it->second->insert(provider); | ||
| } | ||
| } | ||
|
|
||
| void ConfigProviderManagerImplBase::unbindImmutableConfigProvider( | ||
| ImmutableConfigProviderImplBase* provider) { | ||
| ASSERT(provider->type() == ConfigProviderInstanceType::Static || | ||
| provider->type() == ConfigProviderInstanceType::Inline); | ||
| auto it = immutable_config_providers_map_.find(provider->type()); | ||
| ASSERT(it != immutable_config_providers_map_.end()); | ||
| it->second->erase(provider); | ||
| } | ||
|
|
||
| } // namespace Config | ||
| } // namespace Envoy | ||
Uh oh!
There was an error while loading. Please reload this page.