mirror of https://github.com/grpc/grpc.git
[xDS] refactor BUILD targets for resource types (#37130)
This moves more code out of the monolithic `grpc_xds_client` BUILD target. We still need more work to split it up completely, but this is a nice step in the right direction -- and it unblocks a subsequent PR that I'm working on for xDS authority rewriting.
Closes #37130
COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37130 from markdroth:xds_resource_type_build_refactoring a021d9773c
PiperOrigin-RevId: 648742472
pull/37156/head
parent
7091890ae7
commit
62826f5f6e
68 changed files with 4643 additions and 3893 deletions
@ -0,0 +1,730 @@ |
||||
// 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 "src/core/xds/grpc/xds_cluster_parser.h" |
||||
|
||||
#include <algorithm> |
||||
#include <memory> |
||||
#include <utility> |
||||
|
||||
#include "absl/log/check.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/strip.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/cluster/v3/outlier_detection.upb.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/core/v3/extension.upb.h" |
||||
#include "envoy/config/core/v3/health_check.upb.h" |
||||
#include "envoy/config/core/v3/protocol.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 "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" |
||||
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/duration.upb.h" |
||||
#include "google/protobuf/struct.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "upb/base/string_view.h" |
||||
#include "upb/text/encode.h" |
||||
|
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/host_port.h" |
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/load_balancing/lb_policy_registry.h" |
||||
#include "src/core/util/upb_utils.h" |
||||
#include "src/core/xds/grpc/xds_bootstrap_grpc.h" |
||||
#include "src/core/xds/grpc/xds_common_types.h" |
||||
#include "src/core/xds/grpc/xds_common_types_parser.h" |
||||
#include "src/core/xds/grpc/xds_lb_policy_registry.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
CommonTlsContext UpstreamTlsContextParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_core_v3_TransportSocket* transport_socket, |
||||
ValidationErrors* errors) { |
||||
ValidationErrors::ScopedField field(errors, ".typed_config"); |
||||
const auto* typed_config = |
||||
envoy_config_core_v3_TransportSocket_typed_config(transport_socket); |
||||
auto extension = ExtractXdsExtension(context, typed_config, errors); |
||||
if (!extension.has_value()) return {}; |
||||
if (extension->type != |
||||
"envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") { |
||||
ValidationErrors::ScopedField field(errors, ".type_url"); |
||||
errors->AddError("unsupported transport socket type"); |
||||
return {}; |
||||
} |
||||
absl::string_view* serialized_upstream_tls_context = |
||||
absl::get_if<absl::string_view>(&extension->value); |
||||
if (serialized_upstream_tls_context == nullptr) { |
||||
errors->AddError("can't decode UpstreamTlsContext"); |
||||
return {}; |
||||
} |
||||
const auto* upstream_tls_context = |
||||
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( |
||||
serialized_upstream_tls_context->data(), |
||||
serialized_upstream_tls_context->size(), context.arena); |
||||
if (upstream_tls_context == nullptr) { |
||||
errors->AddError("can't decode UpstreamTlsContext"); |
||||
return {}; |
||||
} |
||||
ValidationErrors::ScopedField field3(errors, ".common_tls_context"); |
||||
const auto* common_tls_context_proto = |
||||
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context( |
||||
upstream_tls_context); |
||||
CommonTlsContext common_tls_context; |
||||
if (common_tls_context_proto != nullptr) { |
||||
common_tls_context = |
||||
CommonTlsContextParse(context, common_tls_context_proto, errors); |
||||
} |
||||
if (common_tls_context.certificate_validation_context |
||||
.ca_certificate_provider_instance.instance_name.empty()) { |
||||
errors->AddError("no CA certificate provider instance configured"); |
||||
} |
||||
return common_tls_context; |
||||
} |
||||
|
||||
XdsClusterResource::Eds EdsConfigParse( |
||||
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { |
||||
XdsClusterResource::Eds eds; |
||||
ValidationErrors::ScopedField field(errors, ".eds_cluster_config"); |
||||
const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config = |
||||
envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster); |
||||
if (eds_cluster_config == nullptr) { |
||||
errors->AddError("field not present"); |
||||
} else { |
||||
// Validate ConfigSource.
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".eds_config"); |
||||
const envoy_config_core_v3_ConfigSource* eds_config = |
||||
envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config( |
||||
eds_cluster_config); |
||||
if (eds_config == nullptr) { |
||||
errors->AddError("field not present"); |
||||
} else { |
||||
if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) && |
||||
!envoy_config_core_v3_ConfigSource_has_self(eds_config)) { |
||||
errors->AddError("ConfigSource is not ads or self"); |
||||
} |
||||
} |
||||
} |
||||
// Record EDS service_name (if any).
|
||||
// This field is required if the CDS resource has an xdstp name.
|
||||
eds.eds_service_name = UpbStringToStdString( |
||||
envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name( |
||||
eds_cluster_config)); |
||||
if (eds.eds_service_name.empty()) { |
||||
absl::string_view cluster_name = |
||||
UpbStringToAbsl(envoy_config_cluster_v3_Cluster_name(cluster)); |
||||
if (absl::StartsWith(cluster_name, "xdstp:")) { |
||||
ValidationErrors::ScopedField field(errors, ".service_name"); |
||||
errors->AddError("must be set if Cluster resource has an xdstp name"); |
||||
} |
||||
} |
||||
} |
||||
return eds; |
||||
} |
||||
|
||||
XdsClusterResource::LogicalDns LogicalDnsParse( |
||||
const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) { |
||||
XdsClusterResource::LogicalDns logical_dns; |
||||
ValidationErrors::ScopedField field(errors, ".load_assignment"); |
||||
const auto* load_assignment = |
||||
envoy_config_cluster_v3_Cluster_load_assignment(cluster); |
||||
if (load_assignment == nullptr) { |
||||
errors->AddError("field not present for LOGICAL_DNS cluster"); |
||||
return logical_dns; |
||||
} |
||||
ValidationErrors::ScopedField field2(errors, ".endpoints"); |
||||
size_t num_localities; |
||||
const auto* const* localities = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment, |
||||
&num_localities); |
||||
if (num_localities != 1) { |
||||
errors->AddError(absl::StrCat( |
||||
"must contain exactly one locality for LOGICAL_DNS cluster, found ", |
||||
num_localities)); |
||||
return logical_dns; |
||||
} |
||||
ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints"); |
||||
size_t num_endpoints; |
||||
const auto* const* endpoints = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0], |
||||
&num_endpoints); |
||||
if (num_endpoints != 1) { |
||||
errors->AddError(absl::StrCat( |
||||
"must contain exactly one endpoint for LOGICAL_DNS cluster, found ", |
||||
num_endpoints)); |
||||
return logical_dns; |
||||
} |
||||
ValidationErrors::ScopedField field4(errors, "[0].endpoint"); |
||||
const auto* endpoint = |
||||
envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]); |
||||
if (endpoint == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return logical_dns; |
||||
} |
||||
ValidationErrors::ScopedField field5(errors, ".address"); |
||||
const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint); |
||||
if (address == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return logical_dns; |
||||
} |
||||
ValidationErrors::ScopedField field6(errors, ".socket_address"); |
||||
const auto* socket_address = |
||||
envoy_config_core_v3_Address_socket_address(address); |
||||
if (socket_address == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return logical_dns; |
||||
} |
||||
if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size != |
||||
0) { |
||||
ValidationErrors::ScopedField field(errors, ".resolver_name"); |
||||
errors->AddError( |
||||
"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()) { |
||||
ValidationErrors::ScopedField field(errors, ".address"); |
||||
errors->AddError("field not present"); |
||||
} |
||||
if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) { |
||||
ValidationErrors::ScopedField field(errors, ".port_value"); |
||||
errors->AddError("field not present"); |
||||
} |
||||
logical_dns.hostname = JoinHostPort( |
||||
address_str, |
||||
envoy_config_core_v3_SocketAddress_port_value(socket_address)); |
||||
return logical_dns; |
||||
} |
||||
|
||||
XdsClusterResource::Aggregate AggregateClusterParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_config, ValidationErrors* errors) { |
||||
XdsClusterResource::Aggregate aggregate; |
||||
const auto* aggregate_cluster_config = |
||||
envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse( |
||||
serialized_config.data(), serialized_config.size(), context.arena); |
||||
if (aggregate_cluster_config == nullptr) { |
||||
errors->AddError("can't parse aggregate cluster config"); |
||||
return aggregate; |
||||
} |
||||
size_t size; |
||||
const upb_StringView* clusters = |
||||
envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters( |
||||
aggregate_cluster_config, &size); |
||||
if (size == 0) { |
||||
ValidationErrors::ScopedField field(errors, ".clusters"); |
||||
errors->AddError("must be non-empty"); |
||||
} |
||||
for (size_t i = 0; i < size; ++i) { |
||||
aggregate.prioritized_cluster_names.emplace_back( |
||||
UpbStringToStdString(clusters[i])); |
||||
} |
||||
return aggregate; |
||||
} |
||||
|
||||
void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_cluster_v3_Cluster* cluster, |
||||
XdsClusterResource* cds_update, |
||||
ValidationErrors* errors) { |
||||
// First, check the new load_balancing_policy field.
|
||||
const auto* load_balancing_policy = |
||||
envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster); |
||||
if (load_balancing_policy != nullptr) { |
||||
const auto& registry = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()) |
||||
.lb_policy_registry(); |
||||
ValidationErrors::ScopedField field(errors, ".load_balancing_policy"); |
||||
const size_t original_error_count = errors->size(); |
||||
cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig( |
||||
context, load_balancing_policy, errors); |
||||
// If there were no conversion errors, validate that the converted config
|
||||
// parses with the gRPC LB policy registry.
|
||||
if (original_error_count == errors->size()) { |
||||
auto config = CoreConfiguration::Get() |
||||
.lb_policy_registry() |
||||
.ParseLoadBalancingConfig( |
||||
Json::FromArray(cds_update->lb_policy_config)); |
||||
if (!config.ok()) errors->AddError(config.status().message()); |
||||
} |
||||
return; |
||||
} |
||||
// Didn't find load_balancing_policy field, so fall back to the old
|
||||
// lb_policy enum field.
|
||||
if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == |
||||
envoy_config_cluster_v3_Cluster_ROUND_ROBIN) { |
||||
cds_update->lb_policy_config = { |
||||
Json::FromObject({ |
||||
{"xds_wrr_locality_experimental", |
||||
Json::FromObject({ |
||||
{"childPolicy", Json::FromArray({ |
||||
Json::FromObject({ |
||||
{"round_robin", Json::FromObject({})}, |
||||
}), |
||||
})}, |
||||
})}, |
||||
}), |
||||
}; |
||||
} else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) == |
||||
envoy_config_cluster_v3_Cluster_RING_HASH) { |
||||
// Record ring hash lb config
|
||||
auto* ring_hash_config = |
||||
envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster); |
||||
uint64_t min_ring_size = 1024; |
||||
uint64_t max_ring_size = 8388608; |
||||
if (ring_hash_config != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config"); |
||||
const google_protobuf_UInt64Value* uint64_value = |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size( |
||||
ring_hash_config); |
||||
if (uint64_value != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".maximum_ring_size"); |
||||
max_ring_size = google_protobuf_UInt64Value_value(uint64_value); |
||||
if (max_ring_size > 8388608 || max_ring_size == 0) { |
||||
errors->AddError("must be in the range of 1 to 8388608"); |
||||
} |
||||
} |
||||
uint64_value = |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size( |
||||
ring_hash_config); |
||||
if (uint64_value != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".minimum_ring_size"); |
||||
min_ring_size = google_protobuf_UInt64Value_value(uint64_value); |
||||
if (min_ring_size > 8388608 || min_ring_size == 0) { |
||||
errors->AddError("must be in the range of 1 to 8388608"); |
||||
} |
||||
if (min_ring_size > max_ring_size) { |
||||
errors->AddError("cannot be greater than maximum_ring_size"); |
||||
} |
||||
} |
||||
if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function( |
||||
ring_hash_config) != |
||||
envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) { |
||||
ValidationErrors::ScopedField field(errors, ".hash_function"); |
||||
errors->AddError("invalid hash function"); |
||||
} |
||||
} |
||||
cds_update->lb_policy_config = { |
||||
Json::FromObject({ |
||||
{"ring_hash_experimental", |
||||
Json::FromObject({ |
||||
{"minRingSize", Json::FromNumber(min_ring_size)}, |
||||
{"maxRingSize", Json::FromNumber(max_ring_size)}, |
||||
})}, |
||||
}), |
||||
}; |
||||
} else { |
||||
ValidationErrors::ScopedField field(errors, ".lb_policy"); |
||||
errors->AddError("LB policy is not supported"); |
||||
} |
||||
} |
||||
|
||||
void ParseUpstreamConfig( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_core_v3_TypedExtensionConfig* upstream_config, |
||||
XdsClusterResource* cds_update, ValidationErrors* errors) { |
||||
ValidationErrors::ScopedField field(errors, ".typed_config"); |
||||
const auto* typed_config = |
||||
envoy_config_core_v3_TypedExtensionConfig_typed_config(upstream_config); |
||||
auto extension = ExtractXdsExtension(context, typed_config, errors); |
||||
if (!extension.has_value()) return; |
||||
if (extension->type != |
||||
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions") { |
||||
ValidationErrors::ScopedField field(errors, ".type_url"); |
||||
errors->AddError("unsupported upstream config type"); |
||||
return; |
||||
} |
||||
absl::string_view* serialized_http_protocol_options = |
||||
absl::get_if<absl::string_view>(&extension->value); |
||||
if (serialized_http_protocol_options == nullptr) { |
||||
errors->AddError("can't decode HttpProtocolOptions"); |
||||
return; |
||||
} |
||||
const auto* http_protocol_options = |
||||
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_parse( |
||||
serialized_http_protocol_options->data(), |
||||
serialized_http_protocol_options->size(), context.arena); |
||||
if (http_protocol_options == nullptr) { |
||||
errors->AddError("can't decode HttpProtocolOptions"); |
||||
return; |
||||
} |
||||
ValidationErrors::ScopedField field2(errors, ".common_http_protocol_options"); |
||||
const auto* common_http_protocol_options = |
||||
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_common_http_protocol_options( |
||||
http_protocol_options); |
||||
if (common_http_protocol_options != nullptr) { |
||||
const auto* idle_timeout = |
||||
envoy_config_core_v3_HttpProtocolOptions_idle_timeout( |
||||
common_http_protocol_options); |
||||
if (idle_timeout != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".idle_timeout"); |
||||
cds_update->connection_idle_timeout = ParseDuration(idle_timeout, errors); |
||||
} |
||||
} |
||||
} |
||||
|
||||
absl::StatusOr<std::shared_ptr<const XdsClusterResource>> CdsResourceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_cluster_v3_Cluster* cluster) { |
||||
auto cds_update = std::make_shared<XdsClusterResource>(); |
||||
ValidationErrors errors; |
||||
// Check the cluster discovery type.
|
||||
if (envoy_config_cluster_v3_Cluster_type(cluster) == |
||||
envoy_config_cluster_v3_Cluster_EDS) { |
||||
cds_update->type = EdsConfigParse(cluster, &errors); |
||||
} else if (envoy_config_cluster_v3_Cluster_type(cluster) == |
||||
envoy_config_cluster_v3_Cluster_LOGICAL_DNS) { |
||||
cds_update->type = LogicalDnsParse(cluster, &errors); |
||||
} else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) { |
||||
ValidationErrors::ScopedField field(&errors, ".cluster_type"); |
||||
const auto* custom_cluster_type = |
||||
envoy_config_cluster_v3_Cluster_cluster_type(cluster); |
||||
CHECK_NE(custom_cluster_type, nullptr); |
||||
ValidationErrors::ScopedField field2(&errors, ".typed_config"); |
||||
const auto* typed_config = |
||||
envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config( |
||||
custom_cluster_type); |
||||
if (typed_config == nullptr) { |
||||
errors.AddError("field not present"); |
||||
} else { |
||||
absl::string_view type_url = absl::StripPrefix( |
||||
UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), |
||||
"type.googleapis.com/"); |
||||
if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") { |
||||
ValidationErrors::ScopedField field(&errors, ".type_url"); |
||||
errors.AddError( |
||||
absl::StrCat("unknown cluster_type extension: ", type_url)); |
||||
} else { |
||||
// Retrieve aggregate clusters.
|
||||
ValidationErrors::ScopedField field( |
||||
&errors, |
||||
".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]"); |
||||
absl::string_view serialized_config = |
||||
UpbStringToAbsl(google_protobuf_Any_value(typed_config)); |
||||
cds_update->type = |
||||
AggregateClusterParse(context, serialized_config, &errors); |
||||
} |
||||
} |
||||
} else { |
||||
ValidationErrors::ScopedField field(&errors, ".type"); |
||||
errors.AddError("unknown discovery type"); |
||||
} |
||||
// Check the LB policy.
|
||||
ParseLbPolicyConfig(context, cluster, cds_update.get(), &errors); |
||||
// transport_socket
|
||||
auto* transport_socket = |
||||
envoy_config_cluster_v3_Cluster_transport_socket(cluster); |
||||
if (transport_socket != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".transport_socket"); |
||||
cds_update->common_tls_context = |
||||
UpstreamTlsContextParse(context, transport_socket, &errors); |
||||
} |
||||
// 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)) { |
||||
ValidationErrors::ScopedField field(&errors, ".lrs_server"); |
||||
errors.AddError("ConfigSource is not self"); |
||||
} |
||||
cds_update->lrs_load_reporting_server.emplace( |
||||
static_cast<const GrpcXdsServer&>(context.server)); |
||||
} |
||||
// Protocol options.
|
||||
auto* upstream_config = |
||||
envoy_config_cluster_v3_Cluster_upstream_config(cluster); |
||||
if (upstream_config != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".upstream_config"); |
||||
ParseUpstreamConfig(context, upstream_config, cds_update.get(), &errors); |
||||
} |
||||
// 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; |
||||
} |
||||
} |
||||
} |
||||
// Outlier detection config.
|
||||
if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) { |
||||
ValidationErrors::ScopedField field(&errors, ".outlier_detection"); |
||||
OutlierDetectionConfig outlier_detection_update; |
||||
const envoy_config_cluster_v3_OutlierDetection* outlier_detection = |
||||
envoy_config_cluster_v3_Cluster_outlier_detection(cluster); |
||||
const google_protobuf_Duration* duration = |
||||
envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".interval"); |
||||
outlier_detection_update.interval = ParseDuration(duration, &errors); |
||||
} |
||||
duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time( |
||||
outlier_detection); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".base_ejection_time"); |
||||
outlier_detection_update.base_ejection_time = |
||||
ParseDuration(duration, &errors); |
||||
} |
||||
duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time( |
||||
outlier_detection); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".max_ejection_time"); |
||||
outlier_detection_update.max_ejection_time = |
||||
ParseDuration(duration, &errors); |
||||
} |
||||
const google_protobuf_UInt32Value* max_ejection_percent = |
||||
envoy_config_cluster_v3_OutlierDetection_max_ejection_percent( |
||||
outlier_detection); |
||||
if (max_ejection_percent != nullptr) { |
||||
outlier_detection_update.max_ejection_percent = |
||||
google_protobuf_UInt32Value_value(max_ejection_percent); |
||||
if (outlier_detection_update.max_ejection_percent > 100) { |
||||
ValidationErrors::ScopedField field(&errors, ".max_ejection_percent"); |
||||
errors.AddError("value must be <= 100"); |
||||
} |
||||
} |
||||
const google_protobuf_UInt32Value* enforcing_success_rate = |
||||
envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate( |
||||
outlier_detection); |
||||
if (enforcing_success_rate != nullptr) { |
||||
uint32_t enforcement_percentage = |
||||
google_protobuf_UInt32Value_value(enforcing_success_rate); |
||||
if (enforcement_percentage > 100) { |
||||
ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate"); |
||||
errors.AddError("value must be <= 100"); |
||||
} |
||||
if (enforcement_percentage != 0) { |
||||
OutlierDetectionConfig::SuccessRateEjection success_rate_ejection; |
||||
success_rate_ejection.enforcement_percentage = enforcement_percentage; |
||||
const google_protobuf_UInt32Value* minimum_hosts = |
||||
envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts( |
||||
outlier_detection); |
||||
if (minimum_hosts != nullptr) { |
||||
success_rate_ejection.minimum_hosts = |
||||
google_protobuf_UInt32Value_value(minimum_hosts); |
||||
} |
||||
const google_protobuf_UInt32Value* request_volume = |
||||
envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume( |
||||
outlier_detection); |
||||
if (request_volume != nullptr) { |
||||
success_rate_ejection.request_volume = |
||||
google_protobuf_UInt32Value_value(request_volume); |
||||
} |
||||
const google_protobuf_UInt32Value* stdev_factor = |
||||
envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor( |
||||
outlier_detection); |
||||
if (stdev_factor != nullptr) { |
||||
success_rate_ejection.stdev_factor = |
||||
google_protobuf_UInt32Value_value(stdev_factor); |
||||
} |
||||
outlier_detection_update.success_rate_ejection = success_rate_ejection; |
||||
} |
||||
} |
||||
const google_protobuf_UInt32Value* enforcing_failure_percentage = |
||||
envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage( |
||||
outlier_detection); |
||||
if (enforcing_failure_percentage != nullptr) { |
||||
uint32_t enforcement_percentage = |
||||
google_protobuf_UInt32Value_value(enforcing_failure_percentage); |
||||
if (enforcement_percentage > 100) { |
||||
ValidationErrors::ScopedField field(&errors, |
||||
".enforcing_failure_percentage"); |
||||
errors.AddError("value must be <= 100"); |
||||
} |
||||
if (enforcement_percentage != 0) { |
||||
OutlierDetectionConfig::FailurePercentageEjection |
||||
failure_percentage_ejection; |
||||
failure_percentage_ejection.enforcement_percentage = |
||||
enforcement_percentage; |
||||
const google_protobuf_UInt32Value* minimum_hosts = |
||||
envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts( |
||||
outlier_detection); |
||||
if (minimum_hosts != nullptr) { |
||||
failure_percentage_ejection.minimum_hosts = |
||||
google_protobuf_UInt32Value_value(minimum_hosts); |
||||
} |
||||
const google_protobuf_UInt32Value* request_volume = |
||||
envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume( |
||||
outlier_detection); |
||||
if (request_volume != nullptr) { |
||||
failure_percentage_ejection.request_volume = |
||||
google_protobuf_UInt32Value_value(request_volume); |
||||
} |
||||
const google_protobuf_UInt32Value* threshold = |
||||
envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold( |
||||
outlier_detection); |
||||
if (threshold != nullptr) { |
||||
failure_percentage_ejection.threshold = |
||||
google_protobuf_UInt32Value_value(threshold); |
||||
if (enforcement_percentage > 100) { |
||||
ValidationErrors::ScopedField field( |
||||
&errors, ".failure_percentage_threshold"); |
||||
errors.AddError("value must be <= 100"); |
||||
} |
||||
} |
||||
outlier_detection_update.failure_percentage_ejection = |
||||
failure_percentage_ejection; |
||||
} |
||||
} |
||||
cds_update->outlier_detection = outlier_detection_update; |
||||
} |
||||
// Validate override host status.
|
||||
const auto* common_lb_config = |
||||
envoy_config_cluster_v3_Cluster_common_lb_config(cluster); |
||||
bool override_host_status_found = false; |
||||
if (common_lb_config != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".common_lb_config"); |
||||
const auto* override_host_status = |
||||
envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status( |
||||
common_lb_config); |
||||
if (override_host_status != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, ".override_host_status"); |
||||
size_t size; |
||||
const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses( |
||||
override_host_status, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
auto status = XdsHealthStatus::FromUpb(statuses[i]); |
||||
if (status.has_value()) { |
||||
cds_update->override_host_statuses.Add(*status); |
||||
} |
||||
} |
||||
override_host_status_found = true; |
||||
} |
||||
} |
||||
// If the field is not set, we default to [UNKNOWN, HEALTHY].
|
||||
if (!override_host_status_found) { |
||||
cds_update->override_host_statuses.Add( |
||||
XdsHealthStatus(XdsHealthStatus::kUnknown)); |
||||
cds_update->override_host_statuses.Add( |
||||
XdsHealthStatus(XdsHealthStatus::kHealthy)); |
||||
} |
||||
// Record telemetry labels (if any).
|
||||
const envoy_config_core_v3_Metadata* metadata = |
||||
envoy_config_cluster_v3_Cluster_metadata(cluster); |
||||
if (metadata != nullptr) { |
||||
google_protobuf_Struct* telemetry_labels_struct; |
||||
if (envoy_config_core_v3_Metadata_filter_metadata_get( |
||||
metadata, |
||||
StdStringToUpbString( |
||||
absl::string_view("com.google.csm.telemetry_labels")), |
||||
&telemetry_labels_struct)) { |
||||
size_t iter = kUpb_Map_Begin; |
||||
const google_protobuf_Struct_FieldsEntry* fields_entry; |
||||
while ((fields_entry = google_protobuf_Struct_fields_next( |
||||
telemetry_labels_struct, &iter)) != nullptr) { |
||||
// Adds any entry whose value is a string to telemetry_labels.
|
||||
const google_protobuf_Value* value = |
||||
google_protobuf_Struct_FieldsEntry_value(fields_entry); |
||||
if (google_protobuf_Value_has_string_value(value)) { |
||||
if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( |
||||
fields_entry)) == "service_name") { |
||||
cds_update->service_telemetry_label = RefCountedStringValue( |
||||
UpbStringToAbsl(google_protobuf_Value_string_value(value))); |
||||
} else if (UpbStringToAbsl(google_protobuf_Struct_FieldsEntry_key( |
||||
fields_entry)) == "service_namespace") { |
||||
cds_update->namespace_telemetry_label = RefCountedStringValue( |
||||
UpbStringToAbsl(google_protobuf_Value_string_value(value))); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// Return result.
|
||||
if (!errors.ok()) { |
||||
return errors.status(absl::StatusCode::kInvalidArgument, |
||||
"errors validating Cluster resource"); |
||||
} |
||||
return cds_update; |
||||
} |
||||
|
||||
void MaybeLogCluster(const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_cluster_v3_Cluster* cluster) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { |
||||
const upb_MessageDef* msg_type = |
||||
envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab); |
||||
char buf[10240]; |
||||
upb_TextEncode(reinterpret_cast<const upb_Message*>(cluster), msg_type, |
||||
nullptr, 0, buf, sizeof(buf)); |
||||
VLOG(2) << "[xds_client " << context.client << "] Cluster: " << buf; |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
XdsResourceType::DecodeResult XdsClusterResourceType::Decode( |
||||
const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const { |
||||
DecodeResult result; |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_cluster_v3_Cluster_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
result.resource = |
||||
absl::InvalidArgumentError("Can't parse Cluster resource."); |
||||
return result; |
||||
} |
||||
MaybeLogCluster(context, resource); |
||||
// Validate resource.
|
||||
result.name = |
||||
UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource)); |
||||
auto cds_resource = CdsResourceParse(context, resource); |
||||
if (!cds_resource.ok()) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(ERROR) << "[xds_client " << context.client << "] invalid Cluster " |
||||
<< *result.name << ": " << cds_resource.status(); |
||||
} |
||||
result.resource = cds_resource.status(); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(INFO) << "[xds_client " << context.client << "] parsed Cluster " |
||||
<< *result.name << ": " << (*cds_resource)->ToString(); |
||||
} |
||||
result.resource = std::move(*cds_resource); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,57 @@ |
||||
//
|
||||
// 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_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "envoy/config/cluster/v3/cluster.upbdefs.h" |
||||
#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h" |
||||
#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h" |
||||
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h" |
||||
#include "upb/reflection/def.h" |
||||
|
||||
#include "src/core/xds/grpc/xds_cluster.h" |
||||
#include "src/core/xds/xds_client/xds_client.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type_impl.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsClusterResourceType |
||||
: public XdsResourceTypeImpl<XdsClusterResourceType, XdsClusterResource> { |
||||
public: |
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.cluster.v3.Cluster"; |
||||
} |
||||
|
||||
DecodeResult Decode(const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const override; |
||||
|
||||
bool AllResourcesRequiredInSotW() const override { return true; } |
||||
|
||||
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { |
||||
envoy_config_cluster_v3_Cluster_getmsgdef(symtab); |
||||
envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab); |
||||
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef( |
||||
symtab); |
||||
envoy_extensions_upstreams_http_v3_HttpProtocolOptions_getmsgdef(symtab); |
||||
} |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_CLUSTER_PARSER_H
|
@ -0,0 +1,449 @@ |
||||
//
|
||||
// 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 "src/core/xds/grpc/xds_common_types_parser.h" |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include <algorithm> |
||||
#include <map> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.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/struct.upb.h" |
||||
#include "google/protobuf/struct.upbdefs.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "upb/base/status.hpp" |
||||
#include "upb/json/encode.h" |
||||
#include "upb/mem/arena.h" |
||||
#include "xds/type/v3/typed_struct.upb.h" |
||||
|
||||
#include <grpc/support/json.h> |
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/util/json/json_reader.h" |
||||
#include "src/core/util/upb_utils.h" |
||||
#include "src/core/xds/grpc/xds_bootstrap_grpc.h" |
||||
#include "src/core/xds/xds_client/xds_client.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
//
|
||||
// ParseDuration()
|
||||
//
|
||||
|
||||
Duration ParseDuration(const google_protobuf_Duration* proto_duration, |
||||
ValidationErrors* errors) { |
||||
int64_t seconds = google_protobuf_Duration_seconds(proto_duration); |
||||
if (seconds < 0 || seconds > 315576000000) { |
||||
ValidationErrors::ScopedField field(errors, ".seconds"); |
||||
errors->AddError("value must be in the range [0, 315576000000]"); |
||||
} |
||||
int32_t nanos = google_protobuf_Duration_nanos(proto_duration); |
||||
if (nanos < 0 || nanos > 999999999) { |
||||
ValidationErrors::ScopedField field(errors, ".nanos"); |
||||
errors->AddError("value must be in the range [0, 999999999]"); |
||||
} |
||||
return Duration::FromSecondsAndNanoseconds(seconds, nanos); |
||||
} |
||||
|
||||
//
|
||||
// CommonTlsContextParse()
|
||||
//
|
||||
|
||||
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.
|
||||
CommonTlsContext::CertificateProviderPluginInstance |
||||
CertificateProviderInstanceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance* |
||||
certificate_provider_instance_proto, |
||||
ValidationErrors* errors) { |
||||
CommonTlsContext::CertificateProviderPluginInstance cert_provider; |
||||
cert_provider.instance_name = UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name( |
||||
certificate_provider_instance_proto)); |
||||
const auto& bootstrap = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()); |
||||
if (bootstrap.certificate_providers().find(cert_provider.instance_name) == |
||||
bootstrap.certificate_providers().end()) { |
||||
ValidationErrors::ScopedField field(errors, ".instance_name"); |
||||
errors->AddError( |
||||
absl::StrCat("unrecognized certificate provider instance name: ", |
||||
cert_provider.instance_name)); |
||||
} |
||||
cert_provider.certificate_name = UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name( |
||||
certificate_provider_instance_proto)); |
||||
return cert_provider; |
||||
} |
||||
|
||||
CommonTlsContext::CertificateProviderPluginInstance |
||||
CertificateProviderPluginInstanceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance* |
||||
certificate_provider_plugin_instance_proto, |
||||
ValidationErrors* errors) { |
||||
CommonTlsContext::CertificateProviderPluginInstance cert_provider; |
||||
cert_provider.instance_name = UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_instance_name( |
||||
certificate_provider_plugin_instance_proto)); |
||||
const auto& bootstrap = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()); |
||||
if (bootstrap.certificate_providers().find(cert_provider.instance_name) == |
||||
bootstrap.certificate_providers().end()) { |
||||
ValidationErrors::ScopedField field(errors, ".instance_name"); |
||||
errors->AddError( |
||||
absl::StrCat("unrecognized certificate provider instance name: ", |
||||
cert_provider.instance_name)); |
||||
} |
||||
cert_provider.certificate_name = UpbStringToStdString( |
||||
envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance_certificate_name( |
||||
certificate_provider_plugin_instance_proto)); |
||||
return cert_provider; |
||||
} |
||||
|
||||
CommonTlsContext::CertificateValidationContext |
||||
CertificateValidationContextParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext* |
||||
certificate_validation_context_proto, |
||||
ValidationErrors* errors) { |
||||
CommonTlsContext::CertificateValidationContext certificate_validation_context; |
||||
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) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".match_subject_alt_names[", 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->AddError("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->AddError(string_matcher.status().message()); |
||||
continue; |
||||
} |
||||
if (type == StringMatcher::Type::kSafeRegex && ignore_case) { |
||||
ValidationErrors::ScopedField field(errors, ".ignore_case"); |
||||
errors->AddError("not supported for regex matcher"); |
||||
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) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
".ca_certificate_provider_instance"); |
||||
certificate_validation_context.ca_certificate_provider_instance = |
||||
CertificateProviderPluginInstanceParse( |
||||
context, ca_certificate_provider_instance, errors); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_spki( |
||||
certificate_validation_context_proto, nullptr) != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".verify_certificate_spki"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_verify_certificate_hash( |
||||
certificate_validation_context_proto, nullptr) != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".verify_certificate_hash"); |
||||
errors->AddError("feature 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)) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".require_signed_certificate_timestamp"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_crl( |
||||
certificate_validation_context_proto)) { |
||||
ValidationErrors::ScopedField field(errors, ".crl"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_has_custom_validator_config( |
||||
certificate_validation_context_proto)) { |
||||
ValidationErrors::ScopedField field(errors, ".custom_validator_config"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
return certificate_validation_context; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
CommonTlsContext CommonTlsContextParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* |
||||
common_tls_context_proto, |
||||
ValidationErrors* errors) { |
||||
CommonTlsContext common_tls_context; |
||||
// 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) { |
||||
ValidationErrors::ScopedField field(errors, ".combined_validation_context"); |
||||
auto* default_validation_context = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context( |
||||
combined_validation_context); |
||||
if (default_validation_context != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
".default_validation_context"); |
||||
common_tls_context.certificate_validation_context = |
||||
CertificateValidationContextParse(context, default_validation_context, |
||||
errors); |
||||
} |
||||
// 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.
|
||||
const 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) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".validation_context_certificate_provider_instance"); |
||||
common_tls_context.certificate_validation_context |
||||
.ca_certificate_provider_instance = CertificateProviderInstanceParse( |
||||
context, validation_context_certificate_provider_instance, errors); |
||||
} |
||||
} else { |
||||
auto* validation_context = |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_validation_context( |
||||
common_tls_context_proto); |
||||
if (validation_context != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".validation_context"); |
||||
common_tls_context.certificate_validation_context = |
||||
CertificateValidationContextParse(context, validation_context, |
||||
errors); |
||||
} else if ( |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_validation_context_sds_secret_config( |
||||
common_tls_context_proto)) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".validation_context_sds_secret_config"); |
||||
errors->AddError("feature 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) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
".tls_certificate_provider_instance"); |
||||
common_tls_context.tls_certificate_provider_instance = |
||||
CertificateProviderPluginInstanceParse( |
||||
context, tls_certificate_provider_instance, errors); |
||||
} 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) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".tls_certificate_certificate_provider_instance"); |
||||
common_tls_context.tls_certificate_provider_instance = |
||||
CertificateProviderInstanceParse( |
||||
context, tls_certificate_certificate_provider_instance, errors); |
||||
} else { |
||||
size_t size; |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificates( |
||||
common_tls_context_proto, &size); |
||||
if (size != 0) { |
||||
ValidationErrors::ScopedField field(errors, ".tls_certificates"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_tls_certificate_sds_secret_configs( |
||||
common_tls_context_proto, &size); |
||||
if (size != 0) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".tls_certificate_sds_secret_configs"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
} |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_tls_params( |
||||
common_tls_context_proto)) { |
||||
ValidationErrors::ScopedField field(errors, ".tls_params"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_has_custom_handshaker( |
||||
common_tls_context_proto)) { |
||||
ValidationErrors::ScopedField field(errors, ".custom_handshaker"); |
||||
errors->AddError("feature unsupported"); |
||||
} |
||||
return common_tls_context; |
||||
} |
||||
|
||||
//
|
||||
// ExtractXdsExtension
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
absl::StatusOr<Json> ParseProtobufStructToJson( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const google_protobuf_Struct* resource) { |
||||
upb::Status status; |
||||
const auto* msg_def = google_protobuf_Struct_getmsgdef(context.symtab); |
||||
size_t json_size = |
||||
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def, |
||||
context.symtab, 0, nullptr, 0, status.ptr()); |
||||
if (json_size == static_cast<size_t>(-1)) { |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat("error encoding google::Protobuf::Struct as JSON: ", |
||||
upb_Status_ErrorMessage(status.ptr()))); |
||||
} |
||||
void* buf = upb_Arena_Malloc(context.arena, json_size + 1); |
||||
upb_JsonEncode(reinterpret_cast<const upb_Message*>(resource), msg_def, |
||||
context.symtab, 0, reinterpret_cast<char*>(buf), json_size + 1, |
||||
status.ptr()); |
||||
auto json = JsonParse(reinterpret_cast<char*>(buf)); |
||||
if (!json.ok()) { |
||||
// This should never happen.
|
||||
return absl::InternalError( |
||||
absl::StrCat("error parsing JSON form of google::Protobuf::Struct " |
||||
"produced by upb library: ", |
||||
json.status().ToString())); |
||||
} |
||||
return std::move(*json); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::optional<XdsExtension> ExtractXdsExtension( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const google_protobuf_Any* any, ValidationErrors* errors) { |
||||
if (any == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
XdsExtension extension; |
||||
auto strip_type_prefix = [&]() { |
||||
ValidationErrors::ScopedField field(errors, ".type_url"); |
||||
if (extension.type.empty()) { |
||||
errors->AddError("field not present"); |
||||
return false; |
||||
} |
||||
size_t pos = extension.type.rfind('/'); |
||||
if (pos == absl::string_view::npos || pos == extension.type.size() - 1) { |
||||
errors->AddError(absl::StrCat("invalid value \"", extension.type, "\"")); |
||||
} else { |
||||
extension.type = extension.type.substr(pos + 1); |
||||
} |
||||
return true; |
||||
}; |
||||
extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any)); |
||||
if (!strip_type_prefix()) return absl::nullopt; |
||||
extension.validation_fields.emplace_back( |
||||
errors, absl::StrCat(".value[", extension.type, "]")); |
||||
absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any)); |
||||
if (extension.type == "xds.type.v3.TypedStruct" || |
||||
extension.type == "udpa.type.v1.TypedStruct") { |
||||
const auto* typed_struct = xds_type_v3_TypedStruct_parse( |
||||
any_value.data(), any_value.size(), context.arena); |
||||
if (typed_struct == nullptr) { |
||||
errors->AddError("could not parse"); |
||||
return absl::nullopt; |
||||
} |
||||
extension.type = |
||||
UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct)); |
||||
if (!strip_type_prefix()) return absl::nullopt; |
||||
extension.validation_fields.emplace_back( |
||||
errors, absl::StrCat(".value[", extension.type, "]")); |
||||
auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct); |
||||
if (protobuf_struct == nullptr) { |
||||
extension.value = Json::FromObject({}); // Default to empty object.
|
||||
} else { |
||||
auto json = ParseProtobufStructToJson(context, protobuf_struct); |
||||
if (!json.ok()) { |
||||
errors->AddError(json.status().message()); |
||||
return absl::nullopt; |
||||
} |
||||
extension.value = std::move(*json); |
||||
} |
||||
} else { |
||||
extension.value = any_value; |
||||
} |
||||
return std::move(extension); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,47 @@ |
||||
//
|
||||
// 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_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H |
||||
|
||||
#include "absl/types/optional.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/lib/gprpp/time.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/xds/grpc/xds_common_types.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
Duration ParseDuration(const google_protobuf_Duration* proto_duration, |
||||
ValidationErrors* errors); |
||||
|
||||
CommonTlsContext CommonTlsContextParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext* |
||||
common_tls_context_proto, |
||||
ValidationErrors* errors); |
||||
|
||||
absl::optional<XdsExtension> ExtractXdsExtension( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const google_protobuf_Any* any, ValidationErrors* errors); |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_COMMON_TYPES_PARSER_H
|
@ -0,0 +1,433 @@ |
||||
//
|
||||
// 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 "src/core/xds/grpc/xds_endpoint_parser.h" |
||||
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include <algorithm> |
||||
#include <limits> |
||||
#include <memory> |
||||
#include <set> |
||||
#include <vector> |
||||
|
||||
#include "absl/log/check.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/types/optional.h" |
||||
#include "envoy/config/core/v3/address.upb.h" |
||||
#include "envoy/config/core/v3/base.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 <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/address_utils/parse_address.h" |
||||
#include "src/core/lib/address_utils/sockaddr_utils.h" |
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/env.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/iomgr/resolved_address.h" |
||||
#include "src/core/util/string.h" |
||||
#include "src/core/util/upb_utils.h" |
||||
#include "src/core/xds/grpc/xds_health_status.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
|
||||
// IWYU pragma: no_include "absl/meta/type_traits.h"
|
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
// TODO(roth): Remove this once dualstack support is stable.
|
||||
bool XdsDualstackEndpointsEnabled() { |
||||
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS"); |
||||
if (!value.has_value()) return false; |
||||
bool parsed_value; |
||||
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); |
||||
return parse_succeeded && parsed_value; |
||||
} |
||||
|
||||
void MaybeLogClusterLoadAssignment( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { |
||||
const upb_MessageDef* msg_type = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef( |
||||
context.symtab); |
||||
char buf[10240]; |
||||
upb_TextEncode(reinterpret_cast<const upb_Message*>(cla), msg_type, nullptr, |
||||
0, buf, sizeof(buf)); |
||||
VLOG(2) << "[xds_client " << context.client |
||||
<< "] ClusterLoadAssignment: " << buf; |
||||
} |
||||
} |
||||
|
||||
absl::optional<grpc_resolved_address> ParseCoreAddress( |
||||
const envoy_config_core_v3_Address* address, ValidationErrors* errors) { |
||||
if (address == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
ValidationErrors::ScopedField field(errors, ".socket_address"); |
||||
const envoy_config_core_v3_SocketAddress* socket_address = |
||||
envoy_config_core_v3_Address_socket_address(address); |
||||
if (socket_address == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
std::string address_str = UpbStringToStdString( |
||||
envoy_config_core_v3_SocketAddress_address(socket_address)); |
||||
uint32_t port; |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".port_value"); |
||||
port = envoy_config_core_v3_SocketAddress_port_value(socket_address); |
||||
if (GPR_UNLIKELY(port >> 16) != 0) { |
||||
errors->AddError("invalid port"); |
||||
return absl::nullopt; |
||||
} |
||||
} |
||||
auto addr = StringToSockaddr(address_str, port); |
||||
if (!addr.ok()) { |
||||
errors->AddError(addr.status().message()); |
||||
return absl::nullopt; |
||||
} |
||||
return *addr; |
||||
} |
||||
|
||||
absl::optional<EndpointAddresses> EndpointAddressesParse( |
||||
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, |
||||
ValidationErrors* errors) { |
||||
// health_status
|
||||
const int32_t health_status = |
||||
envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint); |
||||
auto status = XdsHealthStatus::FromUpb(health_status); |
||||
if (!status.has_value()) return absl::nullopt; |
||||
// load_balancing_weight
|
||||
uint32_t weight = 1; |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".load_balancing_weight"); |
||||
const google_protobuf_UInt32Value* load_balancing_weight = |
||||
envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); |
||||
if (load_balancing_weight != nullptr) { |
||||
weight = google_protobuf_UInt32Value_value(load_balancing_weight); |
||||
if (weight == 0) { |
||||
errors->AddError("must be greater than 0"); |
||||
} |
||||
} |
||||
} |
||||
// endpoint
|
||||
std::vector<grpc_resolved_address> addresses; |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".endpoint"); |
||||
const envoy_config_endpoint_v3_Endpoint* endpoint = |
||||
envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); |
||||
if (endpoint == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".address"); |
||||
auto address = ParseCoreAddress( |
||||
envoy_config_endpoint_v3_Endpoint_address(endpoint), errors); |
||||
if (address.has_value()) addresses.push_back(*address); |
||||
} |
||||
if (XdsDualstackEndpointsEnabled()) { |
||||
size_t size; |
||||
auto* additional_addresses = |
||||
envoy_config_endpoint_v3_Endpoint_additional_addresses(endpoint, |
||||
&size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".additional_addresses[", i, "].address")); |
||||
auto address = ParseCoreAddress( |
||||
envoy_config_endpoint_v3_Endpoint_AdditionalAddress_address( |
||||
additional_addresses[i]), |
||||
errors); |
||||
if (address.has_value()) addresses.push_back(*address); |
||||
} |
||||
} |
||||
} |
||||
if (addresses.empty()) return absl::nullopt; |
||||
// Convert to EndpointAddresses.
|
||||
return EndpointAddresses( |
||||
addresses, ChannelArgs() |
||||
.Set(GRPC_ARG_ADDRESS_WEIGHT, weight) |
||||
.Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status())); |
||||
} |
||||
|
||||
struct ParsedLocality { |
||||
size_t priority; |
||||
XdsEndpointResource::Priority::Locality locality; |
||||
}; |
||||
|
||||
struct ResolvedAddressLessThan { |
||||
bool operator()(const grpc_resolved_address& a1, |
||||
const grpc_resolved_address& a2) const { |
||||
if (a1.len != a2.len) return a1.len < a2.len; |
||||
return memcmp(a1.addr, a2.addr, a1.len) < 0; |
||||
} |
||||
}; |
||||
using ResolvedAddressSet = |
||||
std::set<grpc_resolved_address, ResolvedAddressLessThan>; |
||||
|
||||
absl::optional<ParsedLocality> LocalityParse( |
||||
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, |
||||
ResolvedAddressSet* address_set, ValidationErrors* errors) { |
||||
const size_t original_error_size = errors->size(); |
||||
ParsedLocality parsed_locality; |
||||
// load_balancing_weight
|
||||
// If LB weight is not specified or 0, it means this locality is assigned
|
||||
// no load.
|
||||
const google_protobuf_UInt32Value* lb_weight = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight( |
||||
locality_lb_endpoints); |
||||
parsed_locality.locality.lb_weight = |
||||
lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0; |
||||
if (parsed_locality.locality.lb_weight == 0) return absl::nullopt; |
||||
// locality
|
||||
const envoy_config_core_v3_Locality* locality = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_locality( |
||||
locality_lb_endpoints); |
||||
if (locality == nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".locality"); |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
// region
|
||||
std::string region = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); |
||||
// zone
|
||||
std::string zone = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality)); |
||||
// sub_zone
|
||||
std::string sub_zone = |
||||
UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality)); |
||||
parsed_locality.locality.name = MakeRefCounted<XdsLocalityName>( |
||||
std::move(region), std::move(zone), std::move(sub_zone)); |
||||
// lb_endpoints
|
||||
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) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
absl::StrCat(".lb_endpoints[", i, "]")); |
||||
auto endpoint = EndpointAddressesParse(lb_endpoints[i], errors); |
||||
if (endpoint.has_value()) { |
||||
for (const auto& address : endpoint->addresses()) { |
||||
bool inserted = address_set->insert(address).second; |
||||
if (!inserted) { |
||||
errors->AddError(absl::StrCat( |
||||
"duplicate endpoint address \"", |
||||
grpc_sockaddr_to_uri(&address).value_or("<unknown>"), "\"")); |
||||
} |
||||
} |
||||
parsed_locality.locality.endpoints.push_back(std::move(*endpoint)); |
||||
} |
||||
} |
||||
// priority
|
||||
parsed_locality.priority = |
||||
envoy_config_endpoint_v3_LocalityLbEndpoints_priority( |
||||
locality_lb_endpoints); |
||||
// Return result.
|
||||
if (original_error_size != errors->size()) return absl::nullopt; |
||||
return parsed_locality; |
||||
} |
||||
|
||||
void DropParseAndAppend( |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* |
||||
drop_overload, |
||||
XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) { |
||||
// category
|
||||
std::string category = UpbStringToStdString( |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category( |
||||
drop_overload)); |
||||
if (category.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".category"); |
||||
errors->AddError("empty drop category name"); |
||||
} |
||||
// drop_percentage
|
||||
uint32_t numerator; |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".drop_percentage"); |
||||
const envoy_type_v3_FractionalPercent* drop_percentage = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( |
||||
drop_overload); |
||||
if (drop_percentage == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return; |
||||
} |
||||
numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage); |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".denominator"); |
||||
const int denominator = |
||||
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: |
||||
errors->AddError("unknown denominator type"); |
||||
} |
||||
} |
||||
// Cap numerator to 1000000.
|
||||
numerator = std::min(numerator, 1000000u); |
||||
} |
||||
// Add category.
|
||||
drop_config->AddCategory(std::move(category), numerator); |
||||
} |
||||
|
||||
absl::StatusOr<std::shared_ptr<const XdsEndpointResource>> EdsResourceParse( |
||||
const XdsResourceType::DecodeContext& /*context*/, |
||||
const envoy_config_endpoint_v3_ClusterLoadAssignment* |
||||
cluster_load_assignment) { |
||||
ValidationErrors errors; |
||||
auto eds_resource = std::make_shared<XdsEndpointResource>(); |
||||
// endpoints
|
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "endpoints"); |
||||
ResolvedAddressSet address_set; |
||||
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 i = 0; i < locality_size; ++i) { |
||||
ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); |
||||
auto parsed_locality = LocalityParse(endpoints[i], &address_set, &errors); |
||||
if (parsed_locality.has_value()) { |
||||
CHECK_NE(parsed_locality->locality.lb_weight, 0u); |
||||
// Make sure prorities is big enough. Note that they might not
|
||||
// arrive in priority order.
|
||||
if (eds_resource->priorities.size() < parsed_locality->priority + 1) { |
||||
eds_resource->priorities.resize(parsed_locality->priority + 1); |
||||
} |
||||
auto& locality_map = |
||||
eds_resource->priorities[parsed_locality->priority].localities; |
||||
auto it = locality_map.find(parsed_locality->locality.name.get()); |
||||
if (it != locality_map.end()) { |
||||
errors.AddError(absl::StrCat( |
||||
"duplicate locality ", |
||||
parsed_locality->locality.name->human_readable_string() |
||||
.as_string_view(), |
||||
" found in priority ", parsed_locality->priority)); |
||||
} else { |
||||
locality_map.emplace(parsed_locality->locality.name.get(), |
||||
std::move(parsed_locality->locality)); |
||||
} |
||||
} |
||||
} |
||||
for (size_t i = 0; i < eds_resource->priorities.size(); ++i) { |
||||
const auto& priority = eds_resource->priorities[i]; |
||||
if (priority.localities.empty()) { |
||||
errors.AddError(absl::StrCat("priority ", i, " empty")); |
||||
} else { |
||||
// Check that the sum of the locality weights in this priority
|
||||
// does not exceed the max value for a uint32.
|
||||
uint64_t total_weight = 0; |
||||
for (const auto& p : priority.localities) { |
||||
total_weight += p.second.lb_weight; |
||||
if (total_weight > std::numeric_limits<uint32_t>::max()) { |
||||
errors.AddError( |
||||
absl::StrCat("sum of locality weights for priority ", i, |
||||
" exceeds uint32 max")); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// policy
|
||||
const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy( |
||||
cluster_load_assignment); |
||||
if (policy != nullptr) { |
||||
ValidationErrors::ScopedField field(&errors, "policy"); |
||||
size_t drop_size; |
||||
const auto* const* drop_overload = |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( |
||||
policy, &drop_size); |
||||
if (drop_size > 0) { |
||||
eds_resource->drop_config = |
||||
MakeRefCounted<XdsEndpointResource::DropConfig>(); |
||||
} |
||||
for (size_t i = 0; i < drop_size; ++i) { |
||||
ValidationErrors::ScopedField field( |
||||
&errors, absl::StrCat(".drop_overloads[", i, "]")); |
||||
DropParseAndAppend(drop_overload[i], eds_resource->drop_config.get(), |
||||
&errors); |
||||
} |
||||
} |
||||
// Return result.
|
||||
if (!errors.ok()) { |
||||
return errors.status(absl::StatusCode::kInvalidArgument, |
||||
"errors parsing EDS resource"); |
||||
} |
||||
return eds_resource; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
XdsResourceType::DecodeResult XdsEndpointResourceType::Decode( |
||||
const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const { |
||||
DecodeResult result; |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
result.resource = absl::InvalidArgumentError( |
||||
"Can't parse ClusterLoadAssignment resource."); |
||||
return result; |
||||
} |
||||
MaybeLogClusterLoadAssignment(context, resource); |
||||
// Validate resource.
|
||||
result.name = UpbStringToStdString( |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource)); |
||||
auto eds_resource = EdsResourceParse(context, resource); |
||||
if (!eds_resource.ok()) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(ERROR) << "[xds_client " << context.client |
||||
<< "] invalid ClusterLoadAssignment " << *result.name << ": " |
||||
<< eds_resource.status(); |
||||
} |
||||
result.resource = eds_resource.status(); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(INFO) << "[xds_client " << context.client |
||||
<< "] parsed ClusterLoadAssignment " << *result.name << ": " |
||||
<< (*eds_resource)->ToString(); |
||||
} |
||||
result.resource = std::move(*eds_resource); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,48 @@ |
||||
//
|
||||
// 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_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "envoy/config/endpoint/v3/endpoint.upbdefs.h" |
||||
#include "upb/reflection/def.h" |
||||
|
||||
#include "src/core/xds/grpc/xds_endpoint.h" |
||||
#include "src/core/xds/xds_client/xds_client.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type_impl.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsEndpointResourceType final |
||||
: public XdsResourceTypeImpl<XdsEndpointResourceType, XdsEndpointResource> { |
||||
public: |
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.endpoint.v3.ClusterLoadAssignment"; |
||||
} |
||||
|
||||
DecodeResult Decode(const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const override; |
||||
|
||||
void InitUpbSymtab(XdsClient*, upb_DefPool* symtab) const override { |
||||
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab); |
||||
} |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ENDPOINT_PARSER_H
|
@ -0,0 +1,98 @@ |
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H |
||||
|
||||
#include <map> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
#include "upb/reflection/def.h" |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/channel/channel_fwd.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/transport/interception_chain.h" |
||||
#include "src/core/xds/grpc/xds_common_types.h" |
||||
#include "src/core/xds/grpc/xds_http_filter.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Exposed for testing purposes only.
|
||||
class XdsHttpRouterFilter final : public XdsHttpFilterImpl { |
||||
public: |
||||
absl::string_view ConfigProtoName() const override; |
||||
absl::string_view OverrideConfigProtoName() const override; |
||||
void PopulateSymtab(upb_DefPool* symtab) const override; |
||||
absl::optional<FilterConfig> GenerateFilterConfig( |
||||
const XdsResourceType::DecodeContext& context, XdsExtension extension, |
||||
ValidationErrors* errors) const override; |
||||
absl::optional<FilterConfig> GenerateFilterConfigOverride( |
||||
const XdsResourceType::DecodeContext& context, XdsExtension extension, |
||||
ValidationErrors* errors) const override; |
||||
void AddFilter(InterceptionChainBuilder& /*builder*/) const override {} |
||||
const grpc_channel_filter* channel_filter() const override { return nullptr; } |
||||
absl::StatusOr<ServiceConfigJsonEntry> GenerateServiceConfig( |
||||
const FilterConfig& /*hcm_filter_config*/, |
||||
const FilterConfig* /*filter_config_override*/) const override { |
||||
// This will never be called, since channel_filter() returns null.
|
||||
return absl::UnimplementedError("router filter should never be called"); |
||||
} |
||||
bool IsSupportedOnClients() const override { return true; } |
||||
bool IsSupportedOnServers() const override { return true; } |
||||
bool IsTerminalFilter() const override { return true; } |
||||
}; |
||||
|
||||
class XdsHttpFilterRegistry final { |
||||
public: |
||||
explicit XdsHttpFilterRegistry(bool register_builtins = true); |
||||
|
||||
// Not copyable.
|
||||
XdsHttpFilterRegistry(const XdsHttpFilterRegistry&) = delete; |
||||
XdsHttpFilterRegistry& operator=(const XdsHttpFilterRegistry&) = delete; |
||||
|
||||
// Movable.
|
||||
XdsHttpFilterRegistry(XdsHttpFilterRegistry&& other) noexcept |
||||
: owning_list_(std::move(other.owning_list_)), |
||||
registry_map_(std::move(other.registry_map_)) {} |
||||
XdsHttpFilterRegistry& operator=(XdsHttpFilterRegistry&& other) noexcept { |
||||
owning_list_ = std::move(other.owning_list_); |
||||
registry_map_ = std::move(other.registry_map_); |
||||
return *this; |
||||
} |
||||
|
||||
void RegisterFilter(std::unique_ptr<XdsHttpFilterImpl> filter); |
||||
|
||||
const XdsHttpFilterImpl* GetFilterForType( |
||||
absl::string_view proto_type_name) const; |
||||
|
||||
void PopulateSymtab(upb_DefPool* symtab) const; |
||||
|
||||
private: |
||||
std::vector<std::unique_ptr<XdsHttpFilterImpl>> owning_list_; |
||||
std::map<absl::string_view, XdsHttpFilterImpl*> registry_map_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_FILTER_REGISTRY_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,991 @@ |
||||
//
|
||||
// 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 "src/core/xds/grpc/xds_listener_parser.h" |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <set> |
||||
#include <utility> |
||||
|
||||
#include "absl/log/check.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/status/status.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/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/core/v3/protocol.upb.h" |
||||
#include "envoy/config/listener/v3/api_listener.upb.h" |
||||
#include "envoy/config/listener/v3/listener.upb.h" |
||||
#include "envoy/config/listener/v3/listener.upbdefs.h" |
||||
#include "envoy/config/listener/v3/listener_components.upb.h" |
||||
#include "envoy/config/route/v3/route.upb.h" |
||||
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h" |
||||
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" |
||||
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h" |
||||
#include "google/protobuf/any.upb.h" |
||||
#include "google/protobuf/duration.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "upb/text/encode.h" |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/address_utils/parse_address.h" |
||||
#include "src/core/lib/address_utils/sockaddr_utils.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/host_port.h" |
||||
#include "src/core/lib/gprpp/match.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/iomgr/sockaddr.h" |
||||
#include "src/core/lib/matchers/matchers.h" |
||||
#include "src/core/util/upb_utils.h" |
||||
#include "src/core/xds/grpc/xds_common_types.h" |
||||
#include "src/core/xds/grpc/xds_common_types_parser.h" |
||||
#include "src/core/xds/grpc/xds_route_config_parser.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
struct FilterChain { |
||||
struct FilterChainMatch { |
||||
uint32_t destination_port = 0; |
||||
std::vector<XdsListenerResource::FilterChainMap::CidrRange> prefix_ranges; |
||||
XdsListenerResource::FilterChainMap::ConnectionSourceType source_type = |
||||
XdsListenerResource::FilterChainMap::ConnectionSourceType::kAny; |
||||
std::vector<XdsListenerResource::FilterChainMap::CidrRange> |
||||
source_prefix_ranges; |
||||
std::vector<uint32_t> source_ports; |
||||
std::vector<std::string> server_names; |
||||
std::string transport_protocol; |
||||
std::vector<std::string> application_protocols; |
||||
|
||||
std::string ToString() const; |
||||
} filter_chain_match; |
||||
|
||||
std::shared_ptr<XdsListenerResource::FilterChainData> filter_chain_data; |
||||
}; |
||||
|
||||
std::string FilterChain::FilterChainMatch::ToString() const { |
||||
std::vector<std::string> contents; |
||||
if (destination_port != 0) { |
||||
contents.push_back(absl::StrCat("destination_port=", destination_port)); |
||||
} |
||||
if (!prefix_ranges.empty()) { |
||||
std::vector<std::string> prefix_ranges_content; |
||||
prefix_ranges_content.reserve(prefix_ranges.size()); |
||||
for (const auto& range : prefix_ranges) { |
||||
prefix_ranges_content.push_back(range.ToString()); |
||||
} |
||||
contents.push_back(absl::StrCat( |
||||
"prefix_ranges={", absl::StrJoin(prefix_ranges_content, ", "), "}")); |
||||
} |
||||
if (source_type == XdsListenerResource::FilterChainMap::ConnectionSourceType:: |
||||
kSameIpOrLoopback) { |
||||
contents.push_back("source_type=SAME_IP_OR_LOOPBACK"); |
||||
} else if (source_type == XdsListenerResource::FilterChainMap:: |
||||
ConnectionSourceType::kExternal) { |
||||
contents.push_back("source_type=EXTERNAL"); |
||||
} |
||||
if (!source_prefix_ranges.empty()) { |
||||
std::vector<std::string> source_prefix_ranges_content; |
||||
source_prefix_ranges_content.reserve(source_prefix_ranges.size()); |
||||
for (const auto& range : source_prefix_ranges) { |
||||
source_prefix_ranges_content.push_back(range.ToString()); |
||||
} |
||||
contents.push_back( |
||||
absl::StrCat("source_prefix_ranges={", |
||||
absl::StrJoin(source_prefix_ranges_content, ", "), "}")); |
||||
} |
||||
if (!source_ports.empty()) { |
||||
contents.push_back( |
||||
absl::StrCat("source_ports={", absl::StrJoin(source_ports, ", "), "}")); |
||||
} |
||||
if (!server_names.empty()) { |
||||
contents.push_back( |
||||
absl::StrCat("server_names={", absl::StrJoin(server_names, ", "), "}")); |
||||
} |
||||
if (!transport_protocol.empty()) { |
||||
contents.push_back(absl::StrCat("transport_protocol=", transport_protocol)); |
||||
} |
||||
if (!application_protocols.empty()) { |
||||
contents.push_back(absl::StrCat("application_protocols={", |
||||
absl::StrJoin(application_protocols, ", "), |
||||
"}")); |
||||
} |
||||
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); |
||||
} |
||||
|
||||
void MaybeLogHttpConnectionManager( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* |
||||
http_connection_manager_config) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { |
||||
const upb_MessageDef* msg_type = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( |
||||
context.symtab); |
||||
char buf[10240]; |
||||
upb_TextEncode( |
||||
reinterpret_cast<const upb_Message*>(http_connection_manager_config), |
||||
msg_type, nullptr, 0, buf, sizeof(buf)); |
||||
VLOG(2) << "[xds_client " << context.client |
||||
<< "] HttpConnectionManager: " << buf; |
||||
} |
||||
} |
||||
|
||||
XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse( |
||||
bool is_client, const XdsResourceType::DecodeContext& context, |
||||
XdsExtension extension, ValidationErrors* errors) { |
||||
if (extension.type != |
||||
"envoy.extensions.filters.network.http_connection_manager.v3" |
||||
".HttpConnectionManager") { |
||||
errors->AddError("unsupported filter type"); |
||||
return {}; |
||||
} |
||||
auto* serialized_hcm_config = |
||||
absl::get_if<absl::string_view>(&extension.value); |
||||
if (serialized_hcm_config == nullptr) { |
||||
errors->AddError("could not parse HttpConnectionManager config"); |
||||
return {}; |
||||
} |
||||
const auto* http_connection_manager_proto = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( |
||||
serialized_hcm_config->data(), serialized_hcm_config->size(), |
||||
context.arena); |
||||
if (http_connection_manager_proto == nullptr) { |
||||
errors->AddError("could not parse HttpConnectionManager config"); |
||||
return {}; |
||||
} |
||||
MaybeLogHttpConnectionManager(context, http_connection_manager_proto); |
||||
XdsListenerResource::HttpConnectionManager http_connection_manager; |
||||
// xff_num_trusted_hops -- must be zero as per
|
||||
// https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md
|
||||
if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops( |
||||
http_connection_manager_proto) != 0) { |
||||
ValidationErrors::ScopedField field(errors, ".xff_num_trusted_hops"); |
||||
errors->AddError("must be zero"); |
||||
} |
||||
// original_ip_detection_extensions -- must be empty as per
|
||||
// https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md
|
||||
{ |
||||
size_t size; |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_original_ip_detection_extensions( |
||||
http_connection_manager_proto, &size); |
||||
if (size != 0) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
".original_ip_detection_extensions"); |
||||
errors->AddError("must be empty"); |
||||
} |
||||
} |
||||
// common_http_protocol_options
|
||||
const envoy_config_core_v3_HttpProtocolOptions* options = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_common_http_protocol_options( |
||||
http_connection_manager_proto); |
||||
if (options != nullptr) { |
||||
// max_stream_duration
|
||||
const google_protobuf_Duration* duration = |
||||
envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".common_http_protocol_options.max_stream_duration"); |
||||
http_connection_manager.http_max_stream_duration = |
||||
ParseDuration(duration, errors); |
||||
} |
||||
} |
||||
// http_filters
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".http_filters"); |
||||
const auto& http_filter_registry = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()) |
||||
.http_filter_registry(); |
||||
size_t num_filters = 0; |
||||
const auto* http_filters = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( |
||||
http_connection_manager_proto, &num_filters); |
||||
std::set<absl::string_view> names_seen; |
||||
const size_t original_error_size = errors->size(); |
||||
for (size_t i = 0; i < num_filters; ++i) { |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); |
||||
const auto* http_filter = http_filters[i]; |
||||
// name
|
||||
absl::string_view name = UpbStringToAbsl( |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( |
||||
http_filter)); |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".name"); |
||||
if (name.empty()) { |
||||
errors->AddError("empty filter name"); |
||||
continue; |
||||
} |
||||
if (names_seen.find(name) != names_seen.end()) { |
||||
errors->AddError(absl::StrCat("duplicate HTTP filter name: ", name)); |
||||
continue; |
||||
} |
||||
} |
||||
names_seen.insert(name); |
||||
// is_optional
|
||||
const bool is_optional = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( |
||||
http_filter); |
||||
// typed_config
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".typed_config"); |
||||
const google_protobuf_Any* typed_config = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( |
||||
http_filter); |
||||
auto extension = ExtractXdsExtension(context, typed_config, errors); |
||||
if (!extension.has_value()) continue; |
||||
const XdsHttpFilterImpl* filter_impl = |
||||
http_filter_registry.GetFilterForType(extension->type); |
||||
if (filter_impl == nullptr) { |
||||
if (!is_optional) errors->AddError("unsupported filter type"); |
||||
continue; |
||||
} |
||||
if ((is_client && !filter_impl->IsSupportedOnClients()) || |
||||
(!is_client && !filter_impl->IsSupportedOnServers())) { |
||||
if (!is_optional) { |
||||
errors->AddError(absl::StrCat("filter is not supported on ", |
||||
is_client ? "clients" : "servers")); |
||||
} |
||||
continue; |
||||
} |
||||
absl::optional<XdsHttpFilterImpl::FilterConfig> filter_config = |
||||
filter_impl->GenerateFilterConfig(context, std::move(*extension), |
||||
errors); |
||||
if (filter_config.has_value()) { |
||||
http_connection_manager.http_filters.emplace_back( |
||||
XdsListenerResource::HttpConnectionManager::HttpFilter{ |
||||
std::string(name), std::move(*filter_config)}); |
||||
} |
||||
} |
||||
} |
||||
if (errors->size() == original_error_size && |
||||
http_connection_manager.http_filters.empty()) { |
||||
errors->AddError("expected at least one HTTP filter"); |
||||
} |
||||
// Make sure that the last filter is terminal and non-last filters are
|
||||
// non-terminal. Note that this check is being performed in a separate loop
|
||||
// to take care of the case where there are two terminal filters in the list
|
||||
// out of which only one gets added in the final list.
|
||||
for (const auto& http_filter : http_connection_manager.http_filters) { |
||||
const XdsHttpFilterImpl* filter_impl = |
||||
http_filter_registry.GetFilterForType( |
||||
http_filter.config.config_proto_type_name); |
||||
if (&http_filter != &http_connection_manager.http_filters.back()) { |
||||
// Filters before the last filter must not be terminal.
|
||||
if (filter_impl->IsTerminalFilter()) { |
||||
errors->AddError( |
||||
absl::StrCat("terminal filter for config type ", |
||||
http_filter.config.config_proto_type_name, |
||||
" must be the last filter in the chain")); |
||||
} |
||||
} else { |
||||
// The last filter must be terminal.
|
||||
if (!filter_impl->IsTerminalFilter()) { |
||||
errors->AddError( |
||||
absl::StrCat("non-terminal filter for config type ", |
||||
http_filter.config.config_proto_type_name, |
||||
" is the last filter in the chain")); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// Found inlined route_config. Parse it to find the cluster_name.
|
||||
if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_route_config( |
||||
http_connection_manager_proto)) { |
||||
const envoy_config_route_v3_RouteConfiguration* route_config = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_route_config( |
||||
http_connection_manager_proto); |
||||
ValidationErrors::ScopedField field(errors, ".route_config"); |
||||
http_connection_manager.route_config = |
||||
XdsRouteConfigResourceParse(context, route_config, errors); |
||||
} else { |
||||
// Validate that RDS must be used to get the route_config dynamically.
|
||||
const envoy_extensions_filters_network_http_connection_manager_v3_Rds* rds = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_rds( |
||||
http_connection_manager_proto); |
||||
if (rds == nullptr) { |
||||
errors->AddError("neither route_config nor rds fields are present"); |
||||
} else { |
||||
// Get the route_config_name.
|
||||
http_connection_manager.route_config = UpbStringToStdString( |
||||
envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( |
||||
rds)); |
||||
// Check that the ConfigSource specifies ADS.
|
||||
const envoy_config_core_v3_ConfigSource* config_source = |
||||
envoy_extensions_filters_network_http_connection_manager_v3_Rds_config_source( |
||||
rds); |
||||
ValidationErrors::ScopedField field(errors, ".rds.config_source"); |
||||
if (config_source == nullptr) { |
||||
errors->AddError("field not present"); |
||||
} else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && |
||||
!envoy_config_core_v3_ConfigSource_has_self(config_source)) { |
||||
errors->AddError("ConfigSource does not specify ADS or SELF"); |
||||
} |
||||
} |
||||
} |
||||
return http_connection_manager; |
||||
} |
||||
|
||||
absl::StatusOr<std::shared_ptr<const XdsListenerResource>> |
||||
LdsResourceParseClient( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_listener_v3_ApiListener* api_listener) { |
||||
auto lds_update = std::make_shared<XdsListenerResource>(); |
||||
ValidationErrors errors; |
||||
ValidationErrors::ScopedField field(&errors, "api_listener.api_listener"); |
||||
auto* api_listener_field = |
||||
envoy_config_listener_v3_ApiListener_api_listener(api_listener); |
||||
auto extension = ExtractXdsExtension(context, api_listener_field, &errors); |
||||
if (extension.has_value()) { |
||||
lds_update->listener = HttpConnectionManagerParse( |
||||
/*is_client=*/true, context, std::move(*extension), &errors); |
||||
} |
||||
if (!errors.ok()) { |
||||
return errors.status(absl::StatusCode::kInvalidArgument, |
||||
"errors validating ApiListener"); |
||||
} |
||||
return std::move(lds_update); |
||||
} |
||||
|
||||
XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_core_v3_TransportSocket* transport_socket, |
||||
ValidationErrors* errors) { |
||||
ValidationErrors::ScopedField field(errors, ".typed_config"); |
||||
const auto* typed_config = |
||||
envoy_config_core_v3_TransportSocket_typed_config(transport_socket); |
||||
auto extension = ExtractXdsExtension(context, typed_config, errors); |
||||
if (!extension.has_value()) return {}; |
||||
if (extension->type != |
||||
"envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext") { |
||||
ValidationErrors::ScopedField field(errors, ".type_url"); |
||||
errors->AddError("unsupported transport socket type"); |
||||
return {}; |
||||
} |
||||
absl::string_view* serialized_downstream_tls_context = |
||||
absl::get_if<absl::string_view>(&extension->value); |
||||
if (serialized_downstream_tls_context == nullptr) { |
||||
errors->AddError("can't decode DownstreamTlsContext"); |
||||
return {}; |
||||
} |
||||
const auto* downstream_tls_context_proto = |
||||
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse( |
||||
serialized_downstream_tls_context->data(), |
||||
serialized_downstream_tls_context->size(), context.arena); |
||||
if (downstream_tls_context_proto == nullptr) { |
||||
errors->AddError("can't decode DownstreamTlsContext"); |
||||
return {}; |
||||
} |
||||
XdsListenerResource::DownstreamTlsContext downstream_tls_context; |
||||
auto* common_tls_context = |
||||
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_common_tls_context( |
||||
downstream_tls_context_proto); |
||||
if (common_tls_context != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".common_tls_context"); |
||||
downstream_tls_context.common_tls_context = |
||||
CommonTlsContextParse(context, common_tls_context, errors); |
||||
// Note: We can't be more specific about the field name for this
|
||||
// error, because we don't know which fields they were found in
|
||||
// inside of CommonTlsContext, so we make the error message a bit
|
||||
// more verbose to compensate.
|
||||
if (!downstream_tls_context.common_tls_context |
||||
.certificate_validation_context.match_subject_alt_names.empty()) { |
||||
errors->AddError("match_subject_alt_names not supported on servers"); |
||||
} |
||||
} |
||||
// Note: We can't be more specific about the field name for this
|
||||
// error, because we don't know which fields they were found in
|
||||
// inside of CommonTlsContext, so we make the error message a bit
|
||||
// more verbose to compensate.
|
||||
if (downstream_tls_context.common_tls_context |
||||
.tls_certificate_provider_instance.instance_name.empty()) { |
||||
errors->AddError( |
||||
"TLS configuration provided but no " |
||||
"tls_certificate_provider_instance found"); |
||||
} |
||||
auto* require_client_certificate = |
||||
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_client_certificate( |
||||
downstream_tls_context_proto); |
||||
if (require_client_certificate != nullptr) { |
||||
downstream_tls_context.require_client_certificate = |
||||
google_protobuf_BoolValue_value(require_client_certificate); |
||||
if (downstream_tls_context.require_client_certificate && |
||||
downstream_tls_context.common_tls_context.certificate_validation_context |
||||
.ca_certificate_provider_instance.instance_name.empty()) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
".require_client_certificate"); |
||||
errors->AddError( |
||||
"client certificate required but no certificate " |
||||
"provider instance specified for validation"); |
||||
} |
||||
} |
||||
auto* require_sni = |
||||
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni( |
||||
downstream_tls_context_proto); |
||||
if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) { |
||||
ValidationErrors::ScopedField field(errors, ".require_sni"); |
||||
errors->AddError("field unsupported"); |
||||
} |
||||
if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy( |
||||
downstream_tls_context_proto) != |
||||
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) { |
||||
ValidationErrors::ScopedField field(errors, ".ocsp_staple_policy"); |
||||
errors->AddError("value must be LENIENT_STAPLING"); |
||||
} |
||||
return downstream_tls_context; |
||||
} |
||||
|
||||
absl::optional<XdsListenerResource::FilterChainMap::CidrRange> CidrRangeParse( |
||||
const envoy_config_core_v3_CidrRange* cidr_range_proto, |
||||
ValidationErrors* errors) { |
||||
ValidationErrors::ScopedField field(errors, ".address_prefix"); |
||||
XdsListenerResource::FilterChainMap::CidrRange cidr_range; |
||||
std::string address_prefix = UpbStringToStdString( |
||||
envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto)); |
||||
auto address = StringToSockaddr(address_prefix, /*port=*/0); |
||||
if (!address.ok()) { |
||||
errors->AddError(address.status().message()); |
||||
return absl::nullopt; |
||||
} |
||||
cidr_range.address = *address; |
||||
cidr_range.prefix_len = 0; |
||||
auto* prefix_len_proto = |
||||
envoy_config_core_v3_CidrRange_prefix_len(cidr_range_proto); |
||||
if (prefix_len_proto != nullptr) { |
||||
cidr_range.prefix_len = std::min( |
||||
google_protobuf_UInt32Value_value(prefix_len_proto), |
||||
(reinterpret_cast<const grpc_sockaddr*>(cidr_range.address.addr)) |
||||
->sa_family == GRPC_AF_INET |
||||
? uint32_t{32} |
||||
: uint32_t{128}); |
||||
} |
||||
// Normalize the network address by masking it with prefix_len
|
||||
grpc_sockaddr_mask_bits(&cidr_range.address, cidr_range.prefix_len); |
||||
return cidr_range; |
||||
} |
||||
|
||||
absl::optional<FilterChain::FilterChainMatch> FilterChainMatchParse( |
||||
const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto, |
||||
ValidationErrors* errors) { |
||||
FilterChain::FilterChainMatch filter_chain_match; |
||||
const size_t original_error_size = errors->size(); |
||||
// destination_port
|
||||
auto* destination_port = |
||||
envoy_config_listener_v3_FilterChainMatch_destination_port( |
||||
filter_chain_match_proto); |
||||
if (destination_port != nullptr) { |
||||
filter_chain_match.destination_port = |
||||
google_protobuf_UInt32Value_value(destination_port); |
||||
} |
||||
// prefix_ranges
|
||||
size_t size = 0; |
||||
auto* prefix_ranges = envoy_config_listener_v3_FilterChainMatch_prefix_ranges( |
||||
filter_chain_match_proto, &size); |
||||
filter_chain_match.prefix_ranges.reserve(size); |
||||
for (size_t i = 0; i < size; i++) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".prefix_ranges[", i, "]")); |
||||
auto cidr_range = CidrRangeParse(prefix_ranges[i], errors); |
||||
if (cidr_range.has_value()) { |
||||
filter_chain_match.prefix_ranges.push_back(*cidr_range); |
||||
} |
||||
} |
||||
// source_type
|
||||
filter_chain_match.source_type = |
||||
static_cast<XdsListenerResource::FilterChainMap::ConnectionSourceType>( |
||||
envoy_config_listener_v3_FilterChainMatch_source_type( |
||||
filter_chain_match_proto)); |
||||
// source_prefix_ranges
|
||||
auto* source_prefix_ranges = |
||||
envoy_config_listener_v3_FilterChainMatch_source_prefix_ranges( |
||||
filter_chain_match_proto, &size); |
||||
filter_chain_match.source_prefix_ranges.reserve(size); |
||||
for (size_t i = 0; i < size; i++) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".source_prefix_ranges[", i, "]")); |
||||
auto cidr_range = CidrRangeParse(source_prefix_ranges[i], errors); |
||||
if (cidr_range.has_value()) { |
||||
filter_chain_match.source_prefix_ranges.push_back(*cidr_range); |
||||
} |
||||
} |
||||
// source_ports
|
||||
auto* source_ports = envoy_config_listener_v3_FilterChainMatch_source_ports( |
||||
filter_chain_match_proto, &size); |
||||
filter_chain_match.source_ports.reserve(size); |
||||
for (size_t i = 0; i < size; i++) { |
||||
filter_chain_match.source_ports.push_back(source_ports[i]); |
||||
} |
||||
// server_names
|
||||
auto* server_names = envoy_config_listener_v3_FilterChainMatch_server_names( |
||||
filter_chain_match_proto, &size); |
||||
for (size_t i = 0; i < size; i++) { |
||||
filter_chain_match.server_names.push_back( |
||||
UpbStringToStdString(server_names[i])); |
||||
} |
||||
// transport_protocol
|
||||
filter_chain_match.transport_protocol = UpbStringToStdString( |
||||
envoy_config_listener_v3_FilterChainMatch_transport_protocol( |
||||
filter_chain_match_proto)); |
||||
// application_protocols
|
||||
auto* application_protocols = |
||||
envoy_config_listener_v3_FilterChainMatch_application_protocols( |
||||
filter_chain_match_proto, &size); |
||||
for (size_t i = 0; i < size; i++) { |
||||
filter_chain_match.application_protocols.push_back( |
||||
UpbStringToStdString(application_protocols[i])); |
||||
} |
||||
// Return result.
|
||||
if (errors->size() != original_error_size) return absl::nullopt; |
||||
return filter_chain_match; |
||||
} |
||||
|
||||
absl::optional<FilterChain> FilterChainParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_listener_v3_FilterChain* filter_chain_proto, |
||||
ValidationErrors* errors) { |
||||
FilterChain filter_chain; |
||||
const size_t original_error_size = errors->size(); |
||||
// filter_chain_match
|
||||
auto* filter_chain_match = |
||||
envoy_config_listener_v3_FilterChain_filter_chain_match( |
||||
filter_chain_proto); |
||||
if (filter_chain_match != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".filter_chain_match"); |
||||
auto match = FilterChainMatchParse(filter_chain_match, errors); |
||||
if (match.has_value()) { |
||||
filter_chain.filter_chain_match = std::move(*match); |
||||
} |
||||
} |
||||
// filters
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".filters"); |
||||
filter_chain.filter_chain_data = |
||||
std::make_shared<XdsListenerResource::FilterChainData>(); |
||||
size_t size = 0; |
||||
auto* filters = |
||||
envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); |
||||
if (size != 1) { |
||||
errors->AddError( |
||||
"must have exactly one filter (HttpConnectionManager -- " |
||||
"no other filter is supported at the moment)"); |
||||
} |
||||
// entries in filters list
|
||||
for (size_t i = 0; i < size; ++i) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat("[", i, "].typed_config")); |
||||
auto* typed_config = |
||||
envoy_config_listener_v3_Filter_typed_config(filters[i]); |
||||
auto extension = ExtractXdsExtension(context, typed_config, errors); |
||||
if (extension.has_value()) { |
||||
filter_chain.filter_chain_data->http_connection_manager = |
||||
HttpConnectionManagerParse(/*is_client=*/false, context, |
||||
std::move(*extension), errors); |
||||
} |
||||
} |
||||
} |
||||
// transport_socket
|
||||
auto* transport_socket = |
||||
envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto); |
||||
if (transport_socket != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".transport_socket"); |
||||
filter_chain.filter_chain_data->downstream_tls_context = |
||||
DownstreamTlsContextParse(context, transport_socket, errors); |
||||
} |
||||
// Return result.
|
||||
if (errors->size() != original_error_size) return absl::nullopt; |
||||
return filter_chain; |
||||
} |
||||
|
||||
absl::optional<std::string> AddressParse( |
||||
const envoy_config_core_v3_Address* address_proto, |
||||
ValidationErrors* errors) { |
||||
if (address_proto == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
ValidationErrors::ScopedField field(errors, ".socket_address"); |
||||
const auto* socket_address = |
||||
envoy_config_core_v3_Address_socket_address(address_proto); |
||||
if (socket_address == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".protocol"); |
||||
if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != |
||||
envoy_config_core_v3_SocketAddress_TCP) { |
||||
errors->AddError("value must be TCP"); |
||||
} |
||||
} |
||||
ValidationErrors::ScopedField field2(errors, ".port_value"); |
||||
uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); |
||||
if (port > 65535) { |
||||
errors->AddError("invalid port"); |
||||
return absl::nullopt; |
||||
} |
||||
return JoinHostPort( |
||||
UpbStringToAbsl( |
||||
envoy_config_core_v3_SocketAddress_address(socket_address)), |
||||
port); |
||||
} |
||||
|
||||
// An intermediate map for filter chains that we create to validate the list of
|
||||
// filter chains received from the control plane and to finally create
|
||||
// XdsListenerResource::FilterChainMap
|
||||
struct InternalFilterChainMap { |
||||
using SourceIpMap = |
||||
std::map<std::string, XdsListenerResource::FilterChainMap::SourceIp>; |
||||
using ConnectionSourceTypesArray = std::array<SourceIpMap, 3>; |
||||
struct DestinationIp { |
||||
absl::optional<XdsListenerResource::FilterChainMap::CidrRange> prefix_range; |
||||
bool transport_protocol_raw_buffer_provided = false; |
||||
ConnectionSourceTypesArray source_types_array; |
||||
}; |
||||
using DestinationIpMap = std::map<std::string, DestinationIp>; |
||||
DestinationIpMap destination_ip_map; |
||||
}; |
||||
|
||||
void AddFilterChainDataForSourcePort( |
||||
const FilterChain& filter_chain, uint32_t port, |
||||
XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, |
||||
ValidationErrors* errors) { |
||||
auto insert_result = ports_map->emplace( |
||||
port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{ |
||||
filter_chain.filter_chain_data}); |
||||
if (!insert_result.second) { |
||||
errors->AddError(absl::StrCat( |
||||
"duplicate matching rules detected when adding filter chain: ", |
||||
filter_chain.filter_chain_match.ToString())); |
||||
} |
||||
} |
||||
|
||||
void AddFilterChainDataForSourcePorts( |
||||
const FilterChain& filter_chain, |
||||
XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, |
||||
ValidationErrors* errors) { |
||||
if (filter_chain.filter_chain_match.source_ports.empty()) { |
||||
AddFilterChainDataForSourcePort(filter_chain, 0, ports_map, errors); |
||||
} else { |
||||
for (uint32_t port : filter_chain.filter_chain_match.source_ports) { |
||||
AddFilterChainDataForSourcePort(filter_chain, port, ports_map, errors); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void AddFilterChainDataForSourceIpRange( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::SourceIpMap* source_ip_map, |
||||
ValidationErrors* errors) { |
||||
if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) { |
||||
auto insert_result = source_ip_map->emplace( |
||||
"", XdsListenerResource::FilterChainMap::SourceIp()); |
||||
AddFilterChainDataForSourcePorts( |
||||
filter_chain, &insert_result.first->second.ports_map, errors); |
||||
} else { |
||||
for (const auto& prefix_range : |
||||
filter_chain.filter_chain_match.source_prefix_ranges) { |
||||
auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); |
||||
if (!addr_str.ok()) { |
||||
errors->AddError(absl::StrCat( |
||||
"error parsing source IP sockaddr (should not happen): ", |
||||
addr_str.status().message())); |
||||
continue; |
||||
} |
||||
auto insert_result = source_ip_map->emplace( |
||||
absl::StrCat(*addr_str, "/", prefix_range.prefix_len), |
||||
XdsListenerResource::FilterChainMap::SourceIp()); |
||||
if (insert_result.second) { |
||||
insert_result.first->second.prefix_range.emplace(prefix_range); |
||||
} |
||||
AddFilterChainDataForSourcePorts( |
||||
filter_chain, &insert_result.first->second.ports_map, errors); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void AddFilterChainDataForSourceType( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::DestinationIp* destination_ip, |
||||
ValidationErrors* errors) { |
||||
CHECK(static_cast<unsigned int>(filter_chain.filter_chain_match.source_type) < |
||||
3u); |
||||
AddFilterChainDataForSourceIpRange( |
||||
filter_chain, |
||||
&destination_ip->source_types_array[static_cast<int>( |
||||
filter_chain.filter_chain_match.source_type)], |
||||
errors); |
||||
} |
||||
|
||||
void AddFilterChainDataForApplicationProtocols( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::DestinationIp* destination_ip, |
||||
ValidationErrors* errors) { |
||||
// Only allow filter chains that do not mention application protocols
|
||||
if (filter_chain.filter_chain_match.application_protocols.empty()) { |
||||
AddFilterChainDataForSourceType(filter_chain, destination_ip, errors); |
||||
} |
||||
} |
||||
|
||||
void AddFilterChainDataForTransportProtocol( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::DestinationIp* destination_ip, |
||||
ValidationErrors* errors) { |
||||
const std::string& transport_protocol = |
||||
filter_chain.filter_chain_match.transport_protocol; |
||||
// Only allow filter chains with no transport protocol or "raw_buffer"
|
||||
if (!transport_protocol.empty() && transport_protocol != "raw_buffer") { |
||||
return; |
||||
} |
||||
// If for this configuration, we've already seen filter chains that mention
|
||||
// the transport protocol as "raw_buffer", we will never match filter chains
|
||||
// that do not mention it.
|
||||
if (destination_ip->transport_protocol_raw_buffer_provided && |
||||
transport_protocol.empty()) { |
||||
return; |
||||
} |
||||
if (!transport_protocol.empty() && |
||||
!destination_ip->transport_protocol_raw_buffer_provided) { |
||||
destination_ip->transport_protocol_raw_buffer_provided = true; |
||||
// Clear out the previous entries if any since those entries did not mention
|
||||
// "raw_buffer"
|
||||
destination_ip->source_types_array = |
||||
InternalFilterChainMap::ConnectionSourceTypesArray(); |
||||
} |
||||
AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip, |
||||
errors); |
||||
} |
||||
|
||||
void AddFilterChainDataForServerNames( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::DestinationIp* destination_ip, |
||||
ValidationErrors* errors) { |
||||
// Don't continue adding filter chains with server names mentioned
|
||||
if (filter_chain.filter_chain_match.server_names.empty()) { |
||||
AddFilterChainDataForTransportProtocol(filter_chain, destination_ip, |
||||
errors); |
||||
} |
||||
} |
||||
|
||||
void AddFilterChainDataForDestinationIpRange( |
||||
const FilterChain& filter_chain, |
||||
InternalFilterChainMap::DestinationIpMap* destination_ip_map, |
||||
ValidationErrors* errors) { |
||||
if (filter_chain.filter_chain_match.prefix_ranges.empty()) { |
||||
auto insert_result = destination_ip_map->emplace( |
||||
"", InternalFilterChainMap::DestinationIp()); |
||||
AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second, |
||||
errors); |
||||
} else { |
||||
for (const auto& prefix_range : |
||||
filter_chain.filter_chain_match.prefix_ranges) { |
||||
auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); |
||||
if (!addr_str.ok()) { |
||||
errors->AddError(absl::StrCat( |
||||
"error parsing destination IP sockaddr (should not happen): ", |
||||
addr_str.status().message())); |
||||
continue; |
||||
} |
||||
auto insert_result = destination_ip_map->emplace( |
||||
absl::StrCat(*addr_str, "/", prefix_range.prefix_len), |
||||
InternalFilterChainMap::DestinationIp()); |
||||
if (insert_result.second) { |
||||
insert_result.first->second.prefix_range.emplace(prefix_range); |
||||
} |
||||
AddFilterChainDataForServerNames(filter_chain, |
||||
&insert_result.first->second, errors); |
||||
} |
||||
} |
||||
} |
||||
|
||||
XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( |
||||
InternalFilterChainMap* internal_filter_chain_map) { |
||||
XdsListenerResource::FilterChainMap filter_chain_map; |
||||
for (auto& destination_ip_pair : |
||||
internal_filter_chain_map->destination_ip_map) { |
||||
XdsListenerResource::FilterChainMap::DestinationIp destination_ip; |
||||
destination_ip.prefix_range = destination_ip_pair.second.prefix_range; |
||||
for (int i = 0; i < 3; i++) { |
||||
auto& source_ip_map = destination_ip_pair.second.source_types_array[i]; |
||||
for (auto& source_ip_pair : source_ip_map) { |
||||
destination_ip.source_types_array[i].push_back( |
||||
std::move(source_ip_pair.second)); |
||||
} |
||||
} |
||||
filter_chain_map.destination_ip_vector.push_back(std::move(destination_ip)); |
||||
} |
||||
return filter_chain_map; |
||||
} |
||||
|
||||
XdsListenerResource::FilterChainMap BuildFilterChainMap( |
||||
const std::vector<FilterChain>& filter_chains, ValidationErrors* errors) { |
||||
InternalFilterChainMap internal_filter_chain_map; |
||||
for (const auto& filter_chain : filter_chains) { |
||||
// Discard filter chain entries that specify destination port
|
||||
if (filter_chain.filter_chain_match.destination_port != 0) continue; |
||||
AddFilterChainDataForDestinationIpRange( |
||||
filter_chain, &internal_filter_chain_map.destination_ip_map, errors); |
||||
} |
||||
return BuildFromInternalFilterChainMap(&internal_filter_chain_map); |
||||
} |
||||
|
||||
absl::StatusOr<std::shared_ptr<const XdsListenerResource>> |
||||
LdsResourceParseServer(const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_listener_v3_Listener* listener) { |
||||
ValidationErrors errors; |
||||
XdsListenerResource::TcpListener tcp_listener; |
||||
// address
|
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "address"); |
||||
auto address = AddressParse( |
||||
envoy_config_listener_v3_Listener_address(listener), &errors); |
||||
if (address.has_value()) tcp_listener.address = std::move(*address); |
||||
} |
||||
// use_original_dst
|
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "use_original_dst"); |
||||
const auto* use_original_dst = |
||||
envoy_config_listener_v3_Listener_use_original_dst(listener); |
||||
if (use_original_dst != nullptr && |
||||
google_protobuf_BoolValue_value(use_original_dst)) { |
||||
errors.AddError("field not supported"); |
||||
} |
||||
} |
||||
// filter_chains
|
||||
size_t num_filter_chains = 0; |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "filter_chains"); |
||||
auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains( |
||||
listener, &num_filter_chains); |
||||
std::vector<FilterChain> parsed_filter_chains; |
||||
parsed_filter_chains.reserve(num_filter_chains); |
||||
for (size_t i = 0; i < num_filter_chains; i++) { |
||||
ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); |
||||
auto filter_chain = FilterChainParse(context, filter_chains[i], &errors); |
||||
if (filter_chain.has_value()) { |
||||
parsed_filter_chains.push_back(std::move(*filter_chain)); |
||||
} |
||||
} |
||||
tcp_listener.filter_chain_map = |
||||
BuildFilterChainMap(parsed_filter_chains, &errors); |
||||
} |
||||
// default_filter_chain
|
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "default_filter_chain"); |
||||
auto* default_filter_chain = |
||||
envoy_config_listener_v3_Listener_default_filter_chain(listener); |
||||
if (default_filter_chain != nullptr) { |
||||
auto filter_chain = |
||||
FilterChainParse(context, default_filter_chain, &errors); |
||||
if (filter_chain.has_value() && |
||||
filter_chain->filter_chain_data != nullptr) { |
||||
tcp_listener.default_filter_chain = |
||||
std::move(*filter_chain->filter_chain_data); |
||||
} |
||||
} else if (num_filter_chains == 0) { |
||||
// Make sure that there is at least one filter chain to use.
|
||||
errors.AddError("must be set if filter_chains is unset"); |
||||
} |
||||
} |
||||
// Return result.
|
||||
if (!errors.ok()) { |
||||
return errors.status(absl::StatusCode::kInvalidArgument, |
||||
"errors validating server Listener"); |
||||
} |
||||
auto lds_update = std::make_shared<XdsListenerResource>(); |
||||
lds_update->listener = std::move(tcp_listener); |
||||
return lds_update; |
||||
} |
||||
|
||||
absl::StatusOr<std::shared_ptr<const XdsListenerResource>> LdsResourceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_listener_v3_Listener* listener) { |
||||
// Check whether it's a client or server listener.
|
||||
const envoy_config_listener_v3_ApiListener* api_listener = |
||||
envoy_config_listener_v3_Listener_api_listener(listener); |
||||
const envoy_config_core_v3_Address* address = |
||||
envoy_config_listener_v3_Listener_address(listener); |
||||
// TODO(roth): Re-enable the following check once
|
||||
// github.com/istio/istio/issues/38914 is resolved.
|
||||
// if (api_listener != nullptr && address != nullptr) {
|
||||
// return absl::InvalidArgumentError(
|
||||
// "Listener has both address and ApiListener");
|
||||
// }
|
||||
if (api_listener == nullptr && address == nullptr) { |
||||
return absl::InvalidArgumentError( |
||||
"Listener has neither address nor ApiListener"); |
||||
} |
||||
// If api_listener is present, it's for a client; otherwise, it's
|
||||
// for a server.
|
||||
if (api_listener != nullptr) { |
||||
return LdsResourceParseClient(context, api_listener); |
||||
} |
||||
return LdsResourceParseServer(context, listener); |
||||
} |
||||
|
||||
void MaybeLogListener(const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_listener_v3_Listener* listener) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { |
||||
const upb_MessageDef* msg_type = |
||||
envoy_config_listener_v3_Listener_getmsgdef(context.symtab); |
||||
char buf[10240]; |
||||
upb_TextEncode(reinterpret_cast<const upb_Message*>(listener), msg_type, |
||||
nullptr, 0, buf, sizeof(buf)); |
||||
VLOG(2) << "[xds_client " << context.client << "] Listener: " << buf; |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
XdsResourceType::DecodeResult XdsListenerResourceType::Decode( |
||||
const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const { |
||||
DecodeResult result; |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_listener_v3_Listener_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
result.resource = |
||||
absl::InvalidArgumentError("Can't parse Listener resource."); |
||||
return result; |
||||
} |
||||
MaybeLogListener(context, resource); |
||||
// Validate resource.
|
||||
result.name = |
||||
UpbStringToStdString(envoy_config_listener_v3_Listener_name(resource)); |
||||
auto listener = LdsResourceParse(context, resource); |
||||
if (!listener.ok()) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(ERROR) << "[xds_client " << context.client << "] invalid Listener " |
||||
<< *result.name << ": " << listener.status(); |
||||
} |
||||
result.resource = listener.status(); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(INFO) << "[xds_client " << context.client << "] parsed Listener " |
||||
<< *result.name << ": " << (*listener)->ToString(); |
||||
} |
||||
result.resource = std::move(*listener); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,60 @@ |
||||
//
|
||||
// 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_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "envoy/config/listener/v3/listener.upbdefs.h" |
||||
#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" |
||||
#include "upb/reflection/def.h" |
||||
|
||||
#include "src/core/xds/grpc/xds_bootstrap_grpc.h" |
||||
#include "src/core/xds/grpc/xds_http_filter_registry.h" |
||||
#include "src/core/xds/grpc/xds_listener.h" |
||||
#include "src/core/xds/xds_client/xds_client.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type_impl.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsListenerResourceType final |
||||
: public XdsResourceTypeImpl<XdsListenerResourceType, XdsListenerResource> { |
||||
public: |
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.listener.v3.Listener"; |
||||
} |
||||
|
||||
DecodeResult Decode(const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const override; |
||||
|
||||
bool AllResourcesRequiredInSotW() const override { return true; } |
||||
|
||||
void InitUpbSymtab(XdsClient* xds_client, |
||||
upb_DefPool* symtab) const override { |
||||
envoy_config_listener_v3_Listener_getmsgdef(symtab); |
||||
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef( |
||||
symtab); |
||||
const auto& http_filter_registry = |
||||
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap()) |
||||
.http_filter_registry(); |
||||
http_filter_registry.PopulateSymtab(symtab); |
||||
} |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_LISTENER_PARSER_H
|
@ -0,0 +1,956 @@ |
||||
//
|
||||
// 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 "src/core/xds/grpc/xds_route_config_parser.h" |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include <limits> |
||||
#include <map> |
||||
#include <memory> |
||||
#include <set> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "absl/log/check.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/status/status.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 "absl/strings/str_split.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
#include "absl/types/variant.h" |
||||
#include "envoy/config/core/v3/base.upb.h" |
||||
#include "envoy/config/core/v3/extension.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/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/duration.upb.h" |
||||
#include "google/protobuf/wrappers.upb.h" |
||||
#include "re2/re2.h" |
||||
#include "upb/base/string_view.h" |
||||
#include "upb/message/map.h" |
||||
#include "upb/text/encode.h" |
||||
|
||||
#include <grpc/status.h> |
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/debug/trace.h" |
||||
#include "src/core/lib/gprpp/env.h" |
||||
#include "src/core/lib/gprpp/match.h" |
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/matchers/matchers.h" |
||||
#include "src/core/load_balancing/lb_policy_registry.h" |
||||
#include "src/core/util/json/json.h" |
||||
#include "src/core/util/json/json_writer.h" |
||||
#include "src/core/util/string.h" |
||||
#include "src/core/util/upb_utils.h" |
||||
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" |
||||
#include "src/core/xds/grpc/xds_common_types.h" |
||||
#include "src/core/xds/grpc/xds_common_types_parser.h" |
||||
#include "src/core/xds/grpc/xds_http_filter.h" |
||||
#include "src/core/xds/grpc/xds_http_filter_registry.h" |
||||
#include "src/core/xds/grpc/xds_routing.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// TODO(apolcyn): remove this flag by the 1.58 release
|
||||
bool XdsRlsEnabled() { |
||||
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_RLS_LB"); |
||||
if (!value.has_value()) return true; |
||||
bool parsed_value; |
||||
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value); |
||||
return parse_succeeded && parsed_value; |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResourceParse()
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config, |
||||
ValidationErrors* errors) { |
||||
XdsRouteConfigResource::ClusterSpecifierPluginMap |
||||
cluster_specifier_plugin_map; |
||||
const auto& cluster_specifier_plugin_registry = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()) |
||||
.cluster_specifier_plugin_registry(); |
||||
size_t num_cluster_specifier_plugins; |
||||
const envoy_config_route_v3_ClusterSpecifierPlugin* const* |
||||
cluster_specifier_plugin = |
||||
envoy_config_route_v3_RouteConfiguration_cluster_specifier_plugins( |
||||
route_config, &num_cluster_specifier_plugins); |
||||
for (size_t i = 0; i < num_cluster_specifier_plugins; ++i) { |
||||
bool is_optional = envoy_config_route_v3_ClusterSpecifierPlugin_is_optional( |
||||
cluster_specifier_plugin[i]); |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".cluster_specifier_plugins[", i, "].extension")); |
||||
const envoy_config_core_v3_TypedExtensionConfig* typed_extension_config = |
||||
envoy_config_route_v3_ClusterSpecifierPlugin_extension( |
||||
cluster_specifier_plugin[i]); |
||||
if (typed_extension_config == nullptr) { |
||||
errors->AddError("field not present"); |
||||
continue; |
||||
} |
||||
std::string name = UpbStringToStdString( |
||||
envoy_config_core_v3_TypedExtensionConfig_name(typed_extension_config)); |
||||
if (cluster_specifier_plugin_map.find(name) != |
||||
cluster_specifier_plugin_map.end()) { |
||||
ValidationErrors::ScopedField field(errors, ".name"); |
||||
errors->AddError(absl::StrCat("duplicate name \"", name, "\"")); |
||||
} else { |
||||
// Add a sentinel entry in case we encounter an error later, just so we
|
||||
// don't generate duplicate errors for each route that uses this plugin.
|
||||
cluster_specifier_plugin_map[name] = "<sentinel>"; |
||||
} |
||||
ValidationErrors::ScopedField field2(errors, ".typed_config"); |
||||
const google_protobuf_Any* any = |
||||
envoy_config_core_v3_TypedExtensionConfig_typed_config( |
||||
typed_extension_config); |
||||
auto extension = ExtractXdsExtension(context, any, errors); |
||||
if (!extension.has_value()) continue; |
||||
const XdsClusterSpecifierPluginImpl* cluster_specifier_plugin_impl = |
||||
cluster_specifier_plugin_registry.GetPluginForType(extension->type); |
||||
if (cluster_specifier_plugin_impl == nullptr) { |
||||
if (is_optional) { |
||||
// Empty string indicates an optional plugin.
|
||||
// This is used later when validating routes, and since we will skip
|
||||
// any routes that refer to this plugin, we won't wind up including
|
||||
// this plugin in the resource that we return to the watcher.
|
||||
cluster_specifier_plugin_map[std::move(name)] = ""; |
||||
} else { |
||||
// Not optional, report error.
|
||||
errors->AddError("unsupported ClusterSpecifierPlugin type"); |
||||
} |
||||
continue; |
||||
} |
||||
const size_t original_error_size = errors->size(); |
||||
Json lb_policy_config = |
||||
cluster_specifier_plugin_impl->GenerateLoadBalancingPolicyConfig( |
||||
std::move(*extension), context.arena, context.symtab, errors); |
||||
if (errors->size() != original_error_size) continue; |
||||
auto config = |
||||
CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig( |
||||
lb_policy_config); |
||||
if (!config.ok()) { |
||||
errors->AddError(absl::StrCat( |
||||
"ClusterSpecifierPlugin returned invalid LB policy config: ", |
||||
config.status().message())); |
||||
} else { |
||||
cluster_specifier_plugin_map[std::move(name)] = |
||||
JsonDump(lb_policy_config); |
||||
} |
||||
} |
||||
return cluster_specifier_plugin_map; |
||||
} |
||||
|
||||
absl::optional<StringMatcher> RoutePathMatchParse( |
||||
const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) { |
||||
bool case_sensitive = true; |
||||
auto* case_sensitive_ptr = |
||||
envoy_config_route_v3_RouteMatch_case_sensitive(match); |
||||
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)); |
||||
// For any prefix that cannot match a path of the form "/service/method",
|
||||
// ignore the route.
|
||||
if (!prefix.empty()) { |
||||
// Does not start with a slash.
|
||||
if (prefix[0] != '/') return absl::nullopt; |
||||
std::vector<absl::string_view> prefix_elements = |
||||
absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2)); |
||||
// More than 2 slashes.
|
||||
if (prefix_elements.size() > 2) return absl::nullopt; |
||||
// Two consecutive slashes.
|
||||
if (prefix_elements.size() == 2 && prefix_elements[0].empty()) { |
||||
return absl::nullopt; |
||||
} |
||||
} |
||||
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)); |
||||
// For any path not of the form "/service/method", ignore the route.
|
||||
// Empty path.
|
||||
if (path.empty()) return absl::nullopt; |
||||
// Does not start with a slash.
|
||||
if (path[0] != '/') return absl::nullopt; |
||||
std::vector<absl::string_view> path_elements = |
||||
absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2)); |
||||
// Number of slashes does not equal 2.
|
||||
if (path_elements.size() != 2) return absl::nullopt; |
||||
// Empty service name.
|
||||
if (path_elements[0].empty()) return absl::nullopt; |
||||
// Empty method name.
|
||||
if (path_elements[1].empty()) return absl::nullopt; |
||||
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); |
||||
CHECK_NE(regex_matcher, nullptr); |
||||
type = StringMatcher::Type::kSafeRegex; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); |
||||
} else { |
||||
errors->AddError("invalid path specifier"); |
||||
return absl::nullopt; |
||||
} |
||||
absl::StatusOr<StringMatcher> string_matcher = |
||||
StringMatcher::Create(type, match_string, case_sensitive); |
||||
if (!string_matcher.ok()) { |
||||
errors->AddError(absl::StrCat("error creating path matcher: ", |
||||
string_matcher.status().message())); |
||||
return absl::nullopt; |
||||
} |
||||
return std::move(*string_matcher); |
||||
} |
||||
|
||||
void RouteHeaderMatchersParse(const envoy_config_route_v3_RouteMatch* match, |
||||
XdsRouteConfigResource::Route* route, |
||||
ValidationErrors* errors) { |
||||
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) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
absl::StrCat(".headers[", i, "]")); |
||||
const envoy_config_route_v3_HeaderMatcher* header = headers[i]; |
||||
CHECK_NE(header, nullptr); |
||||
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; |
||||
bool case_sensitive = true; |
||||
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_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 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); |
||||
CHECK_NE(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); |
||||
CHECK_NE(range_matcher, nullptr); |
||||
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_string_match(header)) { |
||||
ValidationErrors::ScopedField field(errors, ".string_match"); |
||||
const auto* matcher = |
||||
envoy_config_route_v3_HeaderMatcher_string_match(header); |
||||
CHECK_NE(matcher, nullptr); |
||||
if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) { |
||||
type = HeaderMatcher::Type::kExact; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_StringMatcher_exact(matcher)); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(matcher)) { |
||||
type = HeaderMatcher::Type::kPrefix; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_StringMatcher_prefix(matcher)); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(matcher)) { |
||||
type = HeaderMatcher::Type::kSuffix; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_StringMatcher_suffix(matcher)); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(matcher)) { |
||||
type = HeaderMatcher::Type::kContains; |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_StringMatcher_contains(matcher)); |
||||
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(matcher)) { |
||||
type = HeaderMatcher::Type::kSafeRegex; |
||||
const auto* regex_matcher = |
||||
envoy_type_matcher_v3_StringMatcher_safe_regex(matcher); |
||||
CHECK_NE(regex_matcher, nullptr); |
||||
match_string = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)); |
||||
} else { |
||||
errors->AddError("invalid string matcher"); |
||||
continue; |
||||
} |
||||
case_sensitive = |
||||
!envoy_type_matcher_v3_StringMatcher_ignore_case(matcher); |
||||
} else { |
||||
errors->AddError("invalid header matcher"); |
||||
continue; |
||||
} |
||||
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, case_sensitive); |
||||
if (!header_matcher.ok()) { |
||||
errors->AddError(absl::StrCat("cannot create header matcher: ", |
||||
header_matcher.status().message())); |
||||
} else { |
||||
route->matchers.header_matchers.emplace_back(std::move(*header_matcher)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void RouteRuntimeFractionParse(const envoy_config_route_v3_RouteMatch* match, |
||||
XdsRouteConfigResource::Route* route, |
||||
ValidationErrors* errors) { |
||||
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 uint32_t denominator = |
||||
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: { |
||||
ValidationErrors::ScopedField field( |
||||
errors, ".runtime_fraction.default_value.denominator"); |
||||
errors->AddError("unknown denominator type"); |
||||
return; |
||||
} |
||||
} |
||||
route->matchers.fraction_per_million = numerator; |
||||
} |
||||
} |
||||
} |
||||
|
||||
template <typename ParentType, typename EntryType> |
||||
XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig( |
||||
const XdsResourceType::DecodeContext& context, const ParentType* parent, |
||||
const EntryType* (*entry_func)(const ParentType*, size_t*), |
||||
upb_StringView (*key_func)(const EntryType*), |
||||
const google_protobuf_Any* (*value_func)(const EntryType*), |
||||
ValidationErrors* errors) { |
||||
XdsRouteConfigResource::TypedPerFilterConfig typed_per_filter_config; |
||||
size_t filter_it = kUpb_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)); |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat("[", key, "]")); |
||||
if (key.empty()) errors->AddError("filter name must be non-empty"); |
||||
const google_protobuf_Any* any = value_func(filter_entry); |
||||
auto extension = ExtractXdsExtension(context, any, errors); |
||||
if (!extension.has_value()) continue; |
||||
auto* extension_to_use = &*extension; |
||||
absl::optional<XdsExtension> nested_extension; |
||||
bool is_optional = false; |
||||
if (extension->type == "envoy.config.route.v3.FilterConfig") { |
||||
absl::string_view* serialized_config = |
||||
absl::get_if<absl::string_view>(&extension->value); |
||||
if (serialized_config == nullptr) { |
||||
errors->AddError("could not parse FilterConfig"); |
||||
continue; |
||||
} |
||||
const auto* filter_config = envoy_config_route_v3_FilterConfig_parse( |
||||
serialized_config->data(), serialized_config->size(), context.arena); |
||||
if (filter_config == nullptr) { |
||||
errors->AddError("could not parse FilterConfig"); |
||||
continue; |
||||
} |
||||
is_optional = |
||||
envoy_config_route_v3_FilterConfig_is_optional(filter_config); |
||||
any = envoy_config_route_v3_FilterConfig_config(filter_config); |
||||
extension->validation_fields.emplace_back(errors, ".config"); |
||||
nested_extension = ExtractXdsExtension(context, any, errors); |
||||
if (!nested_extension.has_value()) continue; |
||||
extension_to_use = &*nested_extension; |
||||
} |
||||
const auto& http_filter_registry = |
||||
static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap()) |
||||
.http_filter_registry(); |
||||
const XdsHttpFilterImpl* filter_impl = |
||||
http_filter_registry.GetFilterForType(extension_to_use->type); |
||||
if (filter_impl == nullptr) { |
||||
if (!is_optional) errors->AddError("unsupported filter type"); |
||||
continue; |
||||
} |
||||
absl::optional<XdsHttpFilterImpl::FilterConfig> filter_config = |
||||
filter_impl->GenerateFilterConfigOverride( |
||||
context, std::move(*extension_to_use), errors); |
||||
if (filter_config.has_value()) { |
||||
typed_per_filter_config[std::string(key)] = std::move(*filter_config); |
||||
} |
||||
} |
||||
return typed_per_filter_config; |
||||
} |
||||
|
||||
XdsRouteConfigResource::RetryPolicy RetryPolicyParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RetryPolicy* retry_policy_proto, |
||||
ValidationErrors* errors) { |
||||
XdsRouteConfigResource::RetryPolicy retry_policy; |
||||
auto retry_on = UpbStringToStdString( |
||||
envoy_config_route_v3_RetryPolicy_retry_on(retry_policy_proto)); |
||||
std::vector<absl::string_view> codes = absl::StrSplit(retry_on, ','); |
||||
for (const auto& code : codes) { |
||||
if (code == "cancelled") { |
||||
retry_policy.retry_on.Add(GRPC_STATUS_CANCELLED); |
||||
} else if (code == "deadline-exceeded") { |
||||
retry_policy.retry_on.Add(GRPC_STATUS_DEADLINE_EXCEEDED); |
||||
} else if (code == "internal") { |
||||
retry_policy.retry_on.Add(GRPC_STATUS_INTERNAL); |
||||
} else if (code == "resource-exhausted") { |
||||
retry_policy.retry_on.Add(GRPC_STATUS_RESOURCE_EXHAUSTED); |
||||
} else if (code == "unavailable") { |
||||
retry_policy.retry_on.Add(GRPC_STATUS_UNAVAILABLE); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(INFO) << "Unsupported retry_on policy " << code; |
||||
} |
||||
} |
||||
} |
||||
const google_protobuf_UInt32Value* num_retries = |
||||
envoy_config_route_v3_RetryPolicy_num_retries(retry_policy_proto); |
||||
if (num_retries != nullptr) { |
||||
uint32_t num_retries_value = google_protobuf_UInt32Value_value(num_retries); |
||||
if (num_retries_value == 0) { |
||||
ValidationErrors::ScopedField field(errors, ".num_retries"); |
||||
errors->AddError("must be greater than 0"); |
||||
} else { |
||||
retry_policy.num_retries = num_retries_value; |
||||
} |
||||
} else { |
||||
retry_policy.num_retries = 1; |
||||
} |
||||
const envoy_config_route_v3_RetryPolicy_RetryBackOff* backoff = |
||||
envoy_config_route_v3_RetryPolicy_retry_back_off(retry_policy_proto); |
||||
if (backoff != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".retry_back_off"); |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".base_interval"); |
||||
const google_protobuf_Duration* base_interval = |
||||
envoy_config_route_v3_RetryPolicy_RetryBackOff_base_interval(backoff); |
||||
if (base_interval == nullptr) { |
||||
errors->AddError("field not present"); |
||||
} else { |
||||
retry_policy.retry_back_off.base_interval = |
||||
ParseDuration(base_interval, errors); |
||||
} |
||||
} |
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".max_interval"); |
||||
const google_protobuf_Duration* max_interval = |
||||
envoy_config_route_v3_RetryPolicy_RetryBackOff_max_interval(backoff); |
||||
Duration max; |
||||
if (max_interval != nullptr) { |
||||
max = ParseDuration(max_interval, errors); |
||||
} else { |
||||
// if max interval is not set, it is 10x the base.
|
||||
max = 10 * retry_policy.retry_back_off.base_interval; |
||||
} |
||||
retry_policy.retry_back_off.max_interval = max; |
||||
} |
||||
} else { |
||||
retry_policy.retry_back_off.base_interval = Duration::Milliseconds(25); |
||||
retry_policy.retry_back_off.max_interval = Duration::Milliseconds(250); |
||||
} |
||||
return retry_policy; |
||||
} |
||||
|
||||
absl::optional<XdsRouteConfigResource::Route::RouteAction> RouteActionParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RouteAction* route_action_proto, |
||||
const std::map<std::string /*cluster_specifier_plugin_name*/, |
||||
std::string /*LB policy config*/>& |
||||
cluster_specifier_plugin_map, |
||||
ValidationErrors* errors) { |
||||
XdsRouteConfigResource::Route::RouteAction route_action; |
||||
// grpc_timeout_header_max or max_stream_duration
|
||||
const auto* max_stream_duration = |
||||
envoy_config_route_v3_RouteAction_max_stream_duration(route_action_proto); |
||||
if (max_stream_duration != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".max_stream_duration"); |
||||
const google_protobuf_Duration* duration = |
||||
envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max( |
||||
max_stream_duration); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".grpc_timeout_header_max"); |
||||
route_action.max_stream_duration = ParseDuration(duration, errors); |
||||
} else { |
||||
duration = |
||||
envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration( |
||||
max_stream_duration); |
||||
if (duration != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".max_stream_duration"); |
||||
route_action.max_stream_duration = ParseDuration(duration, errors); |
||||
} |
||||
} |
||||
} |
||||
// hash_policy
|
||||
size_t size = 0; |
||||
const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies = |
||||
envoy_config_route_v3_RouteAction_hash_policy(route_action_proto, &size); |
||||
for (size_t i = 0; i < size; ++i) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
absl::StrCat(".hash_policy[", i, "]")); |
||||
const auto* 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) { |
||||
// header
|
||||
ValidationErrors::ScopedField field(errors, ".header"); |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header |
||||
header_policy; |
||||
header_policy.header_name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name( |
||||
header)); |
||||
if (header_policy.header_name.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".header_name"); |
||||
errors->AddError("must be non-empty"); |
||||
} |
||||
// regex_rewrite
|
||||
const auto* regex_rewrite = |
||||
envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite( |
||||
header); |
||||
if (regex_rewrite != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".regex_rewrite.pattern"); |
||||
const auto* pattern = |
||||
envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern( |
||||
regex_rewrite); |
||||
if (pattern == nullptr) { |
||||
errors->AddError("field not present"); |
||||
continue; |
||||
} |
||||
ValidationErrors::ScopedField field2(errors, ".regex"); |
||||
std::string regex = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatcher_regex(pattern)); |
||||
if (regex.empty()) { |
||||
errors->AddError("field not present"); |
||||
continue; |
||||
} |
||||
RE2::Options options; |
||||
header_policy.regex = std::make_unique<RE2>(regex, options); |
||||
if (!header_policy.regex->ok()) { |
||||
errors->AddError(absl::StrCat("errors compiling regex: ", |
||||
header_policy.regex->error())); |
||||
continue; |
||||
} |
||||
header_policy.regex_substitution = UpbStringToStdString( |
||||
envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution( |
||||
regex_rewrite)); |
||||
} |
||||
policy.policy = std::move(header_policy); |
||||
} else if ((filter_state = |
||||
envoy_config_route_v3_RouteAction_HashPolicy_filter_state( |
||||
hash_policy)) != nullptr) { |
||||
// filter_state
|
||||
std::string key = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key( |
||||
filter_state)); |
||||
if (key != "io.grpc.channel_id") continue; |
||||
policy.policy = |
||||
XdsRouteConfigResource::Route::RouteAction::HashPolicy::ChannelId(); |
||||
} else { |
||||
// Unsupported hash policy type, ignore it.
|
||||
continue; |
||||
} |
||||
route_action.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_proto); |
||||
if (retry_policy != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".retry_policy"); |
||||
route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors); |
||||
} |
||||
// Parse cluster specifier, which is one of several options.
|
||||
if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) { |
||||
// Cluster name.
|
||||
std::string cluster_name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_cluster(route_action_proto)); |
||||
if (cluster_name.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".cluster"); |
||||
errors->AddError("must be non-empty"); |
||||
} |
||||
route_action.action = |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterName{ |
||||
std::move(cluster_name)}; |
||||
} else if (envoy_config_route_v3_RouteAction_has_weighted_clusters( |
||||
route_action_proto)) { |
||||
// WeightedClusters.
|
||||
ValidationErrors::ScopedField field(errors, ".weighted_clusters"); |
||||
const envoy_config_route_v3_WeightedCluster* weighted_clusters_proto = |
||||
envoy_config_route_v3_RouteAction_weighted_clusters(route_action_proto); |
||||
CHECK_NE(weighted_clusters_proto, nullptr); |
||||
std::vector<XdsRouteConfigResource::Route::RouteAction::ClusterWeight> |
||||
action_weighted_clusters; |
||||
uint64_t total_weight = 0; |
||||
size_t clusters_size; |
||||
const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters = |
||||
envoy_config_route_v3_WeightedCluster_clusters(weighted_clusters_proto, |
||||
&clusters_size); |
||||
for (size_t i = 0; i < clusters_size; ++i) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
absl::StrCat(".clusters[", i, "]")); |
||||
const auto* cluster_proto = clusters[i]; |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterWeight cluster; |
||||
// typed_per_filter_config
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); |
||||
cluster.typed_per_filter_config = ParseTypedPerFilterConfig< |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight, |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>( |
||||
context, cluster_proto, |
||||
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, |
||||
errors); |
||||
} |
||||
// name
|
||||
cluster.name = UpbStringToStdString( |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_name( |
||||
cluster_proto)); |
||||
if (cluster.name.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".name"); |
||||
errors->AddError("must be non-empty"); |
||||
} |
||||
// weight
|
||||
const google_protobuf_UInt32Value* weight_proto = |
||||
envoy_config_route_v3_WeightedCluster_ClusterWeight_weight( |
||||
cluster_proto); |
||||
if (weight_proto == nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".weight"); |
||||
errors->AddError("field not present"); |
||||
} else { |
||||
cluster.weight = google_protobuf_UInt32Value_value(weight_proto); |
||||
if (cluster.weight == 0) continue; |
||||
total_weight += cluster.weight; |
||||
} |
||||
// Add entry to WeightedClusters.
|
||||
action_weighted_clusters.emplace_back(std::move(cluster)); |
||||
} |
||||
if (action_weighted_clusters.empty()) { |
||||
errors->AddError("no valid clusters specified"); |
||||
} else if (total_weight > std::numeric_limits<uint32_t>::max()) { |
||||
errors->AddError("sum of cluster weights exceeds uint32 max"); |
||||
} |
||||
route_action.action = std::move(action_weighted_clusters); |
||||
} else if (XdsRlsEnabled() && |
||||
envoy_config_route_v3_RouteAction_has_cluster_specifier_plugin( |
||||
route_action_proto)) { |
||||
// ClusterSpecifierPlugin
|
||||
ValidationErrors::ScopedField field(errors, ".cluster_specifier_plugin"); |
||||
std::string plugin_name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteAction_cluster_specifier_plugin( |
||||
route_action_proto)); |
||||
if (plugin_name.empty()) { |
||||
errors->AddError("must be non-empty"); |
||||
return absl::nullopt; |
||||
} |
||||
auto it = cluster_specifier_plugin_map.find(plugin_name); |
||||
if (it == cluster_specifier_plugin_map.end()) { |
||||
errors->AddError(absl::StrCat("unknown cluster specifier plugin name \"", |
||||
plugin_name, "\"")); |
||||
} else { |
||||
// If the cluster specifier config is empty, that means that the
|
||||
// plugin was unsupported but optional. In that case, skip this route.
|
||||
if (it->second.empty()) return absl::nullopt; |
||||
} |
||||
route_action.action = |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName{ |
||||
std::move(plugin_name)}; |
||||
} else { |
||||
// Not a supported cluster specifier, so ignore this route.
|
||||
return absl::nullopt; |
||||
} |
||||
return route_action; |
||||
} |
||||
|
||||
absl::optional<XdsRouteConfigResource::Route> ParseRoute( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_Route* route_proto, |
||||
const absl::optional<XdsRouteConfigResource::RetryPolicy>& |
||||
virtual_host_retry_policy, |
||||
const XdsRouteConfigResource::ClusterSpecifierPluginMap& |
||||
cluster_specifier_plugin_map, |
||||
std::set<absl::string_view>* cluster_specifier_plugins_not_seen, |
||||
ValidationErrors* errors) { |
||||
XdsRouteConfigResource::Route route; |
||||
// Parse route match.
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".match"); |
||||
const auto* match = envoy_config_route_v3_Route_match(route_proto); |
||||
if (match == nullptr) { |
||||
errors->AddError("field not present"); |
||||
return absl::nullopt; |
||||
} |
||||
// Skip routes with query_parameters set.
|
||||
size_t query_parameters_size; |
||||
static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters( |
||||
match, &query_parameters_size)); |
||||
if (query_parameters_size > 0) return absl::nullopt; |
||||
// Parse matchers.
|
||||
auto path_matcher = RoutePathMatchParse(match, errors); |
||||
if (!path_matcher.has_value()) return absl::nullopt; |
||||
route.matchers.path_matcher = std::move(*path_matcher); |
||||
RouteHeaderMatchersParse(match, &route, errors); |
||||
RouteRuntimeFractionParse(match, &route, errors); |
||||
} |
||||
// Parse route action.
|
||||
const envoy_config_route_v3_RouteAction* route_action_proto = |
||||
envoy_config_route_v3_Route_route(route_proto); |
||||
if (route_action_proto != nullptr) { |
||||
ValidationErrors::ScopedField field(errors, ".route"); |
||||
auto route_action = RouteActionParse(context, route_action_proto, |
||||
cluster_specifier_plugin_map, errors); |
||||
if (!route_action.has_value()) return absl::nullopt; |
||||
// If the route does not have a retry policy but the vhost does,
|
||||
// use the vhost retry policy for this route.
|
||||
if (!route_action->retry_policy.has_value()) { |
||||
route_action->retry_policy = virtual_host_retry_policy; |
||||
} |
||||
// Mark off plugins used in route action.
|
||||
auto* cluster_specifier_action = absl::get_if< |
||||
XdsRouteConfigResource::Route::RouteAction::ClusterSpecifierPluginName>( |
||||
&route_action->action); |
||||
if (cluster_specifier_action != nullptr) { |
||||
cluster_specifier_plugins_not_seen->erase( |
||||
cluster_specifier_action->cluster_specifier_plugin_name); |
||||
} |
||||
route.action = std::move(*route_action); |
||||
} else if (envoy_config_route_v3_Route_has_non_forwarding_action( |
||||
route_proto)) { |
||||
route.action = XdsRouteConfigResource::Route::NonForwardingAction(); |
||||
} else { |
||||
// Leave route.action initialized to UnknownAction (its default).
|
||||
} |
||||
// Parse typed_per_filter_config.
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); |
||||
route.typed_per_filter_config = ParseTypedPerFilterConfig< |
||||
envoy_config_route_v3_Route, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry>( |
||||
context, route_proto, |
||||
envoy_config_route_v3_Route_typed_per_filter_config_next, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key, |
||||
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value, errors); |
||||
} |
||||
return route; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<const XdsRouteConfigResource> XdsRouteConfigResourceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config, |
||||
ValidationErrors* errors) { |
||||
auto rds_update = std::make_shared<XdsRouteConfigResource>(); |
||||
// Get the cluster spcifier plugin map.
|
||||
if (XdsRlsEnabled()) { |
||||
rds_update->cluster_specifier_plugin_map = |
||||
ClusterSpecifierPluginParse(context, route_config, errors); |
||||
} |
||||
// Build a set of configured cluster_specifier_plugin names to make sure
|
||||
// each is actually referenced by a route action.
|
||||
std::set<absl::string_view> cluster_specifier_plugins_not_seen; |
||||
for (auto& plugin : rds_update->cluster_specifier_plugin_map) { |
||||
cluster_specifier_plugins_not_seen.emplace(plugin.first); |
||||
} |
||||
// 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) { |
||||
ValidationErrors::ScopedField field( |
||||
errors, absl::StrCat(".virtual_hosts[", i, "]")); |
||||
rds_update->virtual_hosts.emplace_back(); |
||||
XdsRouteConfigResource::VirtualHost& vhost = |
||||
rds_update->virtual_hosts.back(); |
||||
// Parse domains.
|
||||
size_t domain_size; |
||||
upb_StringView 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)) { |
||||
ValidationErrors::ScopedField field(errors, |
||||
absl::StrCat(".domains[", j, "]")); |
||||
errors->AddError( |
||||
absl::StrCat("invalid domain pattern \"", domain_pattern, "\"")); |
||||
} |
||||
vhost.domains.emplace_back(std::move(domain_pattern)); |
||||
} |
||||
if (vhost.domains.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".domains"); |
||||
errors->AddError("must be non-empty"); |
||||
} |
||||
// Parse typed_per_filter_config.
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".typed_per_filter_config"); |
||||
vhost.typed_per_filter_config = 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, |
||||
errors); |
||||
} |
||||
// 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) { |
||||
ValidationErrors::ScopedField field(errors, ".retry_policy"); |
||||
virtual_host_retry_policy = |
||||
RetryPolicyParse(context, retry_policy, errors); |
||||
} |
||||
// Parse routes.
|
||||
ValidationErrors::ScopedField field2(errors, ".routes"); |
||||
size_t num_routes; |
||||
const envoy_config_route_v3_Route* const* routes = |
||||
envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes); |
||||
for (size_t j = 0; j < num_routes; ++j) { |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat("[", j, "]")); |
||||
auto route = ParseRoute(context, routes[j], virtual_host_retry_policy, |
||||
rds_update->cluster_specifier_plugin_map, |
||||
&cluster_specifier_plugins_not_seen, errors); |
||||
if (route.has_value()) vhost.routes.emplace_back(std::move(*route)); |
||||
} |
||||
} |
||||
// For cluster specifier plugins that were not used in any route action,
|
||||
// delete them from the update, since they will never be used.
|
||||
for (auto& unused_plugin : cluster_specifier_plugins_not_seen) { |
||||
rds_update->cluster_specifier_plugin_map.erase(std::string(unused_plugin)); |
||||
} |
||||
return rds_update; |
||||
} |
||||
|
||||
//
|
||||
// XdsRouteConfigResourceType
|
||||
//
|
||||
|
||||
namespace { |
||||
|
||||
void MaybeLogRouteConfiguration( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config) { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer) && ABSL_VLOG_IS_ON(2)) { |
||||
const upb_MessageDef* msg_type = |
||||
envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab); |
||||
char buf[10240]; |
||||
upb_TextEncode(reinterpret_cast<const upb_Message*>(route_config), msg_type, |
||||
nullptr, 0, buf, sizeof(buf)); |
||||
VLOG(2) << "[xds_client " << context.client |
||||
<< "] RouteConfiguration: " << buf; |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
XdsResourceType::DecodeResult XdsRouteConfigResourceType::Decode( |
||||
const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const { |
||||
DecodeResult result; |
||||
// Parse serialized proto.
|
||||
auto* resource = envoy_config_route_v3_RouteConfiguration_parse( |
||||
serialized_resource.data(), serialized_resource.size(), context.arena); |
||||
if (resource == nullptr) { |
||||
result.resource = |
||||
absl::InvalidArgumentError("Can't parse RouteConfiguration resource."); |
||||
return result; |
||||
} |
||||
MaybeLogRouteConfiguration(context, resource); |
||||
// Validate resource.
|
||||
result.name = UpbStringToStdString( |
||||
envoy_config_route_v3_RouteConfiguration_name(resource)); |
||||
ValidationErrors errors; |
||||
auto rds_update = XdsRouteConfigResourceParse(context, resource, &errors); |
||||
if (!errors.ok()) { |
||||
absl::Status status = |
||||
errors.status(absl::StatusCode::kInvalidArgument, |
||||
"errors validating RouteConfiguration resource"); |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(ERROR) << "[xds_client " << context.client |
||||
<< "] invalid RouteConfiguration " << *result.name << ": " |
||||
<< status; |
||||
} |
||||
result.resource = std::move(status); |
||||
} else { |
||||
if (GRPC_TRACE_FLAG_ENABLED_OBJ(*context.tracer)) { |
||||
LOG(INFO) << "[xds_client " << context.client |
||||
<< "] parsed RouteConfiguration " << *result.name << ": " |
||||
<< rds_update->ToString(); |
||||
} |
||||
result.resource = std::move(rds_update); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,80 @@ |
||||
//
|
||||
// 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_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <algorithm> |
||||
#include <map> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
#include "absl/types/variant.h" |
||||
#include "envoy/config/route/v3/route.upb.h" |
||||
#include "envoy/config/route/v3/route.upbdefs.h" |
||||
#include "re2/re2.h" |
||||
#include "upb/reflection/def.h" |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/matchers/matchers.h" |
||||
#include "src/core/xds/grpc/xds_bootstrap_grpc.h" |
||||
#include "src/core/xds/grpc/xds_cluster_specifier_plugin.h" |
||||
#include "src/core/xds/grpc/xds_http_filter.h" |
||||
#include "src/core/xds/grpc/xds_route_config.h" |
||||
#include "src/core/xds/xds_client/xds_client.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type.h" |
||||
#include "src/core/xds/xds_client/xds_resource_type_impl.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
std::shared_ptr<const XdsRouteConfigResource> XdsRouteConfigResourceParse( |
||||
const XdsResourceType::DecodeContext& context, |
||||
const envoy_config_route_v3_RouteConfiguration* route_config, |
||||
ValidationErrors* errors); |
||||
|
||||
class XdsRouteConfigResourceType final |
||||
: public XdsResourceTypeImpl<XdsRouteConfigResourceType, |
||||
XdsRouteConfigResource> { |
||||
public: |
||||
absl::string_view type_url() const override { |
||||
return "envoy.config.route.v3.RouteConfiguration"; |
||||
} |
||||
|
||||
DecodeResult Decode(const XdsResourceType::DecodeContext& context, |
||||
absl::string_view serialized_resource) const override; |
||||
|
||||
void InitUpbSymtab(XdsClient* xds_client, |
||||
upb_DefPool* symtab) const override { |
||||
envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab); |
||||
const auto& cluster_specifier_plugin_registry = |
||||
static_cast<const GrpcXdsBootstrap&>(xds_client->bootstrap()) |
||||
.cluster_specifier_plugin_registry(); |
||||
cluster_specifier_plugin_registry.PopulateSymtab(symtab); |
||||
} |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_ROUTE_CONFIG_PARSER_H
|
@ -0,0 +1,152 @@ |
||||
//
|
||||
// Copyright 2019 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 "src/core/xds/grpc/xds_server_grpc.h" |
||||
|
||||
#include <stdlib.h> |
||||
|
||||
#include <algorithm> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/util/json/json_reader.h" |
||||
#include "src/core/util/json/json_writer.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
constexpr absl::string_view kServerFeatureIgnoreResourceDeletion = |
||||
"ignore_resource_deletion"; |
||||
|
||||
} // namespace
|
||||
|
||||
bool GrpcXdsServer::IgnoreResourceDeletion() const { |
||||
return server_features_.find(std::string( |
||||
kServerFeatureIgnoreResourceDeletion)) != server_features_.end(); |
||||
} |
||||
|
||||
bool GrpcXdsServer::Equals(const XdsServer& other) const { |
||||
const auto& o = static_cast<const GrpcXdsServer&>(other); |
||||
return (server_uri_ == o.server_uri_ && |
||||
channel_creds_config_->type() == o.channel_creds_config_->type() && |
||||
channel_creds_config_->Equals(*o.channel_creds_config_) && |
||||
server_features_ == o.server_features_); |
||||
} |
||||
|
||||
std::string GrpcXdsServer::Key() const { return JsonDump(ToJson()); } |
||||
|
||||
const JsonLoaderInterface* GrpcXdsServer::JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<GrpcXdsServer>() |
||||
.Field("server_uri", &GrpcXdsServer::server_uri_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
struct ChannelCreds { |
||||
std::string type; |
||||
Json::Object config; |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<ChannelCreds>() |
||||
.Field("type", &ChannelCreds::type) |
||||
.OptionalField("config", &ChannelCreds::config) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
}; |
||||
|
||||
} // namespace
|
||||
|
||||
void GrpcXdsServer::JsonPostLoad(const Json& json, const JsonArgs& args, |
||||
ValidationErrors* errors) { |
||||
// Parse "channel_creds".
|
||||
auto channel_creds_list = LoadJsonObjectField<std::vector<ChannelCreds>>( |
||||
json.object(), args, "channel_creds", errors); |
||||
if (channel_creds_list.has_value()) { |
||||
ValidationErrors::ScopedField field(errors, ".channel_creds"); |
||||
for (size_t i = 0; i < channel_creds_list->size(); ++i) { |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); |
||||
auto& creds = (*channel_creds_list)[i]; |
||||
// Select the first channel creds type that we support, but
|
||||
// validate all entries.
|
||||
if (CoreConfiguration::Get().channel_creds_registry().IsSupported( |
||||
creds.type)) { |
||||
ValidationErrors::ScopedField field(errors, ".config"); |
||||
auto config = |
||||
CoreConfiguration::Get().channel_creds_registry().ParseConfig( |
||||
creds.type, Json::FromObject(creds.config), args, errors); |
||||
if (channel_creds_config_ == nullptr) { |
||||
channel_creds_config_ = std::move(config); |
||||
} |
||||
} |
||||
} |
||||
if (channel_creds_config_ == nullptr) { |
||||
errors->AddError("no known creds type found"); |
||||
} |
||||
} |
||||
// Parse "server_features".
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".server_features"); |
||||
auto it = json.object().find("server_features"); |
||||
if (it != json.object().end()) { |
||||
if (it->second.type() != Json::Type::kArray) { |
||||
errors->AddError("is not an array"); |
||||
} else { |
||||
const Json::Array& array = it->second.array(); |
||||
for (const Json& feature_json : array) { |
||||
if (feature_json.type() == Json::Type::kString && |
||||
(feature_json.string() == kServerFeatureIgnoreResourceDeletion)) { |
||||
server_features_.insert(feature_json.string()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
Json GrpcXdsServer::ToJson() const { |
||||
Json::Object channel_creds_json{ |
||||
{"type", Json::FromString(std::string(channel_creds_config_->type()))}, |
||||
}; |
||||
if (channel_creds_config_ != nullptr) { |
||||
channel_creds_json["config"] = channel_creds_config_->ToJson(); |
||||
} |
||||
Json::Object json{ |
||||
{"server_uri", Json::FromString(server_uri_)}, |
||||
{"channel_creds", |
||||
Json::FromArray({Json::FromObject(std::move(channel_creds_json))})}, |
||||
}; |
||||
if (!server_features_.empty()) { |
||||
Json::Array server_features_json; |
||||
for (auto& feature : server_features_) { |
||||
server_features_json.emplace_back(Json::FromString(feature)); |
||||
} |
||||
json["server_features"] = Json::FromArray(std::move(server_features_json)); |
||||
} |
||||
return Json::FromObject(std::move(json)); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,61 @@ |
||||
//
|
||||
// Copyright 2019 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_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H |
||||
#define GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H |
||||
|
||||
#include <set> |
||||
#include <string> |
||||
|
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/security/credentials/channel_creds_registry.h" |
||||
#include "src/core/util/json/json.h" |
||||
#include "src/core/util/json/json_args.h" |
||||
#include "src/core/util/json/json_object_loader.h" |
||||
#include "src/core/xds/xds_client/xds_bootstrap.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class GrpcXdsServer final : public XdsBootstrap::XdsServer { |
||||
public: |
||||
const std::string& server_uri() const override { return server_uri_; } |
||||
|
||||
bool IgnoreResourceDeletion() const override; |
||||
|
||||
bool Equals(const XdsServer& other) const override; |
||||
|
||||
std::string Key() const override; |
||||
|
||||
RefCountedPtr<ChannelCredsConfig> channel_creds_config() const { |
||||
return channel_creds_config_; |
||||
} |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&); |
||||
void JsonPostLoad(const Json& json, const JsonArgs& args, |
||||
ValidationErrors* errors); |
||||
|
||||
Json ToJson() const; |
||||
|
||||
private: |
||||
std::string server_uri_; |
||||
RefCountedPtr<ChannelCredsConfig> channel_creds_config_; |
||||
std::set<std::string> server_features_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_SERVER_GRPC_H
|
Loading…
Reference in new issue