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_cluster.h"
20 
21 #include <stddef.h>
22 
23 #include <utility>
24 
25 #include "absl/status/status.h"
26 #include "absl/status/statusor.h"
27 #include "absl/strings/str_cat.h"
28 #include "absl/strings/str_join.h"
29 #include "absl/strings/strip.h"
30 #include "absl/types/variant.h"
31 #include "envoy/config/cluster/v3/circuit_breaker.upb.h"
32 #include "envoy/config/cluster/v3/cluster.upb.h"
33 #include "envoy/config/cluster/v3/cluster.upbdefs.h"
34 #include "envoy/config/cluster/v3/outlier_detection.upb.h"
35 #include "envoy/config/core/v3/address.upb.h"
36 #include "envoy/config/core/v3/base.upb.h"
37 #include "envoy/config/core/v3/config_source.upb.h"
38 #include "envoy/config/core/v3/health_check.upb.h"
39 #include "envoy/config/endpoint/v3/endpoint.upb.h"
40 #include "envoy/config/endpoint/v3/endpoint_components.upb.h"
41 #include "envoy/extensions/clusters/aggregate/v3/cluster.upb.h"
42 #include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
43 #include "google/protobuf/any.upb.h"
44 #include "google/protobuf/duration.upb.h"
45 #include "google/protobuf/wrappers.upb.h"
46 #include "upb/base/string_view.h"
47 #include "upb/text/encode.h"
48 
49 #include <grpc/support/json.h>
50 #include <grpc/support/log.h>
51 
52 #include "src/core/ext/xds/upb_utils.h"
53 #include "src/core/ext/xds/xds_client.h"
54 #include "src/core/ext/xds/xds_common_types.h"
55 #include "src/core/ext/xds/xds_lb_policy_registry.h"
56 #include "src/core/ext/xds/xds_resource_type.h"
57 #include "src/core/lib/config/core_configuration.h"
58 #include "src/core/lib/debug/trace.h"
59 #include "src/core/lib/gpr/string.h"
60 #include "src/core/lib/gprpp/env.h"
61 #include "src/core/lib/gprpp/host_port.h"
62 #include "src/core/lib/gprpp/match.h"
63 #include "src/core/lib/gprpp/ref_counted_ptr.h"
64 #include "src/core/lib/gprpp/time.h"
65 #include "src/core/lib/gprpp/validation_errors.h"
66 #include "src/core/lib/json/json_writer.h"
67 #include "src/core/lib/load_balancing/lb_policy_registry.h"
68 #include "src/core/lib/matchers/matchers.h"
69 
70 namespace grpc_core {
71 
72 // TODO(eostroukhov): Remove once this feature is no longer experimental.
XdsOverrideHostEnabled()73 bool XdsOverrideHostEnabled() {
74   auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_ENABLE_OVERRIDE_HOST");
75   if (!value.has_value()) return false;
76   bool parsed_value;
77   bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
78   return parse_succeeded && parsed_value;
79 }
80 
81 //
82 // XdsClusterResource
83 //
84 
ToString() const85 std::string XdsClusterResource::ToString() const {
86   std::vector<std::string> contents;
87   Match(
88       type,
89       [&](const Eds& eds) {
90         contents.push_back("type=EDS");
91         if (!eds.eds_service_name.empty()) {
92           contents.push_back(
93               absl::StrCat("eds_service_name=", eds.eds_service_name));
94         }
95       },
96       [&](const LogicalDns& logical_dns) {
97         contents.push_back("type=LOGICAL_DNS");
98         contents.push_back(absl::StrCat("dns_hostname=", logical_dns.hostname));
99       },
100       [&](const Aggregate& aggregate) {
101         contents.push_back("type=AGGREGATE");
102         contents.push_back(absl::StrCat(
103             "prioritized_cluster_names=[",
104             absl::StrJoin(aggregate.prioritized_cluster_names, ", "), "]"));
105       });
106   contents.push_back(absl::StrCat("lb_policy_config=",
107                                   JsonDump(Json::FromArray(lb_policy_config))));
108   if (lrs_load_reporting_server.has_value()) {
109     contents.push_back(absl::StrCat("lrs_load_reporting_server_name=",
110                                     lrs_load_reporting_server->server_uri()));
111   }
112   if (!common_tls_context.Empty()) {
113     contents.push_back(
114         absl::StrCat("common_tls_context=", common_tls_context.ToString()));
115   }
116   contents.push_back(
117       absl::StrCat("max_concurrent_requests=", max_concurrent_requests));
118   if (!override_host_statuses.empty()) {
119     std::vector<const char*> statuses;
120     statuses.reserve(override_host_statuses.size());
121     for (const auto& status : override_host_statuses) {
122       statuses.push_back(status.ToString());
123     }
124     contents.push_back(absl::StrCat("override_host_statuses={",
125                                     absl::StrJoin(statuses, ", "), "}"));
126   }
127   return absl::StrCat("{", absl::StrJoin(contents, ", "), "}");
128 }
129 
130 //
131 // XdsClusterResourceType
132 //
133 
134 namespace {
135 
UpstreamTlsContextParse(const XdsResourceType::DecodeContext & context,const envoy_config_core_v3_TransportSocket * transport_socket,ValidationErrors * errors)136 CommonTlsContext UpstreamTlsContextParse(
137     const XdsResourceType::DecodeContext& context,
138     const envoy_config_core_v3_TransportSocket* transport_socket,
139     ValidationErrors* errors) {
140   ValidationErrors::ScopedField field(errors, ".typed_config");
141   const auto* typed_config =
142       envoy_config_core_v3_TransportSocket_typed_config(transport_socket);
143   auto extension = ExtractXdsExtension(context, typed_config, errors);
144   if (!extension.has_value()) return {};
145   if (extension->type !=
146       "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") {
147     ValidationErrors::ScopedField field(errors, ".type_url");
148     errors->AddError("unsupported transport socket type");
149     return {};
150   }
151   absl::string_view* serialized_upstream_tls_context =
152       absl::get_if<absl::string_view>(&extension->value);
153   if (serialized_upstream_tls_context == nullptr) {
154     errors->AddError("can't decode UpstreamTlsContext");
155     return {};
156   }
157   const auto* upstream_tls_context =
158       envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse(
159           serialized_upstream_tls_context->data(),
160           serialized_upstream_tls_context->size(), context.arena);
161   if (upstream_tls_context == nullptr) {
162     errors->AddError("can't decode UpstreamTlsContext");
163     return {};
164   }
165   ValidationErrors::ScopedField field3(errors, ".common_tls_context");
166   const auto* common_tls_context_proto =
167       envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_common_tls_context(
168           upstream_tls_context);
169   CommonTlsContext common_tls_context;
170   if (common_tls_context_proto != nullptr) {
171     common_tls_context =
172         CommonTlsContext::Parse(context, common_tls_context_proto, errors);
173   }
174   if (common_tls_context.certificate_validation_context
175           .ca_certificate_provider_instance.instance_name.empty()) {
176     errors->AddError("no CA certificate provider instance configured");
177   }
178   return common_tls_context;
179 }
180 
EdsConfigParse(const envoy_config_cluster_v3_Cluster * cluster,ValidationErrors * errors)181 XdsClusterResource::Eds EdsConfigParse(
182     const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
183   XdsClusterResource::Eds eds;
184   ValidationErrors::ScopedField field(errors, ".eds_cluster_config");
185   const envoy_config_cluster_v3_Cluster_EdsClusterConfig* eds_cluster_config =
186       envoy_config_cluster_v3_Cluster_eds_cluster_config(cluster);
187   if (eds_cluster_config == nullptr) {
188     errors->AddError("field not present");
189   } else {
190     ValidationErrors::ScopedField field(errors, ".eds_config");
191     const envoy_config_core_v3_ConfigSource* eds_config =
192         envoy_config_cluster_v3_Cluster_EdsClusterConfig_eds_config(
193             eds_cluster_config);
194     if (eds_config == nullptr) {
195       errors->AddError("field not present");
196     } else {
197       if (!envoy_config_core_v3_ConfigSource_has_ads(eds_config) &&
198           !envoy_config_core_v3_ConfigSource_has_self(eds_config)) {
199         errors->AddError("ConfigSource is not ads or self");
200       }
201       // Record EDS service_name (if any).
202       upb_StringView service_name =
203           envoy_config_cluster_v3_Cluster_EdsClusterConfig_service_name(
204               eds_cluster_config);
205       if (service_name.size != 0) {
206         eds.eds_service_name = UpbStringToStdString(service_name);
207       }
208     }
209   }
210   return eds;
211 }
212 
LogicalDnsParse(const envoy_config_cluster_v3_Cluster * cluster,ValidationErrors * errors)213 XdsClusterResource::LogicalDns LogicalDnsParse(
214     const envoy_config_cluster_v3_Cluster* cluster, ValidationErrors* errors) {
215   XdsClusterResource::LogicalDns logical_dns;
216   ValidationErrors::ScopedField field(errors, ".load_assignment");
217   const auto* load_assignment =
218       envoy_config_cluster_v3_Cluster_load_assignment(cluster);
219   if (load_assignment == nullptr) {
220     errors->AddError("field not present for LOGICAL_DNS cluster");
221     return logical_dns;
222   }
223   ValidationErrors::ScopedField field2(errors, ".endpoints");
224   size_t num_localities;
225   const auto* const* localities =
226       envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints(load_assignment,
227                                                                &num_localities);
228   if (num_localities != 1) {
229     errors->AddError(absl::StrCat(
230         "must contain exactly one locality for LOGICAL_DNS cluster, found ",
231         num_localities));
232     return logical_dns;
233   }
234   ValidationErrors::ScopedField field3(errors, "[0].lb_endpoints");
235   size_t num_endpoints;
236   const auto* const* endpoints =
237       envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints(localities[0],
238                                                                 &num_endpoints);
239   if (num_endpoints != 1) {
240     errors->AddError(absl::StrCat(
241         "must contain exactly one endpoint for LOGICAL_DNS cluster, found ",
242         num_endpoints));
243     return logical_dns;
244   }
245   ValidationErrors::ScopedField field4(errors, "[0].endpoint");
246   const auto* endpoint =
247       envoy_config_endpoint_v3_LbEndpoint_endpoint(endpoints[0]);
248   if (endpoint == nullptr) {
249     errors->AddError("field not present");
250     return logical_dns;
251   }
252   ValidationErrors::ScopedField field5(errors, ".address");
253   const auto* address = envoy_config_endpoint_v3_Endpoint_address(endpoint);
254   if (address == nullptr) {
255     errors->AddError("field not present");
256     return logical_dns;
257   }
258   ValidationErrors::ScopedField field6(errors, ".socket_address");
259   const auto* socket_address =
260       envoy_config_core_v3_Address_socket_address(address);
261   if (socket_address == nullptr) {
262     errors->AddError("field not present");
263     return logical_dns;
264   }
265   if (envoy_config_core_v3_SocketAddress_resolver_name(socket_address).size !=
266       0) {
267     ValidationErrors::ScopedField field(errors, ".resolver_name");
268     errors->AddError(
269         "LOGICAL_DNS clusters must NOT have a custom resolver name set");
270   }
271   absl::string_view address_str = UpbStringToAbsl(
272       envoy_config_core_v3_SocketAddress_address(socket_address));
273   if (address_str.empty()) {
274     ValidationErrors::ScopedField field(errors, ".address");
275     errors->AddError("field not present");
276   }
277   if (!envoy_config_core_v3_SocketAddress_has_port_value(socket_address)) {
278     ValidationErrors::ScopedField field(errors, ".port_value");
279     errors->AddError("field not present");
280   }
281   logical_dns.hostname = JoinHostPort(
282       address_str,
283       envoy_config_core_v3_SocketAddress_port_value(socket_address));
284   return logical_dns;
285 }
286 
AggregateClusterParse(const XdsResourceType::DecodeContext & context,absl::string_view serialized_config,ValidationErrors * errors)287 XdsClusterResource::Aggregate AggregateClusterParse(
288     const XdsResourceType::DecodeContext& context,
289     absl::string_view serialized_config, ValidationErrors* errors) {
290   XdsClusterResource::Aggregate aggregate;
291   const auto* aggregate_cluster_config =
292       envoy_extensions_clusters_aggregate_v3_ClusterConfig_parse(
293           serialized_config.data(), serialized_config.size(), context.arena);
294   if (aggregate_cluster_config == nullptr) {
295     errors->AddError("can't parse aggregate cluster config");
296     return aggregate;
297   }
298   size_t size;
299   const upb_StringView* clusters =
300       envoy_extensions_clusters_aggregate_v3_ClusterConfig_clusters(
301           aggregate_cluster_config, &size);
302   if (size == 0) {
303     ValidationErrors::ScopedField field(errors, ".clusters");
304     errors->AddError("must be non-empty");
305   }
306   for (size_t i = 0; i < size; ++i) {
307     aggregate.prioritized_cluster_names.emplace_back(
308         UpbStringToStdString(clusters[i]));
309   }
310   return aggregate;
311 }
312 
ParseLbPolicyConfig(const XdsResourceType::DecodeContext & context,const envoy_config_cluster_v3_Cluster * cluster,XdsClusterResource * cds_update,ValidationErrors * errors)313 void ParseLbPolicyConfig(const XdsResourceType::DecodeContext& context,
314                          const envoy_config_cluster_v3_Cluster* cluster,
315                          XdsClusterResource* cds_update,
316                          ValidationErrors* errors) {
317   // First, check the new load_balancing_policy field.
318   const auto* load_balancing_policy =
319       envoy_config_cluster_v3_Cluster_load_balancing_policy(cluster);
320   if (load_balancing_policy != nullptr) {
321     const auto& registry =
322         static_cast<const GrpcXdsBootstrap&>(context.client->bootstrap())
323             .lb_policy_registry();
324     ValidationErrors::ScopedField field(errors, ".load_balancing_policy");
325     const size_t original_error_count = errors->size();
326     cds_update->lb_policy_config = registry.ConvertXdsLbPolicyConfig(
327         context, load_balancing_policy, errors);
328     // If there were no conversion errors, validate that the converted config
329     // parses with the gRPC LB policy registry.
330     if (original_error_count == errors->size()) {
331       auto config = CoreConfiguration::Get()
332                         .lb_policy_registry()
333                         .ParseLoadBalancingConfig(
334                             Json::FromArray(cds_update->lb_policy_config));
335       if (!config.ok()) errors->AddError(config.status().message());
336     }
337     return;
338   }
339   // Didn't find load_balancing_policy field, so fall back to the old
340   // lb_policy enum field.
341   if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
342       envoy_config_cluster_v3_Cluster_ROUND_ROBIN) {
343     cds_update->lb_policy_config = {
344         Json::FromObject({
345             {"xds_wrr_locality_experimental",
346              Json::FromObject({
347                  {"childPolicy", Json::FromArray({
348                                      Json::FromObject({
349                                          {"round_robin", Json::FromObject({})},
350                                      }),
351                                  })},
352              })},
353         }),
354     };
355   } else if (envoy_config_cluster_v3_Cluster_lb_policy(cluster) ==
356              envoy_config_cluster_v3_Cluster_RING_HASH) {
357     // Record ring hash lb config
358     auto* ring_hash_config =
359         envoy_config_cluster_v3_Cluster_ring_hash_lb_config(cluster);
360     uint64_t min_ring_size = 1024;
361     uint64_t max_ring_size = 8388608;
362     if (ring_hash_config != nullptr) {
363       ValidationErrors::ScopedField field(errors, ".ring_hash_lb_config");
364       const google_protobuf_UInt64Value* uint64_value =
365           envoy_config_cluster_v3_Cluster_RingHashLbConfig_maximum_ring_size(
366               ring_hash_config);
367       if (uint64_value != nullptr) {
368         ValidationErrors::ScopedField field(errors, ".maximum_ring_size");
369         max_ring_size = google_protobuf_UInt64Value_value(uint64_value);
370         if (max_ring_size > 8388608 || max_ring_size == 0) {
371           errors->AddError("must be in the range of 1 to 8388608");
372         }
373       }
374       uint64_value =
375           envoy_config_cluster_v3_Cluster_RingHashLbConfig_minimum_ring_size(
376               ring_hash_config);
377       if (uint64_value != nullptr) {
378         ValidationErrors::ScopedField field(errors, ".minimum_ring_size");
379         min_ring_size = google_protobuf_UInt64Value_value(uint64_value);
380         if (min_ring_size > 8388608 || min_ring_size == 0) {
381           errors->AddError("must be in the range of 1 to 8388608");
382         }
383         if (min_ring_size > max_ring_size) {
384           errors->AddError("cannot be greater than maximum_ring_size");
385         }
386       }
387       if (envoy_config_cluster_v3_Cluster_RingHashLbConfig_hash_function(
388               ring_hash_config) !=
389           envoy_config_cluster_v3_Cluster_RingHashLbConfig_XX_HASH) {
390         ValidationErrors::ScopedField field(errors, ".hash_function");
391         errors->AddError("invalid hash function");
392       }
393     }
394     cds_update->lb_policy_config = {
395         Json::FromObject({
396             {"ring_hash_experimental",
397              Json::FromObject({
398                  {"minRingSize", Json::FromNumber(min_ring_size)},
399                  {"maxRingSize", Json::FromNumber(max_ring_size)},
400              })},
401         }),
402     };
403   } else {
404     ValidationErrors::ScopedField field(errors, ".lb_policy");
405     errors->AddError("LB policy is not supported");
406   }
407 }
408 
CdsResourceParse(const XdsResourceType::DecodeContext & context,const envoy_config_cluster_v3_Cluster * cluster)409 absl::StatusOr<XdsClusterResource> CdsResourceParse(
410     const XdsResourceType::DecodeContext& context,
411     const envoy_config_cluster_v3_Cluster* cluster) {
412   XdsClusterResource cds_update;
413   ValidationErrors errors;
414   // Check the cluster discovery type.
415   if (envoy_config_cluster_v3_Cluster_type(cluster) ==
416       envoy_config_cluster_v3_Cluster_EDS) {
417     cds_update.type = EdsConfigParse(cluster, &errors);
418   } else if (envoy_config_cluster_v3_Cluster_type(cluster) ==
419              envoy_config_cluster_v3_Cluster_LOGICAL_DNS) {
420     cds_update.type = LogicalDnsParse(cluster, &errors);
421   } else if (envoy_config_cluster_v3_Cluster_has_cluster_type(cluster)) {
422     ValidationErrors::ScopedField field(&errors, ".cluster_type");
423     const auto* custom_cluster_type =
424         envoy_config_cluster_v3_Cluster_cluster_type(cluster);
425     GPR_ASSERT(custom_cluster_type != nullptr);
426     ValidationErrors::ScopedField field2(&errors, ".typed_config");
427     const auto* typed_config =
428         envoy_config_cluster_v3_Cluster_CustomClusterType_typed_config(
429             custom_cluster_type);
430     if (typed_config == nullptr) {
431       errors.AddError("field not present");
432     } else {
433       absl::string_view type_url = absl::StripPrefix(
434           UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)),
435           "type.googleapis.com/");
436       if (type_url != "envoy.extensions.clusters.aggregate.v3.ClusterConfig") {
437         ValidationErrors::ScopedField field(&errors, ".type_url");
438         errors.AddError(
439             absl::StrCat("unknown cluster_type extension: ", type_url));
440       } else {
441         // Retrieve aggregate clusters.
442         ValidationErrors::ScopedField field(
443             &errors,
444             ".value[envoy.extensions.clusters.aggregate.v3.ClusterConfig]");
445         absl::string_view serialized_config =
446             UpbStringToAbsl(google_protobuf_Any_value(typed_config));
447         cds_update.type =
448             AggregateClusterParse(context, serialized_config, &errors);
449       }
450     }
451   } else {
452     ValidationErrors::ScopedField field(&errors, ".type");
453     errors.AddError("unknown discovery type");
454   }
455   // Check the LB policy.
456   ParseLbPolicyConfig(context, cluster, &cds_update, &errors);
457   // transport_socket
458   auto* transport_socket =
459       envoy_config_cluster_v3_Cluster_transport_socket(cluster);
460   if (transport_socket != nullptr) {
461     ValidationErrors::ScopedField field(&errors, ".transport_socket");
462     cds_update.common_tls_context =
463         UpstreamTlsContextParse(context, transport_socket, &errors);
464   }
465   // Record LRS server name (if any).
466   const envoy_config_core_v3_ConfigSource* lrs_server =
467       envoy_config_cluster_v3_Cluster_lrs_server(cluster);
468   if (lrs_server != nullptr) {
469     if (!envoy_config_core_v3_ConfigSource_has_self(lrs_server)) {
470       ValidationErrors::ScopedField field(&errors, ".lrs_server");
471       errors.AddError("ConfigSource is not self");
472     }
473     cds_update.lrs_load_reporting_server.emplace(
474         static_cast<const GrpcXdsBootstrap::GrpcXdsServer&>(context.server));
475   }
476   // The Cluster resource encodes the circuit breaking parameters in a list of
477   // Thresholds messages, where each message specifies the parameters for a
478   // particular RoutingPriority. we will look only at the first entry in the
479   // list for priority DEFAULT and default to 1024 if not found.
480   if (envoy_config_cluster_v3_Cluster_has_circuit_breakers(cluster)) {
481     const envoy_config_cluster_v3_CircuitBreakers* circuit_breakers =
482         envoy_config_cluster_v3_Cluster_circuit_breakers(cluster);
483     size_t num_thresholds;
484     const envoy_config_cluster_v3_CircuitBreakers_Thresholds* const*
485         thresholds = envoy_config_cluster_v3_CircuitBreakers_thresholds(
486             circuit_breakers, &num_thresholds);
487     for (size_t i = 0; i < num_thresholds; ++i) {
488       const auto* threshold = thresholds[i];
489       if (envoy_config_cluster_v3_CircuitBreakers_Thresholds_priority(
490               threshold) == envoy_config_core_v3_DEFAULT) {
491         const google_protobuf_UInt32Value* max_requests =
492             envoy_config_cluster_v3_CircuitBreakers_Thresholds_max_requests(
493                 threshold);
494         if (max_requests != nullptr) {
495           cds_update.max_concurrent_requests =
496               google_protobuf_UInt32Value_value(max_requests);
497         }
498         break;
499       }
500     }
501   }
502   // Outlier detection config.
503   if (envoy_config_cluster_v3_Cluster_has_outlier_detection(cluster)) {
504     ValidationErrors::ScopedField field(&errors, ".outlier_detection");
505     OutlierDetectionConfig outlier_detection_update;
506     const envoy_config_cluster_v3_OutlierDetection* outlier_detection =
507         envoy_config_cluster_v3_Cluster_outlier_detection(cluster);
508     const google_protobuf_Duration* duration =
509         envoy_config_cluster_v3_OutlierDetection_interval(outlier_detection);
510     if (duration != nullptr) {
511       ValidationErrors::ScopedField field(&errors, ".interval");
512       outlier_detection_update.interval = ParseDuration(duration, &errors);
513     }
514     duration = envoy_config_cluster_v3_OutlierDetection_base_ejection_time(
515         outlier_detection);
516     if (duration != nullptr) {
517       ValidationErrors::ScopedField field(&errors, ".base_ejection_time");
518       outlier_detection_update.base_ejection_time =
519           ParseDuration(duration, &errors);
520     }
521     duration = envoy_config_cluster_v3_OutlierDetection_max_ejection_time(
522         outlier_detection);
523     if (duration != nullptr) {
524       ValidationErrors::ScopedField field(&errors, ".max_ejection_time");
525       outlier_detection_update.max_ejection_time =
526           ParseDuration(duration, &errors);
527     }
528     const google_protobuf_UInt32Value* max_ejection_percent =
529         envoy_config_cluster_v3_OutlierDetection_max_ejection_percent(
530             outlier_detection);
531     if (max_ejection_percent != nullptr) {
532       outlier_detection_update.max_ejection_percent =
533           google_protobuf_UInt32Value_value(max_ejection_percent);
534       if (outlier_detection_update.max_ejection_percent > 100) {
535         ValidationErrors::ScopedField field(&errors, ".max_ejection_percent");
536         errors.AddError("value must be <= 100");
537       }
538     }
539     const google_protobuf_UInt32Value* enforcing_success_rate =
540         envoy_config_cluster_v3_OutlierDetection_enforcing_success_rate(
541             outlier_detection);
542     if (enforcing_success_rate != nullptr) {
543       uint32_t enforcement_percentage =
544           google_protobuf_UInt32Value_value(enforcing_success_rate);
545       if (enforcement_percentage > 100) {
546         ValidationErrors::ScopedField field(&errors, ".enforcing_success_rate");
547         errors.AddError("value must be <= 100");
548       }
549       if (enforcement_percentage != 0) {
550         OutlierDetectionConfig::SuccessRateEjection success_rate_ejection;
551         success_rate_ejection.enforcement_percentage = enforcement_percentage;
552         const google_protobuf_UInt32Value* minimum_hosts =
553             envoy_config_cluster_v3_OutlierDetection_success_rate_minimum_hosts(
554                 outlier_detection);
555         if (minimum_hosts != nullptr) {
556           success_rate_ejection.minimum_hosts =
557               google_protobuf_UInt32Value_value(minimum_hosts);
558         }
559         const google_protobuf_UInt32Value* request_volume =
560             envoy_config_cluster_v3_OutlierDetection_success_rate_request_volume(
561                 outlier_detection);
562         if (request_volume != nullptr) {
563           success_rate_ejection.request_volume =
564               google_protobuf_UInt32Value_value(request_volume);
565         }
566         const google_protobuf_UInt32Value* stdev_factor =
567             envoy_config_cluster_v3_OutlierDetection_success_rate_stdev_factor(
568                 outlier_detection);
569         if (stdev_factor != nullptr) {
570           success_rate_ejection.stdev_factor =
571               google_protobuf_UInt32Value_value(stdev_factor);
572         }
573         outlier_detection_update.success_rate_ejection = success_rate_ejection;
574       }
575     }
576     const google_protobuf_UInt32Value* enforcing_failure_percentage =
577         envoy_config_cluster_v3_OutlierDetection_enforcing_failure_percentage(
578             outlier_detection);
579     if (enforcing_failure_percentage != nullptr) {
580       uint32_t enforcement_percentage =
581           google_protobuf_UInt32Value_value(enforcing_failure_percentage);
582       if (enforcement_percentage > 100) {
583         ValidationErrors::ScopedField field(&errors,
584                                             ".enforcing_failure_percentage");
585         errors.AddError("value must be <= 100");
586       }
587       if (enforcement_percentage != 0) {
588         OutlierDetectionConfig::FailurePercentageEjection
589             failure_percentage_ejection;
590         failure_percentage_ejection.enforcement_percentage =
591             enforcement_percentage;
592         const google_protobuf_UInt32Value* minimum_hosts =
593             envoy_config_cluster_v3_OutlierDetection_failure_percentage_minimum_hosts(
594                 outlier_detection);
595         if (minimum_hosts != nullptr) {
596           failure_percentage_ejection.minimum_hosts =
597               google_protobuf_UInt32Value_value(minimum_hosts);
598         }
599         const google_protobuf_UInt32Value* request_volume =
600             envoy_config_cluster_v3_OutlierDetection_failure_percentage_request_volume(
601                 outlier_detection);
602         if (request_volume != nullptr) {
603           failure_percentage_ejection.request_volume =
604               google_protobuf_UInt32Value_value(request_volume);
605         }
606         const google_protobuf_UInt32Value* threshold =
607             envoy_config_cluster_v3_OutlierDetection_failure_percentage_threshold(
608                 outlier_detection);
609         if (threshold != nullptr) {
610           failure_percentage_ejection.threshold =
611               google_protobuf_UInt32Value_value(threshold);
612           if (enforcement_percentage > 100) {
613             ValidationErrors::ScopedField field(
614                 &errors, ".failure_percentage_threshold");
615             errors.AddError("value must be <= 100");
616           }
617         }
618         outlier_detection_update.failure_percentage_ejection =
619             failure_percentage_ejection;
620       }
621     }
622     cds_update.outlier_detection = outlier_detection_update;
623   }
624   // Validate override host status.
625   if (XdsOverrideHostEnabled()) {
626     const auto* common_lb_config =
627         envoy_config_cluster_v3_Cluster_common_lb_config(cluster);
628     if (common_lb_config != nullptr) {
629       ValidationErrors::ScopedField field(&errors, ".common_lb_config");
630       const auto* override_host_status =
631           envoy_config_cluster_v3_Cluster_CommonLbConfig_override_host_status(
632               common_lb_config);
633       if (override_host_status != nullptr) {
634         ValidationErrors::ScopedField field(&errors, ".override_host_status");
635         size_t size;
636         const int32_t* statuses = envoy_config_core_v3_HealthStatusSet_statuses(
637             override_host_status, &size);
638         for (size_t i = 0; i < size; ++i) {
639           auto status = XdsHealthStatus::FromUpb(statuses[i]);
640           if (status.has_value()) {
641             cds_update.override_host_statuses.insert(*status);
642           }
643         }
644       }
645     }
646   }
647   // Return result.
648   if (!errors.ok()) {
649     return errors.status(absl::StatusCode::kInvalidArgument,
650                          "errors validating Cluster resource");
651   }
652   return cds_update;
653 }
654 
MaybeLogCluster(const XdsResourceType::DecodeContext & context,const envoy_config_cluster_v3_Cluster * cluster)655 void MaybeLogCluster(const XdsResourceType::DecodeContext& context,
656                      const envoy_config_cluster_v3_Cluster* cluster) {
657   if (GRPC_TRACE_FLAG_ENABLED(*context.tracer) &&
658       gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
659     const upb_MessageDef* msg_type =
660         envoy_config_cluster_v3_Cluster_getmsgdef(context.symtab);
661     char buf[10240];
662     upb_TextEncode(cluster, msg_type, nullptr, 0, buf, sizeof(buf));
663     gpr_log(GPR_DEBUG, "[xds_client %p] Cluster: %s", context.client, buf);
664   }
665 }
666 
667 }  // namespace
668 
Decode(const XdsResourceType::DecodeContext & context,absl::string_view serialized_resource) const669 XdsResourceType::DecodeResult XdsClusterResourceType::Decode(
670     const XdsResourceType::DecodeContext& context,
671     absl::string_view serialized_resource) const {
672   DecodeResult result;
673   // Parse serialized proto.
674   auto* resource = envoy_config_cluster_v3_Cluster_parse(
675       serialized_resource.data(), serialized_resource.size(), context.arena);
676   if (resource == nullptr) {
677     result.resource =
678         absl::InvalidArgumentError("Can't parse Cluster resource.");
679     return result;
680   }
681   MaybeLogCluster(context, resource);
682   // Validate resource.
683   result.name =
684       UpbStringToStdString(envoy_config_cluster_v3_Cluster_name(resource));
685   auto cds_resource = CdsResourceParse(context, resource);
686   if (!cds_resource.ok()) {
687     if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) {
688       gpr_log(GPR_ERROR, "[xds_client %p] invalid Cluster %s: %s",
689               context.client, result.name->c_str(),
690               cds_resource.status().ToString().c_str());
691     }
692     result.resource = cds_resource.status();
693   } else {
694     if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) {
695       gpr_log(GPR_INFO, "[xds_client %p] parsed Cluster %s: %s", context.client,
696               result.name->c_str(), cds_resource->ToString().c_str());
697     }
698     result.resource =
699         std::make_unique<XdsClusterResource>(std::move(*cds_resource));
700   }
701   return result;
702 }
703 
704 }  // namespace grpc_core
705