diff --git a/cloudinit/registry.py b/cloudinit/registry.py index 46cf0585..04368ddf 100644 --- a/cloudinit/registry.py +++ b/cloudinit/registry.py @@ -1,3 +1,7 @@ +# Copyright 2015 Canonical Ltd. +# This file is part of cloud-init. See LICENCE file for license information. +# +# vi: ts=4 expandtab import copy @@ -5,6 +9,9 @@ class DictRegistry(object): """A simple registry for a mapping of objects.""" def __init__(self): + self.reset() + + def reset(self): self._items = {} def register_item(self, key, item): @@ -14,6 +21,13 @@ class DictRegistry(object): 'Item already registered with key {0}'.format(key)) self._items[key] = item + def unregister_item(self, key, force=True): + """Remove item from the registry.""" + if key in self._items: + del self._items[key] + elif not force: + raise KeyError("%s: key not present to unregister" % key) + @property def registered_items(self): """All the items that have been registered. diff --git a/cloudinit/reporting/__init__.py b/cloudinit/reporting/__init__.py index 2b92ab58..d0bc14e3 100644 --- a/cloudinit/reporting/__init__.py +++ b/cloudinit/reporting/__init__.py @@ -20,8 +20,6 @@ DEFAULT_CONFIG = { 'logging': {'type': 'log'}, } -instantiated_handler_registry = DictRegistry() - class _nameset(set): def __getattr__(self, name): @@ -46,6 +44,11 @@ class ReportingEvent(object): return '{0}: {1}: {2}'.format( self.event_type, self.name, self.description) + def as_dict(self): + """The event represented as a dictionary.""" + return {'name': self.name, 'description': self.description, + 'event_type': self.event_type} + class FinishReportingEvent(ReportingEvent): @@ -60,9 +63,26 @@ class FinishReportingEvent(ReportingEvent): return '{0}: {1}: {2}: {3}'.format( self.event_type, self.name, self.result, self.description) + def as_dict(self): + """The event represented as json friendly.""" + data = super(FinishReportingEvent, self).as_dict() + data['result'] = self.result + return data -def add_configuration(config): + +def update_configuration(config): + """Update the instanciated_handler_registry. + + :param config: + The dictionary containing changes to apply. If a key is given + with a False-ish value, the registered handler matching that name + will be unregistered. + """ for handler_name, handler_config in config.items(): + if not handler_config: + instantiated_handler_registry.unregister_item( + handler_name, force=True) + continue handler_config = handler_config.copy() cls = available_handlers.registered_items[handler_config.pop('type')] instance = cls(**handler_config) @@ -214,4 +234,5 @@ class ReportEventStack(object): report_finish_event(self.fullname, msg, result) -add_configuration(DEFAULT_CONFIG) +instantiated_handler_registry = DictRegistry() +update_configuration(DEFAULT_CONFIG) diff --git a/cloudinit/tests/test_reporting.py b/cloudinit/tests/test_reporting.py index 25bc6442..922f5959 100644 --- a/cloudinit/tests/test_reporting.py +++ b/cloudinit/tests/test_reporting.py @@ -96,6 +96,23 @@ class TestReportingEvent(TestCase): [event_type, name, description]) self.assertEqual(expected_string_representation, event.as_string()) + def test_as_dict(self): + event_type, name, desc = 'test_type', 'test_name', 'test_desc' + event = reporting.ReportingEvent(event_type, name, desc) + self.assertEqual( + {'event_type': event_type, 'name': name, 'description': desc}, + event.as_dict()) + + +class TestFinishReportingEvent(TestCase): + def test_as_has_result(self): + result = reporting.status.SUCCESS + name, desc = 'test_name', 'test_desc' + event = reporting.FinishReportingEvent(name, desc, result) + ret = event.as_dict() + self.assertTrue('result' in ret) + self.assertEqual(ret['result'], result) + class TestBaseReportingHandler(TestCase): @@ -148,7 +165,7 @@ class TestReportingConfiguration(TestCase): @mock.patch.object(reporting, 'instantiated_handler_registry') def test_empty_configuration_doesnt_add_handlers( self, instantiated_handler_registry): - reporting.add_configuration({}) + reporting.update_configuration({}) self.assertEqual( 0, instantiated_handler_registry.register_item.call_count) @@ -160,7 +177,7 @@ class TestReportingConfiguration(TestCase): handler_cls = mock.Mock() available_handlers.registered_items = {handler_type_name: handler_cls} handler_name = 'my_test_handler' - reporting.add_configuration( + reporting.update_configuration( {handler_name: {'type': handler_type_name}}) self.assertEqual( {handler_name: handler_cls.return_value}, @@ -178,7 +195,7 @@ class TestReportingConfiguration(TestCase): handler_config = extra_kwargs.copy() handler_config.update({'type': handler_type_name}) handler_name = 'my_test_handler' - reporting.add_configuration({handler_name: handler_config}) + reporting.update_configuration({handler_name: handler_config}) self.assertEqual( handler_cls.return_value, reporting.instantiated_handler_registry.registered_items[ @@ -195,9 +212,25 @@ class TestReportingConfiguration(TestCase): 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}) + reporting.update_configuration({'my_test_handler': handler_config}) self.assertEqual(expected_handler_config, handler_config) + @mock.patch.object( + reporting, 'instantiated_handler_registry', reporting.DictRegistry()) + @mock.patch.object(reporting, 'available_handlers') + def test_handlers_removed_if_falseish_specified(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.update_configuration( + {handler_name: {'type': handler_type_name}}) + self.assertEqual( + 1, len(reporting.instantiated_handler_registry.registered_items)) + reporting.update_configuration({handler_name: None}) + self.assertEqual( + 0, len(reporting.instantiated_handler_registry.registered_items)) + class TestReportingEventStack(TestCase): @mock.patch('cloudinit.reporting.report_finish_event')