1 //
2 // Copyright 2018 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include <grpc/support/port_platform.h>
18 
19 #include "src/core/ext/xds/xds_api.h"
20 
21 #include <stdint.h>
22 #include <stdlib.h>
23 
24 #include <algorithm>
25 #include <set>
26 #include <string>
27 #include <vector>
28 
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/strip.h"
31 #include "envoy/config/core/v3/base.upb.h"
32 #include "envoy/config/endpoint/v3/load_report.upb.h"
33 #include "envoy/service/discovery/v3/discovery.upb.h"
34 #include "envoy/service/discovery/v3/discovery.upbdefs.h"
35 #include "envoy/service/load_stats/v3/lrs.upb.h"
36 #include "envoy/service/load_stats/v3/lrs.upbdefs.h"
37 #include "envoy/service/status/v3/csds.upb.h"
38 #include "google/protobuf/any.upb.h"
39 #include "google/protobuf/duration.upb.h"
40 #include "google/protobuf/struct.upb.h"
41 #include "google/protobuf/timestamp.upb.h"
42 #include "google/rpc/status.upb.h"
43 #include "upb/base/string_view.h"
44 #include "upb/reflection/def.h"
45 #include "upb/text/encode.h"
46 #include "upb/upb.hpp"
47 
48 #include <grpc/status.h>
49 #include <grpc/support/log.h>
50 #include <grpc/support/time.h>
51 
52 #include "src/core/ext/xds/upb_utils.h"
53 #include "src/core/ext/xds/xds_client.h"
54 #include "src/core/lib/json/json.h"
55 
56 // IWYU pragma: no_include "upb/msg_internal.h"
57 
58 namespace grpc_core {
59 
XdsApi(XdsClient * client,TraceFlag * tracer,const XdsBootstrap::Node * node,upb::SymbolTable * symtab,std::string user_agent_name,std::string user_agent_version)60 XdsApi::XdsApi(XdsClient* client, TraceFlag* tracer,
61                const XdsBootstrap::Node* node, upb::SymbolTable* symtab,
62                std::string user_agent_name, std::string user_agent_version)
63     : client_(client),
64       tracer_(tracer),
65       node_(node),
66       symtab_(symtab),
67       user_agent_name_(std::move(user_agent_name)),
68       user_agent_version_(std::move(user_agent_version)) {}
69 
70 namespace {
71 
72 struct XdsApiContext {
73   XdsClient* client;
74   TraceFlag* tracer;
75   upb_DefPool* symtab;
76   upb_Arena* arena;
77 };
78 
79 void PopulateMetadataValue(const XdsApiContext& context,
80                            google_protobuf_Value* value_pb, const Json& value);
81 
PopulateListValue(const XdsApiContext & context,google_protobuf_ListValue * list_value,const Json::Array & values)82 void PopulateListValue(const XdsApiContext& context,
83                        google_protobuf_ListValue* list_value,
84                        const Json::Array& values) {
85   for (const auto& value : values) {
86     auto* value_pb =
87         google_protobuf_ListValue_add_values(list_value, context.arena);
88     PopulateMetadataValue(context, value_pb, value);
89   }
90 }
91 
PopulateMetadata(const XdsApiContext & context,google_protobuf_Struct * metadata_pb,const Json::Object & metadata)92 void PopulateMetadata(const XdsApiContext& context,
93                       google_protobuf_Struct* metadata_pb,
94                       const Json::Object& metadata) {
95   for (const auto& p : metadata) {
96     google_protobuf_Value* value = google_protobuf_Value_new(context.arena);
97     PopulateMetadataValue(context, value, p.second);
98     google_protobuf_Struct_fields_set(
99         metadata_pb, StdStringToUpbString(p.first), value, context.arena);
100   }
101 }
102 
PopulateMetadataValue(const XdsApiContext & context,google_protobuf_Value * value_pb,const Json & value)103 void PopulateMetadataValue(const XdsApiContext& context,
104                            google_protobuf_Value* value_pb, const Json& value) {
105   switch (value.type()) {
106     case Json::Type::kNull:
107       google_protobuf_Value_set_null_value(value_pb, 0);
108       break;
109     case Json::Type::kNumber:
110       google_protobuf_Value_set_number_value(
111           value_pb, strtod(value.string().c_str(), nullptr));
112       break;
113     case Json::Type::kString:
114       google_protobuf_Value_set_string_value(
115           value_pb, StdStringToUpbString(value.string()));
116       break;
117     case Json::Type::kBoolean:
118       google_protobuf_Value_set_bool_value(value_pb, value.boolean());
119       break;
120     case Json::Type::kObject: {
121       google_protobuf_Struct* struct_value =
122           google_protobuf_Value_mutable_struct_value(value_pb, context.arena);
123       PopulateMetadata(context, struct_value, value.object());
124       break;
125     }
126     case Json::Type::kArray: {
127       google_protobuf_ListValue* list_value =
128           google_protobuf_Value_mutable_list_value(value_pb, context.arena);
129       PopulateListValue(context, list_value, value.array());
130       break;
131     }
132   }
133 }
134 
PopulateNode(const XdsApiContext & context,const XdsBootstrap::Node * node,const std::string & user_agent_name,const std::string & user_agent_version,envoy_config_core_v3_Node * node_msg)135 void PopulateNode(const XdsApiContext& context, const XdsBootstrap::Node* node,
136                   const std::string& user_agent_name,
137                   const std::string& user_agent_version,
138                   envoy_config_core_v3_Node* node_msg) {
139   if (node != nullptr) {
140     if (!node->id().empty()) {
141       envoy_config_core_v3_Node_set_id(node_msg,
142                                        StdStringToUpbString(node->id()));
143     }
144     if (!node->cluster().empty()) {
145       envoy_config_core_v3_Node_set_cluster(
146           node_msg, StdStringToUpbString(node->cluster()));
147     }
148     if (!node->metadata().empty()) {
149       google_protobuf_Struct* metadata =
150           envoy_config_core_v3_Node_mutable_metadata(node_msg, context.arena);
151       PopulateMetadata(context, metadata, node->metadata());
152     }
153     if (!node->locality_region().empty() || !node->locality_zone().empty() ||
154         !node->locality_sub_zone().empty()) {
155       envoy_config_core_v3_Locality* locality =
156           envoy_config_core_v3_Node_mutable_locality(node_msg, context.arena);
157       if (!node->locality_region().empty()) {
158         envoy_config_core_v3_Locality_set_region(
159             locality, StdStringToUpbString(node->locality_region()));
160       }
161       if (!node->locality_zone().empty()) {
162         envoy_config_core_v3_Locality_set_zone(
163             locality, StdStringToUpbString(node->locality_zone()));
164       }
165       if (!node->locality_sub_zone().empty()) {
166         envoy_config_core_v3_Locality_set_sub_zone(
167             locality, StdStringToUpbString(node->locality_sub_zone()));
168       }
169     }
170   }
171   envoy_config_core_v3_Node_set_user_agent_name(
172       node_msg, StdStringToUpbString(user_agent_name));
173   envoy_config_core_v3_Node_set_user_agent_version(
174       node_msg, StdStringToUpbString(user_agent_version));
175   envoy_config_core_v3_Node_add_client_features(
176       node_msg,
177       upb_StringView_FromString("envoy.lb.does_not_support_overprovisioning"),
178       context.arena);
179 }
180 
MaybeLogDiscoveryRequest(const XdsApiContext & context,const envoy_service_discovery_v3_DiscoveryRequest * request)181 void MaybeLogDiscoveryRequest(
182     const XdsApiContext& context,
183     const envoy_service_discovery_v3_DiscoveryRequest* request) {
184   if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
185       gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
186     const upb_MessageDef* msg_type =
187         envoy_service_discovery_v3_DiscoveryRequest_getmsgdef(context.symtab);
188     char buf[10240];
189     upb_TextEncode(request, msg_type, nullptr, 0, buf, sizeof(buf));
190     gpr_log(GPR_DEBUG, "[xds_client %p] constructed ADS request: %s",
191             context.client, buf);
192   }
193 }
194 
SerializeDiscoveryRequest(const XdsApiContext & context,envoy_service_discovery_v3_DiscoveryRequest * request)195 std::string SerializeDiscoveryRequest(
196     const XdsApiContext& context,
197     envoy_service_discovery_v3_DiscoveryRequest* request) {
198   size_t output_length;
199   char* output = envoy_service_discovery_v3_DiscoveryRequest_serialize(
200       request, context.arena, &output_length);
201   return std::string(output, output_length);
202 }
203 
204 }  // namespace
205 
CreateAdsRequest(absl::string_view type_url,absl::string_view version,absl::string_view nonce,const std::vector<std::string> & resource_names,absl::Status status,bool populate_node)206 std::string XdsApi::CreateAdsRequest(
207     absl::string_view type_url, absl::string_view version,
208     absl::string_view nonce, const std::vector<std::string>& resource_names,
209     absl::Status status, bool populate_node) {
210   upb::Arena arena;
211   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
212   // Create a request.
213   envoy_service_discovery_v3_DiscoveryRequest* request =
214       envoy_service_discovery_v3_DiscoveryRequest_new(arena.ptr());
215   // Set type_url.
216   std::string type_url_str = absl::StrCat("type.googleapis.com/", type_url);
217   envoy_service_discovery_v3_DiscoveryRequest_set_type_url(
218       request, StdStringToUpbString(type_url_str));
219   // Set version_info.
220   if (!version.empty()) {
221     envoy_service_discovery_v3_DiscoveryRequest_set_version_info(
222         request, StdStringToUpbString(version));
223   }
224   // Set nonce.
225   if (!nonce.empty()) {
226     envoy_service_discovery_v3_DiscoveryRequest_set_response_nonce(
227         request, StdStringToUpbString(nonce));
228   }
229   // Set error_detail if it's a NACK.
230   std::string error_string_storage;
231   if (!status.ok()) {
232     google_rpc_Status* error_detail =
233         envoy_service_discovery_v3_DiscoveryRequest_mutable_error_detail(
234             request, arena.ptr());
235     // Hard-code INVALID_ARGUMENT as the status code.
236     // TODO(roth): If at some point we decide we care about this value,
237     // we could attach a status code to the individual errors where we
238     // generate them in the parsing code, and then use that here.
239     google_rpc_Status_set_code(error_detail, GRPC_STATUS_INVALID_ARGUMENT);
240     // Error description comes from the status that was passed in.
241     error_string_storage = std::string(status.message());
242     upb_StringView error_description =
243         StdStringToUpbString(error_string_storage);
244     google_rpc_Status_set_message(error_detail, error_description);
245   }
246   // Populate node.
247   if (populate_node) {
248     envoy_config_core_v3_Node* node_msg =
249         envoy_service_discovery_v3_DiscoveryRequest_mutable_node(request,
250                                                                  arena.ptr());
251     PopulateNode(context, node_, user_agent_name_, user_agent_version_,
252                  node_msg);
253     envoy_config_core_v3_Node_add_client_features(
254         node_msg, upb_StringView_FromString("xds.config.resource-in-sotw"),
255         context.arena);
256   }
257   // Add resource_names.
258   for (const std::string& resource_name : resource_names) {
259     envoy_service_discovery_v3_DiscoveryRequest_add_resource_names(
260         request, StdStringToUpbString(resource_name), arena.ptr());
261   }
262   MaybeLogDiscoveryRequest(context, request);
263   return SerializeDiscoveryRequest(context, request);
264 }
265 
266 namespace {
267 
MaybeLogDiscoveryResponse(const XdsApiContext & context,const envoy_service_discovery_v3_DiscoveryResponse * response)268 void MaybeLogDiscoveryResponse(
269     const XdsApiContext& context,
270     const envoy_service_discovery_v3_DiscoveryResponse* response) {
271   if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
272       gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
273     const upb_MessageDef* msg_type =
274         envoy_service_discovery_v3_DiscoveryResponse_getmsgdef(context.symtab);
275     char buf[10240];
276     upb_TextEncode(response, msg_type, nullptr, 0, buf, sizeof(buf));
277     gpr_log(GPR_DEBUG, "[xds_client %p] received response: %s", context.client,
278             buf);
279   }
280 }
281 
282 }  // namespace
283 
ParseAdsResponse(absl::string_view encoded_response,AdsResponseParserInterface * parser)284 absl::Status XdsApi::ParseAdsResponse(absl::string_view encoded_response,
285                                       AdsResponseParserInterface* parser) {
286   upb::Arena arena;
287   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
288   // Decode the response.
289   const envoy_service_discovery_v3_DiscoveryResponse* response =
290       envoy_service_discovery_v3_DiscoveryResponse_parse(
291           encoded_response.data(), encoded_response.size(), arena.ptr());
292   // If decoding fails, report a fatal error and return.
293   if (response == nullptr) {
294     return absl::InvalidArgumentError("Can't decode DiscoveryResponse.");
295   }
296   MaybeLogDiscoveryResponse(context, response);
297   // Report the type_url, version, nonce, and number of resources to the parser.
298   AdsResponseParserInterface::AdsResponseFields fields;
299   fields.type_url = std::string(absl::StripPrefix(
300       UpbStringToAbsl(
301           envoy_service_discovery_v3_DiscoveryResponse_type_url(response)),
302       "type.googleapis.com/"));
303   fields.version = UpbStringToStdString(
304       envoy_service_discovery_v3_DiscoveryResponse_version_info(response));
305   fields.nonce = UpbStringToStdString(
306       envoy_service_discovery_v3_DiscoveryResponse_nonce(response));
307   size_t num_resources;
308   const google_protobuf_Any* const* resources =
309       envoy_service_discovery_v3_DiscoveryResponse_resources(response,
310                                                              &num_resources);
311   fields.num_resources = num_resources;
312   absl::Status status = parser->ProcessAdsResponseFields(std::move(fields));
313   if (!status.ok()) return status;
314   // Process each resource.
315   for (size_t i = 0; i < num_resources; ++i) {
316     absl::string_view type_url = absl::StripPrefix(
317         UpbStringToAbsl(google_protobuf_Any_type_url(resources[i])),
318         "type.googleapis.com/");
319     absl::string_view serialized_resource =
320         UpbStringToAbsl(google_protobuf_Any_value(resources[i]));
321     // Unwrap Resource messages, if so wrapped.
322     absl::string_view resource_name;
323     if (type_url == "envoy.service.discovery.v3.Resource") {
324       const auto* resource_wrapper = envoy_service_discovery_v3_Resource_parse(
325           serialized_resource.data(), serialized_resource.size(), arena.ptr());
326       if (resource_wrapper == nullptr) {
327         parser->ResourceWrapperParsingFailed(
328             i, "Can't decode Resource proto wrapper");
329         continue;
330       }
331       const auto* resource =
332           envoy_service_discovery_v3_Resource_resource(resource_wrapper);
333       if (resource == nullptr) {
334         parser->ResourceWrapperParsingFailed(
335             i, "No resource present in Resource proto wrapper");
336         continue;
337       }
338       type_url = absl::StripPrefix(
339           UpbStringToAbsl(google_protobuf_Any_type_url(resource)),
340           "type.googleapis.com/");
341       serialized_resource =
342           UpbStringToAbsl(google_protobuf_Any_value(resource));
343       resource_name = UpbStringToAbsl(
344           envoy_service_discovery_v3_Resource_name(resource_wrapper));
345     }
346     parser->ParseResource(context.arena, i, type_url, resource_name,
347                           serialized_resource);
348   }
349   return absl::OkStatus();
350 }
351 
352 namespace {
353 
MaybeLogLrsRequest(const XdsApiContext & context,const envoy_service_load_stats_v3_LoadStatsRequest * request)354 void MaybeLogLrsRequest(
355     const XdsApiContext& context,
356     const envoy_service_load_stats_v3_LoadStatsRequest* request) {
357   if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
358       gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
359     const upb_MessageDef* msg_type =
360         envoy_service_load_stats_v3_LoadStatsRequest_getmsgdef(context.symtab);
361     char buf[10240];
362     upb_TextEncode(request, msg_type, nullptr, 0, buf, sizeof(buf));
363     gpr_log(GPR_DEBUG, "[xds_client %p] constructed LRS request: %s",
364             context.client, buf);
365   }
366 }
367 
SerializeLrsRequest(const XdsApiContext & context,const envoy_service_load_stats_v3_LoadStatsRequest * request)368 std::string SerializeLrsRequest(
369     const XdsApiContext& context,
370     const envoy_service_load_stats_v3_LoadStatsRequest* request) {
371   size_t output_length;
372   char* output = envoy_service_load_stats_v3_LoadStatsRequest_serialize(
373       request, context.arena, &output_length);
374   return std::string(output, output_length);
375 }
376 
377 }  // namespace
378 
CreateLrsInitialRequest()379 std::string XdsApi::CreateLrsInitialRequest() {
380   upb::Arena arena;
381   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
382   // Create a request.
383   envoy_service_load_stats_v3_LoadStatsRequest* request =
384       envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr());
385   // Populate node.
386   envoy_config_core_v3_Node* node_msg =
387       envoy_service_load_stats_v3_LoadStatsRequest_mutable_node(request,
388                                                                 arena.ptr());
389   PopulateNode(context, node_, user_agent_name_, user_agent_version_, node_msg);
390   envoy_config_core_v3_Node_add_client_features(
391       node_msg,
392       upb_StringView_FromString("envoy.lrs.supports_send_all_clusters"),
393       arena.ptr());
394   MaybeLogLrsRequest(context, request);
395   return SerializeLrsRequest(context, request);
396 }
397 
398 namespace {
399 
LocalityStatsPopulate(const XdsApiContext & context,envoy_config_endpoint_v3_UpstreamLocalityStats * output,const XdsLocalityName & locality_name,const XdsClusterLocalityStats::Snapshot & snapshot)400 void LocalityStatsPopulate(
401     const XdsApiContext& context,
402     envoy_config_endpoint_v3_UpstreamLocalityStats* output,
403     const XdsLocalityName& locality_name,
404     const XdsClusterLocalityStats::Snapshot& snapshot) {
405   // Set locality.
406   envoy_config_core_v3_Locality* locality =
407       envoy_config_endpoint_v3_UpstreamLocalityStats_mutable_locality(
408           output, context.arena);
409   if (!locality_name.region().empty()) {
410     envoy_config_core_v3_Locality_set_region(
411         locality, StdStringToUpbString(locality_name.region()));
412   }
413   if (!locality_name.zone().empty()) {
414     envoy_config_core_v3_Locality_set_zone(
415         locality, StdStringToUpbString(locality_name.zone()));
416   }
417   if (!locality_name.sub_zone().empty()) {
418     envoy_config_core_v3_Locality_set_sub_zone(
419         locality, StdStringToUpbString(locality_name.sub_zone()));
420   }
421   // Set total counts.
422   envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_successful_requests(
423       output, snapshot.total_successful_requests);
424   envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_requests_in_progress(
425       output, snapshot.total_requests_in_progress);
426   envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_error_requests(
427       output, snapshot.total_error_requests);
428   envoy_config_endpoint_v3_UpstreamLocalityStats_set_total_issued_requests(
429       output, snapshot.total_issued_requests);
430   // Add backend metrics.
431   for (const auto& p : snapshot.backend_metrics) {
432     const std::string& metric_name = p.first;
433     const XdsClusterLocalityStats::BackendMetric& metric_value = p.second;
434     envoy_config_endpoint_v3_EndpointLoadMetricStats* load_metric =
435         envoy_config_endpoint_v3_UpstreamLocalityStats_add_load_metric_stats(
436             output, context.arena);
437     envoy_config_endpoint_v3_EndpointLoadMetricStats_set_metric_name(
438         load_metric, StdStringToUpbString(metric_name));
439     envoy_config_endpoint_v3_EndpointLoadMetricStats_set_num_requests_finished_with_metric(
440         load_metric, metric_value.num_requests_finished_with_metric);
441     envoy_config_endpoint_v3_EndpointLoadMetricStats_set_total_metric_value(
442         load_metric, metric_value.total_metric_value);
443   }
444 }
445 
446 }  // namespace
447 
CreateLrsRequest(ClusterLoadReportMap cluster_load_report_map)448 std::string XdsApi::CreateLrsRequest(
449     ClusterLoadReportMap cluster_load_report_map) {
450   upb::Arena arena;
451   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
452   // Create a request.
453   envoy_service_load_stats_v3_LoadStatsRequest* request =
454       envoy_service_load_stats_v3_LoadStatsRequest_new(arena.ptr());
455   for (auto& p : cluster_load_report_map) {
456     const std::string& cluster_name = p.first.first;
457     const std::string& eds_service_name = p.first.second;
458     const ClusterLoadReport& load_report = p.second;
459     // Add cluster stats.
460     envoy_config_endpoint_v3_ClusterStats* cluster_stats =
461         envoy_service_load_stats_v3_LoadStatsRequest_add_cluster_stats(
462             request, arena.ptr());
463     // Set the cluster name.
464     envoy_config_endpoint_v3_ClusterStats_set_cluster_name(
465         cluster_stats, StdStringToUpbString(cluster_name));
466     // Set EDS service name, if non-empty.
467     if (!eds_service_name.empty()) {
468       envoy_config_endpoint_v3_ClusterStats_set_cluster_service_name(
469           cluster_stats, StdStringToUpbString(eds_service_name));
470     }
471     // Add locality stats.
472     for (const auto& p : load_report.locality_stats) {
473       const XdsLocalityName& locality_name = *p.first;
474       const auto& snapshot = p.second;
475       envoy_config_endpoint_v3_UpstreamLocalityStats* locality_stats =
476           envoy_config_endpoint_v3_ClusterStats_add_upstream_locality_stats(
477               cluster_stats, arena.ptr());
478       LocalityStatsPopulate(context, locality_stats, locality_name, snapshot);
479     }
480     // Add dropped requests.
481     uint64_t total_dropped_requests = 0;
482     for (const auto& p : load_report.dropped_requests.categorized_drops) {
483       const std::string& category = p.first;
484       const uint64_t count = p.second;
485       envoy_config_endpoint_v3_ClusterStats_DroppedRequests* dropped_requests =
486           envoy_config_endpoint_v3_ClusterStats_add_dropped_requests(
487               cluster_stats, arena.ptr());
488       envoy_config_endpoint_v3_ClusterStats_DroppedRequests_set_category(
489           dropped_requests, StdStringToUpbString(category));
490       envoy_config_endpoint_v3_ClusterStats_DroppedRequests_set_dropped_count(
491           dropped_requests, count);
492       total_dropped_requests += count;
493     }
494     total_dropped_requests += load_report.dropped_requests.uncategorized_drops;
495     // Set total dropped requests.
496     envoy_config_endpoint_v3_ClusterStats_set_total_dropped_requests(
497         cluster_stats, total_dropped_requests);
498     // Set real load report interval.
499     gpr_timespec timespec = load_report.load_report_interval.as_timespec();
500     google_protobuf_Duration* load_report_interval =
501         envoy_config_endpoint_v3_ClusterStats_mutable_load_report_interval(
502             cluster_stats, arena.ptr());
503     google_protobuf_Duration_set_seconds(load_report_interval, timespec.tv_sec);
504     google_protobuf_Duration_set_nanos(load_report_interval, timespec.tv_nsec);
505   }
506   MaybeLogLrsRequest(context, request);
507   return SerializeLrsRequest(context, request);
508 }
509 
510 namespace {
511 
MaybeLogLrsResponse(const XdsApiContext & context,const envoy_service_load_stats_v3_LoadStatsResponse * response)512 void MaybeLogLrsResponse(
513     const XdsApiContext& context,
514     const envoy_service_load_stats_v3_LoadStatsResponse* response) {
515   if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
516       gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
517     const upb_MessageDef* msg_type =
518         envoy_service_load_stats_v3_LoadStatsResponse_getmsgdef(context.symtab);
519     char buf[10240];
520     upb_TextEncode(response, msg_type, nullptr, 0, buf, sizeof(buf));
521     gpr_log(GPR_DEBUG, "[xds_client %p] received LRS response: %s",
522             context.client, buf);
523   }
524 }
525 
526 }  // namespace
527 
ParseLrsResponse(absl::string_view encoded_response,bool * send_all_clusters,std::set<std::string> * cluster_names,Duration * load_reporting_interval)528 absl::Status XdsApi::ParseLrsResponse(absl::string_view encoded_response,
529                                       bool* send_all_clusters,
530                                       std::set<std::string>* cluster_names,
531                                       Duration* load_reporting_interval) {
532   upb::Arena arena;
533   // Decode the response.
534   const envoy_service_load_stats_v3_LoadStatsResponse* decoded_response =
535       envoy_service_load_stats_v3_LoadStatsResponse_parse(
536           encoded_response.data(), encoded_response.size(), arena.ptr());
537   // Parse the response.
538   if (decoded_response == nullptr) {
539     return absl::UnavailableError("Can't decode response.");
540   }
541   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
542   MaybeLogLrsResponse(context, decoded_response);
543   // Check send_all_clusters.
544   if (envoy_service_load_stats_v3_LoadStatsResponse_send_all_clusters(
545           decoded_response)) {
546     *send_all_clusters = true;
547   } else {
548     // Store the cluster names.
549     size_t size;
550     const upb_StringView* clusters =
551         envoy_service_load_stats_v3_LoadStatsResponse_clusters(decoded_response,
552                                                                &size);
553     for (size_t i = 0; i < size; ++i) {
554       cluster_names->emplace(UpbStringToStdString(clusters[i]));
555     }
556   }
557   // Get the load report interval.
558   const google_protobuf_Duration* load_reporting_interval_duration =
559       envoy_service_load_stats_v3_LoadStatsResponse_load_reporting_interval(
560           decoded_response);
561   *load_reporting_interval = Duration::FromSecondsAndNanoseconds(
562       google_protobuf_Duration_seconds(load_reporting_interval_duration),
563       google_protobuf_Duration_nanos(load_reporting_interval_duration));
564   return absl::OkStatus();
565 }
566 
567 namespace {
568 
EncodeTimestamp(const XdsApiContext & context,Timestamp value)569 google_protobuf_Timestamp* EncodeTimestamp(const XdsApiContext& context,
570                                            Timestamp value) {
571   google_protobuf_Timestamp* timestamp =
572       google_protobuf_Timestamp_new(context.arena);
573   gpr_timespec timespec = value.as_timespec(GPR_CLOCK_REALTIME);
574   google_protobuf_Timestamp_set_seconds(timestamp, timespec.tv_sec);
575   google_protobuf_Timestamp_set_nanos(timestamp, timespec.tv_nsec);
576   return timestamp;
577 }
578 
579 }  // namespace
580 
AssembleClientConfig(const ResourceTypeMetadataMap & resource_type_metadata_map)581 std::string XdsApi::AssembleClientConfig(
582     const ResourceTypeMetadataMap& resource_type_metadata_map) {
583   upb::Arena arena;
584   // Create the ClientConfig for resource metadata from XdsClient
585   auto* client_config = envoy_service_status_v3_ClientConfig_new(arena.ptr());
586   // Fill-in the node information
587   auto* node = envoy_service_status_v3_ClientConfig_mutable_node(client_config,
588                                                                  arena.ptr());
589   const XdsApiContext context = {client_, tracer_, symtab_->ptr(), arena.ptr()};
590   PopulateNode(context, node_, user_agent_name_, user_agent_version_, node);
591   // Dump each resource.
592   std::vector<std::string> type_url_storage;
593   for (const auto& p : resource_type_metadata_map) {
594     absl::string_view type_url = p.first;
595     const ResourceMetadataMap& resource_metadata_map = p.second;
596     type_url_storage.emplace_back(
597         absl::StrCat("type.googleapis.com/", type_url));
598     for (const auto& q : resource_metadata_map) {
599       absl::string_view resource_name = q.first;
600       const ResourceMetadata& metadata = *q.second;
601       auto* entry =
602           envoy_service_status_v3_ClientConfig_add_generic_xds_configs(
603               client_config, context.arena);
604       envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_type_url(
605           entry, StdStringToUpbString(type_url_storage.back()));
606       envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_name(
607           entry, StdStringToUpbString(resource_name));
608       envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_client_status(
609           entry, metadata.client_status);
610       if (!metadata.serialized_proto.empty()) {
611         envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_version_info(
612             entry, StdStringToUpbString(metadata.version));
613         envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_last_updated(
614             entry, EncodeTimestamp(context, metadata.update_time));
615         auto* any_field =
616             envoy_service_status_v3_ClientConfig_GenericXdsConfig_mutable_xds_config(
617                 entry, context.arena);
618         google_protobuf_Any_set_type_url(
619             any_field, StdStringToUpbString(type_url_storage.back()));
620         google_protobuf_Any_set_value(
621             any_field, StdStringToUpbString(metadata.serialized_proto));
622       }
623       if (metadata.client_status == XdsApi::ResourceMetadata::NACKED) {
624         auto* update_failure_state =
625             envoy_admin_v3_UpdateFailureState_new(context.arena);
626         envoy_admin_v3_UpdateFailureState_set_details(
627             update_failure_state,
628             StdStringToUpbString(metadata.failed_details));
629         envoy_admin_v3_UpdateFailureState_set_version_info(
630             update_failure_state,
631             StdStringToUpbString(metadata.failed_version));
632         envoy_admin_v3_UpdateFailureState_set_last_update_attempt(
633             update_failure_state,
634             EncodeTimestamp(context, metadata.failed_update_time));
635         envoy_service_status_v3_ClientConfig_GenericXdsConfig_set_error_state(
636             entry, update_failure_state);
637       }
638     }
639   }
640   // Serialize the upb message to bytes
641   size_t output_length;
642   char* output = envoy_service_status_v3_ClientConfig_serialize(
643       client_config, arena.ptr(), &output_length);
644   return std::string(output, output_length);
645 }
646 
647 }  // namespace grpc_core
648