cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #00720
Re: [Merge] lp:~smoser/cloud-init/trunk.reporting into lp:cloud-init
Documentation on the context manager, and tests as well, please. :)
Diff comments:
>
> === added file 'cloudinit/reporting.py'
> --- cloudinit/reporting.py 1970-01-01 00:00:00 +0000
> +++ cloudinit/reporting.py 2015-07-31 14:49:49 +0000
> @@ -0,0 +1,201 @@
> +# 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 abc
> +import logging
> +
> +from cloudinit.registry import DictRegistry
> +
> +
> +FINISH_EVENT_TYPE = 'finish'
> +START_EVENT_TYPE = 'start'
> +
> +DEFAULT_CONFIG = {
> + 'logging': {'type': 'log'},
> + 'print': {'type': 'print'},
> +}
> +
> +
> +class _nameset(set):
> + def __getattr__(self, name):
> + if name in self:
> + return name
> + raise AttributeError
> +
> +status = _nameset(("SUCCESS", "WARN", "FAIL"))
> +
> +instantiated_handler_registry = DictRegistry()
> +available_handlers = DictRegistry()
> +
> +
> +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, result=None):
> + super(FinishReportingEvent, self).__init__(
> + FINISH_EVENT_TYPE, name, description)
> + if result is None:
> + result = status.SUCCESS
> + self.result = result
> + if result not in status:
> + raise ValueError("Invalid result: %s" % result)
> +
> + def as_string(self):
> + return '{0}: {1}: {2}: {3}'.format(
> + self.event_type, self.name, self.result, self.description)
> +
> +
> +class ReportingHandler(object):
> +
> + @abc.abstractmethod
> + def publish_event(self, event):
> + raise NotImplementedError
> +
> +
> +class LogHandler(ReportingHandler):
> + """Publishes events to the cloud-init log at the ``INFO`` log level."""
> +
> + def publish_event(self, event):
> + """Publish an event to the ``INFO`` log level."""
> + logger = logging.getLogger(
> + '.'.join([__name__, event.event_type, event.name]))
> + logger.info(event.as_string())
> +
> +
> +class PrintHandler(ReportingHandler):
> + def publish_event(self, event):
> + print(event.as_string())
Definitely stdout rather than stderr? Not sure what we do elsewhere.
> +
> +
> +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):
> + """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 instantiated_handler_registry.registered_items.items():
> + handler.publish_event(event)
> +
> +
> +def report_finish_event(event_name, event_description, result):
> + """Report a "finish" event.
> +
> + See :py:func:`.report_event` for parameter details.
> + """
> + event = FinishReportingEvent(event_name, event_description, result)
> + 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)
> +
> +
> +class ReportStack(object):
> + def __init__(self, name, description, parent=None, reporting=None,
`reporting` might be better as `reporting_enabled`.
`exc_result` is a little too reminiscent of the __exit__ parameters; maybe `result_on_exception`? I _think_ this is also a case where it's safe to do `result_on_exception=status.FAIL`; you only need to use the foo=None trick when you want the default to be something mutable (like a list or a dict).
> + exc_result=None):
> + self.parent = parent
> + self.name = name
> + self.description = description
> +
> + if exc_result is None:
> + exc_result = status.FAIL
> + self.exc_result = exc_result
> +
> + # use parents reporting value if not provided
> + if reporting is None:
> + if parent:
> + reporting = parent.reporting
> + else:
> + reporting = True
> + self.reporting = reporting
> +
> + if parent:
> + self.fullname = '/'.join((parent.fullname, name,))
> + else:
> + self.fullname = self.name
> + self.children = {}
> +
> + def __repr__(self):
> + return ("%s reporting=%s" % (self.fullname, self.reporting))
> +
> + def __enter__(self):
> + self.exception = None
I don't think self.exception is ever read, just written to; what's it intended for?
> + if self.reporting:
> + report_start_event(self.fullname, self.description)
> + if self.parent:
> + self.parent.children[self.name] = (None, None)
> + return self
> +
> + def childrens_finish_info(self, result=None, description=None):
> + for cand_result in (status.FAIL, status.WARN):
> + for name, (value, msg) in self.children.items():
> + if value == cand_result:
> + return (value, "[" + name + "]" + msg)
> + if result is None:
Do we need these ifs? I don't think `result` or `description` can change, so if we get to this point they will be None. So can this line just be `return (status.SUCCESS, self.description)`?
> + result = status.SUCCESS
> + if description is None:
> + description = self.description
> + return (result, description)
> +
> + def finish_info(self, exc):
> + # return tuple of description, and value
> + if exc:
> + # by default, exceptions are fatal
> + return (self.exc_result, self.description)
> + return self.childrens_finish_info()
> +
> + def __exit__(self, exc_type, exc_value, traceback):
> + self.exception = exc_value
> + (result, msg) = self.finish_info(exc_value)
> + if self.parent:
> + self.parent.children[self.name] = (result, msg)
> + if self.reporting:
> + report_finish_event(self.fullname, msg, result)
> +
> +
> +available_handlers.register_item('log', LogHandler)
> +available_handlers.register_item('print', PrintHandler)
> +add_configuration(DEFAULT_CONFIG)
>
> === modified file 'cloudinit/sources/__init__.py'
> --- cloudinit/sources/__init__.py 2015-07-22 12:06:34 +0000
> +++ cloudinit/sources/__init__.py 2015-07-31 14:49:49 +0000
> @@ -246,17 +247,23 @@
> return keys
>
>
> -def find_source(sys_cfg, distro, paths, ds_deps, cfg_list, pkg_list):
> +def find_source(sys_cfg, distro, paths, ds_deps, cfg_list, pkg_list, reporter):
> ds_list = list_sources(cfg_list, ds_deps, pkg_list)
> ds_names = [type_utils.obj_name(f) for f in ds_list]
> LOG.debug("Searching for data source in: %s", ds_names)
>
> - for cls in ds_list:
> + for i, cls in enumerate(ds_list):
You could do `for name, cls in zip(ds_names, ds_list):`.
> + name=ds_names[i].replace("DataSource", "")
> + myreporter = reporting.ReportStack(
> + "check-%s" % name, "searching for %s" % name,
> + parent=reporter, exc_result=reporting.status.WARN)
> +
> try:
> - LOG.debug("Seeing if we can get any data from %s", cls)
> - s = cls(sys_cfg, distro, paths)
> - if s.get_data():
> - return (s, type_utils.obj_name(cls))
> + with myreporter:
> + LOG.debug("Seeing if we can get any data from %s", cls)
> + s = cls(sys_cfg, distro, paths)
> + if s.get_data():
> + return (s, type_utils.obj_name(cls))
> except Exception:
> util.logexc(LOG, "Getting data from %s failed", cls)
>
--
https://code.launchpad.net/~smoser/cloud-init/trunk.reporting/+merge/266578
Your team cloud init development team is requested to review the proposed merge of lp:~smoser/cloud-init/trunk.reporting into lp:cloud-init.
References