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