~cloud-init-dev/cloud-init/trunk

« back to all changes in this revision

Viewing changes to cloudinit/reporting/events.py

  • Committer: Scott Moser
  • Date: 2016-08-10 15:06:15 UTC
  • Revision ID: smoser@ubuntu.com-20160810150615-ma2fv107w3suy1ma
README: Mention move of revision control to git.

cloud-init development has moved its revision control to git.
It is available at 
  https://code.launchpad.net/cloud-init

Clone with 
  git clone https://git.launchpad.net/cloud-init
or
  git clone git+ssh://git.launchpad.net/cloud-init

For more information see
  https://git.launchpad.net/cloud-init/tree/HACKING.rst

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2015 Canonical Ltd.
2
 
# This file is part of cloud-init.  See LICENCE file for license information.
3
 
#
4
 
"""
5
 
events for reporting.
6
 
 
7
 
The events here are designed to be used with reporting.
8
 
They can be published to registered handlers with report_event.
9
 
"""
10
 
import base64
11
 
import os.path
12
 
import time
13
 
 
14
 
from . import instantiated_handler_registry
15
 
 
16
 
FINISH_EVENT_TYPE = 'finish'
17
 
START_EVENT_TYPE = 'start'
18
 
 
19
 
DEFAULT_EVENT_ORIGIN = 'cloudinit'
20
 
 
21
 
 
22
 
class _nameset(set):
23
 
    def __getattr__(self, name):
24
 
        if name in self:
25
 
            return name
26
 
        raise AttributeError("%s not a valid value" % name)
27
 
 
28
 
 
29
 
status = _nameset(("SUCCESS", "WARN", "FAIL"))
30
 
 
31
 
 
32
 
class ReportingEvent(object):
33
 
    """Encapsulation of event formatting."""
34
 
 
35
 
    def __init__(self, event_type, name, description,
36
 
                 origin=DEFAULT_EVENT_ORIGIN, timestamp=None):
37
 
        self.event_type = event_type
38
 
        self.name = name
39
 
        self.description = description
40
 
        self.origin = origin
41
 
        if timestamp is None:
42
 
            timestamp = time.time()
43
 
        self.timestamp = timestamp
44
 
 
45
 
    def as_string(self):
46
 
        """The event represented as a string."""
47
 
        return '{0}: {1}: {2}'.format(
48
 
            self.event_type, self.name, self.description)
49
 
 
50
 
    def as_dict(self):
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}
55
 
 
56
 
 
57
 
class FinishReportingEvent(ReportingEvent):
58
 
 
59
 
    def __init__(self, name, description, result=status.SUCCESS,
60
 
                 post_files=None):
61
 
        super(FinishReportingEvent, self).__init__(
62
 
            FINISH_EVENT_TYPE, name, description)
63
 
        self.result = result
64
 
        if post_files is None:
65
 
            post_files = []
66
 
        self.post_files = post_files
67
 
        if result not in status:
68
 
            raise ValueError("Invalid result: %s" % result)
69
 
 
70
 
    def as_string(self):
71
 
        return '{0}: {1}: {2}: {3}'.format(
72
 
            self.event_type, self.name, self.result, self.description)
73
 
 
74
 
    def as_dict(self):
75
 
        """The event represented as json friendly."""
76
 
        data = super(FinishReportingEvent, self).as_dict()
77
 
        data['result'] = self.result
78
 
        if self.post_files:
79
 
            data['files'] = _collect_file_info(self.post_files)
80
 
        return data
81
 
 
82
 
 
83
 
def report_event(event):
84
 
    """Report an event to all registered event handlers.
85
 
 
86
 
    This should generally be called via one of the other functions in
87
 
    the reporting module.
88
 
 
89
 
    :param event_type:
90
 
        The type of the event; this should be a constant from the
91
 
        reporting module.
92
 
    """
93
 
    for _, handler in instantiated_handler_registry.registered_items.items():
94
 
        handler.publish_event(event)
95
 
 
96
 
 
97
 
def report_finish_event(event_name, event_description,
98
 
                        result=status.SUCCESS, post_files=None):
99
 
    """Report a "finish" event.
100
 
 
101
 
    See :py:func:`.report_event` for parameter details.
102
 
    """
103
 
    event = FinishReportingEvent(event_name, event_description, result,
104
 
                                 post_files=post_files)
105
 
    return report_event(event)
106
 
 
107
 
 
108
 
def report_start_event(event_name, event_description):
109
 
    """Report a "start" event.
110
 
 
111
 
    :param event_name:
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).
114
 
 
115
 
    :param event_description:
116
 
        A human-readable description of the event that has occurred.
117
 
    """
118
 
    event = ReportingEvent(START_EVENT_TYPE, event_name, event_description)
119
 
    return report_event(event)
120
 
 
121
 
 
122
 
class ReportEventStack(object):
123
 
    """Context Manager for using :py:func:`report_event`
124
 
 
125
 
    This enables calling :py:func:`report_start_event` and
126
 
    :py:func:`report_finish_event` through a context manager.
127
 
 
128
 
    :param name:
129
 
        the name of the event
130
 
 
131
 
    :param description:
132
 
        the event's description, passed on to :py:func:`report_start_event`
133
 
 
134
 
    :param message:
135
 
        the description to use for the finish event. defaults to
136
 
        :param:description.
137
 
 
138
 
    :param parent:
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>
143
 
 
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
147
 
        is provided.
148
 
 
149
 
    :param result_on_exception:
150
 
        The result value to set if an exception is caught. default
151
 
        value is FAIL.
152
 
    """
153
 
    def __init__(self, name, description, message=None, parent=None,
154
 
                 reporting_enabled=None, result_on_exception=status.FAIL,
155
 
                 post_files=None):
156
 
        self.parent = parent
157
 
        self.name = name
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:
163
 
            post_files = []
164
 
        self.post_files = post_files
165
 
 
166
 
        # use parents reporting value if not provided
167
 
        if reporting_enabled is None:
168
 
            if parent:
169
 
                reporting_enabled = parent.reporting_enabled
170
 
            else:
171
 
                reporting_enabled = True
172
 
        self.reporting_enabled = reporting_enabled
173
 
 
174
 
        if parent:
175
 
            self.fullname = '/'.join((parent.fullname, name,))
176
 
        else:
177
 
            self.fullname = self.name
178
 
        self.children = {}
179
 
 
180
 
    def __repr__(self):
181
 
        return ("ReportEventStack(%s, %s, reporting_enabled=%s)" %
182
 
                (self.name, self.description, self.reporting_enabled))
183
 
 
184
 
    def __enter__(self):
185
 
        self.result = status.SUCCESS
186
 
        if self.reporting_enabled:
187
 
            report_start_event(self.fullname, self.description)
188
 
        if self.parent:
189
 
            self.parent.children[self.name] = (None, None)
190
 
        return self
191
 
 
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)
198
 
 
199
 
    @property
200
 
    def result(self):
201
 
        return self._result
202
 
 
203
 
    @result.setter
204
 
    def result(self, value):
205
 
        if value not in status:
206
 
            raise ValueError("'%s' not a valid result" % value)
207
 
        self._result = value
208
 
 
209
 
    @property
210
 
    def message(self):
211
 
        if self._message is not None:
212
 
            return self._message
213
 
        return self.description
214
 
 
215
 
    @message.setter
216
 
    def message(self, value):
217
 
        self._message = value
218
 
 
219
 
    def _finish_info(self, exc):
220
 
        # return tuple of description, and value
221
 
        if exc:
222
 
            return (self.result_on_exception, self.message)
223
 
        return self._childrens_finish_info()
224
 
 
225
 
    def __exit__(self, exc_type, exc_value, traceback):
226
 
        (result, msg) = self._finish_info(exc_value)
227
 
        if self.parent:
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)
232
 
 
233
 
 
234
 
def _collect_file_info(files):
235
 
    if not files:
236
 
        return None
237
 
    ret = []
238
 
    for fname in files:
239
 
        if not os.path.isfile(fname):
240
 
            content = None
241
 
        else:
242
 
            with open(fname, "rb") as fp:
243
 
                content = base64.b64encode(fp.read()).decode()
244
 
        ret.append({'path': fname, 'content': content,
245
 
                    'encoding': 'base64'})
246
 
    return ret
247
 
 
248
 
# vi: ts=4 expandtab syntax=python