mirror of https://github.com/grpc/grpc.git
XdsClient: move resource type parsing out of XdsApi (#28151)
* introduce XdsResourceType API and change Listener parsing to use it * converted RouteConfig parsing * convert cluster and endpoint parsing * cleanup * clang-format * attempt to work around compiler problems * move XdsResourceType to its own file, and move endpoint code out of XdsApi * move cluster parsing to its own file * move route config parsing to its own file * move listener parsing to its own file * clang-format * minor cleanup * remove comment * add missing virtual dtorpull/28165/head
parent
e21505858f
commit
cbe2855866
37 changed files with 4442 additions and 3700 deletions
@ -0,0 +1,62 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_UPB_UTILS_H |
||||
#define GRPC_CORE_EXT_XDS_UPB_UTILS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "upb/text_encode.h" |
||||
#include "upb/upb.h" |
||||
#include "upb/upb.hpp" |
||||
|
||||
#include "src/core/ext/xds/certificate_provider_store.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsClient; |
||||
|
||||
struct XdsEncodingContext { |
||||
XdsClient* client; // Used only for logging. Unsafe for dereferencing.
|
||||
TraceFlag* tracer; |
||||
upb_symtab* symtab; |
||||
upb_arena* arena; |
||||
bool use_v3; |
||||
const CertificateProviderStore::PluginDefinitionMap* |
||||
certificate_provider_definition_map; |
||||
}; |
||||
|
||||
// Works for both std::string and absl::string_view.
|
||||
template <typename T> |
||||
inline upb_strview StdStringToUpbString(const T& str) { |
||||
return upb_strview_make(str.data(), str.size()); |
||||
} |
||||
|
||||
inline absl::string_view UpbStringToAbsl(const upb_strview& str) { |
||||
return absl::string_view(str.data, str.size); |
||||
} |
||||
|
||||
inline std::string UpbStringToStdString(const upb_strview& str) { |
||||
return std::string(str.data, str.size); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_UPB_UTILS_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,442 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/xds/xds_cluster.h" |
||||
|
||||
#include "absl/container/inlined_vector.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "envoy/config/cluster/v3/circuit_breaker.upb.h" |
||||
#include "envoy/config/cluster/v3/cluster.upb.h" |
||||
#include "envoy/config/cluster/v3/cluster.upbdefs.h" |
||||
#include "envoy/config/core/v3/address.upb.h" |
||||
#include "envoy/config/core/v3/base.upb.h" |
||||
#include "envoy/config/core/v3/config_source.upb.h" |
||||
#include "envoy/config/endpoint/v3/endpoint.upb.h" |
||||
#include "envoy/config/endpoint/v3/endpoint_components.upb.h" |
||||
#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
|
||||
#include "src/core/lib/gpr/env.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
#include "src/core/lib/gprpp/host_port.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// XdsClusterResource
|
||||
//
|
||||
|
||||
std::string XdsClusterResource::ToString() const { |
||||
absl::InlinedVector<std::string, 8> contents; |
||||
switch (cluster_type) { |
||||
case EDS: |
||||
contents.push_back("cluster_type=EDS"); |
||||
if (!eds_service_name.empty()) { |
||||
contents.push_back( |
||||
absl::StrFormat("eds_service_name=%s", eds_service_name)); |
||||
} |
||||
break; |
||||
case LOGICAL_DNS: |
||||
contents.push_back("cluster_type=LOGICAL_DNS"); |
||||
contents.push_back(absl::StrFormat("dns_hostname=%s", dns_hostname)); |
||||
break; |
||||
case AGGREGATE: |
||||
contents.push_back("cluster_type=AGGREGATE"); |
||||
contents.push_back( |
||||
absl::StrFormat("prioritized_cluster_names=[%s]", |
||||
absl::StrJoin(prioritized_cluster_names, ", "))); |
||||
} |
||||
if (!common_tls_context.Empty()) { |
||||
contents.push_back(absl::StrFormat("common_tls_context=%s", |
||||
common_tls_context.ToString())); |
||||
} |
||||
if (lrs_load_reporting_server_name.has_value()) { |
||||
contents.push_back(absl::StrFormat("lrs_load_reporting_server_name=%s", |
||||
lrs_load_reporting_server_name.value())); |
||||
} |
||||
contents.push_back(absl::StrCat("lb_policy=", lb_policy)); |
||||
if (lb_policy == "RING_HASH") { |
||||
contents.push_back(absl::StrCat("min_ring_size=", min_ring_size)); |
||||
contents.push_back(absl::StrCat("max_ring_size=", max_ring_size)); |
||||
} |
||||
contents.push_back( |
||||
absl::StrFormat("max_concurrent_requests=%d", max_concurrent_requests)); |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
//
|
||||
// XdsClusterResourceType
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
grpc_error_handle UpstreamTlsContextParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_core_v3_TransportSocket* transport_socket, |
||||
CommonTlsContext* common_tls_context) { |
||||
// Record Upstream tls context
|
||||
absl::string_view name = UpbStringToAbsl( |
||||
envoy_config_core_v3_TransportSocket_name(transport_socket)); |
||||
if (name != "envoy.transport_sockets.tls") { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("Unrecognized transport socket: ", name)); |
||||
} |
||||
auto* typed_config = |
||||
envoy_config_core_v3_TransportSocket_typed_config(transport_socket); |
||||
if (typed_config != nullptr) { |
||||
const upb_strview encoded_upstream_tls_context = |
||||
google_protobuf_Any_value(typed_config); |
||||
auto* upstream_tls_context = |
||||
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( |
||||
encoded_upstream_tls_context.data, |
||||
encoded_upstream_tls_context.size, context.arena); |
||||
if (upstream_tls_context == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Can't decode upstream tls context."); |
||||
} |
||||
auto* common_tls_context_proto = |
||||
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context( |
||||
upstream_tls_context); |
||||
if (common_tls_context_proto != nullptr) { |
||||
grpc_error_handle error = CommonTlsContext::Parse( |
||||
context, common_tls_context_proto, common_tls_context); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
return grpc_error_add_child(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Error parsing UpstreamTlsContext"), |
||||
error); |
||||
} |
||||
} |
||||
} |
||||
if (common_tls_context->certificate_validation_context |
||||
.ca_certificate_provider_instance.instance_name.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"UpstreamTlsContext: TLS configuration provided but no " |
||||
"ca_certificate_provider_instance found."); |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle CdsLogicalDnsParse( |
||||
const envoy_config_cluster_v3_Cluster* cluster, |
||||
XdsClusterResource* cds_update) { |
||||
const auto* load_assignment = |
||||
envoy_config_cluster_v3_Cluster_load_assignment(cluster); |
||||
if (load_assignment == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"load_assignment not present for LOGICAL_DNS cluster"); |
||||
} |
||||
size_t num_localities; |
||||
const auto* const* localities = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment, |
||||
&num_localities); |
||||
if (num_localities != 1) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("load_assignment for LOGICAL_DNS cluster must have " |
||||
"exactly one locality, found ", |
||||
num_localities)); |
||||
} |
||||
size_t num_endpoints; |
||||
const auto* const* endpoints = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0], |
||||
&num_endpoints); |
||||
if (num_endpoints != 1) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("locality for LOGICAL_DNS cluster must have " |
||||
"exactly one endpoint, found ", |
||||
num_endpoints)); |
||||
} |
||||
const auto* endpoint = |
||||
envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]); |
||||
if (endpoint == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"LbEndpoint endpoint field not set"); |
||||
} |
||||
const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint); |
||||
if (address == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Endpoint address field not set"); |
||||
} |
||||
const auto* socket_address = |
||||
envoy_config_core_v3_Address_socket_address(address); |
||||
if (socket_address == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Address socket_address field not set"); |
||||
} |
||||
if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size != |
||||
0) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"LOGICAL_DNS clusters must NOT have a custom resolver name set"); |
||||
} |
||||
absl::string_view address_str = UpbStringToAbsl( |
||||
envoy_config_core_v3_SocketAddress_address(socket_address)); |
||||
if (address_str.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"SocketAddress address field not set"); |
||||
} |
||||
if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"SocketAddress port_value field not set"); |
||||
} |
||||
cds_update->dns_hostname = JoinHostPort( |
||||
address_str, |
||||
envoy_config_core_v3_SocketAddress_port_value(socket_address)); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
// TODO(donnadionne): Check to see if cluster types aggregate_cluster and
|
||||
// logical_dns are enabled, this will be
|
||||
// removed once the cluster types are fully integration-tested and enabled by
|
||||
// default.
|
||||
bool XdsAggregateAndLogicalDnsClusterEnabled() { |
||||
char* value = gpr_getenv( |
||||
"GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER"); |
||||
bool parsed_value; |
||||
bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value); |
||||
gpr_free(value); |
||||
return parse_succeeded && parsed_value; |
||||
} |
||||
|
||||
grpc_error_handle CdsResourceParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_cluster_v3_Cluster* cluster, bool /*is_v2*/, |
||||
XdsClusterResource* cds_update) { |
||||
std::vector<grpc_error_handle> errors; |
||||
// Check the cluster_discovery_type.
|
||||
if (!envoy_config_cluster_v3_Cluster_has_type(cluster) && |
||||
!envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType not found.")); |
||||
} else if (envoy_config_cluster_v3_Cluster_type(cluster) == |
||||
envoy_config_cluster_v3_Cluster_EDS) { |
||||
cds_update->cluster_type = XdsClusterResource::ClusterType::EDS; |
||||
// Check the EDS config source.
|
||||
const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config = |
||||
envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); |
||||
const envoy_config_core_v3_ConfigSource* eds_config = |
||||
envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( |
||||
eds_cluster_config); |
||||
if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config)) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("EDS ConfigSource is not ADS.")); |
||||
} |
||||
// Record EDS service_name (if any).
|
||||
upb_strview service_name = |
||||
envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( |
||||
eds_cluster_config); |
||||
if (service_name.size != 0) { |
||||
cds_update->eds_service_name = UpbStringToStdString(service_name); |
||||
} |
||||
} else if (!XdsAggregateAndLogicalDnsClusterEnabled()) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType is not valid.")); |
||||
} else if (envoy_config_cluster_v3_Cluster_type(cluster) == |
||||
envoy_config_cluster_v3_Cluster_LOGICAL_DNS) { |
||||
cds_update->cluster_type = XdsClusterResource::ClusterType::LOGICAL_DNS; |
||||
grpc_error_handle error = CdsLogicalDnsParse(cluster, cds_update); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} else { |
||||
if (!envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType is not valid.")); |
||||
} else { |
||||
const envoy_config_cluster_v3_Cluster_CustomClusterType* |
||||
custom_cluster_type = |
||||
envoy_config_cluster_v3_Cluster_cluster_type(cluster); |
||||
upb_strview type_name = |
||||
envoy_config_cluster_v3_Cluster_CustomClusterType_name( |
||||
custom_cluster_type); |
||||
if (UpbStringToAbsl(type_name) != "envoy.clusters.aggregate") { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"DiscoveryType is not valid.")); |
||||
} else { |
||||
cds_update->cluster_type = XdsClusterResource::ClusterType::AGGREGATE; |
||||
// Retrieve aggregate clusters.
|
||||
const google_protobuf_Any* typed_config = |
||||
envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config( |
||||
custom_cluster_type); |
||||
const upb_strview aggregate_cluster_config_upb_strview = |
||||
google_protobuf_Any_value(typed_config); |
||||
const envoy_extensions_clusters_aggregate_v3_ClusterConfig* |
||||
aggregate_cluster_config = |
||||
envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse( |
||||
aggregate_cluster_config_upb_strview.data, |
||||
aggregate_cluster_config_upb_strview.size, context.arena); |
||||
if (aggregate_cluster_config == nullptr) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Can't parse aggregate cluster.")); |
||||
} else { |
||||
size_t size; |
||||
const upb_strview* clusters = |
||||
envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters( |
||||
aggregate_cluster_config, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
const upb_strview cluster = clusters[i]; |
||||
cds_update->prioritized_cluster_names.emplace_back( |
||||
UpbStringToStdString(cluster)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// Check the LB policy.
|
||||
if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == |
||||
envoy_config_cluster_v3_Cluster_ROUND_ROBIN) { |
||||
cds_update->lb_policy = "ROUND_ROBIN"; |
||||
} else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == |
||||
envoy_config_cluster_v3_Cluster_RING_HASH) { |
||||
cds_update->lb_policy = "RING_HASH"; |
||||
// Record ring hash lb config
|
||||
auto* ring_hash_config = |
||||
envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster); |
||||
if (ring_hash_config != nullptr) { |
||||
const google_protobuf_UInt64Value* max_ring_size = |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size( |
||||
ring_hash_config); |
||||
if (max_ring_size != nullptr) { |
||||
cds_update->max_ring_size = |
||||
google_protobuf_UInt64Value_value(max_ring_size); |
||||
if (cds_update->max_ring_size > 8388608 || |
||||
cds_update->max_ring_size == 0) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"max_ring_size is not in the range of 1 to 8388608.")); |
||||
} |
||||
} |
||||
const google_protobuf_UInt64Value* min_ring_size = |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size( |
||||
ring_hash_config); |
||||
if (min_ring_size != nullptr) { |
||||
cds_update->min_ring_size = |
||||
google_protobuf_UInt64Value_value(min_ring_size); |
||||
if (cds_update->min_ring_size > 8388608 || |
||||
cds_update->min_ring_size == 0) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"min_ring_size is not in the range of 1 to 8388608.")); |
||||
} |
||||
if (cds_update->min_ring_size > cds_update->max_ring_size) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"min_ring_size cannot be greater than max_ring_size.")); |
||||
} |
||||
} |
||||
if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( |
||||
ring_hash_config) != |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"ring hash lb config has invalid hash function.")); |
||||
} |
||||
} |
||||
} else { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("LB policy is not supported.")); |
||||
} |
||||
auto* transport_socket = |
||||
envoy_config_cluster_v3_Cluster_transport_socket(cluster); |
||||
if (transport_socket != nullptr) { |
||||
grpc_error_handle error = UpstreamTlsContextParse( |
||||
context, transport_socket, &cds_update->common_tls_context); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
errors.push_back( |
||||
grpc_error_add_child(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Error parsing security configuration"), |
||||
error)); |
||||
} |
||||
} |
||||
// Record LRS server name (if any).
|
||||
const envoy_config_core_v3_ConfigSource* lrs_server = |
||||
envoy_config_cluster_v3_Cluster_lrs_server(cluster); |
||||
if (lrs_server != nullptr) { |
||||
if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
": LRS ConfigSource is not self.")); |
||||
} |
||||
cds_update->lrs_load_reporting_server_name.emplace(""); |
||||
} |
||||
// The Cluster resource encodes the circuit breaking parameters in a list of
|
||||
// Thresholds messages, where each message specifies the parameters for a
|
||||
// particular RoutingPriority. we will look only at the first entry in the
|
||||
// list for priority DEFAULT and default to 1024 if not found.
|
||||
if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) { |
||||
const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers = |
||||
envoy_config_cluster_v3_Cluster_circuit_breakers(cluster); |
||||
size_t num_thresholds; |
||||
const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const* |
||||
thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds( |
||||
circuit_breakers, &num_thresholds); |
||||
for (size_t i = 0; i < num_thresholds; ++i) { |
||||
const auto* threshold = thresholds[i]; |
||||
if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority( |
||||
threshold) == envoy_config_core_v3_DEFAULT) { |
||||
const google_protobuf_UInt32Value* max_requests = |
||||
envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests( |
||||
threshold); |
||||
if (max_requests != nullptr) { |
||||
cds_update->max_concurrent_requests = |
||||
google_protobuf_UInt32Value_value(max_requests); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing CDS resource", &errors); |
||||
} |
||||
|
||||
void MaybeLogCluster(const XdsEncodingContext& context, |
||||
const envoy_config_cluster_v3_Cluster* cluster) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && |
||||
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { |
||||
const upb_msgdef* msg_type = |
||||
envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab); |
||||
char buf[10240]; |
||||
upb_text_encode(cluster, msg_type, nullptr, 0, buf, sizeof(buf)); |
||||
gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", context.client, buf); |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<XdsResourceType::DecodeResult> XdsClusterResourceType::Decode( |
||||
const XdsEncodingContext& context, absl::string_view serialized_resource, |
||||
bool is_v2) const { |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_cluster_v3_Cluster_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
return absl::InvalidArgumentError("Can't parse Listener resource."); |
||||
} |
||||
MaybeLogCluster(context, resource); |
||||
// Validate resource.
|
||||
DecodeResult result; |
||||
result.name = |
||||
UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource)); |
||||
auto cluster_data = absl::make_unique<ClusterData>(); |
||||
grpc_error_handle error = |
||||
CdsResourceParse(context, resource, is_v2, &cluster_data->resource); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
result.resource = absl::InvalidArgumentError(grpc_error_std_string(error)); |
||||
GRPC_ERROR_UNREF(error); |
||||
} else { |
||||
result.resource = std::move(cluster_data); |
||||
} |
||||
return std::move(result); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,101 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_CLUSTER_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_CLUSTER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/ext/xds/xds_common_types.h" |
||||
#include "src/core/ext/xds/xds_resource_type.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct XdsClusterResource { |
||||
enum ClusterType { EDS, LOGICAL_DNS, AGGREGATE }; |
||||
ClusterType cluster_type; |
||||
// For cluster type EDS.
|
||||
// The name to use in the EDS request.
|
||||
// If empty, the cluster name will be used.
|
||||
std::string eds_service_name; |
||||
// For cluster type LOGICAL_DNS.
|
||||
// The hostname to lookup in DNS.
|
||||
std::string dns_hostname; |
||||
// For cluster type AGGREGATE.
|
||||
// The prioritized list of cluster names.
|
||||
std::vector<std::string> prioritized_cluster_names; |
||||
|
||||
// Tls Context used by clients
|
||||
CommonTlsContext common_tls_context; |
||||
|
||||
// The LRS server to use for load reporting.
|
||||
// If not set, load reporting will be disabled.
|
||||
// If set to the empty string, will use the same server we obtained the CDS
|
||||
// data from.
|
||||
absl::optional<std::string> lrs_load_reporting_server_name; |
||||
|
||||
// The LB policy to use (e.g., "ROUND_ROBIN" or "RING_HASH").
|
||||
std::string lb_policy; |
||||
// Used for RING_HASH LB policy only.
|
||||
uint64_t min_ring_size = 1024; |
||||
uint64_t max_ring_size = 8388608; |
||||
// Maximum number of outstanding requests can be made to the upstream
|
||||
// cluster.
|
||||
uint32_t max_concurrent_requests = 1024; |
||||
|
||||
bool operator==(const XdsClusterResource& other) const { |
||||
return cluster_type == other.cluster_type && |
||||
eds_service_name == other.eds_service_name && |
||||
dns_hostname == other.dns_hostname && |
||||
prioritized_cluster_names == other.prioritized_cluster_names && |
||||
common_tls_context == other.common_tls_context && |
||||
lrs_load_reporting_server_name == |
||||
other.lrs_load_reporting_server_name && |
||||
lb_policy == other.lb_policy && |
||||
min_ring_size == other.min_ring_size && |
||||
max_ring_size == other.max_ring_size && |
||||
max_concurrent_requests == other.max_concurrent_requests; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
class XdsClusterResourceType : public XdsResourceType { |
||||
public: |
||||
struct ClusterData : public ResourceData { |
||||
XdsClusterResource resource; |
||||
}; |
||||
|
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.cluster.v3.Cluster"; |
||||
} |
||||
absl::string_view v2_type_url() const override { |
||||
return "envoy.api.v2.Cluster"; |
||||
} |
||||
|
||||
absl::StatusOr<DecodeResult> Decode(const XdsEncodingContext& context, |
||||
absl::string_view serialized_resource, |
||||
bool is_v2) const override; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_CLUSTER_H
|
@ -0,0 +1,388 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/xds/xds_common_types.h" |
||||
|
||||
#include "absl/container/inlined_vector.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "envoy/extensions/transport_sockets/tls/v3/common.upb.h" |
||||
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" |
||||
#include "envoy/type/matcher/v3/regex.upb.h" |
||||
#include "envoy/type/matcher/v3/string.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "xds/type/v3/typed_struct.upb.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// CommonTlsContext::CertificateValidationContext
|
||||
//
|
||||
|
||||
std::string CommonTlsContext::CertificateValidationContext::ToString() const { |
||||
std::vector<std::string> contents; |
||||
for (const auto& match : match_subject_alt_names) { |
||||
contents.push_back(match.ToString()); |
||||
} |
||||
return absl::StrFormat("{match_subject_alt_names=[%s]}", |
||||
absl::StrJoin(contents, ", ")); |
||||
} |
||||
|
||||
bool CommonTlsContext::CertificateValidationContext::Empty() const { |
||||
return match_subject_alt_names.empty(); |
||||
} |
||||
|
||||
//
|
||||
// CommonTlsContext::CertificateProviderPluginInstance
|
||||
//
|
||||
|
||||
std::string CommonTlsContext::CertificateProviderPluginInstance::ToString() |
||||
const { |
||||
absl::InlinedVector<std::string, 2> contents; |
||||
if (!instance_name.empty()) { |
||||
contents.push_back(absl::StrFormat("instance_name=%s", instance_name)); |
||||
} |
||||
if (!certificate_name.empty()) { |
||||
contents.push_back( |
||||
absl::StrFormat("certificate_name=%s", certificate_name)); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
bool CommonTlsContext::CertificateProviderPluginInstance::Empty() const { |
||||
return instance_name.empty() && certificate_name.empty(); |
||||
} |
||||
|
||||
//
|
||||
// CommonTlsContext
|
||||
//
|
||||
|
||||
std::string CommonTlsContext::ToString() const { |
||||
absl::InlinedVector<std::string, 2> contents; |
||||
if (!tls_certificate_provider_instance.Empty()) { |
||||
contents.push_back( |
||||
absl::StrFormat("tls_certificate_provider_instance=%s", |
||||
tls_certificate_provider_instance.ToString())); |
||||
} |
||||
if (!certificate_validation_context.Empty()) { |
||||
contents.push_back( |
||||
absl::StrFormat("certificate_validation_context=%s", |
||||
certificate_validation_context.ToString())); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
bool CommonTlsContext::Empty() const { |
||||
return tls_certificate_provider_instance.Empty() && |
||||
certificate_validation_context.Empty(); |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
// CertificateProviderInstance is deprecated but we are still supporting it for
|
||||
// backward compatibility reasons. Note that we still parse the data into the
|
||||
// same CertificateProviderPluginInstance struct since the fields are the same.
|
||||
// TODO(yashykt): Remove this once we stop supporting the old way of fetching
|
||||
// certificate provider instances.
|
||||
grpc_error_handle CertificateProviderInstanceParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance* |
||||
certificate_provider_instance_proto, |
||||
CommonTlsContext::CertificateProviderPluginInstance* |
||||
certificate_provider_plugin_instance) { |
||||
*certificate_provider_plugin_instance = { |
||||
UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name( |
||||
certificate_provider_instance_proto)), |
||||
UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name( |
||||
certificate_provider_instance_proto))}; |
||||
if (context.certificate_provider_definition_map->find( |
||||
certificate_provider_plugin_instance->instance_name) == |
||||
context.certificate_provider_definition_map->end()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("Unrecognized certificate provider instance name: ", |
||||
certificate_provider_plugin_instance->instance_name)); |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle CertificateProviderPluginInstanceParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance* |
||||
certificate_provider_plugin_instance_proto, |
||||
CommonTlsContext::CertificateProviderPluginInstance* |
||||
certificate_provider_plugin_instance) { |
||||
*certificate_provider_plugin_instance = { |
||||
UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name( |
||||
certificate_provider_plugin_instance_proto)), |
||||
UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name( |
||||
certificate_provider_plugin_instance_proto))}; |
||||
if (context.certificate_provider_definition_map->find( |
||||
certificate_provider_plugin_instance->instance_name) == |
||||
context.certificate_provider_definition_map->end()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("Unrecognized certificate provider instance name: ", |
||||
certificate_provider_plugin_instance->instance_name)); |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle CertificateValidationContextParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext* |
||||
certificate_validation_context_proto, |
||||
CommonTlsContext::CertificateValidationContext* |
||||
certificate_validation_context) { |
||||
std::vector<grpc_error_handle> errors; |
||||
size_t len = 0; |
||||
auto* subject_alt_names_matchers = |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names( |
||||
certificate_validation_context_proto, &len); |
||||
for (size_t i = 0; i < len; ++i) { |
||||
StringMatcher::Type type; |
||||
std::string matcher; |
||||
if (envoy_type_matcher_v3_StringMatcher_has_exact( |
||||
subject_alt_names_matchers[i])) { |
||||
type = StringMatcher::Type::kExact; |
||||
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_exact( |
||||
subject_alt_names_matchers[i])); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix( |
||||
subject_alt_names_matchers[i])) { |
||||
type = StringMatcher::Type::kPrefix; |
||||
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_prefix( |
||||
subject_alt_names_matchers[i])); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix( |
||||
subject_alt_names_matchers[i])) { |
||||
type = StringMatcher::Type::kSuffix; |
||||
matcher = UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_suffix( |
||||
subject_alt_names_matchers[i])); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_contains( |
||||
subject_alt_names_matchers[i])) { |
||||
type = StringMatcher::Type::kContains; |
||||
matcher = |
||||
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_contains( |
||||
subject_alt_names_matchers[i])); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex( |
||||
subject_alt_names_matchers[i])) { |
||||
type = StringMatcher::Type::kSafeRegex; |
||||
auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex( |
||||
subject_alt_names_matchers[i]); |
||||
matcher = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); |
||||
} else { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Invalid StringMatcher specified")); |
||||
continue; |
||||
} |
||||
bool ignore_case = envoy_type_matcher_v3_StringMatcher_ignore_case( |
||||
subject_alt_names_matchers[i]); |
||||
absl::StatusOr<StringMatcher> string_matcher = |
||||
StringMatcher::Create(type, matcher, |
||||
/*case_sensitive=*/!ignore_case); |
||||
if (!string_matcher.ok()) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("string matcher: ", string_matcher.status().message()))); |
||||
continue; |
||||
} |
||||
if (type == StringMatcher::Type::kSafeRegex && ignore_case) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"StringMatcher: ignore_case has no effect for SAFE_REGEX.")); |
||||
continue; |
||||
} |
||||
certificate_validation_context->match_subject_alt_names.push_back( |
||||
std::move(string_matcher.value())); |
||||
} |
||||
auto* ca_certificate_provider_instance = |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_ca_certificate_provider_instance( |
||||
certificate_validation_context_proto); |
||||
if (ca_certificate_provider_instance != nullptr) { |
||||
grpc_error_handle error = CertificateProviderPluginInstanceParse( |
||||
context, ca_certificate_provider_instance, |
||||
&certificate_validation_context->ca_certificate_provider_instance); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki( |
||||
certificate_validation_context_proto, nullptr) != nullptr) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"CertificateValidationContext: verify_certificate_spki " |
||||
"unsupported")); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash( |
||||
certificate_validation_context_proto, nullptr) != nullptr) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"CertificateValidationContext: verify_certificate_hash " |
||||
"unsupported")); |
||||
} |
||||
auto* require_signed_certificate_timestamp = |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp( |
||||
certificate_validation_context_proto); |
||||
if (require_signed_certificate_timestamp != nullptr && |
||||
google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"CertificateValidationContext: " |
||||
"require_signed_certificate_timestamp unsupported")); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl( |
||||
certificate_validation_context_proto)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"CertificateValidationContext: crl unsupported")); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config( |
||||
certificate_validation_context_proto)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"CertificateValidationContext: custom_validator_config " |
||||
"unsupported")); |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR( |
||||
"Error parsing CertificateValidationContext", &errors); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
grpc_error_handle CommonTlsContext::Parse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* |
||||
common_tls_context_proto, |
||||
CommonTlsContext* common_tls_context) { |
||||
std::vector<grpc_error_handle> errors; |
||||
// The validation context is derived from the oneof in
|
||||
// 'validation_context_type'. 'validation_context_sds_secret_config' is not
|
||||
// supported.
|
||||
auto* combined_validation_context = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context( |
||||
common_tls_context_proto); |
||||
if (combined_validation_context != nullptr) { |
||||
auto* default_validation_context = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context( |
||||
combined_validation_context); |
||||
if (default_validation_context != nullptr) { |
||||
grpc_error_handle error = CertificateValidationContextParse( |
||||
context, default_validation_context, |
||||
&common_tls_context->certificate_validation_context); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} |
||||
// If after parsing default_validation_context,
|
||||
// common_tls_context->certificate_validation_context.ca_certificate_provider_instance
|
||||
// is empty, fall back onto
|
||||
// 'validation_context_certificate_provider_instance' inside
|
||||
// 'combined_validation_context'. Note that this way of fetching root
|
||||
// certificates is deprecated and will be removed in the future.
|
||||
// TODO(yashykt): Remove this once it's no longer needed.
|
||||
auto* validation_context_certificate_provider_instance = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance( |
||||
combined_validation_context); |
||||
if (common_tls_context->certificate_validation_context |
||||
.ca_certificate_provider_instance.Empty() && |
||||
validation_context_certificate_provider_instance != nullptr) { |
||||
grpc_error_handle error = CertificateProviderInstanceParse( |
||||
context, validation_context_certificate_provider_instance, |
||||
&common_tls_context->certificate_validation_context |
||||
.ca_certificate_provider_instance); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} |
||||
} else { |
||||
auto* validation_context = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context( |
||||
common_tls_context_proto); |
||||
if (validation_context != nullptr) { |
||||
grpc_error_handle error = CertificateValidationContextParse( |
||||
context, validation_context, |
||||
&common_tls_context->certificate_validation_context); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} else if ( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config( |
||||
common_tls_context_proto)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"validation_context_sds_secret_config unsupported")); |
||||
} |
||||
} |
||||
auto* tls_certificate_provider_instance = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_provider_instance( |
||||
common_tls_context_proto); |
||||
if (tls_certificate_provider_instance != nullptr) { |
||||
grpc_error_handle error = CertificateProviderPluginInstanceParse( |
||||
context, tls_certificate_provider_instance, |
||||
&common_tls_context->tls_certificate_provider_instance); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} else { |
||||
// Fall back onto 'tls_certificate_certificate_provider_instance'. Note that
|
||||
// this way of fetching identity certificates is deprecated and will be
|
||||
// removed in the future.
|
||||
// TODO(yashykt): Remove this once it's no longer needed.
|
||||
auto* tls_certificate_certificate_provider_instance = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_certificate_provider_instance( |
||||
common_tls_context_proto); |
||||
if (tls_certificate_certificate_provider_instance != nullptr) { |
||||
grpc_error_handle error = CertificateProviderInstanceParse( |
||||
context, tls_certificate_certificate_provider_instance, |
||||
&common_tls_context->tls_certificate_provider_instance); |
||||
if (error != GRPC_ERROR_NONE) errors.push_back(error); |
||||
} else { |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_certificates( |
||||
common_tls_context_proto)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"tls_certificates unsupported")); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_certificate_sds_secret_configs( |
||||
common_tls_context_proto)) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"tls_certificate_sds_secret_configs unsupported")); |
||||
} |
||||
} |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params( |
||||
common_tls_context_proto)) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("tls_params unsupported")); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker( |
||||
common_tls_context_proto)) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("custom_handshaker unsupported")); |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("Error parsing CommonTlsContext", |
||||
&errors); |
||||
} |
||||
|
||||
grpc_error_handle ExtractHttpFilterTypeName(const XdsEncodingContext& context, |
||||
const google_protobuf_Any* any, |
||||
absl::string_view* filter_type) { |
||||
*filter_type = UpbStringToAbsl(google_protobuf_Any_type_url(any)); |
||||
if (*filter_type == "type.googleapis.com/xds.type.v3.TypedStruct" || |
||||
*filter_type == "type.googleapis.com/udpa.type.v1.TypedStruct") { |
||||
upb_strview any_value = google_protobuf_Any_value(any); |
||||
const auto* typed_struct = xds_type_v3_TypedStruct_parse( |
||||
any_value.data, any_value.size, context.arena); |
||||
if (typed_struct == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"could not parse TypedStruct from filter config"); |
||||
} |
||||
*filter_type = |
||||
UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct)); |
||||
} |
||||
*filter_type = absl::StripPrefix(*filter_type, "type.googleapis.com/"); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,110 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_COMMON_TYPES_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_COMMON_TYPES_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/str_format.h" |
||||
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/duration.upb.h" |
||||
|
||||
#include "src/core/ext/xds/upb_utils.h" |
||||
#include "src/core/lib/matchers/matchers.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct Duration { |
||||
int64_t seconds = 0; |
||||
int32_t nanos = 0; |
||||
|
||||
Duration() = default; |
||||
|
||||
bool operator==(const Duration& other) const { |
||||
return seconds == other.seconds && nanos == other.nanos; |
||||
} |
||||
std::string ToString() const { |
||||
return absl::StrFormat("Duration seconds: %ld, nanos %d", seconds, nanos); |
||||
} |
||||
|
||||
static Duration Parse(const google_protobuf_Duration* proto_duration) { |
||||
Duration duration; |
||||
duration.seconds = google_protobuf_Duration_seconds(proto_duration); |
||||
duration.nanos = google_protobuf_Duration_nanos(proto_duration); |
||||
return duration; |
||||
} |
||||
}; |
||||
|
||||
struct CommonTlsContext { |
||||
struct CertificateProviderPluginInstance { |
||||
std::string instance_name; |
||||
std::string certificate_name; |
||||
|
||||
bool operator==(const CertificateProviderPluginInstance& other) const { |
||||
return instance_name == other.instance_name && |
||||
certificate_name == other.certificate_name; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
bool Empty() const; |
||||
}; |
||||
|
||||
struct CertificateValidationContext { |
||||
CertificateProviderPluginInstance ca_certificate_provider_instance; |
||||
std::vector<StringMatcher> match_subject_alt_names; |
||||
|
||||
bool operator==(const CertificateValidationContext& other) const { |
||||
return ca_certificate_provider_instance == |
||||
other.ca_certificate_provider_instance && |
||||
match_subject_alt_names == other.match_subject_alt_names; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
bool Empty() const; |
||||
}; |
||||
|
||||
CertificateValidationContext certificate_validation_context; |
||||
CertificateProviderPluginInstance tls_certificate_provider_instance; |
||||
|
||||
bool operator==(const CommonTlsContext& other) const { |
||||
return certificate_validation_context == |
||||
other.certificate_validation_context && |
||||
tls_certificate_provider_instance == |
||||
other.tls_certificate_provider_instance; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
bool Empty() const; |
||||
|
||||
static grpc_error_handle Parse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* |
||||
common_tls_context_proto, |
||||
CommonTlsContext* common_tls_context); |
||||
}; |
||||
|
||||
grpc_error_handle ExtractHttpFilterTypeName(const XdsEncodingContext& context, |
||||
const google_protobuf_Any* any, |
||||
absl::string_view* filter_type); |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_COMMON_TYPES_H
|
@ -0,0 +1,352 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/xds/xds_endpoint.h" |
||||
|
||||
#include "absl/memory/memory.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "envoy/config/core/v3/address.upb.h" |
||||
#include "envoy/config/core/v3/base.upb.h" |
||||
#include "envoy/config/core/v3/health_check.upb.h" |
||||
#include "envoy/config/endpoint/v3/endpoint.upb.h" |
||||
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" |
||||
#include "envoy/config/endpoint/v3/endpoint_components.upb.h" |
||||
#include "envoy/type/v3/percent.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "upb/text_encode.h" |
||||
#include "upb/upb.h" |
||||
#include "upb/upb.hpp" |
||||
|
||||
#include "src/core/ext/xds/upb_utils.h" |
||||
#include "src/core/lib/address_utils/sockaddr_utils.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// XdsEndpointResource
|
||||
//
|
||||
|
||||
std::string XdsEndpointResource::Priority::Locality::ToString() const { |
||||
std::vector<std::string> endpoint_strings; |
||||
for (const ServerAddress& endpoint : endpoints) { |
||||
endpoint_strings.emplace_back(endpoint.ToString()); |
||||
} |
||||
return absl::StrCat("{name=", name->AsHumanReadableString(), |
||||
", lb_weight=", lb_weight, ", endpoints=[", |
||||
absl::StrJoin(endpoint_strings, ", "), "]}"); |
||||
} |
||||
|
||||
bool XdsEndpointResource::Priority::operator==(const Priority& other) const { |
||||
if (localities.size() != other.localities.size()) return false; |
||||
auto it1 = localities.begin(); |
||||
auto it2 = other.localities.begin(); |
||||
while (it1 != localities.end()) { |
||||
if (*it1->first != *it2->first) return false; |
||||
if (it1->second != it2->second) return false; |
||||
++it1; |
||||
++it2; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
std::string XdsEndpointResource::Priority::ToString() const { |
||||
std::vector<std::string> locality_strings; |
||||
for (const auto& p : localities) { |
||||
locality_strings.emplace_back(p.second.ToString()); |
||||
} |
||||
return absl::StrCat("[", absl::StrJoin(locality_strings, ", "), "]"); |
||||
} |
||||
|
||||
bool XdsEndpointResource::DropConfig::ShouldDrop( |
||||
const std::string** category_name) const { |
||||
for (size_t i = 0; i < drop_category_list_.size(); ++i) { |
||||
const auto& drop_category = drop_category_list_[i]; |
||||
// Generate a random number in [0, 1000000).
|
||||
const uint32_t random = static_cast<uint32_t>(rand()) % 1000000; |
||||
if (random < drop_category.parts_per_million) { |
||||
*category_name = &drop_category.name; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
std::string XdsEndpointResource::DropConfig::ToString() const { |
||||
std::vector<std::string> category_strings; |
||||
for (const DropCategory& category : drop_category_list_) { |
||||
category_strings.emplace_back( |
||||
absl::StrCat(category.name, "=", category.parts_per_million)); |
||||
} |
||||
return absl::StrCat("{[", absl::StrJoin(category_strings, ", "), |
||||
"], drop_all=", drop_all_, "}"); |
||||
} |
||||
|
||||
std::string XdsEndpointResource::ToString() const { |
||||
std::vector<std::string> priority_strings; |
||||
for (size_t i = 0; i < priorities.size(); ++i) { |
||||
const Priority& priority = priorities[i]; |
||||
priority_strings.emplace_back( |
||||
absl::StrCat("priority ", i, ": ", priority.ToString())); |
||||
} |
||||
return absl::StrCat("priorities=[", absl::StrJoin(priority_strings, ", "), |
||||
"], drop_config=", drop_config->ToString()); |
||||
} |
||||
|
||||
//
|
||||
// XdsEndpointResourceType
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
void MaybeLogClusterLoadAssignment( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && |
||||
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { |
||||
const upb_msgdef* msg_type = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef( |
||||
context.symtab); |
||||
char buf[10240]; |
||||
upb_text_encode(cla, msg_type, nullptr, 0, buf, sizeof(buf)); |
||||
gpr_log(GPR_DEBUG, "[xds_client %p] ClusterLoadAssignment: %s", |
||||
context.client, buf); |
||||
} |
||||
} |
||||
|
||||
grpc_error_handle ServerAddressParseAndAppend( |
||||
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, |
||||
ServerAddressList* list) { |
||||
// If health_status is not HEALTHY or UNKNOWN, skip this endpoint.
|
||||
const int32_t health_status = |
||||
envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint); |
||||
if (health_status != envoy_config_core_v3_UNKNOWN && |
||||
health_status != envoy_config_core_v3_HEALTHY) { |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
// Find the ip:port.
|
||||
const envoy_config_endpoint_v3_Endpoint* endpoint = |
||||
envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); |
||||
const envoy_config_core_v3_Address* address = |
||||
envoy_config_endpoint_v3_Endpoint_address(endpoint); |
||||
const envoy_config_core_v3_SocketAddress* socket_address = |
||||
envoy_config_core_v3_Address_socket_address(address); |
||||
std::string address_str = UpbStringToStdString( |
||||
envoy_config_core_v3_SocketAddress_address(socket_address)); |
||||
uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); |
||||
if (GPR_UNLIKELY(port >> 16) != 0) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid port."); |
||||
} |
||||
// Find load_balancing_weight for the endpoint.
|
||||
const google_protobuf_UInt32Value* load_balancing_weight = |
||||
envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); |
||||
const int32_t weight = |
||||
load_balancing_weight != nullptr |
||||
? google_protobuf_UInt32Value_value(load_balancing_weight) |
||||
: 500; |
||||
if (weight == 0) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Invalid endpoint weight of 0."); |
||||
} |
||||
// Populate grpc_resolved_address.
|
||||
grpc_resolved_address addr; |
||||
grpc_error_handle error = |
||||
grpc_string_to_sockaddr(&addr, address_str.c_str(), port); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
// Append the address to the list.
|
||||
std::map<const char*, std::unique_ptr<ServerAddress::AttributeInterface>> |
||||
attributes; |
||||
attributes[ServerAddressWeightAttribute::kServerAddressWeightAttributeKey] = |
||||
absl::make_unique<ServerAddressWeightAttribute>(weight); |
||||
list->emplace_back(addr, nullptr, std::move(attributes)); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle LocalityParse( |
||||
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, |
||||
XdsEndpointResource::Priority::Locality* output_locality, |
||||
size_t* priority) { |
||||
// Parse LB weight.
|
||||
const google_protobuf_UInt32Value* lb_weight = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight( |
||||
locality_lb_endpoints); |
||||
// If LB weight is not specified, it means this locality is assigned no load.
|
||||
// TODO(juanlishen): When we support CDS to configure the inter-locality
|
||||
// policy, we should change the LB weight handling.
|
||||
output_locality->lb_weight = |
||||
lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0; |
||||
if (output_locality->lb_weight == 0) return GRPC_ERROR_NONE; |
||||
// Parse locality name.
|
||||
const envoy_config_core_v3_Locality* locality = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_locality( |
||||
locality_lb_endpoints); |
||||
if (locality == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty locality."); |
||||
} |
||||
std::string region = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); |
||||
std::string zone = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); |
||||
std::string sub_zone = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality)); |
||||
output_locality->name = MakeRefCounted<XdsLocalityName>( |
||||
std::move(region), std::move(zone), std::move(sub_zone)); |
||||
// Parse the addresses.
|
||||
size_t size; |
||||
const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints( |
||||
locality_lb_endpoints, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
grpc_error_handle error = ServerAddressParseAndAppend( |
||||
lb_endpoints[i], &output_locality->endpoints); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
} |
||||
// Parse the priority.
|
||||
*priority = envoy_config_endpoint_v3_LocalityLbEndpoints_priority( |
||||
locality_lb_endpoints); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle DropParseAndAppend( |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* |
||||
drop_overload, |
||||
XdsEndpointResource::DropConfig* drop_config) { |
||||
// Get the category.
|
||||
std::string category = UpbStringToStdString( |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category( |
||||
drop_overload)); |
||||
if (category.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty drop category name"); |
||||
} |
||||
// Get the drop rate (per million).
|
||||
const envoy_type_v3_FractionalPercent* drop_percentage = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( |
||||
drop_overload); |
||||
uint32_t numerator = |
||||
envoy_type_v3_FractionalPercent_numerator(drop_percentage); |
||||
const auto denominator = |
||||
static_cast<envoy_type_v3_FractionalPercent_DenominatorType>( |
||||
envoy_type_v3_FractionalPercent_denominator(drop_percentage)); |
||||
// Normalize to million.
|
||||
switch (denominator) { |
||||
case envoy_type_v3_FractionalPercent_HUNDRED: |
||||
numerator *= 10000; |
||||
break; |
||||
case envoy_type_v3_FractionalPercent_TEN_THOUSAND: |
||||
numerator *= 100; |
||||
break; |
||||
case envoy_type_v3_FractionalPercent_MILLION: |
||||
break; |
||||
default: |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Unknown denominator type"); |
||||
} |
||||
// Cap numerator to 1000000.
|
||||
numerator = std::min(numerator, 1000000u); |
||||
drop_config->AddCategory(std::move(category), numerator); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle EdsResourceParse( |
||||
const XdsEncodingContext& /*context*/, |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment* |
||||
cluster_load_assignment, |
||||
bool /*is_v2*/, XdsEndpointResource* eds_update) { |
||||
std::vector<grpc_error_handle> errors; |
||||
// Get the endpoints.
|
||||
size_t locality_size; |
||||
const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( |
||||
cluster_load_assignment, &locality_size); |
||||
for (size_t j = 0; j < locality_size; ++j) { |
||||
size_t priority; |
||||
XdsEndpointResource::Priority::Locality locality; |
||||
grpc_error_handle error = LocalityParse(endpoints[j], &locality, &priority); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
errors.push_back(error); |
||||
continue; |
||||
} |
||||
// Filter out locality with weight 0.
|
||||
if (locality.lb_weight == 0) continue; |
||||
// Make sure prorities is big enough. Note that they might not
|
||||
// arrive in priority order.
|
||||
while (eds_update->priorities.size() < priority + 1) { |
||||
eds_update->priorities.emplace_back(); |
||||
} |
||||
eds_update->priorities[priority].localities.emplace(locality.name.get(), |
||||
std::move(locality)); |
||||
} |
||||
for (const auto& priority : eds_update->priorities) { |
||||
if (priority.localities.empty()) { |
||||
errors.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("sparse priority list")); |
||||
} |
||||
} |
||||
// Get the drop config.
|
||||
eds_update->drop_config = MakeRefCounted<XdsEndpointResource::DropConfig>(); |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy* policy = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_policy( |
||||
cluster_load_assignment); |
||||
if (policy != nullptr) { |
||||
size_t drop_size; |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* const* |
||||
drop_overload = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( |
||||
policy, &drop_size); |
||||
for (size_t j = 0; j < drop_size; ++j) { |
||||
grpc_error_handle error = |
||||
DropParseAndAppend(drop_overload[j], eds_update->drop_config.get()); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
errors.push_back( |
||||
grpc_error_add_child(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"drop config validation error"), |
||||
error)); |
||||
} |
||||
} |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing EDS resource", &errors); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<XdsResourceType::DecodeResult> XdsEndpointResourceType::Decode( |
||||
const XdsEncodingContext& context, absl::string_view serialized_resource, |
||||
bool is_v2) const { |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
return absl::InvalidArgumentError("Can't parse Listener resource."); |
||||
} |
||||
MaybeLogClusterLoadAssignment(context, resource); |
||||
// Validate resource.
|
||||
DecodeResult result; |
||||
result.name = UpbStringToStdString( |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource)); |
||||
auto endpoint_data = absl::make_unique<EndpointData>(); |
||||
grpc_error_handle error = |
||||
EdsResourceParse(context, resource, is_v2, &endpoint_data->resource); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
result.resource = absl::InvalidArgumentError(grpc_error_std_string(error)); |
||||
GRPC_ERROR_UNREF(error); |
||||
} else { |
||||
result.resource = std::move(endpoint_data); |
||||
} |
||||
return std::move(result); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,132 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_ENDPOINT_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_ENDPOINT_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <map> |
||||
#include <set> |
||||
#include <string> |
||||
|
||||
#include "absl/container/inlined_vector.h" |
||||
|
||||
#include "src/core/ext/filters/client_channel/server_address.h" |
||||
#include "src/core/ext/xds/xds_client_stats.h" |
||||
#include "src/core/ext/xds/xds_resource_type.h" |
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct XdsEndpointResource { |
||||
struct Priority { |
||||
struct Locality { |
||||
RefCountedPtr<XdsLocalityName> name; |
||||
uint32_t lb_weight; |
||||
ServerAddressList endpoints; |
||||
|
||||
bool operator==(const Locality& other) const { |
||||
return *name == *other.name && lb_weight == other.lb_weight && |
||||
endpoints == other.endpoints; |
||||
} |
||||
bool operator!=(const Locality& other) const { return !(*this == other); } |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
std::map<XdsLocalityName*, Locality, XdsLocalityName::Less> localities; |
||||
|
||||
bool operator==(const Priority& other) const; |
||||
std::string ToString() const; |
||||
}; |
||||
using PriorityList = absl::InlinedVector<Priority, 2>; |
||||
|
||||
// There are two phases of accessing this class's content:
|
||||
// 1. to initialize in the control plane combiner;
|
||||
// 2. to use in the data plane combiner.
|
||||
// So no additional synchronization is needed.
|
||||
class DropConfig : public RefCounted<DropConfig> { |
||||
public: |
||||
struct DropCategory { |
||||
bool operator==(const DropCategory& other) const { |
||||
return name == other.name && |
||||
parts_per_million == other.parts_per_million; |
||||
} |
||||
|
||||
std::string name; |
||||
const uint32_t parts_per_million; |
||||
}; |
||||
|
||||
using DropCategoryList = absl::InlinedVector<DropCategory, 2>; |
||||
|
||||
void AddCategory(std::string name, uint32_t parts_per_million) { |
||||
drop_category_list_.emplace_back( |
||||
DropCategory{std::move(name), parts_per_million}); |
||||
if (parts_per_million == 1000000) drop_all_ = true; |
||||
} |
||||
|
||||
// The only method invoked from outside the WorkSerializer (used in
|
||||
// the data plane).
|
||||
bool ShouldDrop(const std::string** category_name) const; |
||||
|
||||
const DropCategoryList& drop_category_list() const { |
||||
return drop_category_list_; |
||||
} |
||||
|
||||
bool drop_all() const { return drop_all_; } |
||||
|
||||
bool operator==(const DropConfig& other) const { |
||||
return drop_category_list_ == other.drop_category_list_; |
||||
} |
||||
bool operator!=(const DropConfig& other) const { return !(*this == other); } |
||||
|
||||
std::string ToString() const; |
||||
|
||||
private: |
||||
DropCategoryList drop_category_list_; |
||||
bool drop_all_ = false; |
||||
}; |
||||
|
||||
PriorityList priorities; |
||||
RefCountedPtr<DropConfig> drop_config; |
||||
|
||||
bool operator==(const XdsEndpointResource& other) const { |
||||
return priorities == other.priorities && *drop_config == *other.drop_config; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
class XdsEndpointResourceType : public XdsResourceType { |
||||
public: |
||||
struct EndpointData : public ResourceData { |
||||
XdsEndpointResource resource; |
||||
}; |
||||
|
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.endpoint.v3.ClusterLoadAssignment"; |
||||
} |
||||
absl::string_view v2_type_url() const override { |
||||
return "envoy.api.v2.ClusterLoadAssignment"; |
||||
} |
||||
|
||||
absl::StatusOr<DecodeResult> Decode(const XdsEncodingContext& context, |
||||
absl::string_view serialized_resource, |
||||
bool is_v2) const override; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_ENDPOINT_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,210 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_LISTENER_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_LISTENER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <array> |
||||
#include <map> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/ext/xds/xds_common_types.h" |
||||
#include "src/core/ext/xds/xds_http_filters.h" |
||||
#include "src/core/ext/xds/xds_route_config.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// TODO(roth): When we can use absl::variant<>, consider using that
|
||||
// here, to enforce the fact that only one of the two fields can be set.
|
||||
struct XdsListenerResource { |
||||
struct DownstreamTlsContext { |
||||
CommonTlsContext common_tls_context; |
||||
bool require_client_certificate = false; |
||||
|
||||
bool operator==(const DownstreamTlsContext& other) const { |
||||
return common_tls_context == other.common_tls_context && |
||||
require_client_certificate == other.require_client_certificate; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
bool Empty() const; |
||||
}; |
||||
|
||||
enum class ListenerType { |
||||
kTcpListener = 0, |
||||
kHttpApiListener, |
||||
} type; |
||||
|
||||
struct HttpConnectionManager { |
||||
// The name to use in the RDS request.
|
||||
std::string route_config_name; |
||||
// Storing the Http Connection Manager Common Http Protocol Option
|
||||
// max_stream_duration
|
||||
Duration http_max_stream_duration; |
||||
// The RouteConfiguration to use for this listener.
|
||||
// Present only if it is inlined in the LDS response.
|
||||
absl::optional<XdsRouteConfigResource> rds_update; |
||||
|
||||
struct HttpFilter { |
||||
std::string name; |
||||
XdsHttpFilterImpl::FilterConfig config; |
||||
|
||||
bool operator==(const HttpFilter& other) const { |
||||
return name == other.name && config == other.config; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
std::vector<HttpFilter> http_filters; |
||||
|
||||
bool operator==(const HttpConnectionManager& other) const { |
||||
return route_config_name == other.route_config_name && |
||||
http_max_stream_duration == other.http_max_stream_duration && |
||||
rds_update == other.rds_update && |
||||
http_filters == other.http_filters; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
// Populated for type=kHttpApiListener.
|
||||
HttpConnectionManager http_connection_manager; |
||||
|
||||
// Populated for type=kTcpListener.
|
||||
// host:port listening_address set when type is kTcpListener
|
||||
std::string address; |
||||
|
||||
struct FilterChainData { |
||||
DownstreamTlsContext downstream_tls_context; |
||||
// This is in principle the filter list.
|
||||
// We currently require exactly one filter, which is the HCM.
|
||||
HttpConnectionManager http_connection_manager; |
||||
|
||||
bool operator==(const FilterChainData& other) const { |
||||
return downstream_tls_context == other.downstream_tls_context && |
||||
http_connection_manager == other.http_connection_manager; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
// A multi-level map used to determine which filter chain to use for a given
|
||||
// incoming connection. Determining the right filter chain for a given
|
||||
// connection checks the following properties, in order:
|
||||
// - destination port (never matched, so not present in map)
|
||||
// - destination IP address
|
||||
// - server name (never matched, so not present in map)
|
||||
// - transport protocol (allows only "raw_buffer" or unset, prefers the
|
||||
// former, so only one of those two types is present in map)
|
||||
// - application protocol (never matched, so not present in map)
|
||||
// - connection source type (any, local or external)
|
||||
// - source IP address
|
||||
// - source port
|
||||
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener_components.proto#config-listener-v3-filterchainmatch
|
||||
// for more details
|
||||
struct FilterChainMap { |
||||
struct FilterChainDataSharedPtr { |
||||
std::shared_ptr<FilterChainData> data; |
||||
bool operator==(const FilterChainDataSharedPtr& other) const { |
||||
return *data == *other.data; |
||||
} |
||||
}; |
||||
struct CidrRange { |
||||
grpc_resolved_address address; |
||||
uint32_t prefix_len; |
||||
|
||||
bool operator==(const CidrRange& other) const { |
||||
return memcmp(&address, &other.address, sizeof(address)) == 0 && |
||||
prefix_len == other.prefix_len; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
using SourcePortsMap = std::map<uint16_t, FilterChainDataSharedPtr>; |
||||
struct SourceIp { |
||||
absl::optional<CidrRange> prefix_range; |
||||
SourcePortsMap ports_map; |
||||
|
||||
bool operator==(const SourceIp& other) const { |
||||
return prefix_range == other.prefix_range && |
||||
ports_map == other.ports_map; |
||||
} |
||||
}; |
||||
using SourceIpVector = std::vector<SourceIp>; |
||||
enum class ConnectionSourceType { kAny = 0, kSameIpOrLoopback, kExternal }; |
||||
using ConnectionSourceTypesArray = std::array<SourceIpVector, 3>; |
||||
struct DestinationIp { |
||||
absl::optional<CidrRange> prefix_range; |
||||
// We always fail match on server name, so those filter chains are not
|
||||
// included here.
|
||||
ConnectionSourceTypesArray source_types_array; |
||||
|
||||
bool operator==(const DestinationIp& other) const { |
||||
return prefix_range == other.prefix_range && |
||||
source_types_array == other.source_types_array; |
||||
} |
||||
}; |
||||
// We always fail match on destination ports map
|
||||
using DestinationIpVector = std::vector<DestinationIp>; |
||||
DestinationIpVector destination_ip_vector; |
||||
|
||||
bool operator==(const FilterChainMap& other) const { |
||||
return destination_ip_vector == other.destination_ip_vector; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
} filter_chain_map; |
||||
|
||||
absl::optional<FilterChainData> default_filter_chain; |
||||
|
||||
bool operator==(const XdsListenerResource& other) const { |
||||
return http_connection_manager == other.http_connection_manager && |
||||
address == other.address && |
||||
filter_chain_map == other.filter_chain_map && |
||||
default_filter_chain == other.default_filter_chain; |
||||
} |
||||
|
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
class XdsListenerResourceType : public XdsResourceType { |
||||
public: |
||||
struct ListenerData : public ResourceData { |
||||
XdsListenerResource resource; |
||||
}; |
||||
|
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.listener.v3.Listener"; |
||||
} |
||||
absl::string_view v2_type_url() const override { |
||||
return "envoy.api.v2.Listener"; |
||||
} |
||||
|
||||
absl::StatusOr<DecodeResult> Decode(const XdsEncodingContext& context, |
||||
absl::string_view serialized_resource, |
||||
bool is_v2) const override; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_LISTENER_H
|
@ -0,0 +1,77 @@ |
||||
//
|
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
#include "src/core/ext/xds/upb_utils.h" |
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_RESOURCE_TYPE_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_RESOURCE_TYPE_H |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsResourceType { |
||||
public: |
||||
// A base type for resource data.
|
||||
// Subclasses will extend this, and their DecodeResults will be
|
||||
// downcastable to their extended type.
|
||||
struct ResourceData { |
||||
virtual ~ResourceData() = default; |
||||
}; |
||||
|
||||
// Result returned by Decode().
|
||||
struct DecodeResult { |
||||
std::string name; |
||||
absl::StatusOr<std::unique_ptr<ResourceData>> resource; |
||||
}; |
||||
|
||||
virtual ~XdsResourceType() = default; |
||||
|
||||
// Returns v3 resource type.
|
||||
virtual absl::string_view type_url() const = 0; |
||||
|
||||
// Returns v2 resource type.
|
||||
virtual absl::string_view v2_type_url() const = 0; |
||||
|
||||
// Decodes and validates a serialized resource proto.
|
||||
// If the resource fails protobuf deserialization, returns non-OK status.
|
||||
// If the deserialized resource fails validation, returns a DecodeResult
|
||||
// whose resource field is set to a non-OK status.
|
||||
// Otherwise, returns a DecodeResult with a valid resource.
|
||||
virtual absl::StatusOr<DecodeResult> Decode( |
||||
const XdsEncodingContext& context, absl::string_view serialized_resource, |
||||
bool is_v2) const = 0; |
||||
|
||||
// Convenient method for checking if a resource type matches this type.
|
||||
bool IsType(absl::string_view resource_type, bool* is_v2) const { |
||||
if (resource_type == type_url()) return true; |
||||
if (resource_type == v2_type_url()) { |
||||
if (is_v2 != nullptr) *is_v2 = true; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_RESOURCE_TYPE_H
|
@ -0,0 +1,982 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "absl/memory/memory.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/strings/str_split.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "envoy/config/core/v3/base.upb.h" |
||||
#include "envoy/config/route/v3/route.upb.h" |
||||
#include "envoy/config/route/v3/route.upbdefs.h" |
||||
#include "envoy/config/route/v3/route_components.upb.h" |
||||
#include "envoy/config/route/v3/route_components.upbdefs.h" |
||||
#include "envoy/type/matcher/v3/regex.upb.h" |
||||
#include "envoy/type/matcher/v3/string.upb.h" |
||||
#include "envoy/type/v3/percent.upb.h" |
||||
#include "envoy/type/v3/range.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "upb/text_encode.h" |
||||
#include "upb/upb.h" |
||||
#include "upb/upb.hpp" |
||||
|
||||
#include "src/core/ext/xds/upb_utils.h" |
||||
#include "src/core/ext/xds/xds_api.h" |
||||
#include "src/core/ext/xds/xds_common_types.h" |
||||
#include "src/core/ext/xds/xds_resource_type.h" |
||||
#include "src/core/ext/xds/xds_routing.h" |
||||
#include "src/core/lib/gpr/env.h" |
||||
#include "src/core/lib/gpr/string.h" |
||||
#include "src/core/lib/iomgr/error.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// TODO(yashykt): Remove once RBAC is no longer experimental
|
||||
bool XdsRbacEnabled() { |
||||
char* value = gpr_getenv("GRPC_XDS_EXPERIMENTAL_RBAC"); |
||||
bool parsed_value; |
||||
bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value); |
||||
gpr_free(value); |
||||
return parse_succeeded && parsed_value; |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::RetryPolicy
|
||||
//
|
||||
|
||||
std::string XdsRouteConfigResource::RetryPolicy::RetryBackOff::ToString() |
||||
const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back( |
||||
absl::StrCat("RetryBackOff Base: ", base_interval.ToString())); |
||||
contents.push_back( |
||||
absl::StrCat("RetryBackOff max: ", max_interval.ToString())); |
||||
return absl::StrJoin(contents, ","); |
||||
} |
||||
|
||||
std::string XdsRouteConfigResource::RetryPolicy::ToString() const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back(absl::StrFormat("num_retries=%d", num_retries)); |
||||
contents.push_back(retry_back_off.ToString()); |
||||
return absl::StrCat("{", absl::StrJoin(contents, ","), "}"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::Route::Matchers
|
||||
//
|
||||
|
||||
std::string XdsRouteConfigResource::Route::Matchers::ToString() const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back( |
||||
absl::StrFormat("PathMatcher{%s}", path_matcher.ToString())); |
||||
for (const HeaderMatcher& header_matcher : header_matchers) { |
||||
contents.push_back(header_matcher.ToString()); |
||||
} |
||||
if (fraction_per_million.has_value()) { |
||||
contents.push_back(absl::StrFormat("Fraction Per Million %d", |
||||
fraction_per_million.value())); |
||||
} |
||||
return absl::StrJoin(contents, "\n"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::Route::RouteAction::HashPolicy
|
||||
//
|
||||
|
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::HashPolicy( |
||||
const HashPolicy& other) |
||||
: type(other.type), |
||||
header_name(other.header_name), |
||||
regex_substitution(other.regex_substitution) { |
||||
if (other.regex != nullptr) { |
||||
regex = |
||||
absl::make_unique<RE2>(other.regex->pattern(), other.regex->options()); |
||||
} |
||||
} |
||||
|
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy& |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::operator=( |
||||
const HashPolicy& other) { |
||||
type = other.type; |
||||
header_name = other.header_name; |
||||
if (other.regex != nullptr) { |
||||
regex = |
||||
absl::make_unique<RE2>(other.regex->pattern(), other.regex->options()); |
||||
} |
||||
regex_substitution = other.regex_substitution; |
||||
return *this; |
||||
} |
||||
|
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::HashPolicy( |
||||
HashPolicy&& other) noexcept |
||||
: type(other.type), |
||||
header_name(std::move(other.header_name)), |
||||
regex(std::move(other.regex)), |
||||
regex_substitution(std::move(other.regex_substitution)) {} |
||||
|
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy& |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::operator=( |
||||
HashPolicy&& other) noexcept { |
||||
type = other.type; |
||||
header_name = std::move(other.header_name); |
||||
regex = std::move(other.regex); |
||||
regex_substitution = std::move(other.regex_substitution); |
||||
return *this; |
||||
} |
||||
|
||||
bool XdsRouteConfigResource::Route::RouteAction::HashPolicy::HashPolicy:: |
||||
operator==(const HashPolicy& other) const { |
||||
if (type != other.type) return false; |
||||
if (type == Type::HEADER) { |
||||
if (regex == nullptr) { |
||||
if (other.regex != nullptr) return false; |
||||
} else { |
||||
if (other.regex == nullptr) return false; |
||||
return header_name == other.header_name && |
||||
regex->pattern() == other.regex->pattern() && |
||||
regex_substitution == other.regex_substitution; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
std::string XdsRouteConfigResource::Route::RouteAction::HashPolicy::ToString() |
||||
const { |
||||
std::vector<std::string> contents; |
||||
switch (type) { |
||||
case Type::HEADER: |
||||
contents.push_back("type=HEADER"); |
||||
break; |
||||
case Type::CHANNEL_ID: |
||||
contents.push_back("type=CHANNEL_ID"); |
||||
break; |
||||
} |
||||
contents.push_back( |
||||
absl::StrFormat("terminal=%s", terminal ? "true" : "false")); |
||||
if (type == Type::HEADER) { |
||||
contents.push_back(absl::StrFormat( |
||||
"Header %s:/%s/%s", header_name, |
||||
(regex == nullptr) ? "" : regex->pattern(), regex_substitution)); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::Route::RouteAction::ClusterWeight
|
||||
//
|
||||
|
||||
std::string |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterWeight::ToString() const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back(absl::StrCat("cluster=", name)); |
||||
contents.push_back(absl::StrCat("weight=", weight)); |
||||
if (!typed_per_filter_config.empty()) { |
||||
std::vector<std::string> parts; |
||||
for (const auto& p : typed_per_filter_config) { |
||||
const std::string& key = p.first; |
||||
const auto& config = p.second; |
||||
parts.push_back(absl::StrCat(key, "=", config.ToString())); |
||||
} |
||||
contents.push_back(absl::StrCat("typed_per_filter_config={", |
||||
absl::StrJoin(parts, ", "), "}")); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::Route::RouteAction
|
||||
//
|
||||
|
||||
std::string XdsRouteConfigResource::Route::RouteAction::ToString() const { |
||||
std::vector<std::string> contents; |
||||
for (const HashPolicy& hash_policy : hash_policies) { |
||||
contents.push_back(absl::StrCat("hash_policy=", hash_policy.ToString())); |
||||
} |
||||
if (retry_policy.has_value()) { |
||||
contents.push_back(absl::StrCat("retry_policy=", retry_policy->ToString())); |
||||
} |
||||
if (!cluster_name.empty()) { |
||||
contents.push_back(absl::StrFormat("Cluster name: %s", cluster_name)); |
||||
} |
||||
for (const ClusterWeight& cluster_weight : weighted_clusters) { |
||||
contents.push_back(cluster_weight.ToString()); |
||||
} |
||||
if (max_stream_duration.has_value()) { |
||||
contents.push_back(max_stream_duration->ToString()); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource::Route
|
||||
//
|
||||
|
||||
std::string XdsRouteConfigResource::Route::ToString() const { |
||||
std::vector<std::string> contents; |
||||
contents.push_back(matchers.ToString()); |
||||
auto* route_action = |
||||
absl::get_if<XdsRouteConfigResource::Route::RouteAction>(&action); |
||||
if (route_action != nullptr) { |
||||
contents.push_back(absl::StrCat("route=", route_action->ToString())); |
||||
} else if (absl::holds_alternative< |
||||
XdsRouteConfigResource::Route::NonForwardingAction>(action)) { |
||||
contents.push_back("non_forwarding_action={}"); |
||||
} else { |
||||
contents.push_back("unknown_action={}"); |
||||
} |
||||
if (!typed_per_filter_config.empty()) { |
||||
contents.push_back("typed_per_filter_config={"); |
||||
for (const auto& p : typed_per_filter_config) { |
||||
const std::string& name = p.first; |
||||
const auto& config = p.second; |
||||
contents.push_back(absl::StrCat(" ", name, "=", config.ToString())); |
||||
} |
||||
contents.push_back("}"); |
||||
} |
||||
return absl::StrJoin(contents, "\n"); |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResource
|
||||
//
|
||||
|
||||
std::string XdsRouteConfigResource::ToString() const { |
||||
std::vector<std::string> vhosts; |
||||
for (const VirtualHost& vhost : virtual_hosts) { |
||||
vhosts.push_back( |
||||
absl::StrCat("vhost={\n" |
||||
" domains=[", |
||||
absl::StrJoin(vhost.domains, ", "), |
||||
"]\n" |
||||
" routes=[\n")); |
||||
for (const XdsRouteConfigResource::Route& route : vhost.routes) { |
||||
vhosts.push_back(" {\n"); |
||||
vhosts.push_back(route.ToString()); |
||||
vhosts.push_back("\n }\n"); |
||||
} |
||||
vhosts.push_back(" ]\n"); |
||||
vhosts.push_back(" typed_per_filter_config={\n"); |
||||
for (const auto& p : vhost.typed_per_filter_config) { |
||||
const std::string& name = p.first; |
||||
const auto& config = p.second; |
||||
vhosts.push_back( |
||||
absl::StrCat(" ", name, "=", config.ToString(), "\n")); |
||||
} |
||||
vhosts.push_back(" }\n"); |
||||
vhosts.push_back("]\n"); |
||||
} |
||||
return absl::StrJoin(vhosts, ""); |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
grpc_error_handle RoutePathMatchParse( |
||||
const envoy_config_route_v3_RouteMatch* match, |
||||
XdsRouteConfigResource::Route* route, bool* ignore_route) { |
||||
auto* case_sensitive_ptr = |
||||
envoy_config_route_v3_RouteMatch_case_sensitive(match); |
||||
bool case_sensitive = true; |
||||
if (case_sensitive_ptr != nullptr) { |
||||
case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr); |
||||
} |
||||
StringMatcher::Type type; |
||||
std::string match_string; |
||||
if (envoy_config_route_v3_RouteMatch_has_prefix(match)) { |
||||
absl::string_view prefix = |
||||
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_prefix(match)); |
||||
// Empty prefix "" is accepted.
|
||||
if (!prefix.empty()) { |
||||
// Prefix "/" is accepted.
|
||||
if (prefix[0] != '/') { |
||||
// Prefix which does not start with a / will never match anything, so
|
||||
// ignore this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
std::vector<absl::string_view> prefix_elements = |
||||
absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2)); |
||||
if (prefix_elements.size() > 2) { |
||||
// Prefix cannot have more than 2 slashes.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} else if (prefix_elements.size() == 2 && prefix_elements[0].empty()) { |
||||
// Prefix contains empty string between the 2 slashes
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
} |
||||
type = StringMatcher::Type::kPrefix; |
||||
match_string = std::string(prefix); |
||||
} else if (envoy_config_route_v3_RouteMatch_has_path(match)) { |
||||
absl::string_view path = |
||||
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_path(match)); |
||||
if (path.empty()) { |
||||
// Path that is empty will never match anything, so ignore this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
if (path[0] != '/') { |
||||
// Path which does not start with a / will never match anything, so
|
||||
// ignore this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
std::vector<absl::string_view> path_elements = |
||||
absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2)); |
||||
if (path_elements.size() != 2) { |
||||
// Path not in the required format of /service/method will never match
|
||||
// anything, so ignore this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} else if (path_elements[0].empty()) { |
||||
// Path contains empty service name will never match anything, so ignore
|
||||
// this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} else if (path_elements[1].empty()) { |
||||
// Path contains empty method name will never match anything, so ignore
|
||||
// this route.
|
||||
*ignore_route = true; |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
type = StringMatcher::Type::kExact; |
||||
match_string = std::string(path); |
||||
} else if (envoy_config_route_v3_RouteMatch_has_safe_regex(match)) { |
||||
const envoy_type_matcher_v3_RegexMatcher* regex_matcher = |
||||
envoy_config_route_v3_RouteMatch_safe_regex(match); |
||||
GPR_ASSERT(regex_matcher != nullptr); |
||||
type = StringMatcher::Type::kSafeRegex; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); |
||||
} else { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Invalid route path specifier specified."); |
||||
} |
||||
absl::StatusOr<StringMatcher> string_matcher = |
||||
StringMatcher::Create(type, match_string, case_sensitive); |
||||
if (!string_matcher.ok()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("path matcher: ", string_matcher.status().message())); |
||||
} |
||||
route->matchers.path_matcher = std::move(string_matcher.value()); |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle RouteHeaderMatchersParse( |
||||
const envoy_config_route_v3_RouteMatch* match, |
||||
XdsRouteConfigResource::Route* route) { |
||||
size_t size; |
||||
const envoy_config_route_v3_HeaderMatcher* const* headers = |
||||
envoy_config_route_v3_RouteMatch_headers(match, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
const envoy_config_route_v3_HeaderMatcher* header = headers[i]; |
||||
const std::string name = |
||||
UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header)); |
||||
HeaderMatcher::Type type; |
||||
std::string match_string; |
||||
int64_t range_start = 0; |
||||
int64_t range_end = 0; |
||||
bool present_match = false; |
||||
if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) { |
||||
type = HeaderMatcher::Type::kExact; |
||||
match_string = UpbStringToStdString( |
||||
envoy_config_route_v3_HeaderMatcher_exact_match(header)); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_safe_regex_match( |
||||
header)) { |
||||
const envoy_type_matcher_v3_RegexMatcher* regex_matcher = |
||||
envoy_config_route_v3_HeaderMatcher_safe_regex_match(header); |
||||
GPR_ASSERT(regex_matcher != nullptr); |
||||
type = HeaderMatcher::Type::kSafeRegex; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) { |
||||
type = HeaderMatcher::Type::kRange; |
||||
const envoy_type_v3_Int64Range* range_matcher = |
||||
envoy_config_route_v3_HeaderMatcher_range_match(header); |
||||
range_start = envoy_type_v3_Int64Range_start(range_matcher); |
||||
range_end = envoy_type_v3_Int64Range_end(range_matcher); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_present_match(header)) { |
||||
type = HeaderMatcher::Type::kPresent; |
||||
present_match = envoy_config_route_v3_HeaderMatcher_present_match(header); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) { |
||||
type = HeaderMatcher::Type::kPrefix; |
||||
match_string = UpbStringToStdString( |
||||
envoy_config_route_v3_HeaderMatcher_prefix_match(header)); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_suffix_match(header)) { |
||||
type = HeaderMatcher::Type::kSuffix; |
||||
match_string = UpbStringToStdString( |
||||
envoy_config_route_v3_HeaderMatcher_suffix_match(header)); |
||||
} else if (envoy_config_route_v3_HeaderMatcher_has_contains_match(header)) { |
||||
type = HeaderMatcher::Type::kContains; |
||||
match_string = UpbStringToStdString( |
||||
envoy_config_route_v3_HeaderMatcher_contains_match(header)); |
||||
} else { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Invalid route header matcher specified."); |
||||
} |
||||
bool invert_match = |
||||
envoy_config_route_v3_HeaderMatcher_invert_match(header); |
||||
absl::StatusOr<HeaderMatcher> header_matcher = |
||||
HeaderMatcher::Create(name, type, match_string, range_start, range_end, |
||||
present_match, invert_match); |
||||
if (!header_matcher.ok()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("header matcher: ", header_matcher.status().message())); |
||||
} |
||||
route->matchers.header_matchers.emplace_back( |
||||
std::move(header_matcher.value())); |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle RouteRuntimeFractionParse( |
||||
const envoy_config_route_v3_RouteMatch* match, |
||||
XdsRouteConfigResource::Route* route) { |
||||
const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction = |
||||
envoy_config_route_v3_RouteMatch_runtime_fraction(match); |
||||
if (runtime_fraction != nullptr) { |
||||
const envoy_type_v3_FractionalPercent* fraction = |
||||
envoy_config_core_v3_RuntimeFractionalPercent_default_value( |
||||
runtime_fraction); |
||||
if (fraction != nullptr) { |
||||
uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction); |
||||
const auto denominator = |
||||
static_cast<envoy_type_v3_FractionalPercent_DenominatorType>( |
||||
envoy_type_v3_FractionalPercent_denominator(fraction)); |
||||
// Normalize to million.
|
||||
switch (denominator) { |
||||
case envoy_type_v3_FractionalPercent_HUNDRED: |
||||
numerator *= 10000; |
||||
break; |
||||
case envoy_type_v3_FractionalPercent_TEN_THOUSAND: |
||||
numerator *= 100; |
||||
break; |
||||
case envoy_type_v3_FractionalPercent_MILLION: |
||||
break; |
||||
default: |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Unknown denominator type"); |
||||
} |
||||
route->matchers.fraction_per_million = numerator; |
||||
} |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
template <typename ParentType, typename EntryType> |
||||
grpc_error_handle ParseTypedPerFilterConfig( |
||||
const XdsEncodingContext& context, const ParentType* parent, |
||||
const EntryType* (*entry_func)(const ParentType*, size_t*), |
||||
upb_strview (*key_func)(const EntryType*), |
||||
const google_protobuf_Any* (*value_func)(const EntryType*), |
||||
XdsRouteConfigResource::TypedPerFilterConfig* typed_per_filter_config) { |
||||
size_t filter_it = UPB_MAP_BEGIN; |
||||
while (true) { |
||||
const auto* filter_entry = entry_func(parent, &filter_it); |
||||
if (filter_entry == nullptr) break; |
||||
absl::string_view key = UpbStringToAbsl(key_func(filter_entry)); |
||||
if (key.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("empty filter name in map"); |
||||
} |
||||
const google_protobuf_Any* any = value_func(filter_entry); |
||||
GPR_ASSERT(any != nullptr); |
||||
absl::string_view filter_type = |
||||
UpbStringToAbsl(google_protobuf_Any_type_url(any)); |
||||
if (filter_type.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("no filter config specified for filter name ", key)); |
||||
} |
||||
bool is_optional = false; |
||||
if (filter_type == |
||||
"type.googleapis.com/envoy.config.route.v3.FilterConfig") { |
||||
upb_strview any_value = google_protobuf_Any_value(any); |
||||
const auto* filter_config = envoy_config_route_v3_FilterConfig_parse( |
||||
any_value.data, any_value.size, context.arena); |
||||
if (filter_config == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("could not parse FilterConfig wrapper for ", key)); |
||||
} |
||||
is_optional = |
||||
envoy_config_route_v3_FilterConfig_is_optional(filter_config); |
||||
any = envoy_config_route_v3_FilterConfig_config(filter_config); |
||||
if (any == nullptr) { |
||||
if (is_optional) continue; |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("no filter config specified for filter name ", key)); |
||||
} |
||||
} |
||||
grpc_error_handle error = |
||||
ExtractHttpFilterTypeName(context, any, &filter_type); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
const XdsHttpFilterImpl* filter_impl = |
||||
XdsHttpFilterRegistry::GetFilterForType(filter_type); |
||||
if (filter_impl == nullptr) { |
||||
if (is_optional) continue; |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("no filter registered for config type ", filter_type)); |
||||
} |
||||
absl::StatusOr<XdsHttpFilterImpl::FilterConfig> filter_config = |
||||
filter_impl->GenerateFilterConfigOverride( |
||||
google_protobuf_Any_value(any), context.arena); |
||||
if (!filter_config.ok()) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat( |
||||
"filter config for type ", filter_type, |
||||
" failed to parse: ", filter_config.status().ToString())); |
||||
} |
||||
(*typed_per_filter_config)[std::string(key)] = std::move(*filter_config); |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
grpc_error_handle RetryPolicyParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_route_v3_RetryPolicy* retry_policy, |
||||
absl::optional<XdsRouteConfigResource::RetryPolicy>* retry) { |
||||
std::vector<grpc_error_handle> errors; |
||||
XdsRouteConfigResource::RetryPolicy retry_to_return; |
||||
auto retry_on = UpbStringToStdString( |
||||
envoy_config_route_v3_RetryPolicy_retry_on(retry_policy)); |
||||
std::vector<absl::string_view> codes = absl::StrSplit(retry_on, ','); |
||||
for (const auto& code : codes) { |
||||
if (code == "cancelled") { |
||||
retry_to_return.retry_on.Add(GRPC_STATUS_CANCELLED); |
||||
} else if (code == "deadline-exceeded") { |
||||
retry_to_return.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED); |
||||
} else if (code == "internal") { |
||||
retry_to_return.retry_on.Add(GRPC_STATUS_INTERNAL); |
||||
} else if (code == "resource-exhausted") { |
||||
retry_to_return.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED); |
||||
} else if (code == "unavailable") { |
||||
retry_to_return.retry_on.Add(GRPC_STATUS_UNAVAILABLE); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) { |
||||
gpr_log(GPR_INFO, "Unsupported retry_on policy %s.", |
||||
std::string(code).c_str()); |
||||
} |
||||
} |
||||
} |
||||
const google_protobuf_UInt32Value* num_retries = |
||||
envoy_config_route_v3_RetryPolicy_num_retries(retry_policy); |
||||
if (num_retries != nullptr) { |
||||
uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries); |
||||
if (num_retries_value == 0) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction RetryPolicy num_retries set to invalid value 0.")); |
||||
} else { |
||||
retry_to_return.num_retries = num_retries_value; |
||||
} |
||||
} else { |
||||
retry_to_return.num_retries = 1; |
||||
} |
||||
const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff = |
||||
envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy); |
||||
if (backoff != nullptr) { |
||||
const google_protobuf_Duration* base_interval = |
||||
envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff); |
||||
if (base_interval == nullptr) { |
||||
errors.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction RetryPolicy RetryBackoff missing base interval.")); |
||||
} else { |
||||
retry_to_return.retry_back_off.base_interval = |
||||
Duration::Parse(base_interval); |
||||
} |
||||
const google_protobuf_Duration* max_interval = |
||||
envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff); |
||||
Duration max; |
||||
if (max_interval != nullptr) { |
||||
max = Duration::Parse(max_interval); |
||||
} else { |
||||
// if max interval is not set, it is 10x the base, if the value in nanos
|
||||
// can yield another second, adjust the value in seconds accordingly.
|
||||
max.seconds = retry_to_return.retry_back_off.base_interval.seconds * 10; |
||||
max.nanos = retry_to_return.retry_back_off.base_interval.nanos * 10; |
||||
if (max.nanos > 1000000000) { |
||||
max.seconds += max.nanos / 1000000000; |
||||
max.nanos = max.nanos % 1000000000; |
||||
} |
||||
} |
||||
retry_to_return.retry_back_off.max_interval = max; |
||||
} else { |
||||
retry_to_return.retry_back_off.base_interval.seconds = 0; |
||||
retry_to_return.retry_back_off.base_interval.nanos = 25000000; |
||||
retry_to_return.retry_back_off.max_interval.seconds = 0; |
||||
retry_to_return.retry_back_off.max_interval.nanos = 250000000; |
||||
} |
||||
if (errors.empty()) { |
||||
*retry = retry_to_return; |
||||
return GRPC_ERROR_NONE; |
||||
} else { |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing retry policy", |
||||
&errors); |
||||
} |
||||
} |
||||
|
||||
grpc_error_handle RouteActionParse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_route_v3_Route* route_msg, |
||||
XdsRouteConfigResource::Route::RouteAction* route, bool* ignore_route) { |
||||
const envoy_config_route_v3_RouteAction* route_action = |
||||
envoy_config_route_v3_Route_route(route_msg); |
||||
// Get the cluster or weighted_clusters in the RouteAction.
|
||||
if (envoy_config_route_v3_RouteAction_has_cluster(route_action)) { |
||||
route->cluster_name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_cluster(route_action)); |
||||
if (route->cluster_name.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction cluster contains empty cluster name."); |
||||
} |
||||
} else if (envoy_config_route_v3_RouteAction_has_weighted_clusters( |
||||
route_action)) { |
||||
const envoy_config_route_v3_WeightedCluster* weighted_cluster = |
||||
envoy_config_route_v3_RouteAction_weighted_clusters(route_action); |
||||
uint32_t total_weight = 100; |
||||
const google_protobuf_UInt32Value* weight = |
||||
envoy_config_route_v3_WeightedCluster_total_weight(weighted_cluster); |
||||
if (weight != nullptr) { |
||||
total_weight = google_protobuf_UInt32Value_value(weight); |
||||
} |
||||
size_t clusters_size; |
||||
const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters = |
||||
envoy_config_route_v3_WeightedCluster_clusters(weighted_cluster, |
||||
&clusters_size); |
||||
uint32_t sum_of_weights = 0; |
||||
for (size_t j = 0; j < clusters_size; ++j) { |
||||
const envoy_config_route_v3_WeightedCluster_ClusterWeight* |
||||
cluster_weight = clusters[j]; |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster; |
||||
cluster.name = UpbStringToStdString( |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_name( |
||||
cluster_weight)); |
||||
if (cluster.name.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction weighted_cluster cluster contains empty cluster " |
||||
"name."); |
||||
} |
||||
const google_protobuf_UInt32Value* weight = |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_weight( |
||||
cluster_weight); |
||||
if (weight == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction weighted_cluster cluster missing weight"); |
||||
} |
||||
cluster.weight = google_protobuf_UInt32Value_value(weight); |
||||
if (cluster.weight == 0) continue; |
||||
sum_of_weights += cluster.weight; |
||||
if (context.use_v3) { |
||||
grpc_error_handle error = ParseTypedPerFilterConfig< |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight, |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>( |
||||
context, cluster_weight, |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_typed_per_filter_config_next, |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_key, |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_value, |
||||
&cluster.typed_per_filter_config); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
} |
||||
route->weighted_clusters.emplace_back(std::move(cluster)); |
||||
} |
||||
if (total_weight != sum_of_weights) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction weighted_cluster has incorrect total weight"); |
||||
} |
||||
if (route->weighted_clusters.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"RouteAction weighted_cluster has no valid clusters specified."); |
||||
} |
||||
} else { |
||||
// No cluster or weighted_clusters found in RouteAction, ignore this route.
|
||||
*ignore_route = true; |
||||
} |
||||
if (!*ignore_route) { |
||||
const envoy_config_route_v3_RouteAction_MaxStreamDuration* |
||||
max_stream_duration = |
||||
envoy_config_route_v3_RouteAction_max_stream_duration(route_action); |
||||
if (max_stream_duration != nullptr) { |
||||
const google_protobuf_Duration* duration = |
||||
envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max( |
||||
max_stream_duration); |
||||
if (duration == nullptr) { |
||||
duration = |
||||
envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration( |
||||
max_stream_duration); |
||||
} |
||||
if (duration != nullptr) { |
||||
route->max_stream_duration = Duration::Parse(duration); |
||||
} |
||||
} |
||||
} |
||||
// Get HashPolicy from RouteAction
|
||||
size_t size = 0; |
||||
const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies = |
||||
envoy_config_route_v3_RouteAction_hash_policy(route_action, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
const envoy_config_route_v3_RouteAction_HashPolicy* hash_policy = |
||||
hash_policies[i]; |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy policy; |
||||
policy.terminal = |
||||
envoy_config_route_v3_RouteAction_HashPolicy_terminal(hash_policy); |
||||
const envoy_config_route_v3_RouteAction_HashPolicy_Header* header; |
||||
const envoy_config_route_v3_RouteAction_HashPolicy_FilterState* |
||||
filter_state; |
||||
if ((header = envoy_config_route_v3_RouteAction_HashPolicy_header( |
||||
hash_policy)) != nullptr) { |
||||
policy.type = |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::Type::HEADER; |
||||
policy.header_name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name( |
||||
header)); |
||||
const struct envoy_type_matcher_v3_RegexMatchAndSubstitute* |
||||
regex_rewrite = |
||||
envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite( |
||||
header); |
||||
if (regex_rewrite != nullptr) { |
||||
const envoy_type_matcher_v3_RegexMatcher* regex_matcher = |
||||
envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern( |
||||
regex_rewrite); |
||||
if (regex_matcher == nullptr) { |
||||
gpr_log( |
||||
GPR_DEBUG, |
||||
"RouteAction HashPolicy contains policy specifier Header with " |
||||
"RegexMatchAndSubstitution but RegexMatcher pattern is " |
||||
"missing"); |
||||
continue; |
||||
} |
||||
RE2::Options options; |
||||
policy.regex = absl::make_unique<RE2>( |
||||
UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)), |
||||
options); |
||||
if (!policy.regex->ok()) { |
||||
gpr_log( |
||||
GPR_DEBUG, |
||||
"RouteAction HashPolicy contains policy specifier Header with " |
||||
"RegexMatchAndSubstitution but RegexMatcher pattern does not " |
||||
"compile"); |
||||
continue; |
||||
} |
||||
policy.regex_substitution = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution( |
||||
regex_rewrite)); |
||||
} |
||||
} else if ((filter_state = |
||||
envoy_config_route_v3_RouteAction_HashPolicy_filter_state( |
||||
hash_policy)) != nullptr) { |
||||
std::string key = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key( |
||||
filter_state)); |
||||
if (key == "io.grpc.channel_id") { |
||||
policy.type = XdsRouteConfigResource::Route::RouteAction::HashPolicy:: |
||||
Type::CHANNEL_ID; |
||||
} else { |
||||
gpr_log(GPR_DEBUG, |
||||
"RouteAction HashPolicy contains policy specifier " |
||||
"FilterState but " |
||||
"key is not io.grpc.channel_id."); |
||||
continue; |
||||
} |
||||
} else { |
||||
gpr_log(GPR_DEBUG, |
||||
"RouteAction HashPolicy contains unsupported policy specifier."); |
||||
continue; |
||||
} |
||||
route->hash_policies.emplace_back(std::move(policy)); |
||||
} |
||||
// Get retry policy
|
||||
const envoy_config_route_v3_RetryPolicy* retry_policy = |
||||
envoy_config_route_v3_RouteAction_retry_policy(route_action); |
||||
if (retry_policy != nullptr) { |
||||
absl::optional<XdsRouteConfigResource::RetryPolicy> retry; |
||||
grpc_error_handle error = RetryPolicyParse(context, retry_policy, &retry); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
route->retry_policy = retry; |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
grpc_error_handle XdsRouteConfigResource::Parse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config, |
||||
XdsRouteConfigResource* rds_update) { |
||||
// Get the virtual hosts.
|
||||
size_t num_virtual_hosts; |
||||
const envoy_config_route_v3_VirtualHost* const* virtual_hosts = |
||||
envoy_config_route_v3_RouteConfiguration_virtual_hosts( |
||||
route_config, &num_virtual_hosts); |
||||
for (size_t i = 0; i < num_virtual_hosts; ++i) { |
||||
rds_update->virtual_hosts.emplace_back(); |
||||
XdsRouteConfigResource::VirtualHost& vhost = |
||||
rds_update->virtual_hosts.back(); |
||||
// Parse domains.
|
||||
size_t domain_size; |
||||
upb_strview const* domains = envoy_config_route_v3_VirtualHost_domains( |
||||
virtual_hosts[i], &domain_size); |
||||
for (size_t j = 0; j < domain_size; ++j) { |
||||
std::string domain_pattern = UpbStringToStdString(domains[j]); |
||||
if (!XdsRouting::IsValidDomainPattern(domain_pattern)) { |
||||
return GRPC_ERROR_CREATE_FROM_CPP_STRING( |
||||
absl::StrCat("Invalid domain pattern \"", domain_pattern, "\".")); |
||||
} |
||||
vhost.domains.emplace_back(std::move(domain_pattern)); |
||||
} |
||||
if (vhost.domains.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("VirtualHost has no domains"); |
||||
} |
||||
// Parse typed_per_filter_config.
|
||||
if (context.use_v3) { |
||||
grpc_error_handle error = ParseTypedPerFilterConfig< |
||||
envoy_config_route_v3_VirtualHost, |
||||
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry>( |
||||
context, virtual_hosts[i], |
||||
envoy_config_route_v3_VirtualHost_typed_per_filter_config_next, |
||||
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_key, |
||||
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_value, |
||||
&vhost.typed_per_filter_config); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
} |
||||
// Parse retry policy.
|
||||
absl::optional<XdsRouteConfigResource::RetryPolicy> |
||||
virtual_host_retry_policy; |
||||
const envoy_config_route_v3_RetryPolicy* retry_policy = |
||||
envoy_config_route_v3_VirtualHost_retry_policy(virtual_hosts[i]); |
||||
if (retry_policy != nullptr) { |
||||
grpc_error_handle error = |
||||
RetryPolicyParse(context, retry_policy, &virtual_host_retry_policy); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
} |
||||
// Parse routes.
|
||||
size_t num_routes; |
||||
const envoy_config_route_v3_Route* const* routes = |
||||
envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes); |
||||
if (num_routes < 1) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"No route found in the virtual host."); |
||||
} |
||||
// Loop over the whole list of routes
|
||||
for (size_t j = 0; j < num_routes; ++j) { |
||||
const envoy_config_route_v3_RouteMatch* match = |
||||
envoy_config_route_v3_Route_match(routes[j]); |
||||
if (match == nullptr) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Match can't be null."); |
||||
} |
||||
size_t query_parameters_size; |
||||
static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters( |
||||
match, &query_parameters_size)); |
||||
if (query_parameters_size > 0) { |
||||
continue; |
||||
} |
||||
XdsRouteConfigResource::Route route; |
||||
bool ignore_route = false; |
||||
grpc_error_handle error = |
||||
RoutePathMatchParse(match, &route, &ignore_route); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
if (ignore_route) continue; |
||||
error = RouteHeaderMatchersParse(match, &route); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
error = RouteRuntimeFractionParse(match, &route); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
if (envoy_config_route_v3_Route_has_route(routes[j])) { |
||||
route.action.emplace<XdsRouteConfigResource::Route::RouteAction>(); |
||||
auto& route_action = |
||||
absl::get<XdsRouteConfigResource::Route::RouteAction>(route.action); |
||||
error = |
||||
RouteActionParse(context, routes[j], &route_action, &ignore_route); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
if (ignore_route) continue; |
||||
if (route_action.retry_policy == absl::nullopt && |
||||
retry_policy != nullptr) { |
||||
route_action.retry_policy = virtual_host_retry_policy; |
||||
} |
||||
} else if (envoy_config_route_v3_Route_has_non_forwarding_action( |
||||
routes[j])) { |
||||
route.action |
||||
.emplace<XdsRouteConfigResource::Route::NonForwardingAction>(); |
||||
} |
||||
if (context.use_v3) { |
||||
grpc_error_handle error = ParseTypedPerFilterConfig< |
||||
envoy_config_route_v3_Route, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry>( |
||||
context, routes[j], |
||||
envoy_config_route_v3_Route_typed_per_filter_config_next, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, |
||||
&route.typed_per_filter_config); |
||||
if (error != GRPC_ERROR_NONE) return error; |
||||
} |
||||
vhost.routes.emplace_back(std::move(route)); |
||||
} |
||||
if (vhost.routes.empty()) { |
||||
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("No valid routes specified."); |
||||
} |
||||
} |
||||
return GRPC_ERROR_NONE; |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResourceType
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
void MaybeLogRouteConfiguration( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config) { |
||||
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) && |
||||
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) { |
||||
const upb_msgdef* msg_type = |
||||
envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab); |
||||
char buf[10240]; |
||||
upb_text_encode(route_config, msg_type, nullptr, 0, buf, sizeof(buf)); |
||||
gpr_log(GPR_DEBUG, "[xds_client %p] RouteConfiguration: %s", context.client, |
||||
buf); |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<XdsResourceType::DecodeResult> |
||||
XdsRouteConfigResourceType::Decode(const XdsEncodingContext& context, |
||||
absl::string_view serialized_resource, |
||||
bool /*is_v2*/) const { |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_route_v3_RouteConfiguration_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
return absl::InvalidArgumentError("Can't parse Listener resource."); |
||||
} |
||||
MaybeLogRouteConfiguration(context, resource); |
||||
// Validate resource.
|
||||
DecodeResult result; |
||||
result.name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteConfiguration_name(resource)); |
||||
auto route_config_data = absl::make_unique<RouteConfigData>(); |
||||
grpc_error_handle error = XdsRouteConfigResource::Parse( |
||||
context, resource, &route_config_data->resource); |
||||
if (error != GRPC_ERROR_NONE) { |
||||
result.resource = absl::InvalidArgumentError(grpc_error_std_string(error)); |
||||
GRPC_ERROR_UNREF(error); |
||||
} else { |
||||
result.resource = std::move(route_config_data); |
||||
} |
||||
return std::move(result); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,211 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_XDS_XDS_ROUTE_CONFIG_H |
||||
#define GRPC_CORE_EXT_XDS_XDS_ROUTE_CONFIG_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <map> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/types/optional.h" |
||||
#include "absl/types/variant.h" |
||||
#include "envoy/config/route/v3/route.upb.h" |
||||
#include "re2/re2.h" |
||||
|
||||
#include "src/core/ext/xds/xds_common_types.h" |
||||
#include "src/core/ext/xds/xds_http_filters.h" |
||||
#include "src/core/ext/xds/xds_resource_type.h" |
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/matchers/matchers.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
bool XdsRbacEnabled(); |
||||
|
||||
struct XdsRouteConfigResource { |
||||
using TypedPerFilterConfig = |
||||
std::map<std::string, XdsHttpFilterImpl::FilterConfig>; |
||||
|
||||
struct RetryPolicy { |
||||
internal::StatusCodeSet retry_on; |
||||
uint32_t num_retries; |
||||
|
||||
struct RetryBackOff { |
||||
Duration base_interval; |
||||
Duration max_interval; |
||||
|
||||
bool operator==(const RetryBackOff& other) const { |
||||
return base_interval == other.base_interval && |
||||
max_interval == other.max_interval; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
RetryBackOff retry_back_off; |
||||
|
||||
bool operator==(const RetryPolicy& other) const { |
||||
return (retry_on == other.retry_on && num_retries == other.num_retries && |
||||
retry_back_off == other.retry_back_off); |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
// TODO(donnadionne): When we can use absl::variant<>, consider using that
|
||||
// for: PathMatcher, HeaderMatcher, cluster_name and weighted_clusters
|
||||
struct Route { |
||||
// Matchers for this route.
|
||||
struct Matchers { |
||||
StringMatcher path_matcher; |
||||
std::vector<HeaderMatcher> header_matchers; |
||||
absl::optional<uint32_t> fraction_per_million; |
||||
|
||||
bool operator==(const Matchers& other) const { |
||||
return path_matcher == other.path_matcher && |
||||
header_matchers == other.header_matchers && |
||||
fraction_per_million == other.fraction_per_million; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
Matchers matchers; |
||||
|
||||
struct UnknownAction { |
||||
bool operator==(const UnknownAction& /* other */) const { return true; } |
||||
}; |
||||
|
||||
struct RouteAction { |
||||
struct HashPolicy { |
||||
enum Type { HEADER, CHANNEL_ID }; |
||||
Type type; |
||||
bool terminal = false; |
||||
// Fields used for type HEADER.
|
||||
std::string header_name; |
||||
std::unique_ptr<RE2> regex = nullptr; |
||||
std::string regex_substitution; |
||||
|
||||
HashPolicy() {} |
||||
|
||||
// Copyable.
|
||||
HashPolicy(const HashPolicy& other); |
||||
HashPolicy& operator=(const HashPolicy& other); |
||||
|
||||
// Moveable.
|
||||
HashPolicy(HashPolicy&& other) noexcept; |
||||
HashPolicy& operator=(HashPolicy&& other) noexcept; |
||||
|
||||
bool operator==(const HashPolicy& other) const; |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
struct ClusterWeight { |
||||
std::string name; |
||||
uint32_t weight; |
||||
TypedPerFilterConfig typed_per_filter_config; |
||||
|
||||
bool operator==(const ClusterWeight& other) const { |
||||
return name == other.name && weight == other.weight && |
||||
typed_per_filter_config == other.typed_per_filter_config; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
std::vector<HashPolicy> hash_policies; |
||||
absl::optional<RetryPolicy> retry_policy; |
||||
|
||||
// Action for this route.
|
||||
// TODO(roth): When we can use absl::variant<>, consider using that
|
||||
// here, to enforce the fact that only one of the two fields can be set.
|
||||
std::string cluster_name; |
||||
std::vector<ClusterWeight> weighted_clusters; |
||||
// Storing the timeout duration from route action:
|
||||
// RouteAction.max_stream_duration.grpc_timeout_header_max or
|
||||
// RouteAction.max_stream_duration.max_stream_duration if the former is
|
||||
// not set.
|
||||
absl::optional<Duration> max_stream_duration; |
||||
|
||||
bool operator==(const RouteAction& other) const { |
||||
return hash_policies == other.hash_policies && |
||||
retry_policy == other.retry_policy && |
||||
cluster_name == other.cluster_name && |
||||
weighted_clusters == other.weighted_clusters && |
||||
max_stream_duration == other.max_stream_duration; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
struct NonForwardingAction { |
||||
bool operator==(const NonForwardingAction& /* other */) const { |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
absl::variant<UnknownAction, RouteAction, NonForwardingAction> action; |
||||
TypedPerFilterConfig typed_per_filter_config; |
||||
|
||||
bool operator==(const Route& other) const { |
||||
return matchers == other.matchers && action == other.action && |
||||
typed_per_filter_config == other.typed_per_filter_config; |
||||
} |
||||
std::string ToString() const; |
||||
}; |
||||
|
||||
struct VirtualHost { |
||||
std::vector<std::string> domains; |
||||
std::vector<Route> routes; |
||||
TypedPerFilterConfig typed_per_filter_config; |
||||
|
||||
bool operator==(const VirtualHost& other) const { |
||||
return domains == other.domains && routes == other.routes && |
||||
typed_per_filter_config == other.typed_per_filter_config; |
||||
} |
||||
}; |
||||
|
||||
std::vector<VirtualHost> virtual_hosts; |
||||
|
||||
bool operator==(const XdsRouteConfigResource& other) const { |
||||
return virtual_hosts == other.virtual_hosts; |
||||
} |
||||
std::string ToString() const; |
||||
|
||||
static grpc_error_handle Parse( |
||||
const XdsEncodingContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config, |
||||
XdsRouteConfigResource* rds_update); |
||||
}; |
||||
|
||||
class XdsRouteConfigResourceType : public XdsResourceType { |
||||
public: |
||||
struct RouteConfigData : public ResourceData { |
||||
XdsRouteConfigResource resource; |
||||
}; |
||||
|
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.route.v3.RouteConfiguration"; |
||||
} |
||||
absl::string_view v2_type_url() const override { |
||||
return "envoy.api.v2.RouteConfiguration"; |
||||
} |
||||
|
||||
absl::StatusOr<DecodeResult> Decode(const XdsEncodingContext& context, |
||||
absl::string_view serialized_resource, |
||||
bool /*is_v2*/) const override; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_EXT_XDS_XDS_ROUTE_CONFIG_H
|
Loading…
Reference in new issue