Skip to content

Commit 2c9cf71

Browse files
WeiranFangtheacodes
authored andcommitted
Add support for gRPC connection management (available when using optional grpc_gcp dependency) (#5553)
1 parent 807e5ed commit 2c9cf71

5 files changed

Lines changed: 179 additions & 10 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
include README.rst LICENSE
2-
recursive-include google *.json *.proto
2+
recursive-include google *.json *.proto *.config
33
recursive-include tests *
44
global-exclude *.pyc __pycache__
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
channel_pool: {
2+
max_size: 10
3+
max_concurrent_streams_low_watermark: 100
4+
}
5+
method: {
6+
name: "/google.spanner.v1.Spanner/CreateSession"
7+
affinity: {
8+
command: BIND
9+
affinity_key: "name"
10+
}
11+
}
12+
method: {
13+
name: "/google.spanner.v1.Spanner/GetSession"
14+
affinity: {
15+
command: BOUND
16+
affinity_key: "name"
17+
}
18+
}
19+
method: {
20+
name: "/google.spanner.v1.Spanner/DeleteSession"
21+
affinity: {
22+
command: UNBIND
23+
affinity_key: "name"
24+
}
25+
}
26+
method: {
27+
name: "/google.spanner.v1.Spanner/ExecuteSql"
28+
affinity: {
29+
command: BOUND
30+
affinity_key: "session"
31+
}
32+
}
33+
method: {
34+
name: "/google.spanner.v1.Spanner/ExecuteStreamingSql"
35+
affinity: {
36+
command: BOUND
37+
affinity_key: "session"
38+
}
39+
}
40+
method: {
41+
name: "/google.spanner.v1.Spanner/Read"
42+
affinity: {
43+
command: BOUND
44+
affinity_key: "session"
45+
}
46+
}
47+
method: {
48+
name: "/google.spanner.v1.Spanner/StreamingRead"
49+
affinity: {
50+
command: BOUND
51+
affinity_key: "session"
52+
}
53+
}
54+
method: {
55+
name: "/google.spanner.v1.Spanner/BeginTransaction"
56+
affinity: {
57+
command: BOUND
58+
affinity_key: "session"
59+
}
60+
}
61+
method: {
62+
name: "/google.spanner.v1.Spanner/Commit"
63+
affinity: {
64+
command: BOUND
65+
affinity_key: "session"
66+
}
67+
}
68+
method: {
69+
name: "/google.spanner.v1.Spanner/Rollback"
70+
affinity: {
71+
command: BOUND
72+
affinity_key: "session"
73+
}
74+
}
75+
method: {
76+
name: "/google.spanner.v1.Spanner/PartitionQuery"
77+
affinity: {
78+
command: BOUND
79+
affinity_key: "session"
80+
}
81+
}
82+
method: {
83+
name: "/google.spanner.v1.Spanner/PartitionRead"
84+
affinity: {
85+
command: BOUND
86+
affinity_key: "session"
87+
}
88+
}

packages/google-cloud-spanner/google/cloud/spanner_v1/gapic/spanner_client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@
3232
from google.cloud.spanner_v1.proto import transaction_pb2
3333
from google.protobuf import struct_pb2
3434

35+
try:
36+
import grpc_gcp
37+
HAS_GRPC_GCP = True
38+
except ImportError:
39+
HAS_GRPC_GCP = False
40+
3541
_GAPIC_LIBRARY_VERSION = pkg_resources.get_distribution(
3642
'google-cloud-spanner', ).version
43+
_SPANNER_GRPC_CONFIG = 'spanner.grpc.config'
3744

3845

3946
class SpannerClient(object):
@@ -113,10 +120,20 @@ def __init__(self,
113120

114121
# Create the channel.
115122
if channel is None:
123+
options = None
124+
125+
if HAS_GRPC_GCP:
126+
# Initialize grpc gcp config for spanner api.
127+
grpc_gcp_config = grpc_gcp.api_config_from_text_pb(
128+
pkg_resources.resource_string(__name__,
129+
_SPANNER_GRPC_CONFIG))
130+
options = [(grpc_gcp.API_CONFIG_CHANNEL_ARG, grpc_gcp_config)]
131+
116132
channel = google.api_core.grpc_helpers.create_channel(
117133
self.SERVICE_ADDRESS,
118134
credentials=credentials,
119135
scopes=self._DEFAULT_SCOPES,
136+
options=options,
120137
)
121138

122139
# Create the gRPC stubs.

packages/google-cloud-spanner/nox.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,23 @@ def unit(session, py):
6969

7070

7171
@nox.session
72-
@nox.parametrize('py', ['2.7', '3.6'])
73-
def system(session, py):
74-
"""Run the system test suite."""
75-
76-
# Sanity check: Only run system tests if the environment variable is set.
77-
if not os.environ.get('GOOGLE_APPLICATION_CREDENTIALS', ''):
78-
session.skip('Credentials must be set via environment variable.')
72+
@nox.parametrize('py', ['2.7', '3.4', '3.5', '3.6', '3.7'])
73+
def unit_grpc_gcp(session, py):
74+
"""Run the unit test suite with grpcio-gcp installed."""
7975

80-
# Run the system tests against latest Python 2 and Python 3 only.
76+
# Run unit tests against all supported versions of Python.
8177
session.interpreter = 'python{}'.format(py)
8278

8379
# Set the virtualenv dirname.
84-
session.virtualenv_dirname = 'sys-' + py
80+
session.virtualenv_dirname = 'unit-grpc-gcp-' + py
81+
82+
# Install grpcio-gcp
83+
session.install('grpcio-gcp')
8584

85+
default(session)
86+
87+
88+
def system_common(session):
8689
# Use pre-release gRPC for system tests.
8790
session.install('--pre', 'grpcio')
8891

@@ -96,6 +99,45 @@ def system(session, py):
9699
session.run('py.test', '--quiet', 'tests/system', *session.posargs)
97100

98101

102+
@nox.session
103+
@nox.parametrize('py', ['2.7', '3.6'])
104+
def system(session, py):
105+
"""Run the system test suite."""
106+
107+
# Sanity check: Only run system tests if the environment variable is set.
108+
if not os.environ.get('GOOGLE_APPLICATION_CREDENTIALS', ''):
109+
session.skip('Credentials must be set via environment variable.')
110+
111+
# Run the system tests against latest Python 2 and Python 3 only.
112+
session.interpreter = 'python{}'.format(py)
113+
114+
# Set the virtualenv dirname.
115+
session.virtualenv_dirname = 'sys-' + py
116+
117+
system_common(session)
118+
119+
120+
@nox.session
121+
@nox.parametrize('py', ['2.7', '3.6'])
122+
def system_grpc_gcp(session, py):
123+
"""Run the system test suite with grpcio-gcp installed."""
124+
125+
# Sanity check: Only run system tests if the environment variable is set.
126+
if not os.environ.get('GOOGLE_APPLICATION_CREDENTIALS', ''):
127+
session.skip('Credentials must be set via environment variable.')
128+
129+
# Run the system tests against latest Python 2 and Python 3 only.
130+
session.interpreter = 'python{}'.format(py)
131+
132+
# Set the virtualenv dirname.
133+
session.virtualenv_dirname = 'sys-grpc-gcp-' + py
134+
135+
# Install grpcio-gcp
136+
session.install('grpcio-gcp')
137+
138+
system_common(session)
139+
140+
99141
@nox.session
100142
def lint(session):
101143
"""Run linters.

packages/google-cloud-spanner/tests/unit/gapic/v1/test_spanner_client_v1.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414
"""Unit tests."""
1515

16+
import mock
1617
import pytest
1718

1819
# Manual edit to auto-generated import because we do not expose the
@@ -554,3 +555,24 @@ def test_partition_read_exception(self):
554555

555556
with pytest.raises(CustomException):
556557
client.partition_read(session, table, key_set)
558+
559+
@pytest.mark.skipif(not spanner_v1.HAS_GRPC_GCP,
560+
reason='grpc_gcp module not available')
561+
@mock.patch(
562+
'google.auth.default',
563+
return_value=(mock.sentinel.credentials, mock.sentinel.projet))
564+
@mock.patch('google.protobuf.text_format.Merge')
565+
@mock.patch('grpc_gcp.proto.grpc_gcp_pb2.ApiConfig',
566+
return_value=mock.sentinel.api_config)
567+
@mock.patch('grpc_gcp.secure_channel')
568+
def test_client_with_grpc_gcp_channel(self,
569+
grpc_gcp_secure_channel,
570+
api_config,
571+
merge,
572+
auth_default):
573+
spanner_target = 'spanner.googleapis.com:443'
574+
client = spanner_v1.SpannerClient()
575+
merge.assert_called_once_with(mock.ANY, mock.sentinel.api_config)
576+
options = [('grpc_gcp.api_config', mock.sentinel.api_config)]
577+
grpc_gcp_secure_channel.assert_called_once_with(
578+
spanner_target, mock.ANY, options=options)

0 commit comments

Comments
 (0)