1313# limitations under the License.
1414
1515"""Helpers for :mod:`grpc`."""
16- from typing import Generic , TypeVar , Iterator
16+ from typing import Generic , Iterator , Optional , TypeVar
1717
1818import collections
1919import functools
@@ -271,11 +271,23 @@ def _create_composite_credentials(
271271 # Create a set of grpc.CallCredentials using the metadata plugin.
272272 google_auth_credentials = grpc .metadata_call_credentials (metadata_plugin )
273273
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 )
279291
280292
281293def create_channel (
@@ -288,6 +300,7 @@ def create_channel(
288300 default_scopes = None ,
289301 default_host = None ,
290302 compression = None ,
303+ attempt_direct_path : Optional [bool ] = None ,
291304 ** kwargs ,
292305):
293306 """Create a secure channel with credentials.
@@ -311,6 +324,16 @@ def create_channel(
311324 default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
312325 compression (grpc.Compression): An optional value indicating the
313326 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.
314337 kwargs: Additional key-word args passed to
315338 :func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
316339 Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
@@ -320,8 +343,15 @@ def create_channel(
320343
321344 Raises:
322345 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`.
323347 """
324348
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+
325355 composite_credentials = _create_composite_credentials (
326356 credentials = credentials ,
327357 credentials_file = credentials_file ,
@@ -332,17 +362,53 @@ def create_channel(
332362 default_host = default_host ,
333363 )
334364
365+ # Note that grpcio-gcp is deprecated
335366 if HAS_GRPC_GCP : # pragma: NO COVER
336367 if compression is not None and compression != grpc .Compression .NoCompression :
337368 _LOGGER .debug (
338369 "Compression argument is being ignored for grpc_gcp.secure_channel creation."
339370 )
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+ )
340376 return grpc_gcp .secure_channel (target , composite_credentials , ** kwargs )
377+
378+ if attempt_direct_path :
379+ target = _modify_target_for_direct_path (target )
380+
341381 return grpc .secure_channel (
342382 target , composite_credentials , compression = compression , ** kwargs
343383 )
344384
345385
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+
346412_MethodCall = collections .namedtuple (
347413 "_MethodCall" , ("request" , "timeout" , "metadata" , "credentials" , "compression" )
348414)
0 commit comments