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