Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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()
60 changes: 4 additions & 56 deletions gcloud/datastore/_implicit_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down
120 changes: 2 additions & 118 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 @@ -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):
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')
Loading