mirror of https://github.com/grpc/grpc.git
service config: use new JSON API (#30467)
* Declarative JSON parser * Automated change: Fix sanity tests * fix * shrinking stuff a little * static vtables * separate fns * simpler? * make maps work * windows fixes * Automated change: Fix sanity tests * simplify code * Automated change: Fix sanity tests * vtable-test * dont always create vec/map impls for every type * comments * make error consistent * move method private * progress * durations! * Automated change: Fix sanity tests * fix * fix * fix * Automated change: Fix sanity tests * post-load * Automated change: Fix sanity tests * document JsonPostLoad() and add static_assert * don't copy field names, to avoid length limitations * use absl::Status * accept either string or number for numeric values * add test for direct data member of another struct type * remove unused method * add support for retaining part of the JSON wirthout processing * update test for changes in Json::Parse() API * add absl::optional support * Automated change: Fix sanity tests * fix tests, improve error messages, and add overload to parse to existing object * remove overload of LoadFromJson() * change special case for Json to instead use Json::Object * rename resolver_result_parsing to client_channel_service_config * split up service_config_test into a separate test for each component * convert client channel service config parser to new API * fix build * converted retry global params * convert retry method-level parsing, but still need to find a way to control parsing via a channel arg * improve error structure, add missing types, and improve tests * clang-format * Automated change: Fix sanity tests * fix build * add LoadJsonObjectField(), add LoadFromJson() overload that takes an ErrorList parameter, and add tests for parsing bare top-level types * fix msan * Automated change: Fix sanity tests * fix error message * Automated change: Fix sanity tests * fix test * add ability to disable fields * plumb in channel args to disable parsing * Automated change: Fix sanity tests * use const char* instead of absl::string_view for enable_key * fix resolver_component_test * Automated change: Fix sanity tests * work around mac build problem * Automated change: Fix sanity tests * work around gcc6 problem * Automated change: Fix sanity tests * fix build * fix build * don't use alternative builder in tests * convert message size service config parser * convert fault injection service config parser * rename files * add specialization for unique_ptr * avoid moves in client channel service config parser * avoid moves in retry service config parser, and do some cleanup * avoid moves in message_size service config parser * avoid moves for fault injection service config parser, and use internal channel arg * convert rbac service config parser * clang-format * WIP * convert top-level service config parser * clang-format * fix build * fix rbac service config parser test * fix signed-ness problem and reversed-conditional bug * fix unused param * fix json_string method * fix max message length defaults * add copy ctors to appease windows compiler * fix RLS LB config parser test * fix name resolution test * fix build * work around gmock portability bug * fix sanity * add missing build dep * make RBAC principal and permissions movable, not copyable * Revert "make RBAC principal and permissions movable, not copyable" This reverts commitrevert-31481-reland-try53315bccc9
. * attempt to simplify HeaderMatcher and StringMatcher parsing * more bloat reduction in RBAC service config parser * fix sanity * add missing build dep * attempt work-around for MSVC bug * Revert "attempt work-around for MSVC bug" This reverts commite54c89e1e4
. * attempt work-around for Windows build problem * try another work-around * fix sanity * appease clang-tidy * generate_projects * attempt to fix Windows build * more windows fixes * more windows fix * yet more windows fixes * try without noexcept * remove unnecessary boilerplate * code review changes * fix breakage Co-authored-by: Craig Tiller <craig.tiller@gmail.com> Co-authored-by: ctiller <ctiller@users.noreply.github.com> Co-authored-by: Craig Tiller <ctiller@google.com> Co-authored-by: markdroth <markdroth@users.noreply.github.com>
parent
f29f861ed3
commit
b853ccc6db
57 changed files with 3792 additions and 3363 deletions
@ -0,0 +1,153 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/client_channel_service_config.h" |
||||
|
||||
#include <map> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/ascii.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/lib/load_balancing/lb_policy_registry.h" |
||||
|
||||
// As per the retry design, we do not allow more than 5 retry attempts.
|
||||
#define MAX_MAX_RETRY_ATTEMPTS 5 |
||||
|
||||
namespace grpc_core { |
||||
namespace internal { |
||||
|
||||
//
|
||||
// ClientChannelGlobalParsedConfig::HealthCheckConfig
|
||||
//
|
||||
|
||||
const JsonLoaderInterface* |
||||
ClientChannelGlobalParsedConfig::HealthCheckConfig::JsonLoader( |
||||
const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<HealthCheckConfig>() |
||||
.OptionalField("serviceName", &HealthCheckConfig::service_name) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
//
|
||||
// ClientChannelGlobalParsedConfig
|
||||
//
|
||||
|
||||
const JsonLoaderInterface* ClientChannelGlobalParsedConfig::JsonLoader( |
||||
const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<ClientChannelGlobalParsedConfig>() |
||||
// Note: "loadBalancingConfig" requires special handling, so
|
||||
// that field will be parsed in JsonPostLoad() instead.
|
||||
.OptionalField( |
||||
"loadBalancingPolicy", |
||||
&ClientChannelGlobalParsedConfig::parsed_deprecated_lb_policy_) |
||||
.OptionalField("healthCheckConfig", |
||||
&ClientChannelGlobalParsedConfig::health_check_config_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
void ClientChannelGlobalParsedConfig::JsonPostLoad(const Json& json, |
||||
const JsonArgs&, |
||||
ValidationErrors* errors) { |
||||
const auto& lb_policy_registry = |
||||
CoreConfiguration::Get().lb_policy_registry(); |
||||
// Parse LB config.
|
||||
{ |
||||
ValidationErrors::ScopedField field(errors, ".loadBalancingConfig"); |
||||
auto it = json.object_value().find("loadBalancingConfig"); |
||||
if (it != json.object_value().end()) { |
||||
auto config = lb_policy_registry.ParseLoadBalancingConfig(it->second); |
||||
if (!config.ok()) { |
||||
errors->AddError(config.status().message()); |
||||
} else { |
||||
parsed_lb_config_ = std::move(*config); |
||||
} |
||||
} |
||||
} |
||||
// Sanity-check deprecated "loadBalancingPolicy" field.
|
||||
if (!parsed_deprecated_lb_policy_.empty()) { |
||||
ValidationErrors::ScopedField field(errors, ".loadBalancingPolicy"); |
||||
// Convert to lower-case.
|
||||
absl::AsciiStrToLower(&parsed_deprecated_lb_policy_); |
||||
bool requires_config = false; |
||||
if (!lb_policy_registry.LoadBalancingPolicyExists( |
||||
parsed_deprecated_lb_policy_, &requires_config)) { |
||||
errors->AddError(absl::StrCat("unknown LB policy \"", |
||||
parsed_deprecated_lb_policy_, "\"")); |
||||
} else if (requires_config) { |
||||
errors->AddError(absl::StrCat( |
||||
"LB policy \"", parsed_deprecated_lb_policy_, |
||||
"\" requires a config. Please use loadBalancingConfig instead.")); |
||||
} |
||||
} |
||||
} |
||||
|
||||
//
|
||||
// ClientChannelMethodParsedConfig
|
||||
//
|
||||
|
||||
const JsonLoaderInterface* ClientChannelMethodParsedConfig::JsonLoader( |
||||
const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<ClientChannelMethodParsedConfig>() |
||||
.OptionalField("timeout", &ClientChannelMethodParsedConfig::timeout_) |
||||
.OptionalField("waitForReady", |
||||
&ClientChannelMethodParsedConfig::wait_for_ready_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
//
|
||||
// ClientChannelServiceConfigParser
|
||||
//
|
||||
|
||||
size_t ClientChannelServiceConfigParser::ParserIndex() { |
||||
return CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
parser_name()); |
||||
} |
||||
|
||||
void ClientChannelServiceConfigParser::Register( |
||||
CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<ClientChannelServiceConfigParser>()); |
||||
} |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> |
||||
ClientChannelServiceConfigParser::ParseGlobalParams(const ChannelArgs& /*args*/, |
||||
const Json& json, |
||||
ValidationErrors* errors) { |
||||
return LoadFromJson<std::unique_ptr<ClientChannelGlobalParsedConfig>>( |
||||
json, JsonArgs(), errors); |
||||
} |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> |
||||
ClientChannelServiceConfigParser::ParsePerMethodParams( |
||||
const ChannelArgs& /*args*/, const Json& json, ValidationErrors* errors) { |
||||
return LoadFromJson<std::unique_ptr<ClientChannelMethodParsedConfig>>( |
||||
json, JsonArgs(), errors); |
||||
} |
||||
|
||||
} // namespace internal
|
||||
} // namespace grpc_core
|
@ -1,186 +0,0 @@ |
||||
//
|
||||
// Copyright 2018 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/resolver_result_parsing.h" |
||||
|
||||
#include <ctype.h> |
||||
|
||||
#include <algorithm> |
||||
#include <map> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/status_helper.h" |
||||
#include "src/core/lib/iomgr/error.h" |
||||
#include "src/core/lib/json/json_util.h" |
||||
#include "src/core/lib/load_balancing/lb_policy_registry.h" |
||||
|
||||
// As per the retry design, we do not allow more than 5 retry attempts.
|
||||
#define MAX_MAX_RETRY_ATTEMPTS 5 |
||||
|
||||
namespace grpc_core { |
||||
namespace internal { |
||||
|
||||
size_t ClientChannelServiceConfigParser::ParserIndex() { |
||||
return CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
parser_name()); |
||||
} |
||||
|
||||
void ClientChannelServiceConfigParser::Register( |
||||
CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<ClientChannelServiceConfigParser>()); |
||||
} |
||||
|
||||
namespace { |
||||
|
||||
absl::optional<std::string> ParseHealthCheckConfig(const Json& field, |
||||
grpc_error_handle* error) { |
||||
GPR_DEBUG_ASSERT(error != nullptr && error->ok()); |
||||
if (field.type() != Json::Type::OBJECT) { |
||||
*error = GRPC_ERROR_CREATE( |
||||
"field:healthCheckConfig error:should be of type object"); |
||||
return absl::nullopt; |
||||
} |
||||
std::vector<grpc_error_handle> error_list; |
||||
absl::optional<std::string> service_name; |
||||
auto it = field.object_value().find("serviceName"); |
||||
if (it != field.object_value().end()) { |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:serviceName error:should be of type string")); |
||||
} else { |
||||
service_name = it->second.string_value(); |
||||
} |
||||
} |
||||
*error = |
||||
GRPC_ERROR_CREATE_FROM_VECTOR("field:healthCheckConfig", &error_list); |
||||
return service_name; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<std::unique_ptr<ServiceConfigParser::ParsedConfig>> |
||||
ClientChannelServiceConfigParser::ParseGlobalParams(const ChannelArgs& /*args*/, |
||||
const Json& json) { |
||||
std::vector<grpc_error_handle> error_list; |
||||
const auto& lb_policy_registry = |
||||
CoreConfiguration::Get().lb_policy_registry(); |
||||
// Parse LB config.
|
||||
RefCountedPtr<LoadBalancingPolicy::Config> parsed_lb_config; |
||||
auto it = json.object_value().find("loadBalancingConfig"); |
||||
if (it != json.object_value().end()) { |
||||
auto config = lb_policy_registry.ParseLoadBalancingConfig(it->second); |
||||
if (!config.ok()) { |
||||
error_list.push_back(GRPC_ERROR_CREATE(absl::StrCat( |
||||
"field:loadBalancingConfig error:", config.status().message()))); |
||||
} else { |
||||
parsed_lb_config = std::move(*config); |
||||
} |
||||
} |
||||
// Parse deprecated LB policy.
|
||||
std::string lb_policy_name; |
||||
it = json.object_value().find("loadBalancingPolicy"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() != Json::Type::STRING) { |
||||
error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:loadBalancingPolicy error:type should be string")); |
||||
} else { |
||||
lb_policy_name = it->second.string_value(); |
||||
for (size_t i = 0; i < lb_policy_name.size(); ++i) { |
||||
lb_policy_name[i] = tolower(lb_policy_name[i]); |
||||
} |
||||
bool requires_config = false; |
||||
if (!lb_policy_registry.LoadBalancingPolicyExists(lb_policy_name.c_str(), |
||||
&requires_config)) { |
||||
error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:loadBalancingPolicy error:Unknown lb policy")); |
||||
} else if (requires_config) { |
||||
error_list.push_back(GRPC_ERROR_CREATE( |
||||
absl::StrCat("field:loadBalancingPolicy error:", lb_policy_name, |
||||
" requires a config. Please use loadBalancingConfig " |
||||
"instead."))); |
||||
} |
||||
} |
||||
} |
||||
// Parse health check config.
|
||||
absl::optional<std::string> health_check_service_name; |
||||
it = json.object_value().find("healthCheckConfig"); |
||||
if (it != json.object_value().end()) { |
||||
grpc_error_handle parsing_error; |
||||
health_check_service_name = |
||||
ParseHealthCheckConfig(it->second, &parsing_error); |
||||
if (!parsing_error.ok()) { |
||||
error_list.push_back(parsing_error); |
||||
} |
||||
} |
||||
if (!error_list.empty()) { |
||||
grpc_error_handle error = GRPC_ERROR_CREATE_FROM_VECTOR( |
||||
"Client channel global parser", &error_list); |
||||
absl::Status status = absl::InvalidArgumentError( |
||||
absl::StrCat("error parsing client channel global parameters: ", |
||||
StatusToString(error))); |
||||
return status; |
||||
} |
||||
return std::make_unique<ClientChannelGlobalParsedConfig>( |
||||
std::move(parsed_lb_config), std::move(lb_policy_name), |
||||
std::move(health_check_service_name)); |
||||
} |
||||
|
||||
absl::StatusOr<std::unique_ptr<ServiceConfigParser::ParsedConfig>> |
||||
ClientChannelServiceConfigParser::ParsePerMethodParams( |
||||
const ChannelArgs& /*args*/, const Json& json) { |
||||
std::vector<grpc_error_handle> error_list; |
||||
// Parse waitForReady.
|
||||
absl::optional<bool> wait_for_ready; |
||||
auto it = json.object_value().find("waitForReady"); |
||||
if (it != json.object_value().end()) { |
||||
if (it->second.type() == Json::Type::JSON_TRUE) { |
||||
wait_for_ready.emplace(true); |
||||
} else if (it->second.type() == Json::Type::JSON_FALSE) { |
||||
wait_for_ready.emplace(false); |
||||
} else { |
||||
error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:waitForReady error:Type should be true/false")); |
||||
} |
||||
} |
||||
// Parse timeout.
|
||||
Duration timeout; |
||||
ParseJsonObjectFieldAsDuration(json.object_value(), "timeout", &timeout, |
||||
&error_list, false); |
||||
// Return result.
|
||||
if (!error_list.empty()) { |
||||
grpc_error_handle error = |
||||
GRPC_ERROR_CREATE_FROM_VECTOR("Client channel parser", &error_list); |
||||
absl::Status status = absl::InvalidArgumentError( |
||||
absl::StrCat("error parsing client channel method parameters: ", |
||||
StatusToString(error))); |
||||
return status; |
||||
} |
||||
return std::make_unique<ClientChannelMethodParsedConfig>(timeout, |
||||
wait_for_ready); |
||||
} |
||||
|
||||
} // namespace internal
|
||||
} // namespace grpc_core
|
@ -0,0 +1,118 @@ |
||||
//
|
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h" |
||||
|
||||
#include <vector> |
||||
|
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/channel/status_util.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
const JsonLoaderInterface* |
||||
FaultInjectionMethodParsedConfig::FaultInjectionPolicy::JsonLoader( |
||||
const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<FaultInjectionPolicy>() |
||||
.OptionalField("abortMessage", &FaultInjectionPolicy::abort_message) |
||||
.OptionalField("abortCodeHeader", |
||||
&FaultInjectionPolicy::abort_code_header) |
||||
.OptionalField("abortPercentageHeader", |
||||
&FaultInjectionPolicy::abort_percentage_header) |
||||
.OptionalField("abortPercentageNumerator", |
||||
&FaultInjectionPolicy::abort_percentage_numerator) |
||||
.OptionalField("abortPercentageDenominator", |
||||
&FaultInjectionPolicy::abort_percentage_denominator) |
||||
.OptionalField("delay", &FaultInjectionPolicy::delay) |
||||
.OptionalField("delayHeader", &FaultInjectionPolicy::delay_header) |
||||
.OptionalField("delayPercentageHeader", |
||||
&FaultInjectionPolicy::delay_percentage_header) |
||||
.OptionalField("delayPercentageNumerator", |
||||
&FaultInjectionPolicy::delay_percentage_numerator) |
||||
.OptionalField("delayPercentageDenominator", |
||||
&FaultInjectionPolicy::delay_percentage_denominator) |
||||
.OptionalField("maxFaults", &FaultInjectionPolicy::max_faults) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
void FaultInjectionMethodParsedConfig::FaultInjectionPolicy::JsonPostLoad( |
||||
const Json& json, const JsonArgs& args, ValidationErrors* errors) { |
||||
// Parse abort_code.
|
||||
auto abort_code_string = LoadJsonObjectField<std::string>( |
||||
json.object_value(), args, "abortCode", errors, /*required=*/false); |
||||
if (abort_code_string.has_value() && |
||||
!grpc_status_code_from_string(abort_code_string->c_str(), &abort_code)) { |
||||
ValidationErrors::ScopedField field(errors, ".abortCode"); |
||||
errors->AddError("failed to parse status code"); |
||||
} |
||||
// Validate abort_percentage_denominator.
|
||||
if (abort_percentage_denominator != 100 && |
||||
abort_percentage_denominator != 10000 && |
||||
abort_percentage_denominator != 1000000) { |
||||
ValidationErrors::ScopedField field(errors, ".abortPercentageDenominator"); |
||||
errors->AddError("must be one of 100, 10000, or 1000000"); |
||||
} |
||||
// Validate delay_percentage_denominator.
|
||||
if (delay_percentage_denominator != 100 && |
||||
delay_percentage_denominator != 10000 && |
||||
delay_percentage_denominator != 1000000) { |
||||
ValidationErrors::ScopedField field(errors, ".delayPercentageDenominator"); |
||||
errors->AddError("must be one of 100, 10000, or 1000000"); |
||||
} |
||||
} |
||||
|
||||
const JsonLoaderInterface* FaultInjectionMethodParsedConfig::JsonLoader( |
||||
const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<FaultInjectionMethodParsedConfig>() |
||||
.OptionalField( |
||||
"faultInjectionPolicy", |
||||
&FaultInjectionMethodParsedConfig::fault_injection_policies_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> |
||||
FaultInjectionServiceConfigParser::ParsePerMethodParams( |
||||
const ChannelArgs& args, const Json& json, ValidationErrors* errors) { |
||||
// Only parse fault injection policy if the following channel arg is present.
|
||||
if (!args.GetBool(GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG) |
||||
.value_or(false)) { |
||||
return nullptr; |
||||
} |
||||
// Parse fault injection policy from given Json
|
||||
return LoadFromJson<std::unique_ptr<FaultInjectionMethodParsedConfig>>( |
||||
json, JsonArgs(), errors); |
||||
} |
||||
|
||||
void FaultInjectionServiceConfigParser::Register( |
||||
CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<FaultInjectionServiceConfigParser>()); |
||||
} |
||||
|
||||
size_t FaultInjectionServiceConfigParser::ParserIndex() { |
||||
return CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
parser_name()); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -1,185 +0,0 @@ |
||||
//
|
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/fault_injection/service_config_parser.h" |
||||
|
||||
#include <algorithm> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/channel/status_util.h" |
||||
#include "src/core/lib/gprpp/status_helper.h" |
||||
#include "src/core/lib/iomgr/error.h" |
||||
#include "src/core/lib/json/json_util.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
|
||||
std::vector<FaultInjectionMethodParsedConfig::FaultInjectionPolicy> |
||||
ParseFaultInjectionPolicy(const Json::Array& policies_json_array, |
||||
std::vector<grpc_error_handle>* error_list) { |
||||
std::vector<FaultInjectionMethodParsedConfig::FaultInjectionPolicy> policies; |
||||
for (size_t i = 0; i < policies_json_array.size(); i++) { |
||||
FaultInjectionMethodParsedConfig::FaultInjectionPolicy |
||||
fault_injection_policy; |
||||
std::vector<grpc_error_handle> sub_error_list; |
||||
if (policies_json_array[i].type() != Json::Type::OBJECT) { |
||||
error_list->push_back(GRPC_ERROR_CREATE(absl::StrCat( |
||||
"faultInjectionPolicy index ", i, " is not a JSON object"))); |
||||
continue; |
||||
} |
||||
const Json::Object& json_object = policies_json_array[i].object_value(); |
||||
// Parse abort_code
|
||||
std::string abort_code_string; |
||||
if (ParseJsonObjectField(json_object, "abortCode", &abort_code_string, |
||||
&sub_error_list, false)) { |
||||
if (!grpc_status_code_from_string(abort_code_string.c_str(), |
||||
&(fault_injection_policy.abort_code))) { |
||||
sub_error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:abortCode error:failed to parse status code")); |
||||
} |
||||
} |
||||
// Parse abort_message
|
||||
if (!ParseJsonObjectField(json_object, "abortMessage", |
||||
&fault_injection_policy.abort_message, |
||||
&sub_error_list, false)) { |
||||
fault_injection_policy.abort_message = "Fault injected"; |
||||
} |
||||
// Parse abort_code_header
|
||||
ParseJsonObjectField(json_object, "abortCodeHeader", |
||||
&fault_injection_policy.abort_code_header, |
||||
&sub_error_list, false); |
||||
// Parse abort_percentage_header
|
||||
ParseJsonObjectField(json_object, "abortPercentageHeader", |
||||
&fault_injection_policy.abort_percentage_header, |
||||
&sub_error_list, false); |
||||
// Parse abort_percentage_numerator
|
||||
ParseJsonObjectField(json_object, "abortPercentageNumerator", |
||||
&fault_injection_policy.abort_percentage_numerator, |
||||
&sub_error_list, false); |
||||
// Parse abort_percentage_denominator
|
||||
if (ParseJsonObjectField( |
||||
json_object, "abortPercentageDenominator", |
||||
&fault_injection_policy.abort_percentage_denominator, |
||||
&sub_error_list, false)) { |
||||
if (fault_injection_policy.abort_percentage_denominator != 100 && |
||||
fault_injection_policy.abort_percentage_denominator != 10000 && |
||||
fault_injection_policy.abort_percentage_denominator != 1000000) { |
||||
sub_error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:abortPercentageDenominator error:Denominator can only be " |
||||
"one of " |
||||
"100, 10000, 1000000")); |
||||
} |
||||
} |
||||
// Parse delay
|
||||
ParseJsonObjectFieldAsDuration(json_object, "delay", |
||||
&fault_injection_policy.delay, |
||||
&sub_error_list, false); |
||||
// Parse delay_header
|
||||
ParseJsonObjectField(json_object, "delayHeader", |
||||
&fault_injection_policy.delay_header, &sub_error_list, |
||||
false); |
||||
// Parse delay_percentage_header
|
||||
ParseJsonObjectField(json_object, "delayPercentageHeader", |
||||
&fault_injection_policy.delay_percentage_header, |
||||
&sub_error_list, false); |
||||
// Parse delay_percentage_numerator
|
||||
ParseJsonObjectField(json_object, "delayPercentageNumerator", |
||||
&fault_injection_policy.delay_percentage_numerator, |
||||
&sub_error_list, false); |
||||
// Parse delay_percentage_denominator
|
||||
if (ParseJsonObjectField( |
||||
json_object, "delayPercentageDenominator", |
||||
&fault_injection_policy.delay_percentage_denominator, |
||||
&sub_error_list, false)) { |
||||
if (fault_injection_policy.delay_percentage_denominator != 100 && |
||||
fault_injection_policy.delay_percentage_denominator != 10000 && |
||||
fault_injection_policy.delay_percentage_denominator != 1000000) { |
||||
sub_error_list.push_back(GRPC_ERROR_CREATE( |
||||
"field:delayPercentageDenominator error:Denominator can only be " |
||||
"one of " |
||||
"100, 10000, 1000000")); |
||||
} |
||||
} |
||||
// Parse max_faults
|
||||
static_assert( |
||||
std::is_unsigned<decltype(fault_injection_policy.max_faults)>::value, |
||||
"maxFaults should be unsigned"); |
||||
ParseJsonObjectField(json_object, "maxFaults", |
||||
&fault_injection_policy.max_faults, &sub_error_list, |
||||
false); |
||||
if (!sub_error_list.empty()) { |
||||
error_list->push_back(GRPC_ERROR_CREATE_FROM_VECTOR( |
||||
absl::StrCat("failed to parse faultInjectionPolicy index ", i), |
||||
&sub_error_list)); |
||||
} |
||||
policies.push_back(std::move(fault_injection_policy)); |
||||
} |
||||
return policies; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<std::unique_ptr<ServiceConfigParser::ParsedConfig>> |
||||
FaultInjectionServiceConfigParser::ParsePerMethodParams(const ChannelArgs& args, |
||||
const Json& json) { |
||||
// Only parse fault injection policy if the following channel arg is present.
|
||||
if (!args.GetBool(GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG) |
||||
.value_or(false)) { |
||||
return nullptr; |
||||
} |
||||
// Parse fault injection policy from given Json
|
||||
std::vector<FaultInjectionMethodParsedConfig::FaultInjectionPolicy> |
||||
fault_injection_policies; |
||||
std::vector<grpc_error_handle> error_list; |
||||
const Json::Array* policies_json_array; |
||||
if (ParseJsonObjectField(json.object_value(), "faultInjectionPolicy", |
||||
&policies_json_array, &error_list)) { |
||||
fault_injection_policies = |
||||
ParseFaultInjectionPolicy(*policies_json_array, &error_list); |
||||
} |
||||
if (!error_list.empty()) { |
||||
grpc_error_handle error = |
||||
GRPC_ERROR_CREATE_FROM_VECTOR("Fault injection parser", &error_list); |
||||
absl::Status status = absl::InvalidArgumentError( |
||||
absl::StrCat("error parsing fault injection method parameters: ", |
||||
StatusToString(error))); |
||||
return status; |
||||
} |
||||
if (fault_injection_policies.empty()) return nullptr; |
||||
return std::make_unique<FaultInjectionMethodParsedConfig>( |
||||
std::move(fault_injection_policies)); |
||||
} |
||||
|
||||
void FaultInjectionServiceConfigParser::Register( |
||||
CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<FaultInjectionServiceConfigParser>()); |
||||
} |
||||
|
||||
size_t FaultInjectionServiceConfigParser::ParserIndex() { |
||||
return CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
parser_name()); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,315 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include "src/core/ext/filters/client_channel/client_channel_service_config.h" |
||||
|
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/grpc.h> |
||||
#include <grpc/slice.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/service_config/service_config.h" |
||||
#include "src/core/lib/service_config/service_config_impl.h" |
||||
#include "src/core/lib/service_config/service_config_parser.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
// A regular expression to enter referenced or child errors.
|
||||
#define CHILD_ERROR_TAG ".*children.*" |
||||
|
||||
class ClientChannelParserTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() override { |
||||
parser_index_ = |
||||
CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
"client_channel"); |
||||
} |
||||
|
||||
size_t parser_index_; |
||||
}; |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigPickFirst) { |
||||
const char* test_json = "{\"loadBalancingConfig\": [{\"pick_first\":{}}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
auto lb_config = parsed_config->parsed_lb_config(); |
||||
EXPECT_EQ(lb_config->name(), "pick_first"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigRoundRobin) { |
||||
const char* test_json = |
||||
"{\"loadBalancingConfig\": [{\"round_robin\":{}}, {}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
auto parsed_config = static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
auto lb_config = parsed_config->parsed_lb_config(); |
||||
EXPECT_EQ(lb_config->name(), "round_robin"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigGrpclb) { |
||||
const char* test_json = |
||||
"{\"loadBalancingConfig\": " |
||||
"[{\"grpclb\":{\"childPolicy\":[{\"pick_first\":{}}]}}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
auto lb_config = parsed_config->parsed_lb_config(); |
||||
EXPECT_EQ(lb_config->name(), "grpclb"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingConfigXds) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"loadBalancingConfig\":[\n" |
||||
" { \"does_not_exist\":{} },\n" |
||||
" { \"xds_cluster_resolver_experimental\":{\n" |
||||
" \"discoveryMechanisms\": [\n" |
||||
" { \"clusterName\": \"foo\",\n" |
||||
" \"type\": \"EDS\"\n" |
||||
" } ],\n" |
||||
" \"xdsLbPolicy\": [{\"round_robin\":{}}]\n" |
||||
" } }\n" |
||||
" ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
auto lb_config = parsed_config->parsed_lb_config(); |
||||
EXPECT_EQ(lb_config->name(), "xds_cluster_resolver_experimental"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, UnknownLoadBalancingConfig) { |
||||
const char* test_json = "{\"loadBalancingConfig\": [{\"unknown\":{}}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:loadBalancingConfig error:" |
||||
"No known policies in list: unknown]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, InvalidGrpclbLoadBalancingConfig) { |
||||
const char* test_json = |
||||
"{\"loadBalancingConfig\": [" |
||||
" {\"grpclb\":{\"childPolicy\":1}}," |
||||
" {\"round_robin\":{}}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:loadBalancingConfig error:" |
||||
"errors validating grpclb LB policy config: [" |
||||
"field:childPolicy error:type should be array]]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingPolicy) { |
||||
const char* test_json = "{\"loadBalancingPolicy\":\"pick_first\"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
EXPECT_EQ(parsed_config->parsed_deprecated_lb_policy(), "pick_first"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidLoadBalancingPolicyAllCaps) { |
||||
const char* test_json = "{\"loadBalancingPolicy\":\"PICK_FIRST\"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
EXPECT_EQ(parsed_config->parsed_deprecated_lb_policy(), "pick_first"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, UnknownLoadBalancingPolicy) { |
||||
const char* test_json = "{\"loadBalancingPolicy\":\"unknown\"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:loadBalancingPolicy error:unknown LB policy \"unknown\"]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, LoadBalancingPolicyXdsNotAllowed) { |
||||
const char* test_json = |
||||
"{\"loadBalancingPolicy\":\"xds_cluster_resolver_experimental\"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:loadBalancingPolicy error:LB policy " |
||||
"\"xds_cluster_resolver_experimental\" requires a config. Please " |
||||
"use loadBalancingConfig instead.]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidTimeout) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"timeout\": \"5s\"\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
auto parsed_config = ((*vector_ptr)[parser_index_]).get(); |
||||
EXPECT_EQ( |
||||
(static_cast<internal::ClientChannelMethodParsedConfig*>(parsed_config)) |
||||
->timeout(), |
||||
Duration::Seconds(5)); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, InvalidTimeout) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"service\", \"method\": \"method\" }\n" |
||||
" ],\n" |
||||
" \"timeout\": \"5sec\"\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].timeout " |
||||
"error:Not a duration (no s suffix)]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidWaitForReady) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"waitForReady\": true\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
auto parsed_config = ((*vector_ptr)[parser_index_]).get(); |
||||
ASSERT_TRUE( |
||||
(static_cast<internal::ClientChannelMethodParsedConfig*>(parsed_config)) |
||||
->wait_for_ready() |
||||
.has_value()); |
||||
EXPECT_TRUE( |
||||
(static_cast<internal::ClientChannelMethodParsedConfig*>(parsed_config)) |
||||
->wait_for_ready() |
||||
.value()); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, InvalidWaitForReady) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"service\", \"method\": \"method\" }\n" |
||||
" ],\n" |
||||
" \"waitForReady\": \"true\"\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].waitForReady error:is not a boolean]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, ValidHealthCheck) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"healthCheckConfig\": {\n" |
||||
" \"serviceName\": \"health_check_service_name\"\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = |
||||
static_cast<internal::ClientChannelGlobalParsedConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->health_check_service_name(), |
||||
"health_check_service_name"); |
||||
} |
||||
|
||||
TEST_F(ClientChannelParserTest, InvalidHealthCheckMultipleEntries) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"healthCheckConfig\": {\n" |
||||
" \"serviceName\": \"health_check_service_name\"\n" |
||||
" },\n" |
||||
" \"healthCheckConfig\": {\n" |
||||
" \"serviceName\": \"health_check_service_name1\"\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"JSON parsing failed: [" |
||||
"duplicate key \"healthCheckConfig\" at index 104]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
grpc_init(); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
@ -0,0 +1,712 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include "src/core/ext/filters/client_channel/retry_service_config.h" |
||||
|
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/grpc.h> |
||||
#include <grpc/slice.h> |
||||
#include <grpc/status.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/service_config/service_config.h" |
||||
#include "src/core/lib/service_config/service_config_impl.h" |
||||
#include "src/core/lib/service_config/service_config_parser.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
class RetryParserTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() override { |
||||
parser_index_ = |
||||
CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
"retry"); |
||||
} |
||||
|
||||
size_t parser_index_; |
||||
}; |
||||
|
||||
TEST_F(RetryParserTest, ValidRetryThrottling) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"retryThrottling\": {\n" |
||||
" \"maxTokens\": 2,\n" |
||||
" \"tokenRatio\": 1.0\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* parsed_config = static_cast<internal::RetryGlobalConfig*>( |
||||
(*service_config)->GetGlobalParsedConfig(parser_index_)); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_milli_tokens(), 2000); |
||||
EXPECT_EQ(parsed_config->milli_token_ratio(), 1000); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, RetryThrottlingMissingFields) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"retryThrottling\": {\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:retryThrottling.maxTokens error:field not present; " |
||||
"field:retryThrottling.tokenRatio error:field not present]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryThrottlingNegativeMaxTokens) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"retryThrottling\": {\n" |
||||
" \"maxTokens\": -2,\n" |
||||
" \"tokenRatio\": 1.0\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:retryThrottling.maxTokens error:" |
||||
"failed to parse non-negative number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryThrottlingInvalidTokenRatio) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"retryThrottling\": {\n" |
||||
" \"maxTokens\": 2,\n" |
||||
" \"tokenRatio\": -1\n" |
||||
" }\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:retryThrottling.tokenRatio error:" |
||||
"could not parse as a number]"); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, ValidRetryPolicy) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 3,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
const auto* parsed_config = static_cast<internal::RetryMethodConfig*>( |
||||
((*vector_ptr)[parser_index_]).get()); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_attempts(), 3); |
||||
EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1)); |
||||
EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2)); |
||||
EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f); |
||||
EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), absl::nullopt); |
||||
EXPECT_TRUE( |
||||
parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED)); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": 5\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy error:is not an object]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyRequiredFieldsMissing) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.backoffMultiplier " |
||||
"error:field not present; " |
||||
"field:methodConfig[0].retryPolicy.initialBackoff " |
||||
"error:field not present; " |
||||
"field:methodConfig[0].retryPolicy.maxAttempts " |
||||
"error:field not present; " |
||||
"field:methodConfig[0].retryPolicy.maxBackoff " |
||||
"error:field not present]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyMaxAttemptsWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": \"FOO\",\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.maxAttempts " |
||||
"error:failed to parse number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyMaxAttemptsBadValue) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 1,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.maxAttempts " |
||||
"error:must be at least 2]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyInitialBackoffWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1sec\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.initialBackoff " |
||||
"error:Not a duration (no s suffix)]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyInitialBackoffBadValue) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"0s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.initialBackoff " |
||||
"error:must be greater than 0]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyMaxBackoffWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120sec\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.maxBackoff " |
||||
"error:Not a duration (no s suffix)]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyMaxBackoffBadValue) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"0s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.maxBackoff " |
||||
"error:must be greater than 0]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyBackoffMultiplierWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": [],\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.backoffMultiplier " |
||||
"error:is not a number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyBackoffMultiplierBadValue) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 0,\n" |
||||
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.backoffMultiplier " |
||||
"error:must be greater than 0]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyEmptyRetryableStatusCodes) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"retryableStatusCodes\": []\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes " |
||||
"error:must be non-empty]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyRetryableStatusCodesWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"retryableStatusCodes\": 0\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes " |
||||
"error:is not an array]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, |
||||
InvalidRetryPolicyRetryableStatusCodesElementsWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"retryableStatusCodes\": [true, 2]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes " |
||||
"error:must be non-empty; " |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes[0] " |
||||
"error:is not a string; " |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes[1] " |
||||
"error:is not a string]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyUnparseableRetryableStatusCodes) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"retryableStatusCodes\": [\"FOO\", \"BAR\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes " |
||||
"error:must be non-empty; " |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes[0] " |
||||
"error:failed to parse status code; " |
||||
"field:methodConfig[0].retryPolicy.retryableStatusCodes[1] " |
||||
"error:failed to parse status code]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, ValidRetryPolicyWithPerAttemptRecvTimeout) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"perAttemptRecvTimeout\": \"1s\",\n" |
||||
" \"retryableStatusCodes\": [\"ABORTED\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
const ChannelArgs args = |
||||
ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1); |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
const auto* parsed_config = static_cast<internal::RetryMethodConfig*>( |
||||
((*vector_ptr)[parser_index_]).get()); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_attempts(), 2); |
||||
EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1)); |
||||
EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2)); |
||||
EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f); |
||||
EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), Duration::Seconds(1)); |
||||
EXPECT_TRUE( |
||||
parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED)); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, |
||||
ValidRetryPolicyWithPerAttemptRecvTimeoutIgnoredWhenHedgingDisabled) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"perAttemptRecvTimeout\": \"1s\",\n" |
||||
" \"retryableStatusCodes\": [\"ABORTED\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
const auto* parsed_config = static_cast<internal::RetryMethodConfig*>( |
||||
((*vector_ptr)[parser_index_]).get()); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_attempts(), 2); |
||||
EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1)); |
||||
EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2)); |
||||
EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f); |
||||
EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), absl::nullopt); |
||||
EXPECT_TRUE( |
||||
parsed_config->retryable_status_codes().Contains(GRPC_STATUS_ABORTED)); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, |
||||
ValidRetryPolicyWithPerAttemptRecvTimeoutAndUnsetRetryableStatusCodes) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": 1.6,\n" |
||||
" \"perAttemptRecvTimeout\": \"1s\"\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
const ChannelArgs args = |
||||
ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1); |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
const auto* parsed_config = static_cast<internal::RetryMethodConfig*>( |
||||
((*vector_ptr)[parser_index_]).get()); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_attempts(), 2); |
||||
EXPECT_EQ(parsed_config->initial_backoff(), Duration::Seconds(1)); |
||||
EXPECT_EQ(parsed_config->max_backoff(), Duration::Minutes(2)); |
||||
EXPECT_EQ(parsed_config->backoff_multiplier(), 1.6f); |
||||
EXPECT_EQ(parsed_config->per_attempt_recv_timeout(), Duration::Seconds(1)); |
||||
EXPECT_TRUE(parsed_config->retryable_status_codes().Empty()); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutUnparseable) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"perAttemptRecvTimeout\": \"1sec\",\n" |
||||
" \"retryableStatusCodes\": [\"ABORTED\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
const ChannelArgs args = |
||||
ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1); |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.perAttemptRecvTimeout " |
||||
"error:Not a duration (no s suffix)]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutWrongType) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"perAttemptRecvTimeout\": 1,\n" |
||||
" \"retryableStatusCodes\": [\"ABORTED\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
const ChannelArgs args = |
||||
ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1); |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.perAttemptRecvTimeout " |
||||
"error:is not a string]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(RetryParserTest, InvalidRetryPolicyPerAttemptRecvTimeoutBadValue) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"retryPolicy\": {\n" |
||||
" \"maxAttempts\": 2,\n" |
||||
" \"initialBackoff\": \"1s\",\n" |
||||
" \"maxBackoff\": \"120s\",\n" |
||||
" \"backoffMultiplier\": \"1.6\",\n" |
||||
" \"perAttemptRecvTimeout\": \"0s\",\n" |
||||
" \"retryableStatusCodes\": [\"ABORTED\"]\n" |
||||
" }\n" |
||||
" } ]\n" |
||||
"}"; |
||||
const ChannelArgs args = |
||||
ChannelArgs().Set(GRPC_ARG_EXPERIMENTAL_ENABLE_HEDGING, 1); |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].retryPolicy.perAttemptRecvTimeout " |
||||
"error:must be greater than 0]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
grpc_init(); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,33 @@ |
||||
# Copyright 2017 gRPC authors. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_package") |
||||
|
||||
grpc_package(name = "test/core/message_size") |
||||
|
||||
licenses(["notice"]) |
||||
|
||||
grpc_cc_test( |
||||
name = "message_size_service_config_test", |
||||
srcs = ["message_size_service_config_test.cc"], |
||||
external_deps = [ |
||||
"gtest", |
||||
], |
||||
language = "C++", |
||||
deps = [ |
||||
"//:gpr", |
||||
"//:grpc", |
||||
"//test/core/util:grpc_test_util", |
||||
], |
||||
) |
@ -0,0 +1,125 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include <memory> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/grpc.h> |
||||
#include <grpc/slice.h> |
||||
|
||||
#include "src/core/ext/filters/message_size/message_size_filter.h" |
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/service_config/service_config.h" |
||||
#include "src/core/lib/service_config/service_config_impl.h" |
||||
#include "src/core/lib/service_config/service_config_parser.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
class MessageSizeParserTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() override { |
||||
parser_index_ = |
||||
CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
"message_size"); |
||||
} |
||||
|
||||
size_t parser_index_; |
||||
}; |
||||
|
||||
TEST_F(MessageSizeParserTest, Valid) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"maxRequestMessageBytes\": 1024,\n" |
||||
" \"maxResponseMessageBytes\": 1024\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
auto parsed_config = static_cast<MessageSizeParsedConfig*>( |
||||
((*vector_ptr)[parser_index_]).get()); |
||||
ASSERT_NE(parsed_config, nullptr); |
||||
EXPECT_EQ(parsed_config->max_send_size(), 1024U); |
||||
EXPECT_EQ(parsed_config->max_recv_size(), 1024U); |
||||
} |
||||
|
||||
TEST_F(MessageSizeParserTest, InvalidMaxRequestMessageBytes) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"maxRequestMessageBytes\": -1024\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].maxRequestMessageBytes " |
||||
"error:failed to parse non-negative number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(MessageSizeParserTest, InvalidMaxResponseMessageBytes) { |
||||
const char* test_json = |
||||
"{\n" |
||||
" \"methodConfig\": [ {\n" |
||||
" \"name\": [\n" |
||||
" { \"service\": \"TestServ\", \"method\": \"TestMethod\" }\n" |
||||
" ],\n" |
||||
" \"maxResponseMessageBytes\": {}\n" |
||||
" } ]\n" |
||||
"}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].maxResponseMessageBytes " |
||||
"error:is not a number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
grpc_init(); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
@ -0,0 +1,33 @@ |
||||
# Copyright 2017 gRPC authors. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_package") |
||||
|
||||
grpc_package(name = "test/core/service_config") |
||||
|
||||
licenses(["notice"]) |
||||
|
||||
grpc_cc_test( |
||||
name = "service_config_test", |
||||
srcs = ["service_config_test.cc"], |
||||
external_deps = [ |
||||
"gtest", |
||||
], |
||||
language = "C++", |
||||
deps = [ |
||||
"//:gpr", |
||||
"//:grpc", |
||||
"//test/core/util:grpc_test_util", |
||||
], |
||||
) |
@ -0,0 +1,468 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include "src/core/lib/service_config/service_config.h" |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
#include "gmock/gmock.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/grpc.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
#include "src/core/lib/config/core_configuration.h" |
||||
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
#include "src/core/lib/json/json.h" |
||||
#include "src/core/lib/json/json_args.h" |
||||
#include "src/core/lib/json/json_object_loader.h" |
||||
#include "src/core/lib/service_config/service_config_impl.h" |
||||
#include "src/core/lib/service_config/service_config_parser.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
// Set this channel arg to true to disable parsing.
|
||||
#define GRPC_ARG_DISABLE_PARSING "disable_parsing" |
||||
|
||||
class TestParsedConfig1 : public ServiceConfigParser::ParsedConfig { |
||||
public: |
||||
uint32_t value() const { return value_; } |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestParsedConfig1>() |
||||
.OptionalField("global_param", &TestParsedConfig1::value_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
private: |
||||
uint32_t value_; |
||||
}; |
||||
|
||||
class TestParser1 : public ServiceConfigParser::Parser { |
||||
public: |
||||
absl::string_view name() const override { return "test_parser_1"; } |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParseGlobalParams( |
||||
const ChannelArgs& args, const Json& json, |
||||
ValidationErrors* errors) override { |
||||
if (args.GetBool(GRPC_ARG_DISABLE_PARSING).value_or(false)) { |
||||
return nullptr; |
||||
} |
||||
return LoadFromJson<std::unique_ptr<TestParsedConfig1>>(json, JsonArgs(), |
||||
errors); |
||||
} |
||||
}; |
||||
|
||||
class TestParsedConfig2 : public ServiceConfigParser::ParsedConfig { |
||||
public: |
||||
uint32_t value() const { return value_; } |
||||
|
||||
static const JsonLoaderInterface* JsonLoader(const JsonArgs&) { |
||||
static const auto* loader = |
||||
JsonObjectLoader<TestParsedConfig2>() |
||||
.OptionalField("method_param", &TestParsedConfig2::value_) |
||||
.Finish(); |
||||
return loader; |
||||
} |
||||
|
||||
private: |
||||
uint32_t value_; |
||||
}; |
||||
|
||||
class TestParser2 : public ServiceConfigParser::Parser { |
||||
public: |
||||
absl::string_view name() const override { return "test_parser_2"; } |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParsePerMethodParams( |
||||
const ChannelArgs& args, const Json& json, |
||||
ValidationErrors* errors) override { |
||||
if (args.GetBool(GRPC_ARG_DISABLE_PARSING).value_or(false)) { |
||||
return nullptr; |
||||
} |
||||
return LoadFromJson<std::unique_ptr<TestParsedConfig2>>(json, JsonArgs(), |
||||
errors); |
||||
} |
||||
}; |
||||
|
||||
class ServiceConfigTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() override { |
||||
builder_ = std::make_unique<CoreConfiguration::WithSubstituteBuilder>( |
||||
[](CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<TestParser1>()); |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<TestParser2>()); |
||||
}); |
||||
EXPECT_EQ(CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
"test_parser_1"), |
||||
0); |
||||
EXPECT_EQ(CoreConfiguration::Get().service_config_parser().GetParserIndex( |
||||
"test_parser_2"), |
||||
1); |
||||
} |
||||
|
||||
private: |
||||
std::unique_ptr<CoreConfiguration::WithSubstituteBuilder> builder_; |
||||
}; |
||||
|
||||
TEST_F(ServiceConfigTest, JsonParseError) { |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), ""); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_THAT(std::string(service_config.status().message()), |
||||
::testing::StartsWith("JSON parsing failed")) |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, EmptyConfig) { |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), "{}"); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
EXPECT_EQ((*service_config)->json_string(), "{}"); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, SkipMethodConfigWithNoNameOrEmptyName) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"method_param\":1}," |
||||
" {\"name\":[], \"method_param\":1}," |
||||
" {\"name\":[{\"service\":\"TestServ\"}], \"method_param\":2}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
auto vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_EQ(vector_ptr->size(), 2UL); |
||||
auto parsed_config = ((*vector_ptr)[1]).get(); |
||||
EXPECT_EQ(static_cast<TestParsedConfig1*>(parsed_config)->value(), 2); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateMethodConfigNames) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{\"service\":\"TestServ\"}]}," |
||||
" {\"name\":[{\"service\":\"TestServ\"}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:multiple method configs for path /TestServ/]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateMethodConfigNamesWithNullMethod) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{\"service\":\"TestServ\",\"method\":null}]}," |
||||
" {\"name\":[{\"service\":\"TestServ\"}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:multiple method configs for path /TestServ/]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateMethodConfigNamesWithEmptyMethod) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{\"service\":\"TestServ\",\"method\":\"\"}]}," |
||||
" {\"name\":[{\"service\":\"TestServ\"}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:multiple method configs for path /TestServ/]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateDefaultMethodConfigs) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{}]}," |
||||
" {\"name\":[{}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:duplicate default method config]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateDefaultMethodConfigsWithNullService) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{\"service\":null}]}," |
||||
" {\"name\":[{}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:duplicate default method config]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ErrorDuplicateDefaultMethodConfigsWithEmptyService) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [" |
||||
" {\"name\":[{\"service\":\"\"}]}," |
||||
" {\"name\":[{}]}" |
||||
"]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[1].name[0] " |
||||
"error:duplicate default method config]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, ValidMethodConfig) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}]}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser1BasicTest1) { |
||||
const char* test_json = "{\"global_param\":5}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
EXPECT_EQ((static_cast<TestParsedConfig1*>( |
||||
(*service_config)->GetGlobalParsedConfig(0))) |
||||
->value(), |
||||
5); |
||||
EXPECT_EQ((*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")), |
||||
nullptr); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser1BasicTest2) { |
||||
const char* test_json = "{\"global_param\":1000}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
EXPECT_EQ((static_cast<TestParsedConfig1*>( |
||||
(*service_config)->GetGlobalParsedConfig(0))) |
||||
->value(), |
||||
1000); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser1DisabledViaChannelArg) { |
||||
const ChannelArgs args = ChannelArgs().Set(GRPC_ARG_DISABLE_PARSING, 1); |
||||
const char* test_json = "{\"global_param\":5}"; |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
EXPECT_EQ((*service_config)->GetGlobalParsedConfig(0), nullptr); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser1ErrorInvalidType) { |
||||
const char* test_json = "{\"global_param\":[]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:global_param error:is not a number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser1ErrorInvalidValue) { |
||||
const char* test_json = "{\"global_param\":-5}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:global_param error:failed to parse non-negative number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser2BasicTest) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], " |
||||
"\"method_param\":5}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
auto parsed_config = ((*vector_ptr)[1]).get(); |
||||
EXPECT_EQ(static_cast<TestParsedConfig1*>(parsed_config)->value(), 5); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser2DisabledViaChannelArg) { |
||||
const ChannelArgs args = ChannelArgs().Set(GRPC_ARG_DISABLE_PARSING, 1); |
||||
const char* test_json = |
||||
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], " |
||||
"\"method_param\":5}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(args, test_json); |
||||
ASSERT_TRUE(service_config.ok()) << service_config.status(); |
||||
const auto* vector_ptr = |
||||
(*service_config) |
||||
->GetMethodParsedConfigVector( |
||||
grpc_slice_from_static_string("/TestServ/TestMethod")); |
||||
ASSERT_NE(vector_ptr, nullptr); |
||||
auto parsed_config = ((*vector_ptr)[1]).get(); |
||||
EXPECT_EQ(parsed_config, nullptr); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser2ErrorInvalidType) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], " |
||||
"\"method_param\":[]}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].method_param error:is not a number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ServiceConfigTest, Parser2ErrorInvalidValue) { |
||||
const char* test_json = |
||||
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], " |
||||
"\"method_param\":-5}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:methodConfig[0].method_param " |
||||
"error:failed to parse non-negative number]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST(ServiceConfigParserTest, DoubleRegistration) { |
||||
CoreConfiguration::Reset(); |
||||
ASSERT_DEATH_IF_SUPPORTED( |
||||
CoreConfiguration::WithSubstituteBuilder builder( |
||||
[](CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<TestParser1>()); |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<TestParser1>()); |
||||
}), |
||||
"test_parser_1.*already registered"); |
||||
} |
||||
|
||||
// This parser always adds errors
|
||||
class ErrorParser : public ServiceConfigParser::Parser { |
||||
public: |
||||
explicit ErrorParser(absl::string_view name) : name_(name) {} |
||||
|
||||
absl::string_view name() const override { return name_; } |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParsePerMethodParams( |
||||
const ChannelArgs& /*arg*/, const Json& /*json*/, |
||||
ValidationErrors* errors) override { |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat(".", name_)); |
||||
errors->AddError("method error"); |
||||
return nullptr; |
||||
} |
||||
|
||||
std::unique_ptr<ServiceConfigParser::ParsedConfig> ParseGlobalParams( |
||||
const ChannelArgs& /*arg*/, const Json& /*json*/, |
||||
ValidationErrors* errors) override { |
||||
ValidationErrors::ScopedField field(errors, absl::StrCat(".", name_)); |
||||
errors->AddError("global error"); |
||||
return nullptr; |
||||
} |
||||
|
||||
private: |
||||
absl::string_view name_; |
||||
}; |
||||
|
||||
// Test parsing with ErrorParsers which always add errors
|
||||
class ErroredParsersScopingTest : public ::testing::Test { |
||||
protected: |
||||
void SetUp() override { |
||||
builder_ = std::make_unique<CoreConfiguration::WithSubstituteBuilder>( |
||||
[](CoreConfiguration::Builder* builder) { |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<ErrorParser>("ep1")); |
||||
builder->service_config_parser()->RegisterParser( |
||||
std::make_unique<ErrorParser>("ep2")); |
||||
}); |
||||
EXPECT_EQ( |
||||
CoreConfiguration::Get().service_config_parser().GetParserIndex("ep1"), |
||||
0); |
||||
EXPECT_EQ( |
||||
CoreConfiguration::Get().service_config_parser().GetParserIndex("ep2"), |
||||
1); |
||||
} |
||||
|
||||
private: |
||||
std::unique_ptr<CoreConfiguration::WithSubstituteBuilder> builder_; |
||||
}; |
||||
|
||||
TEST_F(ErroredParsersScopingTest, GlobalParams) { |
||||
const char* test_json = "{}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:ep1 error:global error; field:ep2 error:global error]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
TEST_F(ErroredParsersScopingTest, MethodParams) { |
||||
const char* test_json = "{\"methodConfig\": [{}]}"; |
||||
auto service_config = ServiceConfigImpl::Create(ChannelArgs(), test_json); |
||||
EXPECT_EQ(service_config.status().code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(service_config.status().message(), |
||||
"errors validating service config: [" |
||||
"field:ep1 error:global error; " |
||||
"field:ep2 error:global error; " |
||||
"field:methodConfig[0].ep1 error:method error; " |
||||
"field:methodConfig[0].ep2 error:method error]") |
||||
<< service_config.status(); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
grpc_init(); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
Loading…
Reference in new issue