1 //
2 // Copyright 2019 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 "src/core/client_channel/retry_service_config.h"
18
19 #include "absl/status/status.h"
20 #include "absl/status/statusor.h"
21 #include "gtest/gtest.h"
22
23 #include <grpc/grpc.h>
24 #include <grpc/impl/channel_arg_names.h>
25 #include <grpc/slice.h>
26 #include <grpc/status.h>
27
28 #include "src/core/lib/channel/channel_args.h"
29 #include "src/core/lib/config/core_configuration.h"
30 #include "src/core/lib/gprpp/ref_counted_ptr.h"
31 #include "src/core/lib/gprpp/time.h"
32 #include "src/core/service_config/service_config.h"
33 #include "src/core/service_config/service_config_impl.h"
34 #include "src/core/service_config/service_config_parser.h"
35 #include "test/core/util/test_config.h"
36
37 namespace grpc_core {
38 namespace testing {
39
40 class RetryParserTest : public ::testing::Test {
41 protected:
SetUp()42 void SetUp() override {
43 parser_index_ =
44 CoreConfiguration::Get().service_config_parser().GetParserIndex(
45 "retry");
46 }
47
48 size_t parser_index_;
49 };
50
TEST_F(RetryParserTest,ValidRetryThrottling)51 TEST_F(RetryParserTest, ValidRetryThrottling) {
52 const char* test_json =
53 "{\n"
54 " \"retryThrottling\": {\n"
55 " \"maxTokens\": 2,\n"
56 " \"tokenRatio\": 1.0\n"
57 " }\n"
58 "}";
59 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
60 ASSERT_TRUE(service_config.ok()) << service_config.status();
61 const auto* parsed_config = static_cast<internal::RetryGlobalConfig*>(
62 (*service_config)->GetGlobalParsedConfig(parser_index_));
63 ASSERT_NE(parsed_config, nullptr);
64 EXPECT_EQ(parsed_config->max_milli_tokens(), 2000);
65 EXPECT_EQ(parsed_config->milli_token_ratio(), 1000);
66 }
67
TEST_F(RetryParserTest,RetryThrottlingMissingFields)68 TEST_F(RetryParserTest, RetryThrottlingMissingFields) {
69 const char* test_json =
70 "{\n"
71 " \"retryThrottling\": {\n"
72 " }\n"
73 "}";
74 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
75 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
76 EXPECT_EQ(service_config.status().message(),
77 "errors validating service config: ["
78 "field:retryThrottling.maxTokens error:field not present; "
79 "field:retryThrottling.tokenRatio error:field not present]")
80 << service_config.status();
81 }
82
TEST_F(RetryParserTest,InvalidRetryThrottlingNegativeMaxTokens)83 TEST_F(RetryParserTest, InvalidRetryThrottlingNegativeMaxTokens) {
84 const char* test_json =
85 "{\n"
86 " \"retryThrottling\": {\n"
87 " \"maxTokens\": -2,\n"
88 " \"tokenRatio\": 1.0\n"
89 " }\n"
90 "}";
91 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
92 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
93 EXPECT_EQ(service_config.status().message(),
94 "errors validating service config: ["
95 "field:retryThrottling.maxTokens error:"
96 "failed to parse non-negative number]")
97 << service_config.status();
98 }
99
TEST_F(RetryParserTest,InvalidRetryThrottlingInvalidTokenRatio)100 TEST_F(RetryParserTest, InvalidRetryThrottlingInvalidTokenRatio) {
101 const char* test_json =
102 "{\n"
103 " \"retryThrottling\": {\n"
104 " \"maxTokens\": 2,\n"
105 " \"tokenRatio\": -1\n"
106 " }\n"
107 "}";
108 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
109 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
110 EXPECT_EQ(service_config.status().message(),
111 "errors validating service config: ["
112 "field:retryThrottling.tokenRatio error:"
113 "could not parse as a number]");
114 }
115
TEST_F(RetryParserTest,ValidRetryPolicy)116 TEST_F(RetryParserTest, ValidRetryPolicy) {
117 const char* test_json =
118 "{\n"
119 " \"methodConfig\": [ {\n"
120 " \"name\": [\n"
121 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
122 " ],\n"
123 " \"retryPolicy\": {\n"
124 " \"maxAttempts\": 3,\n"
125 " \"initialBackoff\": \"1s\",\n"
126 " \"maxBackoff\": \"120s\",\n"
127 " \"backoffMultiplier\": 1.6,\n"
128 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
129 " }\n"
130 " } ]\n"
131 "}";
132 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
133 ASSERT_TRUE(service_config.ok()) << service_config.status();
134 const auto* vector_ptr =
135 (*service_config)
136 ->GetMethodParsedConfigVector(
137 grpc_slice_from_static_string("/TestServ/TestMethod"));
138 ASSERT_NE(vector_ptr, nullptr);
139 const auto* parsed_config = static_cast<internal::RetryMethodConfig*>(
140 ((*vector_ptr)[parser_index_]).get());
141 ASSERT_NE(parsed_config, nullptr);
142 EXPECT_EQ(parsed_config->max_attempts(), 3);
143 EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1));
144 EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2));
145 EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f);
146 EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), absl::nullopt);
147 EXPECT_TRUE(
148 parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED));
149 }
150
TEST_F(RetryParserTest,InvalidRetryPolicyWrongType)151 TEST_F(RetryParserTest, InvalidRetryPolicyWrongType) {
152 const char* test_json =
153 "{\n"
154 " \"methodConfig\": [ {\n"
155 " \"name\": [\n"
156 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
157 " ],\n"
158 " \"retryPolicy\": 5\n"
159 " } ]\n"
160 "}";
161 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
162 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
163 EXPECT_EQ(service_config.status().message(),
164 "errors validating service config: ["
165 "field:methodConfig[0].retryPolicy error:is not an object]")
166 << service_config.status();
167 }
168
TEST_F(RetryParserTest,InvalidRetryPolicyRequiredFieldsMissing)169 TEST_F(RetryParserTest, InvalidRetryPolicyRequiredFieldsMissing) {
170 const char* test_json =
171 "{\n"
172 " \"methodConfig\": [ {\n"
173 " \"name\": [\n"
174 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
175 " ],\n"
176 " \"retryPolicy\": {\n"
177 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
178 " }\n"
179 " } ]\n"
180 "}";
181 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
182 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
183 EXPECT_EQ(service_config.status().message(),
184 "errors validating service config: ["
185 "field:methodConfig[0].retryPolicy.backoffMultiplier "
186 "error:field not present; "
187 "field:methodConfig[0].retryPolicy.initialBackoff "
188 "error:field not present; "
189 "field:methodConfig[0].retryPolicy.maxAttempts "
190 "error:field not present; "
191 "field:methodConfig[0].retryPolicy.maxBackoff "
192 "error:field not present]")
193 << service_config.status();
194 }
195
TEST_F(RetryParserTest,InvalidRetryPolicyMaxAttemptsWrongType)196 TEST_F(RetryParserTest, InvalidRetryPolicyMaxAttemptsWrongType) {
197 const char* test_json =
198 "{\n"
199 " \"methodConfig\": [ {\n"
200 " \"name\": [\n"
201 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
202 " ],\n"
203 " \"retryPolicy\": {\n"
204 " \"maxAttempts\": \"FOO\",\n"
205 " \"initialBackoff\": \"1s\",\n"
206 " \"maxBackoff\": \"120s\",\n"
207 " \"backoffMultiplier\": 1.6,\n"
208 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
209 " }\n"
210 " } ]\n"
211 "}";
212 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
213 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
214 EXPECT_EQ(service_config.status().message(),
215 "errors validating service config: ["
216 "field:methodConfig[0].retryPolicy.maxAttempts "
217 "error:failed to parse number]")
218 << service_config.status();
219 }
220
TEST_F(RetryParserTest,InvalidRetryPolicyMaxAttemptsBadValue)221 TEST_F(RetryParserTest, InvalidRetryPolicyMaxAttemptsBadValue) {
222 const char* test_json =
223 "{\n"
224 " \"methodConfig\": [ {\n"
225 " \"name\": [\n"
226 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
227 " ],\n"
228 " \"retryPolicy\": {\n"
229 " \"maxAttempts\": 1,\n"
230 " \"initialBackoff\": \"1s\",\n"
231 " \"maxBackoff\": \"120s\",\n"
232 " \"backoffMultiplier\": 1.6,\n"
233 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
234 " }\n"
235 " } ]\n"
236 "}";
237 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
238 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
239 EXPECT_EQ(service_config.status().message(),
240 "errors validating service config: ["
241 "field:methodConfig[0].retryPolicy.maxAttempts "
242 "error:must be at least 2]")
243 << service_config.status();
244 }
245
TEST_F(RetryParserTest,InvalidRetryPolicyInitialBackoffWrongType)246 TEST_F(RetryParserTest, InvalidRetryPolicyInitialBackoffWrongType) {
247 const char* test_json =
248 "{\n"
249 " \"methodConfig\": [ {\n"
250 " \"name\": [\n"
251 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
252 " ],\n"
253 " \"retryPolicy\": {\n"
254 " \"maxAttempts\": 2,\n"
255 " \"initialBackoff\": \"1sec\",\n"
256 " \"maxBackoff\": \"120s\",\n"
257 " \"backoffMultiplier\": 1.6,\n"
258 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
259 " }\n"
260 " } ]\n"
261 "}";
262 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
263 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
264 EXPECT_EQ(service_config.status().message(),
265 "errors validating service config: ["
266 "field:methodConfig[0].retryPolicy.initialBackoff "
267 "error:Not a duration (no s suffix)]")
268 << service_config.status();
269 }
270
TEST_F(RetryParserTest,InvalidRetryPolicyInitialBackoffBadValue)271 TEST_F(RetryParserTest, InvalidRetryPolicyInitialBackoffBadValue) {
272 const char* test_json =
273 "{\n"
274 " \"methodConfig\": [ {\n"
275 " \"name\": [\n"
276 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
277 " ],\n"
278 " \"retryPolicy\": {\n"
279 " \"maxAttempts\": 2,\n"
280 " \"initialBackoff\": \"0s\",\n"
281 " \"maxBackoff\": \"120s\",\n"
282 " \"backoffMultiplier\": 1.6,\n"
283 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
284 " }\n"
285 " } ]\n"
286 "}";
287 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
288 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
289 EXPECT_EQ(service_config.status().message(),
290 "errors validating service config: ["
291 "field:methodConfig[0].retryPolicy.initialBackoff "
292 "error:must be greater than 0]")
293 << service_config.status();
294 }
295
TEST_F(RetryParserTest,InvalidRetryPolicyMaxBackoffWrongType)296 TEST_F(RetryParserTest, InvalidRetryPolicyMaxBackoffWrongType) {
297 const char* test_json =
298 "{\n"
299 " \"methodConfig\": [ {\n"
300 " \"name\": [\n"
301 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
302 " ],\n"
303 " \"retryPolicy\": {\n"
304 " \"maxAttempts\": 2,\n"
305 " \"initialBackoff\": \"1s\",\n"
306 " \"maxBackoff\": \"120sec\",\n"
307 " \"backoffMultiplier\": 1.6,\n"
308 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
309 " }\n"
310 " } ]\n"
311 "}";
312 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
313 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
314 EXPECT_EQ(service_config.status().message(),
315 "errors validating service config: ["
316 "field:methodConfig[0].retryPolicy.maxBackoff "
317 "error:Not a duration (no s suffix)]")
318 << service_config.status();
319 }
320
TEST_F(RetryParserTest,InvalidRetryPolicyMaxBackoffBadValue)321 TEST_F(RetryParserTest, InvalidRetryPolicyMaxBackoffBadValue) {
322 const char* test_json =
323 "{\n"
324 " \"methodConfig\": [ {\n"
325 " \"name\": [\n"
326 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
327 " ],\n"
328 " \"retryPolicy\": {\n"
329 " \"maxAttempts\": 2,\n"
330 " \"initialBackoff\": \"1s\",\n"
331 " \"maxBackoff\": \"0s\",\n"
332 " \"backoffMultiplier\": 1.6,\n"
333 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
334 " }\n"
335 " } ]\n"
336 "}";
337 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
338 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
339 EXPECT_EQ(service_config.status().message(),
340 "errors validating service config: ["
341 "field:methodConfig[0].retryPolicy.maxBackoff "
342 "error:must be greater than 0]")
343 << service_config.status();
344 }
345
TEST_F(RetryParserTest,InvalidRetryPolicyBackoffMultiplierWrongType)346 TEST_F(RetryParserTest, InvalidRetryPolicyBackoffMultiplierWrongType) {
347 const char* test_json =
348 "{\n"
349 " \"methodConfig\": [ {\n"
350 " \"name\": [\n"
351 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
352 " ],\n"
353 " \"retryPolicy\": {\n"
354 " \"maxAttempts\": 2,\n"
355 " \"initialBackoff\": \"1s\",\n"
356 " \"maxBackoff\": \"120s\",\n"
357 " \"backoffMultiplier\": [],\n"
358 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
359 " }\n"
360 " } ]\n"
361 "}";
362 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
363 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
364 EXPECT_EQ(service_config.status().message(),
365 "errors validating service config: ["
366 "field:methodConfig[0].retryPolicy.backoffMultiplier "
367 "error:is not a number]")
368 << service_config.status();
369 }
370
TEST_F(RetryParserTest,InvalidRetryPolicyBackoffMultiplierBadValue)371 TEST_F(RetryParserTest, InvalidRetryPolicyBackoffMultiplierBadValue) {
372 const char* test_json =
373 "{\n"
374 " \"methodConfig\": [ {\n"
375 " \"name\": [\n"
376 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
377 " ],\n"
378 " \"retryPolicy\": {\n"
379 " \"maxAttempts\": 2,\n"
380 " \"initialBackoff\": \"1s\",\n"
381 " \"maxBackoff\": \"120s\",\n"
382 " \"backoffMultiplier\": 0,\n"
383 " \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
384 " }\n"
385 " } ]\n"
386 "}";
387 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
388 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
389 EXPECT_EQ(service_config.status().message(),
390 "errors validating service config: ["
391 "field:methodConfig[0].retryPolicy.backoffMultiplier "
392 "error:must be greater than 0]")
393 << service_config.status();
394 }
395
TEST_F(RetryParserTest,InvalidRetryPolicyEmptyRetryableStatusCodes)396 TEST_F(RetryParserTest, InvalidRetryPolicyEmptyRetryableStatusCodes) {
397 const char* test_json =
398 "{\n"
399 " \"methodConfig\": [ {\n"
400 " \"name\": [\n"
401 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
402 " ],\n"
403 " \"retryPolicy\": {\n"
404 " \"maxAttempts\": 2,\n"
405 " \"initialBackoff\": \"1s\",\n"
406 " \"maxBackoff\": \"120s\",\n"
407 " \"backoffMultiplier\": \"1.6\",\n"
408 " \"retryableStatusCodes\": []\n"
409 " }\n"
410 " } ]\n"
411 "}";
412 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
413 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
414 EXPECT_EQ(service_config.status().message(),
415 "errors validating service config: ["
416 "field:methodConfig[0].retryPolicy.retryableStatusCodes "
417 "error:must be non-empty]")
418 << service_config.status();
419 }
420
TEST_F(RetryParserTest,InvalidRetryPolicyRetryableStatusCodesWrongType)421 TEST_F(RetryParserTest, InvalidRetryPolicyRetryableStatusCodesWrongType) {
422 const char* test_json =
423 "{\n"
424 " \"methodConfig\": [ {\n"
425 " \"name\": [\n"
426 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
427 " ],\n"
428 " \"retryPolicy\": {\n"
429 " \"maxAttempts\": 2,\n"
430 " \"initialBackoff\": \"1s\",\n"
431 " \"maxBackoff\": \"120s\",\n"
432 " \"backoffMultiplier\": \"1.6\",\n"
433 " \"retryableStatusCodes\": 0\n"
434 " }\n"
435 " } ]\n"
436 "}";
437 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
438 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
439 EXPECT_EQ(service_config.status().message(),
440 "errors validating service config: ["
441 "field:methodConfig[0].retryPolicy.retryableStatusCodes "
442 "error:is not an array]")
443 << service_config.status();
444 }
445
TEST_F(RetryParserTest,InvalidRetryPolicyRetryableStatusCodesElementsWrongType)446 TEST_F(RetryParserTest,
447 InvalidRetryPolicyRetryableStatusCodesElementsWrongType) {
448 const char* test_json =
449 "{\n"
450 " \"methodConfig\": [ {\n"
451 " \"name\": [\n"
452 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
453 " ],\n"
454 " \"retryPolicy\": {\n"
455 " \"maxAttempts\": 2,\n"
456 " \"initialBackoff\": \"1s\",\n"
457 " \"maxBackoff\": \"120s\",\n"
458 " \"backoffMultiplier\": \"1.6\",\n"
459 " \"retryableStatusCodes\": [true, 2]\n"
460 " }\n"
461 " } ]\n"
462 "}";
463 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
464 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
465 EXPECT_EQ(service_config.status().message(),
466 "errors validating service config: ["
467 "field:methodConfig[0].retryPolicy.retryableStatusCodes "
468 "error:must be non-empty; "
469 "field:methodConfig[0].retryPolicy.retryableStatusCodes[0] "
470 "error:is not a string; "
471 "field:methodConfig[0].retryPolicy.retryableStatusCodes[1] "
472 "error:is not a string]")
473 << service_config.status();
474 }
475
TEST_F(RetryParserTest,InvalidRetryPolicyUnparseableRetryableStatusCodes)476 TEST_F(RetryParserTest, InvalidRetryPolicyUnparseableRetryableStatusCodes) {
477 const char* test_json =
478 "{\n"
479 " \"methodConfig\": [ {\n"
480 " \"name\": [\n"
481 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
482 " ],\n"
483 " \"retryPolicy\": {\n"
484 " \"maxAttempts\": 2,\n"
485 " \"initialBackoff\": \"1s\",\n"
486 " \"maxBackoff\": \"120s\",\n"
487 " \"backoffMultiplier\": \"1.6\",\n"
488 " \"retryableStatusCodes\": [\"FOO\", \"BAR\"]\n"
489 " }\n"
490 " } ]\n"
491 "}";
492 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
493 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
494 EXPECT_EQ(service_config.status().message(),
495 "errors validating service config: ["
496 "field:methodConfig[0].retryPolicy.retryableStatusCodes "
497 "error:must be non-empty; "
498 "field:methodConfig[0].retryPolicy.retryableStatusCodes[0] "
499 "error:failed to parse status code; "
500 "field:methodConfig[0].retryPolicy.retryableStatusCodes[1] "
501 "error:failed to parse status code]")
502 << service_config.status();
503 }
504
TEST_F(RetryParserTest,ValidRetryPolicyWithPerAttemptRecvTimeout)505 TEST_F(RetryParserTest, ValidRetryPolicyWithPerAttemptRecvTimeout) {
506 const char* test_json =
507 "{\n"
508 " \"methodConfig\": [ {\n"
509 " \"name\": [\n"
510 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
511 " ],\n"
512 " \"retryPolicy\": {\n"
513 " \"maxAttempts\": 2,\n"
514 " \"initialBackoff\": \"1s\",\n"
515 " \"maxBackoff\": \"120s\",\n"
516 " \"backoffMultiplier\": 1.6,\n"
517 " \"perAttemptRecvTimeout\": \"1s\",\n"
518 " \"retryableStatusCodes\": [\"ABORTED\"]\n"
519 " }\n"
520 " } ]\n"
521 "}";
522 const ChannelArgs args =
523 ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1);
524 auto service_config = ServiceConfigImpl::Create(args, test_json);
525 ASSERT_TRUE(service_config.ok()) << service_config.status();
526 const auto* vector_ptr =
527 (*service_config)
528 ->GetMethodParsedConfigVector(
529 grpc_slice_from_static_string("/TestServ/TestMethod"));
530 ASSERT_NE(vector_ptr, nullptr);
531 const auto* parsed_config = static_cast<internal::RetryMethodConfig*>(
532 ((*vector_ptr)[parser_index_]).get());
533 ASSERT_NE(parsed_config, nullptr);
534 EXPECT_EQ(parsed_config->max_attempts(), 2);
535 EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1));
536 EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2));
537 EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f);
538 EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), Duration::Seconds(1));
539 EXPECT_TRUE(
540 parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED));
541 }
542
TEST_F(RetryParserTest,ValidRetryPolicyWithPerAttemptRecvTimeoutIgnoredWhenHedgingDisabled)543 TEST_F(RetryParserTest,
544 ValidRetryPolicyWithPerAttemptRecvTimeoutIgnoredWhenHedgingDisabled) {
545 const char* test_json =
546 "{\n"
547 " \"methodConfig\": [ {\n"
548 " \"name\": [\n"
549 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
550 " ],\n"
551 " \"retryPolicy\": {\n"
552 " \"maxAttempts\": 2,\n"
553 " \"initialBackoff\": \"1s\",\n"
554 " \"maxBackoff\": \"120s\",\n"
555 " \"backoffMultiplier\": 1.6,\n"
556 " \"perAttemptRecvTimeout\": \"1s\",\n"
557 " \"retryableStatusCodes\": [\"ABORTED\"]\n"
558 " }\n"
559 " } ]\n"
560 "}";
561 auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json);
562 ASSERT_TRUE(service_config.ok()) << service_config.status();
563 const auto* vector_ptr =
564 (*service_config)
565 ->GetMethodParsedConfigVector(
566 grpc_slice_from_static_string("/TestServ/TestMethod"));
567 ASSERT_NE(vector_ptr, nullptr);
568 const auto* parsed_config = static_cast<internal::RetryMethodConfig*>(
569 ((*vector_ptr)[parser_index_]).get());
570 ASSERT_NE(parsed_config, nullptr);
571 EXPECT_EQ(parsed_config->max_attempts(), 2);
572 EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1));
573 EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2));
574 EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f);
575 EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), absl::nullopt);
576 EXPECT_TRUE(
577 parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED));
578 }
579
TEST_F(RetryParserTest,ValidRetryPolicyWithPerAttemptRecvTimeoutAndUnsetRetryableStatusCodes)580 TEST_F(RetryParserTest,
581 ValidRetryPolicyWithPerAttemptRecvTimeoutAndUnsetRetryableStatusCodes) {
582 const char* test_json =
583 "{\n"
584 " \"methodConfig\": [ {\n"
585 " \"name\": [\n"
586 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
587 " ],\n"
588 " \"retryPolicy\": {\n"
589 " \"maxAttempts\": 2,\n"
590 " \"initialBackoff\": \"1s\",\n"
591 " \"maxBackoff\": \"120s\",\n"
592 " \"backoffMultiplier\": 1.6,\n"
593 " \"perAttemptRecvTimeout\": \"1s\"\n"
594 " }\n"
595 " } ]\n"
596 "}";
597 const ChannelArgs args =
598 ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1);
599 auto service_config = ServiceConfigImpl::Create(args, test_json);
600 ASSERT_TRUE(service_config.ok()) << service_config.status();
601 const auto* vector_ptr =
602 (*service_config)
603 ->GetMethodParsedConfigVector(
604 grpc_slice_from_static_string("/TestServ/TestMethod"));
605 ASSERT_NE(vector_ptr, nullptr);
606 const auto* parsed_config = static_cast<internal::RetryMethodConfig*>(
607 ((*vector_ptr)[parser_index_]).get());
608 ASSERT_NE(parsed_config, nullptr);
609 EXPECT_EQ(parsed_config->max_attempts(), 2);
610 EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1));
611 EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2));
612 EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f);
613 EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), Duration::Seconds(1));
614 EXPECT_TRUE(parsed_config->retryable_status_codes().Empty());
615 }
616
TEST_F(RetryParserTest,InvalidRetryPolicyPerAttemptRecvTimeoutUnparseable)617 TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutUnparseable) {
618 const char* test_json =
619 "{\n"
620 " \"methodConfig\": [ {\n"
621 " \"name\": [\n"
622 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
623 " ],\n"
624 " \"retryPolicy\": {\n"
625 " \"maxAttempts\": 2,\n"
626 " \"initialBackoff\": \"1s\",\n"
627 " \"maxBackoff\": \"120s\",\n"
628 " \"backoffMultiplier\": \"1.6\",\n"
629 " \"perAttemptRecvTimeout\": \"1sec\",\n"
630 " \"retryableStatusCodes\": [\"ABORTED\"]\n"
631 " }\n"
632 " } ]\n"
633 "}";
634 const ChannelArgs args =
635 ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1);
636 auto service_config = ServiceConfigImpl::Create(args, test_json);
637 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
638 EXPECT_EQ(service_config.status().message(),
639 "errors validating service config: ["
640 "field:methodConfig[0].retryPolicy.perAttemptRecvTimeout "
641 "error:Not a duration (no s suffix)]")
642 << service_config.status();
643 }
644
TEST_F(RetryParserTest,InvalidRetryPolicyPerAttemptRecvTimeoutWrongType)645 TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutWrongType) {
646 const char* test_json =
647 "{\n"
648 " \"methodConfig\": [ {\n"
649 " \"name\": [\n"
650 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
651 " ],\n"
652 " \"retryPolicy\": {\n"
653 " \"maxAttempts\": 2,\n"
654 " \"initialBackoff\": \"1s\",\n"
655 " \"maxBackoff\": \"120s\",\n"
656 " \"backoffMultiplier\": \"1.6\",\n"
657 " \"perAttemptRecvTimeout\": 1,\n"
658 " \"retryableStatusCodes\": [\"ABORTED\"]\n"
659 " }\n"
660 " } ]\n"
661 "}";
662 const ChannelArgs args =
663 ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1);
664 auto service_config = ServiceConfigImpl::Create(args, test_json);
665 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
666 EXPECT_EQ(service_config.status().message(),
667 "errors validating service config: ["
668 "field:methodConfig[0].retryPolicy.perAttemptRecvTimeout "
669 "error:is not a string]")
670 << service_config.status();
671 }
672
TEST_F(RetryParserTest,InvalidRetryPolicyPerAttemptRecvTimeoutBadValue)673 TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutBadValue) {
674 const char* test_json =
675 "{\n"
676 " \"methodConfig\": [ {\n"
677 " \"name\": [\n"
678 " { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n"
679 " ],\n"
680 " \"retryPolicy\": {\n"
681 " \"maxAttempts\": 2,\n"
682 " \"initialBackoff\": \"1s\",\n"
683 " \"maxBackoff\": \"120s\",\n"
684 " \"backoffMultiplier\": \"1.6\",\n"
685 " \"perAttemptRecvTimeout\": \"0s\",\n"
686 " \"retryableStatusCodes\": [\"ABORTED\"]\n"
687 " }\n"
688 " } ]\n"
689 "}";
690 const ChannelArgs args =
691 ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1);
692 auto service_config = ServiceConfigImpl::Create(args, test_json);
693 EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument);
694 EXPECT_EQ(service_config.status().message(),
695 "errors validating service config: ["
696 "field:methodConfig[0].retryPolicy.perAttemptRecvTimeout "
697 "error:must be greater than 0]")
698 << service_config.status();
699 }
700
701 } // namespace testing
702 } // namespace grpc_core
703
main(int argc,char ** argv)704 int main(int argc, char** argv) {
705 ::testing::InitGoogleTest(&argc, argv);
706 grpc::testing::TestEnvironment env(&argc, argv);
707 grpc_init();
708 int ret = RUN_ALL_TESTS();
709 grpc_shutdown();
710 return ret;
711 }
712