Merge pull request #21394 from markdroth/json_new_api_xds_client

Convert xds bootstrap code to use new JSON API
reviewable/pr21846/r1
Mark D. Roth 5 years ago committed by GitHub
commit 37db4d876d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 73
      src/core/ext/filters/client_channel/xds/xds_api.cc
  2. 475
      src/core/ext/filters/client_channel/xds/xds_bootstrap.cc
  3. 57
      src/core/ext/filters/client_channel/xds/xds_bootstrap.h
  4. 4
      src/core/ext/filters/client_channel/xds/xds_channel.cc
  5. 12
      src/core/ext/filters/client_channel/xds/xds_channel_secure.cc
  6. 2
      src/core/ext/filters/client_channel/xds/xds_client.cc
  7. 1
      src/core/lib/iomgr/load_file.cc
  8. 1
      src/core/lib/json/json.h
  9. 286
      test/core/client_channel/xds_bootstrap_test.cc

@ -20,6 +20,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cstdlib>
#include <grpc/impl/codegen/log.h> #include <grpc/impl/codegen/log.h>
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
@ -106,10 +107,10 @@ bool XdsDropConfig::ShouldDrop(const std::string** category_name) const {
namespace { namespace {
void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb, void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
const XdsBootstrap::MetadataValue& value); const Json& value);
void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value, void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value,
const std::vector<XdsBootstrap::MetadataValue>& values) { const Json::Array& values) {
for (const auto& value : values) { for (const auto& value : values) {
auto* value_pb = google_protobuf_ListValue_add_values(list_value, arena); auto* value_pb = google_protobuf_ListValue_add_values(list_value, arena);
PopulateMetadataValue(arena, value_pb, value); PopulateMetadataValue(arena, value_pb, value);
@ -117,13 +118,12 @@ void PopulateListValue(upb_arena* arena, google_protobuf_ListValue* list_value,
} }
void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb, void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb,
const std::map<const char*, XdsBootstrap::MetadataValue, const Json::Object& metadata) {
StringLess>& metadata) {
for (const auto& p : metadata) { for (const auto& p : metadata) {
google_protobuf_Struct_FieldsEntry* field = google_protobuf_Struct_FieldsEntry* field =
google_protobuf_Struct_add_fields(metadata_pb, arena); google_protobuf_Struct_add_fields(metadata_pb, arena);
google_protobuf_Struct_FieldsEntry_set_key(field, google_protobuf_Struct_FieldsEntry_set_key(
upb_strview_makez(p.first)); field, upb_strview_makez(p.first.c_str()));
google_protobuf_Value* value = google_protobuf_Value* value =
google_protobuf_Struct_FieldsEntry_mutable_value(field, arena); google_protobuf_Struct_FieldsEntry_mutable_value(field, arena);
PopulateMetadataValue(arena, value, p.second); PopulateMetadataValue(arena, value, p.second);
@ -131,31 +131,35 @@ void PopulateMetadata(upb_arena* arena, google_protobuf_Struct* metadata_pb,
} }
void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb, void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
const XdsBootstrap::MetadataValue& value) { const Json& value) {
switch (value.type) { switch (value.type()) {
case XdsBootstrap::MetadataValue::Type::MD_NULL: case Json::Type::JSON_NULL:
google_protobuf_Value_set_null_value(value_pb, 0); google_protobuf_Value_set_null_value(value_pb, 0);
break; break;
case XdsBootstrap::MetadataValue::Type::DOUBLE: case Json::Type::NUMBER:
google_protobuf_Value_set_number_value(value_pb, value.double_value); google_protobuf_Value_set_number_value(
value_pb, strtod(value.string_value().c_str(), nullptr));
break; break;
case XdsBootstrap::MetadataValue::Type::STRING: case Json::Type::STRING:
google_protobuf_Value_set_string_value( google_protobuf_Value_set_string_value(
value_pb, upb_strview_makez(value.string_value)); value_pb, upb_strview_makez(value.string_value().c_str()));
break; break;
case XdsBootstrap::MetadataValue::Type::BOOL: case Json::Type::JSON_TRUE:
google_protobuf_Value_set_bool_value(value_pb, value.bool_value); google_protobuf_Value_set_bool_value(value_pb, true);
break; break;
case XdsBootstrap::MetadataValue::Type::STRUCT: { case Json::Type::JSON_FALSE:
google_protobuf_Value_set_bool_value(value_pb, false);
break;
case Json::Type::OBJECT: {
google_protobuf_Struct* struct_value = google_protobuf_Struct* struct_value =
google_protobuf_Value_mutable_struct_value(value_pb, arena); google_protobuf_Value_mutable_struct_value(value_pb, arena);
PopulateMetadata(arena, struct_value, value.struct_value); PopulateMetadata(arena, struct_value, value.object_value());
break; break;
} }
case XdsBootstrap::MetadataValue::Type::LIST: { case Json::Type::ARRAY: {
google_protobuf_ListValue* list_value = google_protobuf_ListValue* list_value =
google_protobuf_Value_mutable_list_value(value_pb, arena); google_protobuf_Value_mutable_list_value(value_pb, arena);
PopulateListValue(arena, list_value, value.list_value); PopulateListValue(arena, list_value, value.array_value());
break; break;
} }
} }
@ -164,33 +168,34 @@ void PopulateMetadataValue(upb_arena* arena, google_protobuf_Value* value_pb,
void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node, void PopulateNode(upb_arena* arena, const XdsBootstrap::Node* node,
const char* build_version, envoy_api_v2_core_Node* node_msg) { const char* build_version, envoy_api_v2_core_Node* node_msg) {
if (node != nullptr) { if (node != nullptr) {
if (node->id != nullptr) { if (!node->id.empty()) {
envoy_api_v2_core_Node_set_id(node_msg, upb_strview_makez(node->id)); envoy_api_v2_core_Node_set_id(node_msg,
upb_strview_makez(node->id.c_str()));
} }
if (node->cluster != nullptr) { if (!node->cluster.empty()) {
envoy_api_v2_core_Node_set_cluster(node_msg, envoy_api_v2_core_Node_set_cluster(
upb_strview_makez(node->cluster)); node_msg, upb_strview_makez(node->cluster.c_str()));
} }
if (!node->metadata.empty()) { if (!node->metadata.object_value().empty()) {
google_protobuf_Struct* metadata = google_protobuf_Struct* metadata =
envoy_api_v2_core_Node_mutable_metadata(node_msg, arena); envoy_api_v2_core_Node_mutable_metadata(node_msg, arena);
PopulateMetadata(arena, metadata, node->metadata); PopulateMetadata(arena, metadata, node->metadata.object_value());
} }
if (node->locality_region != nullptr || node->locality_zone != nullptr || if (!node->locality_region.empty() || !node->locality_zone.empty() ||
node->locality_subzone != nullptr) { !node->locality_subzone.empty()) {
envoy_api_v2_core_Locality* locality = envoy_api_v2_core_Locality* locality =
envoy_api_v2_core_Node_mutable_locality(node_msg, arena); envoy_api_v2_core_Node_mutable_locality(node_msg, arena);
if (node->locality_region != nullptr) { if (!node->locality_region.empty()) {
envoy_api_v2_core_Locality_set_region( envoy_api_v2_core_Locality_set_region(
locality, upb_strview_makez(node->locality_region)); locality, upb_strview_makez(node->locality_region.c_str()));
} }
if (node->locality_zone != nullptr) { if (!node->locality_zone.empty()) {
envoy_api_v2_core_Locality_set_zone( envoy_api_v2_core_Locality_set_zone(
locality, upb_strview_makez(node->locality_zone)); locality, upb_strview_makez(node->locality_zone.c_str()));
} }
if (node->locality_subzone != nullptr) { if (!node->locality_subzone.empty()) {
envoy_api_v2_core_Locality_set_sub_zone( envoy_api_v2_core_Locality_set_sub_zone(
locality, upb_strview_makez(node->locality_subzone)); locality, upb_strview_makez(node->locality_subzone.c_str()));
} }
} }
} }

@ -39,86 +39,54 @@ std::unique_ptr<XdsBootstrap> XdsBootstrap::ReadFromFile(grpc_error** error) {
grpc_slice contents; grpc_slice contents;
*error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents); *error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
if (*error != GRPC_ERROR_NONE) return nullptr; if (*error != GRPC_ERROR_NONE) return nullptr;
return grpc_core::MakeUnique<XdsBootstrap>(contents, error); Json json = Json::Parse(StringViewFromSlice(contents), error);
grpc_slice_unref_internal(contents);
if (*error != GRPC_ERROR_NONE) return nullptr;
return grpc_core::MakeUnique<XdsBootstrap>(std::move(json), error);
} }
XdsBootstrap::XdsBootstrap(grpc_slice contents, grpc_error** error) XdsBootstrap::XdsBootstrap(Json json, grpc_error** error) {
: contents_(contents) { if (json.type() != Json::Type::OBJECT) {
tree_ = grpc_json_parse_string_with_len(
reinterpret_cast<char*>(GPR_SLICE_START_PTR(contents_)),
GPR_SLICE_LENGTH(contents_));
if (tree_ == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"failed to parse bootstrap file JSON");
return;
}
if (tree_->type != GRPC_JSON_OBJECT || tree_->key != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"malformed JSON in bootstrap file"); "malformed JSON in bootstrap file");
return; return;
} }
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
bool seen_xds_servers = false; auto it = json.mutable_object()->find("xds_servers");
bool seen_node = false; if (it == json.mutable_object()->end()) {
for (grpc_json* child = tree_->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "xds_servers") == 0) {
if (child->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field is not an array"));
}
if (seen_xds_servers) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"xds_servers\" field"));
}
seen_xds_servers = true;
grpc_error* parse_error = ParseXdsServerList(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} else if (strcmp(child->key, "node") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"node\" field is not an object"));
}
if (seen_node) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"node\" field"));
}
seen_node = true;
grpc_error* parse_error = ParseNode(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
if (!seen_xds_servers) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field not present")); "\"xds_servers\" field not present"));
} else if (it->second.type() != Json::Type::ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"xds_servers\" field is not an array"));
} else {
grpc_error* parse_error = ParseXdsServerList(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
it = json.mutable_object()->find("node");
if (it != json.mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"node\" field is not an object"));
} else {
grpc_error* parse_error = ParseNode(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
} }
*error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file", *error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file",
&error_list); &error_list);
} }
XdsBootstrap::~XdsBootstrap() { grpc_error* XdsBootstrap::ParseXdsServerList(Json* json) {
grpc_json_destroy(tree_);
grpc_slice_unref_internal(contents_);
}
grpc_error* XdsBootstrap::ParseXdsServerList(grpc_json* json) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0; size_t idx = 0;
for (grpc_json *child = json->child; child != nullptr; for (Json& child : *json->mutable_array()) {
child = child->next, ++idx) { if (child.type() != Json::Type::OBJECT) {
if (child->key != nullptr) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " key is not null", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
}
if (child->type != GRPC_JSON_OBJECT) {
char* msg; char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx); gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
} else { } else {
grpc_error* parse_error = ParseXdsServer(child, idx); grpc_error* parse_error = ParseXdsServer(&child, idx);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error); if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} }
} }
@ -126,42 +94,29 @@ grpc_error* XdsBootstrap::ParseXdsServerList(grpc_json* json) {
&error_list); &error_list);
} }
grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json, size_t idx) { grpc_error* XdsBootstrap::ParseXdsServer(Json* json, size_t idx) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
servers_.emplace_back(); servers_.emplace_back();
XdsServer& server = servers_[servers_.size() - 1]; XdsServer& server = servers_[servers_.size() - 1];
bool seen_channel_creds = false; auto it = json->mutable_object()->find("server_uri");
for (grpc_json* child = json->child; child != nullptr; child = child->next) { if (it == json->mutable_object()->end()) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "server_uri") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field is not a string"));
}
if (server.server_uri != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"server_uri\" field"));
}
server.server_uri = child->value;
} else if (strcmp(child->key, "channel_creds") == 0) {
if (child->type != GRPC_JSON_ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"channel_creds\" field is not an array"));
}
if (seen_channel_creds) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"channel_creds\" field"));
}
seen_channel_creds = true;
grpc_error* parse_error = ParseChannelCredsArray(child, &server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
if (server.server_uri == nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field not present")); "\"server_uri\" field not present"));
} else if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"server_uri\" field is not a string"));
} else {
server.server_uri = std::move(*it->second.mutable_string_value());
}
it = json->mutable_object()->find("channel_creds");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::ARRAY) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"channel_creds\" field is not an array"));
} else {
grpc_error* parse_error = ParseChannelCredsArray(&it->second, &server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
} }
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case. // string is not static in this case.
@ -176,23 +131,17 @@ grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json, size_t idx) {
return error; return error;
} }
grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json, grpc_error* XdsBootstrap::ParseChannelCredsArray(Json* json,
XdsServer* server) { XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0; for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
for (grpc_json *child = json->child; child != nullptr; Json& child = json->mutable_array()->at(i);
child = child->next, ++idx) { if (child.type() != Json::Type::OBJECT) {
if (child->key != nullptr) {
char* msg; char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " key is not null", idx); gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", i);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
}
if (child->type != GRPC_JSON_OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
} else { } else {
grpc_error* parse_error = ParseChannelCreds(child, idx, server); grpc_error* parse_error = ParseChannelCreds(&child, i, server);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error); if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} }
} }
@ -200,38 +149,31 @@ grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json,
&error_list); &error_list);
} }
grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx, grpc_error* XdsBootstrap::ParseChannelCreds(Json* json, size_t idx,
XdsServer* server) { XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
ChannelCreds channel_creds; ChannelCreds channel_creds;
for (grpc_json* child = json->child; child != nullptr; child = child->next) { auto it = json->mutable_object()->find("type");
if (child->key == nullptr) { if (it == json->mutable_object()->end()) {
error_list.push_back( error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null")); GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field not present"));
} else if (strcmp(child->key, "type") == 0) { } else if (it->second.type() != Json::Type::STRING) {
if (child->type != GRPC_JSON_STRING) { error_list.push_back(
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field is not a string"));
"\"type\" field is not a string")); } else {
} channel_creds.type = std::move(*it->second.mutable_string_value());
if (channel_creds.type != nullptr) { }
error_list.push_back( it = json->mutable_object()->find("config");
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"type\" field")); if (it != json->mutable_object()->end()) {
} if (it->second.type() != Json::Type::OBJECT) {
channel_creds.type = child->value; error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
} else if (strcmp(child->key, "config") == 0) { "\"config\" field is not an object"));
if (child->type != GRPC_JSON_OBJECT) { } else {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( channel_creds.config = std::move(it->second);
"\"config\" field is not an object"));
}
if (channel_creds.config != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"config\" field"));
}
channel_creds.config = child;
} }
} }
if (channel_creds.type != nullptr) { if (!channel_creds.type.empty()) {
server->channel_creds.push_back(channel_creds); server->channel_creds.emplace_back(std::move(channel_creds));
} }
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case. // string is not static in this case.
@ -246,242 +188,81 @@ grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx,
return error; return error;
} }
grpc_error* XdsBootstrap::ParseNode(grpc_json* json) { grpc_error* XdsBootstrap::ParseNode(Json* json) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
node_ = grpc_core::MakeUnique<Node>(); node_ = grpc_core::MakeUnique<Node>();
bool seen_metadata = false; auto it = json->mutable_object()->find("id");
bool seen_locality = false; if (it != json->mutable_object()->end()) {
for (grpc_json* child = json->child; child != nullptr; child = child->next) { if (it->second.type() != Json::Type::STRING) {
if (child->key == nullptr) {
error_list.push_back( error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null")); GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"id\" field is not a string"));
} else if (strcmp(child->key, "id") == 0) { } else {
if (child->type != GRPC_JSON_STRING) { node_->id = std::move(*it->second.mutable_string_value());
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"id\" field is not a string"));
}
if (node_->id != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"id\" field"));
}
node_->id = child->value;
} else if (strcmp(child->key, "cluster") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"cluster\" field is not a string"));
}
if (node_->cluster != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"cluster\" field"));
}
node_->cluster = child->value;
} else if (strcmp(child->key, "locality") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"locality\" field is not an object"));
}
if (seen_locality) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"locality\" field"));
}
seen_locality = true;
grpc_error* parse_error = ParseLocality(child);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} else if (strcmp(child->key, "metadata") == 0) {
if (child->type != GRPC_JSON_OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"metadata\" field is not an object"));
}
if (seen_metadata) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"metadata\" field"));
}
seen_metadata = true;
InlinedVector<grpc_error*, 1> parse_errors =
ParseMetadataStruct(child, &node_->metadata);
if (!parse_errors.empty()) {
grpc_error* parse_error = GRPC_ERROR_CREATE_FROM_VECTOR(
"errors parsing \"metadata\" object", &parse_errors);
error_list.push_back(parse_error);
}
} }
} }
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object", it = json->mutable_object()->find("cluster");
&error_list); if (it != json->mutable_object()->end()) {
} if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
grpc_error* XdsBootstrap::ParseLocality(grpc_json* json) { "\"cluster\" field is not a string"));
InlinedVector<grpc_error*, 1> error_list; } else {
node_->locality_region = nullptr; node_->cluster = std::move(*it->second.mutable_string_value());
node_->locality_zone = nullptr;
node_->locality_subzone = nullptr;
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
} else if (strcmp(child->key, "region") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"region\" field is not a string"));
}
if (node_->locality_region != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"region\" field"));
}
node_->locality_region = child->value;
} else if (strcmp(child->key, "zone") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"zone\" field is not a string"));
}
if (node_->locality_zone != nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"zone\" field"));
}
node_->locality_zone = child->value;
} else if (strcmp(child->key, "subzone") == 0) {
if (child->type != GRPC_JSON_STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"subzone\" field is not a string"));
}
if (node_->locality_subzone != nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"duplicate \"subzone\" field"));
}
node_->locality_subzone = child->value;
} }
} }
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object", it = json->mutable_object()->find("locality");
&error_list); if (it != json->mutable_object()->end()) {
} if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataStruct( "\"locality\" field is not an object"));
grpc_json* json, } else {
std::map<const char*, XdsBootstrap::MetadataValue, StringLess>* result) { grpc_error* parse_error = ParseLocality(&it->second);
InlinedVector<grpc_error*, 1> error_list; if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
for (grpc_json* child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
continue;
} }
if (result->find(child->key) != result->end()) { }
char* msg; it = json->mutable_object()->find("metadata");
gpr_asprintf(&msg, "duplicate metadata key \"%s\"", child->key); if (it != json->mutable_object()->end()) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); if (it->second.type() != Json::Type::OBJECT) {
gpr_free(msg); error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"metadata\" field is not an object"));
} else {
node_->metadata = std::move(it->second);
} }
MetadataValue& value = (*result)[child->key];
grpc_error* parse_error = ParseMetadataValue(child, 0, &value);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} }
return error_list; return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
&error_list);
} }
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataList( grpc_error* XdsBootstrap::ParseLocality(Json* json) {
grpc_json* json, std::vector<MetadataValue>* result) {
InlinedVector<grpc_error*, 1> error_list; InlinedVector<grpc_error*, 1> error_list;
size_t idx = 0; auto it = json->mutable_object()->find("region");
for (grpc_json *child = json->child; child != nullptr; if (it != json->mutable_object()->end()) {
child = child->next, ++idx) { if (it->second.type() != Json::Type::STRING) {
if (child->key != nullptr) { error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
char* msg; "\"region\" field is not a string"));
gpr_asprintf(&msg, "JSON key is non-null for index %" PRIuPTR, idx); } else {
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); node_->locality_region = std::move(*it->second.mutable_string_value());
gpr_free(msg);
} }
result->emplace_back();
grpc_error* parse_error = ParseMetadataValue(child, idx, &result->back());
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
} }
return error_list; it = json->mutable_object()->find("zone");
} if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
grpc_error* XdsBootstrap::ParseMetadataValue(grpc_json* json, size_t idx, error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
MetadataValue* result) { "\"zone\" field is not a string"));
grpc_error* error = GRPC_ERROR_NONE;
auto context_func = [json, idx]() {
char* context;
if (json->key != nullptr) {
gpr_asprintf(&context, "key \"%s\"", json->key);
} else { } else {
gpr_asprintf(&context, "index %" PRIuPTR, idx); node_->locality_zone = std::move(*it->second.mutable_string_value());
} }
return context; }
}; it = json->mutable_object()->find("subzone");
switch (json->type) { if (it != json->mutable_object()->end()) {
case GRPC_JSON_STRING: if (it->second.type() != Json::Type::STRING) {
result->type = MetadataValue::Type::STRING; error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
result->string_value = json->value; "\"subzone\" field is not a string"));
break; } else {
case GRPC_JSON_NUMBER: node_->locality_subzone = std::move(*it->second.mutable_string_value());
result->type = MetadataValue::Type::DOUBLE;
errno = 0; // To distinguish error.
result->double_value = strtod(json->value, nullptr);
if (errno != 0) {
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "error parsing numeric value for %s: \"%s\"",
context, json->value);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
}
break;
case GRPC_JSON_TRUE:
result->type = MetadataValue::Type::BOOL;
result->bool_value = true;
break;
case GRPC_JSON_FALSE:
result->type = MetadataValue::Type::BOOL;
result->bool_value = false;
break;
case GRPC_JSON_NULL:
result->type = MetadataValue::Type::MD_NULL;
break;
case GRPC_JSON_ARRAY: {
result->type = MetadataValue::Type::LIST;
InlinedVector<grpc_error*, 1> error_list =
ParseMetadataList(json, &result->list_value);
if (!error_list.empty()) {
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "errors parsing struct for %s", context);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
for (size_t i = 0; i < error_list.size(); ++i) {
error = grpc_error_add_child(error, error_list[i]);
}
}
break;
}
case GRPC_JSON_OBJECT: {
result->type = MetadataValue::Type::STRUCT;
InlinedVector<grpc_error*, 1> error_list =
ParseMetadataStruct(json, &result->struct_value);
if (!error_list.empty()) {
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
// string is not static in this case.
char* context = context_func();
char* msg;
gpr_asprintf(&msg, "errors parsing struct for %s", context);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(context);
gpr_free(msg);
for (size_t i = 0; i < error_list.size(); ++i) {
error = grpc_error_add_child(error, error_list[i]);
GRPC_ERROR_UNREF(error_list[i]);
}
}
break;
} }
default:
break;
} }
return error; return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
&error_list);
} }
} // namespace grpc_core } // namespace grpc_core

@ -19,6 +19,8 @@
#include <grpc/support/port_platform.h> #include <grpc/support/port_platform.h>
#include <memory>
#include <string>
#include <vector> #include <vector>
#include <grpc/impl/codegen/slice.h> #include <grpc/impl/codegen/slice.h>
@ -33,33 +35,22 @@ namespace grpc_core {
class XdsBootstrap { class XdsBootstrap {
public: public:
struct MetadataValue {
enum class Type { MD_NULL, DOUBLE, STRING, BOOL, STRUCT, LIST };
Type type = Type::MD_NULL;
// TODO(roth): Once we can use C++17, these can be in a std::variant.
double double_value;
const char* string_value;
bool bool_value;
std::map<const char*, MetadataValue, StringLess> struct_value;
std::vector<MetadataValue> list_value;
};
struct Node { struct Node {
const char* id = nullptr; std::string id;
const char* cluster = nullptr; std::string cluster;
const char* locality_region = nullptr; std::string locality_region;
const char* locality_zone = nullptr; std::string locality_zone;
const char* locality_subzone = nullptr; std::string locality_subzone;
std::map<const char*, MetadataValue, StringLess> metadata; Json metadata;
}; };
struct ChannelCreds { struct ChannelCreds {
const char* type = nullptr; std::string type;
grpc_json* config = nullptr; Json config;
}; };
struct XdsServer { struct XdsServer {
const char* server_uri = nullptr; std::string server_uri;
InlinedVector<ChannelCreds, 1> channel_creds; InlinedVector<ChannelCreds, 1> channel_creds;
}; };
@ -68,8 +59,7 @@ class XdsBootstrap {
static std::unique_ptr<XdsBootstrap> ReadFromFile(grpc_error** error); static std::unique_ptr<XdsBootstrap> ReadFromFile(grpc_error** error);
// Do not instantiate directly -- use ReadFromFile() above instead. // Do not instantiate directly -- use ReadFromFile() above instead.
XdsBootstrap(grpc_slice contents, grpc_error** error); XdsBootstrap(Json json, grpc_error** error);
~XdsBootstrap();
// TODO(roth): We currently support only one server. Fix this when we // TODO(roth): We currently support only one server. Fix this when we
// add support for fallback for the xds channel. // add support for fallback for the xds channel.
@ -77,23 +67,12 @@ class XdsBootstrap {
const Node* node() const { return node_.get(); } const Node* node() const { return node_.get(); }
private: private:
grpc_error* ParseXdsServerList(grpc_json* json); grpc_error* ParseXdsServerList(Json* json);
grpc_error* ParseXdsServer(grpc_json* json, size_t idx); grpc_error* ParseXdsServer(Json* json, size_t idx);
grpc_error* ParseChannelCredsArray(grpc_json* json, XdsServer* server); grpc_error* ParseChannelCredsArray(Json* json, XdsServer* server);
grpc_error* ParseChannelCreds(grpc_json* json, size_t idx, XdsServer* server); grpc_error* ParseChannelCreds(Json* json, size_t idx, XdsServer* server);
grpc_error* ParseNode(grpc_json* json); grpc_error* ParseNode(Json* json);
grpc_error* ParseLocality(grpc_json* json); grpc_error* ParseLocality(Json* json);
InlinedVector<grpc_error*, 1> ParseMetadataStruct(
grpc_json* json,
std::map<const char*, MetadataValue, StringLess>* result);
InlinedVector<grpc_error*, 1> ParseMetadataList(
grpc_json* json, std::vector<MetadataValue>* result);
grpc_error* ParseMetadataValue(grpc_json* json, size_t idx,
MetadataValue* result);
grpc_slice contents_;
grpc_json* tree_ = nullptr;
InlinedVector<XdsServer, 1> servers_; InlinedVector<XdsServer, 1> servers_;
std::unique_ptr<Node> node_; std::unique_ptr<Node> node_;

@ -31,8 +31,8 @@ grpc_channel_args* ModifyXdsChannelArgs(grpc_channel_args* args) {
grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap, grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
const grpc_channel_args& args) { const grpc_channel_args& args) {
if (!bootstrap.server().channel_creds.empty()) return nullptr; if (!bootstrap.server().channel_creds.empty()) return nullptr;
return grpc_insecure_channel_create(bootstrap.server().server_uri, &args, return grpc_insecure_channel_create(bootstrap.server().server_uri.c_str(),
nullptr); &args, nullptr);
} }
} // namespace grpc_core } // namespace grpc_core

@ -69,12 +69,10 @@ grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
RefCountedPtr<grpc_channel_credentials> creds_to_unref; RefCountedPtr<grpc_channel_credentials> creds_to_unref;
if (!bootstrap.server().channel_creds.empty()) { if (!bootstrap.server().channel_creds.empty()) {
for (size_t i = 0; i < bootstrap.server().channel_creds.size(); ++i) { for (size_t i = 0; i < bootstrap.server().channel_creds.size(); ++i) {
if (strcmp(bootstrap.server().channel_creds[i].type, "google_default") == if (bootstrap.server().channel_creds[i].type == "google_default") {
0) {
creds = grpc_google_default_credentials_create(); creds = grpc_google_default_credentials_create();
break; break;
} else if (strcmp(bootstrap.server().channel_creds[i].type, "fake") == } else if (bootstrap.server().channel_creds[i].type == "fake") {
0) {
creds = grpc_fake_transport_security_credentials_create(); creds = grpc_fake_transport_security_credentials_create();
break; break;
} }
@ -85,15 +83,15 @@ grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
creds = grpc_channel_credentials_find_in_args(&args); creds = grpc_channel_credentials_find_in_args(&args);
if (creds == nullptr) { if (creds == nullptr) {
// Built with security but parent channel is insecure. // Built with security but parent channel is insecure.
return grpc_insecure_channel_create(bootstrap.server().server_uri, &args, return grpc_insecure_channel_create(bootstrap.server().server_uri.c_str(),
nullptr); &args, nullptr);
} }
} }
const char* arg_to_remove = GRPC_ARG_CHANNEL_CREDENTIALS; const char* arg_to_remove = GRPC_ARG_CHANNEL_CREDENTIALS;
grpc_channel_args* new_args = grpc_channel_args* new_args =
grpc_channel_args_copy_and_remove(&args, &arg_to_remove, 1); grpc_channel_args_copy_and_remove(&args, &arg_to_remove, 1);
grpc_channel* channel = grpc_secure_channel_create( grpc_channel* channel = grpc_secure_channel_create(
creds, bootstrap.server().server_uri, new_args, nullptr); creds, bootstrap.server().server_uri.c_str(), new_args, nullptr);
grpc_channel_args_destroy(new_args); grpc_channel_args_destroy(new_args);
return channel; return channel;
} }

@ -1735,7 +1735,7 @@ XdsClient::XdsClient(Combiner* combiner, grpc_pollset_set* interested_parties,
} }
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) { if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
gpr_log(GPR_INFO, "[xds_client %p: creating channel to %s", this, gpr_log(GPR_INFO, "[xds_client %p: creating channel to %s", this,
bootstrap_->server().server_uri); bootstrap_->server().server_uri.c_str());
} }
chand_ = MakeOrphanable<ChannelState>( chand_ = MakeOrphanable<ChannelState>(
Ref(DEBUG_LOCATION, "XdsClient+ChannelState"), channel_args); Ref(DEBUG_LOCATION, "XdsClient+ChannelState"), channel_args);

@ -53,6 +53,7 @@ grpc_error* grpc_load_file(const char* filename, int add_null_terminator,
gpr_malloc(contents_size + (add_null_terminator ? 1 : 0))); gpr_malloc(contents_size + (add_null_terminator ? 1 : 0)));
bytes_read = fread(contents, 1, contents_size, file); bytes_read = fread(contents, 1, contents_size, file);
if (bytes_read < contents_size) { if (bytes_read < contents_size) {
gpr_free(contents);
error = GRPC_OS_ERROR(errno, "fread"); error = GRPC_OS_ERROR(errno, "fread");
GPR_ASSERT(ferror(file)); GPR_ASSERT(ferror(file));
goto end; goto end;

@ -163,6 +163,7 @@ class Json {
// Accessor methods. // Accessor methods.
Type type() const { return type_; } Type type() const { return type_; }
const std::string& string_value() const { return string_value_; } const std::string& string_value() const { return string_value_; }
std::string* mutable_string_value() { return &string_value_; }
const Object& object_value() const { return object_value_; } const Object& object_value() const { return object_value_; }
Object* mutable_object() { return &object_value_; } Object* mutable_object() { return &object_value_; }
const Array& array_value() const { return array_value_; } const Array& array_value() const { return array_value_; }

@ -36,7 +36,7 @@ void VerifyRegexMatch(grpc_error* error, const std::regex& e) {
} }
TEST(XdsBootstrapTest, Basic) { TEST(XdsBootstrapTest, Basic) {
const char* json = const char* json_str =
"{" "{"
" \"xds_servers\": [" " \"xds_servers\": ["
" {" " {"
@ -70,99 +70,46 @@ TEST(XdsBootstrapTest, Basic) {
" \"ignore\": {}" " \"ignore\": {}"
" }," " },"
" \"metadata\": {" " \"metadata\": {"
" \"null\": null," " \"foo\": 1,"
" \"string\": \"quux\"," " \"bar\": 2"
" \"double\": 123.4,"
" \"bool\": true,"
" \"struct\": {"
" \"whee\": 0"
" },"
" \"list\": [1, 2, 3]"
" }," " },"
" \"ignore\": \"whee\"" " \"ignore\": \"whee\""
" }," " },"
" \"ignore\": {}" " \"ignore\": {}"
"}"; "}";
grpc_slice slice = grpc_slice_from_copied_string(json);
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
EXPECT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error); EXPECT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
EXPECT_STREQ(bootstrap.server().server_uri, "fake:///lb"); EXPECT_EQ(bootstrap.server().server_uri, "fake:///lb");
ASSERT_EQ(bootstrap.server().channel_creds.size(), 1); ASSERT_EQ(bootstrap.server().channel_creds.size(), 1);
EXPECT_STREQ(bootstrap.server().channel_creds[0].type, "fake"); EXPECT_EQ(bootstrap.server().channel_creds[0].type, "fake");
EXPECT_EQ(bootstrap.server().channel_creds[0].config, nullptr); EXPECT_EQ(bootstrap.server().channel_creds[0].config.type(),
Json::Type::JSON_NULL);
ASSERT_NE(bootstrap.node(), nullptr); ASSERT_NE(bootstrap.node(), nullptr);
EXPECT_STREQ(bootstrap.node()->id, "foo"); EXPECT_EQ(bootstrap.node()->id, "foo");
EXPECT_STREQ(bootstrap.node()->cluster, "bar"); EXPECT_EQ(bootstrap.node()->cluster, "bar");
EXPECT_STREQ(bootstrap.node()->locality_region, "milky_way"); EXPECT_EQ(bootstrap.node()->locality_region, "milky_way");
EXPECT_STREQ(bootstrap.node()->locality_zone, "sol_system"); EXPECT_EQ(bootstrap.node()->locality_zone, "sol_system");
EXPECT_STREQ(bootstrap.node()->locality_subzone, "earth"); EXPECT_EQ(bootstrap.node()->locality_subzone, "earth");
EXPECT_THAT( ASSERT_EQ(bootstrap.node()->metadata.type(), Json::Type::OBJECT);
bootstrap.node()->metadata, EXPECT_THAT(bootstrap.node()->metadata.object_value(),
::testing::ElementsAre( ::testing::ElementsAre(
::testing::Pair( ::testing::Pair(
::testing::StrEq("bool"), ::testing::Eq("bar"),
::testing::AllOf( ::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type, ::testing::Property(&Json::type, Json::Type::NUMBER),
XdsBootstrap::MetadataValue::Type::BOOL), ::testing::Property(&Json::string_value, "2"))),
::testing::Field(&XdsBootstrap::MetadataValue::bool_value, ::testing::Pair(
true))), ::testing::Eq("foo"),
::testing::Pair( ::testing::AllOf(
::testing::StrEq("double"), ::testing::Property(&Json::type, Json::Type::NUMBER),
::testing::AllOf( ::testing::Property(&Json::string_value, "1")))));
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::DOUBLE),
::testing::Field(&XdsBootstrap::MetadataValue::double_value,
123.4))),
::testing::Pair(
::testing::StrEq("list"),
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::LIST)),
::testing::Pair(::testing::StrEq("null"),
::testing::AllOf(::testing::Field(
&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::MD_NULL))),
::testing::Pair(
::testing::StrEq("string"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::STRING),
::testing::Field(&XdsBootstrap::MetadataValue::string_value,
::testing::StrEq("quux")))),
::testing::Pair(
::testing::StrEq("struct"),
::testing::AllOf(
::testing::Field(&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::STRUCT),
::testing::Field(
&XdsBootstrap::MetadataValue::struct_value,
::testing::ElementsAre(::testing::Pair(
::testing::StrEq("whee"),
::testing::AllOf(
::testing::Field(
&XdsBootstrap::MetadataValue::type,
XdsBootstrap::MetadataValue::Type::DOUBLE),
::testing::Field(
&XdsBootstrap::MetadataValue::double_value,
0)))))))));
// TODO(roth): Once our InlinedVector<> implementation supports
// iteration, replace this by using ElementsAre() in the statement above.
auto it = bootstrap.node()->metadata.find("list");
ASSERT_TRUE(it != bootstrap.node()->metadata.end());
ASSERT_EQ(it->second.list_value.size(), 3);
EXPECT_EQ(it->second.list_value[0].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[0].double_value, 1);
EXPECT_EQ(it->second.list_value[1].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[1].double_value, 2);
EXPECT_EQ(it->second.list_value[2].type,
XdsBootstrap::MetadataValue::Type::DOUBLE);
EXPECT_EQ(it->second.list_value[2].double_value, 3);
} }
TEST(XdsBootstrapTest, ValidWithoutChannelCredsAndNode) { TEST(XdsBootstrapTest, ValidWithoutChannelCredsAndNode) {
const char* json = const char* json_str =
"{" "{"
" \"xds_servers\": [" " \"xds_servers\": ["
" {" " {"
@ -170,93 +117,89 @@ TEST(XdsBootstrapTest, ValidWithoutChannelCredsAndNode) {
" }" " }"
" ]" " ]"
"}"; "}";
grpc_slice slice = grpc_slice_from_copied_string(json);
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
EXPECT_EQ(error, GRPC_ERROR_NONE); ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
EXPECT_STREQ(bootstrap.server().server_uri, "fake:///lb"); grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
EXPECT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
EXPECT_EQ(bootstrap.server().server_uri, "fake:///lb");
EXPECT_EQ(bootstrap.server().channel_creds.size(), 0); EXPECT_EQ(bootstrap.server().channel_creds.size(), 0);
EXPECT_EQ(bootstrap.node(), nullptr); EXPECT_EQ(bootstrap.node(), nullptr);
} }
TEST(XdsBootstrapTest, InvalidJson) { TEST(XdsBootstrapTest, MissingXdsServers) {
grpc_slice slice = grpc_slice_from_copied_string("");
grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("failed to parse bootstrap file JSON"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, MalformedJson) {
grpc_slice slice = grpc_slice_from_copied_string("\"foo\"");
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse("{}", &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("malformed JSON in bootstrap file")); std::regex e(std::string("\"xds_servers\" field not present"));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
TEST(XdsBootstrapTest, MissingXdsServers) { TEST(XdsBootstrapTest, TopFieldsWrongTypes) {
grpc_slice slice = grpc_slice_from_copied_string("{}"); const char* json_str =
"{"
" \"xds_servers\":1,"
" \"node\":1"
"}";
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(std::string("\"xds_servers\" field not present")); std::regex e(
std::string("\"xds_servers\" field is not an array(.*)"
"\"node\" field is not an object"));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
TEST(XdsBootstrapTest, BadXdsServers) { TEST(XdsBootstrapTest, XdsServerMissingServerUri) {
grpc_slice slice = grpc_slice_from_copied_string( const char* json_str =
"{" "{"
" \"xds_servers\":1,"
" \"xds_servers\":[{}]" " \"xds_servers\":[{}]"
"}"); "}";
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e( std::regex e(
std::string("\"xds_servers\" field is not an array(.*)" std::string("errors parsing \"xds_servers\" array(.*)"
"duplicate \"xds_servers\" field(.*)"
"errors parsing \"xds_servers\" array(.*)"
"errors parsing index 0(.*)" "errors parsing index 0(.*)"
"\"server_uri\" field not present")); "\"server_uri\" field not present"));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
TEST(XdsBootstrapTest, BadXdsServerContents) { TEST(XdsBootstrapTest, XdsServerUriAndCredsWrongTypes) {
grpc_slice slice = grpc_slice_from_copied_string( const char* json_str =
"{" "{"
" \"xds_servers\":[" " \"xds_servers\":["
" {" " {"
" \"server_uri\":1," " \"server_uri\":1,"
" \"server_uri\":\"foo\"," " \"channel_creds\":1"
" \"channel_creds\":1,"
" \"channel_creds\":{}"
" }" " }"
" ]" " ]"
"}"); "}";
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e( std::regex e(
std::string("errors parsing \"xds_servers\" array(.*)" std::string("errors parsing \"xds_servers\" array(.*)"
"errors parsing index 0(.*)" "errors parsing index 0(.*)"
"\"server_uri\" field is not a string(.*)" "\"server_uri\" field is not a string(.*)"
"duplicate \"server_uri\" field(.*)" "\"channel_creds\" field is not an array"));
"\"channel_creds\" field is not an array(.*)"
"\"channel_creds\" field is not an array(.*)"
"duplicate \"channel_creds\" field(.*)"));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
TEST(XdsBootstrapTest, BadChannelCredsContents) { TEST(XdsBootstrapTest, ChannelCredsFieldsWrongTypes) {
grpc_slice slice = grpc_slice_from_copied_string( const char* json_str =
"{" "{"
" \"xds_servers\":[" " \"xds_servers\":["
" {" " {"
@ -264,16 +207,16 @@ TEST(XdsBootstrapTest, BadChannelCredsContents) {
" \"channel_creds\":[" " \"channel_creds\":["
" {" " {"
" \"type\":0," " \"type\":0,"
" \"type\":\"fake\"," " \"config\":1"
" \"config\":1,"
" \"config\":{}"
" }" " }"
" ]" " ]"
" }" " }"
" ]" " ]"
"}"); "}";
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e( std::regex e(
@ -282,80 +225,61 @@ TEST(XdsBootstrapTest, BadChannelCredsContents) {
"errors parsing \"channel_creds\" array(.*)" "errors parsing \"channel_creds\" array(.*)"
"errors parsing index 0(.*)" "errors parsing index 0(.*)"
"\"type\" field is not a string(.*)" "\"type\" field is not a string(.*)"
"duplicate \"type\" field(.*)" "\"config\" field is not an object"));
"\"config\" field is not an object(.*)"
"duplicate \"config\" field"));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
// under TSAN, ASAN and UBSAN, bazel RBE can suffer from a std::regex TEST(XdsBootstrapTest, NodeFieldsWrongTypes) {
// stackoverflow bug if the analyzed string is too long (> ~2000 characters). As const char* json_str =
// this test is only single-thread and deterministic, it is safe to just disable
// it under TSAN and ASAN until
// https://github.com/GoogleCloudPlatform/layer-definitions/issues/591
// is resolved. The risk for UBSAN problem also doesn't seem to be very high.
#ifndef GRPC_ASAN
#ifndef GRPC_TSAN
#ifndef GRPC_UBSAN
TEST(XdsBootstrapTest, BadNode) {
grpc_slice slice = grpc_slice_from_copied_string(
"{" "{"
" \"node\":1,"
" \"node\":{" " \"node\":{"
" \"id\":0," " \"id\":0,"
" \"id\":\"foo\","
" \"cluster\":0," " \"cluster\":0,"
" \"cluster\":\"foo\","
" \"locality\":0," " \"locality\":0,"
" \"metadata\":0"
" }"
"}";
grpc_error* error = GRPC_ERROR_NONE;
Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e(
std::string("errors parsing \"node\" object(.*)"
"\"id\" field is not a string(.*)"
"\"cluster\" field is not a string(.*)"
"\"locality\" field is not an object(.*)"
"\"metadata\" field is not an object"));
VerifyRegexMatch(error, e);
}
TEST(XdsBootstrapTest, LocalityFieldsWrongType) {
const char* json_str =
"{"
" \"node\":{"
" \"locality\":{" " \"locality\":{"
" \"region\":0," " \"region\":0,"
" \"region\":\"foo\","
" \"zone\":0," " \"zone\":0,"
" \"zone\":\"foo\"," " \"subzone\":0"
" \"subzone\":0,"
" \"subzone\":\"foo\""
" },"
" \"metadata\":0,"
" \"metadata\":{"
" \"foo\":0,"
" \"foo\":\"whee\","
" \"foo\":\"whee2\""
" }" " }"
" }" " }"
"}"); "}";
grpc_error* error = GRPC_ERROR_NONE; grpc_error* error = GRPC_ERROR_NONE;
grpc_core::XdsBootstrap bootstrap(slice, &error); Json json = Json::Parse(json_str, &error);
ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error);
grpc_core::XdsBootstrap bootstrap(std::move(json), &error);
gpr_log(GPR_ERROR, "%s", grpc_error_string(error)); gpr_log(GPR_ERROR, "%s", grpc_error_string(error));
ASSERT_TRUE(error != GRPC_ERROR_NONE); ASSERT_TRUE(error != GRPC_ERROR_NONE);
std::regex e( std::regex e(
std::string("\"node\" field is not an object(.*)" std::string("errors parsing \"node\" object(.*)"
"duplicate \"node\" field(.*)"
"errors parsing \"node\" object(.*)"
"\"id\" field is not a string(.*)"
"duplicate \"id\" field(.*)"
"\"cluster\" field is not a string(.*)"
"duplicate \"cluster\" field(.*)"
"\"locality\" field is not an object(.*)"
"duplicate \"locality\" field(.*)"
"errors parsing \"locality\" object(.*)" "errors parsing \"locality\" object(.*)"
"\"region\" field is not a string(.*)" "\"region\" field is not a string(.*)"
"duplicate \"region\" field(.*)"
"\"zone\" field is not a string(.*)" "\"zone\" field is not a string(.*)"
"duplicate \"zone\" field(.*)" "\"subzone\" field is not a string"));
"\"subzone\" field is not a string(.*)"
"duplicate \"subzone\" field(.*)"
"\"metadata\" field is not an object(.*)"
"duplicate \"metadata\" field(.*)"
"errors parsing \"metadata\" object(.*)"
"duplicate metadata key \"foo\""));
VerifyRegexMatch(error, e); VerifyRegexMatch(error, e);
} }
#endif
#endif
#endif
} // namespace testing } // namespace testing
} // namespace grpc_core } // namespace grpc_core

Loading…
Cancel
Save