~ubuntu-branches/ubuntu/utopic/ceilometer/utopic-proposed

« back to all changes in this revision

Viewing changes to ceilometer/alarm/storage/pymongo_base.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2014-10-16 14:07:11 UTC
  • mfrom: (1.2.1) (28.1.5 utopic-proposed)
  • Revision ID: package-import@ubuntu.com-20141016140711-95mki6bdkivvfr2x
Tags: 2014.2-0ubuntu1
New upstream release. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright Ericsson AB 2013. All rights reserved
 
3
#
 
4
# Authors: Ildiko Vancsa <ildiko.vancsa@ericsson.com>
 
5
#          Balazs Gibizer <balazs.gibizer@ericsson.com>
 
6
#
 
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
# not use this file except in compliance with the License. You may obtain
 
9
# a copy of the License at
 
10
#
 
11
#      http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
13
# Unless required by applicable law or agreed to in writing, software
 
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
# License for the specific language governing permissions and limitations
 
17
# under the License.
 
18
"""Common functions for MongoDB and DB2 backends
 
19
"""
 
20
 
 
21
 
 
22
import pymongo
 
23
 
 
24
import ceilometer
 
25
from ceilometer.alarm.storage import base
 
26
from ceilometer.alarm.storage import models
 
27
from ceilometer.openstack.common import log
 
28
from ceilometer.storage.mongo import utils as pymongo_utils
 
29
from ceilometer import utils
 
30
 
 
31
LOG = log.getLogger(__name__)
 
32
 
 
33
 
 
34
COMMON_AVAILABLE_CAPABILITIES = {
 
35
    'alarms': {'query': {'simple': True,
 
36
                         'complex': True},
 
37
               'history': {'query': {'simple': True,
 
38
                                     'complex': True}}},
 
39
}
 
40
 
 
41
 
 
42
AVAILABLE_STORAGE_CAPABILITIES = {
 
43
    'storage': {'production_ready': True},
 
44
}
 
45
 
 
46
 
 
47
class Connection(base.Connection):
 
48
    """Base Alarm Connection class for MongoDB and DB2 drivers."""
 
49
    CAPABILITIES = utils.update_nested(base.Connection.CAPABILITIES,
 
50
                                       COMMON_AVAILABLE_CAPABILITIES)
 
51
 
 
52
    STORAGE_CAPABILITIES = utils.update_nested(
 
53
        base.Connection.STORAGE_CAPABILITIES,
 
54
        AVAILABLE_STORAGE_CAPABILITIES,
 
55
    )
 
56
 
 
57
    def update_alarm(self, alarm):
 
58
        """Update alarm."""
 
59
        data = alarm.as_dict()
 
60
 
 
61
        self.db.alarm.update(
 
62
            {'alarm_id': alarm.alarm_id},
 
63
            {'$set': data},
 
64
            upsert=True)
 
65
 
 
66
        stored_alarm = self.db.alarm.find({'alarm_id': alarm.alarm_id})[0]
 
67
        del stored_alarm['_id']
 
68
        self._ensure_encapsulated_rule_format(stored_alarm)
 
69
        self._ensure_time_constraints(stored_alarm)
 
70
        return models.Alarm(**stored_alarm)
 
71
 
 
72
    create_alarm = update_alarm
 
73
 
 
74
    def delete_alarm(self, alarm_id):
 
75
        """Delete an alarm."""
 
76
        self.db.alarm.remove({'alarm_id': alarm_id})
 
77
 
 
78
    def record_alarm_change(self, alarm_change):
 
79
        """Record alarm change event."""
 
80
        self.db.alarm_history.insert(alarm_change.copy())
 
81
 
 
82
    def get_alarms(self, name=None, user=None, state=None, meter=None,
 
83
                   project=None, enabled=None, alarm_id=None, pagination=None):
 
84
        """Yields a lists of alarms that match filters
 
85
 
 
86
        :param name: The Alarm name.
 
87
        :param user: Optional ID for user that owns the resource.
 
88
        :param state: Optional string for alarm state.
 
89
        :param meter: Optional string for alarms associated with meter.
 
90
        :param project: Optional ID for project that owns the resource.
 
91
        :param enabled: Optional boolean to list disable alarm.
 
92
        :param alarm_id: Optional alarm_id to return one alarm.
 
93
        :param pagination: Optional pagination query.
 
94
        """
 
95
        if pagination:
 
96
            raise ceilometer.NotImplementedError('Pagination not implemented')
 
97
 
 
98
        q = {}
 
99
        if user is not None:
 
100
            q['user_id'] = user
 
101
        if project is not None:
 
102
            q['project_id'] = project
 
103
        if name is not None:
 
104
            q['name'] = name
 
105
        if enabled is not None:
 
106
            q['enabled'] = enabled
 
107
        if alarm_id is not None:
 
108
            q['alarm_id'] = alarm_id
 
109
        if state is not None:
 
110
            q['state'] = state
 
111
        if meter is not None:
 
112
            q['rule.meter_name'] = meter
 
113
 
 
114
        return self._retrieve_alarms(q, [], None)
 
115
 
 
116
    def get_alarm_changes(self, alarm_id, on_behalf_of,
 
117
                          user=None, project=None, type=None,
 
118
                          start_timestamp=None, start_timestamp_op=None,
 
119
                          end_timestamp=None, end_timestamp_op=None):
 
120
        """Yields list of AlarmChanges describing alarm history
 
121
 
 
122
        Changes are always sorted in reverse order of occurrence, given
 
123
        the importance of currency.
 
124
 
 
125
        Segregation for non-administrative users is done on the basis
 
126
        of the on_behalf_of parameter. This allows such users to have
 
127
        visibility on both the changes initiated by themselves directly
 
128
        (generally creation, rule changes, or deletion) and also on those
 
129
        changes initiated on their behalf by the alarming service (state
 
130
        transitions after alarm thresholds are crossed).
 
131
 
 
132
        :param alarm_id: ID of alarm to return changes for
 
133
        :param on_behalf_of: ID of tenant to scope changes query (None for
 
134
                             administrative user, indicating all projects)
 
135
        :param user: Optional ID of user to return changes for
 
136
        :param project: Optional ID of project to return changes for
 
137
        :project type: Optional change type
 
138
        :param start_timestamp: Optional modified timestamp start range
 
139
        :param start_timestamp_op: Optional timestamp start range operation
 
140
        :param end_timestamp: Optional modified timestamp end range
 
141
        :param end_timestamp_op: Optional timestamp end range operation
 
142
        """
 
143
        q = dict(alarm_id=alarm_id)
 
144
        if on_behalf_of is not None:
 
145
            q['on_behalf_of'] = on_behalf_of
 
146
        if user is not None:
 
147
            q['user_id'] = user
 
148
        if project is not None:
 
149
            q['project_id'] = project
 
150
        if type is not None:
 
151
            q['type'] = type
 
152
        if start_timestamp or end_timestamp:
 
153
            ts_range = pymongo_utils.make_timestamp_range(start_timestamp,
 
154
                                                          end_timestamp,
 
155
                                                          start_timestamp_op,
 
156
                                                          end_timestamp_op)
 
157
            if ts_range:
 
158
                q['timestamp'] = ts_range
 
159
 
 
160
        return self._retrieve_alarm_changes(q,
 
161
                                            [("timestamp",
 
162
                                              pymongo.DESCENDING)],
 
163
                                            None)
 
164
 
 
165
    def query_alarms(self, filter_expr=None, orderby=None, limit=None):
 
166
        """Return an iterable of model.Alarm objects."""
 
167
        return self._retrieve_data(filter_expr, orderby, limit,
 
168
                                   models.Alarm)
 
169
 
 
170
    def query_alarm_history(self, filter_expr=None, orderby=None, limit=None):
 
171
        """Return an iterable of model.AlarmChange objects."""
 
172
        return self._retrieve_data(filter_expr,
 
173
                                   orderby,
 
174
                                   limit,
 
175
                                   models.AlarmChange)
 
176
 
 
177
    def _retrieve_data(self, filter_expr, orderby, limit, model):
 
178
        if limit == 0:
 
179
            return []
 
180
        query_filter = {}
 
181
        orderby_filter = [("timestamp", pymongo.DESCENDING)]
 
182
        transformer = pymongo_utils.QueryTransformer()
 
183
        if orderby is not None:
 
184
            orderby_filter = transformer.transform_orderby(orderby)
 
185
        if filter_expr is not None:
 
186
            query_filter = transformer.transform_filter(filter_expr)
 
187
 
 
188
        retrieve = {models.Alarm: self._retrieve_alarms,
 
189
                    models.AlarmChange: self._retrieve_alarm_changes}
 
190
        return retrieve[model](query_filter, orderby_filter, limit)
 
191
 
 
192
    def _retrieve_alarms(self, query_filter, orderby, limit):
 
193
        if limit is not None:
 
194
            alarms = self.db.alarm.find(query_filter,
 
195
                                        limit=limit,
 
196
                                        sort=orderby)
 
197
        else:
 
198
            alarms = self.db.alarm.find(query_filter, sort=orderby)
 
199
 
 
200
        for alarm in alarms:
 
201
            a = {}
 
202
            a.update(alarm)
 
203
            del a['_id']
 
204
            self._ensure_encapsulated_rule_format(a)
 
205
            self._ensure_time_constraints(a)
 
206
            yield models.Alarm(**a)
 
207
 
 
208
    def _retrieve_alarm_changes(self, query_filter, orderby, limit):
 
209
        if limit is not None:
 
210
            alarms_history = self.db.alarm_history.find(query_filter,
 
211
                                                        limit=limit,
 
212
                                                        sort=orderby)
 
213
        else:
 
214
            alarms_history = self.db.alarm_history.find(
 
215
                query_filter, sort=orderby)
 
216
 
 
217
        for alarm_history in alarms_history:
 
218
            ah = {}
 
219
            ah.update(alarm_history)
 
220
            del ah['_id']
 
221
            yield models.AlarmChange(**ah)
 
222
 
 
223
    @classmethod
 
224
    def _ensure_encapsulated_rule_format(cls, alarm):
 
225
        """Ensure the alarm returned by the storage have the correct format.
 
226
 
 
227
        The previous format looks like:
 
228
        {
 
229
            'alarm_id': '0ld-4l3rt',
 
230
            'enabled': True,
 
231
            'name': 'old-alert',
 
232
            'description': 'old-alert',
 
233
            'timestamp': None,
 
234
            'meter_name': 'cpu',
 
235
            'user_id': 'me',
 
236
            'project_id': 'and-da-boys',
 
237
            'comparison_operator': 'lt',
 
238
            'threshold': 36,
 
239
            'statistic': 'count',
 
240
            'evaluation_periods': 1,
 
241
            'period': 60,
 
242
            'state': "insufficient data",
 
243
            'state_timestamp': None,
 
244
            'ok_actions': [],
 
245
            'alarm_actions': ['http://nowhere/alarms'],
 
246
            'insufficient_data_actions': [],
 
247
            'repeat_actions': False,
 
248
            'matching_metadata': {'key': 'value'}
 
249
            # or 'matching_metadata': [{'key': 'key', 'value': 'value'}]
 
250
        }
 
251
        """
 
252
 
 
253
        if isinstance(alarm.get('rule'), dict):
 
254
            return
 
255
 
 
256
        alarm['type'] = 'threshold'
 
257
        alarm['rule'] = {}
 
258
        alarm['matching_metadata'] = cls._decode_matching_metadata(
 
259
            alarm['matching_metadata'])
 
260
        for field in ['period', 'evaluation_periods', 'threshold',
 
261
                      'statistic', 'comparison_operator', 'meter_name']:
 
262
            if field in alarm:
 
263
                alarm['rule'][field] = alarm[field]
 
264
                del alarm[field]
 
265
 
 
266
        query = []
 
267
        for key in alarm['matching_metadata']:
 
268
            query.append({'field': key,
 
269
                          'op': 'eq',
 
270
                          'value': alarm['matching_metadata'][key],
 
271
                          'type': 'string'})
 
272
        del alarm['matching_metadata']
 
273
        alarm['rule']['query'] = query
 
274
 
 
275
    @staticmethod
 
276
    def _decode_matching_metadata(matching_metadata):
 
277
        if isinstance(matching_metadata, dict):
 
278
            # note(sileht): keep compatibility with alarm
 
279
            # with matching_metadata as a dict
 
280
            return matching_metadata
 
281
        else:
 
282
            new_matching_metadata = {}
 
283
            for elem in matching_metadata:
 
284
                new_matching_metadata[elem['key']] = elem['value']
 
285
            return new_matching_metadata
 
286
 
 
287
    @staticmethod
 
288
    def _ensure_time_constraints(alarm):
 
289
        """Ensures the alarm has a time constraints field."""
 
290
        if 'time_constraints' not in alarm:
 
291
            alarm['time_constraints'] = []