blob: 6a641714912e11b341aaf54cbc7f97f950045dee [file] [log] [blame]
//
// 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 {
namespace {
UniquePtr<char> BootstrapString(const XdsBootstrap& bootstrap) {
gpr_strvec v;
gpr_strvec_init(&v);
char* tmp;
if (bootstrap.node() != nullptr) {
gpr_asprintf(&tmp,
"node={\n"
" id=\"%s\",\n"
" cluster=\"%s\",\n"
" locality={\n"
" region=\"%s\",\n"
" zone=\"%s\",\n"
" subzone=\"%s\"\n"
" },\n"
" metadata=%s,\n"
"},\n",
bootstrap.node()->id.c_str(),
bootstrap.node()->cluster.c_str(),
bootstrap.node()->locality_region.c_str(),
bootstrap.node()->locality_zone.c_str(),
bootstrap.node()->locality_subzone.c_str(),
bootstrap.node()->metadata.Dump().c_str());
gpr_strvec_add(&v, tmp);
}
gpr_asprintf(&tmp,
"servers=[\n"
" {\n"
" uri=\"%s\",\n"
" creds=[\n",
bootstrap.server().server_uri.c_str());
gpr_strvec_add(&v, tmp);
for (size_t i = 0; i < bootstrap.server().channel_creds.size(); ++i) {
const auto& creds = bootstrap.server().channel_creds[i];
gpr_asprintf(&tmp, " {type=\"%s\", config=%s},\n", creds.type.c_str(),
creds.config.Dump().c_str());
gpr_strvec_add(&v, tmp);
}
gpr_strvec_add(&v, gpr_strdup(" ]\n }\n]"));
UniquePtr<char> result(gpr_strvec_flatten(&v, nullptr));
gpr_strvec_destroy(&v);
return result;
}
} // namespace
std::unique_ptr<XdsBootstrap> XdsBootstrap::ReadFromFile(XdsClient* client,
TraceFlag* tracer,
grpc_error** error) {
grpc_core::UniquePtr<char> path(gpr_getenv("GRPC_XDS_BOOTSTRAP"));
if (path == nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Environment variable GRPC_XDS_BOOTSTRAP not defined");
return nullptr;
}
if (GRPC_TRACE_FLAG_ENABLED(*tracer)) {
gpr_log(GPR_INFO,
"[xds_client %p] Got bootstrap file location from "
"GRPC_XDS_BOOTSTRAP environment variable: %s",
client, path.get());
}
grpc_slice contents;
*error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
if (*error != GRPC_ERROR_NONE) return nullptr;
StringView contents_str_view = StringViewFromSlice(contents);
if (GRPC_TRACE_FLAG_ENABLED(*tracer)) {
UniquePtr<char> str = StringViewToCString(contents_str_view);
gpr_log(GPR_DEBUG, "[xds_client %p] Bootstrap file contents: %s", client,
str.get());
}
Json json = Json::Parse(contents_str_view, error);
grpc_slice_unref_internal(contents);
if (*error != GRPC_ERROR_NONE) {
char* msg;
gpr_asprintf(&msg, "Failed to parse bootstrap file %s", path.get());
grpc_error* error_out =
GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING(msg, error, 1);
gpr_free(msg);
GRPC_ERROR_UNREF(*error);
*error = error_out;
return nullptr;
}
std::unique_ptr<XdsBootstrap> result =
absl::make_unique<XdsBootstrap>(std::move(json), error);
if (*error == GRPC_ERROR_NONE && GRPC_TRACE_FLAG_ENABLED(*tracer)) {
gpr_log(GPR_INFO,
"[xds_client %p] Bootstrap config for creating xds client:\n%s",
client, BootstrapString(*result).get());
}
return result;
}
XdsBootstrap::XdsBootstrap(Json json, grpc_error** error) {
if (json.type() != Json::Type::OBJECT) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"malformed JSON in bootstrap file");
return;
}
InlinedVector<grpc_error*, 1> error_list;
auto it = json.mutable_object()->find("xds_servers");
if (it == json.mutable_object()->end()) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"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_list);
}
grpc_error* XdsBootstrap::ParseXdsServerList(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
Json& child = json->mutable_array()->at(i);
if (child.type() != Json::Type::OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", i);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
gpr_free(msg);
} else {
grpc_error* parse_error = ParseXdsServer(&child, i);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"xds_servers\" array",
&error_list);
}
grpc_error* XdsBootstrap::ParseXdsServer(Json* json, size_t idx) {
InlinedVector<grpc_error*, 1> error_list;
servers_.emplace_back();
XdsServer& server = servers_[servers_.size() - 1];
auto it = json->mutable_object()->find("server_uri");
if (it == json->mutable_object()->end()) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"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
// 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]);
}
return error;
}
grpc_error* XdsBootstrap::ParseChannelCredsArray(Json* json,
XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list;
for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
Json& child = json->mutable_array()->at(i);
if (child.type() != Json::Type::OBJECT) {
char* msg;
gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", i);
error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
gpr_free(msg);
} else {
grpc_error* parse_error = ParseChannelCreds(&child, i, server);
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(Json* json, size_t idx,
XdsServer* server) {
InlinedVector<grpc_error*, 1> error_list;
ChannelCreds channel_creds;
auto it = json->mutable_object()->find("type");
if (it == json->mutable_object()->end()) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field not present"));
} else if (it->second.type() != Json::Type::STRING) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field is not a string"));
} else {
channel_creds.type = std::move(*it->second.mutable_string_value());
}
it = json->mutable_object()->find("config");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"config\" field is not an object"));
} else {
channel_creds.config = std::move(it->second);
}
}
if (!channel_creds.type.empty()) {
server->channel_creds.emplace_back(std::move(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]);
}
return error;
}
grpc_error* XdsBootstrap::ParseNode(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
node_ = absl::make_unique<Node>();
auto it = json->mutable_object()->find("id");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(
GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"id\" field is not a string"));
} else {
node_->id = std::move(*it->second.mutable_string_value());
}
}
it = json->mutable_object()->find("cluster");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"cluster\" field is not a string"));
} else {
node_->cluster = std::move(*it->second.mutable_string_value());
}
}
it = json->mutable_object()->find("locality");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"locality\" field is not an object"));
} else {
grpc_error* parse_error = ParseLocality(&it->second);
if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
}
}
it = json->mutable_object()->find("metadata");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::OBJECT) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"metadata\" field is not an object"));
} else {
node_->metadata = std::move(it->second);
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
&error_list);
}
grpc_error* XdsBootstrap::ParseLocality(Json* json) {
InlinedVector<grpc_error*, 1> error_list;
auto it = json->mutable_object()->find("region");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"region\" field is not a string"));
} else {
node_->locality_region = std::move(*it->second.mutable_string_value());
}
}
it = json->mutable_object()->find("zone");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"zone\" field is not a string"));
} else {
node_->locality_zone = std::move(*it->second.mutable_string_value());
}
}
it = json->mutable_object()->find("subzone");
if (it != json->mutable_object()->end()) {
if (it->second.type() != Json::Type::STRING) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"\"subzone\" field is not a string"));
} else {
node_->locality_subzone = std::move(*it->second.mutable_string_value());
}
}
return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
&error_list);
}
} // namespace grpc_core