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
20
- import logging
21
20
import warnings
22
21
23
22
import grpc
53
52
# The list of gRPC Callable interfaces that return iterators.
54
53
_STREAM_WRAP_CLASSES = (grpc .UnaryStreamMultiCallable , grpc .StreamStreamMultiCallable )
55
54
56
- _LOGGER = logging .getLogger (__name__ )
57
-
58
55
# denotes the proto response type for grpc calls
59
56
P = TypeVar ("P" )
60
57
@@ -271,11 +268,24 @@ def _create_composite_credentials(
271
268
# Create a set of grpc.CallCredentials using the metadata plugin.
272
269
google_auth_credentials = grpc .metadata_call_credentials (metadata_plugin )
273
270
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 )
271
+ # if `ssl_credentials` is set, use `grpc.composite_channel_credentials` instead of
272
+ # `grpc.compute_engine_channel_credentials` as the former supports passing
273
+ # `ssl_credentials` via `channel_credentials` which is needed for mTLS.
274
+ if ssl_credentials :
275
+ # Combine the ssl credentials and the authorization credentials.
276
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.composite_channel_credentials
277
+ return grpc .composite_channel_credentials (
278
+ ssl_credentials , google_auth_credentials
279
+ )
280
+ else :
281
+ # Use grpc.compute_engine_channel_credentials in order to support Direct Path.
282
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.compute_engine_channel_credentials
283
+ # TODO(https://github.com/googleapis/python-api-core/issues/598):
284
+ # Although `grpc.compute_engine_channel_credentials` returns channel credentials
285
+ # outside of a Google Compute Engine environment (GCE), we should determine if
286
+ # there is a way to reliably detect a GCE environment so that
287
+ # `grpc.compute_engine_channel_credentials` is not called outside of GCE.
288
+ return grpc .compute_engine_channel_credentials (google_auth_credentials )
279
289
280
290
281
291
def create_channel (
@@ -288,6 +298,7 @@ def create_channel(
288
298
default_scopes = None ,
289
299
default_host = None ,
290
300
compression = None ,
301
+ attempt_direct_path : Optional [bool ] = False ,
291
302
** kwargs ,
292
303
):
293
304
"""Create a secure channel with credentials.
@@ -311,6 +322,22 @@ def create_channel(
311
322
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
312
323
compression (grpc.Compression): An optional value indicating the
313
324
compression method to be used over the lifetime of the channel.
325
+ attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted
326
+ when the request is made. Direct Path is only available within a Google
327
+ Compute Engine (GCE) environment and provides a proxyless connection
328
+ which increases the available throughput, reduces latency, and increases
329
+ reliability. Note:
330
+
331
+ - This argument should only be set in a GCE environment and for Services
332
+ that are known to support Direct Path.
333
+ - If this argument is set outside of GCE, then this request will fail
334
+ unless the back-end service happens to have configured fall-back to DNS.
335
+ - If the request causes a `ServiceUnavailable` response, it is recommended
336
+ that the client repeat the request with `attempt_direct_path` set to
337
+ `False` as the Service may not support Direct Path.
338
+ - Using `ssl_credentials` with `attempt_direct_path` set to `True` will
339
+ result in `ValueError` as this combination is not yet supported.
340
+
314
341
kwargs: Additional key-word args passed to
315
342
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
316
343
Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
@@ -320,8 +347,15 @@ def create_channel(
320
347
321
348
Raises:
322
349
google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
350
+ ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
323
351
"""
324
352
353
+ # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
354
+ # raise ValueError as this is not yet supported.
355
+ # See https://github.com/googleapis/python-api-core/issues/590
356
+ if ssl_credentials and attempt_direct_path :
357
+ raise ValueError ("Using ssl_credentials with Direct Path is not supported" )
358
+
325
359
composite_credentials = _create_composite_credentials (
326
360
credentials = credentials ,
327
361
credentials_file = credentials_file ,
@@ -332,17 +366,58 @@ def create_channel(
332
366
default_host = default_host ,
333
367
)
334
368
369
+ # Note that grpcio-gcp is deprecated
335
370
if HAS_GRPC_GCP : # pragma: NO COVER
336
371
if compression is not None and compression != grpc .Compression .NoCompression :
337
- _LOGGER .debug (
338
- "Compression argument is being ignored for grpc_gcp.secure_channel creation."
372
+ warnings .warn (
373
+ "The `compression` argument is ignored for grpc_gcp.secure_channel creation." ,
374
+ DeprecationWarning ,
375
+ )
376
+ if attempt_direct_path :
377
+ warnings .warn (
378
+ """The `attempt_direct_path` argument is ignored for grpc_gcp.secure_channel creation.""" ,
379
+ DeprecationWarning ,
339
380
)
340
381
return grpc_gcp .secure_channel (target , composite_credentials , ** kwargs )
382
+
383
+ if attempt_direct_path :
384
+ target = _modify_target_for_direct_path (target )
385
+
341
386
return grpc .secure_channel (
342
387
target , composite_credentials , compression = compression , ** kwargs
343
388
)
344
389
345
390
391
+ def _modify_target_for_direct_path (target : str ) -> str :
392
+ """
393
+ Given a target, return a modified version which is compatible with Direct Path.
394
+
395
+ Args:
396
+ target (str): The target service address in the format 'hostname[:port]' or
397
+ 'dns://hostname[:port]'.
398
+
399
+ Returns:
400
+ target (str): The target service address which is converted into a format compatible with Direct Path.
401
+ If the target contains `dns:///` or does not contain `:///`, the target will be converted in
402
+ a format compatible with Direct Path; otherwise the original target will be returned as the
403
+ original target may already denote Direct Path.
404
+ """
405
+
406
+ # A DNS prefix may be included with the target to indicate the endpoint is living in the Internet,
407
+ # outside of Google Cloud Platform.
408
+ dns_prefix = "dns:///"
409
+ # Remove "dns:///" if `attempt_direct_path` is set to True as
410
+ # the Direct Path prefix `google-c2p:///` will be used instead.
411
+ target = target .replace (dns_prefix , "" )
412
+
413
+ direct_path_separator = ":///"
414
+ if direct_path_separator not in target :
415
+ target_without_port = target .split (":" )[0 ]
416
+ # Modify the target to use Direct Path by adding the `google-c2p:///` prefix
417
+ target = f"google-c2p{ direct_path_separator } { target_without_port } "
418
+ return target
419
+
420
+
346
421
_MethodCall = collections .namedtuple (
347
422
"_MethodCall" , ("request" , "timeout" , "metadata" , "credentials" , "compression" )
348
423
)
0 commit comments