Skip to content

Commit 94726e7

Browse files
authored
feat: Add attempt_direct_path argument to create_channel (#583)
* feat: Add attempt_direct_path argument to create_channel * add more test cases * fix docstring * fix docstring * update docstring of attempt_direct_path arg * update docstring of target arg * Add comment for dns_prefix local variable * Set the default value of attempt_direct_path to False * simplify conditional statement * use warnings.warn instead of _LOGGER.debug * update docstring of target arg in _modify_target_for_direct_path * s/direct_path_prefix/direct_path_separator * default->google_auth_default * parametrize target in def test_create_channel_implicit * Add github issue for TODO * filter deprecation warning related to grpcio-gcp * format docstring
1 parent b72929f commit 94726e7

File tree

5 files changed

+288
-64
lines changed

5 files changed

+288
-64
lines changed

google/api_core/grpc_helpers.py

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
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
20-
import logging
2120
import warnings
2221

2322
import grpc
@@ -53,8 +52,6 @@
5352
# The list of gRPC Callable interfaces that return iterators.
5453
_STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)
5554

56-
_LOGGER = logging.getLogger(__name__)
57-
5855
# denotes the proto response type for grpc calls
5956
P = TypeVar("P")
6057

@@ -271,11 +268,24 @@ def _create_composite_credentials(
271268
# Create a set of grpc.CallCredentials using the metadata plugin.
272269
google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
273270

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

280290

281291
def create_channel(
@@ -288,6 +298,7 @@ def create_channel(
288298
default_scopes=None,
289299
default_host=None,
290300
compression=None,
301+
attempt_direct_path: Optional[bool] = False,
291302
**kwargs,
292303
):
293304
"""Create a secure channel with credentials.
@@ -311,6 +322,22 @@ def create_channel(
311322
default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
312323
compression (grpc.Compression): An optional value indicating the
313324
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+
314341
kwargs: Additional key-word args passed to
315342
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
316343
Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
@@ -320,8 +347,15 @@ def create_channel(
320347
321348
Raises:
322349
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`.
323351
"""
324352

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+
325359
composite_credentials = _create_composite_credentials(
326360
credentials=credentials,
327361
credentials_file=credentials_file,
@@ -332,17 +366,58 @@ def create_channel(
332366
default_host=default_host,
333367
)
334368

369+
# Note that grpcio-gcp is deprecated
335370
if HAS_GRPC_GCP: # pragma: NO COVER
336371
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,
339380
)
340381
return grpc_gcp.secure_channel(target, composite_credentials, **kwargs)
382+
383+
if attempt_direct_path:
384+
target = _modify_target_for_direct_path(target)
385+
341386
return grpc.secure_channel(
342387
target, composite_credentials, compression=compression, **kwargs
343388
)
344389

345390

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+
346421
_MethodCall = collections.namedtuple(
347422
"_MethodCall", ("request", "timeout", "metadata", "credentials", "compression")
348423
)

google/api_core/grpc_helpers_async.py

Lines changed: 28 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] = False,
226227
**kwargs
227228
):
228229
"""Create an AsyncIO secure channel with credentials.
@@ -246,15 +247,38 @@ 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
251+
when the request is made. Direct Path is only available within a Google
252+
Compute Engine (GCE) environment and provides a proxyless connection
253+
which increases the available throughput, reduces latency, and increases
254+
reliability. Note:
255+
256+
- This argument should only be set in a GCE environment and for Services
257+
that are known to support Direct Path.
258+
- If this argument is set outside of GCE, then this request will fail
259+
unless the back-end service happens to have configured fall-back to DNS.
260+
- If the request causes a `ServiceUnavailable` response, it is recommended
261+
that the client repeat the request with `attempt_direct_path` set to
262+
`False` as the Service may not support Direct Path.
263+
- Using `ssl_credentials` with `attempt_direct_path` set to `True` will
264+
result in `ValueError` as this combination is not yet supported.
265+
249266
kwargs: Additional key-word args passed to :func:`aio.secure_channel`.
250267
251268
Returns:
252269
aio.Channel: The created channel.
253270
254271
Raises:
255272
google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
273+
ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
256274
"""
257275

276+
# If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
277+
# raise ValueError as this is not yet supported.
278+
# See https://github.com/googleapis/python-api-core/issues/590
279+
if ssl_credentials and attempt_direct_path:
280+
raise ValueError("Using ssl_credentials with Direct Path is not supported")
281+
258282
composite_credentials = grpc_helpers._create_composite_credentials(
259283
credentials=credentials,
260284
credentials_file=credentials_file,
@@ -265,6 +289,9 @@ def create_channel(
265289
default_host=default_host,
266290
)
267291

292+
if attempt_direct_path:
293+
target = grpc_helpers._modify_target_for_direct_path(target)
294+
268295
return aio.secure_channel(
269296
target, composite_credentials, compression=compression, **kwargs
270297
)

pytest.ini

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ 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-
# Remove once https://github.com/googleapis/python-api-common-protos/pull/187/files is merged
15+
ignore: The `compression` argument is ignored for grpc_gcp.secure_channel creation:DeprecationWarning
16+
ignore:The `attempt_direct_path` argument is ignored for grpc_gcp.secure_channel creation:DeprecationWarning
17+
# Remove once the minimum supported version of googleapis-common-protos is 1.62.0
1618
ignore:.*pkg_resources.declare_namespace:DeprecationWarning
1719
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
20+
# Remove once https://github.com/grpc/grpc/issues/35086 is fixed (and version newer than 1.60.0 is published)
2121
ignore:There is no current event loop:DeprecationWarning

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy