Skip to content

Commit

Permalink
Adding helpers to make gRPC stubs from a client.
Browse files Browse the repository at this point in the history
  • Loading branch information
dhermes committed Sep 28, 2015
1 parent 7c151af commit f5086c7
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 5 deletions.
92 changes: 92 additions & 0 deletions gcloud/bigtable/_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Utility methods for gcloud Bigtable.
Primarily includes helpers for dealing with low-level
protobuf objects.
"""


# See https://gist.github.com/dhermes/bbc5b7be1932bfffae77
# for appropriate values on other systems.
# NOTE: Even this path is Unix specific.
SSL_CERT_FILE = '/etc/ssl/certs/ca-certificates.crt'


class MetadataTransformer(object):
"""Callable class to transform metadata for gRPC requests.
:type client: :class:`.client.Client`
:param client: The client that owns the cluster. Provides authorization and
user agent.
"""

def __init__(self, client):
self._credentials = client.credentials
self._user_agent = client.user_agent

def __call__(self, ignored_val):
"""Adds authorization header to request metadata."""
access_token = self._credentials.get_access_token().access_token
return [
('Authorization', 'Bearer ' + access_token),
('User-agent', self._user_agent),
]


def get_certs():
"""Gets the root certificates.
.. note::
This is only called by :func:`make_stub`. For most applications,
a few gRPC stubs (four total, one for each service) will be created
when a :class:`.Client` is created. This function will not likely
be used again while that application is running.
However, it may be worthwhile to cache the output of this function.
:rtype: str
:returns: The root certificates for the current machine.
"""
with open(SSL_CERT_FILE, mode='rb') as file_obj:
return file_obj.read()


def make_stub(client, stub_factory, host, port):
"""Makes a stub for the an API.
:type client: :class:`.client.Client`
:param client: The client that owns the cluster. Provides authorization and
user agent.
:type stub_factory: callable
:param stub_factory: A factory which will create a gRPC stub for
a given service.
:type host: str
:param host: The host for the service.
:type port: int
:param port: The port for the service.
:rtype: :class:`grpc.early_adopter.implementations._Stub`
:returns: The stub object used to make gRPC requests to a given API.
"""
custom_metadata_transformer = MetadataTransformer(client)
return stub_factory(host, port,
metadata_transformer=custom_metadata_transformer,
secure=True,
root_certificates=get_certs())
33 changes: 31 additions & 2 deletions gcloud/bigtable/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
'cloud-bigtable.data.readonly')
"""Scope for reading table data."""

DEFAULT_TIMEOUT_SECONDS = 10
"""The default timeout to use for API requests."""

DEFAULT_USER_AGENT = 'gcloud-bigtable-python'
"""The default user agent for API requests."""


class Client(_ClientFactoryMixin, _ClientProjectMixin):
"""Client for interacting with Google Cloud Bigtable API.
Expand Down Expand Up @@ -86,12 +92,22 @@ class Client(_ClientFactoryMixin, _ClientProjectMixin):
interact with the Cluster Admin or Table Admin APIs. This
requires the :const:`ADMIN_SCOPE`. Defaults to :data:`False`.
:type user_agent: str
:param user_agent: (Optional) The user agent to be used with API request.
Defaults to :const:`DEFAULT_USER_AGENT`.
:type timeout_seconds: int
:param timeout_seconds: Number of seconds for request time-out. If not
passed, defaults to
:const:`DEFAULT_TIMEOUT_SECONDS`.
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
and ``admin`` are :data:`True`
"""

def __init__(self, project=None, credentials=None,
read_only=False, admin=False):
read_only=False, admin=False, user_agent=DEFAULT_USER_AGENT,
timeout_seconds=DEFAULT_TIMEOUT_SECONDS):
_ClientProjectMixin.__init__(self, project=project)
if credentials is None:
credentials = get_credentials()
Expand All @@ -109,4 +125,17 @@ def __init__(self, project=None, credentials=None,
if admin:
scopes.append(ADMIN_SCOPE)

self.credentials = credentials.create_scoped(scopes)
self._admin = bool(admin)
self._credentials = credentials.create_scoped(scopes)
self.user_agent = user_agent
self.timeout_seconds = timeout_seconds

@property
def credentials(self):
"""Getter for client's credentials.
:rtype:
:class:`OAuth2Credentials <oauth2client.client.OAuth2Credentials>`
:returns: The credentials stored on the client.
"""
return self._credentials
143 changes: 143 additions & 0 deletions gcloud/bigtable/test__helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import unittest2


class TestMetadataTransformer(unittest2.TestCase):

def _getTargetClass(self):
from gcloud.bigtable._helpers import MetadataTransformer
return MetadataTransformer

def _makeOne(self, *args, **kwargs):
return self._getTargetClass()(*args, **kwargs)

def test_constructor(self):
from gcloud.bigtable.client import Client
from gcloud.bigtable.client import DATA_SCOPE

credentials = _Credentials()
project = 'PROJECT'
user_agent = 'USER_AGENT'
client = Client(project=project, credentials=credentials,
user_agent=user_agent)
transformer = self._makeOne(client)
self.assertTrue(transformer._credentials is credentials)
self.assertEqual(transformer._user_agent, user_agent)
self.assertEqual(credentials._scopes, [DATA_SCOPE])

def test___call__(self):
from gcloud.bigtable.client import Client
from gcloud.bigtable.client import DATA_SCOPE
from gcloud.bigtable.client import DEFAULT_USER_AGENT

access_token_expected = 'FOOBARBAZ'
credentials = _Credentials(access_token=access_token_expected)
project = 'PROJECT'
client = Client(project=project, credentials=credentials)

transformer = self._makeOne(client)
result = transformer(None)
self.assertEqual(
result,
[
('Authorization', 'Bearer ' + access_token_expected),
('User-agent', DEFAULT_USER_AGENT),
])
self.assertEqual(credentials._scopes, [DATA_SCOPE])
self.assertEqual(len(credentials._tokens), 1)


class Test_get_certs(unittest2.TestCase):

def _callFUT(self, *args, **kwargs):
from gcloud.bigtable._helpers import get_certs
return get_certs(*args, **kwargs)

def test_it(self):
import tempfile
from gcloud._testing import _Monkey
from gcloud.bigtable import _helpers as MUT

# Just write to a mock file.
filename = tempfile.mktemp()
contents = b'FOOBARBAZ'
with open(filename, 'wb') as file_obj:
file_obj.write(contents)

with _Monkey(MUT, SSL_CERT_FILE=filename):
result = self._callFUT()

self.assertEqual(result, contents)


class Test_make_stub(unittest2.TestCase):

def _callFUT(self, *args, **kwargs):
from gcloud.bigtable._helpers import make_stub
return make_stub(*args, **kwargs)

def test_it(self):
from gcloud._testing import _Monkey
from gcloud.bigtable import _helpers as MUT

mock_result = object()
stub_inputs = []

def mock_stub_factory(host, port, metadata_transformer=None,
secure=None, root_certificates=None):
stub_inputs.append((host, port, metadata_transformer,
secure, root_certificates))
return mock_result

transformed = object()
clients = []

def mock_transformer(client):
clients.append(client)
return transformed

host = 'HOST'
port = 1025
certs = 'FOOBAR'
client = object()
with _Monkey(MUT, get_certs=lambda: certs,
MetadataTransformer=mock_transformer):
result = self._callFUT(client, mock_stub_factory, host, port)

self.assertTrue(result is mock_result)
self.assertEqual(stub_inputs, [(host, port, transformed, True, certs)])
self.assertEqual(clients, [client])


class _Credentials(object):

_scopes = None

def __init__(self, access_token=None):
self._access_token = access_token
self._tokens = []

def get_access_token(self):
from oauth2client.client import AccessTokenInfo
token = AccessTokenInfo(access_token=self._access_token,
expires_in=None)
self._tokens.append(token)
return token

def create_scoped(self, scope):
self._scopes = scope
return self
33 changes: 30 additions & 3 deletions gcloud/bigtable/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@ def _makeOne(self, *args, **kwargs):

def _constructor_test_helper(self, expected_scopes, creds,
read_only=False, admin=False,
user_agent=None, timeout_seconds=None,
expected_creds=None):
from gcloud.bigtable import client as MUT

user_agent = user_agent or MUT.DEFAULT_USER_AGENT
timeout_seconds = timeout_seconds or MUT.DEFAULT_TIMEOUT_SECONDS
PROJECT = 'PROJECT'
client = self._makeOne(project=PROJECT, credentials=creds,
read_only=read_only, admin=admin)
read_only=read_only, admin=admin,
user_agent=user_agent,
timeout_seconds=timeout_seconds)

expected_creds = expected_creds or creds
self.assertTrue(client.credentials is expected_creds)
self.assertTrue(client._credentials is expected_creds)
self.assertEqual(client._credentials._scopes, expected_scopes)

self.assertEqual(client.project, PROJECT)
self.assertEqual(client.credentials._scopes, expected_scopes)
self.assertEqual(client.timeout_seconds, timeout_seconds)
self.assertEqual(client.user_agent, user_agent)

def test_constructor_default_scopes(self):
from gcloud.bigtable import client as MUT
Expand All @@ -44,6 +54,17 @@ def test_constructor_default_scopes(self):
creds = _Credentials()
self._constructor_test_helper(expected_scopes, creds)

def test_constructor_custom_user_agent_and_timeout(self):
from gcloud.bigtable import client as MUT

timeout_seconds = 1337
user_agent = 'custom-application'
expected_scopes = [MUT.DATA_SCOPE]
creds = _Credentials()
self._constructor_test_helper(expected_scopes, creds,
user_agent=user_agent,
timeout_seconds=timeout_seconds)

def test_constructor_with_admin(self):
from gcloud.bigtable import client as MUT

Expand Down Expand Up @@ -78,6 +99,12 @@ def mock_get_credentials():
self._constructor_test_helper(expected_scopes, None,
expected_creds=creds)

def test_credentials_getter(self):
credentials = _Credentials()
project = 'PROJECT'
client = self._makeOne(project=project, credentials=credentials)
self.assertTrue(client.credentials is credentials)


class _Credentials(object):

Expand Down

0 comments on commit f5086c7

Please sign in to comment.