summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/registry.py14
-rw-r--r--cloudinit/reporting/__init__.py29
-rw-r--r--cloudinit/reporting/handlers.py15
-rw-r--r--tests/unittests/test_reporting.py51
4 files changed, 95 insertions, 14 deletions
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/reporting/handlers.py b/cloudinit/reporting/handlers.py
index be323f53..86cbe3c3 100644
--- a/cloudinit/reporting/handlers.py
+++ b/cloudinit/reporting/handlers.py
@@ -1,14 +1,27 @@
+# vi: ts=4 expandtab
+
import abc
import logging
+import oauthlib.oauth1 as oauth1
+
+import six
from cloudinit.registry import DictRegistry
+from cloudinit import url_helper
+from cloudinit import util
+@six.add_metaclass(abc.ABCMeta)
class ReportingHandler(object):
+ """Base class for report handlers.
+
+ Implement :meth:`~publish_event` for controlling what
+ the handler does with an event.
+ """
@abc.abstractmethod
def publish_event(self, event):
- raise NotImplementedError
+ """Publish an event to the ``INFO`` log level."""
class LogHandler(ReportingHandler):
diff --git a/tests/unittests/test_reporting.py b/tests/unittests/test_reporting.py
index ffeb55d2..1a4ee8c4 100644
--- a/tests/unittests/test_reporting.py
+++ b/tests/unittests/test_reporting.py
@@ -4,6 +4,7 @@
# vi: ts=4 expandtab
from cloudinit import reporting
+from cloudinit.reporting import handlers
from .helpers import (mock, TestCase)
@@ -95,13 +96,29 @@ 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 TestReportingHandler(TestCase):
+class TestBaseReportingHandler(TestCase):
- def test_no_default_publish_event_implementation(self):
- self.assertRaises(NotImplementedError,
- reporting.handlers.ReportingHandler().publish_event,
- None)
+ def test_base_reporting_handler_is_abstract(self):
+ regexp = r".*abstract.*publish_event.*"
+ self.assertRaisesRegexp(TypeError, regexp, handlers.ReportingHandler)
class TestLogHandler(TestCase):
@@ -147,7 +164,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)
@@ -159,7 +176,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},
@@ -177,7 +194,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[
@@ -194,9 +211,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')