Skip to content

Commit 4673a43

Browse files
authored
Merge pull request #2244 from dhermes/fix-ds-emulator
Upgrading datastore emulator to work with gRPC
2 parents d093302 + 077d190 commit 4673a43

File tree

14 files changed

+218
-137
lines changed

14 files changed

+218
-137
lines changed

CONTRIBUTING.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ Running System Tests
208208
``datastore`` emulator::
209209

210210
$ tox -e datastore-emulator
211+
$ GOOGLE_CLOUD_DISABLE_GRPC=true tox -e datastore-emulator
211212

212213
This also requires that the ``gcloud`` command line tool is
213214
installed. If you'd like to run them directly (outside of a
214215
``tox`` environment), first start the emulator and
215216
take note of the process ID::
216217

217-
$ gcloud beta emulators datastore start 2>&1 > log.txt &
218+
$ gcloud beta emulators datastore start --no-legacy 2>&1 > log.txt &
218219
[1] 33333
219220

220221
then determine the environment variables needed to interact with

docs/logging-usage.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ Authentication and Configuration
1616
- The library now enables the ``gRPC`` transport for the logging API by
1717
default, assuming that the required dependencies are installed and
1818
importable. To *disable* this transport, set the
19-
:envvar:`GOOGLE_CLOUD_DISABLE_GAX` environment variable to a non-empty string,
20-
e.g.: ``$ export GOOGLE_CLOUD_DISABLE_GAX=1``.
19+
:envvar:`GOOGLE_CLOUD_DISABLE_GRPC` environment variable to a
20+
non-empty string, e.g.: ``$ export GOOGLE_CLOUD_DISABLE_GRPC=true``.
2121

2222
- After configuring your environment, create a
2323
:class:`Client <google.cloud.logging.client.Client>`

docs/pubsub-usage.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Authentication / Configuration
1515
- The library now enables the ``gRPC`` transport for the pubsub API by
1616
default, assuming that the required dependencies are installed and
1717
importable. To *disable* this transport, set the
18-
:envvar:`GOOGLE_CLOUD_DISABLE_GAX` environment variable to a non-empty string,
19-
e.g.: ``$ export GOOGLE_CLOUD_DISABLE_GAX=1``.
18+
:envvar:`GOOGLE_CLOUD_DISABLE_GRPC` environment variable to a
19+
non-empty string, e.g.: ``$ export GOOGLE_CLOUD_DISABLE_GRPC=true``.
2020

2121
- :class:`Client <google.cloud.pubsub.client.Client>` objects hold both a ``project``
2222
and an authenticated connection to the PubSub service.

google/cloud/_helpers.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
grpc = None
4040
_Rendezvous = Exception
4141
import six
42-
from six.moves.http_client import HTTPConnection
42+
from six.moves import http_client
4343
from six.moves import configparser
4444

4545
# pylint: disable=ungrouped-imports
@@ -269,7 +269,7 @@ def _compute_engine_id():
269269
host = '169.254.169.254'
270270
uri_path = '/computeMetadata/v1/project/project-id'
271271
headers = {'Metadata-Flavor': 'Google'}
272-
connection = HTTPConnection(host, timeout=0.1)
272+
connection = http_client.HTTPConnection(host, timeout=0.1)
273273

274274
try:
275275
connection.request('GET', uri_path, headers=headers)
@@ -612,8 +612,8 @@ def __call__(self, unused_context, callback):
612612
callback(headers, None)
613613

614614

615-
def make_stub(credentials, user_agent, stub_class, host, port):
616-
"""Makes a stub for an RPC service.
615+
def make_secure_stub(credentials, user_agent, stub_class, host):
616+
"""Makes a secure stub for an RPC service.
617617
618618
Uses / depends on gRPC.
619619
@@ -630,25 +630,49 @@ def make_stub(credentials, user_agent, stub_class, host, port):
630630
:type host: str
631631
:param host: The host for the service.
632632
633-
:type port: int
634-
:param port: The port for the service.
635-
636633
:rtype: object, instance of ``stub_class``
637634
:returns: The stub object used to make gRPC requests to a given API.
638635
"""
639-
# Leaving the first argument to ssl_channel_credentials() as None
640-
# loads root certificates from `grpc/_adapter/credentials/roots.pem`.
636+
# ssl_channel_credentials() loads root certificates from
637+
# `grpc/_adapter/credentials/roots.pem`.
641638
transport_creds = grpc.ssl_channel_credentials()
642639
custom_metadata_plugin = MetadataPlugin(credentials, user_agent)
643640
auth_creds = grpc.metadata_call_credentials(
644641
custom_metadata_plugin, name='google_creds')
645642
channel_creds = grpc.composite_channel_credentials(
646643
transport_creds, auth_creds)
647-
target = '%s:%d' % (host, port)
644+
target = '%s:%d' % (host, http_client.HTTPS_PORT)
648645
channel = grpc.secure_channel(target, channel_creds)
649646
return stub_class(channel)
650647

651648

649+
def make_insecure_stub(stub_class, host, port=None):
650+
"""Makes an insecure stub for an RPC service.
651+
652+
Uses / depends on gRPC.
653+
654+
:type stub_class: type
655+
:param stub_class: A gRPC stub type for a given service.
656+
657+
:type host: str
658+
:param host: The host for the service. May also include the port
659+
if ``port`` is unspecified.
660+
661+
:type port: int
662+
:param port: (Optional) The port for the service.
663+
664+
:rtype: object, instance of ``stub_class``
665+
:returns: The stub object used to make gRPC requests to a given API.
666+
"""
667+
if port is None:
668+
target = host
669+
else:
670+
# NOTE: This assumes port != http_client.HTTPS_PORT:
671+
target = '%s:%d' % (host, port)
672+
channel = grpc.insecure_channel(target)
673+
return stub_class(channel)
674+
675+
652676
def exc_to_code(exc):
653677
"""Retrieves the status code from a gRPC exception.
654678

google/cloud/bigtable/client.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from pkg_resources import get_distribution
3131

32-
from google.cloud._helpers import make_stub
32+
from google.cloud._helpers import make_secure_stub
3333
from google.cloud.bigtable._generated import bigtable_instance_admin_pb2
3434
from google.cloud.bigtable._generated import bigtable_pb2
3535
from google.cloud.bigtable._generated import bigtable_table_admin_pb2
@@ -44,21 +44,14 @@
4444

4545
TABLE_ADMIN_HOST = 'bigtableadmin.googleapis.com'
4646
"""Table Admin API request host."""
47-
TABLE_ADMIN_PORT = 443
48-
"""Table Admin API request port."""
4947

5048
INSTANCE_ADMIN_HOST = 'bigtableadmin.googleapis.com'
5149
"""Cluster Admin API request host."""
52-
INSTANCE_ADMIN_PORT = 443
53-
"""Cluster Admin API request port."""
5450

5551
DATA_API_HOST = 'bigtable.googleapis.com'
5652
"""Data API request host."""
57-
DATA_API_PORT = 443
58-
"""Data API request port."""
5953

6054
OPERATIONS_API_HOST = INSTANCE_ADMIN_HOST
61-
OPERATIONS_API_PORT = INSTANCE_ADMIN_PORT
6255

6356
ADMIN_SCOPE = 'https://www.googleapis.com/auth/bigtable.admin'
6457
"""Scope for interacting with the Cluster Admin and Table Admin APIs."""
@@ -81,9 +74,8 @@ def _make_data_stub(client):
8174
:rtype: :class:`._generated.bigtable_pb2.BigtableStub`
8275
:returns: A gRPC stub object.
8376
"""
84-
return make_stub(client.credentials, client.user_agent,
85-
bigtable_pb2.BigtableStub,
86-
DATA_API_HOST, DATA_API_PORT)
77+
return make_secure_stub(client.credentials, client.user_agent,
78+
bigtable_pb2.BigtableStub, DATA_API_HOST)
8779

8880

8981
def _make_instance_stub(client):
@@ -95,9 +87,10 @@ def _make_instance_stub(client):
9587
:rtype: :class:`.bigtable_instance_admin_pb2.BigtableInstanceAdminStub`
9688
:returns: A gRPC stub object.
9789
"""
98-
return make_stub(client.credentials, client.user_agent,
99-
bigtable_instance_admin_pb2.BigtableInstanceAdminStub,
100-
INSTANCE_ADMIN_HOST, INSTANCE_ADMIN_PORT)
90+
return make_secure_stub(
91+
client.credentials, client.user_agent,
92+
bigtable_instance_admin_pb2.BigtableInstanceAdminStub,
93+
INSTANCE_ADMIN_HOST)
10194

10295

10396
def _make_operations_stub(client):
@@ -112,9 +105,9 @@ def _make_operations_stub(client):
112105
:rtype: :class:`._generated.operations_grpc_pb2.OperationsStub`
113106
:returns: A gRPC stub object.
114107
"""
115-
return make_stub(client.credentials, client.user_agent,
116-
operations_grpc_pb2.OperationsStub,
117-
OPERATIONS_API_HOST, OPERATIONS_API_PORT)
108+
return make_secure_stub(client.credentials, client.user_agent,
109+
operations_grpc_pb2.OperationsStub,
110+
OPERATIONS_API_HOST)
118111

119112

120113
def _make_table_stub(client):
@@ -126,9 +119,9 @@ def _make_table_stub(client):
126119
:rtype: :class:`.bigtable_instance_admin_pb2.BigtableTableAdminStub`
127120
:returns: A gRPC stub object.
128121
"""
129-
return make_stub(client.credentials, client.user_agent,
130-
bigtable_table_admin_pb2.BigtableTableAdminStub,
131-
TABLE_ADMIN_HOST, TABLE_ADMIN_PORT)
122+
return make_secure_stub(client.credentials, client.user_agent,
123+
bigtable_table_admin_pb2.BigtableTableAdminStub,
124+
TABLE_ADMIN_HOST)
132125

133126

134127
class Client(_ClientFactoryMixin, _ClientProjectMixin):

google/cloud/datastore/connection.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
from google.rpc import status_pb2
2020

21-
from google.cloud._helpers import make_stub
21+
from google.cloud._helpers import make_insecure_stub
22+
from google.cloud._helpers import make_secure_stub
2223
from google.cloud import connection as connection_module
24+
from google.cloud.environment_vars import DISABLE_GRPC
2325
from google.cloud.environment_vars import GCD_HOST
2426
from google.cloud.exceptions import Conflict
2527
from google.cloud.exceptions import make_exception
@@ -41,8 +43,9 @@
4143

4244
DATASTORE_API_HOST = 'datastore.googleapis.com'
4345
"""Datastore API request host."""
44-
DATASTORE_API_PORT = 443
45-
"""Datastore API request port."""
46+
47+
_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False)
48+
_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC
4649

4750

4851
class _DatastoreAPIOverHttp(object):
@@ -230,12 +233,20 @@ class _DatastoreAPIOverGRPC(object):
230233
:type connection: :class:`google.cloud.datastore.connection.Connection`
231234
:param connection: A connection object that contains helpful
232235
information for making requests.
236+
237+
:type secure: bool
238+
:param secure: Flag indicating if a secure stub connection is needed.
233239
"""
234240

235-
def __init__(self, connection):
236-
self._stub = make_stub(connection.credentials, connection.USER_AGENT,
237-
datastore_grpc_pb2.DatastoreStub,
238-
DATASTORE_API_HOST, DATASTORE_API_PORT)
241+
def __init__(self, connection, secure):
242+
if secure:
243+
self._stub = make_secure_stub(connection.credentials,
244+
connection.USER_AGENT,
245+
datastore_grpc_pb2.DatastoreStub,
246+
connection.host)
247+
else:
248+
self._stub = make_insecure_stub(datastore_grpc_pb2.DatastoreStub,
249+
connection.host)
239250

240251
def lookup(self, project, request_pb):
241252
"""Perform a ``lookup`` request.
@@ -352,10 +363,6 @@ class Connection(connection_module.Connection):
352363
353364
:type http: :class:`httplib2.Http` or class that defines ``request()``.
354365
:param http: An optional HTTP object to make requests.
355-
356-
:type api_base_url: string
357-
:param api_base_url: The base of the API call URL. Defaults to
358-
:attr:`API_BASE_URL`.
359366
"""
360367

361368
API_BASE_URL = 'https://' + DATASTORE_API_HOST
@@ -371,18 +378,18 @@ class Connection(connection_module.Connection):
371378
SCOPE = ('https://www.googleapis.com/auth/datastore',)
372379
"""The scopes required for authenticating as a Cloud Datastore consumer."""
373380

374-
def __init__(self, credentials=None, http=None, api_base_url=None):
381+
def __init__(self, credentials=None, http=None):
375382
super(Connection, self).__init__(credentials=credentials, http=http)
376-
if api_base_url is None:
377-
try:
378-
# gcd.sh has /datastore/ in the path still since it supports
379-
# v1beta2 and v1beta3 simultaneously.
380-
api_base_url = '%s/datastore' % (os.environ[GCD_HOST],)
381-
except KeyError:
382-
api_base_url = self.__class__.API_BASE_URL
383-
self.api_base_url = api_base_url
384-
if _HAVE_GRPC:
385-
self._datastore_api = _DatastoreAPIOverGRPC(self)
383+
try:
384+
self.host = os.environ[GCD_HOST]
385+
self.api_base_url = 'http://' + self.host
386+
secure = False
387+
except KeyError:
388+
self.host = DATASTORE_API_HOST
389+
self.api_base_url = self.__class__.API_BASE_URL
390+
secure = True
391+
if _USE_GRPC:
392+
self._datastore_api = _DatastoreAPIOverGRPC(self, secure=secure)
386393
else:
387394
self._datastore_api = _DatastoreAPIOverHttp(self)
388395

google/cloud/environment_vars.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,18 @@
2727
GCD_DATASET = 'DATASTORE_DATASET'
2828
"""Environment variable defining default dataset ID under GCD."""
2929

30-
GCD_HOST = 'DATASTORE_HOST'
30+
GCD_HOST = 'DATASTORE_EMULATOR_HOST'
3131
"""Environment variable defining host for GCD dataset server."""
3232

3333
PUBSUB_EMULATOR = 'PUBSUB_EMULATOR_HOST'
3434
"""Environment variable defining host for Pub/Sub emulator."""
3535

3636
CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS'
3737
"""Environment variable defining location of Google credentials."""
38+
39+
DISABLE_GRPC = 'GOOGLE_CLOUD_DISABLE_GRPC'
40+
"""Environment variable acting as flag to disable gRPC.
41+
42+
To be used for APIs where both an HTTP and gRPC implementation
43+
exist.
44+
"""

google/cloud/logging/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
_HAVE_GAX = True
3636

3737
from google.cloud.client import JSONClient
38+
from google.cloud.environment_vars import DISABLE_GRPC
3839
from google.cloud.logging.connection import Connection
3940
from google.cloud.logging.connection import _LoggingAPI as JSONLoggingAPI
4041
from google.cloud.logging.connection import _MetricsAPI as JSONMetricsAPI
@@ -47,7 +48,7 @@
4748
from google.cloud.logging.sink import Sink
4849

4950

50-
_DISABLE_GAX = os.getenv('GOOGLE_CLOUD_DISABLE_GAX', False)
51+
_DISABLE_GAX = os.getenv(DISABLE_GRPC, False)
5152
_USE_GAX = _HAVE_GAX and not _DISABLE_GAX
5253
ASCENDING = 'timestamp asc'
5354
"""Query string to order by ascending timestamps."""

google/cloud/pubsub/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import os
1818

1919
from google.cloud.client import JSONClient
20+
from google.cloud.environment_vars import DISABLE_GRPC
2021
from google.cloud.pubsub.connection import Connection
2122
from google.cloud.pubsub.connection import _PublisherAPI as JSONPublisherAPI
2223
from google.cloud.pubsub.connection import _SubscriberAPI as JSONSubscriberAPI
@@ -41,7 +42,7 @@
4142
# pylint: enable=ungrouped-imports
4243

4344

44-
_DISABLE_GAX = os.getenv('GOOGLE_CLOUD_DISABLE_GAX', False)
45+
_DISABLE_GAX = os.getenv(DISABLE_GRPC, False)
4546
_USE_GAX = _HAVE_GAX and not _DISABLE_GAX
4647

4748

system_tests/run_emulator.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
'datastore': (GCD_DATASET, GCD_HOST),
3636
'pubsub': (PUBSUB_EMULATOR,)
3737
}
38-
_DS_READY_LINE = '[datastore] INFO: Dev App Server is now running\n'
38+
EXTRA = {
39+
'datastore': ('--no-legacy',),
40+
}
41+
_DS_READY_LINE = '[datastore] Dev App Server is now running.\n'
3942
_PS_READY_LINE_PREFIX = '[pubsub] INFO: Server started, listening on '
4043

4144

@@ -62,7 +65,9 @@ def get_start_command(package):
6265
:rtype: tuple
6366
:returns: The arguments to be used, in a tuple.
6467
"""
65-
return 'gcloud', 'beta', 'emulators', package, 'start'
68+
result = ('gcloud', 'beta', 'emulators', package, 'start')
69+
extra = EXTRA.get(package, ())
70+
return result + extra
6671

6772

6873
def get_env_init_command(package):
@@ -74,7 +79,9 @@ def get_env_init_command(package):
7479
:rtype: tuple
7580
:returns: The arguments to be used, in a tuple.
7681
"""
77-
return 'gcloud', 'beta', 'emulators', package, 'env-init'
82+
result = ('gcloud', 'beta', 'emulators', package, 'env-init')
83+
extra = EXTRA.get(package, ())
84+
return result + extra
7885

7986

8087
def datastore_wait_ready(popen):

0 commit comments

Comments
 (0)