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