Skip to content

Commit c3286a2

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

File tree

3 files changed

+148
-6
lines changed

3 files changed

+148
-6
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
imply the current dataset ID and connection from the enviroment.
1919
"""
2020

21+
import httplib2
22+
import socket
23+
2124
try:
2225
from google.appengine.api import app_identity
2326
except ImportError:
@@ -42,3 +45,36 @@ def app_engine_id():
4245
return None
4346

4447
return app_identity.get_application_id()
48+
49+
50+
def compute_engine_id():
51+
"""Gets the Compute Engine project ID if it can be inferred.
52+
53+
Uses 169.254.169.254 for the metadata server to avoid request
54+
latency from DNS lookup.
55+
56+
See https://cloud.google.com/compute/docs/metadata#metadataserver
57+
for information about this IP address. (This IP is also used for
58+
Amazon EC2 instances, so the metadata flavor is crucial.)
59+
60+
See https://github.com/google/oauth2client/issues/93 for context about
61+
DNS latency.
62+
63+
:rtype: string or ``NoneType``
64+
:returns: Compute Engine project ID if the metadata service is available,
65+
else ``None``.
66+
"""
67+
http = httplib2.Http(timeout=0.1)
68+
uri = 'http://169.254.169.254/computeMetadata/v1/project/project-id'
69+
headers = {'Metadata-Flavor': 'Google'}
70+
71+
response = content = None
72+
try:
73+
response, content = http.request(uri, method='GET', headers=headers)
74+
except socket.timeout:
75+
pass
76+
77+
if response is None or response['status'] != '200':
78+
return None
79+
80+
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)