diff --git a/cloudinit/reporting.py b/cloudinit/reporting.py index 32f98bfe..d2dd4fec 100644 --- a/cloudinit/reporting.py +++ b/cloudinit/reporting.py @@ -18,6 +18,14 @@ from cloudinit.registry import DictRegistry FINISH_EVENT_TYPE = 'finish' START_EVENT_TYPE = 'start' +DEFAULT_CONFIG = { + 'logging': {'type': 'log'}, +} + + +instantiated_handler_registry = DictRegistry() +available_handlers = DictRegistry() + class ReportingEvent(object): """Encapsulation of event formatting.""" @@ -65,8 +73,12 @@ class LogHandler(ReportingHandler): logger.info(event.as_string()) -handler_registry = DictRegistry() -handler_registry.register_item('_logging', LogHandler()) +def add_configuration(config): + for handler_name, handler_config in config.items(): + handler_config = handler_config.copy() + cls = available_handlers.registered_items[handler_config.pop('type')] + instance = cls(**handler_config) + instantiated_handler_registry.register_item(handler_name, instance) def report_event(event): @@ -79,7 +91,7 @@ def report_event(event): The type of the event; this should be a constant from the reporting module. """ - for _, handler in handler_registry.registered_items.items(): + for _, handler in instantiated_handler_registry.registered_items.items(): handler.publish_event(event) @@ -104,3 +116,7 @@ def report_start_event(event_name, event_description): """ event = ReportingEvent(START_EVENT_TYPE, event_name, event_description) return report_event(event) + + +available_handlers.register_item('log', LogHandler) +add_configuration(DEFAULT_CONFIG) diff --git a/cloudinit/tests/test_reporting.py b/cloudinit/tests/test_reporting.py index a297ccdf..e70b9bd0 100644 --- a/cloudinit/tests/test_reporting.py +++ b/cloudinit/tests/test_reporting.py @@ -17,15 +17,16 @@ def _fake_registry(): class TestReportStartEvent(unittest.TestCase): - @mock.patch('cloudinit.reporting.handler_registry', + @mock.patch('cloudinit.reporting.instantiated_handler_registry', new_callable=_fake_registry) def test_report_start_event_passes_something_with_as_string_to_handlers( - self, handler_registry): + self, instantiated_handler_registry): event_name, event_description = 'my_test_event', 'my description' reporting.report_start_event(event_name, event_description) expected_string_representation = ': '.join( ['start', event_name, event_description]) - for _, handler in handler_registry.registered_items.items(): + for _, handler in ( + instantiated_handler_registry.registered_items.items()): self.assertEqual(1, handler.publish_event.call_count) event = handler.publish_event.call_args[0][0] self.assertEqual(expected_string_representation, event.as_string()) @@ -46,37 +47,40 @@ class TestReportFinishEvent(unittest.TestCase): event = handler.publish_event.call_args[0][0] self.assertEqual(expected_as_string, event.as_string()) - @mock.patch('cloudinit.reporting.handler_registry', + @mock.patch('cloudinit.reporting.instantiated_handler_registry', new_callable=_fake_registry) def test_report_finish_event_passes_something_with_as_string_to_handlers( - self, handler_registry): + self, instantiated_handler_registry): event_name, event_description = self._report_finish_event() expected_string_representation = ': '.join( ['finish', event_name, event_description]) self.assertHandlersPassedObjectWithAsString( - handler_registry.registered_items, expected_string_representation) + instantiated_handler_registry.registered_items, + expected_string_representation) - @mock.patch('cloudinit.reporting.handler_registry', + @mock.patch('cloudinit.reporting.instantiated_handler_registry', new_callable=_fake_registry) def test_reporting_successful_finish_has_sensible_string_repr( - self, handler_registry): + self, instantiated_handler_registry): event_name, event_description = self._report_finish_event( successful=True) expected_string_representation = ': '.join( ['finish', event_name, 'success', event_description]) self.assertHandlersPassedObjectWithAsString( - handler_registry.registered_items, expected_string_representation) + instantiated_handler_registry.registered_items, + expected_string_representation) - @mock.patch('cloudinit.reporting.handler_registry', + @mock.patch('cloudinit.reporting.instantiated_handler_registry', new_callable=_fake_registry) def test_reporting_unsuccessful_finish_has_sensible_string_repr( - self, handler_registry): + self, instantiated_handler_registry): event_name, event_description = self._report_finish_event( successful=False) expected_string_representation = ': '.join( ['finish', event_name, 'fail', event_description]) self.assertHandlersPassedObjectWithAsString( - handler_registry.registered_items, expected_string_representation) + instantiated_handler_registry.registered_items, + expected_string_representation) class TestReportingEvent(unittest.TestCase): @@ -125,8 +129,66 @@ class TestLogHandler(TestCase): class TestDefaultRegisteredHandler(TestCase): def test_log_handler_registered_by_default(self): - for _, item in reporting.handler_registry.registered_items.items(): + registered_items = ( + reporting.instantiated_handler_registry.registered_items) + for _, item in registered_items.items(): if isinstance(item, reporting.LogHandler): break else: self.fail('No reporting LogHandler registered by default.') + + +class TestReportingConfiguration(TestCase): + + @mock.patch.object(reporting, 'instantiated_handler_registry') + def test_empty_configuration_doesnt_add_handlers( + self, instantiated_handler_registry): + reporting.add_configuration({}) + self.assertEqual( + 0, instantiated_handler_registry.register_item.call_count) + + @mock.patch.object( + reporting, 'instantiated_handler_registry', reporting.DictRegistry()) + @mock.patch.object(reporting, 'available_handlers') + def test_looks_up_handler_by_type_and_adds_it(self, available_handlers): + handler_type_name = 'test_handler' + handler_cls = mock.Mock() + available_handlers.registered_items = {handler_type_name: handler_cls} + handler_name = 'my_test_handler' + reporting.add_configuration( + {handler_name: {'type': handler_type_name}}) + self.assertEqual( + {handler_name: handler_cls.return_value}, + reporting.instantiated_handler_registry.registered_items) + + @mock.patch.object( + reporting, 'instantiated_handler_registry', reporting.DictRegistry()) + @mock.patch.object(reporting, 'available_handlers') + def test_uses_non_type_parts_of_config_dict_as_kwargs( + self, available_handlers): + handler_type_name = 'test_handler' + handler_cls = mock.Mock() + available_handlers.registered_items = {handler_type_name: handler_cls} + extra_kwargs = {'foo': 'bar', 'bar': 'baz'} + handler_config = extra_kwargs.copy() + handler_config.update({'type': handler_type_name}) + handler_name = 'my_test_handler' + reporting.add_configuration({handler_name: handler_config}) + self.assertEqual( + handler_cls.return_value, + reporting.instantiated_handler_registry.registered_items[ + handler_name]) + self.assertEqual([mock.call(**extra_kwargs)], + handler_cls.call_args_list) + + @mock.patch.object( + reporting, 'instantiated_handler_registry', reporting.DictRegistry()) + @mock.patch.object(reporting, 'available_handlers') + def test_handler_config_not_modified(self, available_handlers): + handler_type_name = 'test_handler' + handler_cls = mock.Mock() + available_handlers.registered_items = {handler_type_name: handler_cls} + handler_config = {'type': handler_type_name, 'foo': 'bar'} + expected_handler_config = handler_config.copy() + reporting.add_configuration({'my_test_handler': handler_config}) + self.assertEqual(expected_handler_config, handler_config)