Imported Upstream version 1.33.1
[platform/upstream/grpc.git] / src / core / ext / xds / xds_bootstrap.cc
1 //
2 // Copyright 2019 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include <grpc/support/port_platform.h>
18
19 #include "src/core/ext/xds/xds_bootstrap.h"
20
21 #include <vector>
22
23 #include <errno.h>
24 #include <stdlib.h>
25
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/str_format.h"
28 #include "absl/strings/str_join.h"
29 #include "absl/strings/string_view.h"
30
31 #include "src/core/lib/gpr/env.h"
32 #include "src/core/lib/gpr/string.h"
33 #include "src/core/lib/iomgr/load_file.h"
34 #include "src/core/lib/slice/slice_internal.h"
35
36 namespace grpc_core {
37
38 bool XdsBootstrap::XdsServer::ShouldUseV3() const {
39   return server_features.find("xds_v3") != server_features.end();
40 }
41
42 namespace {
43
44 std::string BootstrapString(const XdsBootstrap& bootstrap) {
45   std::vector<std::string> parts;
46   if (bootstrap.node() != nullptr) {
47     parts.push_back(absl::StrFormat(
48         "node={\n"
49         "  id=\"%s\",\n"
50         "  cluster=\"%s\",\n"
51         "  locality={\n"
52         "    region=\"%s\",\n"
53         "    zone=\"%s\",\n"
54         "    subzone=\"%s\"\n"
55         "  },\n"
56         "  metadata=%s,\n"
57         "},\n",
58         bootstrap.node()->id, bootstrap.node()->cluster,
59         bootstrap.node()->locality_region, bootstrap.node()->locality_zone,
60         bootstrap.node()->locality_subzone, bootstrap.node()->metadata.Dump()));
61   }
62   parts.push_back(
63       absl::StrFormat("servers=[\n"
64                       "  {\n"
65                       "    uri=\"%s\",\n"
66                       "    creds=[\n",
67                       bootstrap.server().server_uri));
68   for (const auto& creds : bootstrap.server().channel_creds) {
69     parts.push_back(absl::StrFormat("      {type=\"%s\", config=%s},\n",
70                                     creds.type, creds.config.Dump()));
71   }
72   parts.push_back("    ],\n");
73   if (!bootstrap.server().server_features.empty()) {
74     parts.push_back(absl::StrCat(
75         "    server_features=[",
76         absl::StrJoin(bootstrap.server().server_features, ", "), "],\n"));
77   }
78   parts.push_back("  }\n]");
79   return absl::StrJoin(parts, "");
80 }
81
82 }  // namespace
83
84 std::unique_ptr<XdsBootstrap> XdsBootstrap::ReadFromFile(XdsClient* client,
85                                                          TraceFlag* tracer,
86                                                          grpc_error** error) {
87   grpc_core::UniquePtr<char> path(gpr_getenv("GRPC_XDS_BOOTSTRAP"));
88   if (path == nullptr) {
89     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
90         "Environment variable GRPC_XDS_BOOTSTRAP not defined");
91     return nullptr;
92   }
93   if (GRPC_TRACE_FLAG_ENABLED(*tracer)) {
94     gpr_log(GPR_INFO,
95             "[xds_client %p] Got bootstrap file location from "
96             "GRPC_XDS_BOOTSTRAP environment variable: %s",
97             client, path.get());
98   }
99   grpc_slice contents;
100   *error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
101   if (*error != GRPC_ERROR_NONE) return nullptr;
102   absl::string_view contents_str_view = StringViewFromSlice(contents);
103   if (GRPC_TRACE_FLAG_ENABLED(*tracer)) {
104     gpr_log(GPR_DEBUG, "[xds_client %p] Bootstrap file contents: %s", client,
105             std::string(contents_str_view).c_str());
106   }
107   Json json = Json::Parse(contents_str_view, error);
108   grpc_slice_unref_internal(contents);
109   if (*error != GRPC_ERROR_NONE) {
110     grpc_error* error_out = GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING(
111         absl::StrCat("Failed to parse bootstrap file ", path.get()).c_str(),
112         error, 1);
113     GRPC_ERROR_UNREF(*error);
114     *error = error_out;
115     return nullptr;
116   }
117   std::unique_ptr<XdsBootstrap> result =
118       absl::make_unique<XdsBootstrap>(std::move(json), error);
119   if (*error == GRPC_ERROR_NONE && GRPC_TRACE_FLAG_ENABLED(*tracer)) {
120     gpr_log(GPR_INFO,
121             "[xds_client %p] Bootstrap config for creating xds client:\n%s",
122             client, BootstrapString(*result).c_str());
123   }
124   return result;
125 }
126
127 XdsBootstrap::XdsBootstrap(Json json, grpc_error** error) {
128   if (json.type() != Json::Type::OBJECT) {
129     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
130         "malformed JSON in bootstrap file");
131     return;
132   }
133   std::vector<grpc_error*> error_list;
134   auto it = json.mutable_object()->find("xds_servers");
135   if (it == json.mutable_object()->end()) {
136     error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
137         "\"xds_servers\" field not present"));
138   } else if (it->second.type() != Json::Type::ARRAY) {
139     error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
140         "\"xds_servers\" field is not an array"));
141   } else {
142     grpc_error* parse_error = ParseXdsServerList(&it->second);
143     if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
144   }
145   it = json.mutable_object()->find("node");
146   if (it != json.mutable_object()->end()) {
147     if (it->second.type() != Json::Type::OBJECT) {
148       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
149           "\"node\" field is not an object"));
150     } else {
151       grpc_error* parse_error = ParseNode(&it->second);
152       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
153     }
154   }
155   *error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file",
156                                          &error_list);
157 }
158
159 grpc_error* XdsBootstrap::ParseXdsServerList(Json* json) {
160   std::vector<grpc_error*> error_list;
161   for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
162     Json& child = json->mutable_array()->at(i);
163     if (child.type() != Json::Type::OBJECT) {
164       error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
165           absl::StrCat("array element ", i, " is not an object").c_str()));
166     } else {
167       grpc_error* parse_error = ParseXdsServer(&child, i);
168       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
169     }
170   }
171   return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"xds_servers\" array",
172                                        &error_list);
173 }
174
175 grpc_error* XdsBootstrap::ParseXdsServer(Json* json, size_t idx) {
176   std::vector<grpc_error*> error_list;
177   servers_.emplace_back();
178   XdsServer& server = servers_[servers_.size() - 1];
179   auto it = json->mutable_object()->find("server_uri");
180   if (it == json->mutable_object()->end()) {
181     error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
182         "\"server_uri\" field not present"));
183   } else if (it->second.type() != Json::Type::STRING) {
184     error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
185         "\"server_uri\" field is not a string"));
186   } else {
187     server.server_uri = std::move(*it->second.mutable_string_value());
188   }
189   it = json->mutable_object()->find("channel_creds");
190   if (it != json->mutable_object()->end()) {
191     if (it->second.type() != Json::Type::ARRAY) {
192       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
193           "\"channel_creds\" field is not an array"));
194     } else {
195       grpc_error* parse_error = ParseChannelCredsArray(&it->second, &server);
196       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
197     }
198   }
199   it = json->mutable_object()->find("server_features");
200   if (it != json->mutable_object()->end()) {
201     if (it->second.type() != Json::Type::ARRAY) {
202       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
203           "\"server_features\" field is not an array"));
204     } else {
205       grpc_error* parse_error = ParseServerFeaturesArray(&it->second, &server);
206       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
207     }
208   }
209   // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
210   // string is not static in this case.
211   if (error_list.empty()) return GRPC_ERROR_NONE;
212   grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(
213       absl::StrCat("errors parsing index ", idx).c_str());
214   for (size_t i = 0; i < error_list.size(); ++i) {
215     error = grpc_error_add_child(error, error_list[i]);
216   }
217   return error;
218 }
219
220 grpc_error* XdsBootstrap::ParseChannelCredsArray(Json* json,
221                                                  XdsServer* server) {
222   std::vector<grpc_error*> error_list;
223   for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
224     Json& child = json->mutable_array()->at(i);
225     if (child.type() != Json::Type::OBJECT) {
226       error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
227           absl::StrCat("array element ", i, " is not an object").c_str()));
228     } else {
229       grpc_error* parse_error = ParseChannelCreds(&child, i, server);
230       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
231     }
232   }
233   return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"channel_creds\" array",
234                                        &error_list);
235 }
236
237 grpc_error* XdsBootstrap::ParseChannelCreds(Json* json, size_t idx,
238                                             XdsServer* server) {
239   std::vector<grpc_error*> error_list;
240   ChannelCreds channel_creds;
241   auto it = json->mutable_object()->find("type");
242   if (it == json->mutable_object()->end()) {
243     error_list.push_back(
244         GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field not present"));
245   } else if (it->second.type() != Json::Type::STRING) {
246     error_list.push_back(
247         GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"type\" field is not a string"));
248   } else {
249     channel_creds.type = std::move(*it->second.mutable_string_value());
250   }
251   it = json->mutable_object()->find("config");
252   if (it != json->mutable_object()->end()) {
253     if (it->second.type() != Json::Type::OBJECT) {
254       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
255           "\"config\" field is not an object"));
256     } else {
257       channel_creds.config = std::move(it->second);
258     }
259   }
260   if (!channel_creds.type.empty()) {
261     server->channel_creds.emplace_back(std::move(channel_creds));
262   }
263   // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
264   // string is not static in this case.
265   if (error_list.empty()) return GRPC_ERROR_NONE;
266   grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(
267       absl::StrCat("errors parsing index ", idx).c_str());
268   for (size_t i = 0; i < error_list.size(); ++i) {
269     error = grpc_error_add_child(error, error_list[i]);
270   }
271   return error;
272 }
273
274 grpc_error* XdsBootstrap::ParseServerFeaturesArray(Json* json,
275                                                    XdsServer* server) {
276   std::vector<grpc_error*> error_list;
277   for (size_t i = 0; i < json->mutable_array()->size(); ++i) {
278     Json& child = json->mutable_array()->at(i);
279     if (child.type() == Json::Type::STRING &&
280         child.string_value() == "xds_v3") {
281       // TODO(roth): Remove env var check once we do interop testing and
282       // are sure that the v3 code actually works.
283       grpc_core::UniquePtr<char> enable_str(
284           gpr_getenv("GRPC_XDS_EXPERIMENTAL_V3_SUPPORT"));
285       bool enabled = false;
286       if (gpr_parse_bool_value(enable_str.get(), &enabled) && enabled) {
287         server->server_features.insert(
288             std::move(*child.mutable_string_value()));
289       }
290     }
291   }
292   return GRPC_ERROR_CREATE_FROM_VECTOR(
293       "errors parsing \"server_features\" array", &error_list);
294 }
295
296 grpc_error* XdsBootstrap::ParseNode(Json* json) {
297   std::vector<grpc_error*> error_list;
298   node_ = absl::make_unique<Node>();
299   auto it = json->mutable_object()->find("id");
300   if (it != json->mutable_object()->end()) {
301     if (it->second.type() != Json::Type::STRING) {
302       error_list.push_back(
303           GRPC_ERROR_CREATE_FROM_STATIC_STRING("\"id\" field is not a string"));
304     } else {
305       node_->id = std::move(*it->second.mutable_string_value());
306     }
307   }
308   it = json->mutable_object()->find("cluster");
309   if (it != json->mutable_object()->end()) {
310     if (it->second.type() != Json::Type::STRING) {
311       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
312           "\"cluster\" field is not a string"));
313     } else {
314       node_->cluster = std::move(*it->second.mutable_string_value());
315     }
316   }
317   it = json->mutable_object()->find("locality");
318   if (it != json->mutable_object()->end()) {
319     if (it->second.type() != Json::Type::OBJECT) {
320       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
321           "\"locality\" field is not an object"));
322     } else {
323       grpc_error* parse_error = ParseLocality(&it->second);
324       if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
325     }
326   }
327   it = json->mutable_object()->find("metadata");
328   if (it != json->mutable_object()->end()) {
329     if (it->second.type() != Json::Type::OBJECT) {
330       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
331           "\"metadata\" field is not an object"));
332     } else {
333       node_->metadata = std::move(it->second);
334     }
335   }
336   return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
337                                        &error_list);
338 }
339
340 grpc_error* XdsBootstrap::ParseLocality(Json* json) {
341   std::vector<grpc_error*> error_list;
342   auto it = json->mutable_object()->find("region");
343   if (it != json->mutable_object()->end()) {
344     if (it->second.type() != Json::Type::STRING) {
345       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
346           "\"region\" field is not a string"));
347     } else {
348       node_->locality_region = std::move(*it->second.mutable_string_value());
349     }
350   }
351   it = json->mutable_object()->find("zone");
352   if (it != json->mutable_object()->end()) {
353     if (it->second.type() != Json::Type::STRING) {
354       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
355           "\"zone\" field is not a string"));
356     } else {
357       node_->locality_zone = std::move(*it->second.mutable_string_value());
358     }
359   }
360   it = json->mutable_object()->find("subzone");
361   if (it != json->mutable_object()->end()) {
362     if (it->second.type() != Json::Type::STRING) {
363       error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
364           "\"subzone\" field is not a string"));
365     } else {
366       node_->locality_subzone = std::move(*it->second.mutable_string_value());
367     }
368   }
369   return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
370                                        &error_list);
371 }
372
373 }  // namespace grpc_core