1
# Copyright 2015 Canonical Ltd.
2
# This file is part of cloud-init. See LICENCE file for license information.
6
from cloudinit import reporting
7
from cloudinit.reporting import events
8
from cloudinit.reporting import handlers
12
from .helpers import TestCase
16
return mock.Mock(registered_items={'a': mock.MagicMock(),
17
'b': mock.MagicMock()})
20
class TestReportStartEvent(TestCase):
22
@mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
23
new_callable=_fake_registry)
24
def test_report_start_event_passes_something_with_as_string_to_handlers(
25
self, instantiated_handler_registry):
26
event_name, event_description = 'my_test_event', 'my description'
27
events.report_start_event(event_name, event_description)
28
expected_string_representation = ': '.join(
29
['start', event_name, event_description])
31
instantiated_handler_registry.registered_items.items()):
32
self.assertEqual(1, handler.publish_event.call_count)
33
event = handler.publish_event.call_args[0][0]
34
self.assertEqual(expected_string_representation, event.as_string())
37
class TestReportFinishEvent(TestCase):
39
def _report_finish_event(self, result=events.status.SUCCESS):
40
event_name, event_description = 'my_test_event', 'my description'
41
events.report_finish_event(
42
event_name, event_description, result=result)
43
return event_name, event_description
45
def assertHandlersPassedObjectWithAsString(
46
self, handlers, expected_as_string):
47
for _, handler in handlers.items():
48
self.assertEqual(1, handler.publish_event.call_count)
49
event = handler.publish_event.call_args[0][0]
50
self.assertEqual(expected_as_string, event.as_string())
52
@mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
53
new_callable=_fake_registry)
54
def test_report_finish_event_passes_something_with_as_string_to_handlers(
55
self, instantiated_handler_registry):
56
event_name, event_description = self._report_finish_event()
57
expected_string_representation = ': '.join(
58
['finish', event_name, events.status.SUCCESS,
60
self.assertHandlersPassedObjectWithAsString(
61
instantiated_handler_registry.registered_items,
62
expected_string_representation)
64
@mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
65
new_callable=_fake_registry)
66
def test_reporting_successful_finish_has_sensible_string_repr(
67
self, instantiated_handler_registry):
68
event_name, event_description = self._report_finish_event(
69
result=events.status.SUCCESS)
70
expected_string_representation = ': '.join(
71
['finish', event_name, events.status.SUCCESS,
73
self.assertHandlersPassedObjectWithAsString(
74
instantiated_handler_registry.registered_items,
75
expected_string_representation)
77
@mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
78
new_callable=_fake_registry)
79
def test_reporting_unsuccessful_finish_has_sensible_string_repr(
80
self, instantiated_handler_registry):
81
event_name, event_description = self._report_finish_event(
82
result=events.status.FAIL)
83
expected_string_representation = ': '.join(
84
['finish', event_name, events.status.FAIL, event_description])
85
self.assertHandlersPassedObjectWithAsString(
86
instantiated_handler_registry.registered_items,
87
expected_string_representation)
89
def test_invalid_result_raises_attribute_error(self):
90
self.assertRaises(ValueError, self._report_finish_event, ("BOGUS",))
93
class TestReportingEvent(TestCase):
95
def test_as_string(self):
96
event_type, name, description = 'test_type', 'test_name', 'test_desc'
97
event = events.ReportingEvent(event_type, name, description)
98
expected_string_representation = ': '.join(
99
[event_type, name, description])
100
self.assertEqual(expected_string_representation, event.as_string())
102
def test_as_dict(self):
103
event_type, name, desc = 'test_type', 'test_name', 'test_desc'
104
event = events.ReportingEvent(event_type, name, desc)
105
expected = {'event_type': event_type, 'name': name,
106
'description': desc, 'origin': 'cloudinit'}
108
# allow for timestamp to differ, but must be present
109
as_dict = event.as_dict()
110
self.assertIn('timestamp', as_dict)
111
del as_dict['timestamp']
113
self.assertEqual(expected, as_dict)
116
class TestFinishReportingEvent(TestCase):
117
def test_as_has_result(self):
118
result = events.status.SUCCESS
119
name, desc = 'test_name', 'test_desc'
120
event = events.FinishReportingEvent(name, desc, result)
121
ret = event.as_dict()
122
self.assertTrue('result' in ret)
123
self.assertEqual(ret['result'], result)
126
class TestBaseReportingHandler(TestCase):
128
def test_base_reporting_handler_is_abstract(self):
129
regexp = r".*abstract.*publish_event.*"
130
self.assertRaisesRegexp(TypeError, regexp, handlers.ReportingHandler)
133
class TestLogHandler(TestCase):
135
@mock.patch.object(reporting.handlers.logging, 'getLogger')
136
def test_appropriate_logger_used(self, getLogger):
137
event_type, event_name = 'test_type', 'test_name'
138
event = events.ReportingEvent(event_type, event_name, 'description')
139
reporting.handlers.LogHandler().publish_event(event)
142
'cloudinit.reporting.{0}.{1}'.format(event_type, event_name))],
143
getLogger.call_args_list)
145
@mock.patch.object(reporting.handlers.logging, 'getLogger')
146
def test_single_log_message_at_info_published(self, getLogger):
147
event = events.ReportingEvent('type', 'name', 'description')
148
reporting.handlers.LogHandler().publish_event(event)
149
self.assertEqual(1, getLogger.return_value.log.call_count)
151
@mock.patch.object(reporting.handlers.logging, 'getLogger')
152
def test_log_message_uses_event_as_string(self, getLogger):
153
event = events.ReportingEvent('type', 'name', 'description')
154
reporting.handlers.LogHandler(level="INFO").publish_event(event)
155
self.assertIn(event.as_string(),
156
getLogger.return_value.log.call_args[0][1])
159
class TestDefaultRegisteredHandler(TestCase):
161
def test_log_handler_registered_by_default(self):
163
reporting.instantiated_handler_registry.registered_items)
164
for _, item in registered_items.items():
165
if isinstance(item, reporting.handlers.LogHandler):
168
self.fail('No reporting LogHandler registered by default.')
171
class TestReportingConfiguration(TestCase):
173
@mock.patch.object(reporting, 'instantiated_handler_registry')
174
def test_empty_configuration_doesnt_add_handlers(
175
self, instantiated_handler_registry):
176
reporting.update_configuration({})
178
0, instantiated_handler_registry.register_item.call_count)
181
reporting, 'instantiated_handler_registry', reporting.DictRegistry())
182
@mock.patch.object(reporting, 'available_handlers')
183
def test_looks_up_handler_by_type_and_adds_it(self, available_handlers):
184
handler_type_name = 'test_handler'
185
handler_cls = mock.Mock()
186
available_handlers.registered_items = {handler_type_name: handler_cls}
187
handler_name = 'my_test_handler'
188
reporting.update_configuration(
189
{handler_name: {'type': handler_type_name}})
191
{handler_name: handler_cls.return_value},
192
reporting.instantiated_handler_registry.registered_items)
195
reporting, 'instantiated_handler_registry', reporting.DictRegistry())
196
@mock.patch.object(reporting, 'available_handlers')
197
def test_uses_non_type_parts_of_config_dict_as_kwargs(
198
self, available_handlers):
199
handler_type_name = 'test_handler'
200
handler_cls = mock.Mock()
201
available_handlers.registered_items = {handler_type_name: handler_cls}
202
extra_kwargs = {'foo': 'bar', 'bar': 'baz'}
203
handler_config = extra_kwargs.copy()
204
handler_config.update({'type': handler_type_name})
205
handler_name = 'my_test_handler'
206
reporting.update_configuration({handler_name: handler_config})
208
handler_cls.return_value,
209
reporting.instantiated_handler_registry.registered_items[
211
self.assertEqual([mock.call(**extra_kwargs)],
212
handler_cls.call_args_list)
215
reporting, 'instantiated_handler_registry', reporting.DictRegistry())
216
@mock.patch.object(reporting, 'available_handlers')
217
def test_handler_config_not_modified(self, available_handlers):
218
handler_type_name = 'test_handler'
219
handler_cls = mock.Mock()
220
available_handlers.registered_items = {handler_type_name: handler_cls}
221
handler_config = {'type': handler_type_name, 'foo': 'bar'}
222
expected_handler_config = handler_config.copy()
223
reporting.update_configuration({'my_test_handler': handler_config})
224
self.assertEqual(expected_handler_config, handler_config)
227
reporting, 'instantiated_handler_registry', reporting.DictRegistry())
228
@mock.patch.object(reporting, 'available_handlers')
229
def test_handlers_removed_if_falseish_specified(self, available_handlers):
230
handler_type_name = 'test_handler'
231
handler_cls = mock.Mock()
232
available_handlers.registered_items = {handler_type_name: handler_cls}
233
handler_name = 'my_test_handler'
234
reporting.update_configuration(
235
{handler_name: {'type': handler_type_name}})
237
1, len(reporting.instantiated_handler_registry.registered_items))
238
reporting.update_configuration({handler_name: None})
240
0, len(reporting.instantiated_handler_registry.registered_items))
243
class TestReportingEventStack(TestCase):
244
@mock.patch('cloudinit.reporting.events.report_finish_event')
245
@mock.patch('cloudinit.reporting.events.report_start_event')
246
def test_start_and_finish_success(self, report_start, report_finish):
247
with events.ReportEventStack(name="myname", description="mydesc"):
250
[mock.call('myname', 'mydesc')], report_start.call_args_list)
252
[mock.call('myname', 'mydesc', events.status.SUCCESS,
254
report_finish.call_args_list)
256
@mock.patch('cloudinit.reporting.events.report_finish_event')
257
@mock.patch('cloudinit.reporting.events.report_start_event')
258
def test_finish_exception_defaults_fail(self, report_start, report_finish):
262
with events.ReportEventStack(name, description=desc):
263
raise ValueError("This didnt work")
266
self.assertEqual([mock.call(name, desc)], report_start.call_args_list)
268
[mock.call(name, desc, events.status.FAIL, post_files=[])],
269
report_finish.call_args_list)
271
@mock.patch('cloudinit.reporting.events.report_finish_event')
272
@mock.patch('cloudinit.reporting.events.report_start_event')
273
def test_result_on_exception_used(self, report_start, report_finish):
277
with events.ReportEventStack(
278
name, desc, result_on_exception=events.status.WARN):
279
raise ValueError("This didnt work")
282
self.assertEqual([mock.call(name, desc)], report_start.call_args_list)
284
[mock.call(name, desc, events.status.WARN, post_files=[])],
285
report_finish.call_args_list)
287
@mock.patch('cloudinit.reporting.events.report_start_event')
288
def test_child_fullname_respects_parent(self, report_start):
289
parent_name = "topname"
292
c2_expected_fullname = '/'.join([parent_name, c1_name, c2_name])
293
c1_expected_fullname = '/'.join([parent_name, c1_name])
295
parent = events.ReportEventStack(parent_name, "topdesc")
296
c1 = events.ReportEventStack(c1_name, "c1desc", parent=parent)
297
c2 = events.ReportEventStack(c2_name, "c2desc", parent=c1)
299
report_start.assert_called_with(c1_expected_fullname, "c1desc")
301
report_start.assert_called_with(c2_expected_fullname, "c2desc")
303
@mock.patch('cloudinit.reporting.events.report_finish_event')
304
@mock.patch('cloudinit.reporting.events.report_start_event')
305
def test_child_result_bubbles_up(self, report_start, report_finish):
306
parent = events.ReportEventStack("topname", "topdesc")
307
child = events.ReportEventStack("c_name", "c_desc", parent=parent)
310
child.result = events.status.WARN
312
report_finish.assert_called_with(
313
"topname", "topdesc", events.status.WARN, post_files=[])
315
@mock.patch('cloudinit.reporting.events.report_finish_event')
316
def test_message_used_in_finish(self, report_finish):
317
with events.ReportEventStack("myname", "mydesc",
318
message="mymessage"):
321
[mock.call("myname", "mymessage", events.status.SUCCESS,
323
report_finish.call_args_list)
325
@mock.patch('cloudinit.reporting.events.report_finish_event')
326
def test_message_updatable(self, report_finish):
327
with events.ReportEventStack("myname", "mydesc") as c:
328
c.message = "all good"
330
[mock.call("myname", "all good", events.status.SUCCESS,
332
report_finish.call_args_list)
334
@mock.patch('cloudinit.reporting.events.report_start_event')
335
@mock.patch('cloudinit.reporting.events.report_finish_event')
336
def test_reporting_disabled_does_not_report_events(
337
self, report_start, report_finish):
338
with events.ReportEventStack("a", "b", reporting_enabled=False):
340
self.assertEqual(report_start.call_count, 0)
341
self.assertEqual(report_finish.call_count, 0)
343
@mock.patch('cloudinit.reporting.events.report_start_event')
344
@mock.patch('cloudinit.reporting.events.report_finish_event')
345
def test_reporting_child_default_to_parent(
346
self, report_start, report_finish):
347
parent = events.ReportEventStack(
348
"pname", "pdesc", reporting_enabled=False)
349
child = events.ReportEventStack("cname", "cdesc", parent=parent)
354
self.assertEqual(report_start.call_count, 0)
355
self.assertEqual(report_finish.call_count, 0)
357
def test_reporting_event_has_sane_repr(self):
358
myrep = events.ReportEventStack("fooname", "foodesc",
359
reporting_enabled=True).__repr__()
360
self.assertIn("fooname", myrep)
361
self.assertIn("foodesc", myrep)
362
self.assertIn("True", myrep)
364
def test_set_invalid_result_raises_value_error(self):
365
f = events.ReportEventStack("myname", "mydesc")
366
self.assertRaises(ValueError, setattr, f, "result", "BOGUS")
369
class TestStatusAccess(TestCase):
370
def test_invalid_status_access_raises_value_error(self):
371
self.assertRaises(AttributeError, getattr, events.status, "BOGUS")