blob: 4be62a33cd016eefb4bd13aa5209950ec9590edc [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 <cstdlib>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include <grpc/impl/codegen/log.h>
#include <grpc/support/alloc.h>
#include <grpc/support/string_util.h>
#include "src/core/ext/filters/client_channel/xds/xds_api.h"
#include "src/core/lib/gprpp/inlined_vector.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/sockaddr_utils.h"
#include "envoy/api/v2/cds.upb.h"
#include "envoy/api/v2/core/address.upb.h"
#include "envoy/api/v2/core/base.upb.h"
#include "envoy/api/v2/core/config_source.upb.h"
#include "envoy/api/v2/core/health_check.upb.h"
#include "envoy/api/v2/discovery.upb.h"
#include "envoy/api/v2/eds.upb.h"
#include "envoy/api/v2/endpoint/endpoint.upb.h"
#include "envoy/api/v2/endpoint/load_report.upb.h"
#include "envoy/api/v2/lds.upb.h"
#include "envoy/api/v2/rds.upb.h"
#include "envoy/api/v2/route/route.upb.h"
#include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.upb.h"
#include "envoy/config/listener/v2/api_listener.upb.h"
#include "envoy/service/load_stats/v2/lrs.upb.h"
#include "envoy/type/percent.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/struct.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "google/rpc/status.upb.h"
#include "upb/upb.h"
namespace grpc_core {
//
// XdsApi::PriorityListUpdate
//
bool XdsApi::PriorityListUpdate::operator==(
const XdsApi::PriorityListUpdate& other) const {
if (priorities_.size() != other.priorities_.size()) return false;
for (size_t i = 0; i < priorities_.size(); ++i) {
if (priorities_[i].localities != other.priorities_[i].localities) {
return false;
}
}
return true;
}
void XdsApi::PriorityListUpdate::Add(
XdsApi::PriorityListUpdate::LocalityMap::Locality locality) {
// Pad the missing priorities in case the localities are not ordered by
// priority.
if (!Contains(locality.priority)) priorities_.resize(locality.priority + 1);
LocalityMap& locality_map = priorities_[locality.priority];
locality_map.localities.emplace(locality.name, std::move(locality));
}
const XdsApi::PriorityListUpdate::LocalityMap* XdsApi::PriorityListUpdate::Find(
uint32_t priority) const {
if (!Contains(priority)) return nullptr;
return &priorities_[priority];
}
bool XdsApi::PriorityListUpdate::Contains(
const RefCountedPtr<XdsLocalityName>& name) {
for (size_t i = 0; i < priorities_.size(); ++i) {
const LocalityMap& locality_map = priorities_[i];
if (locality_map.Contains(name)) return true;
}
return false;
}
//
// XdsApi::DropConfig
//
bool XdsApi::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;
}
//
// XdsApi
//
const char* XdsApi::kLdsTypeUrl = "type.googleapis.com/envoy.api.v2.Listener";
const char* XdsApi::kRdsTypeUrl =
"type.googleapis.com/envoy.api.v2.RouteConfiguration";
const char* XdsApi::kCdsTypeUrl = "type.googleapis.com/envoy.api.v2.Cluster";
const char* XdsApi::kEdsTypeUrl =
"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment";
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)) {}
namespace {
void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
const Json& value);
void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value,
const Json::Array& values) {
for (const auto& value : values) {
auto* value_pb = google_protobuf_ListValue_add_values(list_value, arena);
PopulateMetadataValue(arena, value_pb, value);
}
}
void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb,
const Json::Object& metadata) {
for (const auto& p : metadata) {
google_protobuf_Struct_FieldsEntry* field =
google_protobuf_Struct_add_fields(metadata_pb, arena);
google_protobuf_Struct_FieldsEntry_set_key(
field, upb_strview_makez(p.first.c_str()));
google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_mutable_value(field, arena);
PopulateMetadataValue(arena, value, p.second);
}
}
void PopulateMetadataValue(upb_arena* arena, 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, upb_strview_makez(value.string_value().c_str()));
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, arena);
PopulateMetadata(arena, struct_value, value.object_value());
break;
}
case Json::Type::ARRAY: {
google_protobuf_ListValue* list_value =
google_protobuf_Value_mutable_list_value(value_pb, arena);
PopulateListValue(arena, list_value, value.array_value());
break;
}
}
}
void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node,
const std::string& build_version,
const std::string& user_agent_name,
const std::string& server_name,
envoy_api_v2_core_Node* node_msg) {
if (node != nullptr) {
if (!node->id.empty()) {
envoy_api_v2_core_Node_set_id(node_msg,
upb_strview_makez(node->id.c_str()));
}
if (!node->cluster.empty()) {
envoy_api_v2_core_Node_set_cluster(
node_msg, upb_strview_makez(node->cluster.c_str()));
}
if (!node->metadata.object_value().empty()) {
google_protobuf_Struct* metadata =
envoy_api_v2_core_Node_mutable_metadata(node_msg, arena);
PopulateMetadata(arena, metadata, node->metadata.object_value());
}
if (!server_name.empty()) {
google_protobuf_Struct* metadata =
envoy_api_v2_core_Node_mutable_metadata(node_msg, arena);
google_protobuf_Struct_FieldsEntry* field =
google_protobuf_Struct_add_fields(metadata, arena);
google_protobuf_Struct_FieldsEntry_set_key(
field, upb_strview_makez("PROXYLESS_CLIENT_HOSTNAME"));
google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_mutable_value(field, arena);
google_protobuf_Value_set_string_value(
value, upb_strview_make(server_name.data(), server_name.size()));
}
if (!node->locality_region.empty() || !node->locality_zone.empty() ||
!node->locality_subzone.empty()) {
envoy_api_v2_core_Locality* locality =
envoy_api_v2_core_Node_mutable_locality(node_msg, arena);
if (!node->locality_region.empty()) {
envoy_api_v2_core_Locality_set_region(
locality, upb_strview_makez(node->locality_region.c_str()));
}
if (!node->locality_zone.empty()) {
envoy_api_v2_core_Locality_set_zone(
locality, upb_strview_makez(node->locality_zone.c_str()));
}
if (!node->locality_subzone.empty()) {
envoy_api_v2_core_Locality_set_sub_zone(
locality, upb_strview_makez(node->locality_subzone.c_str()));
}
}
}
envoy_api_v2_core_Node_set_build_version(
node_msg, upb_strview_make(build_version.data(), build_version.size()));
envoy_api_v2_core_Node_set_user_agent_name(
node_msg,
upb_strview_make(user_agent_name.data(), user_agent_name.size()));
envoy_api_v2_core_Node_set_user_agent_version(
node_msg, upb_strview_makez(grpc_version_string()));
envoy_api_v2_core_Node_add_client_features(
node_msg, upb_strview_makez("envoy.lb.does_not_support_overprovisioning"),
arena);
}
envoy_api_v2_DiscoveryRequest* CreateDiscoveryRequest(
upb_arena* arena, const char* type_url, const std::string& version,
const std::string& nonce, grpc_error* error) {
// Create a request.
envoy_api_v2_DiscoveryRequest* request =
envoy_api_v2_DiscoveryRequest_new(arena);
// Set type_url.
envoy_api_v2_DiscoveryRequest_set_type_url(request,
upb_strview_makez(type_url));
// Set version_info.
if (!version.empty()) {
envoy_api_v2_DiscoveryRequest_set_version_info(
request, upb_strview_makez(version.c_str()));
}
// Set nonce.
if (!nonce.empty()) {
envoy_api_v2_DiscoveryRequest_set_response_nonce(
request, upb_strview_makez(nonce.c_str()));
}
// Set error_detail if it's a NACK.
if (error != GRPC_ERROR_NONE) {
grpc_slice error_description_slice;
GPR_ASSERT(grpc_error_get_str(error, GRPC_ERROR_STR_DESCRIPTION,
&error_description_slice));
upb_strview error_description_strview =
upb_strview_make(reinterpret_cast<const char*>(
GPR_SLICE_START_PTR(error_description_slice)),
GPR_SLICE_LENGTH(error_description_slice));
google_rpc_Status* error_detail =
envoy_api_v2_DiscoveryRequest_mutable_error_detail(request, arena);
google_rpc_Status_set_message(error_detail, error_description_strview);
GRPC_ERROR_UNREF(error);
}
return request;
}
inline absl::string_view UpbStringToAbsl(const upb_strview& str) {
return absl::string_view(str.data, str.size);
}
inline void AddStringField(const char* name, const upb_strview& value,
std::vector<std::string>* fields,
bool add_if_empty = false) {
if (value.size > 0 || add_if_empty) {
fields->emplace_back(
absl::StrCat(name, ": \"", UpbStringToAbsl(value), "\""));
}
}
inline void AddLocalityField(int indent_level,
const envoy_api_v2_core_Locality* locality,
std::vector<std::string>* fields) {
std::string indent =
absl::StrJoin(std::vector<std::string>(indent_level, " "), "");
// region
std::string field = absl::StrCat(indent, "region");
AddStringField(field.c_str(), envoy_api_v2_core_Locality_region(locality),
fields);
// zone
field = absl::StrCat(indent, "zone");
AddStringField(field.c_str(), envoy_api_v2_core_Locality_zone(locality),
fields);
// sub_zone
field = absl::StrCat(indent, "sub_zone");
AddStringField(field.c_str(), envoy_api_v2_core_Locality_sub_zone(locality),
fields);
}
void AddNodeLogFields(const envoy_api_v2_core_Node* node,
std::vector<std::string>* fields) {
fields->emplace_back("node {");
// id
AddStringField(" id", envoy_api_v2_core_Node_id(node), fields);
// metadata
const google_protobuf_Struct* metadata =
envoy_api_v2_core_Node_metadata(node);
if (metadata != nullptr) {
fields->emplace_back(" metadata {");
size_t num_entries;
const google_protobuf_Struct_FieldsEntry* const* entries =
google_protobuf_Struct_fields(metadata, &num_entries);
for (size_t i = 0; i < num_entries; ++i) {
fields->emplace_back(" field {");
// key
AddStringField(" key",
google_protobuf_Struct_FieldsEntry_key(entries[i]),
fields);
// value
const google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_value(entries[i]);
if (value != nullptr) {
std::string value_str;
if (google_protobuf_Value_has_string_value(value)) {
value_str = absl::StrCat(
"string_value: \"",
UpbStringToAbsl(google_protobuf_Value_string_value(value)), "\"");
} else if (google_protobuf_Value_has_null_value(value)) {
value_str = "null_value: NULL_VALUE";
} else if (google_protobuf_Value_has_number_value(value)) {
value_str = absl::StrCat("double_value: ",
google_protobuf_Value_number_value(value));
} else if (google_protobuf_Value_has_bool_value(value)) {
value_str = absl::StrCat("bool_value: ",
google_protobuf_Value_bool_value(value));
} else if (google_protobuf_Value_has_struct_value(value)) {
value_str = "struct_value: <not printed>";
} else if (google_protobuf_Value_has_list_value(value)) {
value_str = "list_value: <not printed>";
} else {
value_str = "<unknown>";
}
fields->emplace_back(absl::StrCat(" value { ", value_str, " }"));
}
fields->emplace_back(" }");
}
fields->emplace_back(" }");
}
// locality
const envoy_api_v2_core_Locality* locality =
envoy_api_v2_core_Node_locality(node);
if (locality != nullptr) {
fields->emplace_back(" locality {");
AddLocalityField(2, locality, fields);
fields->emplace_back(" }");
}
// build_version
AddStringField(" build_version", envoy_api_v2_core_Node_build_version(node),
fields);
// user_agent_name
AddStringField(" user_agent_name",
envoy_api_v2_core_Node_user_agent_name(node), fields);
// user_agent_version
AddStringField(" user_agent_version",
envoy_api_v2_core_Node_user_agent_version(node), fields);
// client_features
size_t num_client_features;
const upb_strview* client_features =
envoy_api_v2_core_Node_client_features(node, &num_client_features);
for (size_t i = 0; i < num_client_features; ++i) {
AddStringField(" client_features", client_features[i], fields);
}
fields->emplace_back("}");
}
void MaybeLogDiscoveryRequest(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryRequest* request) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// version_info
AddStringField("version_info",
envoy_api_v2_DiscoveryRequest_version_info(request),
&fields);
// node
const envoy_api_v2_core_Node* node =
envoy_api_v2_DiscoveryRequest_node(request);
if (node != nullptr) AddNodeLogFields(node, &fields);
// resource_names
size_t num_resource_names;
const upb_strview* resource_names =
envoy_api_v2_DiscoveryRequest_resource_names(request,
&num_resource_names);
for (size_t i = 0; i < num_resource_names; ++i) {
AddStringField("resource_names", resource_names[i], &fields);
}
// type_url
AddStringField("type_url", envoy_api_v2_DiscoveryRequest_type_url(request),
&fields);
// response_nonce
AddStringField("response_nonce",
envoy_api_v2_DiscoveryRequest_response_nonce(request),
&fields);
// error_detail
const struct google_rpc_Status* error_detail =
envoy_api_v2_DiscoveryRequest_error_detail(request);
if (error_detail != nullptr) {
fields.emplace_back("error_detail {");
// code
int32_t code = google_rpc_Status_code(error_detail);
if (code != 0) fields.emplace_back(absl::StrCat(" code: ", code));
// message
AddStringField(" message", google_rpc_Status_message(error_detail),
&fields);
fields.emplace_back("}");
}
gpr_log(GPR_DEBUG, "[xds_client %p] constructed ADS request: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
grpc_slice SerializeDiscoveryRequest(upb_arena* arena,
envoy_api_v2_DiscoveryRequest* request) {
size_t output_length;
char* output =
envoy_api_v2_DiscoveryRequest_serialize(request, arena, &output_length);
return grpc_slice_from_copied_buffer(output, output_length);
}
} // namespace
grpc_slice XdsApi::CreateUnsupportedTypeNackRequest(const std::string& type_url,
const std::string& nonce,
grpc_error* error) {
upb::Arena arena;
envoy_api_v2_DiscoveryRequest* request = CreateDiscoveryRequest(
arena.ptr(), type_url.c_str(), /*version=*/"", nonce, error);
MaybeLogDiscoveryRequest(client_, tracer_, request);
return SerializeDiscoveryRequest(arena.ptr(), request);
}
grpc_slice XdsApi::CreateLdsRequest(const std::string& server_name,
const std::string& version,
const std::string& nonce, grpc_error* error,
bool populate_node) {
upb::Arena arena;
envoy_api_v2_DiscoveryRequest* request =
CreateDiscoveryRequest(arena.ptr(), kLdsTypeUrl, version, nonce, error);
// Populate node.
if (populate_node) {
envoy_api_v2_core_Node* node_msg =
envoy_api_v2_DiscoveryRequest_mutable_node(request, arena.ptr());
PopulateNode(arena.ptr(), node_, build_version_, user_agent_name_, "",
node_msg);
}
// Add resource_name.
envoy_api_v2_DiscoveryRequest_add_resource_names(
request, upb_strview_make(server_name.data(), server_name.size()),
arena.ptr());
MaybeLogDiscoveryRequest(client_, tracer_, request);
return SerializeDiscoveryRequest(arena.ptr(), request);
}
grpc_slice XdsApi::CreateRdsRequest(const std::string& route_config_name,
const std::string& version,
const std::string& nonce, grpc_error* error,
bool populate_node) {
upb::Arena arena;
envoy_api_v2_DiscoveryRequest* request =
CreateDiscoveryRequest(arena.ptr(), kRdsTypeUrl, version, nonce, error);
// Populate node.
if (populate_node) {
envoy_api_v2_core_Node* node_msg =
envoy_api_v2_DiscoveryRequest_mutable_node(request, arena.ptr());
PopulateNode(arena.ptr(), node_, build_version_, user_agent_name_, "",
node_msg);
}
// Add resource_name.
envoy_api_v2_DiscoveryRequest_add_resource_names(
request,
upb_strview_make(route_config_name.data(), route_config_name.size()),
arena.ptr());
MaybeLogDiscoveryRequest(client_, tracer_, request);
return SerializeDiscoveryRequest(arena.ptr(), request);
}
grpc_slice XdsApi::CreateCdsRequest(const std::set<StringView>& cluster_names,
const std::string& version,
const std::string& nonce, grpc_error* error,
bool populate_node) {
upb::Arena arena;
envoy_api_v2_DiscoveryRequest* request =
CreateDiscoveryRequest(arena.ptr(), kCdsTypeUrl, version, nonce, error);
// Populate node.
if (populate_node) {
envoy_api_v2_core_Node* node_msg =
envoy_api_v2_DiscoveryRequest_mutable_node(request, arena.ptr());
PopulateNode(arena.ptr(), node_, build_version_, user_agent_name_, "",
node_msg);
}
// Add resource_names.
for (const auto& cluster_name : cluster_names) {
envoy_api_v2_DiscoveryRequest_add_resource_names(
request, upb_strview_make(cluster_name.data(), cluster_name.size()),
arena.ptr());
}
MaybeLogDiscoveryRequest(client_, tracer_, request);
return SerializeDiscoveryRequest(arena.ptr(), request);
}
grpc_slice XdsApi::CreateEdsRequest(
const std::set<StringView>& eds_service_names, const std::string& version,
const std::string& nonce, grpc_error* error, bool populate_node) {
upb::Arena arena;
envoy_api_v2_DiscoveryRequest* request =
CreateDiscoveryRequest(arena.ptr(), kEdsTypeUrl, version, nonce, error);
// Populate node.
if (populate_node) {
envoy_api_v2_core_Node* node_msg =
envoy_api_v2_DiscoveryRequest_mutable_node(request, arena.ptr());
PopulateNode(arena.ptr(), node_, build_version_, user_agent_name_, "",
node_msg);
}
// Add resource_names.
for (const auto& eds_service_name : eds_service_names) {
envoy_api_v2_DiscoveryRequest_add_resource_names(
request,
upb_strview_make(eds_service_name.data(), eds_service_name.size()),
arena.ptr());
}
MaybeLogDiscoveryRequest(client_, tracer_, request);
return SerializeDiscoveryRequest(arena.ptr(), request);
}
namespace {
void MaybeLogDiscoveryResponse(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryResponse* response) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// version_info
AddStringField("version_info",
envoy_api_v2_DiscoveryResponse_version_info(response),
&fields);
// resources
size_t num_resources;
envoy_api_v2_DiscoveryResponse_resources(response, &num_resources);
fields.emplace_back(
absl::StrCat("resources: <", num_resources, " element(s)>"));
// type_url
AddStringField("type_url",
envoy_api_v2_DiscoveryResponse_type_url(response), &fields);
// nonce
AddStringField("nonce", envoy_api_v2_DiscoveryResponse_nonce(response),
&fields);
gpr_log(GPR_DEBUG, "[xds_client %p] received response: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
void MaybeLogRouteConfiguration(
XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_RouteConfiguration* route_config) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// name
AddStringField("name", envoy_api_v2_RouteConfiguration_name(route_config),
&fields);
// virtual_hosts
size_t num_virtual_hosts;
const envoy_api_v2_route_VirtualHost* const* virtual_hosts =
envoy_api_v2_RouteConfiguration_virtual_hosts(route_config,
&num_virtual_hosts);
for (size_t i = 0; i < num_virtual_hosts; ++i) {
const auto* virtual_host = virtual_hosts[i];
fields.push_back("virtual_hosts {");
// name
AddStringField(
" name", envoy_api_v2_route_VirtualHost_name(virtual_host), &fields);
// domains
size_t num_domains;
const upb_strview* const domains =
envoy_api_v2_route_VirtualHost_domains(virtual_host, &num_domains);
for (size_t j = 0; j < num_domains; ++j) {
AddStringField(" domains", domains[j], &fields);
}
// routes
size_t num_routes;
const envoy_api_v2_route_Route* const* routes =
envoy_api_v2_route_VirtualHost_routes(virtual_host, &num_routes);
for (size_t j = 0; j < num_routes; ++j) {
const auto* route = routes[j];
fields.push_back(" route {");
// name
AddStringField(" name", envoy_api_v2_route_Route_name(route),
&fields);
// match
const envoy_api_v2_route_RouteMatch* match =
envoy_api_v2_route_Route_match(route);
if (match != nullptr) {
fields.emplace_back(" match {");
// path matching
if (envoy_api_v2_route_RouteMatch_has_prefix(match)) {
AddStringField(" prefix",
envoy_api_v2_route_RouteMatch_prefix(match), &fields,
/*add_if_empty=*/true);
} else if (envoy_api_v2_route_RouteMatch_has_path(match)) {
AddStringField(" path",
envoy_api_v2_route_RouteMatch_path(match), &fields,
/*add_if_empty=*/true);
} else if (envoy_api_v2_route_RouteMatch_has_regex(match)) {
AddStringField(" regex",
envoy_api_v2_route_RouteMatch_regex(match), &fields,
/*add_if_empty=*/true);
} else if (envoy_api_v2_route_RouteMatch_has_safe_regex(match)) {
fields.emplace_back(" safe_regex: <not printed>");
} else {
fields.emplace_back(" <unknown path matching type>");
}
// header matching
size_t num_headers;
envoy_api_v2_route_RouteMatch_headers(match, &num_headers);
if (num_headers > 0) {
fields.emplace_back(
absl::StrCat(" headers: <", num_headers, " element(s)>"));
}
fields.emplace_back(" }");
}
// action
if (envoy_api_v2_route_Route_has_route(route)) {
const envoy_api_v2_route_RouteAction* action =
envoy_api_v2_route_Route_route(route);
fields.emplace_back(" route {");
if (envoy_api_v2_route_RouteAction_has_cluster(action)) {
AddStringField(" cluster",
envoy_api_v2_route_RouteAction_cluster(action),
&fields);
} else if (envoy_api_v2_route_RouteAction_has_cluster_header(
action)) {
AddStringField(
" cluster_header",
envoy_api_v2_route_RouteAction_cluster_header(action), &fields);
} else if (envoy_api_v2_route_RouteAction_has_weighted_clusters(
action)) {
fields.emplace_back(" weighted_clusters: <not printed>");
}
fields.emplace_back(" }");
} else if (envoy_api_v2_route_Route_has_redirect(route)) {
fields.emplace_back(" redirect: <not printed>");
} else if (envoy_api_v2_route_Route_has_direct_response(route)) {
fields.emplace_back(" direct_response: <not printed>");
} else if (envoy_api_v2_route_Route_has_filter_action(route)) {
fields.emplace_back(" filter_action: <not printed>");
}
fields.push_back(" }");
}
fields.push_back("}");
}
gpr_log(GPR_DEBUG, "[xds_client %p] RouteConfiguration: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
void MaybeLogCluster(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_Cluster* cluster) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// name
AddStringField("name", envoy_api_v2_Cluster_name(cluster), &fields);
// type
if (envoy_api_v2_Cluster_has_type(cluster)) {
fields.emplace_back(
absl::StrCat("type: ", envoy_api_v2_Cluster_type(cluster)));
} else if (envoy_api_v2_Cluster_has_cluster_type(cluster)) {
fields.emplace_back("cluster_type: <not printed>");
} else {
fields.emplace_back("<unknown type>");
}
// eds_cluster_config
const envoy_api_v2_Cluster_EdsClusterConfig* eds_cluster_config =
envoy_api_v2_Cluster_eds_cluster_config(cluster);
if (eds_cluster_config != nullptr) {
fields.emplace_back("eds_cluster_config {");
// eds_config
const struct envoy_api_v2_core_ConfigSource* eds_config =
envoy_api_v2_Cluster_EdsClusterConfig_eds_config(eds_cluster_config);
if (eds_config != nullptr) {
if (envoy_api_v2_core_ConfigSource_has_ads(eds_config)) {
fields.emplace_back(" eds_config { ads {} }");
} else {
fields.emplace_back(" eds_config: <non-ADS type>");
}
}
// service_name
AddStringField(" service_name",
envoy_api_v2_Cluster_EdsClusterConfig_service_name(
eds_cluster_config),
&fields);
fields.emplace_back("}");
}
// lb_policy
fields.emplace_back(
absl::StrCat("lb_policy: ", envoy_api_v2_Cluster_lb_policy(cluster)));
// lrs_server
const envoy_api_v2_core_ConfigSource* lrs_server =
envoy_api_v2_Cluster_lrs_server(cluster);
if (lrs_server != nullptr) {
if (envoy_api_v2_core_ConfigSource_has_self(lrs_server)) {
fields.emplace_back("lrs_server { self {} }");
} else {
fields.emplace_back("lrs_server: <non-self type>");
}
}
gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
void MaybeLogClusterLoadAssignment(
XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_ClusterLoadAssignment* cla) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// cluster_name
AddStringField("cluster_name",
envoy_api_v2_ClusterLoadAssignment_cluster_name(cla),
&fields);
// endpoints
size_t num_localities;
const struct envoy_api_v2_endpoint_LocalityLbEndpoints* const*
locality_endpoints =
envoy_api_v2_ClusterLoadAssignment_endpoints(cla, &num_localities);
for (size_t i = 0; i < num_localities; ++i) {
const auto* locality_endpoint = locality_endpoints[i];
fields.emplace_back("endpoints {");
// locality
const auto* locality =
envoy_api_v2_endpoint_LocalityLbEndpoints_locality(locality_endpoint);
if (locality != nullptr) {
fields.emplace_back(" locality {");
AddLocalityField(2, locality, &fields);
fields.emplace_back(" }");
}
// lb_endpoints
size_t num_lb_endpoints;
const envoy_api_v2_endpoint_LbEndpoint* const* lb_endpoints =
envoy_api_v2_endpoint_LocalityLbEndpoints_lb_endpoints(
locality_endpoint, &num_lb_endpoints);
for (size_t j = 0; j < num_lb_endpoints; ++j) {
const auto* lb_endpoint = lb_endpoints[j];
fields.emplace_back(" lb_endpoints {");
// health_status
uint32_t health_status =
envoy_api_v2_endpoint_LbEndpoint_health_status(lb_endpoint);
if (health_status > 0) {
fields.emplace_back(
absl::StrCat(" health_status: ", health_status));
}
// endpoint
const envoy_api_v2_endpoint_Endpoint* endpoint =
envoy_api_v2_endpoint_LbEndpoint_endpoint(lb_endpoint);
if (endpoint != nullptr) {
fields.emplace_back(" endpoint {");
// address
const auto* address =
envoy_api_v2_endpoint_Endpoint_address(endpoint);
if (address != nullptr) {
fields.emplace_back(" address {");
// socket_address
const auto* socket_address =
envoy_api_v2_core_Address_socket_address(address);
if (socket_address != nullptr) {
fields.emplace_back(" socket_address {");
// address
AddStringField(
" address",
envoy_api_v2_core_SocketAddress_address(socket_address),
&fields);
// port_value
if (envoy_api_v2_core_SocketAddress_has_port_value(
socket_address)) {
fields.emplace_back(
absl::StrCat(" port_value: ",
envoy_api_v2_core_SocketAddress_port_value(
socket_address)));
} else {
fields.emplace_back(" <non-numeric port>");
}
fields.emplace_back(" }");
} else {
fields.emplace_back(" <non-socket address>");
}
fields.emplace_back(" }");
}
fields.emplace_back(" }");
}
fields.emplace_back(" }");
}
// load_balancing_weight
const google_protobuf_UInt32Value* lb_weight =
envoy_api_v2_endpoint_LocalityLbEndpoints_load_balancing_weight(
locality_endpoint);
if (lb_weight != nullptr) {
fields.emplace_back(
absl::StrCat(" load_balancing_weight { value: ",
google_protobuf_UInt32Value_value(lb_weight), " }"));
}
// priority
uint32_t priority =
envoy_api_v2_endpoint_LocalityLbEndpoints_priority(locality_endpoint);
if (priority > 0) {
fields.emplace_back(absl::StrCat(" priority: ", priority));
}
fields.emplace_back("}");
}
// policy
const envoy_api_v2_ClusterLoadAssignment_Policy* policy =
envoy_api_v2_ClusterLoadAssignment_policy(cla);
if (policy != nullptr) {
fields.emplace_back("policy {");
// drop_overloads
size_t num_drop_overloads;
const envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload* const*
drop_overloads =
envoy_api_v2_ClusterLoadAssignment_Policy_drop_overloads(
policy, &num_drop_overloads);
for (size_t i = 0; i < num_drop_overloads; ++i) {
auto* drop_overload = drop_overloads[i];
fields.emplace_back(" drop_overloads {");
// category
AddStringField(
" category",
envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload_category(
drop_overload),
&fields);
// drop_percentage
const auto* drop_percentage =
envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload_drop_percentage(
drop_overload);
if (drop_percentage != nullptr) {
fields.emplace_back(" drop_percentage {");
fields.emplace_back(absl::StrCat(
" numerator: ",
envoy_type_FractionalPercent_numerator(drop_percentage)));
fields.emplace_back(absl::StrCat(
" denominator: ",
envoy_type_FractionalPercent_denominator(drop_percentage)));
fields.emplace_back(" }");
}
fields.emplace_back(" }");
}
// overprovisioning_factor
fields.emplace_back("}");
}
gpr_log(GPR_DEBUG, "[xds_client %p] ClusterLoadAssignment: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
// 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, std::string domain_pattern,
std::string expected_host_name) {
// Normalize the args to lower-case. Domain matching is case-insensitive.
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;
StringView pattern_suffix(domain_pattern.c_str() + 1);
StringView 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;
StringView pattern_prefix(domain_pattern.c_str(),
domain_pattern.size() - 1);
StringView 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;
}
grpc_error* RouteConfigParse(
XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_RouteConfiguration* route_config,
const std::string& expected_server_name, const bool xds_routing_enabled,
XdsApi::RdsUpdate* rds_update) {
MaybeLogRouteConfiguration(client, tracer, route_config);
// Get the virtual hosts.
size_t size;
const envoy_api_v2_route_VirtualHost* const* virtual_hosts =
envoy_api_v2_RouteConfiguration_virtual_hosts(route_config, &size);
// 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.
const envoy_api_v2_route_VirtualHost* target_virtual_host = 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 (size_t i = 0; i < size; ++i) {
size_t domain_size;
upb_strview const* domains =
envoy_api_v2_route_VirtualHost_domains(virtual_hosts[i], &domain_size);
for (size_t j = 0; j < domain_size; ++j) {
const std::string domain_pattern(domains[j].data, domains[j].size);
// Check the match type first. Skip the pattern if it's not better than
// current match.
const MatchType match_type = DomainPatternMatchType(domain_pattern);
if (match_type == INVALID_MATCH) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid domain pattern.");
}
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, expected_server_name)) {
continue;
}
// Choose this match.
target_virtual_host = virtual_hosts[i];
best_match_type = match_type;
longest_match = domain_pattern.size();
if (best_match_type == EXACT_MATCH) break;
}
if (best_match_type == EXACT_MATCH) break;
}
if (target_virtual_host == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No matched virtual host found in the route config.");
}
// Get the route list from the matched virtual host.
const envoy_api_v2_route_Route* const* routes =
envoy_api_v2_route_VirtualHost_routes(target_virtual_host, &size);
if (size < 1) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No route found in the virtual host.");
}
// If xds_routing is not configured, only look at the last one in the route
// list (the default route)
size_t start_index = xds_routing_enabled ? 0 : size - 1;
for (size_t i = start_index; i < size; ++i) {
const envoy_api_v2_route_Route* route = routes[i];
const envoy_api_v2_route_RouteMatch* match =
envoy_api_v2_route_Route_match(route);
XdsApi::RdsRoute rds_route;
if (envoy_api_v2_route_RouteMatch_has_prefix(match)) {
upb_strview prefix = envoy_api_v2_route_RouteMatch_prefix(match);
// Empty prefix "" is accepted.
if (prefix.size > 0) {
// Prefix "/" is accepted.
if (prefix.data[0] != '/') {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Prefix does not start with a /");
}
if (prefix.size > 1) {
std::vector<absl::string_view> prefix_elements = absl::StrSplit(
absl::string_view(prefix.data, prefix.size).substr(1),
absl::MaxSplits('/', 1));
if (prefix_elements.size() != 2) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Prefix not in the required format of /service/");
} else if (!prefix_elements[1].empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Prefix does not end with a /");
} else if (prefix_elements[0].empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Prefix contains empty service name");
}
rds_route.service = std::string(prefix_elements[0]);
}
}
} else if (envoy_api_v2_route_RouteMatch_has_path(match)) {
upb_strview path = envoy_api_v2_route_RouteMatch_path(match);
if (path.size == 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Path if set cannot be empty");
}
if (path.data[0] != '/') {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Path does not start with a /");
}
std::vector<absl::string_view> path_elements = absl::StrSplit(
absl::string_view(path.data, path.size).substr(1), '/');
if (path_elements.size() != 2) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Path not in the required format of /service/method");
} else if (path_elements[0].empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Path contains empty service name");
} else if (path_elements[1].empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Path contains empty method name");
}
rds_route.service = std::string(path_elements[0]);
rds_route.method = std::string(path_elements[1]);
} else {
// TODO(donnadionne): We may change this behavior once we decide how to
// handle unsupported fields.
continue;
}
if (!envoy_api_v2_route_Route_has_route(route)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No RouteAction found in route.");
}
const envoy_api_v2_route_RouteAction* route_action =
envoy_api_v2_route_Route_route(route);
// Get the cluster in the RouteAction.
if (!envoy_api_v2_route_RouteAction_has_cluster(route_action)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"No cluster found in RouteAction.");
}
const upb_strview action =
envoy_api_v2_route_RouteAction_cluster(route_action);
if (action.size == 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"RouteAction contains empty cluster.");
}
rds_route.cluster_name = std::string(action.data, action.size);
rds_update->routes.emplace_back(std::move(rds_route));
}
if (rds_update->routes.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("No valid routes specified.");
} else {
if (!rds_update->routes.back().service.empty() ||
!rds_update->routes.back().method.empty()) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Default route must have empty service and method");
}
}
return GRPC_ERROR_NONE;
}
grpc_error* LdsResponseParse(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryResponse* response,
const std::string& expected_server_name,
const bool xds_routing_enabled,
absl::optional<XdsApi::LdsUpdate>* lds_update,
upb_arena* arena) {
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_api_v2_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
const upb_strview type_url = google_protobuf_Any_type_url(resources[i]);
if (!upb_strview_eql(type_url, upb_strview_makez(XdsApi::kLdsTypeUrl))) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not LDS.");
}
// Decode the listener.
const upb_strview encoded_listener =
google_protobuf_Any_value(resources[i]);
const envoy_api_v2_Listener* listener = envoy_api_v2_Listener_parse(
encoded_listener.data, encoded_listener.size, arena);
if (listener == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode listener.");
}
// Check listener name. Ignore unexpected listeners.
const upb_strview name = envoy_api_v2_Listener_name(listener);
const upb_strview expected_name =
upb_strview_makez(expected_server_name.c_str());
if (!upb_strview_eql(name, expected_name)) continue;
// Get api_listener and decode it to http_connection_manager.
const envoy_config_listener_v2_ApiListener* api_listener =
envoy_api_v2_Listener_api_listener(listener);
if (api_listener == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Listener doesn't have ApiListener.");
}
const upb_strview encoded_api_listener = google_protobuf_Any_value(
envoy_config_listener_v2_ApiListener_api_listener(api_listener));
const envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager*
http_connection_manager =
envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_parse(
encoded_api_listener.data, encoded_api_listener.size, arena);
// Found inlined route_config. Parse it to find the cluster_name.
if (envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_has_route_config(
http_connection_manager)) {
const envoy_api_v2_RouteConfiguration* route_config =
envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_route_config(
http_connection_manager);
XdsApi::RdsUpdate rds_update;
grpc_error* error =
RouteConfigParse(client, tracer, route_config, expected_server_name,
xds_routing_enabled, &rds_update);
if (error != GRPC_ERROR_NONE) return error;
lds_update->emplace();
(*lds_update)->rds_update.emplace(std::move(rds_update));
return GRPC_ERROR_NONE;
}
// Validate that RDS must be used to get the route_config dynamically.
if (!envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_has_rds(
http_connection_manager)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"HttpConnectionManager neither has inlined route_config nor RDS.");
}
// Get the route_config_name.
const envoy_config_filter_network_http_connection_manager_v2_Rds* rds =
envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_rds(
http_connection_manager);
const upb_strview route_config_name =
envoy_config_filter_network_http_connection_manager_v2_Rds_route_config_name(
rds);
lds_update->emplace();
(*lds_update)->route_config_name =
std::string(route_config_name.data, route_config_name.size);
return GRPC_ERROR_NONE;
}
return GRPC_ERROR_NONE;
}
grpc_error* RdsResponseParse(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryResponse* response,
const std::string& expected_server_name,
const std::string& expected_route_config_name,
const bool xds_routing_enabled,
absl::optional<XdsApi::RdsUpdate>* rds_update,
upb_arena* arena) {
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_api_v2_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
// Check the type_url of the resource.
const upb_strview type_url = google_protobuf_Any_type_url(resources[i]);
if (!upb_strview_eql(type_url, upb_strview_makez(XdsApi::kRdsTypeUrl))) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not RDS.");
}
// Decode the route_config.
const upb_strview encoded_route_config =
google_protobuf_Any_value(resources[i]);
const envoy_api_v2_RouteConfiguration* route_config =
envoy_api_v2_RouteConfiguration_parse(encoded_route_config.data,
encoded_route_config.size, arena);
if (route_config == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode route_config.");
}
// Check route_config_name. Ignore unexpected route_config.
const upb_strview name = envoy_api_v2_RouteConfiguration_name(route_config);
const upb_strview expected_name =
upb_strview_makez(expected_route_config_name.c_str());
if (!upb_strview_eql(name, expected_name)) continue;
// Parse the route_config.
XdsApi::RdsUpdate local_rds_update;
grpc_error* error =
RouteConfigParse(client, tracer, route_config, expected_server_name,
xds_routing_enabled, &local_rds_update);
if (error != GRPC_ERROR_NONE) return error;
rds_update->emplace(std::move(local_rds_update));
return GRPC_ERROR_NONE;
}
return GRPC_ERROR_NONE;
}
grpc_error* CdsResponseParse(XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryResponse* response,
const std::set<StringView>& expected_cluster_names,
XdsApi::CdsUpdateMap* cds_update_map,
upb_arena* arena) {
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_api_v2_DiscoveryResponse_resources(response, &size);
// Parse all the resources in the CDS response.
for (size_t i = 0; i < size; ++i) {
XdsApi::CdsUpdate cds_update;
// Check the type_url of the resource.
const upb_strview type_url = google_protobuf_Any_type_url(resources[i]);
if (!upb_strview_eql(type_url, upb_strview_makez(XdsApi::kCdsTypeUrl))) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not CDS.");
}
// Decode the cluster.
const upb_strview encoded_cluster = google_protobuf_Any_value(resources[i]);
const envoy_api_v2_Cluster* cluster = envoy_api_v2_Cluster_parse(
encoded_cluster.data, encoded_cluster.size, arena);
if (cluster == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Can't decode cluster.");
}
MaybeLogCluster(client, tracer, cluster);
// Ignore unexpected cluster names.
upb_strview cluster_name = envoy_api_v2_Cluster_name(cluster);
StringView cluster_name_strview(cluster_name.data, cluster_name.size);
if (expected_cluster_names.find(cluster_name_strview) ==
expected_cluster_names.end()) {
continue;
}
// Check the cluster_discovery_type.
if (!envoy_api_v2_Cluster_has_type(cluster)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType not found.");
}
if (envoy_api_v2_Cluster_type(cluster) != envoy_api_v2_Cluster_EDS) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("DiscoveryType is not EDS.");
}
// Check the EDS config source.
const envoy_api_v2_Cluster_EdsClusterConfig* eds_cluster_config =
envoy_api_v2_Cluster_eds_cluster_config(cluster);
const envoy_api_v2_core_ConfigSource* eds_config =
envoy_api_v2_Cluster_EdsClusterConfig_eds_config(eds_cluster_config);
if (!envoy_api_v2_core_ConfigSource_has_ads(eds_config)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("ConfigSource is not ADS.");
}
// Record EDS service_name (if any).
upb_strview service_name =
envoy_api_v2_Cluster_EdsClusterConfig_service_name(eds_cluster_config);
if (service_name.size != 0) {
cds_update.eds_service_name =
std::string(service_name.data, service_name.size);
}
// Check the LB policy.
if (envoy_api_v2_Cluster_lb_policy(cluster) !=
envoy_api_v2_Cluster_ROUND_ROBIN) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"LB policy is not ROUND_ROBIN.");
}
// Record LRS server name (if any).
const envoy_api_v2_core_ConfigSource* lrs_server =
envoy_api_v2_Cluster_lrs_server(cluster);
if (lrs_server != nullptr) {
if (!envoy_api_v2_core_ConfigSource_has_self(lrs_server)) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"ConfigSource is not self.");
}
cds_update.lrs_load_reporting_server_name.emplace("");
}
cds_update_map->emplace(std::string(cluster_name.data, cluster_name.size),
std::move(cds_update));
}
return GRPC_ERROR_NONE;
}
grpc_error* ServerAddressParseAndAppend(
const envoy_api_v2_endpoint_LbEndpoint* lb_endpoint,
ServerAddressList* list) {
// If health_status is not HEALTHY or UNKNOWN, skip this endpoint.
const int32_t health_status =
envoy_api_v2_endpoint_LbEndpoint_health_status(lb_endpoint);
if (health_status != envoy_api_v2_core_UNKNOWN &&
health_status != envoy_api_v2_core_HEALTHY) {
return GRPC_ERROR_NONE;
}
// Find the ip:port.
const envoy_api_v2_endpoint_Endpoint* endpoint =
envoy_api_v2_endpoint_LbEndpoint_endpoint(lb_endpoint);
const envoy_api_v2_core_Address* address =
envoy_api_v2_endpoint_Endpoint_address(endpoint);
const envoy_api_v2_core_SocketAddress* socket_address =
envoy_api_v2_core_Address_socket_address(address);
upb_strview address_strview =
envoy_api_v2_core_SocketAddress_address(socket_address);
uint32_t port = envoy_api_v2_core_SocketAddress_port_value(socket_address);
if (GPR_UNLIKELY(port >> 16) != 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid port.");
}
// Populate grpc_resolved_address.
grpc_resolved_address addr;
char* address_str = static_cast<char*>(gpr_malloc(address_strview.size + 1));
memcpy(address_str, address_strview.data, address_strview.size);
address_str[address_strview.size] = '\0';
grpc_string_to_sockaddr(&addr, address_str, port);
gpr_free(address_str);
// Append the address to the list.
list->emplace_back(addr, nullptr);
return GRPC_ERROR_NONE;
}
grpc_error* LocalityParse(
const envoy_api_v2_endpoint_LocalityLbEndpoints* locality_lb_endpoints,
XdsApi::PriorityListUpdate::LocalityMap::Locality* output_locality) {
// Parse LB weight.
const google_protobuf_UInt32Value* lb_weight =
envoy_api_v2_endpoint_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_api_v2_core_Locality* locality =
envoy_api_v2_endpoint_LocalityLbEndpoints_locality(locality_lb_endpoints);
upb_strview region = envoy_api_v2_core_Locality_region(locality);
upb_strview zone = envoy_api_v2_core_Locality_region(locality);
upb_strview sub_zone = envoy_api_v2_core_Locality_sub_zone(locality);
output_locality->name = MakeRefCounted<XdsLocalityName>(
std::string(region.data, region.size), std::string(zone.data, zone.size),
std::string(sub_zone.data, sub_zone.size));
// Parse the addresses.
size_t size;
const envoy_api_v2_endpoint_LbEndpoint* const* lb_endpoints =
envoy_api_v2_endpoint_LocalityLbEndpoints_lb_endpoints(
locality_lb_endpoints, &size);
for (size_t i = 0; i < size; ++i) {
grpc_error* error = ServerAddressParseAndAppend(
lb_endpoints[i], &output_locality->serverlist);
if (error != GRPC_ERROR_NONE) return error;
}
// Parse the priority.
output_locality->priority =
envoy_api_v2_endpoint_LocalityLbEndpoints_priority(locality_lb_endpoints);
return GRPC_ERROR_NONE;
}
grpc_error* DropParseAndAppend(
const envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload* drop_overload,
XdsApi::DropConfig* drop_config) {
// Get the category.
upb_strview category =
envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload_category(
drop_overload);
if (category.size == 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty drop category name");
}
// Get the drop rate (per million).
const envoy_type_FractionalPercent* drop_percentage =
envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload_drop_percentage(
drop_overload);
uint32_t numerator = envoy_type_FractionalPercent_numerator(drop_percentage);
const auto denominator =
static_cast<envoy_type_FractionalPercent_DenominatorType>(
envoy_type_FractionalPercent_denominator(drop_percentage));
// Normalize to million.
switch (denominator) {
case envoy_type_FractionalPercent_HUNDRED:
numerator *= 10000;
break;
case envoy_type_FractionalPercent_TEN_THOUSAND:
numerator *= 100;
break;
case envoy_type_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::string(category.data, category.size),
numerator);
return GRPC_ERROR_NONE;
}
grpc_error* EdsResponseParse(
XdsClient* client, TraceFlag* tracer,
const envoy_api_v2_DiscoveryResponse* response,
const std::set<StringView>& expected_eds_service_names,
XdsApi::EdsUpdateMap* eds_update_map, upb_arena* arena) {
// Get the resources from the response.
size_t size;
const google_protobuf_Any* const* resources =
envoy_api_v2_DiscoveryResponse_resources(response, &size);
for (size_t i = 0; i < size; ++i) {
XdsApi::EdsUpdate eds_update;
// Check the type_url of the resource.
upb_strview type_url = google_protobuf_Any_type_url(resources[i]);
if (!upb_strview_eql(type_url, upb_strview_makez(XdsApi::kEdsTypeUrl))) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING("Resource is not EDS.");
}
// Get the cluster_load_assignment.
upb_strview encoded_cluster_load_assignment =
google_protobuf_Any_value(resources[i]);
envoy_api_v2_ClusterLoadAssignment* cluster_load_assignment =
envoy_api_v2_ClusterLoadAssignment_parse(
encoded_cluster_load_assignment.data,
encoded_cluster_load_assignment.size, arena);
if (cluster_load_assignment == nullptr) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Can't parse cluster_load_assignment.");
}
MaybeLogClusterLoadAssignment(client, tracer, cluster_load_assignment);
// Check the cluster name (which actually means eds_service_name). Ignore
// unexpected names.
upb_strview cluster_name = envoy_api_v2_ClusterLoadAssignment_cluster_name(
cluster_load_assignment);
StringView cluster_name_strview(cluster_name.data, cluster_name.size);
if (expected_eds_service_names.find(cluster_name_strview) ==
expected_eds_service_names.end()) {
continue;
}
// Get the endpoints.
size_t locality_size;
const envoy_api_v2_endpoint_LocalityLbEndpoints* const* endpoints =
envoy_api_v2_ClusterLoadAssignment_endpoints(cluster_load_assignment,
&locality_size);
for (size_t j = 0; j < locality_size; ++j) {
XdsApi::PriorityListUpdate::LocalityMap::Locality locality;
grpc_error* error = LocalityParse(endpoints[j], &locality);
if (error != GRPC_ERROR_NONE) return error;
// Filter out locality with weight 0.
if (locality.lb_weight == 0) continue;
eds_update.priority_list_update.Add(locality);
}
for (uint32_t priority = 0;
priority < eds_update.priority_list_update.size(); ++priority) {
auto* locality_map = eds_update.priority_list_update.Find(priority);
if (locality_map == nullptr || locality_map->size() == 0) {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"EDS update includes sparse priority list");
}
}
// Get the drop config.
eds_update.drop_config = MakeRefCounted<XdsApi::DropConfig>();
const envoy_api_v2_ClusterLoadAssignment_Policy* policy =
envoy_api_v2_ClusterLoadAssignment_policy(cluster_load_assignment);
if (policy != nullptr) {
size_t drop_size;
const envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload* const*
drop_overload =
envoy_api_v2_ClusterLoadAssignment_Policy_drop_overloads(
policy, &drop_size);
for (size_t j = 0; j < drop_size; ++j) {
grpc_error* error =
DropParseAndAppend(drop_overload[j], eds_update.drop_config.get());
if (error != GRPC_ERROR_NONE) return error;
}
}
eds_update_map->emplace(std::string(cluster_name.data, cluster_name.size),
std::move(eds_update));
}
return GRPC_ERROR_NONE;
}
} // namespace
grpc_error* XdsApi::ParseAdsResponse(
const grpc_slice& encoded_response, const std::string& expected_server_name,
const std::string& expected_route_config_name,
const bool xds_routing_enabled,
const std::set<StringView>& expected_cluster_names,
const std::set<StringView>& expected_eds_service_names,
absl::optional<LdsUpdate>* lds_update,
absl::optional<RdsUpdate>* rds_update, CdsUpdateMap* cds_update_map,
EdsUpdateMap* eds_update_map, std::string* version, std::string* nonce,
std::string* type_url) {
upb::Arena arena;
// Decode the response.
const envoy_api_v2_DiscoveryResponse* response =
envoy_api_v2_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) {
*type_url = "";
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Can't decode the whole response.");
}
MaybeLogDiscoveryResponse(client_, tracer_, response);
// Record the type_url, the version_info, and the nonce of the response.
upb_strview type_url_strview =
envoy_api_v2_DiscoveryResponse_type_url(response);
*type_url = std::string(type_url_strview.data, type_url_strview.size);
upb_strview version_info =
envoy_api_v2_DiscoveryResponse_version_info(response);
*version = std::string(version_info.data, version_info.size);
upb_strview nonce_strview = envoy_api_v2_DiscoveryResponse_nonce(response);
*nonce = std::string(nonce_strview.data, nonce_strview.size);
// Parse the response according to the resource type.
if (*type_url == kLdsTypeUrl) {
return LdsResponseParse(client_, tracer_, response, expected_server_name,
xds_routing_enabled, lds_update, arena.ptr());
} else if (*type_url == kRdsTypeUrl) {
return RdsResponseParse(client_, tracer_, response, expected_server_name,
expected_route_config_name, xds_routing_enabled,
rds_update, arena.ptr());
} else if (*type_url == kCdsTypeUrl) {
return CdsResponseParse(client_, tracer_, response, expected_cluster_names,
cds_update_map, arena.ptr());
} else if (*type_url == kEdsTypeUrl) {
return EdsResponseParse(client_, tracer_, response,
expected_eds_service_names, eds_update_map,
arena.ptr());
} else {
return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Unsupported ADS resource type.");
}
}
namespace {
void MaybeLogLrsRequest(
XdsClient* client, TraceFlag* tracer,
const envoy_service_load_stats_v2_LoadStatsRequest* request) {
if (GRPC_TRACE_FLAG_ENABLED(*tracer) &&
gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
// TODO(roth): When we can upgrade upb, use upb textformat code to dump
// the raw proto instead of doing this manually.
std::vector<std::string> fields;
// node
const auto* node =
envoy_service_load_stats_v2_LoadStatsRequest_node(request);
if (node != nullptr) {
AddNodeLogFields(node, &fields);
}
// cluster_stats
size_t num_cluster_stats;
const struct envoy_api_v2_endpoint_ClusterStats* const* cluster_stats =
envoy_service_load_stats_v2_LoadStatsRequest_cluster_stats(
request, &num_cluster_stats);
for (size_t i = 0; i < num_cluster_stats; ++i) {
const auto* cluster_stat = cluster_stats[i];
fields.emplace_back("cluster_stats {");
// cluster_name
AddStringField(
" cluster_name",
envoy_api_v2_endpoint_ClusterStats_cluster_name(cluster_stat),
&fields);
// cluster_service_name
AddStringField(
" cluster_service_name",
envoy_api_v2_endpoint_ClusterStats_cluster_service_name(cluster_stat),
&fields);
// upstream_locality_stats
size_t num_stats;
const envoy_api_v2_endpoint_UpstreamLocalityStats* const* stats =
envoy_api_v2_endpoint_ClusterStats_upstream_locality_stats(
cluster_stat, &num_stats);
for (size_t j = 0; j < num_stats; ++j) {
const auto* stat = stats[j];
fields.emplace_back(" upstream_locality_stats {");
// locality
const auto* locality =
envoy_api_v2_endpoint_UpstreamLocalityStats_locality(stat);
if (locality != nullptr) {
fields.emplace_back(" locality {");
AddLocalityField(3, locality, &fields);
fields.emplace_back(" }");
}
// total_successful_requests
fields.emplace_back(absl::StrCat(
" total_successful_requests: ",
envoy_api_v2_endpoint_UpstreamLocalityStats_total_successful_requests(
stat)));
// total_requests_in_progress
fields.emplace_back(absl::StrCat(
" total_requests_in_progress: ",
envoy_api_v2_endpoint_UpstreamLocalityStats_total_requests_in_progress(
stat)));
// total_error_requests
fields.emplace_back(absl::StrCat(
" total_error_requests: ",
envoy_api_v2_endpoint_UpstreamLocalityStats_total_error_requests(
stat)));
// total_issued_requests
fields.emplace_back(absl::StrCat(
" total_issued_requests: ",
envoy_api_v2_endpoint_UpstreamLocalityStats_total_issued_requests(
stat)));
fields.emplace_back(" }");
}
// total_dropped_requests
fields.emplace_back(absl::StrCat(
" total_dropped_requests: ",
envoy_api_v2_endpoint_ClusterStats_total_dropped_requests(
cluster_stat)));
// dropped_requests
size_t num_drops;
const envoy_api_v2_endpoint_ClusterStats_DroppedRequests* const* drops =
envoy_api_v2_endpoint_ClusterStats_dropped_requests(cluster_stat,
&num_drops);
for (size_t j = 0; j < num_drops; ++j) {
const auto* drop = drops[j];
fields.emplace_back(" dropped_requests {");
// category
AddStringField(
" category",
envoy_api_v2_endpoint_ClusterStats_DroppedRequests_category(drop),
&fields);
// dropped_count
fields.emplace_back(absl::StrCat(
" dropped_count: ",
envoy_api_v2_endpoint_ClusterStats_DroppedRequests_dropped_count(
drop)));
fields.emplace_back(" }");
}
// load_report_interval
const auto* load_report_interval =
envoy_api_v2_endpoint_ClusterStats_load_report_interval(cluster_stat);
if (load_report_interval != nullptr) {
fields.emplace_back(" load_report_interval {");
fields.emplace_back(absl::StrCat(
" seconds: ",
google_protobuf_Duration_seconds(load_report_interval)));
fields.emplace_back(
absl::StrCat(" nanos: ",
google_protobuf_Duration_nanos(load_report_interval)));
fields.emplace_back(" }");
}
fields.emplace_back("}");
}
gpr_log(GPR_DEBUG, "[xds_client %p] constructed LRS request: %s", client,
absl::StrJoin(fields, "\n").c_str());
}
}
grpc_slice SerializeLrsRequest(
const envoy_service_load_stats_v2_LoadStatsRequest* request,
upb_arena* arena) {
size_t output_length;
char* output = envoy_service_load_stats_v2_LoadStatsRequest_serialize(
request, arena, &output_length);
return grpc_slice_from_copied_buffer(output, output_length);
}
} // namespace
grpc_slice XdsApi::CreateLrsInitialRequest(const std::string& server_name) {
upb::Arena arena;
// Create a request.
envoy_service_load_stats_v2_LoadStatsRequest* request =
envoy_service_load_stats_v2_LoadStatsRequest_new(arena.ptr());
// Populate node.
envoy_api_v2_core_Node* node_msg =
envoy_service_load_stats_v2_LoadStatsRequest_mutable_node(request,
arena.ptr());
PopulateNode(arena.ptr(), node_, build_version_, user_agent_name_,
server_name, node_msg);
MaybeLogLrsRequest(client_, tracer_, request);
return SerializeLrsRequest(request, arena.ptr());
}
namespace {
void LocalityStatsPopulate(envoy_api_v2_endpoint_UpstreamLocalityStats* output,
const XdsLocalityName& locality_name,
const XdsClusterLocalityStats::Snapshot& snapshot,
upb_arena* arena) {
// Set locality.
envoy_api_v2_core_Locality* locality =
envoy_api_v2_endpoint_UpstreamLocalityStats_mutable_locality(output,
arena);
if (!locality_name.region().empty()) {
envoy_api_v2_core_Locality_set_region(
locality, upb_strview_makez(locality_name.region().c_str()));
}
if (!locality_name.zone().empty()) {
envoy_api_v2_core_Locality_set_zone(
locality, upb_strview_makez(locality_name.zone().c_str()));
}
if (!locality_name.sub_zone().empty()) {
envoy_api_v2_core_Locality_set_sub_zone(
locality, upb_strview_makez(locality_name.sub_zone().c_str()));
}
// Set total counts.
envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_successful_requests(
output, snapshot.total_successful_requests);
envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_requests_in_progress(
output, snapshot.total_requests_in_progress);
envoy_api_v2_endpoint_UpstreamLocalityStats_set_total_error_requests(
output, snapshot.total_error_requests);
envoy_api_v2_endpoint_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_api_v2_endpoint_EndpointLoadMetricStats* load_metric =
envoy_api_v2_endpoint_UpstreamLocalityStats_add_load_metric_stats(
output, arena);
envoy_api_v2_endpoint_EndpointLoadMetricStats_set_metric_name(
load_metric, upb_strview_make(metric_name.data(), metric_name.size()));
envoy_api_v2_endpoint_EndpointLoadMetricStats_set_num_requests_finished_with_metric(
load_metric, metric_value.num_requests_finished_with_metric);
envoy_api_v2_endpoint_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;
// Create a request.
envoy_service_load_stats_v2_LoadStatsRequest* request =
envoy_service_load_stats_v2_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_api_v2_endpoint_ClusterStats* cluster_stats =
envoy_service_load_stats_v2_LoadStatsRequest_add_cluster_stats(
request, arena.ptr());
// Set the cluster name.
envoy_api_v2_endpoint_ClusterStats_set_cluster_name(
cluster_stats,
upb_strview_make(cluster_name.data(), cluster_name.size()));
// Set EDS service name, if non-empty.
if (!eds_service_name.empty()) {
envoy_api_v2_endpoint_ClusterStats_set_cluster_service_name(
cluster_stats,
upb_strview_make(eds_service_name.data(), eds_service_name.size()));
}
// Add locality stats.
for (const auto& p : load_report.locality_stats) {
const XdsLocalityName& locality_name = *p.first;
const auto& snapshot = p.second;
envoy_api_v2_endpoint_UpstreamLocalityStats* locality_stats =
envoy_api_v2_endpoint_ClusterStats_add_upstream_locality_stats(
cluster_stats, arena.ptr());
LocalityStatsPopulate(locality_stats, locality_name, snapshot,
arena.ptr());
}
// Add dropped requests.
uint64_t total_dropped_requests = 0;
for (const auto& p : load_report.dropped_requests) {
const char* category = p.first.c_str();
const uint64_t count = p.second;
envoy_api_v2_endpoint_ClusterStats_DroppedRequests* dropped_requests =
envoy_api_v2_endpoint_ClusterStats_add_dropped_requests(cluster_stats,
arena.ptr());
envoy_api_v2_endpoint_ClusterStats_DroppedRequests_set_category(
dropped_requests, upb_strview_makez(category));
envoy_api_v2_endpoint_ClusterStats_DroppedRequests_set_dropped_count(
dropped_requests, count);
total_dropped_requests += count;
}
// Set total dropped requests.
envoy_api_v2_endpoint_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_api_v2_endpoint_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(client_, tracer_, request);
return SerializeLrsRequest(request, arena.ptr());
}
grpc_error* XdsApi::ParseLrsResponse(const grpc_slice& encoded_response,
std::set<std::string>* cluster_names,
grpc_millis* load_reporting_interval) {
upb::Arena arena;
// Decode the response.
const envoy_service_load_stats_v2_LoadStatsResponse* decoded_response =
envoy_service_load_stats_v2_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.");
}
// Store the cluster names.
size_t size;
const upb_strview* clusters =
envoy_service_load_stats_v2_LoadStatsResponse_clusters(decoded_response,
&size);
for (size_t i = 0; i < size; ++i) {
cluster_names->emplace(clusters[i].data, clusters[i].size);
}
// Get the load report interval.
const google_protobuf_Duration* load_reporting_interval_duration =
envoy_service_load_stats_v2_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 grpc_core