Merge lp:~harlowja/cloud-init/not-found-userdata into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Joshua Harlow
Status: Merged
Merged at revision: 924
Proposed branch: lp:~harlowja/cloud-init/not-found-userdata
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 164 lines (+46/-14)
5 files modified
cloudinit/ec2_utils.py (+24/-3)
cloudinit/url_helper.py (+9/-8)
cloudinit/util.py (+3/-2)
tests/unittests/test_datasource/test_maas.py (+2/-1)
tests/unittests/test_ec2_util.py (+8/-0)
To merge this branch: bzr merge lp:~harlowja/cloud-init/not-found-userdata
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+202963@code.launchpad.net

Description of the change

Skip retry and continued fetch of userdata when NOT_FOUND

When a 404 http code comes back from the fetching of ec2
data, instead of retrying immediately stop the fetching process
and in the userdata fetching function handle this case as a
special case of no userdata being fetched (an empty string
in this case).

To post a comment you must log in.
924. By Joshua Harlow

Remove pylint warning about unused request_args

925. By Joshua Harlow

Use the right exception

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/ec2_utils.py'
2--- cloudinit/ec2_utils.py 2014-01-17 22:08:58 +0000
3+++ cloudinit/ec2_utils.py 2014-01-24 01:36:44 +0000
4@@ -16,6 +16,7 @@
5 # You should have received a copy of the GNU General Public License
6 # along with this program. If not, see <http://www.gnu.org/licenses/>.
7
8+import httplib
9 from urlparse import (urlparse, urlunparse)
10
11 import functools
12@@ -23,9 +24,11 @@
13 import urllib
14
15 from cloudinit import log as logging
16+from cloudinit import url_helper
17 from cloudinit import util
18
19 LOG = logging.getLogger(__name__)
20+SKIP_USERDATA_CODES = frozenset([httplib.NOT_FOUND])
21
22
23 def maybe_json_object(text):
24@@ -138,20 +141,38 @@
25 return joined
26
27
28+def _skip_retry_on_codes(status_codes, _request_args, cause):
29+ """Returns if a request should retry based on a given set of codes that
30+ case retrying to be stopped/skipped.
31+ """
32+ if cause.code in status_codes:
33+ return False
34+ return True
35+
36+
37 def get_instance_userdata(api_version='latest',
38 metadata_address='http://169.254.169.254',
39 ssl_details=None, timeout=5, retries=5):
40 ud_url = combine_url(metadata_address, api_version)
41 ud_url = combine_url(ud_url, 'user-data')
42+ user_data = ''
43 try:
44+ # It is ok for userdata to not exist (thats why we are stopping if
45+ # NOT_FOUND occurs) and just in that case returning an empty string.
46+ exception_cb = functools.partial(_skip_retry_on_codes,
47+ SKIP_USERDATA_CODES)
48 response = util.read_file_or_url(ud_url,
49 ssl_details=ssl_details,
50 timeout=timeout,
51- retries=retries)
52- return str(response)
53+ retries=retries,
54+ exception_cb=exception_cb)
55+ user_data = str(response)
56+ except url_helper.UrlError as e:
57+ if e.code not in SKIP_USERDATA_CODES:
58+ util.logexc(LOG, "Failed fetching userdata from url %s", ud_url)
59 except Exception:
60 util.logexc(LOG, "Failed fetching userdata from url %s", ud_url)
61- return ''
62+ return user_data
63
64
65 def get_instance_metadata(api_version='latest',
66
67=== modified file 'cloudinit/url_helper.py'
68--- cloudinit/url_helper.py 2013-05-03 22:11:32 +0000
69+++ cloudinit/url_helper.py 2014-01-24 01:36:44 +0000
70@@ -103,7 +103,7 @@
71
72 def readurl(url, data=None, timeout=None, retries=0, sec_between=1,
73 headers=None, headers_cb=None, ssl_details=None,
74- check_status=True, allow_redirects=True):
75+ check_status=True, allow_redirects=True, exception_cb=None):
76 url = _cleanurl(url)
77 req_args = {
78 'url': url,
79@@ -163,14 +163,13 @@
80 # Handle retrying ourselves since the built-in support
81 # doesn't handle sleeping between tries...
82 for i in range(0, manual_tries):
83+ req_args['headers'] = headers_cb(url)
84+ filtered_req_args = {}
85+ for (k, v) in req_args.items():
86+ if k == 'data':
87+ continue
88+ filtered_req_args[k] = v
89 try:
90- req_args['headers'] = headers_cb(url)
91- filtered_req_args = {}
92- for (k, v) in req_args.items():
93- if k == 'data':
94- continue
95- filtered_req_args[k] = v
96-
97 LOG.debug("[%s/%s] open '%s' with %s configuration", i,
98 manual_tries, url, filtered_req_args)
99
100@@ -196,6 +195,8 @@
101 # ssl exceptions are not going to get fixed by waiting a
102 # few seconds
103 break
104+ if exception_cb and not exception_cb(filtered_req_args, excps[-1]):
105+ break
106 if i + 1 < manual_tries and sec_between > 0:
107 LOG.debug("Please wait %s seconds while we wait to try again",
108 sec_between)
109
110=== modified file 'cloudinit/util.py'
111--- cloudinit/util.py 2014-01-23 21:48:12 +0000
112+++ cloudinit/util.py 2014-01-24 01:36:44 +0000
113@@ -691,7 +691,7 @@
114
115 def read_file_or_url(url, timeout=5, retries=10,
116 headers=None, data=None, sec_between=1, ssl_details=None,
117- headers_cb=None):
118+ headers_cb=None, exception_cb=None):
119 url = url.lstrip()
120 if url.startswith("/"):
121 url = "file://%s" % url
122@@ -708,7 +708,8 @@
123 headers_cb=headers_cb,
124 data=data,
125 sec_between=sec_between,
126- ssl_details=ssl_details)
127+ ssl_details=ssl_details,
128+ exception_cb=exception_cb)
129
130
131 def load_yaml(blob, default=None, allowed=(dict,)):
132
133=== modified file 'tests/unittests/test_datasource/test_maas.py'
134--- tests/unittests/test_datasource/test_maas.py 2013-04-25 16:13:08 +0000
135+++ tests/unittests/test_datasource/test_maas.py 2014-01-24 01:36:44 +0000
136@@ -119,7 +119,8 @@
137 mock_request(url, headers=None, timeout=mocker.ANY,
138 data=mocker.ANY, sec_between=mocker.ANY,
139 ssl_details=mocker.ANY, retries=mocker.ANY,
140- headers_cb=my_headers_cb)
141+ headers_cb=my_headers_cb,
142+ exception_cb=mocker.ANY)
143 resp = valid.get(key)
144 self.mocker.result(util.StringResponse(resp))
145 self.mocker.replay()
146
147=== modified file 'tests/unittests/test_ec2_util.py'
148--- tests/unittests/test_ec2_util.py 2014-01-17 21:34:53 +0000
149+++ tests/unittests/test_ec2_util.py 2014-01-24 01:36:44 +0000
150@@ -34,6 +34,14 @@
151 self.assertEquals('', userdata)
152
153 @hp.activate
154+ def test_userdata_fetch_fail_server_not_found(self):
155+ hp.register_uri(hp.GET,
156+ 'http://169.254.169.254/%s/user-data' % (self.VERSION),
157+ status=404)
158+ userdata = eu.get_instance_userdata(self.VERSION)
159+ self.assertEquals('', userdata)
160+
161+ @hp.activate
162 def test_metadata_fetch_no_keys(self):
163 base_url = 'http://169.254.169.254/%s/meta-data' % (self.VERSION)
164 hp.register_uri(hp.GET, base_url, status=200,