Merge ~ajorgens/cloud-init:_include-urlerror into cloud-init:master

Proposed by Andrew Jorgensen on 2017-10-02
Status: Merged
Approved by: Scott Moser on 2017-11-13
Approved revision: f9e35a63caeda8eef72140145815421d1bc1f90f
Merged at revision: e10ad2d7854b87024b5d051db50166125fce2279
Proposed branch: ~ajorgens/cloud-init:_include-urlerror
Merge into: cloud-init:master
Diff against target: 113 lines (+68/-10)
2 files modified
cloudinit/user_data.py (+18/-10)
tests/unittests/test_data.py (+50/-0)
Reviewer Review Type Date Requested Status
Scott Moser 2017-10-02 Approve on 2017-11-13
Review via email: mp+331660@code.launchpad.net

Commit Message

Catch UrlError when #include'ing URLs

Without this the entire stage can fail, which will leave an instance
unaccessible.

Description of the Change

Catch UrlError when #include'ing URLs

Without this the entire stage can fail, which will leave an instance unaccessible.

To post a comment you must log in.
Scott Moser (smoser) wrote :

i approve with 2 small questions in line.

Andrew Jorgensen (ajorgens) wrote :

Revised commit per smoser's advice.

Ryan Harper (raharper) wrote :

On Tue, Oct 3, 2017 at 8:23 AM, Scott Moser <email address hidden> wrote:

> i approve with 2 small questions in line.
>
>
> Diff comments:
>
> > diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
> > index 88cb7f8..de459c9 100644
> > --- a/cloudinit/user_data.py
> > +++ b/cloudinit/user_data.py
> > @@ -222,16 +223,25 @@ class UserDataProcessor(object):
> > if include_once_on and os.path.isfile(include_once_fn):
> > content = util.load_file(include_once_fn)
> > else:
> > - resp = util.read_file_or_url(include_url,
> > -
> ssl_details=self.ssl_details)
> > - if include_once_on and resp.ok():
> > - util.write_file(include_once_fn, resp.contents,
> mode=0o600)
> > - if resp.ok():
> > - content = resp.contents
> > - else:
> > - LOG.warning(("Fetching from %s resulted in"
> > - " a invalid http code of %s"),
> > - include_url, resp.code)
> > + try:
> > + resp = util.read_file_or_url(include_url,
> > +
> ssl_details=self.ssl_details)
> > + if include_once_on and resp.ok():
> > + util.write_file(include_once_fn, resp.contents,
> > + mode=0o600)
> > + if resp.ok():
> > + content = resp.contents
> > + else:
> > + LOG.warning(("Fetching from %s resulted in"
> > + " a invalid http code of %s"),
> > + include_url, resp.code)
> > + except UrlError as urle:
> > + LOG.warning(
> > + "Fetching from %s resulted in a UrlError: %s",
> > + include_url, urle.cause)
>
> i think just printing urle here in its string form should be reasonable.
> str(urle) would look like:
> cloudinit.url_helper.UrlError('404 Client Error: Not Found for url:
> http://example.com/asdf')
> str(urle.cause) would look like:
> requests.exceptions.HTTPError('404 Client Error: Not Found for url:
> http://example.com/asdf')
>

> > + except IOError as ioe:
> > + LOG.warning("Fetching from %s resulted in an
> IOError: %s",
> > + include_url, ioe.strerror)
>
> i guess fine, but is there a reason for 'ioe.strerror' rather than just
> str(ioe) ?
>
> >
> > if content is not None:
> > new_msg = convert_string(content)
>
>
In both cases, using an attribute which is already a string will be faster
than calling str() which introduces some lookup and call overhead.

In an exception path, I don't think this matters; however generally I would
say we should avoid
additional function calls if they're not needed.

Andrew Jorgensen (ajorgens) wrote :

> In an exception path, I don't think this matters;

Do I take this to mean it's okay as it is now, and you approve? Or would you like me to undo the change that Scott suggested before it gets merged?

Ryan Harper (raharper) wrote :

On Wed, Oct 4, 2017 at 9:28 AM, Andrew Jorgensen <email address hidden>
wrote:

> > In an exception path, I don't think this matters;
>
> Do I take this to mean it's okay as it is now, and you approve? Or would
> you like me to undo the change that Scott suggested before it gets merged?
>

That was a comment for Scott; I'm find with his suggested change; I just
wanted to comment on the general case w.r.t
str() vs. %s formatting overhead.

> --
> https://code.launchpad.net/~ajorgens/cloud-init/+git/
> cloud-init/+merge/331660
> Your team cloud-init commiters is requested to review the proposed merge
> of ~ajorgens/cloud-init:_include-urlerror into cloud-init:master.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~cloud-init-dev
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~cloud-init-dev
> More help : https://help.launchpad.net/ListHelp
>

Scott Moser (smoser) wrote :

@Ryan,

str(foo) == '%s' % foo.

In my comments i was not suggessting that we should use 'str(foo)', but just that the we should print the string representation of the error rather than error.cause.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
2index 88cb7f8..e163c72 100644
3--- a/cloudinit/user_data.py
4+++ b/cloudinit/user_data.py
5@@ -19,6 +19,7 @@ import six
6
7 from cloudinit import handlers
8 from cloudinit import log as logging
9+from cloudinit.url_helper import UrlError
10 from cloudinit import util
11
12 LOG = logging.getLogger(__name__)
13@@ -222,16 +223,23 @@ class UserDataProcessor(object):
14 if include_once_on and os.path.isfile(include_once_fn):
15 content = util.load_file(include_once_fn)
16 else:
17- resp = util.read_file_or_url(include_url,
18- ssl_details=self.ssl_details)
19- if include_once_on and resp.ok():
20- util.write_file(include_once_fn, resp.contents, mode=0o600)
21- if resp.ok():
22- content = resp.contents
23- else:
24- LOG.warning(("Fetching from %s resulted in"
25- " a invalid http code of %s"),
26- include_url, resp.code)
27+ try:
28+ resp = util.read_file_or_url(include_url,
29+ ssl_details=self.ssl_details)
30+ if include_once_on and resp.ok():
31+ util.write_file(include_once_fn, resp.contents,
32+ mode=0o600)
33+ if resp.ok():
34+ content = resp.contents
35+ else:
36+ LOG.warning(("Fetching from %s resulted in"
37+ " a invalid http code of %s"),
38+ include_url, resp.code)
39+ except UrlError as urle:
40+ LOG.warning(urle)
41+ except IOError as ioe:
42+ LOG.warning("Fetching from %s resulted in %s",
43+ include_url, ioe)
44
45 if content is not None:
46 new_msg = convert_string(content)
47diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py
48index 6d621d2..275b16d 100644
49--- a/tests/unittests/test_data.py
50+++ b/tests/unittests/test_data.py
51@@ -18,6 +18,8 @@ from email.mime.application import MIMEApplication
52 from email.mime.base import MIMEBase
53 from email.mime.multipart import MIMEMultipart
54
55+import httpretty
56+
57 from cloudinit import handlers
58 from cloudinit import helpers as c_helpers
59 from cloudinit import log
60@@ -522,6 +524,54 @@ c: 4
61 self.assertEqual(cfg.get('password'), 'gocubs')
62 self.assertEqual(cfg.get('locale'), 'chicago')
63
64+ @httpretty.activate
65+ @mock.patch('cloudinit.url_helper.time.sleep')
66+ def test_include(self, mock_sleep):
67+ """Test #include."""
68+ included_url = 'http://hostname/path'
69+ included_data = '#cloud-config\nincluded: true\n'
70+ httpretty.register_uri(httpretty.GET, included_url, included_data)
71+
72+ blob = '#include\n%s\n' % included_url
73+
74+ self.reRoot()
75+ ci = stages.Init()
76+ ci.datasource = FakeDataSource(blob)
77+ ci.fetch()
78+ ci.consume_data()
79+ cc_contents = util.load_file(ci.paths.get_ipath("cloud_config"))
80+ cc = util.load_yaml(cc_contents)
81+ self.assertTrue(cc.get('included'))
82+
83+ @httpretty.activate
84+ @mock.patch('cloudinit.url_helper.time.sleep')
85+ def test_include_bad_url(self, mock_sleep):
86+ """Test #include with a bad URL."""
87+ bad_url = 'http://bad/forbidden'
88+ bad_data = '#cloud-config\nbad: true\n'
89+ httpretty.register_uri(httpretty.GET, bad_url, bad_data, status=403)
90+
91+ included_url = 'http://hostname/path'
92+ included_data = '#cloud-config\nincluded: true\n'
93+ httpretty.register_uri(httpretty.GET, included_url, included_data)
94+
95+ blob = '#include\n%s\n%s' % (bad_url, included_url)
96+
97+ self.reRoot()
98+ ci = stages.Init()
99+ ci.datasource = FakeDataSource(blob)
100+ log_file = self.capture_log(logging.WARNING)
101+ ci.fetch()
102+ ci.consume_data()
103+
104+ self.assertIn("403 Client Error: Forbidden for url: %s" % bad_url,
105+ log_file.getvalue())
106+
107+ cc_contents = util.load_file(ci.paths.get_ipath("cloud_config"))
108+ cc = util.load_yaml(cc_contents)
109+ self.assertIsNone(cc.get('bad'))
110+ self.assertTrue(cc.get('included'))
111+
112
113 class TestUDProcess(helpers.ResourceUsingTestCase):
114

Subscribers

People subscribed via source and target branches