Init scripts for use on cloud images

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

Proposed by Mike Milner on 2012-02-21
Status: Merged
Merged at revision: 528
Proposed branch: lp:~milner/cloud-init/protect-from-bad-part-handlers
Merge into: lp:cloud-init
Diff against target: 250 lines (+204/-8) 2 files modified
To merge this branch: bzr merge lp:~milner/cloud-init/protect-from-bad-part-handlers
Reviewer Review Type Date Requested Status
cloud init development team 2012-02-21 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

1=== modified file 'cloudinit/__init__.py'
2--- cloudinit/__init__.py 2012-02-16 18:31:19 +0000
3+++ cloudinit/__init__.py 2012-02-21 19:55:32 +0000
4@@ -60,7 +60,6 @@
5 import sys
6 import os.path
7 import errno
8-import pwd
9 import subprocess
10 import yaml
11 import logging
12@@ -572,10 +571,14 @@
13 if not (modfreq == per_always or
14 (frequency == per_instance and modfreq == per_instance)):
15 return
16- if mod.handler_version == 1:
17- mod.handle_part(data, ctype, filename, payload)
18- else:
19- mod.handle_part(data, ctype, filename, payload, frequency)
20+ try:
21+ if mod.handler_version == 1:
22+ mod.handle_part(data, ctype, filename, payload)
23+ else:
24+ mod.handle_part(data, ctype, filename, payload, frequency)
25+ except:
26+ util.logexc(log)
27+ traceback.print_exc(file=sys.stderr)
28
29
30 def partwalker_handle_handler(pdata, _ctype, _filename, payload):
31@@ -586,15 +589,13 @@
32 modfname = modname + ".py"
33 util.write_file("%s/%s" % (pdata['handlerdir'], modfname), payload, 0600)
34
35- pdata['handlercount'] = curcount + 1
36-
37 try:
38 mod = __import__(modname)
39 handler_register(mod, pdata['handlers'], pdata['data'], frequency)
40+ pdata['handlercount'] = curcount + 1
41 except:
42 util.logexc(log)
43 traceback.print_exc(file=sys.stderr)
44- return
45
46
47 def partwalker_callback(pdata, ctype, filename, payload):
48
49=== added file 'tests/unittests/test__init__.py'
50--- tests/unittests/test__init__.py 1970-01-01 00:00:00 +0000
51+++ tests/unittests/test__init__.py 2012-02-21 19:55:32 +0000
52@@ -0,0 +1,195 @@
53+from mocker import MockerTestCase, ANY, ARGS, KWARGS
54+import os
55+
56+from cloudinit import (partwalker_handle_handler, handler_handle_part,
57+ handler_register)
58+from cloudinit.util import write_file, logexc
59+
60+
61+class TestPartwalkerHandleHandler(MockerTestCase):
62+ def setUp(self):
63+ self.data = {
64+ "handlercount": 0,
65+ "frequency": "?",
66+ "handlerdir": "?",
67+ "handlers": [],
68+ "data": None}
69+
70+ self.expected_module_name = "part-handler-%03d" % (
71+ self.data["handlercount"],)
72+ expected_file_name = "%s.py" % self.expected_module_name
73+ expected_file_fullname = os.path.join(self.data["handlerdir"],
74+ expected_file_name)
75+ self.module_fake = "fake module handle"
76+ self.ctype = None
77+ self.filename = None
78+ self.payload = "dummy payload"
79+
80+ # Mock the write_file function
81+ write_file_mock = self.mocker.replace(write_file, passthrough=False)
82+ write_file_mock(expected_file_fullname, self.payload, 0600)
83+
84+ def test_no_errors(self):
85+ """Payload gets written to file and added to C{pdata}."""
86+ # Mock the __import__ builtin
87+ import_mock = self.mocker.replace("__builtin__.__import__")
88+ import_mock(self.expected_module_name)
89+ self.mocker.result(self.module_fake)
90+ # Mock the handle_register function
91+ handle_reg_mock = self.mocker.replace(handler_register,
92+ passthrough=False)
93+ handle_reg_mock(self.module_fake, self.data["handlers"],
94+ self.data["data"], self.data["frequency"])
95+ # Activate mocks
96+ self.mocker.replay()
97+
98+ partwalker_handle_handler(self.data, self.ctype, self.filename,
99+ self.payload)
100+
101+ self.assertEqual(1, self.data["handlercount"])
102+
103+ def test_import_error(self):
104+ """Module import errors are logged. No handler added to C{pdata}"""
105+ # Mock the __import__ builtin
106+ import_mock = self.mocker.replace("__builtin__.__import__")
107+ import_mock(self.expected_module_name)
108+ self.mocker.throw(ImportError())
109+ # Mock log function
110+ logexc_mock = self.mocker.replace(logexc, passthrough=False)
111+ logexc_mock(ANY)
112+ # Mock the print_exc function
113+ print_exc_mock = self.mocker.replace("traceback.print_exc",
114+ passthrough=False)
115+ print_exc_mock(ARGS, KWARGS)
116+ # Activate mocks
117+ self.mocker.replay()
118+
119+ partwalker_handle_handler(self.data, self.ctype, self.filename,
120+ self.payload)
121+
122+ self.assertEqual(0, self.data["handlercount"])
123+
124+ def test_attribute_error(self):
125+ """Attribute errors are logged. No handler added to C{pdata}"""
126+ # Mock the __import__ builtin
127+ import_mock = self.mocker.replace("__builtin__.__import__")
128+ import_mock(self.expected_module_name)
129+ self.mocker.result(self.module_fake)
130+ # Mock the handle_register function
131+ handle_reg_mock = self.mocker.replace(handler_register,
132+ passthrough=False)
133+ handle_reg_mock(self.module_fake, self.data["handlers"],
134+ self.data["data"], self.data["frequency"])
135+ self.mocker.throw(AttributeError())
136+ # Mock log function
137+ logexc_mock = self.mocker.replace(logexc, passthrough=False)
138+ logexc_mock(ANY)
139+ # Mock the print_exc function
140+ print_exc_mock = self.mocker.replace("traceback.print_exc",
141+ passthrough=False)
142+ print_exc_mock(ARGS, KWARGS)
143+ # Activate mocks
144+ self.mocker.replay()
145+
146+ partwalker_handle_handler(self.data, self.ctype, self.filename,
147+ self.payload)
148+
149+ self.assertEqual(0, self.data["handlercount"])
150+
151+
152+class TestHandlerHandlePart(MockerTestCase):
153+ def setUp(self):
154+ self.data = "fake data"
155+ self.ctype = "fake ctype"
156+ self.filename = "fake filename"
157+ self.payload = "fake payload"
158+ self.frequency = "once-per-instance"
159+
160+ def test_normal_version_1(self):
161+ """
162+ C{handle_part} is called without C{frequency} for
163+ C{handler_version} == 1.
164+ """
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+ getattr(mod_mock, "handler_version")
170+ self.mocker.result(1)
171+ mod_mock.handle_part(self.data, self.ctype, self.filename,
172+ self.payload)
173+ self.mocker.replay()
174+
175+ handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
176+ self.payload, self.frequency)
177+
178+ def test_normal_version_2(self):
179+ """
180+ C{handle_part} is called with C{frequency} for
181+ C{handler_version} == 2.
182+ """
183+ # Build a mock part-handler module
184+ mod_mock = self.mocker.mock()
185+ getattr(mod_mock, "frequency")
186+ self.mocker.result("once-per-instance")
187+ getattr(mod_mock, "handler_version")
188+ self.mocker.result(2)
189+ mod_mock.handle_part(self.data, self.ctype, self.filename,
190+ self.payload, self.frequency)
191+ self.mocker.replay()
192+
193+ handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
194+ self.payload, self.frequency)
195+
196+ def test_modfreq_per_always(self):
197+ """
198+ C{handle_part} is called regardless of frequency if nofreq is always.
199+ """
200+ self.frequency = "once"
201+ # Build a mock part-handler module
202+ mod_mock = self.mocker.mock()
203+ getattr(mod_mock, "frequency")
204+ self.mocker.result("always")
205+ getattr(mod_mock, "handler_version")
206+ self.mocker.result(1)
207+ mod_mock.handle_part(self.data, self.ctype, self.filename,
208+ self.payload)
209+ self.mocker.replay()
210+
211+ handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
212+ self.payload, self.frequency)
213+
214+ def test_no_handle_when_modfreq_once(self):
215+ """C{handle_part} is not called if frequency is once"""
216+ self.frequency = "once"
217+ # Build a mock part-handler module
218+ mod_mock = self.mocker.mock()
219+ getattr(mod_mock, "frequency")
220+ self.mocker.result("once-per-instance")
221+ self.mocker.replay()
222+
223+ handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
224+ self.payload, self.frequency)
225+
226+ def test_exception_is_caught(self):
227+ """Exceptions within C{handle_part} are caught and logged."""
228+ # Build a mock part-handler module
229+ mod_mock = self.mocker.mock()
230+ getattr(mod_mock, "frequency")
231+ self.mocker.result("once-per-instance")
232+ getattr(mod_mock, "handler_version")
233+ self.mocker.result(1)
234+ mod_mock.handle_part(self.data, self.ctype, self.filename,
235+ self.payload)
236+ self.mocker.throw(Exception())
237+ # Mock log function
238+ logexc_mock = self.mocker.replace(logexc, passthrough=False)
239+ logexc_mock(ANY)
240+ # Mock the print_exc function
241+ print_exc_mock = self.mocker.replace("traceback.print_exc",
242+ passthrough=False)
243+ print_exc_mock(ARGS, KWARGS)
244+ self.mocker.replay()
245+
246+ handler_handle_part(mod_mock, self.data, self.ctype, self.filename,
247+ self.payload, self.frequency)
248
249=== added directory 'tests/unittests/test_handler'
250=== renamed file 'tests/unittests/test_handler_ca_certs.py' => 'tests/unittests/test_handler/test_handler_ca_certs.py'