Skip to content

Commit e777b53

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

File tree

4 files changed

+244
-46
lines changed

4 files changed

+244
-46
lines changed

google/api_core/grpc_helpers.py

Lines changed: 72 additions & 5 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,24 @@ 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()
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
276286

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)
279292

280293

281294
def create_channel(
@@ -288,6 +301,7 @@ def create_channel(
288301
default_scopes=None,
289302
default_host=None,
290303
compression=None,
304+
attempt_direct_path: Optional[bool] = None,
291305
**kwargs,
292306
):
293307
"""Create a secure channel with credentials.
@@ -311,6 +325,16 @@ def create_channel(
311325
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
312326
compression (grpc.Compression): An optional value indicating the
313327
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.
314338
kwargs: Additional key-word args passed to
315339
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
316340
Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
@@ -320,8 +344,15 @@ def create_channel(
320344
321345
Raises:
322346
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`.
323348
"""
324349

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+
325356
composite_credentials = _create_composite_credentials(
326357
credentials=credentials,
327358
credentials_file=credentials_file,
@@ -332,17 +363,53 @@ def create_channel(
332363
default_host=default_host,
333364
)
334365

366+
# Note that grpcio-gcp is deprecated
335367
if HAS_GRPC_GCP: # pragma: NO COVER
336368
if compression is not None and compression != grpc.Compression.NoCompression:
337369
_LOGGER.debug(
338370
"Compression argument is being ignored for grpc_gcp.secure_channel creation."
339371
)
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+
)
340377
return grpc_gcp.secure_channel(target, composite_credentials, **kwargs)
378+
379+
if attempt_direct_path:
380+
target = _modify_target_for_direct_path(target)
381+
341382
return grpc.secure_channel(
342383
target, composite_credentials, compression=compression, **kwargs
343384
)
344385

345386

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+
346413
_MethodCall = collections.namedtuple(
347414
"_MethodCall", ("request", "timeout", "metadata", "credentials", "compression")
348415
)

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+
# TODO(<insert bug to github issue>): Add link to Github Issue
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
)

0 commit comments

Comments
 (0)