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