Skip to content

Commit c4f537d

Browse files
committed
feat: Add attempt_direct_path argument to create_channel
1 parent ebc2635 commit c4f537d

File tree

5 files changed

+248
-50
lines changed

5 files changed

+248
-50
lines changed

google/api_core/grpc_helpers.py

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
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

1818
import collections
1919
import 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

281293
def 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
)

google/api_core/grpc_helpers_async.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import asyncio
2222
import functools
2323

24-
from typing import Generic, Iterator, AsyncGenerator, TypeVar
24+
from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar
2525

2626
import grpc
2727
from grpc import aio
@@ -223,6 +223,7 @@ def create_channel(
223223
default_scopes=None,
224224
default_host=None,
225225
compression=None,
226+
attempt_direct_path: Optional[bool] = None,
226227
**kwargs
227228
):
228229
"""Create an AsyncIO secure channel with credentials.
@@ -246,15 +247,32 @@ def create_channel(
246247
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
247248
compression (grpc.Compression): An optional value indicating the
248249
compression method to be used over the lifetime of the channel.
250+
attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted when
251+
the request is made. Direct Path provides a proxyless connection which
252+
increases the available throughput, reduces latency, and increases
253+
reliability. Outside of GCE, the direct path request may fallback
254+
to DNS if this is configured by the Service. This argument should only
255+
be set in a GCE environment and for Services that are known to support Direct Path.
256+
If a `ServiceUnavailable` response is received when the request is sent, it is
257+
recommended that the client repeat the request with `attempt_direct_path` set to `False`
258+
as the Service may not support Direct Path. Using `ssl_credentials` with `attempt_direct_path`
259+
set to `True` will result in `ValueError` as it is not yet supported.
249260
kwargs: Additional key-word args passed to :func:`aio.secure_channel`.
250261
251262
Returns:
252263
aio.Channel: The created channel.
253264
254265
Raises:
255266
google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
267+
ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
256268
"""
257269

270+
# If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
271+
# raise ValueError as this is not yet supported.
272+
# See https://github.com/googleapis/python-api-core/issues/590
273+
if ssl_credentials is not None and attempt_direct_path:
274+
raise ValueError("Using ssl_credentials with Direct Path is not supported")
275+
258276
composite_credentials = grpc_helpers._create_composite_credentials(
259277
credentials=credentials,
260278
credentials_file=credentials_file,
@@ -265,6 +283,9 @@ def create_channel(
265283
default_host=default_host,
266284
)
267285

286+
if attempt_direct_path:
287+
target = grpc_helpers._modify_target_for_direct_path(target)
288+
268289
return aio.secure_channel(
269290
target, composite_credentials, compression=compression, **kwargs
270291
)

pytest.ini

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ filterwarnings =
1212
# Remove once support for grpcio-gcp is deprecated
1313
# See https://github.com/googleapis/python-api-core/blob/42e8b6e6f426cab749b34906529e8aaf3f133d75/google/api_core/grpc_helpers.py#L39-L45
1414
ignore:.*Support for grpcio-gcp is deprecated:DeprecationWarning
15+
ignore:The `attempt_direct_path` argument is ignored for grpc_gcp.secure_channel creation:DeprecationWarning
1516
# Remove once https://github.com/googleapis/python-api-common-protos/pull/187/files is merged
1617
ignore:.*pkg_resources.declare_namespace:DeprecationWarning
1718
ignore:.*pkg_resources is deprecated as an API:DeprecationWarning
18-
# Remove once release PR https://github.com/googleapis/proto-plus-python/pull/391 is merged
19-
ignore:datetime.datetime.utcfromtimestamp\(\) is deprecated:DeprecationWarning:proto.datetime_helpers
20-
# Remove once https://github.com/grpc/grpc/issues/35086 is fixed
19+
# Remove once https://github.com/grpc/grpc/issues/35086 is fixed (and version newer than 1.60.0 is published)
2120
ignore:There is no current event loop:DeprecationWarning
21+
# Remove once the deprecated `grpc_gcp` feature is removed
22+

0 commit comments

Comments
 (0)