Skip to content

Commit

Permalink
config: Honor transport_api_version for non-ADS xDS services (envoypr…
Browse files Browse the repository at this point in the history
…oxy#11788)

This patch makes sure the chosen endpoint is controlled by the specified transport_api_version. For transport_api_version that equals envoy::config::core::v3::ApiVersion::AUTO, the endpoint will be defaulted to envoy::config::core::v3::ApiVersion::V2.

Risk Level: Low
Testing: Unit.
Docs Changes: N/A
Release Notes: N/A (no behavior change)
Fix envoyproxy#10650

Signed-off-by: Dhi Aurrahman <dio@tetrate.io>
Signed-off-by: scheler <santosh.cheler@appdynamics.com>
  • Loading branch information
dio authored and scheler committed Aug 4, 2020
1 parent 0254095 commit f678cac
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 83 deletions.
13 changes: 8 additions & 5 deletions source/common/config/subscription_factory_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource(
return std::make_unique<HttpSubscriptionImpl>(
local_info_, cm_, api_config_source.cluster_names()[0], dispatcher_, random_,
Utility::apiConfigSourceRefreshDelay(api_config_source),
Utility::apiConfigSourceRequestTimeout(api_config_source), restMethod(type_url), type_url,
Utility::apiConfigSourceRequestTimeout(api_config_source),
restMethod(type_url, api_config_source.transport_api_version()), type_url,
api_config_source.transport_api_version(), callbacks, resource_decoder, stats,
Utility::configSourceInitialFetchTimeout(config), validation_visitor_);
case envoy::config::core::v3::ApiConfigSource::GRPC:
Expand All @@ -69,8 +70,9 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource(
Utility::factoryForGrpcApiConfigSource(cm_.grpcAsyncClientManager(),
api_config_source, scope, true)
->create(),
dispatcher_, sotwGrpcMethod(type_url), api_config_source.transport_api_version(),
random_, scope, Utility::parseRateLimitSettings(api_config_source),
dispatcher_, sotwGrpcMethod(type_url, api_config_source.transport_api_version()),
api_config_source.transport_api_version(), random_, scope,
Utility::parseRateLimitSettings(api_config_source),
api_config_source.set_node_on_first_message_only()),
callbacks, resource_decoder, stats, type_url, dispatcher_,
Utility::configSourceInitialFetchTimeout(config),
Expand All @@ -81,8 +83,9 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource(
Config::Utility::factoryForGrpcApiConfigSource(cm_.grpcAsyncClientManager(),
api_config_source, scope, true)
->create(),
dispatcher_, deltaGrpcMethod(type_url), api_config_source.transport_api_version(),
random_, scope, Utility::parseRateLimitSettings(api_config_source), local_info_),
dispatcher_, deltaGrpcMethod(type_url, api_config_source.transport_api_version()),
api_config_source.transport_api_version(), random_, scope,
Utility::parseRateLimitSettings(api_config_source), local_info_),
callbacks, resource_decoder, stats, type_url, dispatcher_,
Utility::configSourceInitialFetchTimeout(config), false);
}
Expand Down
286 changes: 221 additions & 65 deletions source/common/config/type_to_endpoint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,96 +6,252 @@

// API_NO_BOOST_FILE

#define SERVICE_VERSION_INFO(v2, v3) \
createServiceVersionInfoMap(v2, {v2, v3}), createServiceVersionInfoMap(v3, {v2, v3})

namespace Envoy {
namespace Config {

namespace {

// service RPC method fully qualified names.
struct Service {
std::string sotw_grpc_method_;
std::string delta_grpc_method_;
std::string rest_method_;
// A service's name, e.g. "envoy.api.v2.RouteDiscoveryService",
// "envoy.service.route.v3.RouteDiscoveryService".
using ServiceName = std::string;

struct ServiceVersionInfo {
// This hold a name for each transport_api_version, for example for
// "envoy.api.v2.RouteDiscoveryService":
// {
// "V2": "envoy.api.v2.RouteDiscoveryService",
// "V3": "envoy.service.route.v3.RouteDiscoveryService"
// }
absl::flat_hash_map<envoy::config::core::v3::ApiVersion, ServiceName> names_;
};

// A ServiceVersionInfoMap holds a service's transport_api_version and possible names for each
// available transport_api_version. For examples:
//
// Given "envoy.api.v2.RouteDiscoveryService" as the service name:
// {
// "envoy.api.v2.RouteDiscoveryService": {
// "names_": {
// "V2": "envoy.api.v2.RouteDiscoveryService",
// "V3": "envoy.service.route.v3.RouteDiscoveryService"
// }
// }
// }
//
// And for "envoy.service.route.v3.RouteDiscoveryService":
// {
// "envoy.service.route.v3.RouteDiscoveryService":
// "names_": {
// "V2": "envoy.api.v2.RouteDiscoveryService",
// "V3": "envoy.service.route.v3.RouteDiscoveryService"
// }
// }
// }
using ServiceVersionInfoMap = absl::flat_hash_map<ServiceName, ServiceVersionInfo>;

// This creates a ServiceVersionInfoMap, with service name (For example:
// "envoy.api.v2.RouteDiscoveryService") as the key.
ServiceVersionInfoMap
createServiceVersionInfoMap(absl::string_view service_name,
const std::array<std::string, 2>& versioned_service_names) {
const auto key = static_cast<ServiceName>(service_name);
return ServiceVersionInfoMap{{
// ServiceName as the key.
key,

// ServiceVersionInfo as the value.
ServiceVersionInfo{{
{envoy::config::core::v3::ApiVersion::V2, versioned_service_names[0]},
{envoy::config::core::v3::ApiVersion::V3, versioned_service_names[1]},
}},
}};
}

// A resource type URL. For example: "type.googleapis.com/envoy.api.v2.RouteConfiguration".
using TypeUrl = std::string;

TypeUrl getResourceTypeUrl(absl::string_view service_name) {
const auto* service_desc = Protobuf::DescriptorPool::generated_pool()->FindServiceByName(
static_cast<ServiceName>(service_name));
ASSERT(service_desc != nullptr, fmt::format("{} missing", service_name));
ASSERT(service_desc->options().HasExtension(envoy::annotations::resource));

return Grpc::Common::typeUrl(
service_desc->options().GetExtension(envoy::annotations::resource).type());
}

// A method name, e.g. "envoy.api.v2.RouteDiscoveryService.StreamRoutes".
using MethodName = std::string;

struct VersionedDiscoveryType {
// A map of transport_api_version to discovery service RPC method fully qualified names. e.g.
// {
// "V2": "envoy.api.v2.RouteDiscoveryService.StreamRoutes",
// "V3": "envoy.service.route.v3.RouteDiscoveryService.StreamRoutes"
// }
absl::flat_hash_map<envoy::config::core::v3::ApiVersion, MethodName> methods_;
};

// This holds versioned discovery types.
struct VersionedService {
VersionedDiscoveryType sotw_grpc_;
VersionedDiscoveryType delta_grpc_;
VersionedDiscoveryType rest_;
};

// Map from resource type URL to service RPC methods.
using TypeUrlToServiceMap = std::unordered_map<std::string, Service>;
using TypeUrlToVersionedServiceMap = absl::flat_hash_map<TypeUrl, VersionedService>;

// buildTypeUrlToServiceMap() builds a reverse map from a resource type URLs to a versioned service
// (by transport_api_version).
//
// The way we build it is by firstly constructing a list of ServiceVersionInfoMap:
// [
// {
// "envoy.api.v2.RouteDiscoveryService": {
// "names_": {
// "V2": "envoy.api.v2.RouteDiscoveryService",
// "V3": "envoy.service.route.v3.RouteDiscoveryService"
// }
// }
// },
// {
// "envoy.service.route.v3.RouteDiscoveryService": {
// "names_": {
// "V2": "envoy.api.v2.RouteDiscoveryService",
// "V3": "envoy.service.route.v3.RouteDiscoveryService"
// }
// }
// }
// ...
// ]
//
// Then we convert it into the following map, with the inferred resource type URL as the key:
//
// {
// "type.googleapis.com/envoy.api.v2.RouteConfiguration": {
// "sotw_grpc_": {
// "methods_": {
// "V2": "envoy.api.v2.RouteDiscoveryService.StreamRoutes",
// "V3": "envoy.service.route.v3.RouteDiscoveryService.StreamRoutes"
// }
// },
// ...
// },
// "type.googleapis.com/envoy.config.route.v3.RouteConfiguration": {
// "sotw_grpc_": {
// "methods_": {
// "V2": "envoy.api.v2.RouteDiscoveryService.StreamRoutes",
// "V3": "envoy.service.route.v3.RouteDiscoveryService.StreamRoutes"
// }
// },
// ...
// }
// }
//
TypeUrlToVersionedServiceMap* buildTypeUrlToServiceMap() {
auto* type_url_to_versioned_service_map = new TypeUrlToVersionedServiceMap();

TypeUrlToServiceMap* buildTypeUrlToServiceMap() {
auto* type_url_to_service_map = new TypeUrlToServiceMap();
// This happens once in the lifetime of Envoy. We build a reverse map from resource type URL to
// service methods. We explicitly enumerate all services, since DescriptorPool doesn't support
// iterating over all descriptors, due its lazy load design, see
// https://www.mail-archive.com/protobuf@googlegroups.com/msg04540.html.
for (const std::string& service_name : {
"envoy.api.v2.RouteDiscoveryService",
"envoy.service.route.v3.RouteDiscoveryService",
"envoy.api.v2.ScopedRoutesDiscoveryService",
"envoy.service.route.v3.ScopedRoutesDiscoveryService",
"envoy.api.v2.VirtualHostDiscoveryService",
"envoy.service.route.v3.VirtualHostDiscoveryService",
"envoy.service.discovery.v2.SecretDiscoveryService",
"envoy.service.secret.v3.SecretDiscoveryService",
"envoy.api.v2.ClusterDiscoveryService",
"envoy.service.cluster.v3.ClusterDiscoveryService",
"envoy.api.v2.EndpointDiscoveryService",
"envoy.service.endpoint.v3.EndpointDiscoveryService",
"envoy.api.v2.ListenerDiscoveryService",
"envoy.service.listener.v3.ListenerDiscoveryService",
"envoy.service.discovery.v2.RuntimeDiscoveryService",
"envoy.service.runtime.v3.RuntimeDiscoveryService",
// service methods (versioned by transport_api_version). We explicitly enumerate all services,
// since DescriptorPool doesn't support iterating over all descriptors, due its lazy load design,
// see https://www.mail-archive.com/protobuf@googlegroups.com/msg04540.html.
for (const ServiceVersionInfoMap& registered : {
SERVICE_VERSION_INFO("envoy.api.v2.RouteDiscoveryService",
"envoy.service.route.v3.RouteDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.ScopedRoutesDiscoveryService",
"envoy.service.route.v3.ScopedRoutesDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.ScopedRoutesDiscoveryService",
"envoy.service.route.v3.ScopedRoutesDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.VirtualHostDiscoveryService",
"envoy.service.route.v3.VirtualHostDiscoveryService"),
SERVICE_VERSION_INFO("envoy.service.discovery.v2.SecretDiscoveryService",
"envoy.service.secret.v3.SecretDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.ClusterDiscoveryService",
"envoy.service.cluster.v3.ClusterDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.EndpointDiscoveryService",
"envoy.service.endpoint.v3.EndpointDiscoveryService"),
SERVICE_VERSION_INFO("envoy.api.v2.ListenerDiscoveryService",
"envoy.service.listener.v3.ListenerDiscoveryService"),
SERVICE_VERSION_INFO("envoy.service.discovery.v2.RuntimeDiscoveryService",
"envoy.service.runtime.v3.RuntimeDiscoveryService"),
}) {
const auto* service_desc =
Protobuf::DescriptorPool::generated_pool()->FindServiceByName(service_name);
// TODO(htuch): this should become an ASSERT once all v3 descriptors are linked in.
ASSERT(service_desc != nullptr, fmt::format("{} missing", service_name));
ASSERT(service_desc->options().HasExtension(envoy::annotations::resource));
const std::string resource_type_url = Grpc::Common::typeUrl(
service_desc->options().GetExtension(envoy::annotations::resource).type());
Service& service = (*type_url_to_service_map)[resource_type_url];
// We populate the service methods that are known below, but it's possible that some services
// don't implement all, e.g. VHDS doesn't support SotW or REST.
for (int method_index = 0; method_index < service_desc->method_count(); ++method_index) {
const auto& method_desc = *service_desc->method(method_index);
if (absl::StartsWith(method_desc.name(), "Stream")) {
service.sotw_grpc_method_ = method_desc.full_name();
} else if (absl::StartsWith(method_desc.name(), "Delta")) {
service.delta_grpc_method_ = method_desc.full_name();
} else if (absl::StartsWith(method_desc.name(), "Fetch")) {
service.rest_method_ = method_desc.full_name();
} else {
ASSERT(false, "Unknown xDS service method");
for (const auto& registered_service : registered) {
const TypeUrl resource_type_url = getResourceTypeUrl(registered_service.first);
VersionedService& service = (*type_url_to_versioned_service_map)[resource_type_url];

for (const auto& versioned_service_name : registered_service.second.names_) {
const ServiceName& service_name = versioned_service_name.second;
const auto* service_desc =
Protobuf::DescriptorPool::generated_pool()->FindServiceByName(service_name);
ASSERT(service_desc != nullptr, fmt::format("{} missing", service_name));
ASSERT(service_desc->options().HasExtension(envoy::annotations::resource));

// We populate the service methods that are known below, but it's possible that some
// services don't implement all, e.g. VHDS doesn't support SotW or REST.
for (int method_index = 0; method_index < service_desc->method_count(); ++method_index) {
const auto& method_desc = *service_desc->method(method_index);
const auto transport_api_version = versioned_service_name.first;
if (absl::StartsWith(method_desc.name(), "Stream")) {
service.sotw_grpc_.methods_[transport_api_version] = method_desc.full_name();
} else if (absl::StartsWith(method_desc.name(), "Delta")) {
service.delta_grpc_.methods_[transport_api_version] = method_desc.full_name();
} else if (absl::StartsWith(method_desc.name(), "Fetch")) {
service.rest_.methods_[transport_api_version] = method_desc.full_name();
} else {
ASSERT(false, "Unknown xDS service method");
}
}
}
}
}
return type_url_to_service_map;
return type_url_to_versioned_service_map;
}

TypeUrlToServiceMap& typeUrlToServiceMap() {
static TypeUrlToServiceMap* type_url_to_service_map = buildTypeUrlToServiceMap();
return *type_url_to_service_map;
TypeUrlToVersionedServiceMap& typeUrlToVersionedServiceMap() {
static TypeUrlToVersionedServiceMap* type_url_to_versioned_service_map =
buildTypeUrlToServiceMap();
return *type_url_to_versioned_service_map;
}

envoy::config::core::v3::ApiVersion
effectiveTransportApiVersion(envoy::config::core::v3::ApiVersion transport_api_version) {
// By default (when the transport_api_version is "AUTO"), the effective transport_api_version is
// envoy::config::core::v3::ApiVersion::V2.
if (transport_api_version == envoy::config::core::v3::ApiVersion::AUTO) {
return envoy::config::core::v3::ApiVersion::V2;
}
return transport_api_version;
}

} // namespace

const Protobuf::MethodDescriptor& deltaGrpcMethod(absl::string_view type_url) {
const auto it = typeUrlToServiceMap().find(static_cast<std::string>(type_url));
ASSERT(it != typeUrlToServiceMap().cend());
const Protobuf::MethodDescriptor&
deltaGrpcMethod(absl::string_view type_url,
envoy::config::core::v3::ApiVersion transport_api_version) {
const auto it = typeUrlToVersionedServiceMap().find(static_cast<TypeUrl>(type_url));
ASSERT(it != typeUrlToVersionedServiceMap().cend());
return *Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
it->second.delta_grpc_method_);
it->second.delta_grpc_.methods_[effectiveTransportApiVersion(transport_api_version)]);
}

const Protobuf::MethodDescriptor& sotwGrpcMethod(absl::string_view type_url) {
const auto it = typeUrlToServiceMap().find(static_cast<std::string>(type_url));
ASSERT(it != typeUrlToServiceMap().cend());
const Protobuf::MethodDescriptor&
sotwGrpcMethod(absl::string_view type_url,
envoy::config::core::v3::ApiVersion transport_api_version) {
const auto it = typeUrlToVersionedServiceMap().find(static_cast<TypeUrl>(type_url));
ASSERT(it != typeUrlToVersionedServiceMap().cend());
return *Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
it->second.sotw_grpc_method_);
it->second.sotw_grpc_.methods_[effectiveTransportApiVersion(transport_api_version)]);
}

const Protobuf::MethodDescriptor& restMethod(absl::string_view type_url) {
const auto it = typeUrlToServiceMap().find(static_cast<std::string>(type_url));
ASSERT(it != typeUrlToServiceMap().cend());
return *Protobuf::DescriptorPool::generated_pool()->FindMethodByName(it->second.rest_method_);
const Protobuf::MethodDescriptor&
restMethod(absl::string_view type_url, envoy::config::core::v3::ApiVersion transport_api_version) {
const auto it = typeUrlToVersionedServiceMap().find(static_cast<TypeUrl>(type_url));
ASSERT(it != typeUrlToVersionedServiceMap().cend());
return *Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
it->second.rest_.methods_[effectiveTransportApiVersion(transport_api_version)]);
}

} // namespace Config
Expand Down
12 changes: 9 additions & 3 deletions source/common/config/type_to_endpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ namespace Envoy {
namespace Config {

// Translates an xDS resource type_url to the name of the delta gRPC service that carries it.
const Protobuf::MethodDescriptor& deltaGrpcMethod(absl::string_view resource_type_url);
const Protobuf::MethodDescriptor&
deltaGrpcMethod(absl::string_view resource_type_url,
envoy::config::core::v3::ApiVersion transport_api_version);
// Translates an xDS resource type_url to the name of the state-of-the-world gRPC service that
// carries it.
const Protobuf::MethodDescriptor& sotwGrpcMethod(absl::string_view resource_type_url);
const Protobuf::MethodDescriptor&
sotwGrpcMethod(absl::string_view resource_type_url,
envoy::config::core::v3::ApiVersion transport_api_version);
// Translates an xDS resource type_url to the name of the REST service that carries it.
const Protobuf::MethodDescriptor& restMethod(absl::string_view resource_type_url);
const Protobuf::MethodDescriptor&
restMethod(absl::string_view resource_type_url,
envoy::config::core::v3::ApiVersion transport_api_version);

} // namespace Config
} // namespace Envoy
Loading

0 comments on commit f678cac

Please sign in to comment.