diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index cbd2b51a376c..3a469f300bf7 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -15,6 +15,7 @@ This module is not part of the public API surface of `gcloud`. """ +import socket try: from threading import local as Local @@ -22,6 +23,13 @@ class Local(object): """Placeholder for non-threaded applications.""" +from six.moves.http_client import HTTPConnection # pylint: disable=F0401 + +try: + from google.appengine.api import app_identity +except ImportError: + app_identity = None + class _LocalStack(Local): """Manage a thread-local LIFO stack of resources. @@ -102,3 +110,49 @@ def _lazy_property_deco(deferred_callable): # For Python2.7+ deferred_callable.__func__ would suffice. deferred_callable = deferred_callable.__get__(True) return _LazyProperty(deferred_callable.__name__, deferred_callable) + + +def _app_engine_id(): + """Gets the App Engine application ID if it can be inferred. + + :rtype: string or ``NoneType`` + :returns: App Engine application ID if running in App Engine, + else ``None``. + """ + if app_identity is None: + return None + + return app_identity.get_application_id() + + +def _compute_engine_id(): + """Gets the Compute Engine project ID if it can be inferred. + + Uses 169.254.169.254 for the metadata server to avoid request + latency from DNS lookup. + + See https://cloud.google.com/compute/docs/metadata#metadataserver + for information about this IP address. (This IP is also used for + Amazon EC2 instances, so the metadata flavor is crucial.) + + See https://github.com/google/oauth2client/issues/93 for context about + DNS latency. + + :rtype: string or ``NoneType`` + :returns: Compute Engine project ID if the metadata service is available, + else ``None``. + """ + host = '169.254.169.254' + uri_path = '/computeMetadata/v1/project/project-id' + headers = {'Metadata-Flavor': 'Google'} + connection = HTTPConnection(host, timeout=0.1) + + try: + connection.request('GET', uri_path, headers=headers) + response = connection.getresponse() + if response.status == 200: + return response.read() + except socket.error: # socket.timeout or socket.error(64, 'Host is down') + pass + finally: + connection.close() diff --git a/gcloud/datastore/_implicit_environ.py b/gcloud/datastore/_implicit_environ.py index 0fc3d421c69b..f48619e2dc81 100644 --- a/gcloud/datastore/_implicit_environ.py +++ b/gcloud/datastore/_implicit_environ.py @@ -19,15 +19,9 @@ """ import os -import socket - -from six.moves.http_client import HTTPConnection # pylint: disable=F0401 - -try: - from google.appengine.api import app_identity -except ImportError: - app_identity = None +from gcloud._helpers import _app_engine_id +from gcloud._helpers import _compute_engine_id from gcloud._helpers import _lazy_property_deco from gcloud import credentials from gcloud.datastore.connection import Connection @@ -41,52 +35,6 @@ _GCD_DATASET_ENV_VAR_NAME = 'DATASTORE_DATASET' -def app_engine_id(): - """Gets the App Engine application ID if it can be inferred. - - :rtype: string or ``NoneType`` - :returns: App Engine application ID if running in App Engine, - else ``None``. - """ - if app_identity is None: - return None - - return app_identity.get_application_id() - - -def compute_engine_id(): - """Gets the Compute Engine project ID if it can be inferred. - - Uses 169.254.169.254 for the metadata server to avoid request - latency from DNS lookup. - - See https://cloud.google.com/compute/docs/metadata#metadataserver - for information about this IP address. (This IP is also used for - Amazon EC2 instances, so the metadata flavor is crucial.) - - See https://github.com/google/oauth2client/issues/93 for context about - DNS latency. - - :rtype: string or ``NoneType`` - :returns: Compute Engine project ID if the metadata service is available, - else ``None``. - """ - host = '169.254.169.254' - uri_path = '/computeMetadata/v1/project/project-id' - headers = {'Metadata-Flavor': 'Google'} - connection = HTTPConnection(host, timeout=0.1) - - try: - connection.request('GET', uri_path, headers=headers) - response = connection.getresponse() - if response.status == 200: - return response.read() - except socket.error: # socket.timeout or socket.error(64, 'Host is down') - pass - finally: - connection.close() - - def _get_production_dataset_id(): """Gets the production application ID if it can be inferred.""" return os.getenv(_DATASET_ENV_VAR_NAME) @@ -121,10 +69,10 @@ def _determine_default_dataset_id(dataset_id=None): dataset_id = _get_gcd_dataset_id() if dataset_id is None: - dataset_id = app_engine_id() + dataset_id = _app_engine_id() if dataset_id is None: - dataset_id = compute_engine_id() + dataset_id = _compute_engine_id() return dataset_id diff --git a/gcloud/datastore/test__implicit_environ.py b/gcloud/datastore/test__implicit_environ.py index 31bb826d6426..4c41d8eb9d0c 100644 --- a/gcloud/datastore/test__implicit_environ.py +++ b/gcloud/datastore/test__implicit_environ.py @@ -121,68 +121,6 @@ def test_value_set(self): self.assertEqual(dataset_id, MOCK_DATASET_ID) -class Test_app_engine_id(unittest2.TestCase): - - def _callFUT(self): - from gcloud.datastore import _implicit_environ - return _implicit_environ.app_engine_id() - - def test_no_value(self): - from gcloud._testing import _Monkey - from gcloud.datastore import _implicit_environ - - with _Monkey(_implicit_environ, app_identity=None): - dataset_id = self._callFUT() - self.assertEqual(dataset_id, None) - - def test_value_set(self): - from gcloud._testing import _Monkey - from gcloud.datastore import _implicit_environ - - APP_ENGINE_ID = object() - APP_IDENTITY = _AppIdentity(APP_ENGINE_ID) - with _Monkey(_implicit_environ, app_identity=APP_IDENTITY): - dataset_id = self._callFUT() - self.assertEqual(dataset_id, APP_ENGINE_ID) - - -class Test_compute_engine_id(unittest2.TestCase): - - def _callFUT(self): - from gcloud.datastore import _implicit_environ - return _implicit_environ.compute_engine_id() - - def _monkeyConnection(self, connection): - from gcloud._testing import _Monkey - from gcloud.datastore import _implicit_environ - - def _factory(host, timeout): - connection.host = host - connection.timeout = timeout - return connection - - return _Monkey(_implicit_environ, HTTPConnection=_factory) - - def test_bad_status(self): - connection = _HTTPConnection(404, None) - with self._monkeyConnection(connection): - dataset_id = self._callFUT() - self.assertEqual(dataset_id, None) - - def test_success(self): - COMPUTE_ENGINE_ID = object() - connection = _HTTPConnection(200, COMPUTE_ENGINE_ID) - with self._monkeyConnection(connection): - dataset_id = self._callFUT() - self.assertEqual(dataset_id, COMPUTE_ENGINE_ID) - - def test_socket_raises(self): - connection = _TimeoutHTTPConnection() - with self._monkeyConnection(connection): - dataset_id = self._callFUT() - self.assertEqual(dataset_id, None) - - class Test__determine_default_dataset_id(unittest2.TestCase): def _callFUT(self, dataset_id=None): @@ -216,8 +154,8 @@ def gce_mock(): patched_methods = { '_get_production_dataset_id': prod_mock, '_get_gcd_dataset_id': gcd_mock, - 'app_engine_id': gae_mock, - 'compute_engine_id': gce_mock, + '_app_engine_id': gae_mock, + '_compute_engine_id': gce_mock, } with _Monkey(_implicit_environ, **patched_methods): @@ -412,57 +350,3 @@ def test_set_implicit(self): self._callFUT() self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn) - - -class _AppIdentity(object): - - def __init__(self, app_id): - self.app_id = app_id - - def get_application_id(self): - return self.app_id - - -class _HTTPResponse(object): - - def __init__(self, status, data): - self.status = status - self.data = data - - def read(self): - return self.data - - -class _BaseHTTPConnection(object): - - host = timeout = None - - def __init__(self): - self._close_count = 0 - self._called_args = [] - self._called_kwargs = [] - - def request(self, method, uri, **kwargs): - self._called_args.append((method, uri)) - self._called_kwargs.append(kwargs) - - def close(self): - self._close_count += 1 - - -class _HTTPConnection(_BaseHTTPConnection): - - def __init__(self, status, project_id): - super(_HTTPConnection, self).__init__() - self.status = status - self.project_id = project_id - - def getresponse(self): - return _HTTPResponse(self.status, self.project_id) - - -class _TimeoutHTTPConnection(_BaseHTTPConnection): - - def getresponse(self): - import socket - raise socket.timeout('timed out') diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py index 27668614643d..4817a566699b 100644 --- a/gcloud/test__helpers.py +++ b/gcloud/test__helpers.py @@ -93,3 +93,119 @@ def test_func(): lazy_prop = self._callFUT(staticmethod(test_func)) self.assertTrue(lazy_prop._deferred_callable is test_func) self.assertEqual(lazy_prop._name, 'test_func') + + +class Test__app_engine_id(unittest2.TestCase): + + def _callFUT(self): + from gcloud._helpers import _app_engine_id + return _app_engine_id() + + def test_no_value(self): + from gcloud._testing import _Monkey + from gcloud import _helpers + + with _Monkey(_helpers, app_identity=None): + dataset_id = self._callFUT() + self.assertEqual(dataset_id, None) + + def test_value_set(self): + from gcloud._testing import _Monkey + from gcloud import _helpers + + APP_ENGINE_ID = object() + APP_IDENTITY = _AppIdentity(APP_ENGINE_ID) + with _Monkey(_helpers, app_identity=APP_IDENTITY): + dataset_id = self._callFUT() + self.assertEqual(dataset_id, APP_ENGINE_ID) + + +class Test__compute_engine_id(unittest2.TestCase): + + def _callFUT(self): + from gcloud._helpers import _compute_engine_id + return _compute_engine_id() + + def _monkeyConnection(self, connection): + from gcloud._testing import _Monkey + from gcloud import _helpers + + def _factory(host, timeout): + connection.host = host + connection.timeout = timeout + return connection + + return _Monkey(_helpers, HTTPConnection=_factory) + + def test_bad_status(self): + connection = _HTTPConnection(404, None) + with self._monkeyConnection(connection): + dataset_id = self._callFUT() + self.assertEqual(dataset_id, None) + + def test_success(self): + COMPUTE_ENGINE_ID = object() + connection = _HTTPConnection(200, COMPUTE_ENGINE_ID) + with self._monkeyConnection(connection): + dataset_id = self._callFUT() + self.assertEqual(dataset_id, COMPUTE_ENGINE_ID) + + def test_socket_raises(self): + connection = _TimeoutHTTPConnection() + with self._monkeyConnection(connection): + dataset_id = self._callFUT() + self.assertEqual(dataset_id, None) + + +class _AppIdentity(object): + + def __init__(self, app_id): + self.app_id = app_id + + def get_application_id(self): + return self.app_id + + +class _HTTPResponse(object): + + def __init__(self, status, data): + self.status = status + self.data = data + + def read(self): + return self.data + + +class _BaseHTTPConnection(object): + + host = timeout = None + + def __init__(self): + self._close_count = 0 + self._called_args = [] + self._called_kwargs = [] + + def request(self, method, uri, **kwargs): + self._called_args.append((method, uri)) + self._called_kwargs.append(kwargs) + + def close(self): + self._close_count += 1 + + +class _HTTPConnection(_BaseHTTPConnection): + + def __init__(self, status, project_id): + super(_HTTPConnection, self).__init__() + self.status = status + self.project_id = project_id + + def getresponse(self): + return _HTTPResponse(self.status, self.project_id) + + +class _TimeoutHTTPConnection(_BaseHTTPConnection): + + def getresponse(self): + import socket + raise socket.timeout('timed out')