Skip to content

Commit 6267857

Browse files
committed
Adding implicit dataset ID support for Compute Engine.
Fixes #475.
1 parent d9fd60e commit 6267857

File tree

3 files changed

+149
-7
lines changed

3 files changed

+149
-7
lines changed

gcloud/datastore/__init__.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@
7272
def set_default_dataset_id(dataset_id=None):
7373
"""Set default dataset ID either explicitly or implicitly as fall-back.
7474
75-
In implicit case, currently only supports enviroment variable but will
76-
support App Engine, Compute Engine and other environments in the future.
77-
78-
Local environment variable used is:
79-
- GCLOUD_DATASET_ID
75+
In implicit case, supports three cases. In order of precedence, the
76+
implicit cases are:
77+
- GCLOUD_DATASET_ID environment variable
78+
- Google App Engine application ID
79+
- Google Compute Engine project ID (from metadata server)
8080
8181
:type dataset_id: string
8282
:param dataset_id: Optional. The dataset ID to use as default.
@@ -87,6 +87,9 @@ def set_default_dataset_id(dataset_id=None):
8787
if dataset_id is None:
8888
dataset_id = _implicit_environ.app_engine_id()
8989

90+
if dataset_id is None:
91+
dataset_id = _implicit_environ.compute_engine_id()
92+
9093
if dataset_id is not None:
9194
_implicit_environ.DATASET_ID = dataset_id
9295

gcloud/datastore/_implicit_environ.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
imply the current dataset ID and connection from the enviroment.
55
"""
66

7+
import httplib2
8+
import socket
9+
710
try:
811
from google.appengine.api import app_identity
912
except ImportError:
@@ -18,7 +21,7 @@
1821

1922

2023
def app_engine_id():
21-
"""Gets the App Engine application ID if it can be found.
24+
"""Gets the App Engine application ID if it can be implied.
2225
2326
:rtype: string or ``NoneType``
2427
:returns: App Engine application ID if running in App Engine,
@@ -28,3 +31,36 @@ def app_engine_id():
2831
return None
2932

3033
return app_identity.get_application_id()
34+
35+
36+
def compute_engine_id():
37+
"""Gets the Compute Engine project ID if it can be implied.
38+
39+
Uses 169.254.169.254 for the metadata server to avoid request
40+
latency from DNS lookup.
41+
42+
See https://cloud.google.com/compute/docs/metadata#metadataserver
43+
for information about this IP address. (This IP is also used for
44+
Amazon EC2 instances, so the metadata flavor is crucial.)
45+
46+
See https://github.com/google/oauth2client/issues/93 for context about
47+
DNS latency.
48+
49+
:rtype: string or ``NoneType``
50+
:returns: Compute Engine project ID if the metadata service is available,
51+
else ``None``.
52+
"""
53+
http = httplib2.Http(timeout=0.1)
54+
uri = 'http://169.254.169.254/computeMetadata/v1/project/project-id'
55+
headers = {'Metadata-Flavor': 'Google'}
56+
57+
response = content = None
58+
try:
59+
response, content = http.request(uri, method='GET', headers=headers)
60+
except socket.timeout:
61+
pass
62+
63+
if response is None or response['status'] != '200':
64+
return None
65+
66+
return content

gcloud/datastore/test___init__.py

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,85 @@ def test_set_implicit_both_env_and_appengine(self):
9696
from gcloud.datastore import _implicit_environ
9797

9898
IMPLICIT_DATASET_ID = 'IMPLICIT'
99+
APP_IDENTITY = _AppIdentity('GAE')
100+
101+
with self._monkey(IMPLICIT_DATASET_ID):
102+
with _Monkey(_implicit_environ, app_identity=APP_IDENTITY):
103+
self._callFUT()
104+
105+
self.assertEqual(_implicit_environ.DATASET_ID, IMPLICIT_DATASET_ID)
106+
107+
def _implicit_compute_engine_helper(self, status):
108+
from gcloud._testing import _Monkey
109+
from gcloud.datastore import _implicit_environ
110+
111+
COMPUTE_ENGINE_ID = 'GCE'
112+
HTTPLIB2 = _Httplib2(COMPUTE_ENGINE_ID, status=status)
113+
if status == '200':
114+
EXPECTED_ID = COMPUTE_ENGINE_ID
115+
else:
116+
EXPECTED_ID = None
117+
118+
with self._monkey(None):
119+
with _Monkey(_implicit_environ, httplib2=HTTPLIB2):
120+
self._callFUT()
121+
122+
self.assertEqual(_implicit_environ.DATASET_ID, EXPECTED_ID)
123+
self.assertEqual(len(HTTPLIB2._http_instances), 1)
124+
125+
(timeout, http_instance), = HTTPLIB2._http_instances
126+
self.assertEqual(timeout, 0.1)
127+
self.assertEqual(
128+
http_instance._called_uris,
129+
['http://169.254.169.254/computeMetadata/v1/project/project-id'])
130+
expected_kwargs = {
131+
'method': 'GET',
132+
'headers': {
133+
'Metadata-Flavor': 'Google',
134+
},
135+
}
136+
self.assertEqual(http_instance._called_kwargs, [expected_kwargs])
137+
138+
def test_set_implicit_from_compute_engine(self):
139+
self._implicit_compute_engine_helper('200')
140+
141+
def test_set_implicit_from_compute_engine_bad_status(self):
142+
self._implicit_compute_engine_helper('404')
143+
144+
def test_set_implicit_from_compute_engine_raise_timeout(self):
145+
self._implicit_compute_engine_helper('RAISE')
146+
147+
def test_set_implicit_both_appengine_and_compute(self):
148+
from gcloud._testing import _Monkey
149+
from gcloud.datastore import _implicit_environ
150+
99151
APP_ENGINE_ID = 'GAE'
100152
APP_IDENTITY = _AppIdentity(APP_ENGINE_ID)
153+
HTTPLIB2 = _Httplib2('GCE')
154+
155+
with self._monkey(None):
156+
with _Monkey(_implicit_environ, app_identity=APP_IDENTITY,
157+
httplib2=HTTPLIB2):
158+
self._callFUT()
159+
160+
self.assertEqual(_implicit_environ.DATASET_ID, APP_ENGINE_ID)
161+
self.assertEqual(len(HTTPLIB2._http_instances), 0)
162+
163+
def test_set_implicit_three_env_appengine_and_compute(self):
164+
from gcloud._testing import _Monkey
165+
from gcloud.datastore import _implicit_environ
166+
167+
IMPLICIT_DATASET_ID = 'IMPLICIT'
168+
APP_IDENTITY = _AppIdentity('GAE')
169+
HTTPLIB2 = _Httplib2('GCE')
101170

102171
with self._monkey(IMPLICIT_DATASET_ID):
103-
with _Monkey(_implicit_environ, app_identity=APP_IDENTITY):
172+
with _Monkey(_implicit_environ, app_identity=APP_IDENTITY,
173+
httplib2=HTTPLIB2):
104174
self._callFUT()
105175

106176
self.assertEqual(_implicit_environ.DATASET_ID, IMPLICIT_DATASET_ID)
177+
self.assertEqual(len(HTTPLIB2._http_instances), 0)
107178

108179

109180
class Test_set_default_connection(unittest2.TestCase):
@@ -201,3 +272,35 @@ def __init__(self, app_id):
201272

202273
def get_application_id(self):
203274
return self.app_id
275+
276+
277+
class _Http(object):
278+
279+
def __init__(self, parent):
280+
self.parent = parent
281+
self._called_uris = []
282+
self._called_kwargs = []
283+
284+
def request(self, uri, **kwargs):
285+
import socket
286+
287+
self._called_uris.append(uri)
288+
self._called_kwargs.append(kwargs)
289+
290+
if self.parent.status == 'RAISE':
291+
raise socket.timeout('timed out')
292+
else:
293+
return {'status': self.parent.status}, self.parent.project_id
294+
295+
296+
class _Httplib2(object):
297+
298+
def __init__(self, project_id, status='200'):
299+
self.project_id = project_id
300+
self.status = status
301+
self._http_instances = []
302+
303+
def Http(self, timeout=None):
304+
result = _Http(self)
305+
self._http_instances.append((timeout, result))
306+
return result

0 commit comments

Comments
 (0)