Blame cloudinit/reporting/events.py

Packit Service a04d08
# Copyright (C) 2015 Canonical Ltd.
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
"""
Packit Service a04d08
events for reporting.
Packit Service a04d08
Packit Service a04d08
The events here are designed to be used with reporting.
Packit Service a04d08
They can be published to registered handlers with report_event.
Packit Service a04d08
"""
Packit Service a04d08
import base64
Packit Service a04d08
import os.path
Packit Service a04d08
import time
Packit Service a04d08
Packit Service 9bfd13
from . import instantiated_handler_registry, available_handlers
Packit Service a04d08
Packit Service a04d08
FINISH_EVENT_TYPE = 'finish'
Packit Service a04d08
START_EVENT_TYPE = 'start'
Packit Service a04d08
Packit Service a04d08
DEFAULT_EVENT_ORIGIN = 'cloudinit'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class _nameset(set):
Packit Service a04d08
    def __getattr__(self, name):
Packit Service a04d08
        if name in self:
Packit Service a04d08
            return name
Packit Service a04d08
        raise AttributeError("%s not a valid value" % name)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
status = _nameset(("SUCCESS", "WARN", "FAIL"))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class ReportingEvent(object):
Packit Service a04d08
    """Encapsulation of event formatting."""
Packit Service a04d08
Packit Service a04d08
    def __init__(self, event_type, name, description,
Packit Service a04d08
                 origin=DEFAULT_EVENT_ORIGIN, timestamp=None):
Packit Service a04d08
        self.event_type = event_type
Packit Service a04d08
        self.name = name
Packit Service a04d08
        self.description = description
Packit Service a04d08
        self.origin = origin
Packit Service a04d08
        if timestamp is None:
Packit Service a04d08
            timestamp = time.time()
Packit Service a04d08
        self.timestamp = timestamp
Packit Service a04d08
Packit Service a04d08
    def as_string(self):
Packit Service a04d08
        """The event represented as a string."""
Packit Service a04d08
        return '{0}: {1}: {2}'.format(
Packit Service a04d08
            self.event_type, self.name, self.description)
Packit Service a04d08
Packit Service a04d08
    def as_dict(self):
Packit Service a04d08
        """The event represented as a dictionary."""
Packit Service a04d08
        return {'name': self.name, 'description': self.description,
Packit Service a04d08
                'event_type': self.event_type, 'origin': self.origin,
Packit Service a04d08
                'timestamp': self.timestamp}
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class FinishReportingEvent(ReportingEvent):
Packit Service a04d08
Packit Service a04d08
    def __init__(self, name, description, result=status.SUCCESS,
Packit Service a04d08
                 post_files=None):
Packit Service a04d08
        super(FinishReportingEvent, self).__init__(
Packit Service a04d08
            FINISH_EVENT_TYPE, name, description)
Packit Service a04d08
        self.result = result
Packit Service a04d08
        if post_files is None:
Packit Service a04d08
            post_files = []
Packit Service a04d08
        self.post_files = post_files
Packit Service a04d08
        if result not in status:
Packit Service a04d08
            raise ValueError("Invalid result: %s" % result)
Packit Service a04d08
Packit Service a04d08
    def as_string(self):
Packit Service a04d08
        return '{0}: {1}: {2}: {3}'.format(
Packit Service a04d08
            self.event_type, self.name, self.result, self.description)
Packit Service a04d08
Packit Service a04d08
    def as_dict(self):
Packit Service a04d08
        """The event represented as json friendly."""
Packit Service a04d08
        data = super(FinishReportingEvent, self).as_dict()
Packit Service a04d08
        data['result'] = self.result
Packit Service a04d08
        if self.post_files:
Packit Service a04d08
            data['files'] = _collect_file_info(self.post_files)
Packit Service a04d08
        return data
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
def report_event(event, excluded_handler_types=None):
Packit Service 9bfd13
    """Report an event to all registered event handlers
Packit Service 9bfd13
    except those whose type is in excluded_handler_types.
Packit Service a04d08
Packit Service a04d08
    This should generally be called via one of the other functions in
Packit Service a04d08
    the reporting module.
Packit Service a04d08
Packit Service 9bfd13
    :param excluded_handler_types:
Packit Service 9bfd13
         List of handlers types to exclude from reporting the event to.
Packit Service a04d08
    :param event_type:
Packit Service a04d08
        The type of the event; this should be a constant from the
Packit Service a04d08
        reporting module.
Packit Service a04d08
    """
Packit Service 9bfd13
Packit Service 9bfd13
    if not excluded_handler_types:
Packit Service 9bfd13
        excluded_handler_types = {}
Packit Service 9bfd13
    excluded_handler_classes = {
Packit Service 9bfd13
        hndl_cls
Packit Service 9bfd13
        for hndl_type, hndl_cls in available_handlers.registered_items.items()
Packit Service 9bfd13
        if hndl_type in excluded_handler_types
Packit Service 9bfd13
    }
Packit Service 9bfd13
Packit Service 9bfd13
    handlers = instantiated_handler_registry.registered_items.items()
Packit Service 9bfd13
    for _, handler in handlers:
Packit Service 9bfd13
        if type(handler) in excluded_handler_classes:
Packit Service 9bfd13
            continue  # skip this excluded handler
Packit Service a04d08
        handler.publish_event(event)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def report_finish_event(event_name, event_description,
Packit Service a04d08
                        result=status.SUCCESS, post_files=None):
Packit Service a04d08
    """Report a "finish" event.
Packit Service a04d08
Packit Service a04d08
    See :py:func:`.report_event` for parameter details.
Packit Service a04d08
    """
Packit Service a04d08
    event = FinishReportingEvent(event_name, event_description, result,
Packit Service a04d08
                                 post_files=post_files)
Packit Service a04d08
    return report_event(event)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def report_start_event(event_name, event_description):
Packit Service a04d08
    """Report a "start" event.
Packit Service a04d08
Packit Service a04d08
    :param event_name:
Packit Service a04d08
        The name of the event; this should be a topic which events would
Packit Service a04d08
        share (e.g. it will be the same for start and finish events).
Packit Service a04d08
Packit Service a04d08
    :param event_description:
Packit Service a04d08
        A human-readable description of the event that has occurred.
Packit Service a04d08
    """
Packit Service a04d08
    event = ReportingEvent(START_EVENT_TYPE, event_name, event_description)
Packit Service a04d08
    return report_event(event)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class ReportEventStack(object):
Packit Service a04d08
    """Context Manager for using :py:func:`report_event`
Packit Service a04d08
Packit Service a04d08
    This enables calling :py:func:`report_start_event` and
Packit Service a04d08
    :py:func:`report_finish_event` through a context manager.
Packit Service a04d08
Packit Service a04d08
    :param name:
Packit Service a04d08
        the name of the event
Packit Service a04d08
Packit Service a04d08
    :param description:
Packit Service a04d08
        the event's description, passed on to :py:func:`report_start_event`
Packit Service a04d08
Packit Service a04d08
    :param message:
Packit Service a04d08
        the description to use for the finish event. defaults to
Packit Service a04d08
        :param:description.
Packit Service a04d08
Packit Service a04d08
    :param parent:
Packit Service a04d08
    :type parent: :py:class:ReportEventStack or None
Packit Service a04d08
        The parent of this event.  The parent is populated with
Packit Service a04d08
        results of all its children.  The name used in reporting
Packit Service a04d08
        is <parent.name>/<name>
Packit Service a04d08
Packit Service a04d08
    :param reporting_enabled:
Packit Service a04d08
        Indicates if reporting events should be generated.
Packit Service a04d08
        If not provided, defaults to the parent's value, or True if no parent
Packit Service a04d08
        is provided.
Packit Service a04d08
Packit Service a04d08
    :param result_on_exception:
Packit Service a04d08
        The result value to set if an exception is caught. default
Packit Service a04d08
        value is FAIL.
Packit Service a04d08
    """
Packit Service a04d08
    def __init__(self, name, description, message=None, parent=None,
Packit Service a04d08
                 reporting_enabled=None, result_on_exception=status.FAIL,
Packit Service a04d08
                 post_files=None):
Packit Service a04d08
        self.parent = parent
Packit Service a04d08
        self.name = name
Packit Service a04d08
        self.description = description
Packit Service a04d08
        self.message = message
Packit Service a04d08
        self.result_on_exception = result_on_exception
Packit Service a04d08
        self.result = status.SUCCESS
Packit Service a04d08
        if post_files is None:
Packit Service a04d08
            post_files = []
Packit Service a04d08
        self.post_files = post_files
Packit Service a04d08
Packit Service a04d08
        # use parents reporting value if not provided
Packit Service a04d08
        if reporting_enabled is None:
Packit Service a04d08
            if parent:
Packit Service a04d08
                reporting_enabled = parent.reporting_enabled
Packit Service a04d08
            else:
Packit Service a04d08
                reporting_enabled = True
Packit Service a04d08
        self.reporting_enabled = reporting_enabled
Packit Service a04d08
Packit Service a04d08
        if parent:
Packit Service a04d08
            self.fullname = '/'.join((parent.fullname, name,))
Packit Service a04d08
        else:
Packit Service a04d08
            self.fullname = self.name
Packit Service a04d08
        self.children = {}
Packit Service a04d08
Packit Service a04d08
    def __repr__(self):
Packit Service a04d08
        return ("ReportEventStack(%s, %s, reporting_enabled=%s)" %
Packit Service a04d08
                (self.name, self.description, self.reporting_enabled))
Packit Service a04d08
Packit Service a04d08
    def __enter__(self):
Packit Service a04d08
        self.result = status.SUCCESS
Packit Service a04d08
        if self.reporting_enabled:
Packit Service a04d08
            report_start_event(self.fullname, self.description)
Packit Service a04d08
        if self.parent:
Packit Service a04d08
            self.parent.children[self.name] = (None, None)
Packit Service a04d08
        return self
Packit Service a04d08
Packit Service a04d08
    def _childrens_finish_info(self):
Packit Service a04d08
        for cand_result in (status.FAIL, status.WARN):
Packit Service a04d08
            for _name, (value, _msg) in self.children.items():
Packit Service a04d08
                if value == cand_result:
Packit Service a04d08
                    return (value, self.message)
Packit Service a04d08
        return (self.result, self.message)
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def result(self):
Packit Service a04d08
        return self._result
Packit Service a04d08
Packit Service a04d08
    @result.setter
Packit Service a04d08
    def result(self, value):
Packit Service a04d08
        if value not in status:
Packit Service a04d08
            raise ValueError("'%s' not a valid result" % value)
Packit Service a04d08
        self._result = value
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def message(self):
Packit Service a04d08
        if self._message is not None:
Packit Service a04d08
            return self._message
Packit Service a04d08
        return self.description
Packit Service a04d08
Packit Service a04d08
    @message.setter
Packit Service a04d08
    def message(self, value):
Packit Service a04d08
        self._message = value
Packit Service a04d08
Packit Service a04d08
    def _finish_info(self, exc):
Packit Service a04d08
        # return tuple of description, and value
Packit Service a04d08
        if exc:
Packit Service a04d08
            return (self.result_on_exception, self.message)
Packit Service a04d08
        return self._childrens_finish_info()
Packit Service a04d08
Packit Service a04d08
    def __exit__(self, exc_type, exc_value, traceback):
Packit Service a04d08
        (result, msg) = self._finish_info(exc_value)
Packit Service a04d08
        if self.parent:
Packit Service a04d08
            self.parent.children[self.name] = (result, msg)
Packit Service a04d08
        if self.reporting_enabled:
Packit Service a04d08
            report_finish_event(self.fullname, msg, result,
Packit Service a04d08
                                post_files=self.post_files)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _collect_file_info(files):
Packit Service a04d08
    if not files:
Packit Service a04d08
        return None
Packit Service a04d08
    ret = []
Packit Service a04d08
    for fname in files:
Packit Service a04d08
        if not os.path.isfile(fname):
Packit Service a04d08
            content = None
Packit Service a04d08
        else:
Packit Service a04d08
            with open(fname, "rb") as fp:
Packit Service a04d08
                content = base64.b64encode(fp.read()).decode()
Packit Service a04d08
        ret.append({'path': fname, 'content': content,
Packit Service a04d08
                    'encoding': 'base64'})
Packit Service a04d08
    return ret
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab