272
302
update_calls = self.api_client.alarms.set_state.call_args_list
273
303
self.assertEqual(update_calls, expected)
274
304
reasons = ['Transition to alarm due to 5 samples outside'
275
' threshold, most recent: 85.0',
305
' threshold, most recent: %s' % avgs[-1].avg,
276
306
'Transition to alarm due to 4 samples outside'
277
' threshold, most recent: 7.0']
278
expected = [mock.call(alarm, 'insufficient data', reason)
279
for alarm, reason in zip(self.alarms, reasons)]
307
' threshold, most recent: %s' % maxs[-1].max]
308
reason_datas = [self._reason_data('outside', 5, avgs[-1].avg),
309
self._reason_data('outside', 4, maxs[-1].max)]
310
expected = [mock.call(alarm, 'insufficient data',
312
for alarm, reason, reason_data
313
in zip(self.alarms, reasons, reason_datas)]
280
314
self.assertEqual(self.notifier.notify.call_args_list, expected)
282
def test_bound_duration(self):
316
def _do_test_bound_duration(self, start, exclude_outliers=None):
317
alarm = self.alarms[0]
318
if exclude_outliers is not None:
319
alarm.rule['exclude_outliers'] = exclude_outliers
283
320
timeutils.utcnow.override_time = datetime.datetime(2012, 7, 2, 10, 45)
284
constraint = self.evaluator._bound_duration(self.alarms[0], [])
321
constraint = self.evaluator._bound_duration(alarm, [])
285
322
self.assertEqual(constraint, [
286
323
{'field': 'timestamp',
288
325
'value': timeutils.utcnow().isoformat()},
289
326
{'field': 'timestamp',
291
'value': '2012-07-02T10:39:00'},
331
def test_bound_duration_outlier_exclusion_defaulted(self):
332
self._do_test_bound_duration('2012-07-02T10:39:00')
334
def test_bound_duration_outlier_exclusion_clear(self):
335
self._do_test_bound_duration('2012-07-02T10:39:00', False)
337
def test_bound_duration_outlier_exclusion_set(self):
338
self._do_test_bound_duration('2012-07-02T10:35:00', True)
294
340
def test_threshold_endpoint_types(self):
295
341
endpoint_types = ["internalURL", "publicURL"]
296
342
for endpoint_type in endpoint_types:
311
357
os_endpoint_type=conf.os_endpoint_type)]
312
358
actual = client.call_args_list
313
359
self.assertEqual(actual, expected)
361
def _do_test_simple_alarm_trip_outlier_exclusion(self, exclude_outliers):
362
self._set_all_rules('exclude_outliers', exclude_outliers)
363
self._set_all_alarms('ok')
364
with mock.patch('ceilometerclient.client.get_client',
365
return_value=self.api_client):
366
# most recent datapoints inside threshold but with
367
# anomolously low sample count
368
threshold = self.alarms[0].rule['threshold']
369
avgs = [self._get_stat('avg',
370
threshold + (v if v < 10 else -v),
371
count=20 if v < 10 else 1)
372
for v in xrange(1, 11)]
373
threshold = self.alarms[1].rule['threshold']
374
maxs = [self._get_stat('max',
375
threshold - (v if v < 7 else -v),
376
count=20 if v < 7 else 1)
378
self.api_client.statistics.list.side_effect = [avgs, maxs]
379
self._evaluate_all_alarms()
380
self._assert_all_alarms('alarm' if exclude_outliers else 'ok')
382
expected = [mock.call(alarm.alarm_id, state='alarm')
383
for alarm in self.alarms]
384
update_calls = self.api_client.alarms.set_state.call_args_list
385
self.assertEqual(update_calls, expected)
386
reasons = ['Transition to alarm due to 5 samples outside'
387
' threshold, most recent: %s' % avgs[-2].avg,
388
'Transition to alarm due to 4 samples outside'
389
' threshold, most recent: %s' % maxs[-2].max]
390
reason_datas = [self._reason_data('outside', 5, avgs[-2].avg),
391
self._reason_data('outside', 4, maxs[-2].max)]
392
expected = [mock.call(alarm, 'ok', reason, reason_data)
393
for alarm, reason, reason_data
394
in zip(self.alarms, reasons, reason_datas)]
395
self.assertEqual(self.notifier.notify.call_args_list, expected)
397
def test_simple_alarm_trip_with_outlier_exclusion(self):
398
self. _do_test_simple_alarm_trip_outlier_exclusion(True)
400
def test_simple_alarm_no_trip_without_outlier_exclusion(self):
401
self. _do_test_simple_alarm_trip_outlier_exclusion(False)
403
def _do_test_simple_alarm_clear_outlier_exclusion(self, exclude_outliers):
404
self._set_all_rules('exclude_outliers', exclude_outliers)
405
self._set_all_alarms('alarm')
406
with mock.patch('ceilometerclient.client.get_client',
407
return_value=self.api_client):
408
# most recent datapoints outside threshold but with
409
# anomolously low sample count
410
threshold = self.alarms[0].rule['threshold']
411
avgs = [self._get_stat('avg',
412
threshold - (v if v < 9 else -v),
413
count=20 if v < 9 else 1)
415
threshold = self.alarms[1].rule['threshold']
416
maxs = [self._get_stat('max',
417
threshold + (v if v < 8 else -v),
418
count=20 if v < 8 else 1)
419
for v in xrange(1, 9)]
420
self.api_client.statistics.list.side_effect = [avgs, maxs]
421
self._evaluate_all_alarms()
422
self._assert_all_alarms('ok' if exclude_outliers else 'alarm')
424
expected = [mock.call(alarm.alarm_id, state='ok')
425
for alarm in self.alarms]
426
update_calls = self.api_client.alarms.set_state.call_args_list
427
self.assertEqual(update_calls, expected)
428
reasons = ['Transition to ok due to 5 samples inside'
429
' threshold, most recent: %s' % avgs[-2].avg,
430
'Transition to ok due to 4 samples inside'
431
' threshold, most recent: %s' % maxs[-2].max]
432
reason_datas = [self._reason_data('inside', 5, avgs[-2].avg),
433
self._reason_data('inside', 4, maxs[-2].max)]
434
expected = [mock.call(alarm, 'alarm', reason, reason_data)
435
for alarm, reason, reason_data
436
in zip(self.alarms, reasons, reason_datas)]
437
self.assertEqual(self.notifier.notify.call_args_list, expected)
439
def test_simple_alarm_clear_with_outlier_exclusion(self):
440
self. _do_test_simple_alarm_clear_outlier_exclusion(True)
442
def test_simple_alarm_no_clear_without_outlier_exclusion(self):
443
self. _do_test_simple_alarm_clear_outlier_exclusion(False)
445
def test_state_change_inside_time_constraint(self):
446
self._set_all_alarms('ok')
447
self.alarms[0].time_constraints = [
449
'description': 'test',
450
'start': '0 11 * * *', # daily at 11:00
451
'duration': 10800, # 3 hours
452
'timezone': 'Europe/Ljubljana'}
454
self.alarms[1].time_constraints = self.alarms[0].time_constraints
455
dt = datetime.datetime(2014, 1, 1, 12, 0, 0,
456
tzinfo=pytz.timezone('Europe/Ljubljana'))
457
with mock.patch('ceilometerclient.client.get_client',
458
return_value=self.api_client):
459
timeutils.set_time_override(dt.astimezone(pytz.UTC))
460
# the following part based on test_simple_insufficient
461
self.api_client.statistics.list.return_value = []
462
self._evaluate_all_alarms()
463
self._assert_all_alarms('insufficient data')
464
expected = [mock.call(alarm.alarm_id,
465
state='insufficient data')
466
for alarm in self.alarms]
467
update_calls = self.api_client.alarms.set_state.call_args_list
468
self.assertEqual(expected, update_calls,
469
"Alarm should change state if the current "
470
"time is inside its time constraint.")
471
expected = [mock.call(
474
('%d datapoints are unknown'
475
% alarm.rule['evaluation_periods']),
476
self._reason_data('unknown',
477
alarm.rule['evaluation_periods'],
479
for alarm in self.alarms]
480
self.assertEqual(expected, self.notifier.notify.call_args_list)
482
def test_no_state_change_outside_time_constraint(self):
483
self._set_all_alarms('ok')
484
self.alarms[0].time_constraints = [
486
'description': 'test',
487
'start': '0 11 * * *', # daily at 11:00
488
'duration': 10800, # 3 hours
489
'timezone': 'Europe/Ljubljana'}
491
self.alarms[1].time_constraints = self.alarms[0].time_constraints
492
dt = datetime.datetime(2014, 1, 1, 15, 0, 0,
493
tzinfo=pytz.timezone('Europe/Ljubljana'))
494
with mock.patch('ceilometerclient.client.get_client',
495
return_value=self.api_client):
496
timeutils.set_time_override(dt.astimezone(pytz.UTC))
497
self.api_client.statistics.list.return_value = []
498
self._evaluate_all_alarms()
499
self._assert_all_alarms('ok')
500
update_calls = self.api_client.alarms.set_state.call_args_list
501
self.assertEqual([], update_calls,
502
"Alarm should not change state if the current "
503
" time is outside its time constraint.")
504
self.assertEqual([], self.notifier.notify.call_args_list)