diff --git a/google/cloud/spanner_v1/client.py b/google/cloud/spanner_v1/client.py index 29f3fcf69a..89ab490cff 100644 --- a/google/cloud/spanner_v1/client.py +++ b/google/cloud/spanner_v1/client.py @@ -158,7 +158,6 @@ class Client(ClientWithProject): _instance_admin_api = None _database_admin_api = None - _endpoint_cache = {} user_agent = None _SET_PROJECT = True # Used by from_service_account_json() diff --git a/google/cloud/spanner_v1/database.py b/google/cloud/spanner_v1/database.py index 99b7244f9d..a3aa3390c4 100644 --- a/google/cloud/spanner_v1/database.py +++ b/google/cloud/spanner_v1/database.py @@ -17,16 +17,12 @@ import copy import functools import grpc -import os import re import threading -import warnings -from google.api_core.client_options import ClientOptions import google.auth.credentials from google.protobuf.struct_pb2 import Struct from google.cloud.exceptions import NotFound -from google.api_core.exceptions import PermissionDenied import six # pylint: disable=ungrouped-imports @@ -67,18 +63,6 @@ _DATABASE_METADATA_FILTER = "name:{0}/operations/" -_RESOURCE_ROUTING_PERMISSIONS_WARNING = ( - "The client library attempted to connect to an endpoint closer to your Cloud Spanner data " - "but was unable to do so. The client library will fall back and route requests to the endpoint " - "given in the client options, which may result in increased latency. " - "We recommend including the scope https://www.googleapis.com/auth/spanner.admin so that the " - "client library can get an instance-specific endpoint and efficiently route requests." -) - - -class ResourceRoutingPermissionsWarning(Warning): - pass - class Database(object): """Representation of a Cloud Spanner Database. @@ -245,36 +229,6 @@ def spanner_api(self): credentials = self._instance._client.credentials if isinstance(credentials, google.auth.credentials.Scoped): credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,)) - if ( - os.getenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING") - == "true" - ): - endpoint_cache = self._instance._client._endpoint_cache - if self._instance.name in endpoint_cache: - client_options = ClientOptions( - api_endpoint=endpoint_cache[self._instance.name] - ) - else: - try: - api = self._instance._client.instance_admin_api - resp = api.get_instance( - self._instance.name, - field_mask={"paths": ["endpoint_uris"]}, - metadata=_metadata_with_prefix(self.name), - ) - endpoints = resp.endpoint_uris - if endpoints: - endpoint_cache[self._instance.name] = list(endpoints)[0] - client_options = ClientOptions( - api_endpoint=endpoint_cache[self._instance.name] - ) - # If there are no endpoints, use default endpoint. - except PermissionDenied: - warnings.warn( - _RESOURCE_ROUTING_PERMISSIONS_WARNING, - ResourceRoutingPermissionsWarning, - stacklevel=2, - ) self._spanner_api = SpannerClient( credentials=credentials, client_info=client_info, diff --git a/tests/system/test_system.py b/tests/system/test_system.py index 926cbb4b82..97477119b7 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -56,9 +56,6 @@ CREATE_INSTANCE = os.getenv("GOOGLE_CLOUD_TESTS_CREATE_SPANNER_INSTANCE") is not None USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None -USE_RESOURCE_ROUTING = ( - os.getenv("GOOGLE_CLOUD_SPANNER_ENABLE_RESOURCE_BASED_ROUTING") == "true" -) if CREATE_INSTANCE: INSTANCE_ID = "google-cloud" + unique_resource_id("-") @@ -286,61 +283,6 @@ def tearDown(self): for doomed in self.to_delete: doomed.drop() - @unittest.skipUnless(USE_RESOURCE_ROUTING, "requires enabling resource routing") - def test_spanner_api_use_user_specified_endpoint(self): - # Clear cache. - Client._endpoint_cache = {} - api = Config.CLIENT.instance_admin_api - resp = api.get_instance( - Config.INSTANCE.name, field_mask={"paths": ["endpoint_uris"]} - ) - if not resp or not resp.endpoint_uris: - return # no resolved endpoint. - resolved_endpoint = resp.endpoint_uris[0] - - client = Client(client_options={"api_endpoint": resolved_endpoint}) - - instance = client.instance(Config.INSTANCE.instance_id) - temp_db_id = "temp_db" + unique_resource_id("_") - temp_db = instance.database(temp_db_id) - temp_db.spanner_api - - # No endpoint cache - Default endpoint used. - self.assertEqual(client._endpoint_cache, {}) - - @unittest.skipUnless(USE_RESOURCE_ROUTING, "requires enabling resource routing") - def test_spanner_api_use_resolved_endpoint(self): - # Clear cache. - Client._endpoint_cache = {} - api = Config.CLIENT.instance_admin_api - resp = api.get_instance( - Config.INSTANCE.name, field_mask={"paths": ["endpoint_uris"]} - ) - if not resp or not resp.endpoint_uris: - return # no resolved endpoint. - resolved_endpoint = resp.endpoint_uris[0] - - client = Client( - client_options=Config.CLIENT._client_options - ) # Use same endpoint as main client. - - instance = client.instance(Config.INSTANCE.instance_id) - temp_db_id = "temp_db" + unique_resource_id("_") - temp_db = instance.database(temp_db_id) - temp_db.spanner_api - - # Endpoint is cached - resolved endpoint used. - self.assertIn(Config.INSTANCE.name, client._endpoint_cache) - self.assertEqual( - client._endpoint_cache[Config.INSTANCE.name], resolved_endpoint - ) - - # Endpoint is cached at a class level. - self.assertIn(Config.INSTANCE.name, Config.CLIENT._endpoint_cache) - self.assertEqual( - Config.CLIENT._endpoint_cache[Config.INSTANCE.name], resolved_endpoint - ) - def test_list_databases(self): # Since `Config.INSTANCE` is newly created in `setUpModule`, the # database created in `setUpClass` here will be the only one. diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py index 37d9eb41a9..5b71b08325 100644 --- a/tests/unit/test_database.py +++ b/tests/unit/test_database.py @@ -260,14 +260,8 @@ def test_restore_info(self): self.assertEqual(database.restore_info, restore_info) def test_spanner_api_property_w_scopeless_creds(self): - from google.cloud.spanner_admin_instance_v1.proto import ( - spanner_instance_admin_pb2 as admin_v1_pb2, - ) client = _Client() - client.instance_admin_api.get_instance.return_value = admin_v1_pb2.Instance( - endpoint_uris=[] - ) client_info = client._client_info = mock.Mock() client_options = client._client_options = mock.Mock() credentials = client.credentials = object() @@ -277,10 +271,8 @@ def test_spanner_api_property_w_scopeless_creds(self): patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with patch as spanner_client: - api = database.spanner_api + with patch as spanner_client: + api = database.spanner_api self.assertIs(api, spanner_client.return_value) @@ -288,7 +280,6 @@ def test_spanner_api_property_w_scopeless_creds(self): again = database.spanner_api self.assertIs(again, api) - client.instance_admin_api.get_instance.assert_called_once() spanner_client.assert_called_once_with( credentials=credentials, client_info=client_info, @@ -297,9 +288,6 @@ def test_spanner_api_property_w_scopeless_creds(self): def test_spanner_api_w_scoped_creds(self): import google.auth.credentials - from google.cloud.spanner_admin_instance_v1.proto import ( - spanner_instance_admin_pb2 as admin_v1_pb2, - ) from google.cloud.spanner_v1.database import SPANNER_DATA_SCOPE class _CredentialsWithScopes(google.auth.credentials.Scoped): @@ -323,22 +311,14 @@ def with_scopes(self, scopes): database = self._make_one(self.DATABASE_ID, instance, pool=pool) patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - client.instance_admin_api.get_instance.return_value = admin_v1_pb2.Instance( - endpoint_uris=[] - ) - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with patch as spanner_client: - api = database.spanner_api - self.assertNotIn(instance.name, client._endpoint_cache) + with patch as spanner_client: + api = database.spanner_api # API instance is cached again = database.spanner_api self.assertIs(again, api) - client.instance_admin_api.get_instance.assert_called_once() self.assertEqual(len(spanner_client.call_args_list), 1) called_args, called_kw = spanner_client.call_args self.assertEqual(called_args, ()) @@ -348,222 +328,6 @@ def with_scopes(self, scopes): self.assertEqual(scoped._scopes, expected_scopes) self.assertIs(scoped._source, credentials) - def test_spanner_api_property_w_scopeless_creds_and_new_endpoint(self): - from google.cloud.spanner_admin_instance_v1.proto import ( - spanner_instance_admin_pb2 as admin_v1_pb2, - ) - - client = _Client() - client.instance_admin_api.get_instance.return_value = admin_v1_pb2.Instance( - endpoint_uris=["test1", "test2"] - ) - client_info = client._client_info = mock.Mock() - client._client_options = mock.Mock() - credentials = client.credentials = object() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - client_patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - options_patch = mock.patch("google.cloud.spanner_v1.database.ClientOptions") - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with options_patch as options: - with client_patch as spanner_client: - api = database.spanner_api - - self.assertIs(api, spanner_client.return_value) - self.assertIn(instance.name, client._endpoint_cache) - - # API instance is cached - again = database.spanner_api - self.assertIs(again, api) - - self.assertEqual(len(spanner_client.call_args_list), 1) - called_args, called_kw = spanner_client.call_args - self.assertEqual(called_args, ()) - self.assertEqual(called_kw["client_info"], client_info) - self.assertEqual(called_kw["credentials"], credentials) - options.assert_called_with(api_endpoint="test1") - - def test_spanner_api_w_scoped_creds_and_new_endpoint(self): - import google.auth.credentials - from google.cloud.spanner_admin_instance_v1.proto import ( - spanner_instance_admin_pb2 as admin_v1_pb2, - ) - from google.cloud.spanner_v1.database import SPANNER_DATA_SCOPE - - class _CredentialsWithScopes(google.auth.credentials.Scoped): - def __init__(self, scopes=(), source=None): - self._scopes = scopes - self._source = source - - def requires_scopes(self): # pragma: NO COVER - return True - - def with_scopes(self, scopes): - return self.__class__(scopes, self) - - expected_scopes = (SPANNER_DATA_SCOPE,) - client = _Client() - client_info = client._client_info = mock.Mock() - client._client_options = mock.Mock() - credentials = client.credentials = _CredentialsWithScopes() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - client_patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - options_patch = mock.patch("google.cloud.spanner_v1.database.ClientOptions") - client.instance_admin_api.get_instance.return_value = admin_v1_pb2.Instance( - endpoint_uris=["test1", "test2"] - ) - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with options_patch as options: - with client_patch as spanner_client: - api = database.spanner_api - - self.assertIs(api, spanner_client.return_value) - self.assertIn(instance.name, client._endpoint_cache) - - # API instance is cached - again = database.spanner_api - self.assertIs(again, api) - - self.assertEqual(len(spanner_client.call_args_list), 1) - called_args, called_kw = spanner_client.call_args - self.assertEqual(called_args, ()) - self.assertEqual(called_kw["client_info"], client_info) - scoped = called_kw["credentials"] - self.assertEqual(scoped._scopes, expected_scopes) - self.assertIs(scoped._source, credentials) - options.assert_called_with(api_endpoint="test1") - - def test_spanner_api_resource_routing_permissions_error(self): - from google.api_core.exceptions import PermissionDenied - - client = _Client() - client_info = client._client_info = mock.Mock() - client_options = client._client_options = mock.Mock() - client._endpoint_cache = {} - credentials = client.credentials = mock.Mock() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - client.instance_admin_api.get_instance.side_effect = PermissionDenied("test") - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with patch as spanner_client: - api = database.spanner_api - - self.assertIs(api, spanner_client.return_value) - - # API instance is cached - again = database.spanner_api - self.assertIs(again, api) - - client.instance_admin_api.get_instance.assert_called_once() - spanner_client.assert_called_once_with( - credentials=credentials, - client_info=client_info, - client_options=client_options, - ) - - def test_spanner_api_disable_resource_routing(self): - client = _Client() - client_info = client._client_info = mock.Mock() - client_options = client._client_options = mock.Mock() - client._endpoint_cache = {} - credentials = client.credentials = mock.Mock() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "false" - with patch as spanner_client: - api = database.spanner_api - - self.assertIs(api, spanner_client.return_value) - - # API instance is cached - again = database.spanner_api - self.assertIs(again, api) - - client.instance_admin_api.get_instance.assert_not_called() - spanner_client.assert_called_once_with( - credentials=credentials, - client_info=client_info, - client_options=client_options, - ) - - def test_spanner_api_cached_endpoint(self): - from google.cloud.spanner_admin_instance_v1.proto import ( - spanner_instance_admin_pb2 as admin_v1_pb2, - ) - - client = _Client() - client_info = client._client_info = mock.Mock() - client._client_options = mock.Mock() - client._endpoint_cache = {self.INSTANCE_NAME: "cached"} - credentials = client.credentials = mock.Mock() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - client_patch = mock.patch("google.cloud.spanner_v1.database.SpannerClient") - options_patch = mock.patch("google.cloud.spanner_v1.database.ClientOptions") - client.instance_admin_api.get_instance.return_value = admin_v1_pb2.Instance( - endpoint_uris=["test1", "test2"] - ) - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with options_patch as options: - with client_patch as spanner_client: - api = database.spanner_api - - self.assertIs(api, spanner_client.return_value) - - # API instance is cached - again = database.spanner_api - self.assertIs(again, api) - - self.assertEqual(len(spanner_client.call_args_list), 1) - called_args, called_kw = spanner_client.call_args - self.assertEqual(called_args, ()) - self.assertEqual(called_kw["client_info"], client_info) - self.assertEqual(called_kw["credentials"], credentials) - options.assert_called_with(api_endpoint="cached") - - def test_spanner_api_resource_routing_error(self): - from google.api_core.exceptions import GoogleAPIError - - client = _Client() - client._client_info = mock.Mock() - client._client_options = mock.Mock() - client.credentials = mock.Mock() - instance = _Instance(self.INSTANCE_NAME, client=client) - pool = _Pool() - database = self._make_one(self.DATABASE_ID, instance, pool=pool) - - client.instance_admin_api.get_instance.side_effect = GoogleAPIError("test") - - with mock.patch("os.getenv") as getenv: - getenv.return_value = "true" - with self.assertRaises(GoogleAPIError): - database.spanner_api - - client.instance_admin_api.get_instance.assert_called_once() - def test_spanner_api_w_emulator_host(self): client = _Client() instance = _Instance(self.INSTANCE_NAME, client=client, emulator_host="host")