diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index 94fc3773e793..4848ade20b05 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -29,6 +29,10 @@ from google.appengine.api import app_identity except ImportError: app_identity = None +try: + from grpc.beta import implementations +except ImportError: # pragma: NO COVER + implementations = None import six from six.moves.http_client import HTTPConnection from six.moves import configparser @@ -522,6 +526,76 @@ def _name_from_project_path(path, project, template): return match.group('name') +class MetadataPlugin(object): + """Callable class to transform metadata for gRPC requests. + + :type credentials: :class:`oauth2client.client.OAuth2Credentials` + :param credentials: The OAuth2 Credentials to use for creating + access tokens. + + :type user_agent: str + :param user_agent: The user agent to be used with API requests. + """ + + def __init__(self, credentials, user_agent): + self._credentials = credentials + self._user_agent = user_agent + + def __call__(self, unused_context, callback): + """Adds authorization header to request metadata. + + :type unused_context: object + :param unused_context: A gRPC context which is not needed + to modify headers. + + :type callback: callable + :param callback: A callback which will use the headers. + """ + access_token = self._credentials.get_access_token().access_token + headers = [ + ('Authorization', 'Bearer ' + access_token), + ('User-agent', self._user_agent), + ] + callback(headers, None) + + +def make_stub(credentials, user_agent, stub_factory, host, port): + """Makes a stub for an RPC service. + + Uses / depends on the beta implementation of gRPC. + + :type credentials: :class:`oauth2client.client.OAuth2Credentials` + :param credentials: The OAuth2 Credentials to use for creating + access tokens. + + :type user_agent: str + :param user_agent: (Optional) The user agent to be used with API requests. + + :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.beta._stub._AutoIntermediary` + :returns: The stub object used to make gRPC requests to a given API. + """ + # Leaving the first argument to ssl_channel_credentials() as None + # loads root certificates from `grpc/_adapter/credentials/roots.pem`. + transport_creds = implementations.ssl_channel_credentials(None, None, None) + custom_metadata_plugin = MetadataPlugin(credentials, user_agent) + auth_creds = implementations.metadata_call_credentials( + custom_metadata_plugin, name='google_creds') + channel_creds = implementations.composite_channel_credentials( + transport_creds, auth_creds) + channel = implementations.secure_channel(host, port, channel_creds) + return stub_factory(channel) + + try: from pytz import UTC # pylint: disable=unused-import,wrong-import-order except ImportError: diff --git a/gcloud/bigtable/client.py b/gcloud/bigtable/client.py index 38ecf51ecbc8..e0829c0d8a9b 100644 --- a/gcloud/bigtable/client.py +++ b/gcloud/bigtable/client.py @@ -29,7 +29,7 @@ from pkg_resources import get_distribution -from grpc.beta import implementations +from gcloud._helpers import make_stub from gcloud.bigtable._generated import ( bigtable_instance_admin_pb2 as instance_admin_v2_pb2) @@ -284,8 +284,9 @@ def _make_data_stub(self): :rtype: :class:`grpc.beta._stub._AutoIntermediary` :returns: A gRPC stub object. """ - return _make_stub(self, DATA_STUB_FACTORY_V2, - DATA_API_HOST_V2, DATA_API_PORT_V2) + return make_stub(self.credentials, self.user_agent, + DATA_STUB_FACTORY_V2, DATA_API_HOST_V2, + DATA_API_PORT_V2) def _make_instance_stub(self): """Creates gRPC stub to make requests to the Instance Admin API. @@ -293,8 +294,9 @@ def _make_instance_stub(self): :rtype: :class:`grpc.beta._stub._AutoIntermediary` :returns: A gRPC stub object. """ - return _make_stub(self, INSTANCE_STUB_FACTORY_V2, - INSTANCE_ADMIN_HOST_V2, INSTANCE_ADMIN_PORT_V2) + return make_stub(self.credentials, self.user_agent, + INSTANCE_STUB_FACTORY_V2, INSTANCE_ADMIN_HOST_V2, + INSTANCE_ADMIN_PORT_V2) def _make_operations_stub(self): """Creates gRPC stub to make requests to the Operations API. @@ -305,8 +307,9 @@ def _make_operations_stub(self): :rtype: :class:`grpc.beta._stub._AutoIntermediary` :returns: A gRPC stub object. """ - return _make_stub(self, OPERATIONS_STUB_FACTORY_V2, - OPERATIONS_API_HOST_V2, OPERATIONS_API_PORT_V2) + return make_stub(self.credentials, self.user_agent, + OPERATIONS_STUB_FACTORY_V2, OPERATIONS_API_HOST_V2, + OPERATIONS_API_PORT_V2) def _make_table_stub(self): """Creates gRPC stub to make requests to the Table Admin API. @@ -314,8 +317,9 @@ def _make_table_stub(self): :rtype: :class:`grpc.beta._stub._AutoIntermediary` :returns: A gRPC stub object. """ - return _make_stub(self, TABLE_STUB_FACTORY_V2, - TABLE_ADMIN_HOST_V2, TABLE_ADMIN_PORT_V2) + return make_stub(self.credentials, self.user_agent, + TABLE_STUB_FACTORY_V2, TABLE_ADMIN_HOST_V2, + TABLE_ADMIN_PORT_V2) def is_started(self): """Check if the client has been started. @@ -422,59 +426,3 @@ def list_instances(self): instances = [Instance.from_pb(instance_pb, self) for instance_pb in response.instances] return instances, response.failed_locations - - -class _MetadataPlugin(object): - """Callable class to transform metadata for gRPC requests. - - :type client: :class:`.client.Client` - :param client: The client that owns the instance. - Provides authorization and user agent. - """ - - def __init__(self, client): - self._credentials = client.credentials - self._user_agent = client.user_agent - - def __call__(self, unused_context, callback): - """Adds authorization header to request metadata.""" - access_token = self._credentials.get_access_token().access_token - headers = [ - ('Authorization', 'Bearer ' + access_token), - ('User-agent', self._user_agent), - ] - callback(headers, None) - - -def _make_stub(client, stub_factory, host, port): - """Makes a stub for an RPC service. - - Uses / depends on the beta implementation of gRPC. - - :type client: :class:`.client.Client` - :param client: The client that owns the instance. - 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.beta._stub._AutoIntermediary` - :returns: The stub object used to make gRPC requests to a given API. - """ - # Leaving the first argument to ssl_channel_credentials() as None - # loads root certificates from `grpc/_adapter/credentials/roots.pem`. - transport_creds = implementations.ssl_channel_credentials(None, None, None) - custom_metadata_plugin = _MetadataPlugin(client) - auth_creds = implementations.metadata_call_credentials( - custom_metadata_plugin, name='google_creds') - channel_creds = implementations.composite_channel_credentials( - transport_creds, auth_creds) - channel = implementations.secure_channel(host, port, channel_creds) - return stub_factory(channel) diff --git a/gcloud/bigtable/test_client.py b/gcloud/bigtable/test_client.py index abf8c9d467ba..1eb02a9f67b7 100644 --- a/gcloud/bigtable/test_client.py +++ b/gcloud/bigtable/test_client.py @@ -309,13 +309,14 @@ def mock_make_stub(*args): make_stub_args.append(args) return fake_stub - with _Monkey(MUT, _make_stub=mock_make_stub): + with _Monkey(MUT, make_stub=mock_make_stub): result = client._make_data_stub() self.assertTrue(result is fake_stub) self.assertEqual(make_stub_args, [ ( - client, + client.credentials, + client.user_agent, DATA_STUB_FACTORY_V2, DATA_API_HOST_V2, DATA_API_PORT_V2, @@ -340,13 +341,14 @@ def mock_make_stub(*args): make_stub_args.append(args) return fake_stub - with _Monkey(MUT, _make_stub=mock_make_stub): + with _Monkey(MUT, make_stub=mock_make_stub): result = client._make_instance_stub() self.assertTrue(result is fake_stub) self.assertEqual(make_stub_args, [ ( - client, + client.credentials, + client.user_agent, INSTANCE_STUB_FACTORY_V2, INSTANCE_ADMIN_HOST_V2, INSTANCE_ADMIN_PORT_V2, @@ -371,13 +373,14 @@ def mock_make_stub(*args): make_stub_args.append(args) return fake_stub - with _Monkey(MUT, _make_stub=mock_make_stub): + with _Monkey(MUT, make_stub=mock_make_stub): result = client._make_operations_stub() self.assertTrue(result is fake_stub) self.assertEqual(make_stub_args, [ ( - client, + client.credentials, + client.user_agent, OPERATIONS_STUB_FACTORY_V2, OPERATIONS_API_HOST_V2, OPERATIONS_API_PORT_V2, @@ -402,13 +405,14 @@ def mock_make_stub(*args): make_stub_args.append(args) return fake_stub - with _Monkey(MUT, _make_stub=mock_make_stub): + with _Monkey(MUT, make_stub=mock_make_stub): result = client._make_table_stub() self.assertTrue(result is fake_stub) self.assertEqual(make_stub_args, [ ( - client, + client.credentials, + client.user_agent, TABLE_STUB_FACTORY_V2, TABLE_ADMIN_HOST_V2, TABLE_ADMIN_PORT_V2, @@ -443,7 +447,7 @@ def mock_make_stub(*args): make_stub_args.append(args) return stub - with _Monkey(MUT, _make_stub=mock_make_stub): + with _Monkey(MUT, make_stub=mock_make_stub): client.start() self.assertTrue(client._data_stub_internal is stub) @@ -636,131 +640,6 @@ def test_list_instances(self): )]) -class Test_MetadataPlugin(unittest.TestCase): - - def _getTargetClass(self): - from gcloud.bigtable.client import _MetadataPlugin - return _MetadataPlugin - - 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 - PROJECT = 'PROJECT' - USER_AGENT = 'USER_AGENT' - - credentials = _Credentials() - 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) - callback_args = [] - - def callback(*args): - callback_args.append(args) - - transformer = self._makeOne(client) - result = transformer(None, callback) - cb_headers = [ - ('Authorization', 'Bearer ' + access_token_expected), - ('User-agent', DEFAULT_USER_AGENT), - ] - self.assertEqual(result, None) - self.assertEqual(callback_args, [(cb_headers, None)]) - self.assertEqual(credentials.scopes, [DATA_SCOPE]) - self.assertEqual(len(credentials._tokens), 1) - - -class Test__make_stub(unittest.TestCase): - - def _callFUT(self, *args, **kwargs): - from gcloud.bigtable.client import _make_stub - return _make_stub(*args, **kwargs) - - def test_it(self): - from gcloud._testing import _Monkey - from gcloud.bigtable import client as MUT - - mock_result = object() - stub_inputs = [] - - SSL_CREDS = object() - METADATA_CREDS = object() - COMPOSITE_CREDS = object() - CHANNEL = object() - - class _ImplementationsModule(object): - - def __init__(self): - self.ssl_channel_credentials_args = None - self.metadata_call_credentials_args = None - self.composite_channel_credentials_args = None - self.secure_channel_args = None - - def ssl_channel_credentials(self, *args): - self.ssl_channel_credentials_args = args - return SSL_CREDS - - def metadata_call_credentials(self, *args, **kwargs): - self.metadata_call_credentials_args = (args, kwargs) - return METADATA_CREDS - - def composite_channel_credentials(self, *args): - self.composite_channel_credentials_args = args - return COMPOSITE_CREDS - - def secure_channel(self, *args): - self.secure_channel_args = args - return CHANNEL - - implementations_mod = _ImplementationsModule() - - def mock_stub_factory(channel): - stub_inputs.append(channel) - return mock_result - - metadata_plugin = object() - clients = [] - - def mock_plugin(client): - clients.append(client) - return metadata_plugin - - host = 'HOST' - port = 1025 - client = object() - with _Monkey(MUT, implementations=implementations_mod, - _MetadataPlugin=mock_plugin): - result = self._callFUT(client, mock_stub_factory, host, port) - - self.assertTrue(result is mock_result) - self.assertEqual(stub_inputs, [CHANNEL]) - self.assertEqual(clients, [client]) - self.assertEqual(implementations_mod.ssl_channel_credentials_args, - (None, None, None)) - self.assertEqual(implementations_mod.metadata_call_credentials_args, - ((metadata_plugin,), {'name': 'google_creds'})) - self.assertEqual( - implementations_mod.composite_channel_credentials_args, - (SSL_CREDS, METADATA_CREDS)) - self.assertEqual(implementations_mod.secure_channel_args, - (host, port, COMPOSITE_CREDS)) - - class _Credentials(object): scopes = None @@ -769,13 +648,6 @@ 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 diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py index 738e0b7e0e4c..0ade56c8cf78 100644 --- a/gcloud/test__helpers.py +++ b/gcloud/test__helpers.py @@ -800,6 +800,120 @@ def test_w_project_passed_as_none(self): self.assertEqual(name, self.THING_NAME) +class TestMetadataPlugin(unittest.TestCase): + + def _getTargetClass(self): + from gcloud._helpers import MetadataPlugin + return MetadataPlugin + + def _makeOne(self, *args, **kwargs): + return self._getTargetClass()(*args, **kwargs) + + def test_constructor(self): + credentials = object() + user_agent = object() + plugin = self._makeOne(credentials, user_agent) + self.assertIs(plugin._credentials, credentials) + self.assertIs(plugin._user_agent, user_agent) + + def test___call__(self): + access_token_expected = 'FOOBARBAZ' + credentials = _Credentials(access_token=access_token_expected) + user_agent = 'USER_AGENT' + callback_args = [] + + def callback(*args): + callback_args.append(args) + + transformer = self._makeOne(credentials, user_agent) + result = transformer(None, callback) + cb_headers = [ + ('Authorization', 'Bearer ' + access_token_expected), + ('User-agent', user_agent), + ] + self.assertEqual(result, None) + self.assertEqual(callback_args, [(cb_headers, None)]) + self.assertEqual(len(credentials._tokens), 1) + + +class Test_make_stub(unittest.TestCase): + + def _callFUT(self, *args, **kwargs): + from gcloud._helpers import make_stub + return make_stub(*args, **kwargs) + + def test_it(self): + from gcloud._testing import _Monkey + from gcloud import _helpers as MUT + + mock_result = object() + stub_inputs = [] + + SSL_CREDS = object() + METADATA_CREDS = object() + COMPOSITE_CREDS = object() + CHANNEL = object() + + class _ImplementationsModule(object): + + def __init__(self): + self.ssl_channel_credentials_args = None + self.metadata_call_credentials_args = None + self.composite_channel_credentials_args = None + self.secure_channel_args = None + + def ssl_channel_credentials(self, *args): + self.ssl_channel_credentials_args = args + return SSL_CREDS + + def metadata_call_credentials(self, *args, **kwargs): + self.metadata_call_credentials_args = (args, kwargs) + return METADATA_CREDS + + def composite_channel_credentials(self, *args): + self.composite_channel_credentials_args = args + return COMPOSITE_CREDS + + def secure_channel(self, *args): + self.secure_channel_args = args + return CHANNEL + + implementations_mod = _ImplementationsModule() + + def mock_stub_factory(channel): + stub_inputs.append(channel) + return mock_result + + metadata_plugin = object() + plugin_args = [] + + def mock_plugin(*args): + plugin_args.append(args) + return metadata_plugin + + host = 'HOST' + port = 1025 + credentials = object() + user_agent = 'USER_AGENT' + with _Monkey(MUT, implementations=implementations_mod, + MetadataPlugin=mock_plugin): + result = self._callFUT(credentials, user_agent, + mock_stub_factory, host, port) + + self.assertTrue(result is mock_result) + self.assertEqual(stub_inputs, [CHANNEL]) + self.assertEqual(plugin_args, [(credentials, user_agent)]) + self.assertEqual(implementations_mod.ssl_channel_credentials_args, + (None, None, None)) + self.assertEqual(implementations_mod.metadata_call_credentials_args, + ((metadata_plugin,), {'name': 'google_creds'})) + self.assertEqual( + implementations_mod.composite_channel_credentials_args, + (SSL_CREDS, METADATA_CREDS)) + self.assertEqual(implementations_mod.secure_channel_args, + (host, port, COMPOSITE_CREDS)) + + class _AppIdentity(object): def __init__(self, app_id): @@ -852,3 +966,17 @@ class _TimeoutHTTPConnection(_BaseHTTPConnection): def getresponse(self): import socket raise socket.timeout('timed out') + + +class _Credentials(object): + + 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