Skip to content
Merged
1 change: 1 addition & 0 deletions include/envoy/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ envoy_cc_library(
external_deps = ["abseil_optional"],
deps = [
"//include/envoy/common:time_interface",
"//source/common/common:assert_lib",
"//source/common/protobuf",
],
)
Expand Down
68 changes: 65 additions & 3 deletions include/envoy/config/config_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "envoy/common/time.h"

#include "common/common/assert.h"
#include "common/protobuf/protobuf.h"

#include "absl/types/optional.h"
Expand Down Expand Up @@ -41,6 +42,23 @@ class ConfigProvider {
};
using ConfigConstSharedPtr = std::shared_ptr<const Config>;

/**
* The type of API represented by a ConfigProvider.
*/
enum class ApiType {
/**
* A "Full" API delivers a complete configuration as part of each resource (top level
* config proto); i.e., each resource contains the whole representation of the config intent. An
* example of this type of API is RDS.
*/
Full,
/**
* A "Delta" API delivers a subset of the config intent as part of each resource (top level
* config proto). Examples of this type of API are CDS, LDS and SRDS.
*/
Delta
};

/**
* Stores the config proto as well as the associated version.
*/
Expand All @@ -51,10 +69,26 @@ class ConfigProvider {
std::string version_;
};

using ConfigProtoVector = std::vector<const Protobuf::Message*>;
/**
* Stores the config protos associated with a "Delta" API.
*/
template <typename P> struct ConfigProtoInfoVector {
const std::vector<const P*> config_protos_;

// Only populated by dynamic config providers.
std::string version_;
};

virtual ~ConfigProvider() = default;

/**
* Returns a ConfigProtoInfo associated with the provider.
* The type of API.
*/
virtual ApiType apiType() const PURE;

/**
* Returns a ConfigProtoInfo associated with a ApiType::Full provider.
* @return absl::optional<ConfigProtoInfo<P>> an optional ConfigProtoInfo; the value is set when a
* config is available.
*/
Expand All @@ -69,6 +103,27 @@ class ConfigProvider {
return ConfigProtoInfo<P>{*config_proto, getConfigVersion()};
}

/**
* Returns a ConfigProtoInfoVector associated with a ApiType::Delta provider.
* @return absl::optional<ConfigProtoInfoVector> an optional ConfigProtoInfoVector; the value is
* set when a config is available.
*/
template <typename P> absl::optional<ConfigProtoInfoVector<P>> configProtoInfoVector() const {
static_assert(std::is_base_of<Protobuf::Message, P>::value,
"Proto type must derive from Protobuf::Message");

const ConfigProtoVector config_protos = getConfigProtos();
if (config_protos.empty()) {
return absl::nullopt;
}
std::vector<const P*> ret_protos;
ret_protos.reserve(config_protos.size());
for (const auto* elem : config_protos) {
ret_protos.push_back(static_cast<const P*>(elem));
}
return ConfigProtoInfoVector<P>{ret_protos, getConfigVersion()};
}

/**
* Returns the Config corresponding to the provider.
* @return std::shared_ptr<const C> a shared pointer to the Config.
Expand All @@ -92,13 +147,20 @@ class ConfigProvider {
* @return Protobuf::Message* the config proto corresponding to the Config instantiated by the
* provider.
*/
virtual const Protobuf::Message* getConfigProto() const PURE;
virtual const Protobuf::Message* getConfigProto() const { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }

/**
* Returns the config protos associated with the provider.
* @return const ConfigProtoVector the config protos corresponding to the Config instantiated by
* the provider.
*/
virtual ConfigProtoVector getConfigProtos() const { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }

/**
* Returns the config version associated with the provider.
* @return std::string the config version.
*/
virtual std::string getConfigVersion() const PURE;
virtual std::string getConfigVersion() const { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }

/**
* Returns the config implementation associated with the provider.
Expand Down
41 changes: 39 additions & 2 deletions include/envoy/config/config_provider_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ namespace Config {
*/
class ConfigProviderManager {
public:
class OptionalArg {
public:
virtual ~OptionalArg() = default;
};

class NullOptionalArg : public OptionalArg {
public:
NullOptionalArg() = default;
~NullOptionalArg() override = default;
};

virtual ~ConfigProviderManager() = default;

/**
Expand All @@ -34,24 +45,50 @@ class ConfigProviderManager {
* @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.
* @param optarg supplies an optional argument with data specific to the concrete class.
* @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,
Server::Configuration::FactoryContext& factory_context,
const std::string& stat_prefix) PURE;
const std::string& stat_prefix, const OptionalArg& optarg) 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.
* @param optarg supplies an optional argument with data specific to the concrete class.
* @return ConfigProviderPtr a newly allocated static config provider.
*/
virtual ConfigProviderPtr
createStaticConfigProvider(const Protobuf::Message& config_proto,
Server::Configuration::FactoryContext& factory_context) PURE;
Server::Configuration::FactoryContext& factory_context,
const OptionalArg& optarg) {
UNREFERENCED_PARAMETER(config_proto);
UNREFERENCED_PARAMETER(factory_context);
UNREFERENCED_PARAMETER(optarg);
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
}

/**
* Returns a ConfigProvider associated with a statically specified configuration. This is intended
* to be used when a set of configuration protos is required to build the full configuration.
* @param config_protos supplies a vector of configuration protos.
* @param factory_context is the context to use for the provider.
* @param optarg supplies an optional argument with data specific to the concrete class.
* @return ConfigProviderPtr a newly allocated static config provider.
*/
virtual ConfigProviderPtr
createStaticConfigProvider(std::vector<std::unique_ptr<const Protobuf::Message>>&& config_protos,
Server::Configuration::FactoryContext& factory_context,
const OptionalArg& optarg) {
UNREFERENCED_PARAMETER(config_protos);
UNREFERENCED_PARAMETER(factory_context);
UNREFERENCED_PARAMETER(optarg);
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
}
};

} // namespace Config
Expand Down
100 changes: 65 additions & 35 deletions source/common/config/config_provider_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,51 @@
namespace Envoy {
namespace Config {

ImmutableConfigProviderImplBase::ImmutableConfigProviderImplBase(
ImmutableConfigProviderBase::ImmutableConfigProviderBase(
Server::Configuration::FactoryContext& factory_context,
ConfigProviderManagerImplBase& config_provider_manager, ConfigProviderInstanceType type)
ConfigProviderManagerImplBase& config_provider_manager,
ConfigProviderInstanceType instance_type, ApiType api_type)
: last_updated_(factory_context.timeSource().systemTime()),
config_provider_manager_(config_provider_manager), type_(type) {
config_provider_manager_(config_provider_manager), instance_type_(instance_type),
api_type_(api_type) {
config_provider_manager_.bindImmutableConfigProvider(this);
}

ImmutableConfigProviderImplBase::~ImmutableConfigProviderImplBase() {
ImmutableConfigProviderBase::~ImmutableConfigProviderBase() {
config_provider_manager_.unbindImmutableConfigProvider(this);
}

ConfigSubscriptionInstanceBase::~ConfigSubscriptionInstanceBase() {
ConfigSubscriptionCommonBase::~ConfigSubscriptionCommonBase() {
init_target_.ready();
config_provider_manager_.unbindSubscription(manager_identifier_);
}

bool ConfigSubscriptionInstanceBase::checkAndApplyConfig(const Protobuf::Message& config_proto,
const std::string& config_name,
const std::string& version_info) {
void ConfigSubscriptionCommonBase::bindConfigProvider(MutableConfigProviderCommonBase* provider) {
// All config providers bound to a ConfigSubscriptionCommonBase must be of the same concrete
// type; this is assumed by ConfigSubscriptionInstance::checkAndApplyConfigUpdate() and is
// verified by the assertion below. NOTE: an inlined statement ASSERT() triggers a potentially
// evaluated expression warning from 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);
}

bool ConfigSubscriptionInstance::checkAndApplyConfigUpdate(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;
if (config_info_) {
ASSERT(config_info_.value().last_config_hash_.has_value());
if (config_info_.value().last_config_hash_.value() == new_hash) {
return false;
}
}

config_info_ = {new_hash, version_info};
Expand All @@ -39,31 +61,39 @@ bool ConfigSubscriptionInstanceBase::checkAndApplyConfig(const Protobuf::Message
// 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.
auto* typed_provider = static_cast<MutableConfigProviderBase*>(provider);
if (new_config == nullptr) {
if ((new_config = provider->onConfigProtoUpdate(config_proto)) == nullptr) {
if ((new_config = typed_provider->onConfigProtoUpdate(config_proto)) == nullptr) {
return false;
}
}
provider->onConfigUpdate(new_config);
typed_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
// 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);
void DeltaConfigSubscriptionInstance::applyDeltaConfigUpdate(
const std::function<void(const ConfigSharedPtr&)>& update_fn) {
// The Config implementation is assumed to be shared across the config providers bound to this
// subscription, therefore, simply propagating the update to all worker threads for a single bound
// provider will be sufficient.
if (mutable_config_providers_.size() > 1) {
ASSERT(static_cast<DeltaMutableConfigProviderBase*>(*mutable_config_providers_.begin())
->getConfig() == static_cast<DeltaMutableConfigProviderBase*>(
*std::next(mutable_config_providers_.begin()))
->getConfig());
}

// TODO(AndresGuedez): currently, the caller has to compute the differences in resources between
// DS API config updates and passes a granular update_fn() that adds/modifies/removes resources as
// needed. Such logic could be generalized as part of this framework such that this function owns
// the diffing and issues the corresponding call to add/modify/remove a resource according to a
// vector of functions passed by the caller.
auto* typed_provider =
static_cast<DeltaMutableConfigProviderBase*>(getAnyBoundMutableConfigProvider());
ConfigSharedPtr config = typed_provider->getConfig();
typed_provider->onConfigUpdate([config, update_fn]() { update_fn(config); });
}

ConfigProviderManagerImplBase::ConfigProviderManagerImplBase(Server::Admin& admin,
Expand All @@ -87,25 +117,25 @@ ConfigProviderManagerImplBase::immutableConfigProviders(ConfigProviderInstanceTy
}

void ConfigProviderManagerImplBase::bindImmutableConfigProvider(
ImmutableConfigProviderImplBase* provider) {
ASSERT(provider->type() == ConfigProviderInstanceType::Static ||
provider->type() == ConfigProviderInstanceType::Inline);
ImmutableConfigProviderBase* provider) {
ASSERT(provider->instanceType() == ConfigProviderInstanceType::Static ||
provider->instanceType() == ConfigProviderInstanceType::Inline);
ConfigProviderMap::iterator it;
if ((it = immutable_config_providers_map_.find(provider->type())) ==
if ((it = immutable_config_providers_map_.find(provider->instanceType())) ==
immutable_config_providers_map_.end()) {
immutable_config_providers_map_.insert(std::make_pair(
provider->type(),
provider->instanceType(),
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());
ImmutableConfigProviderBase* provider) {
ASSERT(provider->instanceType() == ConfigProviderInstanceType::Static ||
provider->instanceType() == ConfigProviderInstanceType::Inline);
auto it = immutable_config_providers_map_.find(provider->instanceType());
ASSERT(it != immutable_config_providers_map_.end());
it->second->erase(provider);
}
Expand Down
Loading