• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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