ed2b40242fb5ff36f519d0fef7ff9cecaea72eeb
[platform/upstream/grpc.git] / src / core / ext / filters / client_channel / resolver / google_c2p / google_c2p_resolver.cc
1 //
2 // Copyright 2021 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 <random>
20
21 #include "src/core/ext/filters/client_channel/resolver_registry.h"
22 #include "src/core/ext/xds/xds_client.h"
23 #include "src/core/lib/gpr/env.h"
24 #include "src/core/lib/gpr/string.h"
25 #include "src/core/lib/http/httpcli.h"
26 #include "src/core/lib/iomgr/polling_entity.h"
27 #include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
28
29 namespace grpc_core {
30
31 namespace {
32
33 class GoogleCloud2ProdResolver : public Resolver {
34  public:
35   explicit GoogleCloud2ProdResolver(ResolverArgs args);
36
37   void StartLocked() override;
38   void RequestReresolutionLocked() override;
39   void ResetBackoffLocked() override;
40   void ShutdownLocked() override;
41
42  private:
43   // Represents an HTTP request to the metadata server.
44   class MetadataQuery : public InternallyRefCounted<MetadataQuery> {
45    public:
46     MetadataQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
47                   const char* path, grpc_polling_entity* pollent);
48     ~MetadataQuery() override;
49
50     void Orphan() override;
51
52    private:
53     static void OnHttpRequestDone(void* arg, grpc_error_handle error);
54
55     // Calls OnDone() if not already called.  Releases a ref.
56     void MaybeCallOnDone(grpc_error_handle error);
57
58     // If error is not GRPC_ERROR_NONE, then it's not safe to look at response.
59     virtual void OnDone(GoogleCloud2ProdResolver* resolver,
60                         const grpc_http_response* response,
61                         grpc_error_handle error) = 0;
62
63     RefCountedPtr<GoogleCloud2ProdResolver> resolver_;
64     grpc_httpcli_context context_;
65     grpc_httpcli_response response_;
66     grpc_closure on_done_;
67     Atomic<bool> on_done_called_{false};
68   };
69
70   // A metadata server query to get the zone.
71   class ZoneQuery : public MetadataQuery {
72    public:
73     ZoneQuery(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
74               grpc_polling_entity* pollent);
75
76    private:
77     void OnDone(GoogleCloud2ProdResolver* resolver,
78                 const grpc_http_response* response,
79                 grpc_error_handle error) override;
80   };
81
82   // A metadata server query to get the IPv6 address.
83   class IPv6Query : public MetadataQuery {
84    public:
85     IPv6Query(RefCountedPtr<GoogleCloud2ProdResolver> resolver,
86               grpc_polling_entity* pollent);
87
88    private:
89     void OnDone(GoogleCloud2ProdResolver* resolver,
90                 const grpc_http_response* response,
91                 grpc_error_handle error) override;
92   };
93
94   void ZoneQueryDone(std::string zone);
95   void IPv6QueryDone(bool ipv6_supported);
96   void StartXdsResolver();
97
98   std::shared_ptr<WorkSerializer> work_serializer_;
99   grpc_polling_entity pollent_;
100   bool using_dns_ = false;
101   OrphanablePtr<Resolver> child_resolver_;
102
103   OrphanablePtr<ZoneQuery> zone_query_;
104   absl::optional<std::string> zone_;
105
106   OrphanablePtr<IPv6Query> ipv6_query_;
107   absl::optional<bool> supports_ipv6_;
108 };
109
110 //
111 // GoogleCloud2ProdResolver::MetadataQuery
112 //
113
114 GoogleCloud2ProdResolver::MetadataQuery::MetadataQuery(
115     RefCountedPtr<GoogleCloud2ProdResolver> resolver, const char* path,
116     grpc_polling_entity* pollent)
117     : resolver_(std::move(resolver)) {
118   grpc_httpcli_context_init(&context_);
119   // Start HTTP request.
120   GRPC_CLOSURE_INIT(&on_done_, OnHttpRequestDone, this, nullptr);
121   Ref().release();  // Ref held by callback.
122   grpc_httpcli_request request;
123   memset(&request, 0, sizeof(grpc_httpcli_request));
124   grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
125                              const_cast<char*>("Google")};
126   request.host = const_cast<char*>("metadata.google.internal");
127   request.http.path = const_cast<char*>(path);
128   request.http.hdr_count = 1;
129   request.http.hdrs = &header;
130   grpc_resource_quota* resource_quota =
131       grpc_resource_quota_create("c2p_resolver");
132   grpc_httpcli_get(&context_, pollent, resource_quota, &request,
133                    ExecCtx::Get()->Now() + 10000,  // 10s timeout
134                    &on_done_, &response_);
135   grpc_resource_quota_unref_internal(resource_quota);
136 }
137
138 GoogleCloud2ProdResolver::MetadataQuery::~MetadataQuery() {
139   grpc_httpcli_context_destroy(&context_);
140   grpc_http_response_destroy(&response_);
141 }
142
143 void GoogleCloud2ProdResolver::MetadataQuery::Orphan() {
144   // TODO(roth): Once the HTTP client library supports cancellation,
145   // use that here.
146   MaybeCallOnDone(GRPC_ERROR_CANCELLED);
147 }
148
149 void GoogleCloud2ProdResolver::MetadataQuery::OnHttpRequestDone(
150     void* arg, grpc_error_handle error) {
151   auto* self = static_cast<MetadataQuery*>(arg);
152   self->MaybeCallOnDone(GRPC_ERROR_REF(error));
153 }
154
155 void GoogleCloud2ProdResolver::MetadataQuery::MaybeCallOnDone(
156     grpc_error_handle error) {
157   bool expected = false;
158   if (!on_done_called_.CompareExchangeStrong(
159           &expected, true, MemoryOrder::RELAXED, MemoryOrder::RELAXED)) {
160     // We've already called OnDone(), so just clean up.
161     GRPC_ERROR_UNREF(error);
162     Unref();
163     return;
164   }
165   // Hop back into WorkSerializer to call OnDone().
166   // Note: We implicitly pass our ref to the callback here.
167   resolver_->work_serializer_->Run(
168       [this, error]() {
169         OnDone(resolver_.get(), &response_, error);
170         Unref();
171       },
172       DEBUG_LOCATION);
173 }
174
175 //
176 // GoogleCloud2ProdResolver::ZoneQuery
177 //
178
179 GoogleCloud2ProdResolver::ZoneQuery::ZoneQuery(
180     RefCountedPtr<GoogleCloud2ProdResolver> resolver,
181     grpc_polling_entity* pollent)
182     : MetadataQuery(std::move(resolver), "/computeMetadata/v1/instance/zone",
183                     pollent) {}
184
185 void GoogleCloud2ProdResolver::ZoneQuery::OnDone(
186     GoogleCloud2ProdResolver* resolver, const grpc_http_response* response,
187     grpc_error_handle error) {
188   if (error != GRPC_ERROR_NONE) {
189     gpr_log(GPR_ERROR, "error fetching zone from metadata server: %s",
190             grpc_error_std_string(error).c_str());
191   }
192   std::string zone;
193   if (error == GRPC_ERROR_NONE && response->status == 200) {
194     absl::string_view body(response->body, response->body_length);
195     size_t i = body.find_last_of('/');
196     if (i == body.npos) {
197       gpr_log(GPR_ERROR, "could not parse zone from metadata server: %s",
198               std::string(body).c_str());
199     } else {
200       zone = std::string(body.substr(i + 1));
201     }
202   }
203   resolver->ZoneQueryDone(std::move(zone));
204   GRPC_ERROR_UNREF(error);
205 }
206
207 //
208 // GoogleCloud2ProdResolver::IPv6Query
209 //
210
211 GoogleCloud2ProdResolver::IPv6Query::IPv6Query(
212     RefCountedPtr<GoogleCloud2ProdResolver> resolver,
213     grpc_polling_entity* pollent)
214     : MetadataQuery(std::move(resolver),
215                     "/computeMetadata/v1/instance/network-interfaces/0/ipv6s",
216                     pollent) {}
217
218 void GoogleCloud2ProdResolver::IPv6Query::OnDone(
219     GoogleCloud2ProdResolver* resolver, const grpc_http_response* response,
220     grpc_error_handle error) {
221   if (error != GRPC_ERROR_NONE) {
222     gpr_log(GPR_ERROR, "error fetching IPv6 address from metadata server: %s",
223             grpc_error_std_string(error).c_str());
224   }
225   resolver->IPv6QueryDone(error == GRPC_ERROR_NONE && response->status == 200);
226   GRPC_ERROR_UNREF(error);
227 }
228
229 //
230 // GoogleCloud2ProdResolver
231 //
232
233 GoogleCloud2ProdResolver::GoogleCloud2ProdResolver(ResolverArgs args)
234     : work_serializer_(std::move(args.work_serializer)),
235       pollent_(grpc_polling_entity_create_from_pollset_set(args.pollset_set)) {
236   absl::string_view name_to_resolve = absl::StripPrefix(args.uri.path(), "/");
237   // If we're not running on GCP, we can't use DirectPath, so delegate
238   // to the DNS resolver.
239   if (!grpc_alts_is_running_on_gcp() ||
240       // If the client is already using xDS, we can't use it here, because
241       // they may be talking to a completely different xDS server than we
242       // want to.
243       // TODO(roth): When we implement xDS federation, remove this constraint.
244       UniquePtr<char>(gpr_getenv("GRPC_XDS_BOOTSTRAP")) != nullptr ||
245       UniquePtr<char>(gpr_getenv("GRPC_XDS_BOOTSTRAP_CONFIG")) != nullptr) {
246     using_dns_ = true;
247     child_resolver_ = ResolverRegistry::CreateResolver(
248         absl::StrCat("dns:", name_to_resolve).c_str(), args.args,
249         args.pollset_set, work_serializer_, std::move(args.result_handler));
250     GPR_ASSERT(child_resolver_ != nullptr);
251     return;
252   }
253   // Create xds resolver.
254   child_resolver_ = ResolverRegistry::CreateResolver(
255       absl::StrCat("xds:", name_to_resolve).c_str(), args.args,
256       args.pollset_set, work_serializer_, std::move(args.result_handler));
257   GPR_ASSERT(child_resolver_ != nullptr);
258 }
259
260 void GoogleCloud2ProdResolver::StartLocked() {
261   if (using_dns_) {
262     child_resolver_->StartLocked();
263     return;
264   }
265   // Using xDS.  Start metadata server queries.
266   zone_query_ = MakeOrphanable<ZoneQuery>(Ref(), &pollent_);
267   ipv6_query_ = MakeOrphanable<IPv6Query>(Ref(), &pollent_);
268 }
269
270 void GoogleCloud2ProdResolver::RequestReresolutionLocked() {
271   if (child_resolver_ != nullptr) {
272     child_resolver_->RequestReresolutionLocked();
273   }
274 }
275
276 void GoogleCloud2ProdResolver::ResetBackoffLocked() {
277   if (child_resolver_ != nullptr) {
278     child_resolver_->ResetBackoffLocked();
279   }
280 }
281
282 void GoogleCloud2ProdResolver::ShutdownLocked() {
283   zone_query_.reset();
284   ipv6_query_.reset();
285   child_resolver_.reset();
286 }
287
288 void GoogleCloud2ProdResolver::ZoneQueryDone(std::string zone) {
289   zone_query_.reset();
290   zone_ = std::move(zone);
291   if (supports_ipv6_.has_value()) StartXdsResolver();
292 }
293
294 void GoogleCloud2ProdResolver::IPv6QueryDone(bool ipv6_supported) {
295   ipv6_query_.reset();
296   supports_ipv6_ = ipv6_supported;
297   if (zone_.has_value()) StartXdsResolver();
298 }
299
300 void GoogleCloud2ProdResolver::StartXdsResolver() {
301   // Construct bootstrap JSON.
302   std::random_device rd;
303   std::mt19937 mt(rd());
304   std::uniform_int_distribution<uint64_t> dist(1, UINT64_MAX);
305   Json::Object node = {
306       {"id", absl::StrCat("C2P-", dist(mt))},
307   };
308   if (!zone_->empty()) {
309     node["locality"] = Json::Object{
310         {"zone", *zone_},
311     };
312   };
313   if (*supports_ipv6_) {
314     node["metadata"] = Json::Object{
315         {"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true},
316     };
317   }
318   // Allow the TD server uri to be overridden for testing purposes.
319   UniquePtr<char> override_server(
320       gpr_getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI"));
321   const char* server_uri =
322       override_server != nullptr && strlen(override_server.get()) > 0
323           ? override_server.get()
324           : "directpath-trafficdirector.googleapis.com";
325   Json bootstrap = Json::Object{
326       {"xds_servers",
327        Json::Array{
328            Json::Object{
329                {"server_uri", server_uri},
330                {"channel_creds",
331                 Json::Array{
332                     Json::Object{
333                         {"type", "google_default"},
334                     },
335                 }},
336                {"server_features", Json::Array{"xds_v3"}},
337            },
338        }},
339       {"node", std::move(node)},
340   };
341   // Inject bootstrap JSON as fallback config.
342   internal::SetXdsFallbackBootstrapConfig(bootstrap.Dump().c_str());
343   // Now start xDS resolver.
344   child_resolver_->StartLocked();
345 }
346
347 //
348 // Factory
349 //
350
351 class GoogleCloud2ProdResolverFactory : public ResolverFactory {
352  public:
353   bool IsValidUri(const URI& uri) const override {
354     if (GPR_UNLIKELY(!uri.authority().empty())) {
355       gpr_log(GPR_ERROR, "google-c2p URI scheme does not support authorities");
356       return false;
357     }
358     return true;
359   }
360
361   OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
362     if (!IsValidUri(args.uri)) return nullptr;
363     return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
364   }
365
366   const char* scheme() const override { return "google-c2p"; }
367 };
368
369 }  // namespace
370
371 void GoogleCloud2ProdResolverInit() {
372   // TODO(roth): Remove env var protection once this code is proven stable.
373   UniquePtr<char> value(gpr_getenv("GRPC_EXPERIMENTAL_GOOGLE_C2P_RESOLVER"));
374   bool parsed_value;
375   bool parse_succeeded = gpr_parse_bool_value(value.get(), &parsed_value);
376   if (parse_succeeded && parsed_value) {
377     ResolverRegistry::Builder::RegisterResolverFactory(
378         absl::make_unique<GoogleCloud2ProdResolverFactory>());
379   }
380 }
381
382 void GoogleCloud2ProdResolverShutdown() {}
383
384 }  // namespace grpc_core