~ubuntu-branches/ubuntu/saucy/ceilometer/saucy-proposed

« back to all changes in this revision

Viewing changes to tests/alarm/test_threshold_evaluation.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-10-03 08:40:40 UTC
  • mfrom: (1.1.7)
  • Revision ID: package-import@ubuntu.com-20131003084040-47e1qrwl5s5bkwjh
Tags: 2013.2~rc1-0ubuntu1
* debian/patches/fix-setup-requirements.patch: Dropped no longer
  needed.
* debian/patches/skip-database.patch: Refreshed
* debian/control:
  - Add versioned dependency for python-pbr.
  - Bump version dependency for python-webob.
  - Add versioned dependency for alembic.
  - Bump versioned dependency for python-sqlalchemy.
  - Add versioned dependency for python-pymongo.
  - Add versioned dependency for python-eventlet.
  - Dropped python-extras dependency.
  - Bump versioned dependency for python-flask.
  - Bump versioned dependency for python-stevedore.
  - Add versioned dependency for python-glanceclient.
  - Bump versioned dependency for python-novaclient.
  - Bump versioned dependency for python-keystoneclient.
  - Bump versioned dependency for python-ceilometerclient.
  - Add versioned dependency for python-lxml.
  - Bump versioned dependency for python-wsme.
  - Dropped python-netifaces dependency.
  - Added python-httplib2 build dependency.
  - Bump versioned dependency for python-fixtures.
  - Bump versioned dependency for testrepository.
  - Added versioned dependency for python-testtools.
  - Added versioned dependency for python-swiftclient.
  - Dropped python-cinderclient dependency.
  - Dropped python-lockfile dependency.
  - Dropped python-setuptoools-git dependency.
  - Dropped python-unittest2 dependency.
  - Dropped python-d2to1 dependency.
  - Added versioned dependency for python-testtools.
  - Added binary dependency for python-netaddr.
  - Add python-six as a dependency.
* debian/patches/fix-setup-requirements.patch: Bump sqlachemy version.
* debian/ceilometer-common.install:
  - Dropped ceilometer-alarm-singleton, no longer exists.
  - Added usr/bin/ceilometer-alarm-evaluator.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- encoding: utf-8 -*-
2
 
#
3
 
# Copyright © 2013 Red Hat, Inc
4
 
#
5
 
# Author: Eoghan Glynn <eglynn@redhat.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
 
"""Tests for ceilometer/alarm/threshold_evaluation.py
19
 
"""
20
 
import datetime
21
 
import mock
22
 
import uuid
23
 
 
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
30
 
 
31
 
 
32
 
class TestEvaluate(base.TestCase):
33
 
    def setUp(self):
34
 
        super(TestEvaluate, self).setUp()
35
 
        self.api_client = mock.Mock()
36
 
        self.notifier = mock.MagicMock()
37
 
        self.alarms = [
38
 
            models.Alarm(name='instance_running_hot',
39
 
                         meter_name='cpu_util',
40
 
                         comparison_operator='gt',
41
 
                         threshold=80.0,
42
 
                         evaluation_periods=5,
43
 
                         statistic='avg',
44
 
                         user_id='foobar',
45
 
                         project_id='snafu',
46
 
                         period=60,
47
 
                         alarm_id=str(uuid.uuid4()),
48
 
                         matching_metadata={'resource_id':
49
 
                                            'my_instance'}),
50
 
            models.Alarm(name='group_running_idle',
51
 
                         meter_name='cpu_util',
52
 
                         comparison_operator='le',
53
 
                         threshold=10.0,
54
 
                         statistic='max',
55
 
                         evaluation_periods=4,
56
 
                         user_id='foobar',
57
 
                         project_id='snafu',
58
 
                         period=300,
59
 
                         alarm_id=str(uuid.uuid4()),
60
 
                         matching_metadata={'metadata.user_metadata.AS':
61
 
                                            'my_group'}),
62
 
        ]
63
 
        self.evaluator = threshold_evaluation.Evaluator(self.notifier)
64
 
        self.evaluator.assign_alarms(self.alarms)
65
 
 
66
 
    def tearDown(self):
67
 
        super(TestEvaluate, self).tearDown()
68
 
        timeutils.utcnow.override_time = None
69
 
 
70
 
    @staticmethod
71
 
    def _get_stat(attr, value):
72
 
        return statistics.Statistics(None, {attr: value})
73
 
 
74
 
    def _set_all_alarms(self, state):
75
 
        for alarm in self.alarms:
76
 
            alarm.state = state
77
 
 
78
 
    def _assert_all_alarms(self, state):
79
 
        for alarm in self.alarms:
80
 
            self.assertEqual(alarm.state, state)
81
 
 
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)
87
 
                    for v in xrange(5)]
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,
91
 
                                                           broken,
92
 
                                                           avgs,
93
 
                                                           maxs]
94
 
            self.evaluator.evaluate()
95
 
            self._assert_all_alarms('insufficient data')
96
 
            self.evaluator.evaluate()
97
 
            self._assert_all_alarms('ok')
98
 
 
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,
111
 
                                  'ok',
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)
116
 
 
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'
129
 
            )
130
 
            self.notifier.notify.assert_called_once_with(
131
 
                self.alarms[0],
132
 
                'ok',
133
 
                mock.ANY
134
 
            )
135
 
 
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)
143
 
                    for v in xrange(4)]
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)
158
 
 
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)
164
 
                    for v in xrange(5)]
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)
181
 
 
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)
187
 
                    for v in xrange(5)]
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,
194
 
                             [])
195
 
            self.assertEqual(self.notifier.notify.call_args_list, [])
196
 
 
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)
203
 
                    for v in xrange(5)]
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,
210
 
                             [])
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)
215
 
 
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)
224
 
                    for v in xrange(4)]
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,
229
 
                             [])
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)
234
 
 
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)
244
 
                    for v in xrange(4)]
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)
259
 
 
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)
267
 
                    for v in xrange(4)]
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)
282
 
 
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',
288
 
             'op': 'le',
289
 
             'value': timeutils.utcnow().isoformat()},
290
 
            {'field': 'timestamp',
291
 
             'op': 'ge',
292
 
             'value': '2012-07-02T10:39:00'},
293
 
        ])