Merge ~wesley-wiedenmeier/cloud-init:error-output into cloud-init:master

Proposed by Wesley Wiedenmeier on 2016-09-26
Status: Merged
Merged at revision: 0fd1dd02c755cb75a73c04a59f70df1b87a0ed42
Proposed branch: ~wesley-wiedenmeier/cloud-init:error-output
Merge into: cloud-init:master
Diff against target: 171 lines (+105/-15)
2 files modified
cloudinit/util.py (+36/-15)
tests/unittests/test_util.py (+69/-0)
Reviewer Review Type Date Requested Status
Scott Moser 2016-09-26 Needs Fixing on 2016-10-25
Ryan Harper 2016-10-19 Pending
Review via email: mp+306731@code.launchpad.net

Description of the Change

Improve formatting for util.ProcessExecutionError and properly display multi line error messages. Add unittests for util.ProcessExecutionError.

old error formatting:
http://paste.ubuntu.com/23232468/

new error formatting:
http://paste.ubuntu.com/23232463/

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

Please rebase.

Ryan, will your log analyzer handle this change?

Ryan Harper (raharper) wrote :

On Wed, Oct 19, 2016 at 9:09 AM, Scott Moser <email address hidden> wrote:

> Please rebase.
>
> Ryan, will your log analyzer handle this change?
>

Yes, the analyzer is primarily interested in the event 'start' and 'finish'
messages; the log exceptions
aren't utilized for event tracking.

I'll confirm.

>
> --
> https://code.launchpad.net/~wesley-wiedenmeier/cloud-init/
> +git/cloud-init/+merge/306731
> You are requested to review the proposed merge of
> ~wesley-wiedenmeier/cloud-init:error-output into cloud-init:master.
>

I'll get this rebased later today, thanks.

78ab9b6... by Wesley Wiedenmeier on 2016-10-19

Merge branch 'error-output' of git+ssh://git.launchpad.net/~wesley-wiedenmeier/cloud-init into error-output

This should be able to merge cleanly now

Scott Moser (smoser) :
a0bc2ea... by Wesley Wiedenmeier on 2016-10-25

Better handling of string types in util.ProcessExecutionError

 - ProcessExecutionError._indent_text returns same type as input
 - before formatting message, decode bytes objects, if any

691e083... by Wesley Wiedenmeier on 2016-10-25

Better handling of string types in util.ProcessExecutionError

 - ProcessExecutionError._indent_text returns same type as input
 - before formatting message, decode bytes objects, if any

06eb297... by Wesley Wiedenmeier on 2016-10-25

Merge branch 'error-output' of git+ssh://git.launchpad.net/~wesley-wiedenmeier/cloud-init into error-output

Scott Moser (smoser) wrote :

this needs rebasing.
currently fails due to the now gone 'message' on the ProcessExecutionError in python3.

Scott Moser (smoser) :
review: Needs Fixing
d7f2a49... by Wesley Wiedenmeier on 2016-10-26

Remove duplicate created by rebase

aef16f1... by Wesley Wiedenmeier on 2016-10-26

Merge branch 'error-output' of git+ssh://git.launchpad.net/~wesley-wiedenmeier/cloud-init into error-output

5481ff8... by Wesley Wiedenmeier on 2016-10-26

Fix whitespace damage

I rebased again and fixed up the unittests. I think the rebase was correct this time.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/util.py b/cloudinit/util.py
2index 4b3fd0c..9c1a6fb 100644
3--- a/cloudinit/util.py
4+++ b/cloudinit/util.py
5@@ -235,15 +235,16 @@ class ProcessExecutionError(IOError):
6 'Command: %(cmd)s\n'
7 'Exit code: %(exit_code)s\n'
8 'Reason: %(reason)s\n'
9- 'Stdout: %(stdout)r\n'
10- 'Stderr: %(stderr)r')
11+ 'Stdout: %(stdout)s\n'
12+ 'Stderr: %(stderr)s')
13+ empty_attr = '-'
14
15 def __init__(self, stdout=None, stderr=None,
16 exit_code=None, cmd=None,
17 description=None, reason=None,
18 errno=None):
19 if not cmd:
20- self.cmd = '-'
21+ self.cmd = self.empty_attr
22 else:
23 self.cmd = cmd
24
25@@ -253,36 +254,56 @@ class ProcessExecutionError(IOError):
26 self.description = description
27
28 if not isinstance(exit_code, six.integer_types):
29- self.exit_code = '-'
30+ self.exit_code = self.empty_attr
31 else:
32 self.exit_code = exit_code
33
34 if not stderr:
35- self.stderr = ''
36+ self.stderr = self.empty_attr
37 else:
38- self.stderr = stderr
39+ self.stderr = self._indent_text(stderr)
40
41 if not stdout:
42- self.stdout = ''
43+ self.stdout = self.empty_attr
44 else:
45- self.stdout = stdout
46+ self.stdout = self._indent_text(stdout)
47
48 if reason:
49 self.reason = reason
50 else:
51- self.reason = '-'
52+ self.reason = self.empty_attr
53
54 self.errno = errno
55 message = self.MESSAGE_TMPL % {
56- 'description': self.description,
57- 'cmd': self.cmd,
58- 'exit_code': self.exit_code,
59- 'stdout': self.stdout,
60- 'stderr': self.stderr,
61- 'reason': self.reason,
62+ 'description': self._ensure_string(self.description),
63+ 'cmd': self._ensure_string(self.cmd),
64+ 'exit_code': self._ensure_string(self.exit_code),
65+ 'stdout': self._ensure_string(self.stdout),
66+ 'stderr': self._ensure_string(self.stderr),
67+ 'reason': self._ensure_string(self.reason),
68 }
69 IOError.__init__(self, message)
70
71+ def _ensure_string(self, text):
72+ """
73+ if data is bytes object, decode
74+ """
75+ return text.decode() if isinstance(text, six.binary_type) else text
76+
77+ def _indent_text(self, text, indent_level=8):
78+ """
79+ indent text on all but the first line, allowing for easy to read output
80+ """
81+ cr = '\n'
82+ indent = ' ' * indent_level
83+ # if input is bytes, return bytes
84+ if isinstance(text, six.binary_type):
85+ cr = cr.encode()
86+ indent = indent.encode()
87+ # remove any newlines at end of text first to prevent unneeded blank
88+ # line in output
89+ return text.rstrip(cr).replace(cr, cr + indent)
90+
91
92 class SeLinuxGuard(object):
93 def __init__(self, path, recursive=False):
94diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
95index 881509a..b671838 100644
96--- a/tests/unittests/test_util.py
97+++ b/tests/unittests/test_util.py
98@@ -611,4 +611,73 @@ class TestEncode(helpers.TestCase):
99 text = util.decode_binary(blob)
100 self.assertEqual(text, blob)
101
102+
103+class TestProcessExecutionError(helpers.TestCase):
104+
105+ template = ('{description}\n'
106+ 'Command: {cmd}\n'
107+ 'Exit code: {exit_code}\n'
108+ 'Reason: {reason}\n'
109+ 'Stdout: {stdout}\n'
110+ 'Stderr: {stderr}')
111+ empty_attr = '-'
112+ empty_description = 'Unexpected error while running command.'
113+
114+ def test_pexec_error_indent_text(self):
115+ error = util.ProcessExecutionError()
116+ msg = 'abc\ndef'
117+ formatted = 'abc\n{}def'.format(' ' * 4)
118+ self.assertEqual(error._indent_text(msg, indent_level=4), formatted)
119+ self.assertEqual(error._indent_text(msg.encode(), indent_level=4),
120+ formatted.encode())
121+ self.assertIsInstance(
122+ error._indent_text(msg.encode()), type(msg.encode()))
123+
124+ def test_pexec_error_type(self):
125+ self.assertIsInstance(util.ProcessExecutionError(), IOError)
126+
127+ def test_pexec_error_empty_msgs(self):
128+ error = util.ProcessExecutionError()
129+ self.assertTrue(all(attr == self.empty_attr for attr in
130+ (error.stderr, error.stdout, error.reason)))
131+ self.assertEqual(error.description, self.empty_description)
132+ self.assertEqual(str(error), self.template.format(
133+ description=self.empty_description, exit_code=self.empty_attr,
134+ reason=self.empty_attr, stdout=self.empty_attr,
135+ stderr=self.empty_attr, cmd=self.empty_attr))
136+
137+ def test_pexec_error_single_line_msgs(self):
138+ stdout_msg = 'out out'
139+ stderr_msg = 'error error'
140+ cmd = 'test command'
141+ exit_code = 3
142+ error = util.ProcessExecutionError(
143+ stdout=stdout_msg, stderr=stderr_msg, exit_code=3, cmd=cmd)
144+ self.assertEqual(str(error), self.template.format(
145+ description=self.empty_description, stdout=stdout_msg,
146+ stderr=stderr_msg, exit_code=str(exit_code),
147+ reason=self.empty_attr, cmd=cmd))
148+
149+ def test_pexec_error_multi_line_msgs(self):
150+ # make sure bytes is converted handled properly when formatting
151+ stdout_msg = 'multi\nline\noutput message'.encode()
152+ stderr_msg = 'multi\nline\nerror message\n\n\n'
153+ error = util.ProcessExecutionError(
154+ stdout=stdout_msg, stderr=stderr_msg)
155+ self.assertEqual(
156+ str(error),
157+ '\n'.join((
158+ '{description}',
159+ 'Command: {empty_attr}',
160+ 'Exit code: {empty_attr}',
161+ 'Reason: {empty_attr}',
162+ 'Stdout: multi',
163+ ' line',
164+ ' output message',
165+ 'Stderr: multi',
166+ ' line',
167+ ' error message',
168+ )).format(description=self.empty_description,
169+ empty_attr=self.empty_attr))
170+
171 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches