Basic implementation of a reporting framework.
Change-Id: Ic468e388a4b3d66763b5db5c1b3e599f3005fa4d
This commit is contained in:
parent
f512e05819
commit
2856779179
|
@ -0,0 +1,96 @@
|
|||
# Copyright 2015 Canonical Ltd.
|
||||
# This file is part of cloud-init. See LICENCE file for license information.
|
||||
#
|
||||
# vi: ts=4 expandtab
|
||||
"""
|
||||
cloud-init reporting framework
|
||||
|
||||
The reporting framework is intended to allow all parts of cloud-init to
|
||||
report events in a structured manner.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
FINISH_EVENT_TYPE = 'finish'
|
||||
START_EVENT_TYPE = 'start'
|
||||
|
||||
|
||||
class ReportingEvent(object):
|
||||
"""Encapsulation of event formatting."""
|
||||
|
||||
def __init__(self, event_type, name, description):
|
||||
self.event_type = event_type
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
def as_string(self):
|
||||
"""The event represented as a string."""
|
||||
return '{0}: {1}: {2}'.format(
|
||||
self.event_type, self.name, self.description)
|
||||
|
||||
|
||||
class FinishReportingEvent(ReportingEvent):
|
||||
|
||||
def __init__(self, name, description, successful=None):
|
||||
super(FinishReportingEvent, self).__init__(
|
||||
FINISH_EVENT_TYPE, name, description)
|
||||
self.successful = successful
|
||||
|
||||
def as_string(self):
|
||||
if self.successful is None:
|
||||
return super(FinishReportingEvent, self).as_string()
|
||||
success_string = 'success' if self.successful else 'fail'
|
||||
return '{0}: {1}: {2}: {3}'.format(
|
||||
self.event_type, self.name, success_string, self.description)
|
||||
|
||||
|
||||
class LogHandler(object):
|
||||
"""Publishes events to the cloud-init log at the ``INFO`` log level."""
|
||||
|
||||
@staticmethod
|
||||
def publish_event(event):
|
||||
"""Publish an event to the ``INFO`` log level."""
|
||||
logger = logging.getLogger(
|
||||
'.'.join([__name__, event.event_type, event.name]))
|
||||
logger.info(event.as_string())
|
||||
|
||||
|
||||
HANDLERS = [LogHandler]
|
||||
|
||||
|
||||
def report_event(event):
|
||||
"""Report an event to all registered event handlers.
|
||||
|
||||
This should generally be called via one of the other functions in
|
||||
the reporting module.
|
||||
|
||||
:param event_type:
|
||||
The type of the event; this should be a constant from the
|
||||
reporting module.
|
||||
"""
|
||||
for handler in HANDLERS:
|
||||
handler.publish_event(event)
|
||||
|
||||
|
||||
def report_finish_event(event_name, event_description, successful=None):
|
||||
"""Report a "finish" event.
|
||||
|
||||
See :py:func:`.report_event` for parameter details.
|
||||
"""
|
||||
event = FinishReportingEvent(event_name, event_description, successful)
|
||||
return report_event(event)
|
||||
|
||||
|
||||
def report_start_event(event_name, event_description):
|
||||
"""Report a "start" event.
|
||||
|
||||
:param event_name:
|
||||
The name of the event; this should be a topic which events would
|
||||
share (e.g. it will be the same for start and finish events).
|
||||
|
||||
:param event_description:
|
||||
A human-readable description of the event that has occurred.
|
||||
"""
|
||||
event = ReportingEvent(START_EVENT_TYPE, event_name, event_description)
|
||||
return report_event(event)
|
|
@ -0,0 +1,110 @@
|
|||
# Copyright 2015 Canonical Ltd.
|
||||
# This file is part of cloud-init. See LICENCE file for license information.
|
||||
#
|
||||
# vi: ts=4 expandtab
|
||||
|
||||
import unittest
|
||||
|
||||
from cloudinit import reporting
|
||||
from cloudinit.tests import TestCase
|
||||
from cloudinit.tests.util import mock
|
||||
|
||||
|
||||
class TestReportStartEvent(unittest.TestCase):
|
||||
|
||||
@mock.patch('cloudinit.reporting.HANDLERS',
|
||||
new_callable=lambda: [mock.MagicMock(), mock.MagicMock()])
|
||||
def test_report_start_event_passes_something_with_as_string_to_handlers(
|
||||
self, HANDLERS):
|
||||
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 HANDLERS:
|
||||
self.assertEqual(1, handler.publish_event.call_count)
|
||||
event = handler.publish_event.call_args[0][0]
|
||||
self.assertEqual(expected_string_representation, event.as_string())
|
||||
|
||||
|
||||
class TestReportFinishEvent(unittest.TestCase):
|
||||
|
||||
def _report_finish_event(self, successful=None):
|
||||
event_name, event_description = 'my_test_event', 'my description'
|
||||
reporting.report_finish_event(
|
||||
event_name, event_description, successful=successful)
|
||||
return event_name, event_description
|
||||
|
||||
def assertHandlersPassedObjectWithAsString(
|
||||
self, handlers, expected_as_string):
|
||||
for handler in handlers:
|
||||
self.assertEqual(1, handler.publish_event.call_count)
|
||||
event = handler.publish_event.call_args[0][0]
|
||||
self.assertEqual(expected_as_string, event.as_string())
|
||||
|
||||
@mock.patch('cloudinit.reporting.HANDLERS',
|
||||
new_callable=lambda: [mock.MagicMock(), mock.MagicMock()])
|
||||
def test_report_finish_event_passes_something_with_as_string_to_handlers(
|
||||
self, HANDLERS):
|
||||
event_name, event_description = self._report_finish_event()
|
||||
expected_string_representation = ': '.join(
|
||||
['finish', event_name, event_description])
|
||||
self.assertHandlersPassedObjectWithAsString(
|
||||
HANDLERS, expected_string_representation)
|
||||
|
||||
@mock.patch('cloudinit.reporting.HANDLERS',
|
||||
new_callable=lambda: [mock.MagicMock(), mock.MagicMock()])
|
||||
def test_reporting_successful_finish_has_sensible_string_repr(self,
|
||||
HANDLERS):
|
||||
event_name, event_description = self._report_finish_event(
|
||||
successful=True)
|
||||
expected_string_representation = ': '.join(
|
||||
['finish', event_name, 'success', event_description])
|
||||
self.assertHandlersPassedObjectWithAsString(
|
||||
HANDLERS, expected_string_representation)
|
||||
|
||||
@mock.patch('cloudinit.reporting.HANDLERS',
|
||||
new_callable=lambda: [mock.MagicMock(), mock.MagicMock()])
|
||||
def test_reporting_unsuccessful_finish_has_sensible_string_repr(self,
|
||||
HANDLERS):
|
||||
event_name, event_description = self._report_finish_event(
|
||||
successful=False)
|
||||
expected_string_representation = ': '.join(
|
||||
['finish', event_name, 'fail', event_description])
|
||||
self.assertHandlersPassedObjectWithAsString(
|
||||
HANDLERS, expected_string_representation)
|
||||
|
||||
|
||||
class TestReportingEvent(unittest.TestCase):
|
||||
|
||||
def test_as_string(self):
|
||||
event_type, name, description = 'test_type', 'test_name', 'test_desc'
|
||||
event = reporting.ReportingEvent(event_type, name, description)
|
||||
expected_string_representation = ': '.join(
|
||||
[event_type, name, description])
|
||||
self.assertEqual(expected_string_representation, event.as_string())
|
||||
|
||||
|
||||
class TestLogHandler(TestCase):
|
||||
|
||||
@mock.patch.object(reporting.logging, 'getLogger')
|
||||
def test_appropriate_logger_used(self, getLogger):
|
||||
event_type, event_name = 'test_type', 'test_name'
|
||||
event = reporting.ReportingEvent(event_type, event_name, 'description')
|
||||
reporting.LogHandler.publish_event(event)
|
||||
self.assertEqual(
|
||||
[mock.call(
|
||||
'cloudinit.reporting.{0}.{1}'.format(event_type, event_name))],
|
||||
getLogger.call_args_list)
|
||||
|
||||
@mock.patch.object(reporting.logging, 'getLogger')
|
||||
def test_single_log_message_at_info_published(self, getLogger):
|
||||
event = reporting.ReportingEvent('type', 'name', 'description')
|
||||
reporting.LogHandler.publish_event(event)
|
||||
self.assertEqual(1, getLogger.return_value.info.call_count)
|
||||
|
||||
@mock.patch.object(reporting.logging, 'getLogger')
|
||||
def test_log_message_uses_event_as_string(self, getLogger):
|
||||
event = reporting.ReportingEvent('type', 'name', 'description')
|
||||
reporting.LogHandler.publish_event(event)
|
||||
self.assertIn(event.as_string(),
|
||||
getLogger.return_value.info.call_args[0][0])
|
Loading…
Reference in New Issue