diff --git a/BUILD b/BUILD
index 34bd146cef3..90502289f66 100644
--- a/BUILD
+++ b/BUILD
@@ -1384,12 +1384,28 @@ grpc_cc_library(
"envoy_ads_upbdefs",
"grpc_base",
"grpc_client_channel",
+ "grpc_file_watcher_certificate_provider_factory",
"grpc_google_mesh_ca_certificate_provider_factory",
"grpc_transport_chttp2_client_secure",
"grpc_xds_credentials",
],
)
+grpc_cc_library(
+ name = "grpc_file_watcher_certificate_provider_factory",
+ srcs = [
+ "src/core/ext/xds/file_watcher_certificate_provider_factory.cc",
+ ],
+ hdrs = [
+ "src/core/ext/xds/file_watcher_certificate_provider_factory.h",
+ ],
+ language = "c++",
+ deps = [
+ "grpc_base",
+ "grpc_xds_credentials",
+ ],
+)
+
grpc_cc_library(
name = "grpc_google_mesh_ca_certificate_provider_factory",
srcs = [
diff --git a/BUILD.gn b/BUILD.gn
index 0acbd1b9048..6c9be4cd0b2 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -728,6 +728,8 @@ config("grpc_config") {
"src/core/ext/xds/certificate_provider_registry.h",
"src/core/ext/xds/certificate_provider_store.cc",
"src/core/ext/xds/certificate_provider_store.h",
+ "src/core/ext/xds/file_watcher_certificate_provider_factory.cc",
+ "src/core/ext/xds/file_watcher_certificate_provider_factory.h",
"src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc",
"src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h",
"src/core/ext/xds/xds_api.cc",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9339b3f725e..91229935083 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -823,6 +823,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx examine_stack_test)
endif()
add_dependencies(buildtests_cxx exception_test)
+ add_dependencies(buildtests_cxx file_watcher_certificate_provider_factory_test)
add_dependencies(buildtests_cxx filter_end2end_test)
add_dependencies(buildtests_cxx flaky_network_test)
add_dependencies(buildtests_cxx generic_end2end_test)
@@ -1708,6 +1709,7 @@ add_library(grpc
src/core/ext/upbdefs-generated/validate/validate.upbdefs.c
src/core/ext/xds/certificate_provider_registry.cc
src/core/ext/xds/certificate_provider_store.cc
+ src/core/ext/xds/file_watcher_certificate_provider_factory.cc
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
src/core/ext/xds/xds_api.cc
src/core/ext/xds/xds_bootstrap.cc
@@ -11088,6 +11090,45 @@ target_link_libraries(exception_test
)
+endif()
+if(gRPC_BUILD_TESTS)
+
+add_executable(file_watcher_certificate_provider_factory_test
+ test/core/xds/file_watcher_certificate_provider_factory_test.cc
+ third_party/googletest/googletest/src/gtest-all.cc
+ third_party/googletest/googlemock/src/gmock-all.cc
+)
+
+target_include_directories(file_watcher_certificate_provider_factory_test
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+ ${_gRPC_RE2_INCLUDE_DIR}
+ ${_gRPC_SSL_INCLUDE_DIR}
+ ${_gRPC_UPB_GENERATED_DIR}
+ ${_gRPC_UPB_GRPC_GENERATED_DIR}
+ ${_gRPC_UPB_INCLUDE_DIR}
+ ${_gRPC_ZLIB_INCLUDE_DIR}
+ third_party/googletest/googletest/include
+ third_party/googletest/googletest
+ third_party/googletest/googlemock/include
+ third_party/googletest/googlemock
+ ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(file_watcher_certificate_provider_factory_test
+ ${_gRPC_PROTOBUF_LIBRARIES}
+ ${_gRPC_ALLTARGETS_LIBRARIES}
+ grpc_test_util
+ grpc
+ gpr
+ address_sorting
+ upb
+ ${_gRPC_GFLAGS_LIBRARIES}
+)
+
+
endif()
if(gRPC_BUILD_TESTS)
diff --git a/Makefile b/Makefile
index 7438b4a44d9..d35cda2f758 100644
--- a/Makefile
+++ b/Makefile
@@ -2109,6 +2109,7 @@ LIBGRPC_SRC = \
src/core/ext/upbdefs-generated/validate/validate.upbdefs.c \
src/core/ext/xds/certificate_provider_registry.cc \
src/core/ext/xds/certificate_provider_store.cc \
+ src/core/ext/xds/file_watcher_certificate_provider_factory.cc \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \
src/core/ext/xds/xds_api.cc \
src/core/ext/xds/xds_bootstrap.cc \
@@ -4776,6 +4777,7 @@ src/core/ext/upbdefs-generated/udpa/core/v1/resource_name.upbdefs.c: $(OPENSSL_D
src/core/ext/upbdefs-generated/validate/validate.upbdefs.c: $(OPENSSL_DEP)
src/core/ext/xds/certificate_provider_registry.cc: $(OPENSSL_DEP)
src/core/ext/xds/certificate_provider_store.cc: $(OPENSSL_DEP)
+src/core/ext/xds/file_watcher_certificate_provider_factory.cc: $(OPENSSL_DEP)
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc: $(OPENSSL_DEP)
src/core/ext/xds/xds_api.cc: $(OPENSSL_DEP)
src/core/ext/xds/xds_bootstrap.cc: $(OPENSSL_DEP)
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index 576c84ae732..a819c8015dc 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -632,6 +632,7 @@ libs:
- src/core/ext/xds/certificate_provider_factory.h
- src/core/ext/xds/certificate_provider_registry.h
- src/core/ext/xds/certificate_provider_store.h
+ - src/core/ext/xds/file_watcher_certificate_provider_factory.h
- src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h
- src/core/ext/xds/xds_api.h
- src/core/ext/xds/xds_bootstrap.h
@@ -1134,6 +1135,7 @@ libs:
- src/core/ext/upbdefs-generated/validate/validate.upbdefs.c
- src/core/ext/xds/certificate_provider_registry.cc
- src/core/ext/xds/certificate_provider_store.cc
+ - src/core/ext/xds/file_watcher_certificate_provider_factory.cc
- src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
- src/core/ext/xds/xds_api.cc
- src/core/ext/xds/xds_bootstrap.cc
@@ -5979,6 +5981,19 @@ targets:
- gpr
- address_sorting
- upb
+- name: file_watcher_certificate_provider_factory_test
+ gtest: true
+ build: test
+ language: c++
+ headers: []
+ src:
+ - test/core/xds/file_watcher_certificate_provider_factory_test.cc
+ deps:
+ - grpc_test_util
+ - grpc
+ - gpr
+ - address_sorting
+ - upb
- name: filter_end2end_test
gtest: true
build: test
diff --git a/config.m4 b/config.m4
index 9fc7ee10271..2b440d9a9cf 100644
--- a/config.m4
+++ b/config.m4
@@ -312,6 +312,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/ext/upbdefs-generated/validate/validate.upbdefs.c \
src/core/ext/xds/certificate_provider_registry.cc \
src/core/ext/xds/certificate_provider_store.cc \
+ src/core/ext/xds/file_watcher_certificate_provider_factory.cc \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \
src/core/ext/xds/xds_api.cc \
src/core/ext/xds/xds_bootstrap.cc \
diff --git a/config.w32 b/config.w32
index 914dceba5f0..b337409cade 100644
--- a/config.w32
+++ b/config.w32
@@ -279,6 +279,7 @@ if (PHP_GRPC != "no") {
"src\\core\\ext\\upbdefs-generated\\validate\\validate.upbdefs.c " +
"src\\core\\ext\\xds\\certificate_provider_registry.cc " +
"src\\core\\ext\\xds\\certificate_provider_store.cc " +
+ "src\\core\\ext\\xds\\file_watcher_certificate_provider_factory.cc " +
"src\\core\\ext\\xds\\google_mesh_ca_certificate_provider_factory.cc " +
"src\\core\\ext\\xds\\xds_api.cc " +
"src\\core\\ext\\xds\\xds_bootstrap.cc " +
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 2d2843a8809..c99f16cdda0 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -446,6 +446,7 @@ Pod::Spec.new do |s|
'src/core/ext/xds/certificate_provider_factory.h',
'src/core/ext/xds/certificate_provider_registry.h',
'src/core/ext/xds/certificate_provider_store.h',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.h',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h',
'src/core/ext/xds/xds_api.h',
'src/core/ext/xds/xds_bootstrap.h',
@@ -1054,6 +1055,7 @@ Pod::Spec.new do |s|
'src/core/ext/xds/certificate_provider_factory.h',
'src/core/ext/xds/certificate_provider_registry.h',
'src/core/ext/xds/certificate_provider_store.h',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.h',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h',
'src/core/ext/xds/xds_api.h',
'src/core/ext/xds/xds_bootstrap.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index c99259b3577..06ccfca9e2f 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -709,6 +709,8 @@ Pod::Spec.new do |s|
'src/core/ext/xds/certificate_provider_registry.h',
'src/core/ext/xds/certificate_provider_store.cc',
'src/core/ext/xds/certificate_provider_store.h',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.cc',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.h',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h',
'src/core/ext/xds/xds_api.cc',
@@ -1583,6 +1585,7 @@ Pod::Spec.new do |s|
'src/core/ext/xds/certificate_provider_factory.h',
'src/core/ext/xds/certificate_provider_registry.h',
'src/core/ext/xds/certificate_provider_store.h',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.h',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h',
'src/core/ext/xds/xds_api.h',
'src/core/ext/xds/xds_bootstrap.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index df687b72ec7..c99857566f9 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -626,6 +626,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/ext/xds/certificate_provider_registry.h )
s.files += %w( src/core/ext/xds/certificate_provider_store.cc )
s.files += %w( src/core/ext/xds/certificate_provider_store.h )
+ s.files += %w( src/core/ext/xds/file_watcher_certificate_provider_factory.cc )
+ s.files += %w( src/core/ext/xds/file_watcher_certificate_provider_factory.h )
s.files += %w( src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc )
s.files += %w( src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h )
s.files += %w( src/core/ext/xds/xds_api.cc )
diff --git a/grpc.gyp b/grpc.gyp
index dd7fa56df7e..964aa4857e3 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -722,6 +722,7 @@
'src/core/ext/upbdefs-generated/validate/validate.upbdefs.c',
'src/core/ext/xds/certificate_provider_registry.cc',
'src/core/ext/xds/certificate_provider_store.cc',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.cc',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc',
'src/core/ext/xds/xds_api.cc',
'src/core/ext/xds/xds_bootstrap.cc',
diff --git a/package.xml b/package.xml
index 40997492ed6..9860a395924 100644
--- a/package.xml
+++ b/package.xml
@@ -606,6 +606,8 @@
+
+
diff --git a/src/core/ext/filters/client_channel/resolver_result_parsing.cc b/src/core/ext/filters/client_channel/resolver_result_parsing.cc
index 6642b423021..c716bc5720d 100644
--- a/src/core/ext/filters/client_channel/resolver_result_parsing.cc
+++ b/src/core/ext/filters/client_channel/resolver_result_parsing.cc
@@ -95,26 +95,19 @@ std::unique_ptr ParseRetryPolicy(
}
}
// Parse initialBackoff.
- it = json.object_value().find("initialBackoff");
- if (it != json.object_value().end()) {
- if (!ParseDurationFromJson(it->second, &retry_policy->initial_backoff)) {
- error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "field:initialBackoff error:Failed to parse"));
- } else if (retry_policy->initial_backoff == 0) {
- error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "field:initialBackoff error:must be greater than 0"));
- }
+ if (ParseJsonObjectFieldAsDuration(json.object_value(), "initialBackoff",
+ &retry_policy->initial_backoff,
+ &error_list) &&
+ retry_policy->initial_backoff == 0) {
+ error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "field:initialBackoff error:must be greater than 0"));
}
// Parse maxBackoff.
- it = json.object_value().find("maxBackoff");
- if (it != json.object_value().end()) {
- if (!ParseDurationFromJson(it->second, &retry_policy->max_backoff)) {
- error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "field:maxBackoff error:failed to parse"));
- } else if (retry_policy->max_backoff == 0) {
- error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "field:maxBackoff error:should be greater than 0"));
- }
+ if (ParseJsonObjectFieldAsDuration(json.object_value(), "maxBackoff",
+ &retry_policy->max_backoff, &error_list) &&
+ retry_policy->max_backoff == 0) {
+ error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "field:maxBackoff error:should be greater than 0"));
}
// Parse backoffMultiplier.
it = json.object_value().find("backoffMultiplier");
@@ -383,13 +376,8 @@ ClientChannelServiceConfigParser::ParsePerMethodParams(
}
}
// Parse timeout.
- it = json.object_value().find("timeout");
- if (it != json.object_value().end()) {
- if (!ParseDurationFromJson(it->second, &timeout)) {
- error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
- "field:timeout error:Failed parsing"));
- };
- }
+ ParseJsonObjectFieldAsDuration(json.object_value(), "timeout", &timeout,
+ &error_list, false);
// Parse retry policy.
it = json.object_value().find("retryPolicy");
if (it != json.object_value().end()) {
diff --git a/src/core/ext/xds/file_watcher_certificate_provider_factory.cc b/src/core/ext/xds/file_watcher_certificate_provider_factory.cc
new file mode 100644
index 00000000000..fab8dac52ef
--- /dev/null
+++ b/src/core/ext/xds/file_watcher_certificate_provider_factory.cc
@@ -0,0 +1,119 @@
+//
+//
+// Copyright 2020 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
+
+#include "src/core/ext/xds/file_watcher_certificate_provider_factory.h"
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
+
+#include "src/core/lib/json/json_util.h"
+
+namespace grpc_core {
+
+namespace {
+
+const char* kFileWatcherPlugin = "file_watcher";
+
+} // namespace
+
+//
+// FileWatcherCertificateProviderFactory::Config
+//
+
+const char* FileWatcherCertificateProviderFactory::Config::name() const {
+ return kFileWatcherPlugin;
+}
+
+std::string FileWatcherCertificateProviderFactory::Config::ToString() const {
+ std::vector parts;
+ parts.push_back("{");
+ if (!identity_cert_file_.empty()) {
+ parts.push_back(
+ absl::StrFormat("certificate_file=\"%s\", ", identity_cert_file_));
+ }
+ if (!identity_cert_file_.empty()) {
+ parts.push_back(
+ absl::StrFormat("private_key_file=\"%s\", ", private_key_file_));
+ }
+ if (!identity_cert_file_.empty()) {
+ parts.push_back(
+ absl::StrFormat("ca_certificate_file=\"%s\", ", root_cert_file_));
+ }
+ parts.push_back(
+ absl::StrFormat("refresh_interval=%ldms}", refresh_interval_ms_));
+ return absl::StrJoin(parts, "");
+}
+
+RefCountedPtr
+FileWatcherCertificateProviderFactory::Config::Parse(const Json& config_json,
+ grpc_error** error) {
+ auto config = MakeRefCounted();
+ if (config_json.type() != Json::Type::OBJECT) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "error:config type should be OBJECT.");
+ return nullptr;
+ }
+ std::vector error_list;
+ ParseJsonObjectField(config_json.object_value(), "certificate_file",
+ &config->identity_cert_file_, &error_list, false);
+ ParseJsonObjectField(config_json.object_value(), "private_key_file",
+ &config->private_key_file_, &error_list, false);
+ if (config->identity_cert_file_.empty() !=
+ config->private_key_file_.empty()) {
+ error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "fields \"certificate_file\" and \"private_key_file\" must be both set "
+ "or both unset."));
+ }
+ ParseJsonObjectField(config_json.object_value(), "ca_certificate_file",
+ &config->root_cert_file_, &error_list, false);
+ if (config->identity_cert_file_.empty() && config->root_cert_file_.empty()) {
+ error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "At least one of \"certificate_file\" and \"ca_certificate_file\" must "
+ "be specified."));
+ }
+ if (!ParseJsonObjectFieldAsDuration(
+ config_json.object_value(), "refresh_interval",
+ &config->refresh_interval_ms_, &error_list, false)) {
+ config->refresh_interval_ms_ = 10 * 60 * 1000; // 10 minutes default
+ }
+ if (!error_list.empty()) {
+ *error = GRPC_ERROR_CREATE_FROM_VECTOR(
+ "Error parsing file watcher certificate provider config", &error_list);
+ return nullptr;
+ }
+ return config;
+}
+
+//
+// FileWatcherCertificateProviderFactory
+//
+
+const char* FileWatcherCertificateProviderFactory::name() const {
+ return kFileWatcherPlugin;
+}
+
+RefCountedPtr
+FileWatcherCertificateProviderFactory::CreateCertificateProviderConfig(
+ const Json& config_json, grpc_error** error) {
+ return FileWatcherCertificateProviderFactory::Config::Parse(config_json,
+ error);
+}
+
+} // namespace grpc_core
diff --git a/src/core/ext/xds/file_watcher_certificate_provider_factory.h b/src/core/ext/xds/file_watcher_certificate_provider_factory.h
new file mode 100644
index 00000000000..96b61e1869b
--- /dev/null
+++ b/src/core/ext/xds/file_watcher_certificate_provider_factory.h
@@ -0,0 +1,72 @@
+//
+//
+// Copyright 2020 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.
+//
+//
+
+#ifndef GRPC_CORE_EXT_XDS_FILE_WATCHER_CERTIFICATE_PROVIDER_FACTORY_H
+#define GRPC_CORE_EXT_XDS_FILE_WATCHER_CERTIFICATE_PROVIDER_FACTORY_H
+
+#include
+
+#include "src/core/ext/xds/certificate_provider_factory.h"
+
+namespace grpc_core {
+
+class FileWatcherCertificateProviderFactory
+ : public CertificateProviderFactory {
+ public:
+ class Config : public CertificateProviderFactory::Config {
+ public:
+ static RefCountedPtr Parse(const Json& config_json,
+ grpc_error** error);
+
+ const char* name() const override;
+
+ std::string ToString() const override;
+
+ const std::string& identity_cert_file() const {
+ return identity_cert_file_;
+ }
+
+ const std::string& private_key_file() const { return private_key_file_; }
+
+ const std::string& root_cert_file() const { return root_cert_file_; }
+
+ grpc_millis refresh_interval_ms() const { return refresh_interval_ms_; }
+
+ private:
+ std::string identity_cert_file_;
+ std::string private_key_file_;
+ std::string root_cert_file_;
+ grpc_millis refresh_interval_ms_;
+ };
+
+ const char* name() const override;
+
+ RefCountedPtr
+ CreateCertificateProviderConfig(const Json& config_json,
+ grpc_error** error) override;
+
+ RefCountedPtr CreateCertificateProvider(
+ RefCountedPtr config) override {
+ // TODO(yashykt) : To be implemented
+ return nullptr;
+ }
+};
+
+} // namespace grpc_core
+
+#endif // GRPC_CORE_EXT_XDS_FILE_WATCHER_CERTIFICATE_PROVIDER_FACTORY_H
diff --git a/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc b/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
index ef59b894d60..c1b7b84a826 100644
--- a/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
+++ b/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
@@ -37,123 +37,6 @@ namespace {
const char* kMeshCaPlugin = "meshCA";
-//
-// Helper functions for extracting types from JSON
-//
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- NumericType* output, ErrorVectorType* error_list) {
- static_assert(std::is_integral::value, "Integral required");
- if (json.type() != Json::Type::NUMBER) {
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:type should be NUMBER")
- .c_str()));
- return false;
- }
- std::istringstream ss(json.string_value());
- ss >> *output;
- // The JSON parsing API should have dealt with parsing errors, but check
- // anyway
- if (GPR_UNLIKELY(ss.bad())) {
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:failed to parse.").c_str()));
- return false;
- }
- return true;
-}
-
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- bool* output, ErrorVectorType* error_list) {
- switch (json.type()) {
- case Json::Type::JSON_TRUE:
- *output = true;
- return true;
- case Json::Type::JSON_FALSE:
- *output = false;
- return true;
- default:
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:type should be BOOLEAN")
- .c_str()));
- return false;
- }
-}
-
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- std::string* output, ErrorVectorType* error_list) {
- if (json.type() != Json::Type::STRING) {
- *output = "";
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:type should be STRING")
- .c_str()));
- return false;
- }
- *output = json.string_value();
- return true;
-}
-
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- const Json::Array** output, ErrorVectorType* error_list) {
- if (json.type() != Json::Type::ARRAY) {
- *output = nullptr;
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:type should be ARRAY")
- .c_str()));
- return false;
- }
- *output = &json.array_value();
- return true;
-}
-
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- const Json::Object** output, ErrorVectorType* error_list) {
- if (json.type() != Json::Type::OBJECT) {
- *output = nullptr;
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:type should be OBJECT")
- .c_str()));
- return false;
- }
- *output = &json.object_value();
- return true;
-}
-
-template
-bool ExtractJsonType(const Json& json, const std::string& field_name,
- grpc_millis* output, ErrorVectorType* error_list) {
- if (!ParseDurationFromJson(json, output)) {
- *output = GRPC_MILLIS_INF_PAST;
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name,
- " error:type should be STRING of the form given by "
- "google.proto.Duration.")
- .c_str()));
- return false;
- }
- return true;
-}
-
-template
-bool ParseJsonObjectField(const Json::Object& object,
- const std::string& field_name, T* output,
- ErrorVectorType* error_list, bool optional = false) {
- auto it = object.find(field_name);
- if (it == object.end()) {
- if (!optional) {
- error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
- absl::StrCat("field:", field_name, " error:does not exist.")
- .c_str()));
- }
- return false;
- }
- auto& child_object_json = it->second;
- return ExtractJsonType(child_object_json, field_name, output, error_list);
-}
-
} // namespace
//
@@ -175,22 +58,22 @@ GoogleMeshCaCertificateProviderFactory::Config::ParseJsonObjectStsService(
std::vector error_list_sts_service;
if (!ParseJsonObjectField(sts_service, "token_exchange_service_uri",
&sts_config_.token_exchange_service_uri,
- &error_list_sts_service, true)) {
+ &error_list_sts_service, false)) {
sts_config_.token_exchange_service_uri =
"securetoken.googleapis.com"; // default
}
ParseJsonObjectField(sts_service, "resource", &sts_config_.resource,
- &error_list_sts_service, true);
+ &error_list_sts_service, false);
ParseJsonObjectField(sts_service, "audience", &sts_config_.audience,
- &error_list_sts_service, true);
+ &error_list_sts_service, false);
if (!ParseJsonObjectField(sts_service, "scope", &sts_config_.scope,
- &error_list_sts_service, true)) {
+ &error_list_sts_service, false)) {
sts_config_.scope =
"https://www.googleapis.com/auth/cloud-platform"; // default
}
ParseJsonObjectField(sts_service, "requested_token_type",
&sts_config_.requested_token_type,
- &error_list_sts_service, true);
+ &error_list_sts_service, false);
ParseJsonObjectField(sts_service, "subject_token_path",
&sts_config_.subject_token_path,
&error_list_sts_service);
@@ -199,10 +82,10 @@ GoogleMeshCaCertificateProviderFactory::Config::ParseJsonObjectStsService(
&error_list_sts_service);
ParseJsonObjectField(sts_service, "actor_token_path",
&sts_config_.actor_token_path, &error_list_sts_service,
- true);
+ false);
ParseJsonObjectField(sts_service, "actor_token_type",
&sts_config_.actor_token_type, &error_list_sts_service,
- true);
+ false);
return error_list_sts_service;
}
@@ -228,7 +111,7 @@ GoogleMeshCaCertificateProviderFactory::Config::ParseJsonObjectGoogleGrpc(
const Json::Object& google_grpc) {
std::vector error_list_google_grpc;
if (!ParseJsonObjectField(google_grpc, "target_uri", &endpoint_,
- &error_list_google_grpc, true)) {
+ &error_list_google_grpc, false)) {
endpoint_ = "meshca.googleapis.com"; // Default target
}
const Json::Array* call_credentials_array = nullptr;
@@ -268,8 +151,8 @@ GoogleMeshCaCertificateProviderFactory::Config::ParseJsonObjectGrpcServices(
"field:google_grpc", &error_list_google_grpc));
}
}
- if (!ParseJsonObjectField(grpc_service, "timeout", &timeout_,
- &error_list_grpc_services, true)) {
+ if (!ParseJsonObjectFieldAsDuration(grpc_service, "timeout", &timeout_,
+ &error_list_grpc_services, false)) {
timeout_ = 10 * 1000; // 10sec default
}
return error_list_grpc_services;
@@ -281,7 +164,7 @@ GoogleMeshCaCertificateProviderFactory::Config::ParseJsonObjectServer(
std::vector error_list_server;
std::string api_type;
if (ParseJsonObjectField(server, "api_type", &api_type, &error_list_server,
- true)) {
+ false)) {
if (api_type != "GRPC") {
error_list_server.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:api_type error:Only GRPC is supported"));
@@ -330,30 +213,30 @@ GoogleMeshCaCertificateProviderFactory::Config::Parse(const Json& config_json,
GRPC_ERROR_CREATE_FROM_VECTOR("field:server", &error_list_server));
}
}
- if (!ParseJsonObjectField(config_json.object_value(), "certificate_lifetime",
- &config->certificate_lifetime_, &error_list,
- true)) {
+ if (!ParseJsonObjectFieldAsDuration(
+ config_json.object_value(), "certificate_lifetime",
+ &config->certificate_lifetime_, &error_list, false)) {
config->certificate_lifetime_ = 24 * 60 * 60 * 1000; // 24hrs default
}
- if (!ParseJsonObjectField(config_json.object_value(), "renewal_grace_period",
- &config->renewal_grace_period_, &error_list,
- true)) {
+ if (!ParseJsonObjectFieldAsDuration(
+ config_json.object_value(), "renewal_grace_period",
+ &config->renewal_grace_period_, &error_list, false)) {
config->renewal_grace_period_ = 12 * 60 * 60 * 1000; // 12hrs default
}
std::string key_type;
if (ParseJsonObjectField(config_json.object_value(), "key_type", &key_type,
- &error_list, true)) {
+ &error_list, false)) {
if (key_type != "RSA") {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"field:key_type error:Only RSA is supported."));
}
}
if (!ParseJsonObjectField(config_json.object_value(), "key_size",
- &config->key_size_, &error_list, true)) {
+ &config->key_size_, &error_list, false)) {
config->key_size_ = 2048; // default 2048 bit key size
}
if (!ParseJsonObjectField(config_json.object_value(), "location",
- &config->location_, &error_list, true)) {
+ &config->location_, &error_list, false)) {
// GCE/GKE Metadata server needs to be contacted to get the value.
}
if (!error_list.empty()) {
diff --git a/src/core/lib/json/json_util.h b/src/core/lib/json/json_util.h
index 071087f7658..00a96eb761e 100644
--- a/src/core/lib/json/json_util.h
+++ b/src/core/lib/json/json_util.h
@@ -21,6 +21,9 @@
#include
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/json/json.h"
@@ -32,6 +35,170 @@ namespace grpc_core {
// Returns true on success, false otherwise.
bool ParseDurationFromJson(const Json& field, grpc_millis* duration);
+//
+// Helper functions for extracting types from JSON.
+// Return true on success, false otherwise. If an error is encountered during
+// parsing, a descriptive error is appended to \a error_list.
+//
+template
+inline bool ExtractJsonNumber(const Json& json, const std::string& field_name,
+ NumericType* output,
+ ErrorVectorType* error_list) {
+ static_assert(std::is_integral::value, "Integral required");
+ if (json.type() != Json::Type::NUMBER) {
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:type should be NUMBER")
+ .c_str()));
+ return false;
+ }
+ if (!absl::SimpleAtoi(json.string_value(), output)) {
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:failed to parse.").c_str()));
+ return false;
+ }
+ return true;
+}
+
+template
+inline bool ExtractJsonBool(const Json& json, const std::string& field_name,
+ bool* output, ErrorVectorType* error_list) {
+ switch (json.type()) {
+ case Json::Type::JSON_TRUE:
+ *output = true;
+ return true;
+ case Json::Type::JSON_FALSE:
+ *output = false;
+ return true;
+ default:
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:type should be BOOLEAN")
+ .c_str()));
+ return false;
+ }
+}
+
+template
+inline bool ExtractJsonString(const Json& json, const std::string& field_name,
+ std::string* output,
+ ErrorVectorType* error_list) {
+ if (json.type() != Json::Type::STRING) {
+ *output = "";
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:type should be STRING")
+ .c_str()));
+ return false;
+ }
+ *output = json.string_value();
+ return true;
+}
+
+template
+inline bool ExtractJsonArray(const Json& json, const std::string& field_name,
+ const Json::Array** output,
+ ErrorVectorType* error_list) {
+ if (json.type() != Json::Type::ARRAY) {
+ *output = nullptr;
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:type should be ARRAY")
+ .c_str()));
+ return false;
+ }
+ *output = &json.array_value();
+ return true;
+}
+
+template
+inline bool ExtractJsonObject(const Json& json, const std::string& field_name,
+ const Json::Object** output,
+ ErrorVectorType* error_list) {
+ if (json.type() != Json::Type::OBJECT) {
+ *output = nullptr;
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:type should be OBJECT")
+ .c_str()));
+ return false;
+ }
+ *output = &json.object_value();
+ return true;
+}
+
+template
+inline bool ExtractJsonType(const Json& json, const std::string& field_name,
+ NumericType* output, ErrorVectorType* error_list) {
+ return ExtractJsonNumber(json, field_name, output, error_list);
+}
+
+template
+inline bool ExtractJsonType(const Json& json, const std::string& field_name,
+ bool* output, ErrorVectorType* error_list) {
+ return ExtractJsonBool(json, field_name, output, error_list);
+}
+
+template
+inline bool ExtractJsonType(const Json& json, const std::string& field_name,
+ std::string* output, ErrorVectorType* error_list) {
+ return ExtractJsonString(json, field_name, output, error_list);
+}
+
+template
+inline bool ExtractJsonType(const Json& json, const std::string& field_name,
+ const Json::Array** output,
+ ErrorVectorType* error_list) {
+ return ExtractJsonArray(json, field_name, output, error_list);
+}
+
+template
+inline bool ExtractJsonType(const Json& json, const std::string& field_name,
+ const Json::Object** output,
+ ErrorVectorType* error_list) {
+ return ExtractJsonObject(json, field_name, output, error_list);
+}
+
+template
+inline bool ParseJsonObjectField(const Json::Object& object,
+ const std::string& field_name, T* output,
+ ErrorVectorType* error_list,
+ bool required = true) {
+ auto it = object.find(field_name);
+ if (it == object.end()) {
+ if (required) {
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:does not exist.")
+ .c_str()));
+ }
+ return false;
+ }
+ auto& child_object_json = it->second;
+ return ExtractJsonType(child_object_json, field_name, output, error_list);
+}
+
+template
+inline bool ParseJsonObjectFieldAsDuration(const Json::Object& object,
+ const std::string& field_name,
+ grpc_millis* output,
+ ErrorVectorType* error_list,
+ bool required = true) {
+ auto it = object.find(field_name);
+ if (it == object.end()) {
+ if (required) {
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name, " error:does not exist.")
+ .c_str()));
+ }
+ return false;
+ }
+ if (!ParseDurationFromJson(it->second, output)) {
+ *output = GRPC_MILLIS_INF_PAST;
+ error_list->push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrCat("field:", field_name,
+ " error:type should be STRING of the form given by "
+ "google.proto.Duration.")
+ .c_str()));
+ return false;
+ }
+ return true;
+}
+
} // namespace grpc_core
#endif // GRPC_CORE_LIB_JSON_JSON_UTIL_H
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index db2683376ef..2ee93303c37 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -288,6 +288,7 @@ CORE_SOURCE_FILES = [
'src/core/ext/upbdefs-generated/validate/validate.upbdefs.c',
'src/core/ext/xds/certificate_provider_registry.cc',
'src/core/ext/xds/certificate_provider_store.cc',
+ 'src/core/ext/xds/file_watcher_certificate_provider_factory.cc',
'src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc',
'src/core/ext/xds/xds_api.cc',
'src/core/ext/xds/xds_bootstrap.cc',
diff --git a/test/core/client_channel/service_config_test.cc b/test/core/client_channel/service_config_test.cc
index abdb43fefa9..97749cfb15f 100644
--- a/test/core/client_channel/service_config_test.cc
+++ b/test/core/client_channel/service_config_test.cc
@@ -733,7 +733,8 @@ TEST_F(ClientChannelParserTest, InvalidTimeout) {
"Method Params.*referenced_errors.*"
"methodConfig.*referenced_errors.*"
"Client channel parser.*referenced_errors.*"
- "field:timeout error:Failed parsing"));
+ "field:timeout error:type should be STRING of the form given "
+ "by google.proto.Duration"));
GRPC_ERROR_UNREF(error);
}
@@ -876,7 +877,8 @@ TEST_F(ClientChannelParserTest, InvalidRetryPolicyInitialBackoff) {
"methodConfig.*referenced_errors.*"
"Client channel parser.*referenced_errors.*"
"retryPolicy.*referenced_errors.*"
- "field:initialBackoff error:Failed to parse"));
+ "field:initialBackoff error:type should be STRING of the "
+ "form given by google.proto.Duration"));
GRPC_ERROR_UNREF(error);
}
@@ -905,7 +907,8 @@ TEST_F(ClientChannelParserTest, InvalidRetryPolicyMaxBackoff) {
"methodConfig.*referenced_errors.*"
"Client channel parser.*referenced_errors.*"
"retryPolicy.*referenced_errors.*"
- "field:maxBackoff error:failed to parse"));
+ "field:maxBackoff error:type should be STRING of the form "
+ "given by google.proto.Duration"));
GRPC_ERROR_UNREF(error);
}
diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD
index 6ddd10d2b3f..ceca797f9c0 100644
--- a/test/core/xds/BUILD
+++ b/test/core/xds/BUILD
@@ -44,6 +44,18 @@ grpc_cc_test(
],
)
+grpc_cc_test(
+ name = "file_watcher_certificate_provider_factory_test",
+ srcs = ["file_watcher_certificate_provider_factory_test.cc"],
+ external_deps = ["gtest"],
+ language = "C++",
+ deps = [
+ "//:gpr",
+ "//:grpc",
+ "//test/core/util:grpc_test_util",
+ ],
+)
+
grpc_cc_test(
name = "google_mesh_ca_certificate_provider_factory_test",
srcs = ["google_mesh_ca_certificate_provider_factory_test.cc"],
diff --git a/test/core/xds/file_watcher_certificate_provider_factory_test.cc b/test/core/xds/file_watcher_certificate_provider_factory_test.cc
new file mode 100644
index 00000000000..0ecb0123cae
--- /dev/null
+++ b/test/core/xds/file_watcher_certificate_provider_factory_test.cc
@@ -0,0 +1,202 @@
+//
+//
+// Copyright 2020 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/xds/file_watcher_certificate_provider_factory.h"
+
+#include "absl/strings/str_format.h"
+
+#include
+#include
+
+#include
+
+#include "test/core/util/test_config.h"
+
+namespace grpc_core {
+namespace testing {
+namespace {
+
+const char* kIdentityCertFile = "/path/to/identity_cert_file";
+const char* kPrivateKeyFile = "/path/to/private_key_file";
+const char* kRootCertFile = "/path/to/root_cert_file";
+const int kRefreshInterval = 400;
+
+TEST(FileWatcherConfigTest, Basic) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"certificate_file\": \"%s\","
+ " \"private_key_file\": \"%s\","
+ " \"ca_certificate_file\": \"%s\","
+ " \"refresh_interval\": \"%ds\""
+ "}",
+ kIdentityCertFile, kPrivateKeyFile, kRootCertFile, kRefreshInterval);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ EXPECT_EQ(config->identity_cert_file(), kIdentityCertFile);
+ EXPECT_EQ(config->private_key_file(), kPrivateKeyFile);
+ EXPECT_EQ(config->root_cert_file(), kRootCertFile);
+ EXPECT_EQ(config->refresh_interval_ms(), kRefreshInterval * 1000);
+}
+
+TEST(FileWatcherConfigTest, DefaultRefreshInterval) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"certificate_file\": \"%s\","
+ " \"private_key_file\": \"%s\","
+ " \"ca_certificate_file\": \"%s\""
+ "}",
+ kIdentityCertFile, kPrivateKeyFile, kRootCertFile);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ EXPECT_EQ(config->identity_cert_file(), kIdentityCertFile);
+ EXPECT_EQ(config->private_key_file(), kPrivateKeyFile);
+ EXPECT_EQ(config->root_cert_file(), kRootCertFile);
+ EXPECT_EQ(config->refresh_interval_ms(), 600 * 1000);
+}
+
+TEST(FileWatcherConfigTest, OnlyRootCertificatesFileProvided) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"ca_certificate_file\": \"%s\""
+ "}",
+ kRootCertFile);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ EXPECT_TRUE(config->identity_cert_file().empty());
+ EXPECT_TRUE(config->private_key_file().empty());
+ EXPECT_EQ(config->root_cert_file(), kRootCertFile);
+ EXPECT_EQ(config->refresh_interval_ms(), 600 * 1000);
+}
+
+TEST(FileWatcherConfigTest, OnlyIdenityCertificatesAndPrivateKeyProvided) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"certificate_file\": \"%s\","
+ " \"private_key_file\": \"%s\""
+ "}",
+ kIdentityCertFile, kPrivateKeyFile);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ EXPECT_EQ(config->identity_cert_file(), kIdentityCertFile);
+ EXPECT_EQ(config->private_key_file(), kPrivateKeyFile);
+ EXPECT_TRUE(config->root_cert_file().empty());
+ EXPECT_EQ(config->refresh_interval_ms(), 600 * 1000);
+}
+
+TEST(FileWatcherConfigTest, WrongTypes) {
+ const char* json_str =
+ "{"
+ " \"certificate_file\": 123,"
+ " \"private_key_file\": 123,"
+ " \"ca_certificate_file\": 123,"
+ " \"refresh_interval\": 123"
+ "}";
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ EXPECT_THAT(grpc_error_string(error),
+ ::testing::ContainsRegex(
+ "field:certificate_file error:type should be STRING.*"
+ "field:private_key_file error:type should be STRING.*"
+ "field:ca_certificate_file error:type should be STRING.*"
+ "field:refresh_interval error:type should be STRING of the "
+ "form given by "
+ "google.proto.Duration.*"));
+ GRPC_ERROR_UNREF(error);
+}
+
+TEST(FileWatcherConfigTest, IdentityCertProvidedButPrivateKeyMissing) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"certificate_file\": \"%s\""
+ "}",
+ kIdentityCertFile);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ EXPECT_THAT(grpc_error_string(error),
+ ::testing::ContainsRegex(
+ "fields \"certificate_file\" and \"private_key_file\" must "
+ "be both set or both unset."));
+ GRPC_ERROR_UNREF(error);
+}
+
+TEST(FileWatcherConfigTest, PrivateKeyProvidedButIdentityCertMissing) {
+ std::string json_str = absl::StrFormat(
+ "{"
+ " \"private_key_file\": \"%s\""
+ "}",
+ kPrivateKeyFile);
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ EXPECT_THAT(grpc_error_string(error),
+ ::testing::ContainsRegex(
+ "fields \"certificate_file\" and \"private_key_file\" must "
+ "be both set or both unset."));
+ GRPC_ERROR_UNREF(error);
+}
+
+TEST(FileWatcherConfigTest, EmptyJsonObject) {
+ std::string json_str = absl::StrFormat("{}");
+ grpc_error* error = GRPC_ERROR_NONE;
+ Json json = Json::Parse(json_str, &error);
+ ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
+ auto config =
+ FileWatcherCertificateProviderFactory::Config::Parse(json, &error);
+ EXPECT_THAT(
+ grpc_error_string(error),
+ ::testing::ContainsRegex("At least one of \"certificate_file\" and "
+ "\"ca_certificate_file\" must be specified."));
+ GRPC_ERROR_UNREF(error);
+}
+
+} // namespace
+} // namespace testing
+} // namespace grpc_core
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ grpc::testing::TestEnvironment env(argc, argv);
+ grpc_init();
+ auto result = RUN_ALL_TESTS();
+ grpc_shutdown();
+ return result;
+}
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 791e2298921..7b8f2aaab8b 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -1558,6 +1558,8 @@ src/core/ext/xds/certificate_provider_registry.cc \
src/core/ext/xds/certificate_provider_registry.h \
src/core/ext/xds/certificate_provider_store.cc \
src/core/ext/xds/certificate_provider_store.h \
+src/core/ext/xds/file_watcher_certificate_provider_factory.cc \
+src/core/ext/xds/file_watcher_certificate_provider_factory.h \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h \
src/core/ext/xds/xds_api.cc \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 8abfd99d44a..aede4d9a3d3 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1395,6 +1395,8 @@ src/core/ext/xds/certificate_provider_registry.cc \
src/core/ext/xds/certificate_provider_registry.h \
src/core/ext/xds/certificate_provider_store.cc \
src/core/ext/xds/certificate_provider_store.h \
+src/core/ext/xds/file_watcher_certificate_provider_factory.cc \
+src/core/ext/xds/file_watcher_certificate_provider_factory.h \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc \
src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h \
src/core/ext/xds/xds_api.cc \
diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json
index 1a19005d128..198ab5978a9 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -4455,6 +4455,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": "file_watcher_certificate_provider_factory_test",
+ "platforms": [
+ "linux",
+ "mac",
+ "posix",
+ "windows"
+ ],
+ "uses_polling": true
+ },
{
"args": [],
"benchmark": false,