1 //
2 // Copyright 2022 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_lb_policy_registry.h"
20 
21 #include <stddef.h>
22 #include <stdint.h>
23 
24 #include <string>
25 #include <utility>
26 
27 #include "absl/strings/str_cat.h"
28 #include "absl/types/optional.h"
29 #include "absl/types/variant.h"
30 #include "envoy/config/core/v3/extension.upb.h"
31 #include "envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb.h"
32 #include "envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb.h"
33 #include "envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb.h"
34 #include "google/protobuf/wrappers.upb.h"
35 
36 #include <grpc/support/json.h>
37 
38 #include "src/core/ext/xds/xds_common_types.h"
39 #include "src/core/lib/config/core_configuration.h"
40 #include "src/core/lib/gprpp/time.h"
41 #include "src/core/lib/gprpp/validation_errors.h"
42 #include "src/core/lib/load_balancing/lb_policy_registry.h"
43 
44 namespace grpc_core {
45 
46 namespace {
47 
48 class RoundRobinLbPolicyConfigFactory
49     : public XdsLbPolicyRegistry::ConfigFactory {
50  public:
ConvertXdsLbPolicyConfig(const XdsLbPolicyRegistry *,const XdsResourceType::DecodeContext &,absl::string_view,ValidationErrors *,int)51   Json::Object ConvertXdsLbPolicyConfig(
52       const XdsLbPolicyRegistry* /*registry*/,
53       const XdsResourceType::DecodeContext& /*context*/,
54       absl::string_view /*configuration*/, ValidationErrors* /*errors*/,
55       int /*recursion_depth*/) override {
56     return Json::Object{{"round_robin", Json::FromObject({})}};
57   }
58 
type()59   absl::string_view type() override { return Type(); }
60 
Type()61   static absl::string_view Type() {
62     return "envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin";
63   }
64 };
65 
66 class ClientSideWeightedRoundRobinLbPolicyConfigFactory
67     : public XdsLbPolicyRegistry::ConfigFactory {
68  public:
ConvertXdsLbPolicyConfig(const XdsLbPolicyRegistry *,const XdsResourceType::DecodeContext & context,absl::string_view configuration,ValidationErrors * errors,int)69   Json::Object ConvertXdsLbPolicyConfig(
70       const XdsLbPolicyRegistry* /*registry*/,
71       const XdsResourceType::DecodeContext& context,
72       absl::string_view configuration, ValidationErrors* errors,
73       int /*recursion_depth*/) override {
74     const auto* resource =
75         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_parse(
76             configuration.data(), configuration.size(), context.arena);
77     if (resource == nullptr) {
78       errors->AddError(
79           "can't decode ClientSideWeightedRoundRobin LB policy config");
80       return {};
81     }
82     Json::Object config;
83     // enable_oob_load_report
84     auto* enable_oob_load_report =
85         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_enable_oob_load_report(
86             resource);
87     if (enable_oob_load_report != nullptr &&
88         google_protobuf_BoolValue_value(enable_oob_load_report)) {
89       config["enableOobLoadReport"] = Json::FromBool(true);
90     }
91     // oob_reporting_period
92     auto* duration_proto =
93         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_oob_reporting_period(
94             resource);
95     if (duration_proto != nullptr) {
96       ValidationErrors::ScopedField field(errors, ".oob_reporting_period");
97       Duration duration = ParseDuration(duration_proto, errors);
98       config["oobReportingPeriod"] = Json::FromString(duration.ToJsonString());
99     }
100     // blackout_period
101     duration_proto =
102         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_blackout_period(
103             resource);
104     if (duration_proto != nullptr) {
105       ValidationErrors::ScopedField field(errors, ".blackout_period");
106       Duration duration = ParseDuration(duration_proto, errors);
107       config["blackoutPeriod"] = Json::FromString(duration.ToJsonString());
108     }
109     // weight_update_period
110     duration_proto =
111         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_weight_update_period(
112             resource);
113     if (duration_proto != nullptr) {
114       ValidationErrors::ScopedField field(errors, ".weight_update_period");
115       Duration duration = ParseDuration(duration_proto, errors);
116       config["weightUpdatePeriod"] = Json::FromString(duration.ToJsonString());
117     }
118     // weight_expiration_period
119     duration_proto =
120         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_weight_expiration_period(
121             resource);
122     if (duration_proto != nullptr) {
123       ValidationErrors::ScopedField field(errors, ".weight_expiration_period");
124       Duration duration = ParseDuration(duration_proto, errors);
125       config["weightExpirationPeriod"] =
126           Json::FromString(duration.ToJsonString());
127     }
128     // error_utilization_penalty
129     auto* error_utilization_penalty =
130         envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_error_utilization_penalty(
131             resource);
132     if (error_utilization_penalty != nullptr) {
133       ValidationErrors::ScopedField field(errors, ".error_utilization_penalty");
134       const float value =
135           google_protobuf_FloatValue_value(error_utilization_penalty);
136       if (value < 0.0) {
137         errors->AddError("value must be non-negative");
138       }
139       config["errorUtilizationPenalty"] = Json::FromNumber(value);
140     }
141     return Json::Object{
142         {"weighted_round_robin", Json::FromObject(std::move(config))}};
143   }
144 
type()145   absl::string_view type() override { return Type(); }
146 
Type()147   static absl::string_view Type() {
148     return "envoy.extensions.load_balancing_policies.client_side_weighted_"
149            "round_robin.v3.ClientSideWeightedRoundRobin";
150   }
151 };
152 
153 class RingHashLbPolicyConfigFactory
154     : public XdsLbPolicyRegistry::ConfigFactory {
155  public:
ConvertXdsLbPolicyConfig(const XdsLbPolicyRegistry *,const XdsResourceType::DecodeContext & context,absl::string_view configuration,ValidationErrors * errors,int)156   Json::Object ConvertXdsLbPolicyConfig(
157       const XdsLbPolicyRegistry* /*registry*/,
158       const XdsResourceType::DecodeContext& context,
159       absl::string_view configuration, ValidationErrors* errors,
160       int /*recursion_depth*/) override {
161     const auto* resource =
162         envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_parse(
163             configuration.data(), configuration.size(), context.arena);
164     if (resource == nullptr) {
165       errors->AddError("can't decode RingHash LB policy config");
166       return {};
167     }
168     if (envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_hash_function(
169             resource) !=
170             envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_XX_HASH &&
171         envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_hash_function(
172             resource) !=
173             envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_DEFAULT_HASH) {
174       ValidationErrors::ScopedField field(errors, ".hash_function");
175       errors->AddError("unsupported value (must be XX_HASH)");
176     }
177     uint64_t max_ring_size = 8388608;
178     const auto* uint64_value =
179         envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_maximum_ring_size(
180             resource);
181     if (uint64_value != nullptr) {
182       max_ring_size = google_protobuf_UInt64Value_value(uint64_value);
183       if (max_ring_size == 0 || max_ring_size > 8388608) {
184         ValidationErrors::ScopedField field(errors, ".maximum_ring_size");
185         errors->AddError("value must be in the range [1, 8388608]");
186       }
187     }
188     uint64_t min_ring_size = 1024;
189     uint64_value =
190         envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_minimum_ring_size(
191             resource);
192     if (uint64_value != nullptr) {
193       min_ring_size = google_protobuf_UInt64Value_value(uint64_value);
194       ValidationErrors::ScopedField field(errors, ".minimum_ring_size");
195       if (min_ring_size == 0 || min_ring_size > 8388608) {
196         errors->AddError("value must be in the range [1, 8388608]");
197       }
198       if (min_ring_size > max_ring_size) {
199         errors->AddError("cannot be greater than maximum_ring_size");
200       }
201     }
202     return Json::Object{
203         {"ring_hash_experimental",
204          Json::FromObject({
205              {"minRingSize", Json::FromNumber(min_ring_size)},
206              {"maxRingSize", Json::FromNumber(max_ring_size)},
207          })},
208     };
209   }
210 
type()211   absl::string_view type() override { return Type(); }
212 
Type()213   static absl::string_view Type() {
214     return "envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash";
215   }
216 };
217 
218 class WrrLocalityLbPolicyConfigFactory
219     : public XdsLbPolicyRegistry::ConfigFactory {
220  public:
ConvertXdsLbPolicyConfig(const XdsLbPolicyRegistry * registry,const XdsResourceType::DecodeContext & context,absl::string_view configuration,ValidationErrors * errors,int recursion_depth)221   Json::Object ConvertXdsLbPolicyConfig(
222       const XdsLbPolicyRegistry* registry,
223       const XdsResourceType::DecodeContext& context,
224       absl::string_view configuration, ValidationErrors* errors,
225       int recursion_depth) override {
226     const auto* resource =
227         envoy_extensions_load_balancing_policies_wrr_locality_v3_WrrLocality_parse(
228             configuration.data(), configuration.size(), context.arena);
229     if (resource == nullptr) {
230       errors->AddError("can't decode WrrLocality LB policy config");
231       return {};
232     }
233     ValidationErrors::ScopedField field(errors, ".endpoint_picking_policy");
234     const auto* endpoint_picking_policy =
235         envoy_extensions_load_balancing_policies_wrr_locality_v3_WrrLocality_endpoint_picking_policy(
236             resource);
237     if (endpoint_picking_policy == nullptr) {
238       errors->AddError("field not present");
239       return {};
240     }
241     auto child_policy = registry->ConvertXdsLbPolicyConfig(
242         context, endpoint_picking_policy, errors, recursion_depth + 1);
243     return Json::Object{
244         {"xds_wrr_locality_experimental",
245          Json::FromObject(
246              {{"childPolicy", Json::FromArray(std::move(child_policy))}})}};
247   }
248 
type()249   absl::string_view type() override { return Type(); }
250 
Type()251   static absl::string_view Type() {
252     return "envoy.extensions.load_balancing_policies.wrr_locality.v3."
253            "WrrLocality";
254   }
255 };
256 
257 }  // namespace
258 
259 //
260 // XdsLbPolicyRegistry
261 //
262 
XdsLbPolicyRegistry()263 XdsLbPolicyRegistry::XdsLbPolicyRegistry() {
264   policy_config_factories_.emplace(
265       RingHashLbPolicyConfigFactory::Type(),
266       std::make_unique<RingHashLbPolicyConfigFactory>());
267   policy_config_factories_.emplace(
268       RoundRobinLbPolicyConfigFactory::Type(),
269       std::make_unique<RoundRobinLbPolicyConfigFactory>());
270   policy_config_factories_.emplace(
271       ClientSideWeightedRoundRobinLbPolicyConfigFactory::Type(),
272       std::make_unique<ClientSideWeightedRoundRobinLbPolicyConfigFactory>());
273   policy_config_factories_.emplace(
274       WrrLocalityLbPolicyConfigFactory::Type(),
275       std::make_unique<WrrLocalityLbPolicyConfigFactory>());
276 }
277 
ConvertXdsLbPolicyConfig(const XdsResourceType::DecodeContext & context,const envoy_config_cluster_v3_LoadBalancingPolicy * lb_policy,ValidationErrors * errors,int recursion_depth) const278 Json::Array XdsLbPolicyRegistry::ConvertXdsLbPolicyConfig(
279     const XdsResourceType::DecodeContext& context,
280     const envoy_config_cluster_v3_LoadBalancingPolicy* lb_policy,
281     ValidationErrors* errors, int recursion_depth) const {
282   constexpr int kMaxRecursionDepth = 16;
283   if (recursion_depth >= kMaxRecursionDepth) {
284     errors->AddError(
285         absl::StrCat("exceeded max recursion depth of ", kMaxRecursionDepth));
286     return {};
287   }
288   const size_t original_error_size = errors->size();
289   size_t size = 0;
290   const auto* policies =
291       envoy_config_cluster_v3_LoadBalancingPolicy_policies(lb_policy, &size);
292   for (size_t i = 0; i < size; ++i) {
293     ValidationErrors::ScopedField field(
294         errors, absl::StrCat(".policies[", i, "].typed_extension_config"));
295     const auto* typed_extension_config =
296         envoy_config_cluster_v3_LoadBalancingPolicy_Policy_typed_extension_config(
297             policies[i]);
298     if (typed_extension_config == nullptr) {
299       errors->AddError("field not present");
300       return {};
301     }
302     ValidationErrors::ScopedField field2(errors, ".typed_config");
303     const auto* typed_config =
304         envoy_config_core_v3_TypedExtensionConfig_typed_config(
305             typed_extension_config);
306     auto extension = ExtractXdsExtension(context, typed_config, errors);
307     if (!extension.has_value()) return {};
308     // Check for registered LB policy type.
309     absl::string_view* serialized_value =
310         absl::get_if<absl::string_view>(&extension->value);
311     if (serialized_value != nullptr) {
312       auto config_factory_it = policy_config_factories_.find(extension->type);
313       if (config_factory_it != policy_config_factories_.end()) {
314         return Json::Array{Json::FromObject(
315             config_factory_it->second->ConvertXdsLbPolicyConfig(
316                 this, context, *serialized_value, errors, recursion_depth))};
317       }
318     }
319     // Check for custom LB policy type.
320     Json* json = absl::get_if<Json>(&extension->value);
321     if (json != nullptr &&
322         CoreConfiguration::Get().lb_policy_registry().LoadBalancingPolicyExists(
323             extension->type, nullptr)) {
324       return Json::Array{
325           Json::FromObject({{std::string(extension->type), std::move(*json)}})};
326     }
327     // Unsupported type.  Continue to next entry.
328   }
329   if (original_error_size == errors->size()) {
330     errors->AddError("no supported load balancing policy config found");
331   }
332   return {};
333 }
334 
335 }  // namespace grpc_core
336