Merge pull request #18586 from yashykt/svc_cfg1

Global Registry for Service Config Parsers
pull/18630/head^2
Yash Tibrewal 6 years ago committed by GitHub
commit 3d0788a242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      CMakeLists.txt
  2. 48
      Makefile
  3. 13
      build.yaml
  4. 2
      src/core/ext/filters/client_channel/client_channel_plugin.cc
  5. 6
      src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
  6. 5
      src/core/ext/filters/client_channel/resolver_result_parsing.cc
  7. 279
      src/core/ext/filters/client_channel/service_config.cc
  8. 96
      src/core/ext/filters/client_channel/service_config.h
  9. 5
      src/core/ext/filters/client_channel/subchannel.cc
  10. 5
      src/core/ext/filters/message_size/message_size_filter.cc
  11. 14
      test/core/client_channel/BUILD
  12. 365
      test/core/client_channel/service_config_test.cc
  13. 4
      test/cpp/end2end/grpclb_end2end_test.cc
  14. 8
      test/cpp/end2end/xds_end2end_test.cc
  15. 18
      tools/run_tests/generated/sources_and_headers.json
  16. 24
      tools/run_tests/generated/tests.json

@ -699,6 +699,7 @@ add_dependencies(buildtests_cxx server_crash_test_client)
add_dependencies(buildtests_cxx server_early_return_test)
add_dependencies(buildtests_cxx server_interceptors_end2end_test)
add_dependencies(buildtests_cxx server_request_call_test)
add_dependencies(buildtests_cxx service_config_test)
add_dependencies(buildtests_cxx shutdown_test)
add_dependencies(buildtests_cxx slice_hash_table_test)
add_dependencies(buildtests_cxx slice_weak_hash_table_test)
@ -15766,6 +15767,45 @@ target_link_libraries(server_request_call_test
)
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
add_executable(service_config_test
test/core/client_channel/service_config_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(service_config_test
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
PRIVATE third_party/googletest/googletest/include
PRIVATE third_party/googletest/googletest
PRIVATE third_party/googletest/googlemock/include
PRIVATE third_party/googletest/googlemock
PRIVATE ${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(service_config_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
grpc++
grpc
gpr
${_gRPC_GFLAGS_LIBRARIES}
)
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)

@ -1262,6 +1262,7 @@ server_crash_test_client: $(BINDIR)/$(CONFIG)/server_crash_test_client
server_early_return_test: $(BINDIR)/$(CONFIG)/server_early_return_test
server_interceptors_end2end_test: $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test
server_request_call_test: $(BINDIR)/$(CONFIG)/server_request_call_test
service_config_test: $(BINDIR)/$(CONFIG)/service_config_test
shutdown_test: $(BINDIR)/$(CONFIG)/shutdown_test
slice_hash_table_test: $(BINDIR)/$(CONFIG)/slice_hash_table_test
slice_weak_hash_table_test: $(BINDIR)/$(CONFIG)/slice_weak_hash_table_test
@ -1727,6 +1728,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/server_early_return_test \
$(BINDIR)/$(CONFIG)/server_interceptors_end2end_test \
$(BINDIR)/$(CONFIG)/server_request_call_test \
$(BINDIR)/$(CONFIG)/service_config_test \
$(BINDIR)/$(CONFIG)/shutdown_test \
$(BINDIR)/$(CONFIG)/slice_hash_table_test \
$(BINDIR)/$(CONFIG)/slice_weak_hash_table_test \
@ -1869,6 +1871,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/server_early_return_test \
$(BINDIR)/$(CONFIG)/server_interceptors_end2end_test \
$(BINDIR)/$(CONFIG)/server_request_call_test \
$(BINDIR)/$(CONFIG)/service_config_test \
$(BINDIR)/$(CONFIG)/shutdown_test \
$(BINDIR)/$(CONFIG)/slice_hash_table_test \
$(BINDIR)/$(CONFIG)/slice_weak_hash_table_test \
@ -2382,6 +2385,8 @@ test_cxx: buildtests_cxx
$(Q) $(BINDIR)/$(CONFIG)/server_interceptors_end2end_test || ( echo test server_interceptors_end2end_test failed ; exit 1 )
$(E) "[RUN] Testing server_request_call_test"
$(Q) $(BINDIR)/$(CONFIG)/server_request_call_test || ( echo test server_request_call_test failed ; exit 1 )
$(E) "[RUN] Testing service_config_test"
$(Q) $(BINDIR)/$(CONFIG)/service_config_test || ( echo test service_config_test failed ; exit 1 )
$(E) "[RUN] Testing shutdown_test"
$(Q) $(BINDIR)/$(CONFIG)/shutdown_test || ( echo test shutdown_test failed ; exit 1 )
$(E) "[RUN] Testing slice_hash_table_test"
@ -18696,6 +18701,49 @@ endif
$(OBJDIR)/$(CONFIG)/test/cpp/server/server_request_call_test.o: $(GENDIR)/src/proto/grpc/testing/echo_messages.pb.cc $(GENDIR)/src/proto/grpc/testing/echo_messages.grpc.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.pb.cc $(GENDIR)/src/proto/grpc/testing/echo.grpc.pb.cc
SERVICE_CONFIG_TEST_SRC = \
test/core/client_channel/service_config_test.cc \
SERVICE_CONFIG_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SERVICE_CONFIG_TEST_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/service_config_test: openssl_dep_error
else
ifeq ($(NO_PROTOBUF),true)
# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
$(BINDIR)/$(CONFIG)/service_config_test: protobuf_dep_error
else
$(BINDIR)/$(CONFIG)/service_config_test: $(PROTOBUF_DEP) $(SERVICE_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LDXX) $(LDFLAGS) $(SERVICE_CONFIG_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/service_config_test
endif
endif
$(OBJDIR)/$(CONFIG)/test/core/client_channel/service_config_test.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
deps_service_config_test: $(SERVICE_CONFIG_TEST_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(SERVICE_CONFIG_TEST_OBJS:.o=.dep)
endif
endif
SHUTDOWN_TEST_SRC = \
test/cpp/end2end/shutdown_test.cc \

@ -5481,6 +5481,19 @@ targets:
- grpc++_unsecure
- grpc_unsecure
- gpr
- name: service_config_test
gtest: true
build: test
language: c++
src:
- test/core/client_channel/service_config_test.cc
deps:
- grpc_test_util
- grpc++
- grpc
- gpr
uses:
- grpc++_test
- name: shutdown_test
gtest: true
build: test

@ -49,6 +49,7 @@ static bool append_filter(grpc_channel_stack_builder* builder, void* arg) {
}
void grpc_client_channel_init(void) {
grpc_core::ServiceConfig::Init();
grpc_core::LoadBalancingPolicyRegistry::Builder::InitRegistry();
grpc_core::ResolverRegistry::Builder::InitRegistry();
grpc_core::internal::ServerRetryThrottleMap::Init();
@ -68,4 +69,5 @@ void grpc_client_channel_shutdown(void) {
grpc_core::internal::ServerRetryThrottleMap::Shutdown();
grpc_core::ResolverRegistry::Builder::ShutdownRegistry();
grpc_core::LoadBalancingPolicyRegistry::Builder::ShutdownRegistry();
grpc_core::ServiceConfig::Shutdown();
}

@ -308,7 +308,11 @@ void AresDnsResolver::OnResolvedLocked(void* arg, grpc_error* error) {
if (service_config_string != nullptr) {
GRPC_CARES_TRACE_LOG("resolver:%p selected service config choice: %s",
r, service_config_string);
result.service_config = ServiceConfig::Create(service_config_string);
grpc_error* service_config_error = GRPC_ERROR_NONE;
result.service_config =
ServiceConfig::Create(service_config_string, &service_config_error);
// Error is currently unused.
GRPC_ERROR_UNREF(service_config_error);
}
gpr_free(service_config_string);
}

@ -52,7 +52,10 @@ ProcessedResolverResult::ProcessedResolverResult(
const char* service_config_json = grpc_channel_arg_get_string(
grpc_channel_args_find(resolver_result->args, GRPC_ARG_SERVICE_CONFIG));
if (service_config_json != nullptr) {
service_config_ = ServiceConfig::Create(service_config_json);
grpc_error* error = GRPC_ERROR_NONE;
service_config_ = ServiceConfig::Create(service_config_json, &error);
// Error is currently unused.
GRPC_ERROR_UNREF(error);
}
} else {
// Add the service config JSON to channel args so that it's

@ -33,23 +33,201 @@
namespace grpc_core {
RefCountedPtr<ServiceConfig> ServiceConfig::Create(const char* json) {
namespace {
typedef InlinedVector<UniquePtr<ServiceConfigParser>,
ServiceConfig::kNumPreallocatedParsers>
ServiceConfigParserList;
ServiceConfigParserList* registered_parsers;
// Consumes all the errors in the vector and forms a referencing error from
// them. If the vector is empty, return GRPC_ERROR_NONE.
template <size_t N>
grpc_error* CreateErrorFromVector(const char* desc,
InlinedVector<grpc_error*, N>* error_list) {
grpc_error* error = GRPC_ERROR_NONE;
if (error_list->size() != 0) {
error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
desc, error_list->data(), error_list->size());
// Remove refs to all errors in error_list.
for (size_t i = 0; i < error_list->size(); i++) {
GRPC_ERROR_UNREF((*error_list)[i]);
}
error_list->clear();
}
return error;
}
} // namespace
RefCountedPtr<ServiceConfig> ServiceConfig::Create(const char* json,
grpc_error** error) {
UniquePtr<char> service_config_json(gpr_strdup(json));
UniquePtr<char> json_string(gpr_strdup(json));
GPR_DEBUG_ASSERT(error != nullptr);
grpc_json* json_tree = grpc_json_parse_string(json_string.get());
if (json_tree == nullptr) {
gpr_log(GPR_INFO, "failed to parse JSON for service config");
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"failed to parse JSON for service config");
return nullptr;
}
return MakeRefCounted<ServiceConfig>(std::move(service_config_json),
std::move(json_string), json_tree);
return MakeRefCounted<ServiceConfig>(
std::move(service_config_json), std::move(json_string), json_tree, error);
}
ServiceConfig::ServiceConfig(UniquePtr<char> service_config_json,
UniquePtr<char> json_string, grpc_json* json_tree)
UniquePtr<char> json_string, grpc_json* json_tree,
grpc_error** error)
: service_config_json_(std::move(service_config_json)),
json_string_(std::move(json_string)),
json_tree_(json_tree) {}
json_tree_(json_tree) {
GPR_DEBUG_ASSERT(error != nullptr);
if (json_tree->type != GRPC_JSON_OBJECT || json_tree->key != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Malformed service Config JSON object");
return;
}
grpc_error* error_list[2];
int error_count = 0;
grpc_error* global_error = ParseGlobalParams(json_tree);
grpc_error* local_error = ParsePerMethodParams(json_tree);
if (global_error != GRPC_ERROR_NONE) {
error_list[error_count++] = global_error;
}
if (local_error != GRPC_ERROR_NONE) {
error_list[error_count++] = local_error;
}
if (error_count > 0) {
*error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"Service config parsing error", error_list, error_count);
GRPC_ERROR_UNREF(global_error);
GRPC_ERROR_UNREF(local_error);
}
}
grpc_error* ServiceConfig::ParseGlobalParams(const grpc_json* json_tree) {
GPR_DEBUG_ASSERT(json_tree_->type == GRPC_JSON_OBJECT);
GPR_DEBUG_ASSERT(json_tree_->key == nullptr);
InlinedVector<grpc_error*, 4> error_list;
for (size_t i = 0; i < registered_parsers->size(); i++) {
grpc_error* parser_error = GRPC_ERROR_NONE;
auto parsed_obj =
(*registered_parsers)[i]->ParseGlobalParams(json_tree, &parser_error);
if (parser_error != GRPC_ERROR_NONE) {
error_list.push_back(parser_error);
}
parsed_global_service_config_objects_.push_back(std::move(parsed_obj));
}
return CreateErrorFromVector("Global Params", &error_list);
}
grpc_error* ServiceConfig::ParseJsonMethodConfigToServiceConfigObjectsTable(
const grpc_json* json,
SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries,
size_t* idx) {
auto objs_vector = MakeUnique<ServiceConfigObjectsVector>();
InlinedVector<grpc_error*, 4> error_list;
for (size_t i = 0; i < registered_parsers->size(); i++) {
grpc_error* parser_error = GRPC_ERROR_NONE;
auto parsed_obj =
(*registered_parsers)[i]->ParsePerMethodParams(json, &parser_error);
if (parser_error != GRPC_ERROR_NONE) {
error_list.push_back(parser_error);
}
objs_vector->push_back(std::move(parsed_obj));
}
const auto* vector_ptr = objs_vector.get();
service_config_objects_vectors_storage_.push_back(std::move(objs_vector));
// Construct list of paths.
InlinedVector<UniquePtr<char>, 10> paths;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) continue;
if (strcmp(child->key, "name") == 0) {
if (child->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error:not of type Array"));
goto wrap_error;
}
for (grpc_json* name = child->child; name != nullptr; name = name->next) {
grpc_error* parse_error = GRPC_ERROR_NONE;
UniquePtr<char> path = ParseJsonMethodName(name, &parse_error);
if (path == nullptr) {
error_list.push_back(parse_error);
} else {
GPR_DEBUG_ASSERT(parse_error == GRPC_ERROR_NONE);
paths.push_back(std::move(path));
}
}
}
}
if (paths.size() == 0) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("No names specified"));
}
// Add entry for each path.
for (size_t i = 0; i < paths.size(); ++i) {
entries[*idx].key = grpc_slice_from_copied_string(paths[i].get());
entries[*idx].value = vector_ptr;
++*idx;
}
wrap_error:
return CreateErrorFromVector("methodConfig", &error_list);
}
grpc_error* ServiceConfig::ParsePerMethodParams(const grpc_json* json_tree) {
GPR_DEBUG_ASSERT(json_tree_->type == GRPC_JSON_OBJECT);
GPR_DEBUG_ASSERT(json_tree_->key == nullptr);
SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries = nullptr;
size_t num_entries = 0;
InlinedVector<grpc_error*, 4> error_list;
for (grpc_json* field = json_tree->child; field != nullptr;
field = field->next) {
if (field->key == nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"error:Illegal key value - NULL"));
continue;
}
if (strcmp(field->key, "methodConfig") == 0) {
if (entries != nullptr) {
GPR_ASSERT(false);
}
if (field->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:methodConfig error:not of type Array"));
}
for (grpc_json* method = field->child; method != nullptr;
method = method->next) {
int count = CountNamesInMethodConfig(method);
if (count <= 0) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:methodConfig error:No names found"));
}
num_entries += static_cast<size_t>(count);
}
entries = static_cast<
SliceHashTable<const ServiceConfigObjectsVector*>::Entry*>(gpr_zalloc(
num_entries *
sizeof(SliceHashTable<const ServiceConfigObjectsVector*>::Entry)));
size_t idx = 0;
for (grpc_json* method = field->child; method != nullptr;
method = method->next) {
grpc_error* error = ParseJsonMethodConfigToServiceConfigObjectsTable(
method, entries, &idx);
if (error != GRPC_ERROR_NONE) {
error_list.push_back(error);
}
}
// idx might not be equal to num_entries due to parsing errors
num_entries = idx;
break;
}
}
if (entries != nullptr) {
parsed_method_service_config_objects_table_ =
SliceHashTable<const ServiceConfigObjectsVector*>::Create(
num_entries, entries, nullptr);
gpr_free(entries);
}
return CreateErrorFromVector("Method Params", &error_list);
}
ServiceConfig::~ServiceConfig() { grpc_json_destroy(json_tree_); }
@ -84,28 +262,99 @@ int ServiceConfig::CountNamesInMethodConfig(grpc_json* json) {
return num_names;
}
UniquePtr<char> ServiceConfig::ParseJsonMethodName(grpc_json* json) {
if (json->type != GRPC_JSON_OBJECT) return nullptr;
UniquePtr<char> ServiceConfig::ParseJsonMethodName(grpc_json* json,
grpc_error** error) {
if (json->type != GRPC_JSON_OBJECT) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error:type is not object");
return nullptr;
}
const char* service_name = nullptr;
const char* method_name = nullptr;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) return nullptr;
if (child->type != GRPC_JSON_STRING) return nullptr;
if (child->key == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error:Child entry with no key");
return nullptr;
}
if (child->type != GRPC_JSON_STRING) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error:Child entry not of type string");
return nullptr;
}
if (strcmp(child->key, "service") == 0) {
if (service_name != nullptr) return nullptr; // Duplicate.
if (child->value == nullptr) return nullptr;
if (service_name != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error: field:service error:Multiple entries");
return nullptr; // Duplicate.
}
if (child->value == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error: field:service error:empty value");
return nullptr;
}
service_name = child->value;
} else if (strcmp(child->key, "method") == 0) {
if (method_name != nullptr) return nullptr; // Duplicate.
if (child->value == nullptr) return nullptr;
if (method_name != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error: field:method error:multiple entries");
return nullptr; // Duplicate.
}
if (child->value == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error: field:method error:empty value");
return nullptr;
}
method_name = child->value;
}
}
if (service_name == nullptr) return nullptr; // Required field.
if (service_name == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:name error: field:service error:not found");
return nullptr; // Required field.
}
char* path;
gpr_asprintf(&path, "/%s/%s", service_name,
method_name == nullptr ? "*" : method_name);
return UniquePtr<char>(path);
}
const ServiceConfig::ServiceConfigObjectsVector* const*
ServiceConfig::GetMethodServiceConfigObjectsVector(const grpc_slice& path) {
const auto* value = parsed_method_service_config_objects_table_->Get(path);
// If we didn't find a match for the path, try looking for a wildcard
// entry (i.e., change "/service/method" to "/service/*").
if (value == nullptr) {
char* path_str = grpc_slice_to_c_string(path);
const char* sep = strrchr(path_str, '/') + 1;
const size_t len = (size_t)(sep - path_str);
char* buf = (char*)gpr_malloc(len + 2); // '*' and NUL
memcpy(buf, path_str, len);
buf[len] = '*';
buf[len + 1] = '\0';
grpc_slice wildcard_path = grpc_slice_from_copied_string(buf);
gpr_free(buf);
value = parsed_method_service_config_objects_table_->Get(wildcard_path);
grpc_slice_unref_internal(wildcard_path);
gpr_free(path_str);
if (value == nullptr) return nullptr;
}
return value;
}
size_t ServiceConfig::RegisterParser(UniquePtr<ServiceConfigParser> parser) {
registered_parsers->push_back(std::move(parser));
return registered_parsers->size() - 1;
}
void ServiceConfig::Init() {
GPR_ASSERT(registered_parsers == nullptr);
registered_parsers = New<ServiceConfigParserList>();
}
void ServiceConfig::Shutdown() {
Delete(registered_parsers);
registered_parsers = nullptr;
}
} // namespace grpc_core

@ -25,6 +25,7 @@
#include "src/core/lib/gprpp/inlined_vector.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/slice/slice_hash_table.h"
@ -54,11 +55,46 @@
namespace grpc_core {
/// This is the base class that all service config parsers MUST use to store
/// parsed service config data.
class ServiceConfigParsedObject {
public:
virtual ~ServiceConfigParsedObject() = default;
GRPC_ABSTRACT_BASE_CLASS;
};
/// This is the base class that all service config parsers should derive from.
class ServiceConfigParser {
public:
virtual ~ServiceConfigParser() = default;
virtual UniquePtr<ServiceConfigParsedObject> ParseGlobalParams(
const grpc_json* json, grpc_error** error) {
GPR_DEBUG_ASSERT(error != nullptr);
return nullptr;
}
virtual UniquePtr<ServiceConfigParsedObject> ParsePerMethodParams(
const grpc_json* json, grpc_error** error) {
GPR_DEBUG_ASSERT(error != nullptr);
return nullptr;
}
GRPC_ABSTRACT_BASE_CLASS;
};
class ServiceConfig : public RefCounted<ServiceConfig> {
public:
static constexpr int kNumPreallocatedParsers = 4;
typedef InlinedVector<UniquePtr<ServiceConfigParsedObject>,
kNumPreallocatedParsers>
ServiceConfigObjectsVector;
/// Creates a new service config from parsing \a json_string.
/// Returns null on parse error.
static RefCountedPtr<ServiceConfig> Create(const char* json);
static RefCountedPtr<ServiceConfig> Create(const char* json,
grpc_error** error);
~ServiceConfig();
@ -96,6 +132,30 @@ class ServiceConfig : public RefCounted<ServiceConfig> {
static RefCountedPtr<T> MethodConfigTableLookup(
const SliceHashTable<RefCountedPtr<T>>& table, const grpc_slice& path);
/// Retrieves the parsed global service config object at index \a index.
ServiceConfigParsedObject* GetParsedGlobalServiceConfigObject(int index) {
GPR_DEBUG_ASSERT(
index < static_cast<int>(parsed_global_service_config_objects_.size()));
return parsed_global_service_config_objects_[index].get();
}
/// Retrieves the vector of method service config objects for a given path \a
/// path.
const ServiceConfigObjectsVector* const* GetMethodServiceConfigObjectsVector(
const grpc_slice& path);
/// Globally register a service config parser. On successful registration, it
/// returns the index at which the parser was registered. On failure, -1 is
/// returned. Each new service config update will go through all the
/// registered parser. Each parser is responsible for reading the service
/// config json and returning a parsed object. This parsed object can later be
/// retrieved using the same index that was returned at registration time.
static size_t RegisterParser(UniquePtr<ServiceConfigParser> parser);
static void Init();
static void Shutdown();
private:
// So New() can call our private ctor.
template <typename T, typename... Args>
@ -103,14 +163,20 @@ class ServiceConfig : public RefCounted<ServiceConfig> {
// Takes ownership of \a json_tree.
ServiceConfig(UniquePtr<char> service_config_json,
UniquePtr<char> json_string, grpc_json* json_tree);
UniquePtr<char> json_string, grpc_json* json_tree,
grpc_error** error);
// Helper functions to parse the service config
grpc_error* ParseGlobalParams(const grpc_json* json_tree);
grpc_error* ParsePerMethodParams(const grpc_json* json_tree);
// Returns the number of names specified in the method config \a json.
static int CountNamesInMethodConfig(grpc_json* json);
// Returns a path string for the JSON name object specified by \a json.
// Returns null on error.
static UniquePtr<char> ParseJsonMethodName(grpc_json* json);
// Returns null on error, and stores error in \a error.
static UniquePtr<char> ParseJsonMethodName(grpc_json* json,
grpc_error** error);
// Parses the method config from \a json. Adds an entry to \a entries for
// each name found, incrementing \a idx for each entry added.
@ -120,9 +186,26 @@ class ServiceConfig : public RefCounted<ServiceConfig> {
grpc_json* json, CreateValue<T> create_value,
typename SliceHashTable<RefCountedPtr<T>>::Entry* entries, size_t* idx);
grpc_error* ParseJsonMethodConfigToServiceConfigObjectsTable(
const grpc_json* json,
SliceHashTable<const ServiceConfigObjectsVector*>::Entry* entries,
size_t* idx);
UniquePtr<char> service_config_json_;
UniquePtr<char> json_string_; // Underlying storage for json_tree.
grpc_json* json_tree_;
InlinedVector<UniquePtr<ServiceConfigParsedObject>, kNumPreallocatedParsers>
parsed_global_service_config_objects_;
// A map from the method name to the service config objects vector. Note that
// we are using a raw pointer and not a unique pointer so that we can use the
// same vector for multiple names.
RefCountedPtr<SliceHashTable<const ServiceConfigObjectsVector*>>
parsed_method_service_config_objects_table_;
// Storage for all the vectors that are being used in
// parsed_method_service_config_objects_table_.
InlinedVector<UniquePtr<ServiceConfigObjectsVector>, 32>
service_config_objects_vectors_storage_;
};
//
@ -157,7 +240,10 @@ bool ServiceConfig::ParseJsonMethodConfig(
if (strcmp(child->key, "name") == 0) {
if (child->type != GRPC_JSON_ARRAY) return false;
for (grpc_json* name = child->child; name != nullptr; name = name->next) {
UniquePtr<char> path = ParseJsonMethodName(name);
grpc_error* error = GRPC_ERROR_NONE;
UniquePtr<char> path = ParseJsonMethodName(name, &error);
// We are not reporting the error here.
GRPC_ERROR_UNREF(error);
if (path == nullptr) return false;
paths.push_back(std::move(path));
}

@ -590,8 +590,11 @@ Subchannel::Subchannel(SubchannelKey* key, grpc_connector* connector,
const char* service_config_json = grpc_channel_arg_get_string(
grpc_channel_args_find(args_, GRPC_ARG_SERVICE_CONFIG));
if (service_config_json != nullptr) {
grpc_error* service_config_error = GRPC_ERROR_NONE;
RefCountedPtr<ServiceConfig> service_config =
ServiceConfig::Create(service_config_json);
ServiceConfig::Create(service_config_json, &service_config_error);
// service_config_error is currently unused.
GRPC_ERROR_UNREF(service_config_error);
if (service_config != nullptr) {
HealthCheckParams params;
service_config->ParseGlobalParams(HealthCheckParams::Parse, &params);

@ -319,8 +319,11 @@ static grpc_error* init_channel_elem(grpc_channel_element* elem,
grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVICE_CONFIG);
const char* service_config_str = grpc_channel_arg_get_string(channel_arg);
if (service_config_str != nullptr) {
grpc_error* service_config_error = GRPC_ERROR_NONE;
grpc_core::RefCountedPtr<grpc_core::ServiceConfig> service_config =
grpc_core::ServiceConfig::Create(service_config_str);
grpc_core::ServiceConfig::Create(service_config_str,
&service_config_error);
GRPC_ERROR_UNREF(service_config_error);
if (service_config != nullptr) {
chand->method_limit_table = service_config->CreateMethodConfigTable(
grpc_core::MessageSizeLimits::CreateFromJson);

@ -79,3 +79,17 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
],
)
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,365 @@
/*
*
* 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 <regex>
#include <gtest/gtest.h>
#include <grpc/grpc.h>
#include "src/core/ext/filters/client_channel/service_config.h"
#include "src/core/lib/gpr/string.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
namespace grpc_core {
namespace testing {
class TestParsedObject1 : public ServiceConfigParsedObject {
public:
TestParsedObject1(int value) : value_(value) {}
int value() const { return value_; }
private:
int value_;
};
class TestParser1 : public ServiceConfigParser {
public:
UniquePtr<ServiceConfigParsedObject> ParseGlobalParams(
const grpc_json* json, grpc_error** error) override {
GPR_DEBUG_ASSERT(error != nullptr);
for (grpc_json* field = json->child; field != nullptr;
field = field->next) {
if (strcmp(field->key, "global_param") == 0) {
if (field->type != GRPC_JSON_NUMBER) {
*error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidTypeErrorMessage());
return nullptr;
}
int value = gpr_parse_nonnegative_int(field->value);
if (value == -1) {
*error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidValueErrorMessage());
return nullptr;
}
return UniquePtr<ServiceConfigParsedObject>(
New<TestParsedObject1>(value));
}
}
return nullptr;
}
static const char* InvalidTypeErrorMessage() {
return "global_param value type should be a number";
}
static const char* InvalidValueErrorMessage() {
return "global_param value type should be non-negative";
}
};
class TestParser2 : public ServiceConfigParser {
public:
UniquePtr<ServiceConfigParsedObject> ParsePerMethodParams(
const grpc_json* json, grpc_error** error) override {
GPR_DEBUG_ASSERT(error != nullptr);
for (grpc_json* field = json->child; field != nullptr;
field = field->next) {
if (field->key == nullptr || strcmp(field->key, "name") == 0) {
continue;
}
if (strcmp(field->key, "method_param") == 0) {
if (field->type != GRPC_JSON_NUMBER) {
*error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidTypeErrorMessage());
return nullptr;
}
int value = gpr_parse_nonnegative_int(field->value);
if (value == -1) {
*error =
GRPC_ERROR_CREATE_FROM_STATIC_STRING(InvalidValueErrorMessage());
return nullptr;
}
return UniquePtr<ServiceConfigParsedObject>(
New<TestParsedObject1>(value));
}
}
return nullptr;
}
static const char* InvalidTypeErrorMessage() {
return "method_param value type should be a number";
}
static const char* InvalidValueErrorMessage() {
return "method_param value type should be non-negative";
}
};
// This parser always adds errors
class ErrorParser : public ServiceConfigParser {
public:
UniquePtr<ServiceConfigParsedObject> ParsePerMethodParams(
const grpc_json* json, grpc_error** error) override {
GPR_DEBUG_ASSERT(error != nullptr);
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(MethodError());
return nullptr;
}
UniquePtr<ServiceConfigParsedObject> ParseGlobalParams(
const grpc_json* json, grpc_error** error) override {
GPR_DEBUG_ASSERT(error != nullptr);
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(GlobalError());
return nullptr;
}
static const char* MethodError() { return "ErrorParser : methodError"; }
static const char* GlobalError() { return "ErrorParser : globalError"; }
};
class ServiceConfigTest : public ::testing::Test {
protected:
void SetUp() override {
ServiceConfig::Shutdown();
ServiceConfig::Init();
EXPECT_TRUE(ServiceConfig::RegisterParser(
UniquePtr<ServiceConfigParser>(New<TestParser1>())) == 0);
EXPECT_TRUE(ServiceConfig::RegisterParser(
UniquePtr<ServiceConfigParser>(New<TestParser2>())) == 1);
}
};
TEST_F(ServiceConfigTest, ErrorCheck1) {
const char* test_json = "";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
EXPECT_TRUE(strstr(grpc_error_string(error),
"failed to parse JSON for service config") != nullptr);
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, BasicTest1) {
const char* test_json = "{}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
EXPECT_TRUE(error == GRPC_ERROR_NONE);
}
TEST_F(ServiceConfigTest, ErrorNoNames) {
const char* test_json = "{\"methodConfig\": [{\"blah\":1}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
EXPECT_TRUE(strstr(grpc_error_string(error), "No names found") != nullptr);
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, ErrorNoNamesWithMultipleMethodConfigs) {
const char* test_json =
"{\"methodConfig\": [{}, {\"name\":[{\"service\":\"TestServ\"}]}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
EXPECT_TRUE(strstr(grpc_error_string(error), "No names found") != nullptr);
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, ValidMethodConfig) {
const char* test_json =
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}]}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
EXPECT_TRUE(error == GRPC_ERROR_NONE);
}
TEST_F(ServiceConfigTest, Parser1BasicTest1) {
const char* test_json = "{\"global_param\":5}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error == GRPC_ERROR_NONE);
EXPECT_TRUE((static_cast<TestParsedObject1*>(
svc_cfg->GetParsedGlobalServiceConfigObject(0)))
->value() == 5);
}
TEST_F(ServiceConfigTest, Parser1BasicTest2) {
const char* test_json = "{\"global_param\":1000}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error == GRPC_ERROR_NONE);
EXPECT_TRUE((static_cast<TestParsedObject1*>(
svc_cfg->GetParsedGlobalServiceConfigObject(0)))
->value() == 1000);
}
TEST_F(ServiceConfigTest, Parser1ErrorInvalidType) {
const char* test_json = "{\"global_param\":\"5\"}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("(Service config parsing "
"error)(.*)(referenced_errors)(.*)(Global "
"Params)(.*)(referenced_errors)()(.*)") +
TestParser1::InvalidTypeErrorMessage());
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, Parser1ErrorInvalidValue) {
const char* test_json = "{\"global_param\":-5}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("(Service config parsing "
"error)(.*)(referenced_errors)(.*)(Global "
"Params)(.*)(referenced_errors)()(.*)") +
TestParser1::InvalidValueErrorMessage());
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, Parser2BasicTest) {
const char* test_json =
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
"\"method_param\":5}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error == GRPC_ERROR_NONE);
const auto* const* vector_ptr = svc_cfg->GetMethodServiceConfigObjectsVector(
grpc_slice_from_static_string("/TestServ/TestMethod"));
EXPECT_TRUE(vector_ptr != nullptr);
const auto* vector = *vector_ptr;
auto parsed_object = ((*vector)[1]).get();
EXPECT_TRUE(static_cast<TestParsedObject1*>(parsed_object)->value() == 5);
}
TEST_F(ServiceConfigTest, Parser2ErrorInvalidType) {
const char* test_json =
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
"\"method_param\":\"5\"}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error != GRPC_ERROR_NONE);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
std::regex e(std::string("(Service config parsing "
"error)(.*)(referenced_errors\":\\[)(.*)(Method "
"Params)(.*)(referenced_errors)()(.*)(methodConfig)("
".*)(referenced_errors)(.*)") +
TestParser2::InvalidTypeErrorMessage());
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
TEST_F(ServiceConfigTest, Parser2ErrorInvalidValue) {
const char* test_json =
"{\"methodConfig\": [{\"name\":[{\"service\":\"TestServ\"}], "
"\"method_param\":-5}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error != GRPC_ERROR_NONE);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
std::regex e(std::string("(Service config parsing "
"error)(.*)(referenced_errors\":\\[)(.*)(Method "
"Params)(.*)(referenced_errors)()(.*)(methodConfig)("
".*)(referenced_errors)(.*)") +
TestParser2::InvalidValueErrorMessage());
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
// Test parsing with ErrorParsers which always add errors
class ErroredParsersScopingTest : public ::testing::Test {
protected:
void SetUp() override {
ServiceConfig::Shutdown();
ServiceConfig::Init();
EXPECT_TRUE(ServiceConfig::RegisterParser(
UniquePtr<ServiceConfigParser>(New<ErrorParser>())) == 0);
EXPECT_TRUE(ServiceConfig::RegisterParser(
UniquePtr<ServiceConfigParser>(New<ErrorParser>())) == 1);
}
};
TEST_F(ErroredParsersScopingTest, GlobalParams) {
const char* test_json = "{}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error != GRPC_ERROR_NONE);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
std::regex e(std::string("(Service config parsing "
"error)(.*)(referenced_errors\":\\[)(.*)(Global "
"Params)(.*)(referenced_errors)()(.*)") +
ErrorParser::GlobalError() + std::string("(.*)") +
ErrorParser::GlobalError());
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
TEST_F(ErroredParsersScopingTest, MethodParams) {
const char* test_json = "{\"methodConfig\": [{}]}";
grpc_error* error = GRPC_ERROR_NONE;
auto svc_cfg = ServiceConfig::Create(test_json, &error);
ASSERT_TRUE(error != GRPC_ERROR_NONE);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
std::regex e(
std::string("(Service config parsing "
"error)(.*)(referenced_errors\":\\[)(.*)(Global "
"Params)(.*)(referenced_errors)()(.*)") +
ErrorParser::GlobalError() + std::string("(.*)") +
ErrorParser::GlobalError() +
std::string("(.*)(Method "
"Params)(.*)(referenced_errors)(.*)(field:methodConfig "
"error:No names "
"found)(.*)(methodConfig)(.*)(referenced_errors)(.*)") +
ErrorParser::MethodError() + std::string("(.*)") +
ErrorParser::MethodError() + std::string("(.*)(No names specified)"));
std::smatch match;
std::string s(grpc_error_string(error));
EXPECT_TRUE(std::regex_search(s, match, e));
GRPC_ERROR_UNREF(error);
}
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(argc, argv);
grpc_init();
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
grpc_shutdown();
return ret;
}

@ -550,8 +550,10 @@ class GrpclbEnd2endTest : public ::testing::Test {
grpc_core::Resolver::Result result;
result.addresses = CreateLbAddressesFromAddressDataList(address_data);
if (service_config_json != nullptr) {
grpc_error* error = GRPC_ERROR_NONE;
result.service_config =
grpc_core::ServiceConfig::Create(service_config_json);
grpc_core::ServiceConfig::Create(service_config_json, &error);
GRPC_ERROR_UNREF(error);
}
response_generator_->SetResponse(std::move(result));
}

@ -528,8 +528,10 @@ class XdsEnd2endTest : public ::testing::Test {
grpc_core::Resolver::Result result;
result.addresses = CreateLbAddressesFromPortList(ports);
if (service_config_json != nullptr) {
grpc_error* error = GRPC_ERROR_NONE;
result.service_config =
grpc_core::ServiceConfig::Create(service_config_json);
grpc_core::ServiceConfig::Create(service_config_json, &error);
GRPC_ERROR_UNREF(error);
}
grpc_arg arg = grpc_core::FakeResolverResponseGenerator::MakeChannelArg(
lb_channel_response_generator == nullptr
@ -559,8 +561,10 @@ class XdsEnd2endTest : public ::testing::Test {
grpc_core::Resolver::Result result;
result.addresses = CreateLbAddressesFromPortList(ports);
if (service_config_json != nullptr) {
grpc_error* error = GRPC_ERROR_NONE;
result.service_config =
grpc_core::ServiceConfig::Create(service_config_json);
grpc_core::ServiceConfig::Create(service_config_json, &error);
GRPC_ERROR_UNREF(error);
}
if (lb_channel_response_generator == nullptr) {
lb_channel_response_generator = lb_channel_response_generator_.get();

@ -4744,6 +4744,24 @@
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",
"grpc",
"grpc++",
"grpc++_test",
"grpc_test_util"
],
"headers": [],
"is_filegroup": false,
"language": "c++",
"name": "service_config_test",
"src": [
"test/core/client_channel/service_config_test.cc"
],
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",

@ -5373,6 +5373,30 @@
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "service_config_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save