xref: /aosp_15_r20/external/grpc-grpc/test/core/client_channel/retry_service_config_test.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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