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