Skip to content

Commit 9c09ced

Browse files
committed
Merge pull request #747 from tseaver/741-hoist_gae_gcd_detection
#741: hoist GAE/GCD detection into `gcloud._helpers`
2 parents 8948a49 + 14a7415 commit 9c09ced

File tree

4 files changed

+176
-174
lines changed

4 files changed

+176
-174
lines changed

gcloud/_helpers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@
1515
1616
This module is not part of the public API surface of `gcloud`.
1717
"""
18+
import socket
1819

1920
try:
2021
from threading import local as Local
2122
except ImportError: # pragma: NO COVER (who doesn't have it?)
2223
class Local(object):
2324
"""Placeholder for non-threaded applications."""
2425

26+
from six.moves.http_client import HTTPConnection # pylint: disable=F0401
27+
28+
try:
29+
from google.appengine.api import app_identity
30+
except ImportError:
31+
app_identity = None
32+
2533

2634
class _LocalStack(Local):
2735
"""Manage a thread-local LIFO stack of resources.
@@ -102,3 +110,49 @@ def _lazy_property_deco(deferred_callable):
102110
# For Python2.7+ deferred_callable.__func__ would suffice.
103111
deferred_callable = deferred_callable.__get__(True)
104112
return _LazyProperty(deferred_callable.__name__, deferred_callable)
113+
114+
115+
def _app_engine_id():
116+
"""Gets the App Engine application ID if it can be inferred.
117+
118+
:rtype: string or ``NoneType``
119+
:returns: App Engine application ID if running in App Engine,
120+
else ``None``.
121+
"""
122+
if app_identity is None:
123+
return None
124+
125+
return app_identity.get_application_id()
126+
127+
128+
def _compute_engine_id():
129+
"""Gets the Compute Engine project ID if it can be inferred.
130+
131+
Uses 169.254.169.254 for the metadata server to avoid request
132+
latency from DNS lookup.
133+
134+
See https://cloud.google.com/compute/docs/metadata#metadataserver
135+
for information about this IP address. (This IP is also used for
136+
Amazon EC2 instances, so the metadata flavor is crucial.)
137+
138+
See https://github.com/google/oauth2client/issues/93 for context about
139+
DNS latency.
140+
141+
:rtype: string or ``NoneType``
142+
:returns: Compute Engine project ID if the metadata service is available,
143+
else ``None``.
144+
"""
145+
host = '169.254.169.254'
146+
uri_path = '/computeMetadata/v1/project/project-id'
147+
headers = {'Metadata-Flavor': 'Google'}
148+
connection = HTTPConnection(host, timeout=0.1)
149+
150+
try:
151+
connection.request('GET', uri_path, headers=headers)
152+
response = connection.getresponse()
153+
if response.status == 200:
154+
return response.read()
155+
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
156+
pass
157+
finally:
158+
connection.close()

gcloud/datastore/_implicit_environ.py

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,9 @@
1919
"""
2020

2121
import os
22-
import socket
23-
24-
from six.moves.http_client import HTTPConnection # pylint: disable=F0401
25-
26-
try:
27-
from google.appengine.api import app_identity
28-
except ImportError:
29-
app_identity = None
3022

23+
from gcloud._helpers import _app_engine_id
24+
from gcloud._helpers import _compute_engine_id
3125
from gcloud._helpers import _lazy_property_deco
3226
from gcloud import credentials
3327
from gcloud.datastore.connection import Connection
@@ -41,52 +35,6 @@
4135
_GCD_DATASET_ENV_VAR_NAME = 'DATASTORE_DATASET'
4236

4337

44-
def app_engine_id():
45-
"""Gets the App Engine application ID if it can be inferred.
46-
47-
:rtype: string or ``NoneType``
48-
:returns: App Engine application ID if running in App Engine,
49-
else ``None``.
50-
"""
51-
if app_identity is None:
52-
return None
53-
54-
return app_identity.get_application_id()
55-
56-
57-
def compute_engine_id():
58-
"""Gets the Compute Engine project ID if it can be inferred.
59-
60-
Uses 169.254.169.254 for the metadata server to avoid request
61-
latency from DNS lookup.
62-
63-
See https://cloud.google.com/compute/docs/metadata#metadataserver
64-
for information about this IP address. (This IP is also used for
65-
Amazon EC2 instances, so the metadata flavor is crucial.)
66-
67-
See https://github.com/google/oauth2client/issues/93 for context about
68-
DNS latency.
69-
70-
:rtype: string or ``NoneType``
71-
:returns: Compute Engine project ID if the metadata service is available,
72-
else ``None``.
73-
"""
74-
host = '169.254.169.254'
75-
uri_path = '/computeMetadata/v1/project/project-id'
76-
headers = {'Metadata-Flavor': 'Google'}
77-
connection = HTTPConnection(host, timeout=0.1)
78-
79-
try:
80-
connection.request('GET', uri_path, headers=headers)
81-
response = connection.getresponse()
82-
if response.status == 200:
83-
return response.read()
84-
except socket.error: # socket.timeout or socket.error(64, 'Host is down')
85-
pass
86-
finally:
87-
connection.close()
88-
89-
9038
def _get_production_dataset_id():
9139
"""Gets the production application ID if it can be inferred."""
9240
return os.getenv(_DATASET_ENV_VAR_NAME)
@@ -121,10 +69,10 @@ def _determine_default_dataset_id(dataset_id=None):
12169
dataset_id = _get_gcd_dataset_id()
12270

12371
if dataset_id is None:
124-
dataset_id = app_engine_id()
72+
dataset_id = _app_engine_id()
12573

12674
if dataset_id is None:
127-
dataset_id = compute_engine_id()
75+
dataset_id = _compute_engine_id()
12876

12977
return dataset_id
13078

gcloud/datastore/test__implicit_environ.py

Lines changed: 2 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -121,68 +121,6 @@ def test_value_set(self):
121121
self.assertEqual(dataset_id, MOCK_DATASET_ID)
122122

123123

124-
class Test_app_engine_id(unittest2.TestCase):
125-
126-
def _callFUT(self):
127-
from gcloud.datastore import _implicit_environ
128-
return _implicit_environ.app_engine_id()
129-
130-
def test_no_value(self):
131-
from gcloud._testing import _Monkey
132-
from gcloud.datastore import _implicit_environ
133-
134-
with _Monkey(_implicit_environ, app_identity=None):
135-
dataset_id = self._callFUT()
136-
self.assertEqual(dataset_id, None)
137-
138-
def test_value_set(self):
139-
from gcloud._testing import _Monkey
140-
from gcloud.datastore import _implicit_environ
141-
142-
APP_ENGINE_ID = object()
143-
APP_IDENTITY = _AppIdentity(APP_ENGINE_ID)
144-
with _Monkey(_implicit_environ, app_identity=APP_IDENTITY):
145-
dataset_id = self._callFUT()
146-
self.assertEqual(dataset_id, APP_ENGINE_ID)
147-
148-
149-
class Test_compute_engine_id(unittest2.TestCase):
150-
151-
def _callFUT(self):
152-
from gcloud.datastore import _implicit_environ
153-
return _implicit_environ.compute_engine_id()
154-
155-
def _monkeyConnection(self, connection):
156-
from gcloud._testing import _Monkey
157-
from gcloud.datastore import _implicit_environ
158-
159-
def _factory(host, timeout):
160-
connection.host = host
161-
connection.timeout = timeout
162-
return connection
163-
164-
return _Monkey(_implicit_environ, HTTPConnection=_factory)
165-
166-
def test_bad_status(self):
167-
connection = _HTTPConnection(404, None)
168-
with self._monkeyConnection(connection):
169-
dataset_id = self._callFUT()
170-
self.assertEqual(dataset_id, None)
171-
172-
def test_success(self):
173-
COMPUTE_ENGINE_ID = object()
174-
connection = _HTTPConnection(200, COMPUTE_ENGINE_ID)
175-
with self._monkeyConnection(connection):
176-
dataset_id = self._callFUT()
177-
self.assertEqual(dataset_id, COMPUTE_ENGINE_ID)
178-
179-
def test_socket_raises(self):
180-
connection = _TimeoutHTTPConnection()
181-
with self._monkeyConnection(connection):
182-
dataset_id = self._callFUT()
183-
self.assertEqual(dataset_id, None)
184-
185-
186124
class Test__determine_default_dataset_id(unittest2.TestCase):
187125

188126
def _callFUT(self, dataset_id=None):
@@ -216,8 +154,8 @@ def gce_mock():
216154
patched_methods = {
217155
'_get_production_dataset_id': prod_mock,
218156
'_get_gcd_dataset_id': gcd_mock,
219-
'app_engine_id': gae_mock,
220-
'compute_engine_id': gce_mock,
157+
'_app_engine_id': gae_mock,
158+
'_compute_engine_id': gce_mock,
221159
}
222160

223161
with _Monkey(_implicit_environ, **patched_methods):
@@ -412,57 +350,3 @@ def test_set_implicit(self):
412350
self._callFUT()
413351

414352
self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn)
415-
416-
417-
class _AppIdentity(object):
418-
419-
def __init__(self, app_id):
420-
self.app_id = app_id
421-
422-
def get_application_id(self):
423-
return self.app_id
424-
425-
426-
class _HTTPResponse(object):
427-
428-
def __init__(self, status, data):
429-
self.status = status
430-
self.data = data
431-
432-
def read(self):
433-
return self.data
434-
435-
436-
class _BaseHTTPConnection(object):
437-
438-
host = timeout = None
439-
440-
def __init__(self):
441-
self._close_count = 0
442-
self._called_args = []
443-
self._called_kwargs = []
444-
445-
def request(self, method, uri, **kwargs):
446-
self._called_args.append((method, uri))
447-
self._called_kwargs.append(kwargs)
448-
449-
def close(self):
450-
self._close_count += 1
451-
452-
453-
class _HTTPConnection(_BaseHTTPConnection):
454-
455-
def __init__(self, status, project_id):
456-
super(_HTTPConnection, self).__init__()
457-
self.status = status
458-
self.project_id = project_id
459-
460-
def getresponse(self):
461-
return _HTTPResponse(self.status, self.project_id)
462-
463-
464-
class _TimeoutHTTPConnection(_BaseHTTPConnection):
465-
466-
def getresponse(self):
467-
import socket
468-
raise socket.timeout('timed out')

0 commit comments

Comments
 (0)