Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Hoist 'app_engine_id'/'compute_engine_id' into 'gcloud._helpers'.
  • Loading branch information
tseaver committed Mar 20, 2015
commit 8dcdf7681478b0a5598b45e656268824b30a63d9
54 changes: 54 additions & 0 deletions gcloud/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@

This module is not part of the public API surface of `gcloud`.
"""
import socket

try:
from threading import local as Local
except ImportError: # pragma: NO COVER (who doesn't have it?)
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.
Expand Down Expand Up @@ -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()
53 changes: 2 additions & 51 deletions gcloud/datastore/_implicit_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@

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
Expand All @@ -41,52 +38,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)
Expand Down
116 changes: 0 additions & 116 deletions gcloud/datastore/test__implicit_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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')
116 changes: 116 additions & 0 deletions gcloud/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')