mirror of https://github.com/grpc/grpc.git
Merge pull request #20380 from markdroth/xds_client_bootstrap
xds client bootstrap filereviewable/pr20476/r2^2
commit
36319502be
32 changed files with 1320 additions and 114 deletions
@ -0,0 +1,452 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/xds/xds_bootstrap.h" |
||||
|
||||
#include <errno.h> |
||||
#include <stdlib.h> |
||||
|
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/lib/gpr/env.h" |
||||
#include "src/core/lib/iomgr/load_file.h" |
||||
#include "src/core/lib/slice/slice_internal.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
UniquePtr<XdsBootstrap> XdsBootstrap::ReadFromFile(grpc_error** error) { |
||||
UniquePtr<char> path(gpr_getenv("GRPC_XDS_BOOTSTRAP")); |
||||
if (path == nullptr) { |
||||
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"GRPC_XDS_BOOTSTRAP env var not set"); |
||||
return nullptr; |
||||
} |
||||
grpc_slice contents; |
||||
*error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents); |
||||
if (*error != GRPC_ERROR_NONE) return nullptr; |
||||
return MakeUnique<XdsBootstrap>(contents, error); |
||||
} |
||||
|
||||
XdsBootstrap::XdsBootstrap(grpc_slice contents, grpc_error** error) |
||||
: contents_(contents) { |
||||
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( |
||||
"malformed JSON in bootstrap file"); |
||||
return; |
||||
} |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
bool seen_xds_server = false; |
||||
bool seen_node = false; |
||||
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_server") == 0) { |
||||
if (child->type != GRPC_JSON_OBJECT) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"\"xds_server\" field is not an object")); |
||||
} |
||||
if (seen_xds_server) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"duplicate \"xds_server\" field")); |
||||
} |
||||
seen_xds_server = true; |
||||
grpc_error* parse_error = ParseXdsServer(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_server) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"\"xds_server\" field not present")); |
||||
} |
||||
*error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file", |
||||
&error_list); |
||||
} |
||||
|
||||
XdsBootstrap::~XdsBootstrap() { |
||||
grpc_json_destroy(tree_); |
||||
grpc_slice_unref_internal(contents_); |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
server_uri_ = nullptr; |
||||
bool seen_channel_creds = false; |
||||
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, "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_uri_ != nullptr) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"duplicate \"server_uri\" field")); |
||||
} |
||||
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 a 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); |
||||
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error); |
||||
} |
||||
} |
||||
if (server_uri_ == nullptr) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"\"server_uri\" field not present")); |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"xds_server\" object", |
||||
&error_list); |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
size_t idx = 0; |
||||
for (grpc_json *child = json->child; child != nullptr; |
||||
child = child->next, ++idx) { |
||||
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; |
||||
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx); |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); |
||||
} else { |
||||
grpc_error* parse_error = ParseChannelCreds(child, idx); |
||||
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error); |
||||
} |
||||
} |
||||
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"channel_creds\" array", |
||||
&error_list); |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
ChannelCreds channel_creds; |
||||
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, "type") == 0) { |
||||
if (child->type != GRPC_JSON_STRING) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"\"type\" field is not a string")); |
||||
} |
||||
if (channel_creds.type != nullptr) { |
||||
error_list.push_back( |
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"type\" field")); |
||||
} |
||||
channel_creds.type = child->value; |
||||
} else if (strcmp(child->key, "config") == 0) { |
||||
if (child->type != GRPC_JSON_OBJECT) { |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"\"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) channel_creds_.push_back(channel_creds); |
||||
// Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
|
||||
// string is not static in this case.
|
||||
if (error_list.empty()) return GRPC_ERROR_NONE; |
||||
char* msg; |
||||
gpr_asprintf(&msg, "errors parsing index %" PRIuPTR, idx); |
||||
grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg); |
||||
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]); |
||||
} |
||||
return error; |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseNode(grpc_json* json) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
node_ = MakeUnique<Node>(); |
||||
bool seen_metadata = false; |
||||
bool seen_locality = false; |
||||
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, "id") == 0) { |
||||
if (child->type != GRPC_JSON_STRING) { |
||||
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", |
||||
&error_list); |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseLocality(grpc_json* json) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
node_->locality_region = nullptr; |
||||
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", |
||||
&error_list); |
||||
} |
||||
|
||||
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataStruct( |
||||
grpc_json* json, |
||||
Map<const char*, XdsBootstrap::MetadataValue, StringLess>* result) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
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; |
||||
gpr_asprintf(&msg, "duplicate metadata key \"%s\"", child->key); |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); |
||||
gpr_free(msg); |
||||
} |
||||
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; |
||||
} |
||||
|
||||
InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataList( |
||||
grpc_json* json, std::vector<MetadataValue>* result) { |
||||
InlinedVector<grpc_error*, 1> error_list; |
||||
size_t idx = 0; |
||||
for (grpc_json *child = json->child; child != nullptr; |
||||
child = child->next, ++idx) { |
||||
if (child->key != nullptr) { |
||||
char* msg; |
||||
gpr_asprintf(&msg, "JSON key is non-null for index %" PRIuPTR, idx); |
||||
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg)); |
||||
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; |
||||
} |
||||
|
||||
grpc_error* XdsBootstrap::ParseMetadataValue(grpc_json* json, size_t idx, |
||||
MetadataValue* result) { |
||||
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 { |
||||
gpr_asprintf(&context, "index %" PRIuPTR, idx); |
||||
} |
||||
return context; |
||||
}; |
||||
switch (json->type) { |
||||
case GRPC_JSON_STRING: |
||||
result->type = MetadataValue::Type::STRING; |
||||
result->string_value = json->value; |
||||
break; |
||||
case GRPC_JSON_NUMBER: |
||||
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]); |
||||
GRPC_ERROR_UNREF(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; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,99 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_XDS_XDS_BOOTSTRAP_H |
||||
#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_XDS_XDS_BOOTSTRAP_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <vector> |
||||
|
||||
#include <grpc/impl/codegen/slice.h> |
||||
|
||||
#include "src/core/lib/gprpp/inlined_vector.h" |
||||
#include "src/core/lib/gprpp/map.h" |
||||
#include "src/core/lib/gprpp/memory.h" |
||||
#include "src/core/lib/iomgr/error.h" |
||||
#include "src/core/lib/json/json.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class XdsBootstrap { |
||||
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; |
||||
Map<const char*, MetadataValue, StringLess> struct_value; |
||||
std::vector<MetadataValue> list_value; |
||||
}; |
||||
|
||||
struct Node { |
||||
const char* id = nullptr; |
||||
const char* cluster = nullptr; |
||||
const char* locality_region = nullptr; |
||||
const char* locality_zone = nullptr; |
||||
const char* locality_subzone = nullptr; |
||||
Map<const char*, MetadataValue, StringLess> metadata; |
||||
}; |
||||
|
||||
struct ChannelCreds { |
||||
const char* type = nullptr; |
||||
grpc_json* config = nullptr; |
||||
}; |
||||
|
||||
// If *error is not GRPC_ERROR_NONE after returning, then there was an
|
||||
// error reading the file.
|
||||
static UniquePtr<XdsBootstrap> ReadFromFile(grpc_error** error); |
||||
|
||||
// Do not instantiate directly -- use ReadFromFile() above instead.
|
||||
XdsBootstrap(grpc_slice contents, grpc_error** error); |
||||
~XdsBootstrap(); |
||||
|
||||
const char* server_uri() const { return server_uri_; } |
||||
const InlinedVector<ChannelCreds, 1>& channel_creds() const { |
||||
return channel_creds_; |
||||
} |
||||
const Node* node() const { return node_.get(); } |
||||
|
||||
private: |
||||
grpc_error* ParseXdsServer(grpc_json* json); |
||||
grpc_error* ParseChannelCredsArray(grpc_json* json); |
||||
grpc_error* ParseChannelCreds(grpc_json* json, size_t idx); |
||||
grpc_error* ParseNode(grpc_json* json); |
||||
grpc_error* ParseLocality(grpc_json* json); |
||||
|
||||
InlinedVector<grpc_error*, 1> ParseMetadataStruct( |
||||
grpc_json* json, 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; |
||||
|
||||
const char* server_uri_ = nullptr; |
||||
InlinedVector<ChannelCreds, 1> channel_creds_; |
||||
UniquePtr<Node> node_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_XDS_XDS_BOOTSTRAP_H */ |
@ -0,0 +1,338 @@ |
||||
//
|
||||
// Copyright 2019 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include <regex> |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
#include <grpc/grpc.h> |
||||
#include <grpc/slice.h> |
||||
|
||||
#include "src/core/ext/filters/client_channel/xds/xds_bootstrap.h" |
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
void VerifyRegexMatch(grpc_error* error, const std::regex& e) { |
||||
std::smatch match; |
||||
std::string s(grpc_error_string(error)); |
||||
EXPECT_TRUE(std::regex_search(s, match, e)); |
||||
GRPC_ERROR_UNREF(error); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, Basic) { |
||||
const char* json = |
||||
"{" |
||||
" \"xds_server\": {" |
||||
" \"server_uri\": \"fake:///lb\"," |
||||
" \"channel_creds\": [" |
||||
" {" |
||||
" \"type\": \"fake\"," |
||||
" \"ignore\": 0" |
||||
" }" |
||||
" ]," |
||||
" \"ignore\": 0" |
||||
" }," |
||||
" \"node\": {" |
||||
" \"id\": \"foo\"," |
||||
" \"cluster\": \"bar\"," |
||||
" \"locality\": {" |
||||
" \"region\": \"milky_way\"," |
||||
" \"zone\": \"sol_system\"," |
||||
" \"subzone\": \"earth\"," |
||||
" \"ignore\": {}" |
||||
" }," |
||||
" \"metadata\": {" |
||||
" \"null\": null," |
||||
" \"string\": \"quux\"," |
||||
" \"double\": 123.4," |
||||
" \"bool\": true," |
||||
" \"struct\": {" |
||||
" \"whee\": 0" |
||||
" }," |
||||
" \"list\": [1, 2, 3]" |
||||
" }," |
||||
" \"ignore\": \"whee\"" |
||||
" }," |
||||
" \"ignore\": {}" |
||||
"}"; |
||||
grpc_slice slice = grpc_slice_from_copied_string(json); |
||||
grpc_error* error = GRPC_ERROR_NONE; |
||||
grpc_core::XdsBootstrap bootstrap(slice, &error); |
||||
EXPECT_EQ(error, GRPC_ERROR_NONE); |
||||
EXPECT_STREQ(bootstrap.server_uri(), "fake:///lb"); |
||||
ASSERT_EQ(bootstrap.channel_creds().size(), 1); |
||||
EXPECT_STREQ(bootstrap.channel_creds()[0].type, "fake"); |
||||
EXPECT_EQ(bootstrap.channel_creds()[0].config, nullptr); |
||||
ASSERT_NE(bootstrap.node(), nullptr); |
||||
EXPECT_STREQ(bootstrap.node()->id, "foo"); |
||||
EXPECT_STREQ(bootstrap.node()->cluster, "bar"); |
||||
EXPECT_STREQ(bootstrap.node()->locality_region, "milky_way"); |
||||
EXPECT_STREQ(bootstrap.node()->locality_zone, "sol_system"); |
||||
EXPECT_STREQ(bootstrap.node()->locality_subzone, "earth"); |
||||
EXPECT_THAT( |
||||
bootstrap.node()->metadata, |
||||
::testing::ElementsAre( |
||||
::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("double"), |
||||
::testing::AllOf( |
||||
::testing::Field(&XdsBootstrap::MetadataValue::type, |
||||
XdsBootstrap::MetadataValue::Type::DOUBLE), |
||||
::testing::Field(&XdsBootstrap::MetadataValue::double_value, |
||||
123.4))), |
||||
::testing::Pair( |
||||
::testing::StrEq("bool"), |
||||
::testing::AllOf( |
||||
::testing::Field(&XdsBootstrap::MetadataValue::type, |
||||
XdsBootstrap::MetadataValue::Type::BOOL), |
||||
::testing::Field(&XdsBootstrap::MetadataValue::bool_value, |
||||
true))), |
||||
::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))))))), |
||||
::testing::Pair( |
||||
::testing::StrEq("list"), |
||||
::testing::Field(&XdsBootstrap::MetadataValue::type, |
||||
XdsBootstrap::MetadataValue::Type::LIST)))); |
||||
// 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) { |
||||
const char* json = |
||||
"{" |
||||
" \"xds_server\": {" |
||||
" \"server_uri\": \"fake:///lb\"" |
||||
" }" |
||||
"}"; |
||||
grpc_slice slice = grpc_slice_from_copied_string(json); |
||||
grpc_error* error = GRPC_ERROR_NONE; |
||||
grpc_core::XdsBootstrap bootstrap(slice, &error); |
||||
EXPECT_EQ(error, GRPC_ERROR_NONE); |
||||
EXPECT_STREQ(bootstrap.server_uri(), "fake:///lb"); |
||||
EXPECT_EQ(bootstrap.channel_creds().size(), 0); |
||||
EXPECT_EQ(bootstrap.node(), nullptr); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, InvalidJson) { |
||||
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_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("malformed JSON in bootstrap file")); |
||||
VerifyRegexMatch(error, e); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, MissingXdsServer) { |
||||
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("\"xds_server\" field not present")); |
||||
VerifyRegexMatch(error, e); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, BadXdsServer) { |
||||
grpc_slice slice = grpc_slice_from_copied_string( |
||||
"{" |
||||
" \"xds_server\":1," |
||||
" \"xds_server\":{}" |
||||
"}"); |
||||
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("\"xds_server\" field is not an object(.*)" |
||||
"duplicate \"xds_server\" field(.*)" |
||||
"errors parsing \"xds_server\" object(.*)" |
||||
"\"server_uri\" field not present")); |
||||
VerifyRegexMatch(error, e); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, BadXdsServerContents) { |
||||
grpc_slice slice = grpc_slice_from_copied_string( |
||||
"{" |
||||
" \"xds_server\":{" |
||||
" \"server_uri\":1," |
||||
" \"server_uri\":\"foo\"," |
||||
" \"channel_creds\":1," |
||||
" \"channel_creds\":{}" |
||||
" }" |
||||
"}"); |
||||
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("errors parsing \"xds_server\" object(.*)" |
||||
"\"server_uri\" field is not a string(.*)" |
||||
"duplicate \"server_uri\" field(.*)" |
||||
"\"channel_creds\" field is not an array(.*)" |
||||
"duplicate \"channel_creds\" field(.*)" |
||||
"\"channel_creds\" field is not an array")); |
||||
VerifyRegexMatch(error, e); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, BadChannelCredsContents) { |
||||
grpc_slice slice = grpc_slice_from_copied_string( |
||||
"{" |
||||
" \"xds_server\":{" |
||||
" \"server_uri\":\"foo\"," |
||||
" \"channel_creds\":[" |
||||
" {" |
||||
" \"type\":0," |
||||
" \"type\":\"fake\"," |
||||
" \"config\":1," |
||||
" \"config\":{}" |
||||
" }" |
||||
" ]" |
||||
" }" |
||||
"}"); |
||||
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("errors parsing \"xds_server\" object(.*)" |
||||
"errors parsing \"channel_creds\" object(.*)" |
||||
"\"type\" field is not a string(.*)" |
||||
"duplicate \"type\" field(.*)" |
||||
"\"config\" field is not an object(.*)" |
||||
"duplicate \"config\" field")); |
||||
VerifyRegexMatch(error, e); |
||||
} |
||||
|
||||
TEST(XdsBootstrapTest, BadNode) { |
||||
grpc_slice slice = grpc_slice_from_copied_string( |
||||
"{" |
||||
" \"node\":1," |
||||
" \"node\":{" |
||||
" \"id\":0," |
||||
" \"id\":\"foo\"," |
||||
" \"cluster\":0," |
||||
" \"cluster\":\"foo\"," |
||||
" \"locality\":0," |
||||
" \"locality\":{" |
||||
" \"region\":0," |
||||
" \"region\":\"foo\"," |
||||
" \"zone\":0," |
||||
" \"zone\":\"foo\"," |
||||
" \"subzone\":0," |
||||
" \"subzone\":\"foo\"" |
||||
" }," |
||||
" \"metadata\":0," |
||||
" \"metadata\":{" |
||||
" \"foo\":0," |
||||
" \"foo\":\"whee\"," |
||||
" \"foo\":\"whee2\"" |
||||
" }" |
||||
" }" |
||||
"}"); |
||||
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("\"node\" field is not an 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(.*)" |
||||
"\"region\" field is not a string(.*)" |
||||
"duplicate \"region\" field(.*)" |
||||
"\"zone\" field is not a string(.*)" |
||||
"duplicate \"zone\" field(.*)" |
||||
"\"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); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
// Regexes don't work in gcc4.8 and below, so just skip testing in those cases
|
||||
#if defined(__GNUC__) && \ |
||||
((__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__) <= 8)) |
||||
return 0; |
||||
#endif |
||||
grpc::testing::TestEnvironment env(argc, argv); |
||||
grpc_init(); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
int ret = RUN_ALL_TESTS(); |
||||
grpc_shutdown(); |
||||
return ret; |
||||
} |
@ -0,0 +1,22 @@ |
||||
{ |
||||
"xds_server": { |
||||
"server_uri": "fake:///lb", |
||||
"channel_creds": [ |
||||
{ |
||||
"type": "fake" |
||||
} |
||||
] |
||||
}, |
||||
"node": { |
||||
"id": "xds_end2end_test", |
||||
"cluster": "test", |
||||
"metadata": { |
||||
"foo": "bar" |
||||
}, |
||||
"locality": { |
||||
"region": "corp", |
||||
"zone": "svl", |
||||
"subzone": "mp3" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"xds_server": { |
||||
"server_uri": "fake:///wrong_lb", |
||||
"channel_creds": [ |
||||
{ |
||||
"type": "fake" |
||||
} |
||||
] |
||||
}, |
||||
"node": { |
||||
} |
||||
} |
Loading…
Reference in new issue