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