Merge lp:~harlowja/cloud-init/smart-read into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Joshua Harlow
Status: Merged
Merged at revision: 1002
Proposed branch: lp:~harlowja/cloud-init/smart-read
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 264 lines (+100/-57)
2 files modified
cloudinit/sources/helpers/openstack.py (+80/-52)
tests/unittests/test_datasource/test_openstack.py (+20/-5)
To merge this branch: bzr merge lp:~harlowja/cloud-init/smart-read
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+232316@code.launchpad.net
To post a comment you must log in.
lp:~harlowja/cloud-init/smart-read updated
1003. By Joshua Harlow

Add logging around failure to read optional/mandatory paths

1004. By Joshua Harlow

Just use os.path.exists directly

1005. By Joshua Harlow

Log a warning when unable to fetch the openstack versions

1006. By Joshua Harlow

Show the available versions in the debug log message

1007. By Joshua Harlow

Fixed more of the slowness around fetching and retrying

1008. By Joshua Harlow

Fix retry cb to reflect what used to exist

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/sources/helpers/openstack.py'
2--- cloudinit/sources/helpers/openstack.py 2014-02-24 22:41:42 +0000
3+++ cloudinit/sources/helpers/openstack.py 2014-08-27 19:59:20 +0000
4@@ -150,17 +150,34 @@
5 pass
6
7 @abc.abstractmethod
8- def _path_exists(self, path):
9- pass
10-
11- @abc.abstractmethod
12 def _path_read(self, path):
13 pass
14
15 @abc.abstractmethod
16+ def _fetch_available_versions(self):
17+ pass
18+
19+ @abc.abstractmethod
20 def _read_ec2_metadata(self):
21 pass
22
23+ def _find_working_version(self, version):
24+ search_versions = [version] + list(OS_VERSIONS)
25+ available_versions = self._fetch_available_versions()
26+ for potential_version in search_versions:
27+ if not potential_version:
28+ continue
29+ if potential_version not in available_versions:
30+ continue
31+ if potential_version != version:
32+ LOG.debug("Version '%s' not available, attempting to use"
33+ " version '%s' instead", version,
34+ potential_version)
35+ return potential_version
36+ LOG.debug("Version '%s' not available, attempting to use '%s'"
37+ " instead", version, OS_LATEST)
38+ return OS_LATEST
39+
40 def _read_content_path(self, item):
41 path = item.get('content_path', '').lstrip("/")
42 path_pieces = path.split("/")
43@@ -170,23 +187,6 @@
44 path = self._path_join(self.base_path, "openstack", *path_pieces)
45 return self._path_read(path)
46
47- def _find_working_version(self, version):
48- search_versions = [version] + list(OS_VERSIONS)
49- for potential_version in search_versions:
50- if not potential_version:
51- continue
52- path = self._path_join(self.base_path, "openstack",
53- potential_version)
54- if self._path_exists(path):
55- if potential_version != version:
56- LOG.debug("Version '%s' not available, attempting to use"
57- " version '%s' instead", version,
58- potential_version)
59- return potential_version
60- LOG.debug("Version '%s' not available, attempting to use '%s'"
61- " instead", version, OS_LATEST)
62- return OS_LATEST
63-
64 def read_v2(self, version=None):
65 """Reads a version 2 formatted location.
66
67@@ -228,15 +228,18 @@
68 path = self._path_join(self.base_path, path)
69 data = None
70 found = False
71- if self._path_exists(path):
72- try:
73- data = self._path_read(path)
74- except IOError:
75- raise NonReadable("Failed to read: %s" % path)
76+ try:
77+ data = self._path_read(path)
78+ except IOError as e:
79+ if not required:
80+ LOG.debug("Failed reading optional path %s due"
81+ " to: %s", path, e)
82+ else:
83+ LOG.exception("Failed reading mandatory path %s", path)
84+ else:
85 found = True
86- else:
87- if required:
88- raise NonReadable("Missing mandatory path: %s" % path)
89+ if required and not found:
90+ raise NonReadable("Missing mandatory path: %s" % path)
91 if found and translator:
92 try:
93 data = translator(data)
94@@ -304,21 +307,35 @@
95 class ConfigDriveReader(BaseReader):
96 def __init__(self, base_path):
97 super(ConfigDriveReader, self).__init__(base_path)
98+ self._versions = None
99
100 def _path_join(self, base, *add_ons):
101 components = [base] + list(add_ons)
102 return os.path.join(*components)
103
104- def _path_exists(self, path):
105- return os.path.exists(path)
106-
107 def _path_read(self, path):
108 return util.load_file(path)
109
110+ def _fetch_available_versions(self):
111+ if self._versions is not None:
112+ return self._versions
113+ else:
114+ versions_available = []
115+ path = self._path_join(self.base_path, 'openstack')
116+ try:
117+ for child in os.listdir(path):
118+ child_path = os.path.join(path, child)
119+ if os.path.isdir(child_path):
120+ versions_available.append(child)
121+ except (OSError, IOError):
122+ pass
123+ self._versions = tuple(versions_available)
124+ return self._versions
125+
126 def _read_ec2_metadata(self):
127 path = self._path_join(self.base_path,
128 'ec2', 'latest', 'meta-data.json')
129- if not self._path_exists(path):
130+ if not os.path.exists(path):
131 return {}
132 else:
133 try:
134@@ -338,7 +355,7 @@
135 found = {}
136 for name in FILES_V1.keys():
137 path = self._path_join(self.base_path, name)
138- if self._path_exists(path):
139+ if os.path.exists(path):
140 found[name] = path
141 if len(found) == 0:
142 raise NonReadable("%s: no files found" % (self.base_path))
143@@ -400,17 +417,31 @@
144 self.ssl_details = ssl_details
145 self.timeout = float(timeout)
146 self.retries = int(retries)
147+ self._versions = None
148+
149+ def _fetch_available_versions(self):
150+ if self._versions is not None:
151+ return self._versions
152+ else:
153+ path = self._path_join(self.base_path, "openstack")
154+ versions_available = []
155+ try:
156+ versions = self._path_read(path)
157+ except IOError as e:
158+ LOG.warn("Unable to read openstack versions from %s due"
159+ " to: %s", path, e)
160+ else:
161+ for line in versions.splitlines():
162+ line = line.strip()
163+ if not line:
164+ continue
165+ versions_available.append(line)
166+ self._versions = tuple(versions_available)
167+ return self._versions
168
169 def _path_read(self, path):
170- response = url_helper.readurl(path,
171- retries=self.retries,
172- ssl_details=self.ssl_details,
173- timeout=self.timeout)
174- return response.contents
175-
176- def _path_exists(self, path):
177-
178- def should_retry_cb(request, cause):
179+
180+ def should_retry_cb(_request_args, cause):
181 try:
182 code = int(cause.code)
183 if code >= 400:
184@@ -420,15 +451,12 @@
185 pass
186 return True
187
188- try:
189- response = url_helper.readurl(path,
190- retries=self.retries,
191- ssl_details=self.ssl_details,
192- timeout=self.timeout,
193- exception_cb=should_retry_cb)
194- return response.ok()
195- except IOError:
196- return False
197+ response = url_helper.readurl(path,
198+ retries=self.retries,
199+ ssl_details=self.ssl_details,
200+ timeout=self.timeout,
201+ exception_cb=should_retry_cb)
202+ return response.contents
203
204 def _path_join(self, base, *add_ons):
205 return url_helper.combine_url(base, *add_ons)
206
207=== modified file 'tests/unittests/test_datasource/test_openstack.py'
208--- tests/unittests/test_datasource/test_openstack.py 2014-08-22 15:40:25 +0000
209+++ tests/unittests/test_datasource/test_openstack.py 2014-08-27 19:59:20 +0000
210@@ -67,8 +67,8 @@
211 CONTENT_0 = 'This is contents of /etc/foo.cfg\n'
212 CONTENT_1 = '# this is /etc/bar/bar.cfg\n'
213 OS_FILES = {
214- 'openstack/2012-08-10/meta_data.json': json.dumps(OSTACK_META),
215- 'openstack/2012-08-10/user_data': USER_DATA,
216+ 'openstack/latest/meta_data.json': json.dumps(OSTACK_META),
217+ 'openstack/latest/user_data': USER_DATA,
218 'openstack/content/0000': CONTENT_0,
219 'openstack/content/0001': CONTENT_1,
220 'openstack/latest/meta_data.json': json.dumps(OSTACK_META),
221@@ -78,6 +78,9 @@
222 EC2_FILES = {
223 'latest/user-data': USER_DATA,
224 }
225+EC2_VERSIONS = [
226+ 'lastest',
227+]
228
229
230 def _register_uris(version, ec2_files, ec2_meta, os_files):
231@@ -85,6 +88,9 @@
232 same data returned by the openstack metadata service (and ec2 service)."""
233
234 def match_ec2_url(uri, headers):
235+ path = uri.path.strip("/")
236+ if len(path) == 0:
237+ return (200, headers, "\n".join(EC2_VERSIONS))
238 path = uri.path.lstrip("/")
239 if path in ec2_files:
240 return (200, headers, ec2_files.get(path))
241@@ -110,11 +116,20 @@
242 return (200, headers, str(value))
243 return (404, headers, '')
244
245+ def match_os_uri(uri, headers):
246+ path = uri.path.strip("/")
247+ if path == 'openstack':
248+ return (200, headers, "\n".join([openstack.OS_LATEST]))
249+ path = uri.path.lstrip("/")
250+ if path in os_files:
251+ return (200, headers, os_files.get(path))
252+ return (404, headers, '')
253+
254 def get_request_callback(method, uri, headers):
255 uri = urlparse(uri)
256- path = uri.path.lstrip("/")
257- if path in os_files:
258- return (200, headers, os_files.get(path))
259+ path = uri.path.lstrip("/").split("/")
260+ if path[0] == 'openstack':
261+ return match_os_uri(uri, headers)
262 return match_ec2_url(uri, headers)
263
264 hp.register_uri(hp.GET, re.compile(r'http://169.254.169.254/.*'),