Merge lp:~milner/cloud-init/protect-from-bad-part-handlers into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Mike Milner
Status: Merged
Merged at revision: 528
Proposed branch: lp:~milner/cloud-init/protect-from-bad-part-handlers
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 250 lines (+204/-8)
2 files modified
cloudinit/__init__.py (+9/-8)
tests/unittests/test__init__.py (+195/-0)
To merge this branch: bzr merge lp:~milner/cloud-init/protect-from-bad-part-handlers
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+94042@code.launchpad.net

Description of the change

Catch exceptions from part-handlers and log the error before continuing.
This branch also adds tests for part-handler registration and part-handler handling.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cloudinit/__init__.py'
--- cloudinit/__init__.py 2012-02-16 18:31:19 +0000
+++ cloudinit/__init__.py 2012-02-21 19:55:32 +0000
@@ -60,7 +60,6 @@
60import sys60import sys
61import os.path61import os.path
62import errno62import errno
63import pwd
64import subprocess63import subprocess
65import yaml64import yaml
66import logging65import logging
@@ -572,10 +571,14 @@
572 if not (modfreq == per_always or571 if not (modfreq == per_always or
573 (frequency == per_instance and modfreq == per_instance)):572 (frequency == per_instance and modfreq == per_instance)):
574 return573 return
575 if mod.handler_version == 1:574 try:
576 mod.handle_part(data, ctype, filename, payload)575 if mod.handler_version == 1:
577 else:576 mod.handle_part(data, ctype, filename, payload)
578 mod.handle_part(data, ctype, filename, payload, frequency)577 else:
578 mod.handle_part(data, ctype, filename, payload, frequency)
579 except:
580 util.logexc(log)
581 traceback.print_exc(file=sys.stderr)
579582
580583
581def partwalker_handle_handler(pdata, _ctype, _filename, payload):584def partwalker_handle_handler(pdata, _ctype, _filename, payload):
@@ -586,15 +589,13 @@
586 modfname = modname + ".py"589 modfname = modname + ".py"
587 util.write_file("%s/%s" % (pdata['handlerdir'], modfname), payload, 0600)590 util.write_file("%s/%s" % (pdata['handlerdir'], modfname), payload, 0600)
588591
589 pdata['handlercount'] = curcount + 1
590
591 try:592 try:
592 mod = __import__(modname)593 mod = __import__(modname)
593 handler_register(mod, pdata['handlers'], pdata['data'], frequency)594 handler_register(mod, pdata['handlers'], pdata['data'], frequency)
595 pdata['handlercount'] = curcount + 1
594 except:596 except:
595 util.logexc(log)597 util.logexc(log)
596 traceback.print_exc(file=sys.stderr)598 traceback.print_exc(file=sys.stderr)
597 return
598599
599600
600def partwalker_callback(pdata, ctype, filename, payload):601def partwalker_callback(pdata, ctype, filename, payload):
601602
=== added file 'tests/unittests/test__init__.py'
--- tests/unittests/test__init__.py 1970-01-01 00:00:00 +0000
+++ tests/unittests/test__init__.py 2012-02-21 19:55:32 +0000
@@ -0,0 +1,195 @@
1from mocker import MockerTestCase, ANY, ARGS, KWARGS
2import os
3
4from cloudinit import (partwalker_handle_handler, handler_handle_part,
5 handler_register)
6from cloudinit.util import write_file, logexc
7
8
9class TestPartwalkerHandleHandler(MockerTestCase):
10 def setUp(self):
11 self.data = {
12 "handlercount": 0,
13 "frequency": "?",
14 "handlerdir": "?",
15 "handlers": [],
16 "data": None}
17
18 self.expected_module_name = "part-handler-%03d" % (
19 self.data["handlercount"],)
20 expected_file_name = "%s.py" % self.expected_module_name
21 expected_file_fullname = os.path.join(self.data["handlerdir"],
22 expected_file_name)
23 self.module_fake = "fake module handle"
24 self.ctype = None
25 self.filename = None
26 self.payload = "dummy payload"
27
28 # Mock the write_file function
29 write_file_mock = self.mocker.replace(write_file, passthrough=False)
30 write_file_mock(expected_file_fullname, self.payload, 0600)
31
32 def test_no_errors(self):
33 """Payload gets written to file and added to C{pdata}."""
34 # Mock the __import__ builtin
35 import_mock = self.mocker.replace("__builtin__.__import__")
36 import_mock(self.expected_module_name)
37 self.mocker.result(self.module_fake)
38 # Mock the handle_register function
39 handle_reg_mock = self.mocker.replace(handler_register,
40 passthrough=False)
41 handle_reg_mock(self.module_fake, self.data["handlers"],
42 self.data["data"], self.data["frequency"])
43 # Activate mocks
44 self.mocker.replay()
45
46 partwalker_handle_handler(self.data, self.ctype, self.filename,
47 self.payload)
48
49 self.assertEqual(1, self.data["handlercount"])
50
51 def test_import_error(self):
52 """Module import errors are logged. No handler added to C{pdata}"""
53 # Mock the __import__ builtin
54 import_mock = self.mocker.replace("__builtin__.__import__")
55 import_mock(self.expected_module_name)
56 self.mocker.throw(ImportError())
57 # Mock log function
58 logexc_mock = self.mocker.replace(logexc, passthrough=False)
59 logexc_mock(ANY)
60 # Mock the print_exc function
61 print_exc_mock = self.mocker.replace("traceback.print_exc",
62 passthrough=False)
63 print_exc_mock(ARGS, KWARGS)
64 # Activate mocks
65 self.mocker.replay()
66
67 partwalker_handle_handler(self.data, self.ctype, self.filename,
68 self.payload)
69
70 self.assertEqual(0, self.data["handlercount"])
71
72 def test_attribute_error(self):
73 """Attribute errors are logged. No handler added to C{pdata}"""
74 # Mock the __import__ builtin
75 import_mock = self.mocker.replace("__builtin__.__import__")
76 import_mock(self.expected_module_name)
77 self.mocker.result(self.module_fake)
78 # Mock the handle_register function
79 handle_reg_mock = self.mocker.replace(handler_register,
80 passthrough=False)
81 handle_reg_mock(self.module_fake, self.data["handlers"],
82 self.data["data"], self.data["frequency"])
83 self.mocker.throw(AttributeError())
84 # Mock log function
85 logexc_mock = self.mocker.replace(logexc, passthrough=False)
86 logexc_mock(ANY)
87 # Mock the print_exc function
88 print_exc_mock = self.mocker.replace("traceback.print_exc",
89 passthrough=False)
90 print_exc_mock(ARGS, KWARGS)
91 # Activate mocks
92 self.mocker.replay()
93
94 partwalker_handle_handler(self.data, self.ctype, self.filename,
95 self.payload)
96
97 self.assertEqual(0, self.data["handlercount"])
98
99
100class TestHandlerHandlePart(MockerTestCase):
101 def setUp(self):
102 self.data = "fake data"
103 self.ctype = "fake ctype"
104 self.filename = "fake filename"
105 self.payload = "fake payload"
106 self.frequency = "once-per-instance"
107
108 def test_normal_version_1(self):
109 """
110 C{handle_part} is called without C{frequency} for
111 C{handler_version} == 1.
112 """
113 # Build a mock part-handler module
114 mod_mock = self.mocker.mock()
115 getattr(mod_mock, "frequency")
116 self.mocker.result("once-per-instance")
117 getattr(mod_mock, "handler_version")
118 self.mocker.result(1)
119 mod_mock.handle_part(self.data, self.ctype, self.filename,
120 self.payload)
121 self.mocker.replay()
122
123 handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
124 self.payload, self.frequency)
125
126 def test_normal_version_2(self):
127 """
128 C{handle_part} is called with C{frequency} for
129 C{handler_version} == 2.
130 """
131 # Build a mock part-handler module
132 mod_mock = self.mocker.mock()
133 getattr(mod_mock, "frequency")
134 self.mocker.result("once-per-instance")
135 getattr(mod_mock, "handler_version")
136 self.mocker.result(2)
137 mod_mock.handle_part(self.data, self.ctype, self.filename,
138 self.payload, self.frequency)
139 self.mocker.replay()
140
141 handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
142 self.payload, self.frequency)
143
144 def test_modfreq_per_always(self):
145 """
146 C{handle_part} is called regardless of frequency if nofreq is always.
147 """
148 self.frequency = "once"
149 # Build a mock part-handler module
150 mod_mock = self.mocker.mock()
151 getattr(mod_mock, "frequency")
152 self.mocker.result("always")
153 getattr(mod_mock, "handler_version")
154 self.mocker.result(1)
155 mod_mock.handle_part(self.data, self.ctype, self.filename,
156 self.payload)
157 self.mocker.replay()
158
159 handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
160 self.payload, self.frequency)
161
162 def test_no_handle_when_modfreq_once(self):
163 """C{handle_part} is not called if frequency is once"""
164 self.frequency = "once"
165 # Build a mock part-handler module
166 mod_mock = self.mocker.mock()
167 getattr(mod_mock, "frequency")
168 self.mocker.result("once-per-instance")
169 self.mocker.replay()
170
171 handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
172 self.payload, self.frequency)
173
174 def test_exception_is_caught(self):
175 """Exceptions within C{handle_part} are caught and logged."""
176 # Build a mock part-handler module
177 mod_mock = self.mocker.mock()
178 getattr(mod_mock, "frequency")
179 self.mocker.result("once-per-instance")
180 getattr(mod_mock, "handler_version")
181 self.mocker.result(1)
182 mod_mock.handle_part(self.data, self.ctype, self.filename,
183 self.payload)
184 self.mocker.throw(Exception())
185 # Mock log function
186 logexc_mock = self.mocker.replace(logexc, passthrough=False)
187 logexc_mock(ANY)
188 # Mock the print_exc function
189 print_exc_mock = self.mocker.replace("traceback.print_exc",
190 passthrough=False)
191 print_exc_mock(ARGS, KWARGS)
192 self.mocker.replay()
193
194 handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
195 self.payload, self.frequency)
0196
=== added directory 'tests/unittests/test_handler'
=== renamed file 'tests/unittests/test_handler_ca_certs.py' => 'tests/unittests/test_handler/test_handler_ca_certs.py'