xref: /aosp_15_r20/external/grpc-grpc/src/core/resolver/google_c2p/google_c2p_resolver.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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 <cstdint>
20 #include <memory>
21 #include <random>
22 #include <string>
23 #include <type_traits>
24 #include <utility>
25 
26 #include "absl/status/statusor.h"
27 #include "absl/strings/str_cat.h"
28 #include "absl/strings/string_view.h"
29 #include "absl/strings/strip.h"
30 #include "absl/types/optional.h"
31 
32 #include <grpc/support/json.h>
33 #include <grpc/support/log.h>
34 
35 #include "src/core/ext/gcp/metadata_query.h"
36 #include "src/core/ext/xds/xds_bootstrap.h"
37 #include "src/core/ext/xds/xds_client_grpc.h"
38 #include "src/core/lib/channel/channel_args.h"
39 #include "src/core/lib/config/core_configuration.h"
40 #include "src/core/lib/gprpp/debug_location.h"
41 #include "src/core/lib/gprpp/env.h"
42 #include "src/core/lib/gprpp/orphanable.h"
43 #include "src/core/lib/gprpp/ref_counted_ptr.h"
44 #include "src/core/lib/gprpp/time.h"
45 #include "src/core/lib/gprpp/work_serializer.h"
46 #include "src/core/lib/iomgr/polling_entity.h"
47 #include "src/core/lib/json/json.h"
48 #include "src/core/lib/json/json_writer.h"
49 #include "src/core/resolver/resolver.h"
50 #include "src/core/resolver/resolver_factory.h"
51 #include "src/core/resolver/resolver_registry.h"
52 #include "src/core/lib/resource_quota/resource_quota.h"
53 #include "src/core/lib/security/credentials/alts/check_gcp_environment.h"
54 #include "src/core/lib/uri/uri_parser.h"
55 
56 namespace grpc_core {
57 
58 namespace {
59 
60 const char* kC2PAuthority = "traffic-director-c2p.xds.googleapis.com";
61 
62 class GoogleCloud2ProdResolver final : public Resolver {
63  public:
64   explicit GoogleCloud2ProdResolver(ResolverArgs args);
65 
66   void StartLocked() override;
67   void RequestReresolutionLocked() override;
68   void ResetBackoffLocked() override;
69   void ShutdownLocked() override;
70 
71  private:
72   void ZoneQueryDone(std::string zone);
73   void IPv6QueryDone(bool ipv6_supported);
74   void StartXdsResolver();
75 
76   ResourceQuotaRefPtr resource_quota_;
77   std::shared_ptr<WorkSerializer> work_serializer_;
78   grpc_polling_entity pollent_;
79   bool using_dns_ = false;
80   OrphanablePtr<Resolver> child_resolver_;
81   std::string metadata_server_name_ = "metadata.google.internal.";
82   bool shutdown_ = false;
83 
84   OrphanablePtr<MetadataQuery> zone_query_;
85   absl::optional<std::string> zone_;
86 
87   OrphanablePtr<MetadataQuery> ipv6_query_;
88   absl::optional<bool> supports_ipv6_;
89 };
90 
91 //
92 // GoogleCloud2ProdResolver
93 //
94 
XdsBootstrapConfigured()95 bool XdsBootstrapConfigured() {
96   return GetEnv("GRPC_XDS_BOOTSTRAP").has_value() ||
97          GetEnv("GRPC_XDS_BOOTSTRAP_CONFIG").has_value();
98 }
99 
GoogleCloud2ProdResolver(ResolverArgs args)100 GoogleCloud2ProdResolver::GoogleCloud2ProdResolver(ResolverArgs args)
101     : resource_quota_(args.args.GetObjectRef<ResourceQuota>()),
102       work_serializer_(std::move(args.work_serializer)),
103       pollent_(grpc_polling_entity_create_from_pollset_set(args.pollset_set)) {
104   absl::string_view name_to_resolve = absl::StripPrefix(args.uri.path(), "/");
105   // If we're not running on GCP, we can't use DirectPath, so delegate
106   // to the DNS resolver.
107   const bool test_only_pretend_running_on_gcp =
108       args.args
109           .GetBool("grpc.testing.google_c2p_resolver_pretend_running_on_gcp")
110           .value_or(false);
111   const bool running_on_gcp =
112       test_only_pretend_running_on_gcp || grpc_alts_is_running_on_gcp();
113   const bool federation_enabled = XdsFederationEnabled();
114   if (!running_on_gcp ||
115       // If the client is already using xDS and federation is not enabled,
116       // we can't use it here, because they may be talking to a completely
117       // different xDS server than we want to.
118       // TODO(roth): When we remove xDS federation env var protection,
119       // remove this constraint.
120       (!federation_enabled && XdsBootstrapConfigured())) {
121     using_dns_ = true;
122     child_resolver_ =
123         CoreConfiguration::Get().resolver_registry().CreateResolver(
124             absl::StrCat("dns:", name_to_resolve), args.args, args.pollset_set,
125             work_serializer_, std::move(args.result_handler));
126     GPR_ASSERT(child_resolver_ != nullptr);
127     return;
128   }
129   // Maybe override metadata server name for testing
130   absl::optional<std::string> test_only_metadata_server_override =
131       args.args.GetOwnedString(
132           "grpc.testing.google_c2p_resolver_metadata_server_override");
133   if (test_only_metadata_server_override.has_value() &&
134       !test_only_metadata_server_override->empty()) {
135     metadata_server_name_ = std::move(*test_only_metadata_server_override);
136   }
137   // Create xds resolver.
138   std::string xds_uri =
139       federation_enabled
140           ? absl::StrCat("xds://", kC2PAuthority, "/", name_to_resolve)
141           : absl::StrCat("xds:", name_to_resolve);
142   child_resolver_ = CoreConfiguration::Get().resolver_registry().CreateResolver(
143       xds_uri, args.args, args.pollset_set, work_serializer_,
144       std::move(args.result_handler));
145   GPR_ASSERT(child_resolver_ != nullptr);
146 }
147 
StartLocked()148 void GoogleCloud2ProdResolver::StartLocked() {
149   if (using_dns_) {
150     child_resolver_->StartLocked();
151     return;
152   }
153   // Using xDS.  Start metadata server queries.
154   zone_query_ = MakeOrphanable<MetadataQuery>(
155       metadata_server_name_, std::string(MetadataQuery::kZoneAttribute),
156       &pollent_,
157       [resolver = RefAsSubclass<GoogleCloud2ProdResolver>()](
158           std::string /* attribute */,
159           absl::StatusOr<std::string> result) mutable {
160         resolver->work_serializer_->Run(
161             [resolver, result = std::move(result)]() mutable {
162               resolver->ZoneQueryDone(result.ok() ? std::move(result).value()
163                                                   : "");
164             },
165             DEBUG_LOCATION);
166       },
167       Duration::Seconds(10));
168   ipv6_query_ = MakeOrphanable<MetadataQuery>(
169       metadata_server_name_, std::string(MetadataQuery::kIPv6Attribute),
170       &pollent_,
171       [resolver = RefAsSubclass<GoogleCloud2ProdResolver>()](
172           std::string /* attribute */,
173           absl::StatusOr<std::string> result) mutable {
174         resolver->work_serializer_->Run(
175             [resolver, result = std::move(result)]() {
176               // Check that the payload is non-empty in order to work around
177               // the fact that there are buggy implementations of metadata
178               // servers in the wild, which can in some cases return 200
179               // plus an empty result when they should have returned 404.
180               resolver->IPv6QueryDone(result.ok() && !result->empty());
181             },
182             DEBUG_LOCATION);
183       },
184       Duration::Seconds(10));
185 }
186 
RequestReresolutionLocked()187 void GoogleCloud2ProdResolver::RequestReresolutionLocked() {
188   if (child_resolver_ != nullptr) {
189     child_resolver_->RequestReresolutionLocked();
190   }
191 }
192 
ResetBackoffLocked()193 void GoogleCloud2ProdResolver::ResetBackoffLocked() {
194   if (child_resolver_ != nullptr) {
195     child_resolver_->ResetBackoffLocked();
196   }
197 }
198 
ShutdownLocked()199 void GoogleCloud2ProdResolver::ShutdownLocked() {
200   shutdown_ = true;
201   zone_query_.reset();
202   ipv6_query_.reset();
203   child_resolver_.reset();
204 }
205 
ZoneQueryDone(std::string zone)206 void GoogleCloud2ProdResolver::ZoneQueryDone(std::string zone) {
207   zone_query_.reset();
208   zone_ = std::move(zone);
209   if (supports_ipv6_.has_value()) StartXdsResolver();
210 }
211 
IPv6QueryDone(bool ipv6_supported)212 void GoogleCloud2ProdResolver::IPv6QueryDone(bool ipv6_supported) {
213   ipv6_query_.reset();
214   supports_ipv6_ = ipv6_supported;
215   if (zone_.has_value()) StartXdsResolver();
216 }
217 
StartXdsResolver()218 void GoogleCloud2ProdResolver::StartXdsResolver() {
219   if (shutdown_) {
220     return;
221   }
222   // Construct bootstrap JSON.
223   std::random_device rd;
224   std::mt19937 mt(rd());
225   std::uniform_int_distribution<uint64_t> dist(1, UINT64_MAX);
226   Json::Object node = {
227       {"id", Json::FromString(absl::StrCat("C2P-", dist(mt)))},
228   };
229   if (!zone_->empty()) {
230     node["locality"] = Json::FromObject({
231         {"zone", Json::FromString(*zone_)},
232     });
233   };
234   if (*supports_ipv6_) {
235     node["metadata"] = Json::FromObject({
236         {"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", Json::FromBool(true)},
237     });
238   }
239   // Allow the TD server uri to be overridden for testing purposes.
240   auto override_server =
241       GetEnv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI");
242   const char* server_uri =
243       override_server.has_value() && !override_server->empty()
244           ? override_server->c_str()
245           : "directpath-pa.googleapis.com";
246   Json xds_server = Json::FromArray({
247       Json::FromObject({
248           {"server_uri", Json::FromString(server_uri)},
249           {"channel_creds",
250            Json::FromArray({
251                Json::FromObject({
252                    {"type", Json::FromString("google_default")},
253                }),
254            })},
255           {"server_features",
256            Json::FromArray({Json::FromString("ignore_resource_deletion")})},
257       }),
258   });
259   Json bootstrap = Json::FromObject({
260       {"xds_servers", xds_server},
261       {"authorities",
262        Json::FromObject({
263            {kC2PAuthority, Json::FromObject({
264                                {"xds_servers", std::move(xds_server)},
265                            })},
266        })},
267       {"node", Json::FromObject(std::move(node))},
268   });
269   // Inject bootstrap JSON as fallback config.
270   internal::SetXdsFallbackBootstrapConfig(JsonDump(bootstrap).c_str());
271   // Now start xDS resolver.
272   child_resolver_->StartLocked();
273 }
274 
275 //
276 // Factory
277 //
278 
279 class GoogleCloud2ProdResolverFactory final : public ResolverFactory {
280  public:
scheme() const281   absl::string_view scheme() const override { return "google-c2p"; }
282 
IsValidUri(const URI & uri) const283   bool IsValidUri(const URI& uri) const override {
284     if (GPR_UNLIKELY(!uri.authority().empty())) {
285       gpr_log(GPR_ERROR, "google-c2p URI scheme does not support authorities");
286       return false;
287     }
288     return true;
289   }
290 
CreateResolver(ResolverArgs args) const291   OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
292     if (!IsValidUri(args.uri)) return nullptr;
293     return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
294   }
295 };
296 
297 // TODO(apolcyn): remove this class after user code has updated to the
298 // stable "google-c2p" URI scheme.
299 class ExperimentalGoogleCloud2ProdResolverFactory final
300     : public ResolverFactory {
301  public:
scheme() const302   absl::string_view scheme() const override {
303     return "google-c2p-experimental";
304   }
305 
IsValidUri(const URI & uri) const306   bool IsValidUri(const URI& uri) const override {
307     if (GPR_UNLIKELY(!uri.authority().empty())) {
308       gpr_log(
309           GPR_ERROR,
310           "google-c2p-experimental URI scheme does not support authorities");
311       return false;
312     }
313     return true;
314   }
315 
CreateResolver(ResolverArgs args) const316   OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
317     if (!IsValidUri(args.uri)) return nullptr;
318     return MakeOrphanable<GoogleCloud2ProdResolver>(std::move(args));
319   }
320 };
321 
322 }  // namespace
323 
RegisterCloud2ProdResolver(CoreConfiguration::Builder * builder)324 void RegisterCloud2ProdResolver(CoreConfiguration::Builder* builder) {
325   builder->resolver_registry()->RegisterResolverFactory(
326       std::make_unique<GoogleCloud2ProdResolverFactory>());
327   builder->resolver_registry()->RegisterResolverFactory(
328       std::make_unique<ExperimentalGoogleCloud2ProdResolverFactory>());
329 }
330 
331 }  // namespace grpc_core
332