1
# Copyright 2015 Canonical Ltd.
2
# This file is part of cloud-init. See LICENCE file for license information.
7
The events here are designed to be used with reporting.
8
They can be published to registered handlers with report_event.
14
from . import instantiated_handler_registry
16
FINISH_EVENT_TYPE = 'finish'
17
START_EVENT_TYPE = 'start'
19
DEFAULT_EVENT_ORIGIN = 'cloudinit'
23
def __getattr__(self, name):
26
raise AttributeError("%s not a valid value" % name)
29
status = _nameset(("SUCCESS", "WARN", "FAIL"))
32
class ReportingEvent(object):
33
"""Encapsulation of event formatting."""
35
def __init__(self, event_type, name, description,
36
origin=DEFAULT_EVENT_ORIGIN, timestamp=None):
37
self.event_type = event_type
39
self.description = description
42
timestamp = time.time()
43
self.timestamp = timestamp
46
"""The event represented as a string."""
47
return '{0}: {1}: {2}'.format(
48
self.event_type, self.name, self.description)
51
"""The event represented as a dictionary."""
52
return {'name': self.name, 'description': self.description,
53
'event_type': self.event_type, 'origin': self.origin,
54
'timestamp': self.timestamp}
57
class FinishReportingEvent(ReportingEvent):
59
def __init__(self, name, description, result=status.SUCCESS,
61
super(FinishReportingEvent, self).__init__(
62
FINISH_EVENT_TYPE, name, description)
64
if post_files is None:
66
self.post_files = post_files
67
if result not in status:
68
raise ValueError("Invalid result: %s" % result)
71
return '{0}: {1}: {2}: {3}'.format(
72
self.event_type, self.name, self.result, self.description)
75
"""The event represented as json friendly."""
76
data = super(FinishReportingEvent, self).as_dict()
77
data['result'] = self.result
79
data['files'] = _collect_file_info(self.post_files)
83
def report_event(event):
84
"""Report an event to all registered event handlers.
86
This should generally be called via one of the other functions in
90
The type of the event; this should be a constant from the
93
for _, handler in instantiated_handler_registry.registered_items.items():
94
handler.publish_event(event)
97
def report_finish_event(event_name, event_description,
98
result=status.SUCCESS, post_files=None):
99
"""Report a "finish" event.
101
See :py:func:`.report_event` for parameter details.
103
event = FinishReportingEvent(event_name, event_description, result,
104
post_files=post_files)
105
return report_event(event)
108
def report_start_event(event_name, event_description):
109
"""Report a "start" event.
112
The name of the event; this should be a topic which events would
113
share (e.g. it will be the same for start and finish events).
115
:param event_description:
116
A human-readable description of the event that has occurred.
118
event = ReportingEvent(START_EVENT_TYPE, event_name, event_description)
119
return report_event(event)
122
class ReportEventStack(object):
123
"""Context Manager for using :py:func:`report_event`
125
This enables calling :py:func:`report_start_event` and
126
:py:func:`report_finish_event` through a context manager.
129
the name of the event
132
the event's description, passed on to :py:func:`report_start_event`
135
the description to use for the finish event. defaults to
139
:type parent: :py:class:ReportEventStack or None
140
The parent of this event. The parent is populated with
141
results of all its children. The name used in reporting
142
is <parent.name>/<name>
144
:param reporting_enabled:
145
Indicates if reporting events should be generated.
146
If not provided, defaults to the parent's value, or True if no parent
149
:param result_on_exception:
150
The result value to set if an exception is caught. default
153
def __init__(self, name, description, message=None, parent=None,
154
reporting_enabled=None, result_on_exception=status.FAIL,
158
self.description = description
159
self.message = message
160
self.result_on_exception = result_on_exception
161
self.result = status.SUCCESS
162
if post_files is None:
164
self.post_files = post_files
166
# use parents reporting value if not provided
167
if reporting_enabled is None:
169
reporting_enabled = parent.reporting_enabled
171
reporting_enabled = True
172
self.reporting_enabled = reporting_enabled
175
self.fullname = '/'.join((parent.fullname, name,))
177
self.fullname = self.name
181
return ("ReportEventStack(%s, %s, reporting_enabled=%s)" %
182
(self.name, self.description, self.reporting_enabled))
185
self.result = status.SUCCESS
186
if self.reporting_enabled:
187
report_start_event(self.fullname, self.description)
189
self.parent.children[self.name] = (None, None)
192
def _childrens_finish_info(self):
193
for cand_result in (status.FAIL, status.WARN):
194
for name, (value, msg) in self.children.items():
195
if value == cand_result:
196
return (value, self.message)
197
return (self.result, self.message)
204
def result(self, value):
205
if value not in status:
206
raise ValueError("'%s' not a valid result" % value)
211
if self._message is not None:
213
return self.description
216
def message(self, value):
217
self._message = value
219
def _finish_info(self, exc):
220
# return tuple of description, and value
222
return (self.result_on_exception, self.message)
223
return self._childrens_finish_info()
225
def __exit__(self, exc_type, exc_value, traceback):
226
(result, msg) = self._finish_info(exc_value)
228
self.parent.children[self.name] = (result, msg)
229
if self.reporting_enabled:
230
report_finish_event(self.fullname, msg, result,
231
post_files=self.post_files)
234
def _collect_file_info(files):
239
if not os.path.isfile(fname):
242
with open(fname, "rb") as fp:
243
content = base64.b64encode(fp.read()).decode()
244
ret.append({'path': fname, 'content': content,
245
'encoding': 'base64'})
248
# vi: ts=4 expandtab syntax=python