blob: 610d4e228635cc016e7833b55572bf5c3b55b117 [file] [log] [blame]
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <string>
#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 "envoy/admin/v3/config_dump.upb.h"
#include "envoy/config/cluster/v3/circuit_breaker.upb.h"
#include "envoy/config/cluster/v3/cluster.upb.h"
#include "envoy/config/cluster/v3/cluster.upbdefs.h"
#include "envoy/config/core/v3/address.upb.h"
#include "envoy/config/core/v3/base.upb.h"
#include "envoy/config/core/v3/base.upbdefs.h"
#include "envoy/config/core/v3/config_source.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.upbdefs.h"
#include "envoy/config/endpoint/v3/endpoint_components.upb.h"
#include "envoy/config/endpoint/v3/load_report.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/config/route/v3/route.upbdefs.h"
#include "envoy/config/route/v3/route_components.upb.h"
#include "envoy/config/route/v3/route_components.upbdefs.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h"
#include "envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.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/common.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h"
#include "envoy/service/cluster/v3/cds.upb.h"
#include "envoy/service/cluster/v3/cds.upbdefs.h"
#include "envoy/service/discovery/v3/discovery.upb.h"
#include "envoy/service/discovery/v3/discovery.upbdefs.h"
#include "envoy/service/endpoint/v3/eds.upb.h"
#include "envoy/service/endpoint/v3/eds.upbdefs.h"
#include "envoy/service/listener/v3/lds.upb.h"
#include "envoy/service/load_stats/v3/lrs.upb.h"
#include "envoy/service/load_stats/v3/lrs.upbdefs.h"
#include "envoy/service/route/v3/rds.upb.h"
#include "envoy/service/route/v3/rds.upbdefs.h"
#include "envoy/service/status/v3/csds.upb.h"
#include "envoy/service/status/v3/csds.upbdefs.h"
#include "envoy/type/matcher/v3/regex.upb.h"
#include "envoy/type/matcher/v3/string.upb.h"
#include "envoy/type/v3/percent.upb.h"
#include "envoy/type/v3/range.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/timestamp.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "google/rpc/status.upb.h"
#include "udpa/type/v1/typed_struct.upb.h"
#include "upb/text_encode.h"
#include "upb/upb.h"
#include "upb/upb.hpp"
#include <grpc/impl/codegen/log.h>
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
#include "src/core/ext/xds/xds_api.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/sockaddr.h"
#include "src/core/lib/iomgr/socket_utils.h"
#include "src/core/lib/slice/slice_utils.h"
namespace grpc_core {
// TODO(donnadionne): Check to see if cluster types aggregate_cluster and
// logical_dns are enabled, this will be
// removed once the cluster types are fully integration-tested and enabled by
// default.
bool XdsAggregateAndLogicalDnsClusterEnabled() {
char* value = gpr_getenv(
"GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER");
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value);
gpr_free(value);
return parse_succeeded && parsed_value;
}
// TODO(donnadionne): Check to see if ring hash policy is enabled, this will be
// removed once ring hash policy is fully integration-tested and enabled by
// default.
bool XdsRingHashEnabled() {
char* value = gpr_getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH");
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value);
gpr_free(value);
return parse_succeeded && parsed_value;
}
// TODO(yashykt): Check to see if xDS security is enabled. This will be
// removed once this feature is fully integration-tested and enabled by
// default.
bool XdsSecurityEnabled() {
char* value = gpr_getenv("GRPC_XDS_EXPERIMENTAL_SECURITY_SUPPORT");
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value, &parsed_value);
gpr_free(value);
return parse_succeeded && parsed_value;
}
//
// XdsApi::Route::HashPolicy
//
XdsApi::Route::HashPolicy::HashPolicy(const HashPolicy& other)
: type(other.type),
header_name(other.header_name),
regex_substitution(other.regex_substitution) {
if (other.regex != nullptr) {
regex =
absl::make_unique<RE2>(other.regex->pattern(), other.regex->options());
}
}
XdsApi::Route::HashPolicy& XdsApi::Route::HashPolicy::operator=(
const HashPolicy& other) {
type = other.type;
header_name = other.header_name;
if (other.regex != nullptr) {
regex =
absl::make_unique<RE2>(other.regex->pattern(), other.regex->options());
}
regex_substitution = other.regex_substitution;
return *this;
}
XdsApi::Route::HashPolicy::HashPolicy(HashPolicy&& other) noexcept
: type(other.type),
header_name(std::move(other.header_name)),
regex(std::move(other.regex)),
regex_substitution(std::move(other.regex_substitution)) {}
XdsApi::Route::HashPolicy& XdsApi::Route::HashPolicy::operator=(
HashPolicy&& other) noexcept {
type = other.type;
header_name = std::move(other.header_name);
regex = std::move(other.regex);
regex_substitution = std::move(other.regex_substitution);
return *this;
}
bool XdsApi::Route::HashPolicy::HashPolicy::operator==(
const HashPolicy& other) const {
if (type != other.type) return false;
if (type == Type::HEADER) {
if (regex == nullptr) {
if (other.regex != nullptr) return false;
} else {
if (other.regex == nullptr) return false;
return header_name == other.header_name &&
regex->pattern() == other.regex->pattern() &&
regex_substitution == other.regex_substitution;
}
}
return true;
}
std::string XdsApi::Route::HashPolicy::ToString() const {
std::vector<std::string> contents;
switch (type) {
case Type::HEADER:
contents.push_back("type=HEADER");
break;
case Type::CHANNEL_ID:
contents.push_back("type=CHANNEL_ID");
break;
}
contents.push_back(
absl::StrFormat("terminal=%s", terminal ? "true" : "false"));
if (type == Type::HEADER) {
contents.push_back(absl::StrFormat(
"Header %s:/%s/%s", header_name,
(regex == nullptr) ? "" : regex->pattern(), regex_substitution));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsApi::Route
//
std::string XdsApi::Route::Matchers::ToString() const {
std::vector<std::string> contents;
contents.push_back(
absl::StrFormat("PathMatcher{%s}", path_matcher.ToString()));
for (const HeaderMatcher& header_matcher : header_matchers) {
contents.push_back(header_matcher.ToString());
}
if (fraction_per_million.has_value()) {
contents.push_back(absl::StrFormat("Fraction Per Million %d",
fraction_per_million.value()));
}
return absl::StrJoin(contents, "\n");
}
std::string XdsApi::Route::ClusterWeight::ToString() const {
std::vector<std::string> contents;
contents.push_back(absl::StrCat("cluster=", name));
contents.push_back(absl::StrCat("weight=", weight));
if (!typed_per_filter_config.empty()) {
std::vector<std::string> parts;
for (const auto& p : typed_per_filter_config) {
const std::string& key = p.first;
const auto& config = p.second;
parts.push_back(absl::StrCat(key, "=", config.ToString()));
}
contents.push_back(absl::StrCat("typed_per_filter_config={",
absl::StrJoin(parts, ", "), "}"));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
std::string XdsApi::Route::ToString() const {
std::vector<std::string> contents;
contents.push_back(matchers.ToString());
for (const HashPolicy& hash_policy : hash_policies) {
contents.push_back(absl::StrCat("hash_policy=", hash_policy.ToString()));
}
if (!cluster_name.empty()) {
contents.push_back(absl::StrFormat("Cluster name: %s", cluster_name));
}
for (const ClusterWeight& cluster_weight : weighted_clusters) {
contents.push_back(cluster_weight.ToString());
}
if (max_stream_duration.has_value()) {
contents.push_back(max_stream_duration->ToString());
}
if (!typed_per_filter_config.empty()) {
contents.push_back("typed_per_filter_config={");
for (const auto& p : typed_per_filter_config) {
const std::string& name = p.first;
const auto& config = p.second;
contents.push_back(absl::StrCat(" ", name, "=", config.ToString()));
}
contents.push_back("}");
}
return absl::StrJoin(contents, "\n");
}
//
// XdsApi::RdsUpdate
//
std::string XdsApi::RdsUpdate::ToString() const {
std::vector<std::string> vhosts;
for (const VirtualHost& vhost : virtual_hosts) {
vhosts.push_back(
absl::StrCat("vhost={\n"
" domains=[",
absl::StrJoin(vhost.domains, ", "),
"]\n"
" routes=[\n"));
for (const XdsApi::Route& route : vhost.routes) {
vhosts.push_back(" {\n");
vhosts.push_back(route.ToString());
vhosts.push_back("\n }\n");
}
vhosts.push_back(" ]\n");
vhosts.push_back(" typed_per_filter_config={\n");
for (const auto& p : vhost.typed_per_filter_config) {
const std::string& name = p.first;
const auto& config = p.second;
vhosts.push_back(
absl::StrCat(" ", name, "=", config.ToString(), "\n"));
}
vhosts.push_back(" }\n");
vhosts.push_back("]\n");
}
return absl::StrJoin(vhosts, "");
}
namespace {
// Better match type has smaller value.
enum MatchType {
EXACT_MATCH,
SUFFIX_MATCH,
PREFIX_MATCH,
UNIVERSE_MATCH,
INVALID_MATCH,
};
// Returns true if match succeeds.
bool DomainMatch(MatchType match_type, const std::string& domain_pattern_in,
const std::string& expected_host_name_in) {
// Normalize the args to lower-case. Domain matching is case-insensitive.
std::string domain_pattern = domain_pattern_in;
std::string expected_host_name = expected_host_name_in;
std::transform(domain_pattern.begin(), domain_pattern.end(),
domain_pattern.begin(),
[](unsigned char c) { return std::tolower(c); });
std::transform(expected_host_name.begin(), expected_host_name.end(),
expected_host_name.begin(),
[](unsigned char c) { return std::tolower(c); });
if (match_type == EXACT_MATCH) {
return domain_pattern == expected_host_name;
} else if (match_type == SUFFIX_MATCH) {
// Asterisk must match at least one char.
if (expected_host_name.size() < domain_pattern.size()) return false;
absl::string_view pattern_suffix(domain_pattern.c_str() + 1);
absl::string_view host_suffix(expected_host_name.c_str() +
expected_host_name.size() -
pattern_suffix.size());
return pattern_suffix == host_suffix;
} else if (match_type == PREFIX_MATCH) {
// Asterisk must match at least one char.
if (expected_host_name.size() < domain_pattern.size()) return false;
absl::string_view pattern_prefix(domain_pattern.c_str(),
domain_pattern.size() - 1);
absl::string_view host_prefix(expected_host_name.c_str(),
pattern_prefix.size());
return pattern_prefix == host_prefix;
} else {
return match_type == UNIVERSE_MATCH;
}
}
MatchType DomainPatternMatchType(const std::string& domain_pattern) {
if (domain_pattern.empty()) return INVALID_MATCH;
if (domain_pattern.find('*') == std::string::npos) return EXACT_MATCH;
if (domain_pattern == "*") return UNIVERSE_MATCH;
if (domain_pattern[0] == '*') return SUFFIX_MATCH;
if (domain_pattern[domain_pattern.size() - 1] == '*') return PREFIX_MATCH;
return INVALID_MATCH;
}
} // namespace
XdsApi::RdsUpdate::VirtualHost* XdsApi::RdsUpdate::FindVirtualHostForDomain(
const std::string& domain) {
// Find the best matched virtual host.
// The search order for 4 groups of domain patterns:
// 1. Exact match.
// 2. Suffix match (e.g., "*ABC").
// 3. Prefix match (e.g., "ABC*").
// 4. Universe match (i.e., "*").
// Within each group, longest match wins.
// If the same best matched domain pattern appears in multiple virtual hosts,
// the first matched virtual host wins.
VirtualHost* target_vhost = nullptr;
MatchType best_match_type = INVALID_MATCH;
size_t longest_match = 0;
// Check each domain pattern in each virtual host to determine the best
// matched virtual host.
for (VirtualHost& vhost : virtual_hosts) {
for (const std::string& domain_pattern : vhost.domains) {
// Check the match type first. Skip the pattern if it's not better than
// current match.
const MatchType match_type = DomainPatternMatchType(domain_pattern);
// This should be caught by RouteConfigParse().
GPR_ASSERT(match_type != INVALID_MATCH);
if (match_type > best_match_type) continue;
if (match_type == best_match_type &&
domain_pattern.size() <= longest_match) {
continue;
}
// Skip if match fails.
if (!DomainMatch(match_type, domain_pattern, domain)) continue;
// Choose this match.
target_vhost = &vhost;
best_match_type = match_type;
longest_match = domain_pattern.size();
if (best_match_type == EXACT_MATCH) break;
}
if (best_match_type == EXACT_MATCH) break;
}
return target_vhost;
}
//
// XdsApi::CommonTlsContext::CertificateValidationContext
//
std::string XdsApi::CommonTlsContext::CertificateValidationContext::ToString()
const {
std::vector<std::string> contents;
for (const auto& match : match_subject_alt_names) {
contents.push_back(match.ToString());
}
return absl::StrFormat("{match_subject_alt_names=[%s]}",
absl::StrJoin(contents, ", "));
}
bool XdsApi::CommonTlsContext::CertificateValidationContext::Empty() const {
return match_subject_alt_names.empty();
}
//
// XdsApi::CommonTlsContext::CertificateValidationContext
//
std::string XdsApi::CommonTlsContext::CertificateProviderInstance::ToString()
const {
absl::InlinedVector<std::string, 2> contents;
if (!instance_name.empty()) {
contents.push_back(absl::StrFormat("instance_name=%s", instance_name));
}
if (!certificate_name.empty()) {
contents.push_back(
absl::StrFormat("certificate_name=%s", certificate_name));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
bool XdsApi::CommonTlsContext::CertificateProviderInstance::Empty() const {
return instance_name.empty() && certificate_name.empty();
}
//
// XdsApi::CommonTlsContext::CombinedCertificateValidationContext
//
std::string
XdsApi::CommonTlsContext::CombinedCertificateValidationContext::ToString()
const {
absl::InlinedVector<std::string, 2> contents;
if (!default_validation_context.Empty()) {
contents.push_back(absl::StrFormat("default_validation_context=%s",
default_validation_context.ToString()));
}
if (!validation_context_certificate_provider_instance.Empty()) {
contents.push_back(absl::StrFormat(
"validation_context_certificate_provider_instance=%s",
validation_context_certificate_provider_instance.ToString()));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
bool XdsApi::CommonTlsContext::CombinedCertificateValidationContext::Empty()
const {
return default_validation_context.Empty() &&
validation_context_certificate_provider_instance.Empty();
}
//
// XdsApi::CommonTlsContext
//
std::string XdsApi::CommonTlsContext::ToString() const {
absl::InlinedVector<std::string, 2> contents;
if (!tls_certificate_certificate_provider_instance.Empty()) {
contents.push_back(absl::StrFormat(
"tls_certificate_certificate_provider_instance=%s",
tls_certificate_certificate_provider_instance.ToString()));
}
if (!combined_validation_context.Empty()) {
contents.push_back(absl::StrFormat("combined_validation_context=%s",
combined_validation_context.ToString()));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
bool XdsApi::CommonTlsContext::Empty() const {
return tls_certificate_certificate_provider_instance.Empty() &&
combined_validation_context.Empty();
}
//
// XdsApi::DownstreamTlsContext
//
std::string XdsApi::DownstreamTlsContext::ToString() const {
return absl::StrFormat("common_tls_context=%s, require_client_certificate=%s",
common_tls_context.ToString(),
require_client_certificate ? "true" : "false");
}
bool XdsApi::DownstreamTlsContext::Empty() const {
return common_tls_context.Empty();
}
//
// XdsApi::LdsUpdate::HttpConnectionManager
//
std::string XdsApi::LdsUpdate::HttpConnectionManager::ToString() const {
absl::InlinedVector<std::string, 4> contents;
contents.push_back(absl::StrFormat(
"route_config_name=%s",
!route_config_name.empty() ? route_config_name.c_str() : "<inlined>"));
contents.push_back(absl::StrFormat("http_max_stream_duration=%s",
http_max_stream_duration.ToString()));
if (rds_update.has_value()) {
contents.push_back(
absl::StrFormat("rds_update=%s", rds_update->ToString()));
}
if (!http_filters.empty()) {
std::vector<std::string> filter_strings;
for (const auto& http_filter : http_filters) {
filter_strings.push_back(http_filter.ToString());
}
contents.push_back(absl::StrCat("http_filters=[",
absl::StrJoin(filter_strings, ", "), "]"));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsApi::LdsUpdate::HttpFilter
//
std::string XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter::ToString()
const {
return absl::StrCat("{name=", name, ", config=", config.ToString(), "}");
}
//
// XdsApi::LdsUpdate::FilterChainData
//
std::string XdsApi::LdsUpdate::FilterChainData::ToString() const {
return absl::StrCat(
"{downstream_tls_context=", downstream_tls_context.ToString(),
" http_connection_manager=", http_connection_manager.ToString(), "}");
}
//
// XdsApi::LdsUpdate::FilterChainMap::CidrRange
//
std::string XdsApi::LdsUpdate::FilterChainMap::CidrRange::ToString() const {
return absl::StrCat(
"{address_prefix=", grpc_sockaddr_to_string(&address, false),
", prefix_len=", prefix_len, "}");
}
//
// FilterChain
//
struct FilterChain {
struct FilterChainMatch {
uint32_t destination_port = 0;
std::vector<XdsApi::LdsUpdate::FilterChainMap::CidrRange> prefix_ranges;
XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType source_type =
XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType::kAny;
std::vector<XdsApi::LdsUpdate::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<XdsApi::LdsUpdate::FilterChainData> filter_chain_data;
};
std::string FilterChain::FilterChainMatch::ToString() const {
absl::InlinedVector<std::string, 8> 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;
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 == XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType::
kSameIpOrLoopback) {
contents.push_back("source_type=SAME_IP_OR_LOOPBACK");
} else if (source_type == XdsApi::LdsUpdate::FilterChainMap::
ConnectionSourceType::kExternal) {
contents.push_back("source_type=EXTERNAL");
}
if (!source_prefix_ranges.empty()) {
std::vector<std::string> source_prefix_ranges_content;
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, ", "), "}");
}
//
// XdsApi::LdsUpdate::FilterChainMap
//
std::string XdsApi::LdsUpdate::FilterChainMap::ToString() const {
std::vector<std::string> contents;
for (const auto& destination_ip : destination_ip_vector) {
for (int source_type = 0; source_type < 3; ++source_type) {
for (const auto& source_ip :
destination_ip.source_types_array[source_type]) {
for (const auto& source_port_pair : source_ip.ports_map) {
FilterChain::FilterChainMatch filter_chain_match;
if (destination_ip.prefix_range.has_value()) {
filter_chain_match.prefix_ranges.push_back(
*destination_ip.prefix_range);
}
filter_chain_match.source_type = static_cast<
XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType>(
source_type);
if (source_ip.prefix_range.has_value()) {
filter_chain_match.source_prefix_ranges.push_back(
*source_ip.prefix_range);
}
if (source_port_pair.first != 0) {
filter_chain_match.source_ports.push_back(source_port_pair.first);
}
contents.push_back(absl::StrCat(
"{filter_chain_match=", filter_chain_match.ToString(),
", filter_chain=", source_port_pair.second.data->ToString(),
"}"));
}
}
}
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsApi::LdsUpdate
//
std::string XdsApi::LdsUpdate::ToString() const {
absl::InlinedVector<std::string, 4> contents;
if (type == ListenerType::kTcpListener) {
contents.push_back(absl::StrCat("address=", address));
contents.push_back(
absl::StrCat("filter_chain_map=", filter_chain_map.ToString()));
if (default_filter_chain.has_value()) {
contents.push_back(absl::StrCat("default_filter_chain=",
default_filter_chain->ToString()));
}
} else if (type == ListenerType::kHttpApiListener) {
contents.push_back(absl::StrFormat("http_connection_manager=%s",
http_connection_manager.ToString()));
}
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsApi::CdsUpdate
//
std::string XdsApi::CdsUpdate::ToString() const {
absl::InlinedVector<std::string, 4> contents;
if (!eds_service_name.empty()) {
contents.push_back(
absl::StrFormat("eds_service_name=%s", eds_service_name));
}
if (!common_tls_context.Empty()) {
contents.push_back(absl::StrFormat("common_tls_context=%s",
common_tls_context.ToString()));
}
if (lrs_load_reporting_server_name.has_value()) {
contents.push_back(absl::StrFormat("lrs_load_reporting_server_name=%s",
lrs_load_reporting_server_name.value()));
}
contents.push_back(
absl::StrFormat("max_concurrent_requests=%d", max_concurrent_requests));
return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
}
//
// XdsApi::EdsUpdate
//
std::string XdsApi::EdsUpdate::Priority::Locality::ToString() const {
std::vector<std::string> endpoint_strings;
for (const ServerAddress& endpoint : endpoints) {
endpoint_strings.emplace_back(endpoint.ToString());
}
return absl::StrCat("{name=", name->AsHumanReadableString(),
", lb_weight=", lb_weight, ", endpoints=[",
absl::StrJoin(endpoint_strings, ", "), "]}");
}
bool XdsApi::EdsUpdate::Priority::operator==(const Priority& other) const {
if (localities.size() != other.localities.size()) return false;
auto it1 = localities.begin();
auto it2 = other.localities.begin();
while (it1 != localities.end()) {
if (*it1->first != *it2->first) return false;
if (it1->second != it2->second) return false;
++it1;
++it2;
}
return true;
}
std::string XdsApi::EdsUpdate::Priority::ToString() const {
std::vector<std::string> locality_strings;
for (const auto& p : localities) {
locality_strings.emplace_back(p.second.ToString());
}
return absl::StrCat("[", absl::StrJoin(locality_strings, ", "), "]");
}
bool XdsApi::EdsUpdate::DropConfig::ShouldDrop(
const std::string** category_name) const {
for (size_t i = 0; i < drop_category_list_.size(); ++i) {
const auto& drop_category = drop_category_list_[i];
// Generate a random number in [0, 1000000).
const uint32_t random = static_cast<uint32_t>(rand()) % 1000000;
if (random < drop_category.parts_per_million) {
*category_name = &drop_category.name;
return true;
}
}
return false;
}
std::string XdsApi::EdsUpdate::DropConfig::ToString() const {
std::vector<std::string> category_strings;
for (const DropCategory& category : drop_category_list_) {
category_strings.emplace_back(
absl::StrCat(category.name, "=", category.parts_per_million));
}
return absl::StrCat("{[", absl::StrJoin(category_strings, ", "),
"], drop_all=", drop_all_, "}");
}
std::string XdsApi::EdsUpdate::ToString() const {
std::vector<std::string> priority_strings;
for (size_t i = 0; i < priorities.size(); ++i) {
const Priority& priority = priorities[i];
priority_strings.emplace_back(
absl::StrCat("priority ", i, ": ", priority.ToString()));
}
return absl::StrCat("priorities=[", absl::StrJoin(priority_strings, ", "),
"], drop_config=", drop_config->ToString());
}
//
// XdsApi
//
const char* XdsApi::kLdsTypeUrl =
"type.googleapis.com/envoy.config.listener.v3.Listener";
const char* XdsApi::kRdsTypeUrl =
"type.googleapis.com/envoy.config.route.v3.RouteConfiguration";
const char* XdsApi::kCdsTypeUrl =
"type.googleapis.com/envoy.config.cluster.v3.Cluster";
const char* XdsApi::kEdsTypeUrl =
"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
namespace {
const char* kLdsV2TypeUrl = "type.googleapis.com/envoy.api.v2.Listener";
const char* kRdsV2TypeUrl =
"type.googleapis.com/envoy.api.v2.RouteConfiguration";
const char* kCdsV2TypeUrl = "type.googleapis.com/envoy.api.v2.Cluster";
const char* kEdsV2TypeUrl =
"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment";
bool IsLds(absl::string_view type_url, bool* is_v2 = nullptr) {
if (type_url == XdsApi::kLdsTypeUrl) return true;
if (type_url == kLdsV2TypeUrl) {
if (is_v2 != nullptr) *is_v2 = true;
return true;
}
return false;
}
bool IsRds(absl::string_view type_url) {
return type_url == XdsApi::kRdsTypeUrl || type_url == kRdsV2TypeUrl;
}
bool IsCds(absl::string_view type_url) {
return type_url == XdsApi::kCdsTypeUrl || type_url == kCdsV2TypeUrl;
}
bool IsEds(absl::string_view type_url) {
return type_url == XdsApi::kEdsTypeUrl || type_url == kEdsV2TypeUrl;
}
} // namespace
XdsApi::XdsApi(XdsClient* client, TraceFlag* tracer,
const XdsBootstrap::Node* node)
: client_(client),
tracer_(tracer),
node_(node),
build_version_(absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING, " ",
grpc_version_string())),
user_agent_name_(absl::StrCat("gRPC C-core ", GPR_PLATFORM_STRING)) {
// Populate upb symtab with xDS proto messages that we want to print
// properly in logs.
// Note: This won't actually work properly until upb adds support for
// Any fields in textproto printing (internal b/178821188).
envoy_config_listener_v3_Listener_getmsgdef(symtab_.ptr());
envoy_config_route_v3_RouteConfiguration_getmsgdef(symtab_.ptr());
envoy_config_cluster_v3_Cluster_getmsgdef(symtab_.ptr());
envoy_extensions_clusters_aggregate_v3_ClusterConfig_getmsgdef(symtab_.ptr());
envoy_config_cluster_v3_Cluster_getmsgdef(symtab_.ptr());
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(symtab_.ptr());
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_getmsgdef(
symtab_.ptr());
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef(
symtab_.ptr());
// Load HTTP filter proto messages into the upb symtab.
XdsHttpFilterRegistry::PopulateSymtab(symtab_.ptr());
}
namespace {
struct EncodingContext {
XdsClient* client;
TraceFlag* tracer;
upb_symtab* symtab;
upb_arena* arena;
bool use_v3;
};
// Works for both std::string and absl::string_view.
template <typename T>
inline upb_strview StdStringToUpbString(const T& str) {
return upb_strview_make(str.data(), str.size());
}
void PopulateMetadataValue(const EncodingContext& context,
google_protobuf_Value* value_pb, const Json& value);
void PopulateListValue(const EncodingContext& context,
google_protobuf_ListValue* list_value,
const Json::Array& values) {
for (const auto& value : values) {
auto* value_pb =
google_protobuf_ListValue_add_values(list_value, context.arena);
PopulateMetadataValue(context, value_pb, value);
}
}
void PopulateMetadata(const EncodingContext& context,
google_protobuf_Struct* metadata_pb,
const Json::Object& metadata) {
for (const auto& p : metadata) {
google_protobuf_Value* value = google_protobuf_Value_new(context.arena);
PopulateMetadataValue(context, value, p.second);
google_protobuf_Struct_fields_set(
metadata_pb, StdStringToUpbString(p.first), value, context.arena);
}
}
void PopulateMetadataValue(const EncodingContext& context,
google_protobuf_Value* value_pb, const Json& value) {
switch (value.type()) {
case Json::Type::JSON_NULL:
google_protobuf_Value_set_null_value(value_pb, 0);
break;
case Json::Type::NUMBER:
google_protobuf_Value_set_number_value(
value_pb, strtod(value.string_value().c_str(), nullptr));
break;
case Json::Type::STRING:
google_protobuf_Value_set_string_value(
value_pb, StdStringToUpbString(value.string_value()));
break;
case Json::Type::JSON_TRUE:
google_protobuf_Value_set_bool_value(value_pb, true);
break;
case Json::Type::JSON_FALSE:
google_protobuf_Value_set_bool_value(value_pb, false);
break;
case Json::Type::OBJECT: {
google_protobuf_Struct* struct_value =
google_protobuf_Value_mutable_struct_value(value_pb, context.arena);
PopulateMetadata(context, struct_value, value.object_value());
break;
}
case Json::Type::ARRAY: {
google_protobuf_ListValue* list_value =
google_protobuf_Value_mutable_list_value(value_pb, context.arena);
PopulateListValue(context, list_value, value.array_value());
break;
}
}
}
// Helper functions to manually do protobuf string encoding, so that we
// can populate the node build_version field that was removed in v3.
std::string EncodeVarint(uint64_t val) {
std::string data;
do {
uint8_t byte = val & 0x7fU;
val >>= 7;
if (val) byte |= 0x80U;
data += byte;
} while (val);
return data;
}
std::string EncodeTag(uint32_t field_number, uint8_t wire_type) {
return EncodeVarint((field_number << 3) | wire_type);
}
std::string EncodeStringField(uint32_t field_number, const std::string& str) {
static const uint8_t kDelimitedWireType = 2;
return EncodeTag(field_number, kDelimitedWireType) +
EncodeVarint(str.size()) + str;
}
void PopulateBuildVersion(const EncodingContext& context,
envoy_config_core_v3_Node* node_msg,
const std::string& build_version) {
std::string encoded_build_version = EncodeStringField(5, build_version);
// TODO(roth): This should use upb_msg_addunknown(), but that API is
// broken in the current version of upb, so we're using the internal
// API for now. Change this once we upgrade to a version of upb that
// fixes this bug.
_upb_msg_addunknown(node_msg, encoded_build_version.data(),
encoded_build_version.size(), context.arena);
}
void PopulateNode(const EncodingContext& context,
const XdsBootstrap::Node* node,
const std::string& build_version,
const std::string& user_agent_name,
envoy_config_core_v3_Node* node_msg) {
if (node != nullptr) {
if (!node->id.empty()) {
envoy_config_core_v3_Node_set_id(node_msg,
StdStringToUpbString(node->id));
}
if (!node->cluster.empty()) {
envoy_config_core_v3_Node_set_cluster(
node_msg, StdStringToUpbString(node->cluster));
}
if (!node->metadata.object_value().empty()) {
google_protobuf_Struct* metadata =
envoy_config_core_v3_Node_mutable_metadata(node_msg, context.arena);
PopulateMetadata(context, metadata, node->metadata.object_value());
}
if (!node->locality_region.empty() || !node->locality_zone.empty() ||
!node->locality_sub_zone.empty()) {
envoy_config_core_v3_Locality* locality =
envoy_config_core_v3_Node_mutable_locality(node_msg, context.arena);
if (!node->locality_region.empty()) {
envoy_config_core_v3_Locality_set_region(
locality, StdStringToUpbString(node->locality_region));
}
if (!node->locality_zone.empty()) {
envoy_config_core_v3_Locality_set_zone(
locality, StdStringToUpbString(node->locality_zone));
}
if (!node->locality_sub_zone.empty()) {
envoy_config_core_v3_Locality_set_sub_zone(
locality, StdStringToUpbString(node->locality_sub_zone));
}
}
}
if (!context.use_v3) {
PopulateBuildVersion(context, node_msg, build_version);
}
envoy_config_core_v3_Node_set_user_agent_name(
node_msg, StdStringToUpbString(user_agent_name));
envoy_config_core_v3_Node_set_user_agent_version(
node_msg, upb_strview_makez(grpc_version_string()));
envoy_config_core_v3_Node_add_client_features(
node_msg, upb_strview_makez("envoy.lb.does_not_support_overprovisioning"),
context.arena);
}
inline absl::string_view UpbStringToAbsl(const upb_strview& str) {
return absl::string_view(str.data, str.size);
}
inline std::string UpbStringToStdString(const upb_strview& str) {
return std::string(str.data, str.size);
}
void MaybeLogDiscoveryRequest(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryRequest* request) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_service_discovery_v3_DiscoveryRequest_getmsgdef(context.symtab);
char buf[10240];
upb_text_encode(request, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] constructed ADS request: %s",
context.client, buf);
}
}
grpc_slice SerializeDiscoveryRequest(
const EncodingContext& context,
envoy_service_discovery_v3_DiscoveryRequest* request) {
size_t output_length;
char* output = envoy_service_discovery_v3_DiscoveryRequest_serialize(
request, context.arena, &output_length);
return grpc_slice_from_copied_buffer(output, output_length);
}
absl::string_view TypeUrlExternalToInternal(bool use_v3,
const std::string& type_url) {
if (!use_v3) {
if (type_url == XdsApi::kLdsTypeUrl) {
return kLdsV2TypeUrl;
}
if (type_url == XdsApi::kRdsTypeUrl) {
return kRdsV2TypeUrl;
}
if (type_url == XdsApi::kCdsTypeUrl) {
return kCdsV2TypeUrl;
}
if (type_url == XdsApi::kEdsTypeUrl) {
return kEdsV2TypeUrl;
}
}
return type_url;
}
} // namespace
grpc_slice XdsApi::CreateAdsRequest(
const XdsBootstrap::XdsServer& server, const std::string& type_url,
const std::set<absl::string_view>& resource_names,
const std::string& version, const std::string& nonce,
grpc_error_handle error, bool populate_node) {
upb::Arena arena;
const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(),
server.ShouldUseV3()};
// Create a request.
envoy_service_discovery_v3_DiscoveryRequest* request =
envoy_service_discovery_v3_DiscoveryRequest_new(arena.ptr());
// Set type_url.
absl::string_view real_type_url =
TypeUrlExternalToInternal(server.ShouldUseV3(), type_url);
envoy_service_discovery_v3_DiscoveryRequest_set_type_url(
request, StdStringToUpbString(real_type_url));
// Set version_info.
if (!version.empty()) {
envoy_service_discovery_v3_DiscoveryRequest_set_version_info(
request, StdStringToUpbString(version));
}
// Set nonce.
if (!nonce.empty()) {
envoy_service_discovery_v3_DiscoveryRequest_set_response_nonce(
request, StdStringToUpbString(nonce));
}
// Set error_detail if it's a NACK.
std::string error_string_storage;
if (error != GRPC_ERROR_NONE) {
google_rpc_Status* error_detail =
envoy_service_discovery_v3_DiscoveryRequest_mutable_error_detail(
request, arena.ptr());
// Hard-code INVALID_ARGUMENT as the status code.
// TODO(roth): If at some point we decide we care about this value,
// we could attach a status code to the individual errors where we
// generate them in the parsing code, and then use that here.
google_rpc_Status_set_code(error_detail, GRPC_STATUS_INVALID_ARGUMENT);
// Error description comes from the error that was passed in.
error_string_storage = grpc_error_std_string(error);
upb_strview error_description = StdStringToUpbString(error_string_storage);
google_rpc_Status_set_message(error_detail, error_description);
GRPC_ERROR_UNREF(error);
}
// Populate node.
if (populate_node) {
envoy_config_core_v3_Node* node_msg =
envoy_service_discovery_v3_DiscoveryRequest_mutable_node(request,
arena.ptr());
PopulateNode(context, node_, build_version_, user_agent_name_, node_msg);
}
// Add resource_names.
for (const auto& resource_name : resource_names) {
envoy_service_discovery_v3_DiscoveryRequest_add_resource_names(
request, StdStringToUpbString(resource_name), arena.ptr());
}
MaybeLogDiscoveryRequest(context, request);
return SerializeDiscoveryRequest(context, request);
}
namespace {
void MaybeLogDiscoveryResponse(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryResponse* response) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_service_discovery_v3_DiscoveryResponse_getmsgdef(context.symtab);
char buf[10240];
upb_text_encode(response, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] received response: %s", context.client,
buf);
}
}
void MaybeLogHttpConnectionManager(
const EncodingContext& context,
const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager*
http_connection_manager_config) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_getmsgdef(
context.symtab);
char buf[10240];
upb_text_encode(http_connection_manager_config, msg_type, nullptr, 0, buf,
sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] HttpConnectionManager: %s",
context.client, buf);
}
}
void MaybeLogRouteConfiguration(
const EncodingContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_config_route_v3_RouteConfiguration_getmsgdef(context.symtab);
char buf[10240];
upb_text_encode(route_config, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] RouteConfiguration: %s", context.client,
buf);
}
}
void MaybeLogCluster(const EncodingContext& context,
const envoy_config_cluster_v3_Cluster* cluster) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab);
char buf[10240];
upb_text_encode(cluster, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", context.client, buf);
}
}
void MaybeLogClusterLoadAssignment(
const EncodingContext& context,
const envoy_config_endpoint_v3_ClusterLoadAssignment* cla) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_config_endpoint_v3_ClusterLoadAssignment_getmsgdef(
context.symtab);
char buf[10240];
upb_text_encode(cla, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] ClusterLoadAssignment: %s",
context.client, buf);
}
}
grpc_error_handle RoutePathMatchParse(
const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route,
bool* ignore_route) {
auto* case_sensitive_ptr =
envoy_config_route_v3_RouteMatch_case_sensitive(match);
bool case_sensitive = true;
if (case_sensitive_ptr != nullptr) {
case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr);
}
StringMatcher::Type type;
std::string match_string;
if (envoy_config_route_v3_RouteMatch_has_prefix(match)) {
absl::string_view prefix =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_prefix(match));
// Empty prefix "" is accepted.
if (!prefix.empty()) {
// Prefix "/" is accepted.
if (prefix[0] != '/') {
// Prefix which does not start with a / will never match anything, so
// ignore this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
}
std::vector<absl::string_view> prefix_elements =
absl::StrSplit(prefix.substr(1), absl::MaxSplits('/', 2));
if (prefix_elements.size() > 2) {
// Prefix cannot have more than 2 slashes.
*ignore_route = true;
return GRPC_ERROR_NONE;
} else if (prefix_elements.size() == 2 && prefix_elements[0].empty()) {
// Prefix contains empty string between the 2 slashes
*ignore_route = true;
return GRPC_ERROR_NONE;
}
}
type = StringMatcher::Type::kPrefix;
match_string = std::string(prefix);
} else if (envoy_config_route_v3_RouteMatch_has_path(match)) {
absl::string_view path =
UpbStringToAbsl(envoy_config_route_v3_RouteMatch_path(match));
if (path.empty()) {
// Path that is empty will never match anything, so ignore this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
}
if (path[0] != '/') {
// Path which does not start with a / will never match anything, so
// ignore this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
}
std::vector<absl::string_view> path_elements =
absl::StrSplit(path.substr(1), absl::MaxSplits('/', 2));
if (path_elements.size() != 2) {
// Path not in the required format of /service/method will never match
// anything, so ignore this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
} else if (path_elements[0].empty()) {
// Path contains empty service name will never match anything, so ignore
// this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
} else if (path_elements[1].empty()) {
// Path contains empty method name will never match anything, so ignore
// this route.
*ignore_route = true;
return GRPC_ERROR_NONE;
}
type = StringMatcher::Type::kExact;
match_string = std::string(path);
} else if (envoy_config_route_v3_RouteMatch_has_safe_regex(match)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_RouteMatch_safe_regex(match);
GPR_ASSERT(regex_matcher != nullptr);
type = StringMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid route path specifier specified.");
}
absl::StatusOr<StringMatcher> string_matcher =
StringMatcher::Create(type, match_string, case_sensitive);
if (!string_matcher.ok()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("path matcher: ", string_matcher.status().message())
.c_str());
;
}
route->matchers.path_matcher = std::move(string_matcher.value());
return GRPC_ERROR_NONE;
}
grpc_error_handle RouteHeaderMatchersParse(
const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route) {
size_t size;
const envoy_config_route_v3_HeaderMatcher* const* headers =
envoy_config_route_v3_RouteMatch_headers(match, &size);
for (size_t i = 0; i < size; ++i) {
const envoy_config_route_v3_HeaderMatcher* header = headers[i];
const std::string name =
UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header));
HeaderMatcher::Type type;
std::string match_string;
int64_t range_start = 0;
int64_t range_end = 0;
bool present_match = false;
if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) {
type = HeaderMatcher::Type::kExact;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_exact_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_safe_regex_match(
header)) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_config_route_v3_HeaderMatcher_safe_regex_match(header);
GPR_ASSERT(regex_matcher != nullptr);
type = HeaderMatcher::Type::kSafeRegex;
match_string = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else if (envoy_config_route_v3_HeaderMatcher_has_range_match(header)) {
type = HeaderMatcher::Type::kRange;
const envoy_type_v3_Int64Range* range_matcher =
envoy_config_route_v3_HeaderMatcher_range_match(header);
range_start = envoy_type_v3_Int64Range_start(range_matcher);
range_end = envoy_type_v3_Int64Range_end(range_matcher);
} else if (envoy_config_route_v3_HeaderMatcher_has_present_match(header)) {
type = HeaderMatcher::Type::kPresent;
present_match = envoy_config_route_v3_HeaderMatcher_present_match(header);
} else if (envoy_config_route_v3_HeaderMatcher_has_prefix_match(header)) {
type = HeaderMatcher::Type::kPrefix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_prefix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_suffix_match(header)) {
type = HeaderMatcher::Type::kSuffix;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_suffix_match(header));
} else if (envoy_config_route_v3_HeaderMatcher_has_contains_match(header)) {
type = HeaderMatcher::Type::kContains;
match_string = UpbStringToStdString(
envoy_config_route_v3_HeaderMatcher_contains_match(header));
} else {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid route header matcher specified.");
}
bool invert_match =
envoy_config_route_v3_HeaderMatcher_invert_match(header);
absl::StatusOr<HeaderMatcher> header_matcher =
HeaderMatcher::Create(name, type, match_string, range_start, range_end,
present_match, invert_match);
if (!header_matcher.ok()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("header matcher: ", header_matcher.status().message())
.c_str());
}
route->matchers.header_matchers.emplace_back(
std::move(header_matcher.value()));
}
return GRPC_ERROR_NONE;
}
grpc_error_handle RouteRuntimeFractionParse(
const envoy_config_route_v3_RouteMatch* match, XdsApi::Route* route) {
const envoy_config_core_v3_RuntimeFractionalPercent* runtime_fraction =
envoy_config_route_v3_RouteMatch_runtime_fraction(match);
if (runtime_fraction != nullptr) {
const envoy_type_v3_FractionalPercent* fraction =
envoy_config_core_v3_RuntimeFractionalPercent_default_value(
runtime_fraction);
if (fraction != nullptr) {
uint32_t numerator = envoy_type_v3_FractionalPercent_numerator(fraction);
const auto denominator =
static_cast<envoy_type_v3_FractionalPercent_DenominatorType>(
envoy_type_v3_FractionalPercent_denominator(fraction));
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default:
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unknown denominator type");
}
route->matchers.fraction_per_million = numerator;
}
}
return GRPC_ERROR_NONE;
}
grpc_error_handle ExtractHttpFilterTypeName(const EncodingContext& context,
const google_protobuf_Any* any,
absl::string_view* filter_type) {
*filter_type = UpbStringToAbsl(google_protobuf_Any_type_url(any));
if (*filter_type == "type.googleapis.com/udpa.type.v1.TypedStruct") {
upb_strview any_value = google_protobuf_Any_value(any);
const auto* typed_struct = udpa_type_v1_TypedStruct_parse(
any_value.data, any_value.size, context.arena);
if (typed_struct == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"could not parse TypedStruct from filter config");
}
*filter_type =
UpbStringToAbsl(udpa_type_v1_TypedStruct_type_url(typed_struct));
}
*filter_type = absl::StripPrefix(*filter_type, "type.googleapis.com/");
return GRPC_ERROR_NONE;
}
template <typename ParentType, typename EntryType>
grpc_error_handle ParseTypedPerFilterConfig(
const EncodingContext& context, const ParentType* parent,
const EntryType* (*entry_func)(const ParentType*, size_t*),
upb_strview (*key_func)(const EntryType*),
const google_protobuf_Any* (*value_func)(const EntryType*),
XdsApi::TypedPerFilterConfig* typed_per_filter_config) {
size_t filter_it = UPB_MAP_BEGIN;
while (true) {
const auto* filter_entry = entry_func(parent, &filter_it);
if (filter_entry == nullptr) break;
absl::string_view key = UpbStringToAbsl(key_func(filter_entry));
if (key.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("empty filter name in map");
}
const google_protobuf_Any* any = value_func(filter_entry);
GPR_ASSERT(any != nullptr);
absl::string_view filter_type =
UpbStringToAbsl(google_protobuf_Any_type_url(any));
if (filter_type.empty()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("no filter config specified for filter name ", key)
.c_str());
}
bool is_optional = false;
if (filter_type ==
"type.googleapis.com/envoy.config.route.v3.FilterConfig") {
upb_strview any_value = google_protobuf_Any_value(any);
const auto* filter_config = envoy_config_route_v3_FilterConfig_parse(
any_value.data, any_value.size, context.arena);
if (filter_config == nullptr) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("could not parse FilterConfig wrapper for ", key)
.c_str());
}
is_optional =
envoy_config_route_v3_FilterConfig_is_optional(filter_config);
any = envoy_config_route_v3_FilterConfig_config(filter_config);
if (any == nullptr) {
if (is_optional) continue;
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("no filter config specified for filter name ", key)
.c_str());
}
}
grpc_error_handle error =
ExtractHttpFilterTypeName(context, any, &filter_type);
if (error != GRPC_ERROR_NONE) return error;
const XdsHttpFilterImpl* filter_impl =
XdsHttpFilterRegistry::GetFilterForType(filter_type);
if (filter_impl == nullptr) {
if (is_optional) continue;
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("no filter registered for config type ", filter_type)
.c_str());
}
absl::StatusOr<XdsHttpFilterImpl::FilterConfig> filter_config =
filter_impl->GenerateFilterConfigOverride(
google_protobuf_Any_value(any), context.arena);
if (!filter_config.ok()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("filter config for type ", filter_type,
" failed to parse: ", filter_config.status().ToString())
.c_str());
}
(*typed_per_filter_config)[std::string(key)] = std::move(*filter_config);
}
return GRPC_ERROR_NONE;
}
grpc_error_handle RouteActionParse(const EncodingContext& context,
const envoy_config_route_v3_Route* route_msg,
XdsApi::Route* route, bool* ignore_route) {
if (!envoy_config_route_v3_Route_has_route(route_msg)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No RouteAction found in route.");
}
const envoy_config_route_v3_RouteAction* route_action =
envoy_config_route_v3_Route_route(route_msg);
// Get the cluster or weighted_clusters in the RouteAction.
if (envoy_config_route_v3_RouteAction_has_cluster(route_action)) {
route->cluster_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_cluster(route_action));
if (route->cluster_name.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction cluster contains empty cluster name.");
}
} else if (envoy_config_route_v3_RouteAction_has_weighted_clusters(
route_action)) {
const envoy_config_route_v3_WeightedCluster* weighted_cluster =
envoy_config_route_v3_RouteAction_weighted_clusters(route_action);
uint32_t total_weight = 100;
const google_protobuf_UInt32Value* weight =
envoy_config_route_v3_WeightedCluster_total_weight(weighted_cluster);
if (weight != nullptr) {
total_weight = google_protobuf_UInt32Value_value(weight);
}
size_t clusters_size;
const envoy_config_route_v3_WeightedCluster_ClusterWeight* const* clusters =
envoy_config_route_v3_WeightedCluster_clusters(weighted_cluster,
&clusters_size);
uint32_t sum_of_weights = 0;
for (size_t j = 0; j < clusters_size; ++j) {
const envoy_config_route_v3_WeightedCluster_ClusterWeight*
cluster_weight = clusters[j];
XdsApi::Route::ClusterWeight cluster;
cluster.name = UpbStringToStdString(
envoy_config_route_v3_WeightedCluster_ClusterWeight_name(
cluster_weight));
if (cluster.name.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction weighted_cluster cluster contains empty cluster "
"name.");
}
const google_protobuf_UInt32Value* weight =
envoy_config_route_v3_WeightedCluster_ClusterWeight_weight(
cluster_weight);
if (weight == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction weighted_cluster cluster missing weight");
}
cluster.weight = google_protobuf_UInt32Value_value(weight);
if (cluster.weight == 0) continue;
sum_of_weights += cluster.weight;
if (context.use_v3) {
grpc_error_handle error = ParseTypedPerFilterConfig<
envoy_config_route_v3_WeightedCluster_ClusterWeight,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry>(
context, cluster_weight,
envoy_config_route_v3_WeightedCluster_ClusterWeight_typed_per_filter_config_next,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_WeightedCluster_ClusterWeight_TypedPerFilterConfigEntry_value,
&cluster.typed_per_filter_config);
if (error != GRPC_ERROR_NONE) return error;
}
route->weighted_clusters.emplace_back(std::move(cluster));
}
if (total_weight != sum_of_weights) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction weighted_cluster has incorrect total weight");
}
if (route->weighted_clusters.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction weighted_cluster has no valid clusters specified.");
}
} else {
// No cluster or weighted_clusters found in RouteAction, ignore this route.
*ignore_route = true;
}
if (!*ignore_route) {
const envoy_config_route_v3_RouteAction_MaxStreamDuration*
max_stream_duration =
envoy_config_route_v3_RouteAction_max_stream_duration(route_action);
if (max_stream_duration != nullptr) {
const google_protobuf_Duration* duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_grpc_timeout_header_max(
max_stream_duration);
if (duration == nullptr) {
duration =
envoy_config_route_v3_RouteAction_MaxStreamDuration_max_stream_duration(
max_stream_duration);
}
if (duration != nullptr) {
XdsApi::Duration duration_in_route;
duration_in_route.seconds = google_protobuf_Duration_seconds(duration);
duration_in_route.nanos = google_protobuf_Duration_nanos(duration);
route->max_stream_duration = duration_in_route;
}
}
}
// Get HashPolicy from RouteAction
if (XdsRingHashEnabled()) {
size_t size = 0;
const envoy_config_route_v3_RouteAction_HashPolicy* const* hash_policies =
envoy_config_route_v3_RouteAction_hash_policy(route_action, &size);
for (size_t i = 0; i < size; ++i) {
const envoy_config_route_v3_RouteAction_HashPolicy* hash_policy =
hash_policies[i];
XdsApi::Route::HashPolicy policy;
policy.terminal =
envoy_config_route_v3_RouteAction_HashPolicy_terminal(hash_policy);
const envoy_config_route_v3_RouteAction_HashPolicy_Header* header;
const envoy_config_route_v3_RouteAction_HashPolicy_FilterState*
filter_state;
if ((header = envoy_config_route_v3_RouteAction_HashPolicy_header(
hash_policy)) != nullptr) {
policy.type = XdsApi::Route::HashPolicy::Type::HEADER;
policy.header_name = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_Header_header_name(
header));
const struct envoy_type_matcher_v3_RegexMatchAndSubstitute*
regex_rewrite =
envoy_config_route_v3_RouteAction_HashPolicy_Header_regex_rewrite(
header);
if (regex_rewrite != nullptr) {
const envoy_type_matcher_v3_RegexMatcher* regex_matcher =
envoy_type_matcher_v3_RegexMatchAndSubstitute_pattern(
regex_rewrite);
if (regex_matcher == nullptr) {
gpr_log(
GPR_DEBUG,
"RouteAction HashPolicy contains policy specifier Header with "
"RegexMatchAndSubstitution but RegexMatcher pattern is "
"missing");
continue;
}
RE2::Options options;
policy.regex = absl::make_unique<RE2>(
UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher)),
options);
if (!policy.regex->ok()) {
gpr_log(
GPR_DEBUG,
"RouteAction HashPolicy contains policy specifier Header with "
"RegexMatchAndSubstitution but RegexMatcher pattern does not "
"compile");
continue;
}
policy.regex_substitution = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatchAndSubstitute_substitution(
regex_rewrite));
}
} else if ((filter_state =
envoy_config_route_v3_RouteAction_HashPolicy_filter_state(
hash_policy)) != nullptr) {
std::string key = UpbStringToStdString(
envoy_config_route_v3_RouteAction_HashPolicy_FilterState_key(
filter_state));
if (key == "io.grpc.channel_id") {
policy.type = XdsApi::Route::HashPolicy::Type::CHANNEL_ID;
} else {
gpr_log(GPR_DEBUG,
"RouteAction HashPolicy contains policy specifier "
"FilterState but "
"key is not io.grpc.channel_id.");
continue;
}
} else {
gpr_log(
GPR_DEBUG,
"RouteAction HashPolicy contains unsupported policy specifier.");
continue;
}
route->hash_policies.emplace_back(std::move(policy));
}
}
return GRPC_ERROR_NONE;
}
grpc_error_handle RouteConfigParse(
const EncodingContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config,
XdsApi::RdsUpdate* rds_update) {
MaybeLogRouteConfiguration(context, route_config);
// Get the virtual hosts.
size_t num_virtual_hosts;
const envoy_config_route_v3_VirtualHost* const* virtual_hosts =
envoy_config_route_v3_RouteConfiguration_virtual_hosts(
route_config, &num_virtual_hosts);
for (size_t i = 0; i < num_virtual_hosts; ++i) {
rds_update->virtual_hosts.emplace_back();
XdsApi::RdsUpdate::VirtualHost& vhost = rds_update->virtual_hosts.back();
// Parse domains.
size_t domain_size;
upb_strview const* domains = envoy_config_route_v3_VirtualHost_domains(
virtual_hosts[i], &domain_size);
for (size_t j = 0; j < domain_size; ++j) {
std::string domain_pattern = UpbStringToStdString(domains[j]);
const MatchType match_type = DomainPatternMatchType(domain_pattern);
if (match_type == INVALID_MATCH) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("Invalid domain pattern \"", domain_pattern, "\".")
.c_str());
}
vhost.domains.emplace_back(std::move(domain_pattern));
}
if (vhost.domains.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("VirtualHost has no domains");
}
// Parse typed_per_filter_config.
if (context.use_v3) {
grpc_error_handle error = ParseTypedPerFilterConfig<
envoy_config_route_v3_VirtualHost,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry>(
context, virtual_hosts[i],
envoy_config_route_v3_VirtualHost_typed_per_filter_config_next,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_VirtualHost_TypedPerFilterConfigEntry_value,
&vhost.typed_per_filter_config);
if (error != GRPC_ERROR_NONE) return error;
}
// Parse routes.
size_t num_routes;
const envoy_config_route_v3_Route* const* routes =
envoy_config_route_v3_VirtualHost_routes(virtual_hosts[i], &num_routes);
if (num_routes < 1) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No route found in the virtual host.");
}
// Loop over the whole list of routes
for (size_t j = 0; j < num_routes; ++j) {
const envoy_config_route_v3_RouteMatch* match =
envoy_config_route_v3_Route_match(routes[j]);
if (match == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Match can't be null.");
}
size_t query_parameters_size;
static_cast<void>(envoy_config_route_v3_RouteMatch_query_parameters(
match, &query_parameters_size));
if (query_parameters_size > 0) {
continue;
}
XdsApi::Route route;
bool ignore_route = false;
grpc_error_handle error =
RoutePathMatchParse(match, &route, &ignore_route);
if (error != GRPC_ERROR_NONE) return error;
if (ignore_route) continue;
error = RouteHeaderMatchersParse(match, &route);
if (error != GRPC_ERROR_NONE) return error;
error = RouteRuntimeFractionParse(match, &route);
if (error != GRPC_ERROR_NONE) return error;
error = RouteActionParse(context, routes[j], &route, &ignore_route);
if (error != GRPC_ERROR_NONE) return error;
if (ignore_route) continue;
if (context.use_v3) {
grpc_error_handle error = ParseTypedPerFilterConfig<
envoy_config_route_v3_Route,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry>(
context, routes[j],
envoy_config_route_v3_Route_typed_per_filter_config_next,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_key,
envoy_config_route_v3_Route_TypedPerFilterConfigEntry_value,
&route.typed_per_filter_config);
if (error != GRPC_ERROR_NONE) return error;
}
vhost.routes.emplace_back(std::move(route));
}
if (vhost.routes.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("No valid routes specified.");
}
}
return GRPC_ERROR_NONE;
}
XdsApi::CommonTlsContext::CertificateProviderInstance
CertificateProviderInstanceParse(
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance*
certificate_provider_instance_proto) {
return {
UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_instance_name(
certificate_provider_instance_proto)),
UpbStringToStdString(
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CertificateProviderInstance_certificate_name(
certificate_provider_instance_proto))};
}
grpc_error_handle CommonTlsContextParse(
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
XdsApi::CommonTlsContext* common_tls_context) GRPC_MUST_USE_RESULT;
grpc_error_handle CommonTlsContextParse(
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*
common_tls_context_proto,
XdsApi::CommonTlsContext* common_tls_context) {
auto* combined_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_combined_validation_context(
common_tls_context_proto);
if (combined_validation_context != nullptr) {
auto* default_validation_context =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_default_validation_context(
combined_validation_context);
if (default_validation_context != nullptr) {
size_t len = 0;
auto* subject_alt_names_matchers =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_match_subject_alt_names(
default_validation_context, &len);
for (size_t i = 0; i < len; ++i) {
StringMatcher::Type type;
std::string matcher;
if (envoy_type_matcher_v3_StringMatcher_has_exact(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kExact;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_exact(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_prefix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kPrefix;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_prefix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_suffix(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSuffix;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_suffix(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_contains(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kContains;
matcher =
UpbStringToStdString(envoy_type_matcher_v3_StringMatcher_contains(
subject_alt_names_matchers[i]));
} else if (envoy_type_matcher_v3_StringMatcher_has_safe_regex(
subject_alt_names_matchers[i])) {
type = StringMatcher::Type::kSafeRegex;
auto* regex_matcher = envoy_type_matcher_v3_StringMatcher_safe_regex(
subject_alt_names_matchers[i]);
matcher = UpbStringToStdString(
envoy_type_matcher_v3_RegexMatcher_regex(regex_matcher));
} else {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid StringMatcher specified");
}
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()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("string matcher: ",
string_matcher.status().message())
.c_str());
}
if (type == StringMatcher::Type::kSafeRegex && ignore_case) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"StringMatcher: ignore_case has no effect for SAFE_REGEX.");
}
common_tls_context->combined_validation_context
.default_validation_context.match_subject_alt_names.push_back(
std::move(string_matcher.value()));
}
}
auto* validation_context_certificate_provider_instance =
envoy_extensions_transport_sockets_tls_v3_CommonTlsContext_CombinedCertificateValidationContext_validation_context_certificate_provider_instance(
combined_validation_context);
if (validation_context_certificate_provider_instance != nullptr) {
common_tls_context->combined_validation_context
.validation_context_certificate_provider_instance =
CertificateProviderInstanceParse(
validation_context_certificate_provider_instance);
}
}
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) {
common_tls_context->tls_certificate_certificate_provider_instance =
CertificateProviderInstanceParse(
tls_certificate_certificate_provider_instance);
}
return GRPC_ERROR_NONE;
}
grpc_error_handle HttpConnectionManagerParse(
bool is_client, const EncodingContext& context,
const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager*
http_connection_manager_proto,
bool is_v2,
XdsApi::LdsUpdate::HttpConnectionManager* http_connection_manager) {
MaybeLogHttpConnectionManager(context, http_connection_manager_proto);
// Obtain max_stream_duration from 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) {
const google_protobuf_Duration* duration =
envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options);
if (duration != nullptr) {
http_connection_manager->http_max_stream_duration.seconds =
google_protobuf_Duration_seconds(duration);
http_connection_manager->http_max_stream_duration.nanos =
google_protobuf_Duration_nanos(duration);
}
}
// Parse filters.
if (!is_v2) {
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;
for (size_t i = 0; i < num_filters; ++i) {
const auto* http_filter = http_filters[i];
absl::string_view name = UpbStringToAbsl(
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name(
http_filter));
if (name.empty()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("empty filter name at index ", i).c_str());
}
if (names_seen.find(name) != names_seen.end()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("duplicate HTTP filter name: ", name).c_str());
}
names_seen.insert(name);
const bool is_optional =
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional(
http_filter);
const google_protobuf_Any* any =
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config(
http_filter);
if (any == nullptr) {
if (is_optional) continue;
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("no filter config specified for filter name ", name)
.c_str());
}
absl::string_view filter_type;
grpc_error_handle error =
ExtractHttpFilterTypeName(context, any, &filter_type);
if (error != GRPC_ERROR_NONE) return error;
const XdsHttpFilterImpl* filter_impl =
XdsHttpFilterRegistry::GetFilterForType(filter_type);
if (filter_impl == nullptr) {
if (is_optional) continue;
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("no filter registered for config type ", filter_type)
.c_str());
}
if ((is_client && !filter_impl->IsSupportedOnClients()) ||
(!is_client && !filter_impl->IsSupportedOnServers())) {
if (is_optional) continue;
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrFormat("Filter %s is not supported on %s", filter_type,
is_client ? "clients" : "servers")
.c_str());
}
absl::StatusOr<XdsHttpFilterImpl::FilterConfig> filter_config =
filter_impl->GenerateFilterConfig(google_protobuf_Any_value(any),
context.arena);
if (!filter_config.ok()) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(
"filter config for type ", filter_type,
" failed to parse: ", filter_config.status().ToString())
.c_str());
}
http_connection_manager->http_filters.emplace_back(
XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter{
std::string(name), std::move(*filter_config)});
}
} else {
// If using a v2 config, we just hard-code a list containing only the
// router filter without actually looking at the config. This ensures
// that the right thing happens in the xds resolver without having
// to expose whether the resource we received was v2 or v3.
http_connection_manager->http_filters.emplace_back(
XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter{
"router", {kXdsHttpRouterFilterConfigName, Json()}});
}
if (is_client) {
// 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);
XdsApi::RdsUpdate rds_update;
grpc_error_handle error =
RouteConfigParse(context, route_config, &rds_update);
if (error != GRPC_ERROR_NONE) return error;
http_connection_manager->rds_update = std::move(rds_update);
return GRPC_ERROR_NONE;
}
// 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) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"HttpConnectionManager neither has inlined route_config nor 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);
if (config_source == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"HttpConnectionManager missing config_source for RDS.");
}
if (!envoy_config_core_v3_ConfigSource_has_ads(config_source)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"HttpConnectionManager ConfigSource for RDS does not specify ADS.");
}
// Get the route_config_name.
http_connection_manager->route_config_name = UpbStringToStdString(
envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name(
rds));
}
return GRPC_ERROR_NONE;
}
grpc_error_handle LdsResponseParseClient(
const EncodingContext& context,
const envoy_config_listener_v3_ApiListener* api_listener, bool is_v2,
XdsApi::LdsUpdate* lds_update) {
lds_update->type = XdsApi::LdsUpdate::ListenerType::kHttpApiListener;
const upb_strview encoded_api_listener = google_protobuf_Any_value(
envoy_config_listener_v3_ApiListener_api_listener(api_listener));
const auto* http_connection_manager =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse(
encoded_api_listener.data, encoded_api_listener.size, context.arena);
if (http_connection_manager == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Could not parse HttpConnectionManager config from ApiListener");
}
return HttpConnectionManagerParse(true /* is_client */, context,
http_connection_manager, is_v2,
&lds_update->http_connection_manager);
}
grpc_error_handle DownstreamTlsContextParse(
const EncodingContext& context,
const envoy_config_core_v3_TransportSocket* transport_socket,
XdsApi::DownstreamTlsContext* downstream_tls_context) {
absl::string_view name = UpbStringToAbsl(
envoy_config_core_v3_TransportSocket_name(transport_socket));
if (name == "envoy.transport_sockets.tls") {
auto* typed_config =
envoy_config_core_v3_TransportSocket_typed_config(transport_socket);
if (typed_config != nullptr) {
const upb_strview encoded_downstream_tls_context =
google_protobuf_Any_value(typed_config);
auto* downstream_tls_context_proto =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse(
encoded_downstream_tls_context.data,
encoded_downstream_tls_context.size, context.arena);
if (downstream_tls_context_proto == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Can't decode 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) {
grpc_error_handle error = CommonTlsContextParse(
common_tls_context, &downstream_tls_context->common_tls_context);
if (error != GRPC_ERROR_NONE) return error;
}
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->common_tls_context
.tls_certificate_certificate_provider_instance.instance_name
.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"TLS configuration provided but no "
"tls_certificate_certificate_provider_instance found.");
}
}
return GRPC_ERROR_NONE;
}
grpc_error_handle CidrRangeParse(
const envoy_config_core_v3_CidrRange* cidr_range_proto,
XdsApi::LdsUpdate::FilterChainMap::CidrRange* cidr_range) {
std::string address_prefix = UpbStringToStdString(
envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto));
grpc_error_handle error =
grpc_string_to_sockaddr(&cidr_range->address, address_prefix.c_str(), 0);
if (error != GRPC_ERROR_NONE) return error;
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 GRPC_ERROR_NONE;
}
grpc_error_handle FilterChainMatchParse(
const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto,
FilterChain::FilterChainMatch* filter_chain_match) {
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);
}
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++) {
XdsApi::LdsUpdate::FilterChainMap::CidrRange cidr_range;
grpc_error_handle error = CidrRangeParse(prefix_ranges[i], &cidr_range);
if (error != GRPC_ERROR_NONE) return error;
filter_chain_match->prefix_ranges.push_back(cidr_range);
}
filter_chain_match->source_type =
static_cast<XdsApi::LdsUpdate::FilterChainMap::ConnectionSourceType>(
envoy_config_listener_v3_FilterChainMatch_source_type(
filter_chain_match_proto));
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++) {
XdsApi::LdsUpdate::FilterChainMap::CidrRange cidr_range;
grpc_error_handle error =
CidrRangeParse(source_prefix_ranges[i], &cidr_range);
if (error != GRPC_ERROR_NONE) return error;
filter_chain_match->source_prefix_ranges.push_back(cidr_range);
}
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]);
}
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]));
}
filter_chain_match->transport_protocol = UpbStringToStdString(
envoy_config_listener_v3_FilterChainMatch_transport_protocol(
filter_chain_match_proto));
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 GRPC_ERROR_NONE;
}
grpc_error_handle FilterChainParse(
const EncodingContext& context,
const envoy_config_listener_v3_FilterChain* filter_chain_proto, bool is_v2,
FilterChain* filter_chain) {
grpc_error_handle error = GRPC_ERROR_NONE;
auto* filter_chain_match =
envoy_config_listener_v3_FilterChain_filter_chain_match(
filter_chain_proto);
if (filter_chain_match != nullptr) {
error = FilterChainMatchParse(filter_chain_match,
&filter_chain->filter_chain_match);
if (error != GRPC_ERROR_NONE) return error;
}
// Parse the filters list. Currently we only support HttpConnectionManager.
size_t size = 0;
auto* filters =
envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size);
if (size != 1) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"FilterChain should have exactly one filter: HttpConnectionManager; no "
"other filter is supported at the moment");
}
auto* typed_config = envoy_config_listener_v3_Filter_typed_config(filters[0]);
if (typed_config == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No typed_config found in filter.");
}
absl::string_view type_url =
UpbStringToAbsl(google_protobuf_Any_type_url(typed_config));
if (type_url !=
"type.googleapis.com/"
"envoy.extensions.filters.network.http_connection_manager.v3."
"HttpConnectionManager") {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("Unsupported filter type ", type_url).c_str());
}
const upb_strview encoded_http_connection_manager =
google_protobuf_Any_value(typed_config);
const auto* http_connection_manager =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse(
encoded_http_connection_manager.data,
encoded_http_connection_manager.size, context.arena);
if (http_connection_manager == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Could not parse HttpConnectionManager config from filter "
"typed_config");
}
filter_chain->filter_chain_data =
std::make_shared<XdsApi::LdsUpdate::FilterChainData>();
error = HttpConnectionManagerParse(
false /* is_client */, context, http_connection_manager, is_v2,
&filter_chain->filter_chain_data->http_connection_manager);
if (error != GRPC_ERROR_NONE) return error;
// Get the DownstreamTlsContext for the filter chain
if (XdsSecurityEnabled()) {
auto* transport_socket =
envoy_config_listener_v3_FilterChain_transport_socket(
filter_chain_proto);
if (transport_socket != nullptr) {
error = DownstreamTlsContextParse(
context, transport_socket,
&filter_chain->filter_chain_data->downstream_tls_context);
}
}
return error;
}
grpc_error_handle AddressParse(
const envoy_config_core_v3_Address* address_proto, std::string* address) {
const auto* socket_address =
envoy_config_core_v3_Address_socket_address(address_proto);
if (socket_address == nullptr) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
"Address does not have socket_address");
}
if (envoy_config_core_v3_SocketAddress_protocol(socket_address) !=
envoy_config_core_v3_SocketAddress_TCP) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"SocketAddress protocol is not TCP");
}
uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address);
if (port > 65535) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid port");
}
*address = JoinHostPort(
UpbStringToAbsl(
envoy_config_core_v3_SocketAddress_address(socket_address)),
port);
return GRPC_ERROR_NONE;
}
// 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
// XdsApi::LdsUpdate::FilterChainMap
struct InternalFilterChainMap {
using SourceIpMap =
std::map<std::string, XdsApi::LdsUpdate::FilterChainMap::SourceIp>;
using ConnectionSourceTypesArray = std::array<SourceIpMap, 3>;
struct DestinationIp {
absl::optional<XdsApi::LdsUpdate::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;
};
grpc_error_handle AddFilterChainDataForSourcePort(
const FilterChain& filter_chain,
XdsApi::LdsUpdate::FilterChainMap::SourcePortsMap* ports_map,
uint32_t port) {
auto insert_result = ports_map->emplace(
port, XdsApi::LdsUpdate::FilterChainMap::FilterChainDataSharedPtr{
filter_chain.filter_chain_data});
if (!insert_result.second) {
return GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(
"Duplicate matching rules detected when adding filter chain: ",
filter_chain.filter_chain_match.ToString())
.c_str());
}
return GRPC_ERROR_NONE;
}
grpc_error_handle AddFilterChainDataForSourcePorts(
const FilterChain& filter_chain,
XdsApi::LdsUpdate::FilterChainMap::SourcePortsMap* ports_map) {
if (filter_chain.filter_chain_match.source_ports.empty()) {
return AddFilterChainDataForSourcePort(filter_chain, ports_map, 0);
} else {
for (uint32_t port : filter_chain.filter_chain_match.source_ports) {
grpc_error_handle error =
AddFilterChainDataForSourcePort(filter_chain, ports_map, port);
if (error != GRPC_ERROR_NONE) return error;
}
}
return GRPC_ERROR_NONE;
}
grpc_error_handle AddFilterChainDataForSourceIpRange(
const FilterChain& filter_chain,
InternalFilterChainMap::SourceIpMap* source_ip_map) {
if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) {
auto insert_result = source_ip_map->emplace(
"", XdsApi::LdsUpdate::FilterChainMap::SourceIp());
return AddFilterChainDataForSourcePorts(
filter_chain, &insert_result.first->second.ports_map);
} else {
for (const auto& prefix_range :
filter_chain.filter_chain_match.source_prefix_ranges) {
auto insert_result = source_ip_map->emplace(
absl::StrCat(grpc_sockaddr_to_string(&prefix_range.address, false),
"/", prefix_range.prefix_len),
XdsApi::LdsUpdate::FilterChainMap::SourceIp());
if (insert_result.second) {
insert_result.first->second.prefix_range.emplace(prefix_range);
}
grpc_error_handle error = AddFilterChainDataForSourcePorts(
filter_chain, &insert_result.first->second.ports_map);
if (error != GRPC_ERROR_NONE) return error;
}
}
return GRPC_ERROR_NONE;
}
grpc_error_handle AddFilterChainDataForSourceType(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip) {
GPR_ASSERT(static_cast<unsigned int>(
filter_chain.filter_chain_match.source_type) < 3);
return AddFilterChainDataForSourceIpRange(
filter_chain, &destination_ip->source_types_array[static_cast<int>(
filter_chain.filter_chain_match.source_type)]);
}
grpc_error_handle AddFilterChainDataForApplicationProtocols(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip) {
// Only allow filter chains that do not mention application protocols
if (!filter_chain.filter_chain_match.application_protocols.empty()) {
return GRPC_ERROR_NONE;
}
return AddFilterChainDataForSourceType(filter_chain, destination_ip);
}
grpc_error_handle AddFilterChainDataForTransportProtocol(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip) {
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 GRPC_ERROR_NONE;
}
// 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 GRPC_ERROR_NONE;
}
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();
}
return AddFilterChainDataForApplicationProtocols(filter_chain,
destination_ip);
}
grpc_error_handle AddFilterChainDataForServerNames(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIp* destination_ip) {
// Don't continue adding filter chains with server names mentioned
if (!filter_chain.filter_chain_match.server_names.empty()) {
return GRPC_ERROR_NONE;
}
return AddFilterChainDataForTransportProtocol(filter_chain, destination_ip);
}
grpc_error_handle AddFilterChainDataForDestinationIpRange(
const FilterChain& filter_chain,
InternalFilterChainMap::DestinationIpMap* destination_ip_map) {
if (filter_chain.filter_chain_match.prefix_ranges.empty()) {
auto insert_result = destination_ip_map->emplace(
"", InternalFilterChainMap::DestinationIp());
return AddFilterChainDataForServerNames(filter_chain,
&insert_result.first->second);
} else {
for (const auto& prefix_range :
filter_chain.filter_chain_match.prefix_ranges) {
auto insert_result = destination_ip_map->emplace(
absl::StrCat(grpc_sockaddr_to_string(&prefix_range.address, false),
"/", prefix_range.prefix_len),
InternalFilterChainMap::DestinationIp());
if (insert_result.second) {
insert_result.first->second.prefix_range.emplace(prefix_range);
}
grpc_error_handle error = AddFilterChainDataForServerNames(
filter_chain, &insert_result.first->second);
if (error != GRPC_ERROR_NONE) return error;
}
}
return GRPC_ERROR_NONE;
}
XdsApi::LdsUpdate::FilterChainMap BuildFromInternalFilterChainMap(
InternalFilterChainMap* internal_filter_chain_map) {
XdsApi::LdsUpdate::FilterChainMap filter_chain_map;
for (auto& destination_ip_pair :
internal_filter_chain_map->destination_ip_map) {
XdsApi::LdsUpdate::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;
}
grpc_error_handle BuildFilterChainMap(
const std::vector<FilterChain>& filter_chains,
XdsApi::LdsUpdate::FilterChainMap* filter_chain_map) {
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;
grpc_error_handle error = AddFilterChainDataForDestinationIpRange(
filter_chain, &internal_filter_chain_map.destination_ip_map);
if (error != GRPC_ERROR_NONE) return error;
}
*filter_chain_map =
BuildFromInternalFilterChainMap(&internal_filter_chain_map);
return GRPC_ERROR_NONE;
}
grpc_error_handle LdsResponseParseServer(
const EncodingContext& context,
const envoy_config_listener_v3_Listener* listener, bool is_v2,
XdsApi::LdsUpdate* lds_update) {
lds_update->type = XdsApi::LdsUpdate::ListenerType::kTcpListener;
grpc_error_handle error =
AddressParse(envoy_config_listener_v3_Listener_address(listener),
&lds_update->address);
if (error != GRPC_ERROR_NONE) return error;
const auto* use_original_dst =
envoy_config_listener_v3_Listener_use_original_dst(listener);
if (use_original_dst != nullptr) {
if (google_protobuf_BoolValue_value(use_original_dst)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Field \'use_original_dst\' is not supported.");
}
}
size_t size = 0;
auto* filter_chains =
envoy_config_listener_v3_Listener_filter_chains(listener, &size);
std::vector<FilterChain> parsed_filter_chains;
parsed_filter_chains.reserve(size);
for (size_t i = 0; i < size; i++) {
FilterChain filter_chain;
error = FilterChainParse(context, filter_chains[i], is_v2, &filter_chain);
if (error != GRPC_ERROR_NONE) return error;
parsed_filter_chains.push_back(std::move(filter_chain));
}
error =
BuildFilterChainMap(parsed_filter_chains, &lds_update->filter_chain_map);
if (error != GRPC_ERROR_NONE) return error;
auto* default_filter_chain =
envoy_config_listener_v3_Listener_default_filter_chain(listener);
if (default_filter_chain != nullptr) {
FilterChain filter_chain;
error =
FilterChainParse(context, default_filter_chain, is_v2, &filter_chain);
if (error != GRPC_ERROR_NONE) return error;
if (filter_chain.filter_chain_data != nullptr) {
lds_update->default_filter_chain =
std::move(*filter_chain.filter_chain_data);
}
}
if (size == 0 && default_filter_chain == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("No filter chain provided.");
}
return GRPC_ERROR_NONE;
}
grpc_error_handle LdsResponseParse(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryResponse* response,
const std::set<absl::string_view>& expected_listener_names,
XdsApi::LdsUpdateMap* lds_update_map,
std::set<std::string>* resource_names_failed) {
std::vector<grpc_error_handle> errors;
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
absl::string_view type_url =
UpbStringToAbsl(google_protobuf_Any_type_url(resources[i]));
bool is_v2 = false;
if (!IsLds(type_url, &is_v2)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Resource is not LDS.")
.c_str()));
continue;
}
// Decode the listener.
const upb_strview encoded_listener =
google_protobuf_Any_value(resources[i]);
const envoy_config_listener_v3_Listener* listener =
envoy_config_listener_v3_Listener_parse(
encoded_listener.data, encoded_listener.size, context.arena);
if (listener == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Can't decode listener.")
.c_str()));
continue;
}
// Check listener name. Ignore unexpected listeners.
std::string listener_name =
UpbStringToStdString(envoy_config_listener_v3_Listener_name(listener));
if (expected_listener_names.find(listener_name) ==
expected_listener_names.end()) {
continue;
}
// Fail if listener name is duplicated.
if (lds_update_map->find(listener_name) != lds_update_map->end()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("duplicate listener name \"", listener_name, "\"")
.c_str()));
resource_names_failed->insert(listener_name);
continue;
}
// Serialize into JSON and store it in the LdsUpdateMap
XdsApi::LdsResourceData& lds_resource_data =
(*lds_update_map)[listener_name];
XdsApi::LdsUpdate& lds_update = lds_resource_data.resource;
lds_resource_data.serialized_proto = UpbStringToStdString(encoded_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);
if (api_listener != nullptr && address != nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(listener_name,
": Listener has both address and ApiListener")
.c_str()));
resource_names_failed->insert(listener_name);
continue;
}
if (api_listener == nullptr && address == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(listener_name,
": Listener has neither address nor ApiListener")
.c_str()));
resource_names_failed->insert(listener_name);
continue;
}
grpc_error_handle error = GRPC_ERROR_NONE;
if (api_listener != nullptr) {
error = LdsResponseParseClient(context, api_listener, is_v2, &lds_update);
} else {
error = LdsResponseParseServer(context, listener, is_v2, &lds_update);
}
if (error != GRPC_ERROR_NONE) {
errors.push_back(grpc_error_add_child(
GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(listener_name, ": validation error").c_str()),
error));
resource_names_failed->insert(listener_name);
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing LDS response", &errors);
}
grpc_error_handle RdsResponseParse(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryResponse* response,
const std::set<absl::string_view>& expected_route_configuration_names,
XdsApi::RdsUpdateMap* rds_update_map,
std::set<std::string>* resource_names_failed) {
std::vector<grpc_error_handle> errors;
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
absl::string_view type_url =
UpbStringToAbsl(google_protobuf_Any_type_url(resources[i]));
if (!IsRds(type_url)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Resource is not RDS.")
.c_str()));
continue;
}
// Decode the route_config.
const upb_strview encoded_route_config =
google_protobuf_Any_value(resources[i]);
const envoy_config_route_v3_RouteConfiguration* route_config =
envoy_config_route_v3_RouteConfiguration_parse(
encoded_route_config.data, encoded_route_config.size,
context.arena);
if (route_config == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Can't decode route_config.")
.c_str()));
continue;
}
// Check route_config_name. Ignore unexpected route_config.
std::string route_config_name = UpbStringToStdString(
envoy_config_route_v3_RouteConfiguration_name(route_config));
if (expected_route_configuration_names.find(route_config_name) ==
expected_route_configuration_names.end()) {
continue;
}
// Fail if route config name is duplicated.
if (rds_update_map->find(route_config_name) != rds_update_map->end()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("duplicate route config name \"", route_config_name,
"\"")
.c_str()));
resource_names_failed->insert(route_config_name);
continue;
}
// Serialize into JSON and store it in the RdsUpdateMap
XdsApi::RdsResourceData& rds_resource_data =
(*rds_update_map)[route_config_name];
XdsApi::RdsUpdate& rds_update = rds_resource_data.resource;
rds_resource_data.serialized_proto =
UpbStringToStdString(encoded_route_config);
// Parse the route_config.
grpc_error_handle error =
RouteConfigParse(context, route_config, &rds_update);
if (error != GRPC_ERROR_NONE) {
errors.push_back(grpc_error_add_child(
GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(route_config_name, ": validation error").c_str()),
error));
resource_names_failed->insert(route_config_name);
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing RDS response", &errors);
}
grpc_error_handle CdsResponseParse(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryResponse* response,
const std::set<absl::string_view>& expected_cluster_names,
XdsApi::CdsUpdateMap* cds_update_map,
std::set<std::string>* resource_names_failed) {
std::vector<grpc_error_handle> errors;
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size);
// Parse all the resources in the CDS response.
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
absl::string_view type_url =
UpbStringToAbsl(google_protobuf_Any_type_url(resources[i]));
if (!IsCds(type_url)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Resource is not CDS.")
.c_str()));
continue;
}
// Decode the cluster.
const upb_strview encoded_cluster = google_protobuf_Any_value(resources[i]);
const envoy_config_cluster_v3_Cluster* cluster =
envoy_config_cluster_v3_Cluster_parse(
encoded_cluster.data, encoded_cluster.size, context.arena);
if (cluster == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Can't decode cluster.")
.c_str()));
continue;
}
MaybeLogCluster(context, cluster);
// Ignore unexpected cluster names.
std::string cluster_name =
UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(cluster));
if (expected_cluster_names.find(cluster_name) ==
expected_cluster_names.end()) {
continue;
}
// Fail on duplicate resources.
if (cds_update_map->find(cluster_name) != cds_update_map->end()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("duplicate resource name \"", cluster_name, "\"")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
// Serialize into JSON and store it in the CdsUpdateMap
XdsApi::CdsResourceData& cds_resource_data =
(*cds_update_map)[cluster_name];
XdsApi::CdsUpdate& cds_update = cds_resource_data.resource;
cds_resource_data.serialized_proto = UpbStringToStdString(encoded_cluster);
// Check the cluster_discovery_type.
if (!envoy_config_cluster_v3_Cluster_has_type(cluster) &&
!envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": DiscoveryType not found.").c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_EDS) {
cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::EDS;
// Check the EDS config source.
const envoy_config_cluster_v3_Cluster_EdsClusterConfig*
eds_cluster_config =
envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster);
const envoy_config_core_v3_ConfigSource* eds_config =
envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config(
eds_cluster_config);
if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": EDS ConfigSource is not ADS.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
// Record EDS service_name (if any).
upb_strview service_name =
envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name(
eds_cluster_config);
if (service_name.size != 0) {
cds_update.eds_service_name = UpbStringToStdString(service_name);
}
} else if (!XdsAggregateAndLogicalDnsClusterEnabled()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": DiscoveryType is not valid.").c_str()));
resource_names_failed->insert(cluster_name);
continue;
} else if (envoy_config_cluster_v3_Cluster_type(cluster) ==
envoy_config_cluster_v3_Cluster_LOGICAL_DNS) {
cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::LOGICAL_DNS;
} else {
if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) {
const envoy_config_cluster_v3_Cluster_CustomClusterType*
custom_cluster_type =
envoy_config_cluster_v3_Cluster_cluster_type(cluster);
upb_strview type_name =
envoy_config_cluster_v3_Cluster_CustomClusterType_name(
custom_cluster_type);
if (UpbStringToAbsl(type_name) == "envoy.clusters.aggregate") {
cds_update.cluster_type = XdsApi::CdsUpdate::ClusterType::AGGREGATE;
// Retrieve aggregate clusters.
const google_protobuf_Any* typed_config =
envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config(
custom_cluster_type);
const upb_strview aggregate_cluster_config_upb_strview =
google_protobuf_Any_value(typed_config);
const envoy_extensions_clusters_aggregate_v3_ClusterConfig*
aggregate_cluster_config =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse(
aggregate_cluster_config_upb_strview.data,
aggregate_cluster_config_upb_strview.size, context.arena);
if (aggregate_cluster_config == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": Can't parse aggregate cluster.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
size_t size;
const upb_strview* clusters =
envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters(
aggregate_cluster_config, &size);
for (size_t i = 0; i < size; ++i) {
const upb_strview cluster = clusters[i];
cds_update.prioritized_cluster_names.emplace_back(
UpbStringToStdString(cluster));
}
} else {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": DiscoveryType is not valid.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
} else {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": DiscoveryType is not valid.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
}
// Check the LB policy.
if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_ROUND_ROBIN) {
cds_update.lb_policy = "ROUND_ROBIN";
} else if (XdsRingHashEnabled() &&
envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
envoy_config_cluster_v3_Cluster_RING_HASH) {
cds_update.lb_policy = "RING_HASH";
// Record ring hash lb config
auto* ring_hash_config =
envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster);
if (ring_hash_config != nullptr) {
const google_protobuf_UInt64Value* max_ring_size =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size(
ring_hash_config);
if (max_ring_size != nullptr) {
cds_update.max_ring_size =
google_protobuf_UInt64Value_value(max_ring_size);
if (cds_update.max_ring_size > 8388608 ||
cds_update.max_ring_size == 0) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(
cluster_name,
": max_ring_size is not in the range of 1 to 8388608.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
}
const google_protobuf_UInt64Value* min_ring_size =
envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size(
ring_hash_config);
if (min_ring_size != nullptr) {
cds_update.min_ring_size =
google_protobuf_UInt64Value_value(min_ring_size);
if (cds_update.min_ring_size > 8388608 ||
cds_update.min_ring_size == 0) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(
cluster_name,
": min_ring_size is not in the range of 1 to 8388608.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
if (cds_update.min_ring_size > cds_update.max_ring_size) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(
cluster_name,
": min_ring_size cannot be greater than max_ring_size.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
}
if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function(
ring_hash_config) !=
envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name,
": ring hash lb config has invalid hash function.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
}
} else {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": LB policy is not supported.").c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
if (XdsSecurityEnabled()) {
// Record Upstream tls context
auto* transport_socket =
envoy_config_cluster_v3_Cluster_transport_socket(cluster);
if (transport_socket != nullptr) {
absl::string_view name = UpbStringToAbsl(
envoy_config_core_v3_TransportSocket_name(transport_socket));
if (name == "envoy.transport_sockets.tls") {
auto* typed_config =
envoy_config_core_v3_TransportSocket_typed_config(
transport_socket);
if (typed_config != nullptr) {
const upb_strview encoded_upstream_tls_context =
google_protobuf_Any_value(typed_config);
auto* upstream_tls_context =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse(
encoded_upstream_tls_context.data,
encoded_upstream_tls_context.size, context.arena);
if (upstream_tls_context == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name,
": Can't decode upstream tls context.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
auto* common_tls_context =
envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context(
upstream_tls_context);
if (common_tls_context != nullptr) {
grpc_error_handle error = CommonTlsContextParse(
common_tls_context, &cds_update.common_tls_context);
if (error != GRPC_ERROR_NONE) {
errors.push_back(grpc_error_add_child(
GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": error in TLS context")
.c_str()),
error));
resource_names_failed->insert(cluster_name);
continue;
}
}
}
if (cds_update.common_tls_context.combined_validation_context
.validation_context_certificate_provider_instance
.instance_name.empty()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name,
"TLS configuration provided but no "
"validation_context_certificate_provider_instance "
"found.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
}
}
}
// Record LRS server name (if any).
const envoy_config_core_v3_ConfigSource* lrs_server =
envoy_config_cluster_v3_Cluster_lrs_server(cluster);
if (lrs_server != nullptr) {
if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(cluster_name, ": LRS ConfigSource is not self.")
.c_str()));
resource_names_failed->insert(cluster_name);
continue;
}
cds_update.lrs_load_reporting_server_name.emplace("");
}
// The Cluster resource encodes the circuit breaking parameters in a list of
// Thresholds messages, where each message specifies the parameters for a
// particular RoutingPriority. we will look only at the first entry in the
// list for priority DEFAULT and default to 1024 if not found.
if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) {
const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers =
envoy_config_cluster_v3_Cluster_circuit_breakers(cluster);
size_t num_thresholds;
const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const*
thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds(
circuit_breakers, &num_thresholds);
for (size_t i = 0; i < num_thresholds; ++i) {
const auto* threshold = thresholds[i];
if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority(
threshold) == envoy_config_core_v3_DEFAULT) {
const google_protobuf_UInt32Value* max_requests =
envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests(
threshold);
if (max_requests != nullptr) {
cds_update.max_concurrent_requests =
google_protobuf_UInt32Value_value(max_requests);
}
break;
}
}
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing CDS response", &errors);
}
grpc_error_handle ServerAddressParseAndAppend(
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint,
ServerAddressList* list) {
// If health_status is not HEALTHY or UNKNOWN, skip this endpoint.
const int32_t health_status =
envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint);
if (health_status != envoy_config_core_v3_UNKNOWN &&
health_status != envoy_config_core_v3_HEALTHY) {
return GRPC_ERROR_NONE;
}
// Find the ip:port.
const envoy_config_endpoint_v3_Endpoint* endpoint =
envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint);
const envoy_config_core_v3_Address* address =
envoy_config_endpoint_v3_Endpoint_address(endpoint);
const envoy_config_core_v3_SocketAddress* socket_address =
envoy_config_core_v3_Address_socket_address(address);
std::string address_str = UpbStringToStdString(
envoy_config_core_v3_SocketAddress_address(socket_address));
uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address);
if (GPR_UNLIKELY(port >> 16) != 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid port.");
}
// Find load_balancing_weight for the endpoint.
const google_protobuf_UInt32Value* load_balancing_weight =
envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint);
const int32_t weight =
load_balancing_weight != nullptr
? google_protobuf_UInt32Value_value(load_balancing_weight)
: 500;
if (weight == 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid endpoint weight of 0.");
}
// Populate grpc_resolved_address.
grpc_resolved_address addr;
grpc_error_handle error =
grpc_string_to_sockaddr(&addr, address_str.c_str(), port);
if (error != GRPC_ERROR_NONE) return error;
// Append the address to the list.
std::map<const char*, std::unique_ptr<ServerAddress::AttributeInterface>>
attributes;
attributes[ServerAddressWeightAttribute::kServerAddressWeightAttributeKey] =
absl::make_unique<ServerAddressWeightAttribute>(weight);
list->emplace_back(addr, nullptr, std::move(attributes));
return GRPC_ERROR_NONE;
}
grpc_error_handle LocalityParse(
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints,
XdsApi::EdsUpdate::Priority::Locality* output_locality, size_t* priority) {
// Parse LB weight.
const google_protobuf_UInt32Value* lb_weight =
envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight(
locality_lb_endpoints);
// If LB weight is not specified, it means this locality is assigned no load.
// TODO(juanlishen): When we support CDS to configure the inter-locality
// policy, we should change the LB weight handling.
output_locality->lb_weight =
lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0;
if (output_locality->lb_weight == 0) return GRPC_ERROR_NONE;
// Parse locality name.
const envoy_config_core_v3_Locality* locality =
envoy_config_endpoint_v3_LocalityLbEndpoints_locality(
locality_lb_endpoints);
if (locality == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty locality.");
}
std::string region =
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality));
std::string zone =
UpbStringToStdString(envoy_config_core_v3_Locality_region(locality));
std::string sub_zone =
UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality));
output_locality->name = MakeRefCounted<XdsLocalityName>(
std::move(region), std::move(zone), std::move(sub_zone));
// Parse the addresses.
size_t size;
const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints =
envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(
locality_lb_endpoints, &size);
for (size_t i = 0; i < size; ++i) {
grpc_error_handle error = ServerAddressParseAndAppend(
lb_endpoints[i], &output_locality->endpoints);
if (error != GRPC_ERROR_NONE) return error;
}
// Parse the priority.
*priority = envoy_config_endpoint_v3_LocalityLbEndpoints_priority(
locality_lb_endpoints);
return GRPC_ERROR_NONE;
}
grpc_error_handle DropParseAndAppend(
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload*
drop_overload,
XdsApi::EdsUpdate::DropConfig* drop_config) {
// Get the category.
std::string category = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category(
drop_overload));
if (category.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty drop category name");
}
// Get the drop rate (per million).
const envoy_type_v3_FractionalPercent* drop_percentage =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage(
drop_overload);
uint32_t numerator =
envoy_type_v3_FractionalPercent_numerator(drop_percentage);
const auto denominator =
static_cast<envoy_type_v3_FractionalPercent_DenominatorType>(
envoy_type_v3_FractionalPercent_denominator(drop_percentage));
// Normalize to million.
switch (denominator) {
case envoy_type_v3_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_v3_FractionalPercent_MILLION:
break;
default:
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Unknown denominator type");
}
// Cap numerator to 1000000.
numerator = GPR_MIN(numerator, 1000000);
drop_config->AddCategory(std::move(category), numerator);
return GRPC_ERROR_NONE;
}
grpc_error_handle EdsResponseParse(
const EncodingContext& context,
const envoy_service_discovery_v3_DiscoveryResponse* response,
const std::set<absl::string_view>& expected_eds_service_names,
XdsApi::EdsUpdateMap* eds_update_map,
std::set<std::string>* resource_names_failed) {
std::vector<grpc_error_handle> errors;
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_service_discovery_v3_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
absl::string_view type_url =
UpbStringToAbsl(google_protobuf_Any_type_url(resources[i]));
if (!IsEds(type_url)) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i, ": Resource is not EDS.")
.c_str()));
continue;
}
// Get the cluster_load_assignment.
upb_strview encoded_cluster_load_assignment =
google_protobuf_Any_value(resources[i]);
envoy_config_endpoint_v3_ClusterLoadAssignment* cluster_load_assignment =
envoy_config_endpoint_v3_ClusterLoadAssignment_parse(
encoded_cluster_load_assignment.data,
encoded_cluster_load_assignment.size, context.arena);
if (cluster_load_assignment == nullptr) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("resource index ", i,
": Can't parse cluster_load_assignment.")
.c_str()));
continue;
}
MaybeLogClusterLoadAssignment(context, cluster_load_assignment);
// Check the EDS service name. Ignore unexpected names.
std::string eds_service_name = UpbStringToStdString(
envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(
cluster_load_assignment));
if (expected_eds_service_names.find(eds_service_name) ==
expected_eds_service_names.end()) {
continue;
}
// Fail on duplicate resources.
if (eds_update_map->find(eds_service_name) != eds_update_map->end()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat("duplicate resource name \"", eds_service_name, "\"")
.c_str()));
resource_names_failed->insert(eds_service_name);
continue;
}
// Serialize into JSON and store it in the EdsUpdateMap
XdsApi::EdsResourceData& eds_resource_data =
(*eds_update_map)[eds_service_name];
XdsApi::EdsUpdate& eds_update = eds_resource_data.resource;
eds_resource_data.serialized_proto =
UpbStringToStdString(encoded_cluster_load_assignment);
// Get the endpoints.
size_t locality_size;
const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints =
envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(
cluster_load_assignment, &locality_size);
grpc_error_handle error = GRPC_ERROR_NONE;
for (size_t j = 0; j < locality_size; ++j) {
size_t priority;
XdsApi::EdsUpdate::Priority::Locality locality;
error = LocalityParse(endpoints[j], &locality, &priority);
if (error != GRPC_ERROR_NONE) break;
// Filter out locality with weight 0.
if (locality.lb_weight == 0) continue;
// Make sure prorities is big enough. Note that they might not
// arrive in priority order.
while (eds_update.priorities.size() < priority + 1) {
eds_update.priorities.emplace_back();
}
eds_update.priorities[priority].localities.emplace(locality.name.get(),
std::move(locality));
}
if (error != GRPC_ERROR_NONE) {
errors.push_back(grpc_error_add_child(
GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(eds_service_name, ": locality validation error")
.c_str()),
error));
resource_names_failed->insert(eds_service_name);
continue;
}
for (const auto& priority : eds_update.priorities) {
if (priority.localities.empty()) {
errors.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(eds_service_name, ": sparse priority list").c_str()));
resource_names_failed->insert(eds_service_name);
continue;
}
}
// Get the drop config.
eds_update.drop_config = MakeRefCounted<XdsApi::EdsUpdate::DropConfig>();
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy* policy =
envoy_config_endpoint_v3_ClusterLoadAssignment_policy(
cluster_load_assignment);
if (policy != nullptr) {
size_t drop_size;
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* const*
drop_overload =
envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads(
policy, &drop_size);
for (size_t j = 0; j < drop_size; ++j) {
error =
DropParseAndAppend(drop_overload[j], eds_update.drop_config.get());
if (error != GRPC_ERROR_NONE) break;
}
if (error != GRPC_ERROR_NONE) {
errors.push_back(grpc_error_add_child(
GRPC_ERROR_CREATE_FROM_COPIED_STRING(
absl::StrCat(eds_service_name, ": drop config validation error")
.c_str()),
error));
resource_names_failed->insert(eds_service_name);
continue;
}
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing EDS response", &errors);
}
std::string TypeUrlInternalToExternal(absl::string_view type_url) {
if (type_url == kLdsV2TypeUrl) {
return XdsApi::kLdsTypeUrl;
} else if (type_url == kRdsV2TypeUrl) {
return XdsApi::kRdsTypeUrl;
} else if (type_url == kCdsV2TypeUrl) {
return XdsApi::kCdsTypeUrl;
} else if (type_url == kEdsV2TypeUrl) {
return XdsApi::kEdsTypeUrl;
}
return std::string(type_url);
}
template <typename UpdateMap>
void MoveUpdatesToFailedSet(UpdateMap* update_map,
std::set<std::string>* resource_names_failed) {
for (const auto& p : *update_map) {
resource_names_failed->insert(p.first);
}
update_map->clear();
}
} // namespace
XdsApi::AdsParseResult XdsApi::ParseAdsResponse(
const XdsBootstrap::XdsServer& server, const grpc_slice& encoded_response,
const std::set<absl::string_view>& expected_listener_names,
const std::set<absl::string_view>& expected_route_configuration_names,
const std::set<absl::string_view>& expected_cluster_names,
const std::set<absl::string_view>& expected_eds_service_names) {
AdsParseResult result;
upb::Arena arena;
const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(),
server.ShouldUseV3()};
// Decode the response.
const envoy_service_discovery_v3_DiscoveryResponse* response =
envoy_service_discovery_v3_DiscoveryResponse_parse(
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(encoded_response)),
GRPC_SLICE_LENGTH(encoded_response), arena.ptr());
// If decoding fails, output an empty type_url and return.
if (response == nullptr) {
result.parse_error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode DiscoveryResponse.");
return result;
}
MaybeLogDiscoveryResponse(context, response);
// Record the type_url, the version_info, and the nonce of the response.
result.type_url = TypeUrlInternalToExternal(UpbStringToAbsl(
envoy_service_discovery_v3_DiscoveryResponse_type_url(response)));
result.version = UpbStringToStdString(
envoy_service_discovery_v3_DiscoveryResponse_version_info(response));
result.nonce = UpbStringToStdString(
envoy_service_discovery_v3_DiscoveryResponse_nonce(response));
// Parse the response according to the resource type.
if (IsLds(result.type_url)) {
result.parse_error =
LdsResponseParse(context, response, expected_listener_names,
&result.lds_update_map, &result.resource_names_failed);
if (result.parse_error != GRPC_ERROR_NONE) {
MoveUpdatesToFailedSet(&result.lds_update_map,
&result.resource_names_failed);
}
} else if (IsRds(result.type_url)) {
result.parse_error =
RdsResponseParse(context, response, expected_route_configuration_names,
&result.rds_update_map, &result.resource_names_failed);
if (result.parse_error != GRPC_ERROR_NONE) {
MoveUpdatesToFailedSet(&result.rds_update_map,
&result.resource_names_failed);
}
} else if (IsCds(result.type_url)) {
result.parse_error =
CdsResponseParse(context, response, expected_cluster_names,
&result.cds_update_map, &result.resource_names_failed);
if (result.parse_error != GRPC_ERROR_NONE) {
MoveUpdatesToFailedSet(&result.cds_update_map,
&result.resource_names_failed);
}
} else if (IsEds(result.type_url)) {
result.parse_error =
EdsResponseParse(context, response, expected_eds_service_names,
&result.eds_update_map, &result.resource_names_failed);
if (result.parse_error != GRPC_ERROR_NONE) {
MoveUpdatesToFailedSet(&result.eds_update_map,
&result.resource_names_failed);
}
}
return result;
}
namespace {
void MaybeLogLrsRequest(
const EncodingContext& context,
const envoy_service_load_stats_v3_LoadStatsRequest* request) {
if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
const upb_msgdef* msg_type =
envoy_service_load_stats_v3_LoadStatsRequest_getmsgdef(context.symtab);
char buf[10240];
upb_text_encode(request, msg_type, nullptr, 0, buf, sizeof(buf));
gpr_log(GPR_DEBUG, "[xds_client %p] constructed LRS request: %s",
context.client, buf);
}
}
grpc_slice SerializeLrsRequest(
const EncodingContext& context,
const envoy_service_load_stats_v3_LoadStatsRequest* request) {
size_t output_length;
char* output = envoy_service_load_stats_v3_LoadStatsRequest_serialize(
request, context.arena, &output_length);
return grpc_slice_from_copied_buffer(output, output_length);
}
} // namespace
grpc_slice XdsApi::CreateLrsInitialRequest(
const XdsBootstrap::XdsServer& server) {
upb::Arena arena;
const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(),
server.ShouldUseV3()};
// Create a request.
envoy_service_load_stats_v3_LoadStatsRequest* request =
envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr());
// Populate node.
envoy_config_core_v3_Node* node_msg =
envoy_service_load_stats_v3_LoadStatsRequest_mutable_node(request,
arena.ptr());
PopulateNode(context, node_, build_version_, user_agent_name_, node_msg);
envoy_config_core_v3_Node_add_client_features(
node_msg, upb_strview_makez("envoy.lrs.supports_send_all_clusters"),
arena.ptr());
MaybeLogLrsRequest(context, request);
return SerializeLrsRequest(context, request);
}
namespace {
void LocalityStatsPopulate(
const EncodingContext& context,
envoy_config_endpoint_v3_UpstreamLocalityStats* output,
const XdsLocalityName& locality_name,
const XdsClusterLocalityStats::Snapshot& snapshot) {
// Set locality.
envoy_config_core_v3_Locality* locality =
envoy_config_endpoint_v3_UpstreamLocalityStats_mutable_locality(
output, context.arena);
if (!locality_name.region().empty()) {
envoy_config_core_v3_Locality_set_region(
locality, StdStringToUpbString(locality_name.region()));
}
if (!locality_name.zone().empty()) {
envoy_config_core_v3_Locality_set_zone(
locality, StdStringToUpbString(locality_name.zone()));
}
if (!locality_name.sub_zone().empty()) {
envoy_config_core_v3_Locality_set_sub_zone(
locality, StdStringToUpbString(locality_name.sub_zone()));
}
// Set total counts.
envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_successful_requests(
output, snapshot.total_successful_requests);
envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_requests_in_progress(
output, snapshot.total_requests_in_progress);
envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_error_requests(
output, snapshot.total_error_requests);
envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_issued_requests(
output, snapshot.total_issued_requests);
// Add backend metrics.
for (const auto& p : snapshot.backend_metrics) {
const std::string& metric_name = p.first;
const XdsClusterLocalityStats::BackendMetric& metric_value = p.second;
envoy_config_endpoint_v3_EndpointLoadMetricStats* load_metric =
envoy_config_endpoint_v3_UpstreamLocalityStats_add_load_metric_stats(
output, context.arena);
envoy_config_endpoint_v3_EndpointLoadMetricStats_set_metric_name(
load_metric, StdStringToUpbString(metric_name));
envoy_config_endpoint_v3_EndpointLoadMetricStats_set_num_requests_finished_with_metric(
load_metric, metric_value.num_requests_finished_with_metric);
envoy_config_endpoint_v3_EndpointLoadMetricStats_set_total_metric_value(
load_metric, metric_value.total_metric_value);
}
}
} // namespace
grpc_slice XdsApi::CreateLrsRequest(
ClusterLoadReportMap cluster_load_report_map) {
upb::Arena arena;
const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(),
false};
// Create a request.
envoy_service_load_stats_v3_LoadStatsRequest* request =
envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr());
for (auto& p : cluster_load_report_map) {
const std::string& cluster_name = p.first.first;
const std::string& eds_service_name = p.first.second;
const ClusterLoadReport& load_report = p.second;
// Add cluster stats.
envoy_config_endpoint_v3_ClusterStats* cluster_stats =
envoy_service_load_stats_v3_LoadStatsRequest_add_cluster_stats(
request, arena.ptr());
// Set the cluster name.
envoy_config_endpoint_v3_ClusterStats_set_cluster_name(
cluster_stats, StdStringToUpbString(cluster_name));
// Set EDS service name, if non-empty.
if (!eds_service_name.empty()) {
envoy_config_endpoint_v3_ClusterStats_set_cluster_service_name(
cluster_stats, StdStringToUpbString(eds_service_name));
}
// Add locality stats.
for (const auto& p : load_report.locality_stats) {
const XdsLocalityName& locality_name = *p.first;
const auto& snapshot = p.second;
envoy_config_endpoint_v3_UpstreamLocalityStats* locality_stats =
envoy_config_endpoint_v3_ClusterStats_add_upstream_locality_stats(
cluster_stats, arena.ptr());
LocalityStatsPopulate(context, locality_stats, locality_name, snapshot);
}
// Add dropped requests.
uint64_t total_dropped_requests = 0;
for (const auto& p : load_report.dropped_requests.categorized_drops) {
const std::string& category = p.first;
const uint64_t count = p.second;
envoy_config_endpoint_v3_ClusterStats_DroppedRequests* dropped_requests =
envoy_config_endpoint_v3_ClusterStats_add_dropped_requests(
cluster_stats, arena.ptr());
envoy_config_endpoint_v3_ClusterStats_DroppedRequests_set_category(
dropped_requests, StdStringToUpbString(category));
envoy_config_endpoint_v3_ClusterStats_DroppedRequests_set_dropped_count(
dropped_requests, count);
total_dropped_requests += count;
}
total_dropped_requests += load_report.dropped_requests.uncategorized_drops;
// Set total dropped requests.
envoy_config_endpoint_v3_ClusterStats_set_total_dropped_requests(
cluster_stats, total_dropped_requests);
// Set real load report interval.
gpr_timespec timespec =
grpc_millis_to_timespec(load_report.load_report_interval, GPR_TIMESPAN);
google_protobuf_Duration* load_report_interval =
envoy_config_endpoint_v3_ClusterStats_mutable_load_report_interval(
cluster_stats, arena.ptr());
google_protobuf_Duration_set_seconds(load_report_interval, timespec.tv_sec);
google_protobuf_Duration_set_nanos(load_report_interval, timespec.tv_nsec);
}
MaybeLogLrsRequest(context, request);
return SerializeLrsRequest(context, request);
}
grpc_error_handle XdsApi::ParseLrsResponse(
const grpc_slice& encoded_response, bool* send_all_clusters,
std::set<std::string>* cluster_names,
grpc_millis* load_reporting_interval) {
upb::Arena arena;
// Decode the response.
const envoy_service_load_stats_v3_LoadStatsResponse* decoded_response =
envoy_service_load_stats_v3_LoadStatsResponse_parse(
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(encoded_response)),
GRPC_SLICE_LENGTH(encoded_response), arena.ptr());
// Parse the response.
if (decoded_response == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode response.");
}
// Check send_all_clusters.
if (envoy_service_load_stats_v3_LoadStatsResponse_send_all_clusters(
decoded_response)) {
*send_all_clusters = true;
} else {
// Store the cluster names.
size_t size;
const upb_strview* clusters =
envoy_service_load_stats_v3_LoadStatsResponse_clusters(decoded_response,
&size);
for (size_t i = 0; i < size; ++i) {
cluster_names->emplace(UpbStringToStdString(clusters[i]));
}
}
// Get the load report interval.
const google_protobuf_Duration* load_reporting_interval_duration =
envoy_service_load_stats_v3_LoadStatsResponse_load_reporting_interval(
decoded_response);
gpr_timespec timespec{
google_protobuf_Duration_seconds(load_reporting_interval_duration),
google_protobuf_Duration_nanos(load_reporting_interval_duration),
GPR_TIMESPAN};
*load_reporting_interval = gpr_time_to_millis(timespec);
return GRPC_ERROR_NONE;
}
namespace {
google_protobuf_Timestamp* GrpcMillisToTimestamp(const EncodingContext& context,
grpc_millis value) {
google_protobuf_Timestamp* timestamp =
google_protobuf_Timestamp_new(context.arena);
gpr_timespec timespec = grpc_millis_to_timespec(value, GPR_CLOCK_REALTIME);
google_protobuf_Timestamp_set_seconds(timestamp, timespec.tv_sec);
google_protobuf_Timestamp_set_nanos(timestamp, timespec.tv_nsec);
return timestamp;
}
envoy_admin_v3_UpdateFailureState* CreateUpdateFailureStateUpb(
const EncodingContext& context,
const XdsApi::ResourceMetadata* resource_metadata) {
auto* update_failure_state =
envoy_admin_v3_UpdateFailureState_new(context.arena);
envoy_admin_v3_UpdateFailureState_set_details(
update_failure_state,
StdStringToUpbString(resource_metadata->failed_details));
envoy_admin_v3_UpdateFailureState_set_version_info(
update_failure_state,
StdStringToUpbString(resource_metadata->failed_version));
envoy_admin_v3_UpdateFailureState_set_last_update_attempt(
update_failure_state,
GrpcMillisToTimestamp(context, resource_metadata->failed_update_time));
return update_failure_state;
}
void DumpLdsConfig(const EncodingContext& context,
const XdsApi::ResourceTypeMetadata& resource_type_metadata,
envoy_service_status_v3_PerXdsConfig* per_xds_config) {
upb_strview kLdsTypeUrlUpb = upb_strview_makez(XdsApi::kLdsTypeUrl);
auto* listener_config_dump =
envoy_service_status_v3_PerXdsConfig_mutable_listener_config(
per_xds_config, context.arena);
envoy_admin_v3_ListenersConfigDump_set_version_info(
listener_config_dump,
StdStringToUpbString(resource_type_metadata.version));
for (auto& p : resource_type_metadata.resource_metadata_map) {
absl::string_view name = p.first;
const XdsApi::ResourceMetadata* meta = p.second;
const upb_strview name_upb = StdStringToUpbString(name);
auto* dynamic_listener =
envoy_admin_v3_ListenersConfigDump_add_dynamic_listeners(
listener_config_dump, context.arena);
envoy_admin_v3_ListenersConfigDump_DynamicListener_set_name(
dynamic_listener, name_upb);
envoy_admin_v3_ListenersConfigDump_DynamicListener_set_client_status(
dynamic_listener, meta->client_status);
if (!meta->serialized_proto.empty()) {
// Set in-effective listeners
auto* dynamic_listener_state =
envoy_admin_v3_ListenersConfigDump_DynamicListener_mutable_active_state(
dynamic_listener, context.arena);
envoy_admin_v3_ListenersConfigDump_DynamicListenerState_set_version_info(
dynamic_listener_state, StdStringToUpbString(meta->version));
envoy_admin_v3_ListenersConfigDump_DynamicListenerState_set_last_updated(
dynamic_listener_state,
GrpcMillisToTimestamp(context, meta->update_time));
auto* listener_any =
envoy_admin_v3_ListenersConfigDump_DynamicListenerState_mutable_listener(
dynamic_listener_state, context.arena);
google_protobuf_Any_set_type_url(listener_any, kLdsTypeUrlUpb);
google_protobuf_Any_set_value(
listener_any, StdStringToUpbString(meta->serialized_proto));
}
if (meta->client_status == XdsApi::ResourceMetadata::NACKED) {
// Set error_state if NACKED
envoy_admin_v3_ListenersConfigDump_DynamicListener_set_error_state(
dynamic_listener, CreateUpdateFailureStateUpb(context, meta));
}
}
}
void DumpRdsConfig(const EncodingContext& context,
const XdsApi::ResourceTypeMetadata& resource_type_metadata,
envoy_service_status_v3_PerXdsConfig* per_xds_config) {
upb_strview kRdsTypeUrlUpb = upb_strview_makez(XdsApi::kRdsTypeUrl);
auto* route_config_dump =
envoy_service_status_v3_PerXdsConfig_mutable_route_config(per_xds_config,
context.arena);
for (auto& p : resource_type_metadata.resource_metadata_map) {
absl::string_view name = p.first;
const XdsApi::ResourceMetadata* meta = p.second;
const upb_strview name_upb = StdStringToUpbString(name);
auto* dynamic_route_config =
envoy_admin_v3_RoutesConfigDump_add_dynamic_route_configs(
route_config_dump, context.arena);
envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_client_status(
dynamic_route_config, meta->client_status);
auto* route_config_any =
envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_mutable_route_config(
dynamic_route_config, context.arena);
if (!meta->serialized_proto.empty()) {
// Set in-effective route configs
envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_version_info(
dynamic_route_config, StdStringToUpbString(meta->version));
envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_last_updated(
dynamic_route_config,
GrpcMillisToTimestamp(context, meta->update_time));
google_protobuf_Any_set_type_url(route_config_any, kRdsTypeUrlUpb);
google_protobuf_Any_set_value(
route_config_any, StdStringToUpbString(meta->serialized_proto));
} else {
// If there isn't a working route config, we still need to print the
// name.
auto* route_config =
envoy_config_route_v3_RouteConfiguration_new(context.arena);
envoy_config_route_v3_RouteConfiguration_set_name(route_config, name_upb);
size_t length;
char* bytes = envoy_config_route_v3_RouteConfiguration_serialize(
route_config, context.arena, &length);
google_protobuf_Any_set_type_url(route_config_any, kRdsTypeUrlUpb);
google_protobuf_Any_set_value(route_config_any,
upb_strview_make(bytes, length));
}
if (meta->client_status == XdsApi::ResourceMetadata::NACKED) {
// Set error_state if NACKED
envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig_set_error_state(
dynamic_route_config, CreateUpdateFailureStateUpb(context, meta));
}
}
}
void DumpCdsConfig(const EncodingContext& context,
const XdsApi::ResourceTypeMetadata& resource_type_metadata,
envoy_service_status_v3_PerXdsConfig* per_xds_config) {
upb_strview kCdsTypeUrlUpb = upb_strview_makez(XdsApi::kCdsTypeUrl);
auto* cluster_config_dump =
envoy_service_status_v3_PerXdsConfig_mutable_cluster_config(
per_xds_config, context.arena);
envoy_admin_v3_ClustersConfigDump_set_version_info(
cluster_config_dump,
StdStringToUpbString(resource_type_metadata.version));
for (auto& p : resource_type_metadata.resource_metadata_map) {
absl::string_view name = p.first;
const XdsApi::ResourceMetadata* meta = p.second;
const upb_strview name_upb = StdStringToUpbString(name);
auto* dynamic_cluster =
envoy_admin_v3_ClustersConfigDump_add_dynamic_active_clusters(
cluster_config_dump, context.arena);
envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_client_status(
dynamic_cluster, meta->client_status);
auto* cluster_any =
envoy_admin_v3_ClustersConfigDump_DynamicCluster_mutable_cluster(
dynamic_cluster, context.arena);
if (!meta->serialized_proto.empty()) {
// Set in-effective clusters
envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_version_info(
dynamic_cluster, StdStringToUpbString(meta->version));
envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_last_updated(
dynamic_cluster, GrpcMillisToTimestamp(context, meta->update_time));
google_protobuf_Any_set_type_url(cluster_any, kCdsTypeUrlUpb);
google_protobuf_Any_set_value(
cluster_any, StdStringToUpbString(meta->serialized_proto));
} else {
// If there isn't a working cluster, we still need to print the name.
auto* cluster = envoy_config_cluster_v3_Cluster_new(context.arena);
envoy_config_cluster_v3_Cluster_set_name(cluster, name_upb);
size_t length;
char* bytes = envoy_config_cluster_v3_Cluster_serialize(
cluster, context.arena, &length);
google_protobuf_Any_set_type_url(cluster_any, kCdsTypeUrlUpb);
google_protobuf_Any_set_value(cluster_any,
upb_strview_make(bytes, length));
}
if (meta->client_status == XdsApi::ResourceMetadata::NACKED) {
// Set error_state if NACKED
envoy_admin_v3_ClustersConfigDump_DynamicCluster_set_error_state(
dynamic_cluster, CreateUpdateFailureStateUpb(context, meta));
}
}
}
void DumpEdsConfig(const EncodingContext& context,
const XdsApi::ResourceTypeMetadata& resource_type_metadata,
envoy_service_status_v3_PerXdsConfig* per_xds_config) {
upb_strview kEdsTypeUrlUpb = upb_strview_makez(XdsApi::kEdsTypeUrl);
auto* endpoint_config_dump =
envoy_service_status_v3_PerXdsConfig_mutable_endpoint_config(
per_xds_config, context.arena);
for (auto& p : resource_type_metadata.resource_metadata_map) {
absl::string_view name = p.first;
const XdsApi::ResourceMetadata* meta = p.second;
const upb_strview name_upb = StdStringToUpbString(name);
auto* dynamic_endpoint =
envoy_admin_v3_EndpointsConfigDump_add_dynamic_endpoint_configs(
endpoint_config_dump, context.arena);
envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_client_status(
dynamic_endpoint, meta->client_status);
auto* endpoint_any =
envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_mutable_endpoint_config(
dynamic_endpoint, context.arena);
if (!meta->serialized_proto.empty()) {
// Set in-effective endpoints
envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_version_info(
dynamic_endpoint, StdStringToUpbString(meta->version));
envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_last_updated(
dynamic_endpoint, GrpcMillisToTimestamp(context, meta->update_time));
google_protobuf_Any_set_type_url(endpoint_any, kEdsTypeUrlUpb);
google_protobuf_Any_set_value(
endpoint_any, StdStringToUpbString(meta->serialized_proto));
} else {
// If there isn't a working endpoint, we still need to print the name.
auto* cluster_load_assignment =
envoy_config_endpoint_v3_ClusterLoadAssignment_new(context.arena);
envoy_config_endpoint_v3_ClusterLoadAssignment_set_cluster_name(
cluster_load_assignment, name_upb);
size_t length;
char* bytes = envoy_config_endpoint_v3_ClusterLoadAssignment_serialize(
cluster_load_assignment, context.arena, &length);
google_protobuf_Any_set_type_url(endpoint_any, kEdsTypeUrlUpb);
google_protobuf_Any_set_value(endpoint_any,
upb_strview_make(bytes, length));
}
if (meta->client_status == XdsApi::ResourceMetadata::NACKED) {
// Set error_state if NACKED
envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig_set_error_state(
dynamic_endpoint, CreateUpdateFailureStateUpb(context, meta));
}
}
}
} // namespace
std::string XdsApi::AssembleClientConfig(
const ResourceTypeMetadataMap& resource_type_metadata_map) {
upb::Arena arena;
// Create the ClientConfig for resource metadata from XdsClient
auto* client_config = envoy_service_status_v3_ClientConfig_new(arena.ptr());
// Fill-in the node information
auto* node = envoy_service_status_v3_ClientConfig_mutable_node(client_config,
arena.ptr());
const EncodingContext context = {client_, tracer_, symtab_.ptr(), arena.ptr(),
true};
PopulateNode(context, node_, build_version_, user_agent_name_, node);
// Dump each xDS-type config into PerXdsConfig
for (auto& p : resource_type_metadata_map) {
absl::string_view type_url = p.first;
const ResourceTypeMetadata& resource_type_metadata = p.second;
if (type_url == kLdsTypeUrl) {
auto* per_xds_config =
envoy_service_status_v3_ClientConfig_add_xds_config(client_config,
context.arena);
DumpLdsConfig(context, resource_type_metadata, per_xds_config);
} else if (type_url == kRdsTypeUrl) {
auto* per_xds_config =
envoy_service_status_v3_ClientConfig_add_xds_config(client_config,
context.arena);
DumpRdsConfig(context, resource_type_metadata, per_xds_config);
} else if (type_url == kCdsTypeUrl) {
auto* per_xds_config =
envoy_service_status_v3_ClientConfig_add_xds_config(client_config,
context.arena);
DumpCdsConfig(context, resource_type_metadata, per_xds_config);
} else if (type_url == kEdsTypeUrl) {
auto* per_xds_config =
envoy_service_status_v3_ClientConfig_add_xds_config(client_config,
context.arena);
DumpEdsConfig(context, resource_type_metadata, per_xds_config);
} else {
gpr_log(GPR_ERROR, "invalid type_url %s", std::string(type_url).c_str());
return "";
}
}
// Serialize the upb message to bytes
size_t output_length;
char* output = envoy_service_status_v3_ClientConfig_serialize(
client_config, arena.ptr(), &output_length);
return std::string(output, output_length);
}
} // namespace grpc_core