13
13
# limitations under the License.
14
14
15
15
"""Helpers for :mod:`grpc`."""
16
- from typing import Generic , TypeVar , Iterator
16
+ from typing import Generic , Iterator , Optional , TypeVar
17
17
18
18
import collections
19
19
import functools
@@ -271,11 +271,23 @@ def _create_composite_credentials(
271
271
# Create a set of grpc.CallCredentials using the metadata plugin.
272
272
google_auth_credentials = grpc .metadata_call_credentials (metadata_plugin )
273
273
274
- if ssl_credentials is None :
275
- ssl_credentials = grpc .ssl_channel_credentials ()
276
-
277
- # Combine the ssl credentials and the authorization credentials.
278
- return grpc .composite_channel_credentials (ssl_credentials , google_auth_credentials )
274
+ # if `ssl_credentials` is set, use `grpc.composite_channel_credentials` instead of
275
+ # `grpc.compute_engine_channel_credentials` as the former supports passing
276
+ # `ssl_credentials` via `channel_credentials` which is needed for mTLS.
277
+ if ssl_credentials :
278
+ # Combine the ssl credentials and the authorization credentials.
279
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.composite_channel_credentials
280
+ return grpc .composite_channel_credentials (
281
+ ssl_credentials , google_auth_credentials
282
+ )
283
+ else :
284
+ # Use grpc.compute_engine_channel_credentials in order to support Direct Path.
285
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.compute_engine_channel_credentials
286
+ # TODO(b/323073050): Although `grpc.compute_engine_channel_credentials`
287
+ # returns channel credentials outside of GCE, we should determine if there is a way to
288
+ # reliably detect when the client is in a GCE environment so that
289
+ # `grpc.compute_engine_channel_credentials` is not called outside of GCE.
290
+ return grpc .compute_engine_channel_credentials (google_auth_credentials )
279
291
280
292
281
293
def create_channel (
@@ -288,6 +300,7 @@ def create_channel(
288
300
default_scopes = None ,
289
301
default_host = None ,
290
302
compression = None ,
303
+ attempt_direct_path : Optional [bool ] = None ,
291
304
** kwargs ,
292
305
):
293
306
"""Create a secure channel with credentials.
@@ -311,6 +324,16 @@ def create_channel(
311
324
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
312
325
compression (grpc.Compression): An optional value indicating the
313
326
compression method to be used over the lifetime of the channel.
327
+ attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted when
328
+ the request is made. Direct Path provides a proxyless connection which
329
+ increases the available throughput, reduces latency, and increases
330
+ reliability. Outside of GCE, the direct path request may fallback
331
+ to DNS if this is configured by the Service. This argument should only
332
+ be set in a GCE environment and for Services that are known to support Direct Path.
333
+ If a `ServiceUnavailable` response is received when the request is sent, it is
334
+ recommended that the client repeat the request with `attempt_direct_path` set to `False`
335
+ as the Service may not support Direct Path. Using `ssl_credentials` with `attempt_direct_path`
336
+ set to `True` will result in `ValueError` as it is not yet supported.
314
337
kwargs: Additional key-word args passed to
315
338
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
316
339
Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
@@ -320,8 +343,15 @@ def create_channel(
320
343
321
344
Raises:
322
345
google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
346
+ ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
323
347
"""
324
348
349
+ # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
350
+ # raise ValueError as this is not yet supported.
351
+ # See https://github.com/googleapis/python-api-core/issues/590
352
+ if ssl_credentials is not None and attempt_direct_path :
353
+ raise ValueError ("Using ssl_credentials with Direct Path is not supported" )
354
+
325
355
composite_credentials = _create_composite_credentials (
326
356
credentials = credentials ,
327
357
credentials_file = credentials_file ,
@@ -332,17 +362,53 @@ def create_channel(
332
362
default_host = default_host ,
333
363
)
334
364
365
+ # Note that grpcio-gcp is deprecated
335
366
if HAS_GRPC_GCP : # pragma: NO COVER
336
367
if compression is not None and compression != grpc .Compression .NoCompression :
337
368
_LOGGER .debug (
338
369
"Compression argument is being ignored for grpc_gcp.secure_channel creation."
339
370
)
371
+ if attempt_direct_path :
372
+ warnings .warn (
373
+ """The `attempt_direct_path` argument is ignored for grpc_gcp.secure_channel creation.""" ,
374
+ DeprecationWarning ,
375
+ )
340
376
return grpc_gcp .secure_channel (target , composite_credentials , ** kwargs )
377
+
378
+ if attempt_direct_path :
379
+ target = _modify_target_for_direct_path (target )
380
+
341
381
return grpc .secure_channel (
342
382
target , composite_credentials , compression = compression , ** kwargs
343
383
)
344
384
345
385
386
+ def _modify_target_for_direct_path (target : str ) -> str :
387
+ """Create a secure channel with credentials.
388
+
389
+ Args:
390
+ target (str): The target service address in the format 'hostname:port', 'dns://hostname' or other
391
+ compatible format.
392
+
393
+ Returns:
394
+ target (str): The target service address which is converted into a format compatible with Direct Path.
395
+ If the target contains `dns:///` or does not have contain `:///`, the target will be converted in
396
+ a format compatible with Direct Path, otherwise the original target will be returned.
397
+ """
398
+
399
+ dns_prefix = "dns:///"
400
+ # Remove "dns:///" if `attempt_direct_path` is set to True as
401
+ # the Direct Path prefix `google-c2p:///` will be used instead.
402
+ target = target .replace (dns_prefix , "" )
403
+
404
+ direct_path_prefix = ":///"
405
+ if direct_path_prefix not in target :
406
+ target_without_port = target .split (":" )[0 ]
407
+ # Modify the target to use Direct Path by adding the `google-c2p:///` prefix
408
+ target = f"google-c2p{ direct_path_prefix } { target_without_port } "
409
+ return target
410
+
411
+
346
412
_MethodCall = collections .namedtuple (
347
413
"_MethodCall" , ("request" , "timeout" , "metadata" , "credentials" , "compression" )
348
414
)
0 commit comments