1
# -*- encoding: utf-8 -*-
3
# Copyright © 2013 Red Hat, Inc
5
# Author: Eoghan Glynn <eglynn@redhat.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
"""Tests for ceilometer/alarm/threshold_evaluation.py
24
from ceilometer.alarm import threshold_evaluation
25
from ceilometer.openstack.common import timeutils
26
from ceilometer.storage import models
27
from ceilometer.tests import base
28
from ceilometerclient import exc
29
from ceilometerclient.v2 import statistics
32
class TestEvaluate(base.TestCase):
34
super(TestEvaluate, self).setUp()
35
self.api_client = mock.Mock()
36
self.notifier = mock.MagicMock()
38
models.Alarm(name='instance_running_hot',
39
meter_name='cpu_util',
40
comparison_operator='gt',
47
alarm_id=str(uuid.uuid4()),
48
matching_metadata={'resource_id':
50
models.Alarm(name='group_running_idle',
51
meter_name='cpu_util',
52
comparison_operator='le',
59
alarm_id=str(uuid.uuid4()),
60
matching_metadata={'metadata.user_metadata.AS':
63
self.evaluator = threshold_evaluation.Evaluator(self.notifier)
64
self.evaluator.assign_alarms(self.alarms)
67
super(TestEvaluate, self).tearDown()
68
timeutils.utcnow.override_time = None
71
def _get_stat(attr, value):
72
return statistics.Statistics(None, {attr: value})
74
def _set_all_alarms(self, state):
75
for alarm in self.alarms:
78
def _assert_all_alarms(self, state):
79
for alarm in self.alarms:
80
self.assertEqual(alarm.state, state)
82
def test_retry_transient_api_failure(self):
83
with mock.patch('ceilometerclient.client.get_client',
84
return_value=self.api_client):
85
broken = exc.CommunicationError(message='broken')
86
avgs = [self._get_stat('avg', self.alarms[0].threshold - v)
88
maxs = [self._get_stat('max', self.alarms[1].threshold + v)
89
for v in xrange(1, 4)]
90
self.api_client.statistics.list.side_effect = [broken,
94
self.evaluator.evaluate()
95
self._assert_all_alarms('insufficient data')
96
self.evaluator.evaluate()
97
self._assert_all_alarms('ok')
99
def test_simple_insufficient(self):
100
self._set_all_alarms('ok')
101
with mock.patch('ceilometerclient.client.get_client',
102
return_value=self.api_client):
103
self.api_client.statistics.list.return_value = []
104
self.evaluator.evaluate()
105
self._assert_all_alarms('insufficient data')
106
expected = [mock.call(alarm.alarm_id, state='insufficient data')
107
for alarm in self.alarms]
108
update_calls = self.api_client.alarms.update.call_args_list
109
self.assertEqual(update_calls, expected)
110
expected = [mock.call(alarm,
112
('%d datapoints are unknown' %
113
alarm.evaluation_periods))
114
for alarm in self.alarms]
115
self.assertEqual(self.notifier.notify.call_args_list, expected)
117
def test_disabled_is_skipped(self):
118
self._set_all_alarms('ok')
119
self.alarms[1].enabled = False
120
with mock.patch('ceilometerclient.client.get_client',
121
return_value=self.api_client):
122
self.api_client.statistics.list.return_value = []
123
self.evaluator.evaluate()
124
self.assertEqual(self.alarms[0].state, 'insufficient data')
125
self.assertEqual(self.alarms[1].state, 'ok')
126
self.api_client.alarms.update.assert_called_once_with(
127
self.alarms[0].alarm_id,
128
state='insufficient data'
130
self.notifier.notify.assert_called_once_with(
136
def test_simple_alarm_trip(self):
137
self._set_all_alarms('ok')
138
with mock.patch('ceilometerclient.client.get_client',
139
return_value=self.api_client):
140
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
141
for v in xrange(1, 6)]
142
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
144
self.api_client.statistics.list.side_effect = [avgs, maxs]
145
self.evaluator.evaluate()
146
self._assert_all_alarms('alarm')
147
expected = [mock.call(alarm.alarm_id, state='alarm')
148
for alarm in self.alarms]
149
update_calls = self.api_client.alarms.update.call_args_list
150
self.assertEqual(update_calls, expected)
151
reasons = ['Transition to alarm due to 5 samples outside'
152
' threshold, most recent: 85.0',
153
'Transition to alarm due to 4 samples outside'
154
' threshold, most recent: 7.0']
155
expected = [mock.call(alarm, 'ok', reason)
156
for alarm, reason in zip(self.alarms, reasons)]
157
self.assertEqual(self.notifier.notify.call_args_list, expected)
159
def test_simple_alarm_clear(self):
160
self._set_all_alarms('alarm')
161
with mock.patch('ceilometerclient.client.get_client',
162
return_value=self.api_client):
163
avgs = [self._get_stat('avg', self.alarms[0].threshold - v)
165
maxs = [self._get_stat('max', self.alarms[1].threshold + v)
166
for v in xrange(1, 5)]
167
self.api_client.statistics.list.side_effect = [avgs, maxs]
168
self.evaluator.evaluate()
169
self._assert_all_alarms('ok')
170
expected = [mock.call(alarm.alarm_id, state='ok')
171
for alarm in self.alarms]
172
update_calls = self.api_client.alarms.update.call_args_list
173
self.assertEqual(update_calls, expected)
174
reasons = ['Transition to ok due to 5 samples inside'
175
' threshold, most recent: 76.0',
176
'Transition to ok due to 4 samples inside'
177
' threshold, most recent: 14.0']
178
expected = [mock.call(alarm, 'alarm', reason)
179
for alarm, reason in zip(self.alarms, reasons)]
180
self.assertEqual(self.notifier.notify.call_args_list, expected)
182
def test_equivocal_from_known_state(self):
183
self._set_all_alarms('ok')
184
with mock.patch('ceilometerclient.client.get_client',
185
return_value=self.api_client):
186
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
188
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
189
for v in xrange(-1, 3)]
190
self.api_client.statistics.list.side_effect = [avgs, maxs]
191
self.evaluator.evaluate()
192
self._assert_all_alarms('ok')
193
self.assertEqual(self.api_client.alarms.update.call_args_list,
195
self.assertEqual(self.notifier.notify.call_args_list, [])
197
def test_equivocal_from_known_state_and_repeat_actions(self):
198
self._set_all_alarms('ok')
199
self.alarms[1].repeat_actions = True
200
with mock.patch('ceilometerclient.client.get_client',
201
return_value=self.api_client):
202
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
204
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
205
for v in xrange(-1, 3)]
206
self.api_client.statistics.list.side_effect = [avgs, maxs]
207
self.evaluator.evaluate()
208
self._assert_all_alarms('ok')
209
self.assertEqual(self.api_client.alarms.update.call_args_list,
211
reason = 'Remaining as ok due to 4 samples inside' \
212
' threshold, most recent: 8.0'
213
expected = [mock.call(self.alarms[1], 'ok', reason)]
214
self.assertEqual(self.notifier.notify.call_args_list, expected)
216
def test_unequivocal_from_known_state_and_repeat_actions(self):
217
self._set_all_alarms('alarm')
218
self.alarms[1].repeat_actions = True
219
with mock.patch('ceilometerclient.client.get_client',
220
return_value=self.api_client):
221
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
222
for v in xrange(1, 6)]
223
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
225
self.api_client.statistics.list.side_effect = [avgs, maxs]
226
self.evaluator.evaluate()
227
self._assert_all_alarms('alarm')
228
self.assertEqual(self.api_client.alarms.update.call_args_list,
230
reason = 'Remaining as alarm due to 4 samples outside' \
231
' threshold, most recent: 7.0'
232
expected = [mock.call(self.alarms[1], 'alarm', reason)]
233
self.assertEqual(self.notifier.notify.call_args_list, expected)
235
def test_state_change_and_repeat_actions(self):
236
self._set_all_alarms('ok')
237
self.alarms[0].repeat_actions = True
238
self.alarms[1].repeat_actions = True
239
with mock.patch('ceilometerclient.client.get_client',
240
return_value=self.api_client):
241
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
242
for v in xrange(1, 6)]
243
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
245
self.api_client.statistics.list.side_effect = [avgs, maxs]
246
self.evaluator.evaluate()
247
self._assert_all_alarms('alarm')
248
expected = [mock.call(alarm.alarm_id, state='alarm')
249
for alarm in self.alarms]
250
update_calls = self.api_client.alarms.update.call_args_list
251
self.assertEqual(update_calls, expected)
252
reasons = ['Transition to alarm due to 5 samples outside'
253
' threshold, most recent: 85.0',
254
'Transition to alarm due to 4 samples outside'
255
' threshold, most recent: 7.0']
256
expected = [mock.call(alarm, 'ok', reason)
257
for alarm, reason in zip(self.alarms, reasons)]
258
self.assertEqual(self.notifier.notify.call_args_list, expected)
260
def test_equivocal_from_unknown(self):
261
self._set_all_alarms('insufficient data')
262
with mock.patch('ceilometerclient.client.get_client',
263
return_value=self.api_client):
264
avgs = [self._get_stat('avg', self.alarms[0].threshold + v)
265
for v in xrange(1, 6)]
266
maxs = [self._get_stat('max', self.alarms[1].threshold - v)
268
self.api_client.statistics.list.side_effect = [avgs, maxs]
269
self.evaluator.evaluate()
270
self._assert_all_alarms('alarm')
271
expected = [mock.call(alarm.alarm_id, state='alarm')
272
for alarm in self.alarms]
273
update_calls = self.api_client.alarms.update.call_args_list
274
self.assertEqual(update_calls, expected)
275
reasons = ['Transition to alarm due to 5 samples outside'
276
' threshold, most recent: 85.0',
277
'Transition to alarm due to 4 samples outside'
278
' threshold, most recent: 7.0']
279
expected = [mock.call(alarm, 'insufficient data', reason)
280
for alarm, reason in zip(self.alarms, reasons)]
281
self.assertEqual(self.notifier.notify.call_args_list, expected)
283
def test_bound_duration(self):
284
timeutils.utcnow.override_time = datetime.datetime(2012, 7, 2, 10, 45)
285
constraint = self.evaluator._bound_duration(self.alarms[0], [])
286
self.assertEqual(constraint, [
287
{'field': 'timestamp',
289
'value': timeutils.utcnow().isoformat()},
290
{'field': 'timestamp',
292
'value': '2012-07-02T10:39:00'},