2
# Copyright Ericsson AB 2013. All rights reserved
4
# Authors: Ildiko Vancsa <ildiko.vancsa@ericsson.com>
5
# Balazs Gibizer <balazs.gibizer@ericsson.com>
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
11
# http://www.apache.org/licenses/LICENSE-2.0
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
18
"""Common functions for MongoDB and DB2 backends
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
31
LOG = log.getLogger(__name__)
34
COMMON_AVAILABLE_CAPABILITIES = {
35
'alarms': {'query': {'simple': True,
37
'history': {'query': {'simple': True,
42
AVAILABLE_STORAGE_CAPABILITIES = {
43
'storage': {'production_ready': True},
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)
52
STORAGE_CAPABILITIES = utils.update_nested(
53
base.Connection.STORAGE_CAPABILITIES,
54
AVAILABLE_STORAGE_CAPABILITIES,
57
def update_alarm(self, alarm):
59
data = alarm.as_dict()
62
{'alarm_id': alarm.alarm_id},
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)
72
create_alarm = update_alarm
74
def delete_alarm(self, alarm_id):
75
"""Delete an alarm."""
76
self.db.alarm.remove({'alarm_id': alarm_id})
78
def record_alarm_change(self, alarm_change):
79
"""Record alarm change event."""
80
self.db.alarm_history.insert(alarm_change.copy())
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
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.
96
raise ceilometer.NotImplementedError('Pagination not implemented')
101
if project is not None:
102
q['project_id'] = project
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:
111
if meter is not None:
112
q['rule.meter_name'] = meter
114
return self._retrieve_alarms(q, [], None)
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
122
Changes are always sorted in reverse order of occurrence, given
123
the importance of currency.
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).
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
143
q = dict(alarm_id=alarm_id)
144
if on_behalf_of is not None:
145
q['on_behalf_of'] = on_behalf_of
148
if project is not None:
149
q['project_id'] = project
152
if start_timestamp or end_timestamp:
153
ts_range = pymongo_utils.make_timestamp_range(start_timestamp,
158
q['timestamp'] = ts_range
160
return self._retrieve_alarm_changes(q,
162
pymongo.DESCENDING)],
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,
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,
177
def _retrieve_data(self, filter_expr, orderby, limit, model):
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)
188
retrieve = {models.Alarm: self._retrieve_alarms,
189
models.AlarmChange: self._retrieve_alarm_changes}
190
return retrieve[model](query_filter, orderby_filter, limit)
192
def _retrieve_alarms(self, query_filter, orderby, limit):
193
if limit is not None:
194
alarms = self.db.alarm.find(query_filter,
198
alarms = self.db.alarm.find(query_filter, sort=orderby)
204
self._ensure_encapsulated_rule_format(a)
205
self._ensure_time_constraints(a)
206
yield models.Alarm(**a)
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,
214
alarms_history = self.db.alarm_history.find(
215
query_filter, sort=orderby)
217
for alarm_history in alarms_history:
219
ah.update(alarm_history)
221
yield models.AlarmChange(**ah)
224
def _ensure_encapsulated_rule_format(cls, alarm):
225
"""Ensure the alarm returned by the storage have the correct format.
227
The previous format looks like:
229
'alarm_id': '0ld-4l3rt',
232
'description': 'old-alert',
236
'project_id': 'and-da-boys',
237
'comparison_operator': 'lt',
239
'statistic': 'count',
240
'evaluation_periods': 1,
242
'state': "insufficient data",
243
'state_timestamp': None,
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'}]
253
if isinstance(alarm.get('rule'), dict):
256
alarm['type'] = 'threshold'
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']:
263
alarm['rule'][field] = alarm[field]
267
for key in alarm['matching_metadata']:
268
query.append({'field': key,
270
'value': alarm['matching_metadata'][key],
272
del alarm['matching_metadata']
273
alarm['rule']['query'] = query
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
282
new_matching_metadata = {}
283
for elem in matching_metadata:
284
new_matching_metadata[elem['key']] = elem['value']
285
return new_matching_metadata
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'] = []