Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Connection management support (with optional grpc_gcp dependency) #5553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ htmlcov
# JetBrains
.idea

# VS Code
.vscode

# Built documentation
docs/_build
docs/_build_doc2dash
Expand Down
37 changes: 33 additions & 4 deletions api_core/google/api_core/grpc_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
import google.auth.transport.grpc
import google.auth.transport.requests

try:
import grpc_gcp
HAS_GRPC_GCP = True
except ImportError:
HAS_GRPC_GCP = False

# The list of gRPC Callable interfaces that return iterators.
_STREAM_WRAP_CLASSES = (
Expand Down Expand Up @@ -149,7 +154,11 @@ def wrap_errors(callable_):
return _wrap_unary_errors(callable_)


def create_channel(target, credentials=None, scopes=None, **kwargs):
def create_channel(target,
credentials=None,
scopes=None,
ssl_credentials=None,

This comment was marked as spam.

This comment was marked as spam.

**kwargs):
"""Create a secure channel with credentials.

Args:
Expand All @@ -160,8 +169,10 @@ def create_channel(target, credentials=None, scopes=None, **kwargs):
scopes (Sequence[str]): A optional list of scopes needed for this
service. These are only used when credentials are not specified and
are passed to :func:`google.auth.default`.
ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
credentials. This can be used to specify different certificates.
kwargs: Additional key-word args passed to
:func:`google.auth.transport.grpc.secure_authorized_channel`.
:func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.

Returns:
grpc.Channel: The created channel.
Expand All @@ -174,8 +185,26 @@ def create_channel(target, credentials=None, scopes=None, **kwargs):

request = google.auth.transport.requests.Request()

return google.auth.transport.grpc.secure_authorized_channel(
credentials, request, target, **kwargs)
# Create the metadata plugin for inserting the authorization header.
metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
credentials, request)

# Create a set of grpc.CallCredentials using the metadata plugin.
google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)

if ssl_credentials is None:
ssl_credentials = grpc.ssl_channel_credentials()

# Combine the ssl credentials and the authorization credentials.
composite_credentials = grpc.composite_channel_credentials(
ssl_credentials, google_auth_credentials)

if HAS_GRPC_GCP:
# If grpc_gcp module is available use grpc_gcp.secure_channel,
# otherwise, use grpc.secure_channel to create grpc channel.
return grpc_gcp.secure_channel(target, composite_credentials, **kwargs)
else:
return grpc.secure_channel(target, composite_credentials, **kwargs)


_MethodCall = collections.namedtuple(
Expand Down
17 changes: 17 additions & 0 deletions api_core/nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ def unit(session, py):
default(session)


@nox.session
@nox.parametrize('py', ['2.7', '3.5', '3.6', '3.7'])
def unit_grpc_gcp(session, py):
"""Run the unit test suite with grpcio-gcp installed."""

# Run unit tests against all supported versions of Python.
session.interpreter = 'python{}'.format(py)

# Set the virtualenv dirname.
session.virtualenv_dirname = 'unit-grpc-gcp-' + py

# Install grpcio-gcp
session.install('grpcio-gcp')

default(session)


@nox.session
def lint(session):
"""Run linters.
Expand Down
133 changes: 115 additions & 18 deletions api_core/tests/unit/test_grpc_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,60 +176,157 @@ def test_wrap_errors_streaming(wrap_stream_errors):
wrap_stream_errors.assert_called_once_with(callable_)


@mock.patch('grpc.composite_channel_credentials')
@mock.patch(
'google.auth.default',
return_value=(mock.sentinel.credentials, mock.sentinel.projet))
@mock.patch('google.auth.transport.grpc.secure_authorized_channel')
def test_create_channel_implicit(secure_authorized_channel, default):
@mock.patch('grpc.secure_channel')
def test_create_channel_implicit(
grpc_secure_channel, default, composite_creds_call):
target = 'example.com:443'
composite_creds = composite_creds_call.return_value

channel = grpc_helpers.create_channel(target)

assert channel is secure_authorized_channel.return_value
assert channel is grpc_secure_channel.return_value
default.assert_called_once_with(scopes=None)
secure_authorized_channel.assert_called_once_with(
mock.sentinel.credentials, mock.ANY, target)
if (grpc_helpers.HAS_GRPC_GCP):
grpc_secure_channel.assert_called_once_with(
target, composite_creds, None)
else:
grpc_secure_channel.assert_called_once_with(
target, composite_creds)


@mock.patch('grpc.composite_channel_credentials')
@mock.patch(
'google.auth.default',
return_value=(mock.sentinel.credentials, mock.sentinel.projet))
@mock.patch('google.auth.transport.grpc.secure_authorized_channel')
@mock.patch('grpc.secure_channel')
def test_create_channel_implicit_with_ssl_creds(
grpc_secure_channel, default, composite_creds_call):
target = 'example.com:443'

ssl_creds = grpc.ssl_channel_credentials()

grpc_helpers.create_channel(target, ssl_credentials=ssl_creds)

default.assert_called_once_with(scopes=None)
composite_creds_call.assert_called_once_with(ssl_creds, mock.ANY)
composite_creds = composite_creds_call.return_value
if (grpc_helpers.HAS_GRPC_GCP):
grpc_secure_channel.assert_called_once_with(
target, composite_creds, None)
else:
grpc_secure_channel.assert_called_once_with(
target, composite_creds)


@mock.patch('grpc.composite_channel_credentials')
@mock.patch(
'google.auth.default',
return_value=(mock.sentinel.credentials, mock.sentinel.projet))
@mock.patch('grpc.secure_channel')
def test_create_channel_implicit_with_scopes(
secure_authorized_channel, default):
grpc_secure_channel, default, composite_creds_call):
target = 'example.com:443'
composite_creds = composite_creds_call.return_value

channel = grpc_helpers.create_channel(target, scopes=['one', 'two'])

assert channel is secure_authorized_channel.return_value
assert channel is grpc_secure_channel.return_value
default.assert_called_once_with(scopes=['one', 'two'])


@mock.patch('google.auth.transport.grpc.secure_authorized_channel')
def test_create_channel_explicit(secure_authorized_channel):
if (grpc_helpers.HAS_GRPC_GCP):
grpc_secure_channel.assert_called_once_with(
target, composite_creds, None)
else:
grpc_secure_channel.assert_called_once_with(
target, composite_creds)


@mock.patch('grpc.composite_channel_credentials')
@mock.patch('google.auth.credentials.with_scopes_if_required')
@mock.patch('grpc.secure_channel')
def test_create_channel_explicit(
grpc_secure_channel, auth_creds, composite_creds_call):
target = 'example.com:443'
composite_creds = composite_creds_call.return_value

channel = grpc_helpers.create_channel(
target, credentials=mock.sentinel.credentials)

assert channel is secure_authorized_channel.return_value
secure_authorized_channel.assert_called_once_with(
mock.sentinel.credentials, mock.ANY, target)
auth_creds.assert_called_once_with(mock.sentinel.credentials, None)
assert channel is grpc_secure_channel.return_value
if (grpc_helpers.HAS_GRPC_GCP):
grpc_secure_channel.assert_called_once_with(
target, composite_creds, None)
else:
grpc_secure_channel.assert_called_once_with(
target, composite_creds)


@mock.patch('google.auth.transport.grpc.secure_authorized_channel')
def test_create_channel_explicit_scoped(unused_secure_authorized_channel):
@mock.patch('grpc.composite_channel_credentials')
@mock.patch('grpc.secure_channel')
def test_create_channel_explicit_scoped(
grpc_secure_channel, composite_creds_call):
target = 'example.com:443'
scopes = ['1', '2']
composite_creds = composite_creds_call.return_value

credentials = mock.create_autospec(
google.auth.credentials.Scoped, instance=True)
credentials.requires_scopes = True

channel = grpc_helpers.create_channel(
target,
credentials=credentials,
scopes=scopes)

credentials.with_scopes.assert_called_once_with(scopes)
assert channel is grpc_secure_channel.return_value
if (grpc_helpers.HAS_GRPC_GCP):
grpc_secure_channel.assert_called_once_with(
target, composite_creds, None)
else:
grpc_secure_channel.assert_called_once_with(
target, composite_creds)


@pytest.mark.skipif(not grpc_helpers.HAS_GRPC_GCP,
reason='grpc_gcp module not available')
@mock.patch('grpc_gcp.secure_channel')
def test_create_channel_with_grpc_gcp(grpc_gcp_secure_channel):
target = 'example.com:443'
scopes = ['test_scope']

credentials = mock.create_autospec(
google.auth.credentials.Scoped, instance=True)
credentials.requires_scopes = True

grpc_helpers.create_channel(
mock.sentinel.target,
target,
credentials=credentials,
scopes=scopes)
grpc_gcp_secure_channel.assert_called()
credentials.with_scopes.assert_called_once_with(scopes)


@pytest.mark.skipif(grpc_helpers.HAS_GRPC_GCP,
reason='grpc_gcp module not available')
@mock.patch('grpc.secure_channel')
def test_create_channel_without_grpc_gcp(grpc_secure_channel):
target = 'example.com:443'
scopes = ['test_scope']

credentials = mock.create_autospec(
google.auth.credentials.Scoped, instance=True)
credentials.requires_scopes = True

grpc_helpers.create_channel(
target,
credentials=credentials,
scopes=scopes)
grpc_secure_channel.assert_called()
credentials.with_scopes.assert_called_once_with(scopes)


Expand Down
2 changes: 1 addition & 1 deletion spanner/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include README.rst LICENSE
recursive-include google *.json *.proto
recursive-include google *.json *.proto *.config
recursive-include tests *
global-exclude *.pyc __pycache__
88 changes: 88 additions & 0 deletions spanner/google/cloud/spanner_v1/gapic/spanner.grpc.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
channel_pool: {
max_size: 10
max_concurrent_streams_low_watermark: 100
}
method: {
name: "/google.spanner.v1.Spanner/CreateSession"
affinity: {
command: BIND
affinity_key: "name"
}
}
method: {
name: "/google.spanner.v1.Spanner/GetSession"
affinity: {
command: BOUND
affinity_key: "name"
}
}
method: {
name: "/google.spanner.v1.Spanner/DeleteSession"
affinity: {
command: UNBIND
affinity_key: "name"
}
}
method: {
name: "/google.spanner.v1.Spanner/ExecuteSql"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/ExecuteStreamingSql"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/Read"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/StreamingRead"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/BeginTransaction"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/Commit"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/Rollback"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/PartitionQuery"
affinity: {
command: BOUND
affinity_key: "session"
}
}
method: {
name: "/google.spanner.v1.Spanner/PartitionRead"
affinity: {
command: BOUND
affinity_key: "session"
}
}
Loading