Remove deprecated combination alarms
Change-Id: I8977c69d0c73261782ddfcb295b7cfe1d76f9de3
This commit is contained in:
parent
021a3e1613
commit
1e63626e9c
@ -27,11 +27,4 @@ OPTS = [
|
||||
'auth_mode',
|
||||
default="keystone",
|
||||
help="Authentication mode to use. Unset to disable authentication"),
|
||||
cfg.BoolOpt('enable_combination_alarms',
|
||||
default=False,
|
||||
help="Enable deprecated combination alarms.",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="Combination alarms are deprecated. "
|
||||
"This option and combination alarms will be "
|
||||
"removed in Aodh 5.0."),
|
||||
]
|
||||
|
@ -1,76 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from aodh.api.controllers.v2 import base
|
||||
from aodh.api.controllers.v2 import utils as v2_utils
|
||||
from aodh.i18n import _
|
||||
|
||||
|
||||
class AlarmCombinationRule(base.AlarmRule):
|
||||
"""Alarm Combination Rule
|
||||
|
||||
Describe when to trigger the alarm based on combining the state of
|
||||
other alarms.
|
||||
"""
|
||||
|
||||
operator = base.AdvEnum('operator', str, 'or', 'and', default='and')
|
||||
"How to combine the sub-alarms"
|
||||
|
||||
alarm_ids = wsme.wsattr([wtypes.text], mandatory=True)
|
||||
"List of alarm identifiers to combine"
|
||||
|
||||
@property
|
||||
def default_description(self):
|
||||
joiner = ' %s ' % self.operator
|
||||
return _('Combined state of alarms %s') % joiner.join(self.alarm_ids)
|
||||
|
||||
def as_dict(self):
|
||||
return self.as_dict_from_keys(['operator', 'alarm_ids'])
|
||||
|
||||
@staticmethod
|
||||
def validate(rule):
|
||||
rule.alarm_ids = sorted(set(rule.alarm_ids), key=rule.alarm_ids.index)
|
||||
if len(rule.alarm_ids) <= 1:
|
||||
raise base.ClientSideError(_('Alarm combination rule should '
|
||||
'contain at least two different '
|
||||
'alarm ids.'))
|
||||
return rule
|
||||
|
||||
@staticmethod
|
||||
def validate_alarm(alarm):
|
||||
project = v2_utils.get_auth_project(
|
||||
alarm.project_id if alarm.project_id != wtypes.Unset else None)
|
||||
for id in alarm.combination_rule.alarm_ids:
|
||||
alarms = list(pecan.request.storage.get_alarms(
|
||||
alarm_id=id, project=project))
|
||||
if not alarms:
|
||||
raise base.AlarmNotFound(id, project)
|
||||
|
||||
@staticmethod
|
||||
def update_hook(alarm):
|
||||
# should check if there is any circle in the dependency, but for
|
||||
# efficiency reason, here only check alarm cannot depend on itself
|
||||
if alarm.alarm_id in alarm.combination_rule.alarm_ids:
|
||||
raise base.ClientSideError(
|
||||
_('Cannot specify alarm %s itself in combination rule') %
|
||||
alarm.alarm_id)
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(operator='or',
|
||||
alarm_ids=['739e99cb-c2ec-4718-b900-332502355f38',
|
||||
'153462d0-a9b8-4b5b-8175-9e4b05e9b856'])
|
@ -39,7 +39,6 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
import aodh
|
||||
from aodh.api.controllers.v2.alarm_rules import combination
|
||||
from aodh.api.controllers.v2 import base
|
||||
from aodh.api.controllers.v2 import utils as v2_utils
|
||||
from aodh.api import rbac
|
||||
@ -184,13 +183,7 @@ ACTIONS_SCHEMA = extension.ExtensionManager(
|
||||
|
||||
|
||||
class Alarm(base.Base):
|
||||
"""Representation of an alarm.
|
||||
|
||||
.. note::
|
||||
combination_rule and threshold_rule are mutually exclusive. The *type*
|
||||
of the alarm should be set to *threshold* or *combination* and the
|
||||
appropriate rule should be filled.
|
||||
"""
|
||||
"""Representation of an alarm."""
|
||||
|
||||
alarm_id = wtypes.text
|
||||
"The UUID of the alarm"
|
||||
@ -289,10 +282,6 @@ class Alarm(base.Base):
|
||||
|
||||
@staticmethod
|
||||
def check_rule(alarm):
|
||||
if (not pecan.request.cfg.api.enable_combination_alarms
|
||||
and alarm.type == 'combination'):
|
||||
raise base.ClientSideError("Unavailable alarm type")
|
||||
|
||||
rule = '%s_rule' % alarm.type
|
||||
if getattr(alarm, rule) in (wtypes.Unset, None):
|
||||
error = _("%(rule)s must be set for %(type)s"
|
||||
@ -354,7 +343,7 @@ class Alarm(base.Base):
|
||||
return cls(alarm_id=None,
|
||||
name="SwiftObjectAlarm",
|
||||
description="An alarm",
|
||||
type='combination',
|
||||
type='threshold',
|
||||
time_constraints=[AlarmTimeConstraint.sample().as_dict()],
|
||||
user_id="c96c887c216949acbdfbd8b494863567",
|
||||
project_id="c96c887c216949acbdfbd8b494863567",
|
||||
@ -367,7 +356,6 @@ class Alarm(base.Base):
|
||||
alarm_actions=["http://site:8000/alarm"],
|
||||
insufficient_data_actions=["http://site:8000/nodata"],
|
||||
repeat_actions=False,
|
||||
combination_rule=combination.AlarmCombinationRule.sample(),
|
||||
)
|
||||
|
||||
def as_dict(self, db_model):
|
||||
|
@ -37,8 +37,6 @@ def get_auth_project(on_behalf_of=None):
|
||||
# we must ensure for:
|
||||
# - threshold alarm, that an implicit query constraint on project_id is
|
||||
# added so that admin-level visibility on statistics is not leaked
|
||||
# - combination alarm, that alarm ids verification is scoped to
|
||||
# alarms owned by the alarm project.
|
||||
# Hence, for null auth_project (indicating admin-ness) we check if
|
||||
# the creating tenant differs from the tenant on whose behalf the
|
||||
# alarm is being created
|
||||
|
@ -1,139 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""A tool for converting combination alarms to composite alarms.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import argparse
|
||||
from oslo_log import log
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from aodh.i18n import _LI, _LW
|
||||
from aodh import service
|
||||
from aodh import storage
|
||||
from aodh.storage import models
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DependentAlarmNotFound(Exception):
|
||||
"""The dependent alarm is not found."""
|
||||
|
||||
def __init__(self, com_alarm_id, dependent_alarm_id):
|
||||
self.com_alarm_id = com_alarm_id
|
||||
self.dependent_alarm_id = dependent_alarm_id
|
||||
|
||||
|
||||
class UnsupportedSubAlarmType(Exception):
|
||||
"""Unsupported sub-alarm type."""
|
||||
|
||||
def __init__(self, sub_alarm_id, sub_alarm_type):
|
||||
self.sub_alarm_id = sub_alarm_id
|
||||
self.sub_alarm_type = sub_alarm_type
|
||||
|
||||
|
||||
def _generate_composite_rule(conn, combin_alarm):
|
||||
alarm_ids = combin_alarm.rule['alarm_ids']
|
||||
com_op = combin_alarm.rule['operator']
|
||||
LOG.info(_LI('Start converting combination alarm %(alarm)s, it depends on '
|
||||
'alarms: %(alarm_ids)s'),
|
||||
{'alarm': combin_alarm.alarm_id, 'alarm_ids': str(alarm_ids)})
|
||||
threshold_rules = []
|
||||
for alarm_id in alarm_ids:
|
||||
try:
|
||||
sub_alarm = list(conn.get_alarms(alarm_id=alarm_id))[0]
|
||||
except IndexError:
|
||||
raise DependentAlarmNotFound(combin_alarm.alarm_id, alarm_id)
|
||||
if sub_alarm.type in ('threshold', 'gnocchi_resources_threshold',
|
||||
'gnocchi_aggregation_by_metrics_threshold',
|
||||
'gnocchi_aggregation_by_resources_threshold'):
|
||||
sub_alarm.rule.update(type=sub_alarm.type)
|
||||
threshold_rules.append(sub_alarm.rule)
|
||||
elif sub_alarm.type == 'combination':
|
||||
threshold_rules.append(_generate_composite_rule(conn, sub_alarm))
|
||||
else:
|
||||
raise UnsupportedSubAlarmType(alarm_id, sub_alarm.type)
|
||||
else:
|
||||
return {com_op: threshold_rules}
|
||||
|
||||
|
||||
def get_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='for converting combination alarms to composite alarms.')
|
||||
parser.add_argument(
|
||||
'--delete-combination-alarm',
|
||||
default=False,
|
||||
type=bool,
|
||||
help='Delete the combination alarm when conversion is done.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--alarm-id',
|
||||
type=str,
|
||||
help='Only convert the alarm specified by this option.',
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def conversion():
|
||||
args = get_parser().parse_args()
|
||||
conf = service.prepare_service([])
|
||||
conn = storage.get_connection_from_config(conf)
|
||||
combination_alarms = list(conn.get_alarms(alarm_type='combination',
|
||||
alarm_id=args.alarm_id or None))
|
||||
count = 0
|
||||
for alarm in combination_alarms:
|
||||
new_name = 'From-combination: %s' % alarm.alarm_id
|
||||
n_alarm = list(conn.get_alarms(name=new_name, alarm_type='composite'))
|
||||
if n_alarm:
|
||||
LOG.warning(_LW('Alarm %(alarm)s has been already converted as '
|
||||
'composite alarm: %(n_alarm_id)s, skipped.'),
|
||||
{'alarm': alarm.alarm_id,
|
||||
'n_alarm_id': n_alarm[0].alarm_id})
|
||||
continue
|
||||
try:
|
||||
composite_rule = _generate_composite_rule(conn, alarm)
|
||||
except DependentAlarmNotFound as e:
|
||||
LOG.warning(_LW('The dependent alarm %(dep_alarm)s of alarm %'
|
||||
'(com_alarm)s not found, skipped.'),
|
||||
{'com_alarm': e.com_alarm_id,
|
||||
'dep_alarm': e.dependent_alarm_id})
|
||||
continue
|
||||
except UnsupportedSubAlarmType as e:
|
||||
LOG.warning(_LW('Alarm conversion from combination to composite '
|
||||
'only support combination alarms depending '
|
||||
'threshold alarms, the type of alarm %(alarm)s '
|
||||
'is: %(type)s, skipped.'),
|
||||
{'alarm': e.sub_alarm_id, 'type': e.sub_alarm_type})
|
||||
continue
|
||||
new_alarm = models.Alarm(**alarm.as_dict())
|
||||
new_alarm.alarm_id = uuidutils.generate_uuid()
|
||||
new_alarm.name = new_name
|
||||
new_alarm.type = 'composite'
|
||||
new_alarm.description = ('composite alarm converted from combination '
|
||||
'alarm: %s' % alarm.alarm_id)
|
||||
new_alarm.rule = composite_rule
|
||||
new_alarm.timestamp = datetime.datetime.now()
|
||||
conn.create_alarm(new_alarm)
|
||||
LOG.info(_LI('End Converting combination alarm %(s_alarm)s to '
|
||||
'composite alarm %(d_alarm)s'),
|
||||
{'s_alarm': alarm.alarm_id, 'd_alarm': new_alarm.alarm_id})
|
||||
count += 1
|
||||
if args.delete_combination_alarm:
|
||||
for alarm in combination_alarms:
|
||||
LOG.info(_LI('Deleting the combination alarm %s...'),
|
||||
alarm.alarm_id)
|
||||
conn.delete_alarm(alarm.alarm_id)
|
||||
LOG.info(_LI('%s combination alarms have been converted to composite '
|
||||
'alarms.'), count)
|
@ -1,116 +0,0 @@
|
||||
#
|
||||
# Copyright 2013 eNovance <licensing@enovance.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from oslo_log import log
|
||||
from six import moves
|
||||
|
||||
from aodh import evaluator
|
||||
from aodh.i18n import _, _LE
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
COMPARATORS = {'and': all, 'or': any}
|
||||
|
||||
|
||||
class CombinationEvaluator(evaluator.Evaluator):
|
||||
|
||||
def _get_alarm_state(self, alarm_id):
|
||||
try:
|
||||
alarms = self._storage_conn.get_alarms(alarm_id=alarm_id)
|
||||
except Exception:
|
||||
LOG.exception(_LE('alarm %s retrieval failed'), alarm_id)
|
||||
return None
|
||||
if not alarms:
|
||||
LOG.error(_LE("alarm %s doesn't exist anymore"), alarm_id)
|
||||
return None
|
||||
return list(alarms)[0].state
|
||||
|
||||
def _sufficient_states(self, alarm, states):
|
||||
"""Check for the sufficiency of the data for evaluation.
|
||||
|
||||
Ensure that there is sufficient data for evaluation,
|
||||
transitioning to unknown otherwise.
|
||||
"""
|
||||
# note(sileht): alarm can be evaluated only with
|
||||
# stable state of other alarm
|
||||
alarms_missing_states = [alarm_id for alarm_id, state in states
|
||||
if not state or state == evaluator.UNKNOWN]
|
||||
sufficient = len(alarms_missing_states) == 0
|
||||
if not sufficient and alarm.rule['operator'] == 'or':
|
||||
# if operator is 'or' and there is one alarm, then the combinated
|
||||
# alarm's state should be 'alarm'
|
||||
sufficient = bool([alarm_id for alarm_id, state in states
|
||||
if state == evaluator.ALARM])
|
||||
if not sufficient and alarm.state != evaluator.UNKNOWN:
|
||||
reason = (_('Alarms %(alarm_ids)s'
|
||||
' are in unknown state') %
|
||||
{'alarm_ids': ",".join(alarms_missing_states)})
|
||||
reason_data = self._reason_data(alarms_missing_states)
|
||||
self._refresh(alarm, evaluator.UNKNOWN, reason, reason_data)
|
||||
return sufficient
|
||||
|
||||
@staticmethod
|
||||
def _reason_data(alarm_ids):
|
||||
"""Create a reason data dictionary for this evaluator type."""
|
||||
return {'type': 'combination', 'alarm_ids': alarm_ids}
|
||||
|
||||
@classmethod
|
||||
def _reason(cls, alarm, state, underlying_states):
|
||||
"""Fabricate reason string."""
|
||||
transition = alarm.state != state
|
||||
|
||||
alarms_to_report = [alarm_id for alarm_id, alarm_state
|
||||
in underlying_states
|
||||
if alarm_state == state]
|
||||
reason_data = cls._reason_data(alarms_to_report)
|
||||
if transition:
|
||||
return (_('Transition to %(state)s due to alarms'
|
||||
' %(alarm_ids)s in state %(state)s') %
|
||||
{'state': state,
|
||||
'alarm_ids': ",".join(alarms_to_report)}), reason_data
|
||||
return (_('Remaining as %(state)s due to alarms'
|
||||
' %(alarm_ids)s in state %(state)s') %
|
||||
{'state': state,
|
||||
'alarm_ids': ",".join(alarms_to_report)}), reason_data
|
||||
|
||||
def _transition(self, alarm, underlying_states):
|
||||
"""Transition alarm state if necessary."""
|
||||
op = alarm.rule['operator']
|
||||
if COMPARATORS[op](s == evaluator.ALARM
|
||||
for __, s in underlying_states):
|
||||
state = evaluator.ALARM
|
||||
else:
|
||||
state = evaluator.OK
|
||||
|
||||
continuous = alarm.repeat_actions
|
||||
reason, reason_data = self._reason(alarm, state, underlying_states)
|
||||
if alarm.state != state or continuous:
|
||||
self._refresh(alarm, state, reason, reason_data)
|
||||
|
||||
def evaluate(self, alarm):
|
||||
if not self.within_time_constraint(alarm):
|
||||
LOG.debug('Attempted to evaluate alarm %s, but it is not '
|
||||
'within its time constraint.', alarm.alarm_id)
|
||||
return
|
||||
|
||||
states = zip(alarm.rule['alarm_ids'],
|
||||
moves.map(self._get_alarm_state, alarm.rule['alarm_ids']))
|
||||
|
||||
# states is consumed more than once, we need a list
|
||||
states = list(states)
|
||||
|
||||
if self._sufficient_states(alarm, states):
|
||||
self._transition(alarm, states)
|
@ -26,7 +26,6 @@ from six import moves
|
||||
import webtest
|
||||
|
||||
from aodh.api import app
|
||||
from aodh.cmd import alarm_conversion
|
||||
from aodh import messaging
|
||||
from aodh.storage import models
|
||||
from aodh.tests import constants
|
||||
@ -116,27 +115,7 @@ def default_alarms(auth_headers):
|
||||
'op': 'eq', 'value':
|
||||
auth_headers['X-Project-Id']}
|
||||
]),
|
||||
),
|
||||
models.Alarm(name='name4',
|
||||
type='combination',
|
||||
enabled=True,
|
||||
alarm_id='d',
|
||||
description='d',
|
||||
state='insufficient data',
|
||||
severity='low',
|
||||
state_timestamp=constants.MIN_DATETIME,
|
||||
timestamp=constants.MIN_DATETIME,
|
||||
ok_actions=[],
|
||||
insufficient_data_actions=[],
|
||||
alarm_actions=[],
|
||||
repeat_actions=False,
|
||||
user_id=auth_headers['X-User-Id'],
|
||||
project_id=auth_headers['X-Project-Id'],
|
||||
time_constraints=[],
|
||||
rule=dict(alarm_ids=['a', 'b'],
|
||||
operator='or'),
|
||||
),
|
||||
]
|
||||
)]
|
||||
|
||||
|
||||
class TestAlarmsBase(v2.FunctionalTest):
|
||||
@ -199,15 +178,12 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_list_alarms(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(set(['name1', 'name2', 'name3', 'name4']),
|
||||
self.assertEqual(3, len(data))
|
||||
self.assertEqual(set(['name1', 'name2', 'name3']),
|
||||
set(r['name'] for r in data))
|
||||
self.assertEqual(set(['meter.test', 'meter.mine']),
|
||||
set(r['threshold_rule']['meter_name']
|
||||
for r in data if 'threshold_rule' in r))
|
||||
self.assertEqual(set(['or']),
|
||||
set(r['combination_rule']['operator']
|
||||
for r in data if 'combination_rule' in r))
|
||||
|
||||
def test_alarms_query_with_timestamp(self):
|
||||
date_time = datetime.datetime(2012, 7, 2, 10, 41)
|
||||
@ -241,10 +217,10 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_alarms_query_with_state(self):
|
||||
alarm = models.Alarm(name='disabled',
|
||||
type='combination',
|
||||
type='threshold',
|
||||
enabled=False,
|
||||
alarm_id='d',
|
||||
description='d',
|
||||
alarm_id='c',
|
||||
description='c',
|
||||
state='ok',
|
||||
state_timestamp=constants.MIN_DATETIME,
|
||||
timestamp=constants.MIN_DATETIME,
|
||||
@ -255,7 +231,17 @@ class TestAlarms(TestAlarmsBase):
|
||||
user_id=self.auth_headers['X-User-Id'],
|
||||
project_id=self.auth_headers['X-Project-Id'],
|
||||
time_constraints=[],
|
||||
rule=dict(alarm_ids=['a', 'b'], operator='or'),
|
||||
rule=dict(comparison_operator='gt',
|
||||
threshold=3.0,
|
||||
statistic='avg',
|
||||
evaluation_periods=60,
|
||||
period=1,
|
||||
meter_name='meter.mine',
|
||||
query=[
|
||||
{'field': 'project_id',
|
||||
'op': 'eq', 'value':
|
||||
self.auth_headers['X-Project-Id']}
|
||||
]),
|
||||
severity='critical')
|
||||
self.alarm_conn.update_alarm(alarm)
|
||||
resp = self.get_json('/alarms',
|
||||
@ -308,10 +294,10 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_get_alarm_disabled(self):
|
||||
alarm = models.Alarm(name='disabled',
|
||||
type='combination',
|
||||
type='threshold',
|
||||
enabled=False,
|
||||
alarm_id='d',
|
||||
description='d',
|
||||
alarm_id='c',
|
||||
description='c',
|
||||
state='insufficient data',
|
||||
state_timestamp=constants.MIN_DATETIME,
|
||||
timestamp=constants.MIN_DATETIME,
|
||||
@ -322,7 +308,17 @@ class TestAlarms(TestAlarmsBase):
|
||||
user_id=self.auth_headers['X-User-Id'],
|
||||
project_id=self.auth_headers['X-Project-Id'],
|
||||
time_constraints=[],
|
||||
rule=dict(alarm_ids=['a', 'b'], operator='or'),
|
||||
rule=dict(comparison_operator='gt',
|
||||
threshold=3.0,
|
||||
statistic='avg',
|
||||
evaluation_periods=60,
|
||||
period=1,
|
||||
meter_name='meter.mine',
|
||||
query=[
|
||||
{'field': 'project_id',
|
||||
'op': 'eq', 'value':
|
||||
self.auth_headers['X-Project-Id']}
|
||||
]),
|
||||
severity='critical')
|
||||
self.alarm_conn.update_alarm(alarm)
|
||||
|
||||
@ -366,7 +362,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
q=[{'field': field,
|
||||
'op': 'eq',
|
||||
'value': project}])
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
_test('project')
|
||||
_test('project_id')
|
||||
@ -433,11 +429,6 @@ class TestAlarms(TestAlarmsBase):
|
||||
'meter_name': 'ameter',
|
||||
}
|
||||
},
|
||||
'combination_rule/alarm_ids': {
|
||||
'name': 'missing alarm_ids',
|
||||
'type': 'combination',
|
||||
'combination_rule': {}
|
||||
}
|
||||
}
|
||||
for field, json in six.iteritems(jsons):
|
||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||
@ -447,7 +438,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
% field.split('/', 1)[-1],
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_time_constraint_start(self):
|
||||
json = {
|
||||
@ -468,7 +459,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_duplicate_time_constraint_name(self):
|
||||
json = {
|
||||
@ -497,7 +488,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
"Time constraint names must be unique for a given alarm.",
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_alarm_null_time_constraint(self):
|
||||
json = {
|
||||
@ -531,7 +522,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_time_constraint_timezone(self):
|
||||
json = {
|
||||
@ -553,7 +544,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_period(self):
|
||||
json = {
|
||||
@ -571,14 +562,13 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_null_rule(self):
|
||||
json = {
|
||||
'name': 'added_alarm_invalid_threshold_rule',
|
||||
'type': 'threshold',
|
||||
'threshold_rule': None,
|
||||
'combination_rule': None,
|
||||
}
|
||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||
status=400, headers=self.auth_headers)
|
||||
@ -604,7 +594,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.assertIn(expected_err_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_input_severity(self):
|
||||
json = {
|
||||
@ -625,7 +615,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.assertIn(expected_err_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_input_type(self):
|
||||
json = {
|
||||
@ -646,7 +636,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.assertIn(expected_err_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_input_enabled_str(self):
|
||||
json = {
|
||||
@ -666,7 +656,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.assertIn(expected_err_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_input_enabled_int(self):
|
||||
json = {
|
||||
@ -684,37 +674,8 @@ class TestAlarms(TestAlarmsBase):
|
||||
headers=self.auth_headers)
|
||||
self.assertFalse(resp.json['enabled'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(5, len(alarms))
|
||||
|
||||
def test_post_invalid_alarm_have_multiple_rules(self):
|
||||
json = {
|
||||
'name': 'added_alarm',
|
||||
'type': 'threshold',
|
||||
'threshold_rule': {
|
||||
'meter_name': 'ameter',
|
||||
'query': [{'field': 'meter',
|
||||
'value': 'ameter'}],
|
||||
'comparison_operator': 'gt',
|
||||
'threshold': 2.0,
|
||||
},
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
|
||||
}
|
||||
}
|
||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||
status=400, headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
|
||||
# threshold_rule and combination_rule order is not
|
||||
# predictable so it is not possible to do an exact match
|
||||
# here
|
||||
error_faultstring = resp.json['error_message']['faultstring']
|
||||
for expected_string in ['threshold_rule', 'combination_rule',
|
||||
'cannot be set at the same time']:
|
||||
self.assertIn(expected_string, error_faultstring)
|
||||
|
||||
def _do_post_alarm_invalid_action(self, ok_actions=None,
|
||||
alarm_actions=None,
|
||||
insufficient_data_actions=None,
|
||||
@ -748,7 +709,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
resp = self.post_json('/alarms', params=json, status=400,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
self.assertEqual(3, len(alarms))
|
||||
self.assertEqual(error_message,
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
@ -799,7 +760,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, status=201,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(5, len(alarms))
|
||||
self.assertEqual(4, len(alarms))
|
||||
for alarm in alarms:
|
||||
if alarm.name == 'added_alarm_defaults':
|
||||
for key in to_check:
|
||||
@ -1655,18 +1616,18 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_delete_alarm(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(3, len(data))
|
||||
|
||||
resp = self.delete('/alarms/%s' % data[0]['alarm_id'],
|
||||
headers=self.auth_headers,
|
||||
status=204)
|
||||
self.assertEqual(b'', resp.body)
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(3, len(alarms))
|
||||
self.assertEqual(2, len(alarms))
|
||||
|
||||
def test_get_state_alarm(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(3, len(data))
|
||||
|
||||
resp = self.get_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||
headers=self.auth_headers)
|
||||
@ -1674,7 +1635,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_set_state_alarm(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(3, len(data))
|
||||
|
||||
resp = self.put_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||
headers=self.auth_headers,
|
||||
@ -1686,7 +1647,7 @@ class TestAlarms(TestAlarmsBase):
|
||||
|
||||
def test_set_invalid_state_alarm(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(3, len(data))
|
||||
|
||||
self.put_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||
headers=self.auth_headers,
|
||||
@ -2443,383 +2404,6 @@ class TestAlarmsRuleThreshold(TestAlarmsBase):
|
||||
self.fail("Alarm not found")
|
||||
|
||||
|
||||
class TestAlarmsRuleCombination(TestAlarmsBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAlarmsRuleCombination, self).setUp()
|
||||
self.CONF.set_override("enable_combination_alarms", True, "api")
|
||||
for alarm in default_alarms(self.auth_headers):
|
||||
self.alarm_conn.create_alarm(alarm)
|
||||
|
||||
def test_get_alarm_combination(self):
|
||||
alarms = self.get_json('/alarms',
|
||||
headers=self.auth_headers,
|
||||
q=[{'field': 'name',
|
||||
'value': 'name4',
|
||||
}])
|
||||
self.assertEqual('name4', alarms[0]['name'])
|
||||
self.assertEqual(['a', 'b'],
|
||||
alarms[0]['combination_rule']['alarm_ids'])
|
||||
self.assertEqual('or', alarms[0]['combination_rule']['operator'])
|
||||
|
||||
one = self.get_json('/alarms/%s' % alarms[0]['alarm_id'],
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual('name4', one['name'])
|
||||
self.assertEqual(['a', 'b'],
|
||||
alarms[0]['combination_rule']['alarm_ids'])
|
||||
self.assertEqual('or', alarms[0]['combination_rule']['operator'])
|
||||
self.assertEqual(alarms[0]['alarm_id'], one['alarm_id'])
|
||||
self.assertEqual(alarms[0]['repeat_actions'], one['repeat_actions'])
|
||||
|
||||
def test_post_alarm_combination(self):
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
self.post_json('/alarms', params=json, status=201,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms(enabled=False))
|
||||
self.assertEqual(1, len(alarms))
|
||||
if alarms[0].name == 'added_alarm':
|
||||
for key in json:
|
||||
if key.endswith('_rule'):
|
||||
storage_key = 'rule'
|
||||
else:
|
||||
storage_key = key
|
||||
self.assertEqual(json[key], getattr(alarms[0], storage_key))
|
||||
else:
|
||||
self.fail("Alarm not found")
|
||||
|
||||
def test_post_invalid_alarm_combination(self):
|
||||
"""Test that post a combination alarm with a not existing alarm id."""
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['not_exists',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
self.post_json('/alarms', params=json, status=404,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms(enabled=False))
|
||||
self.assertEqual(0, len(alarms))
|
||||
|
||||
def test_post_invalid_combination_alarm_input_operator(self):
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'alarm6',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'bad_operator',
|
||||
}
|
||||
}
|
||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||
status=400, headers=self.auth_headers)
|
||||
expected_err_msg = ("Invalid input for field/attribute"
|
||||
" operator."
|
||||
" Value: 'bad_operator'.")
|
||||
self.assertIn(expected_err_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
alarms = list(self.alarm_conn.get_alarms())
|
||||
self.assertEqual(4, len(alarms))
|
||||
|
||||
def test_post_combination_alarm_as_user_with_unauthorized_alarm(self):
|
||||
"""Test posting a combination alarm.
|
||||
|
||||
Test that post a combination alarm as normal user/project
|
||||
with an alarm_id unauthorized for this project/user
|
||||
"""
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
an_other_user_auth = {'X-User-Id': uuidutils.generate_uuid(),
|
||||
'X-Project-Id': uuidutils.generate_uuid()}
|
||||
resp = self.post_json('/alarms', params=json, status=404,
|
||||
headers=an_other_user_auth)
|
||||
self.assertEqual("Alarm a not found in project "
|
||||
"%s" %
|
||||
an_other_user_auth['X-Project-Id'],
|
||||
jsonutils.loads(resp.body)['error_message']
|
||||
['faultstring'])
|
||||
|
||||
def test_post_combination_alarm_as_admin_on_behalf_of_an_other_user(self):
|
||||
"""Test posting a combination alarm.
|
||||
|
||||
Test that post a combination alarm as admin on behalf of an other
|
||||
user/project with an alarm_id unauthorized for this project/user
|
||||
"""
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'user_id': 'auseridthatisnotmine',
|
||||
'project_id': 'aprojectidthatisnotmine',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
|
||||
headers = {}
|
||||
headers.update(self.auth_headers)
|
||||
headers['X-Roles'] = 'admin'
|
||||
resp = self.post_json('/alarms', params=json, status=404,
|
||||
headers=headers)
|
||||
self.assertEqual("Alarm a not found in project "
|
||||
"aprojectidthatisnotmine",
|
||||
jsonutils.loads(resp.body)['error_message']
|
||||
['faultstring'])
|
||||
|
||||
def test_post_combination_alarm_with_reasonable_description(self):
|
||||
"""Test posting a combination alarm.
|
||||
|
||||
Test that post a combination alarm with two blanks around the
|
||||
operator in alarm description.
|
||||
"""
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
self.post_json('/alarms', params=json, status=201,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms(enabled=False))
|
||||
self.assertEqual(1, len(alarms))
|
||||
self.assertEqual(u'Combined state of alarms a and b',
|
||||
alarms[0].description)
|
||||
|
||||
def _do_post_combination_alarm_as_admin_success(self, owner_is_set):
|
||||
"""Test posting a combination alarm.
|
||||
|
||||
Test that post a combination alarm as admin on behalf of nobody
|
||||
with an alarm_id of someone else, with owner set or not
|
||||
"""
|
||||
json = {
|
||||
'enabled': False,
|
||||
'name': 'added_alarm',
|
||||
'state': 'ok',
|
||||
'type': 'combination',
|
||||
'ok_actions': ['http://something/ok'],
|
||||
'alarm_actions': ['http://something/alarm'],
|
||||
'insufficient_data_actions': ['http://something/no'],
|
||||
'repeat_actions': True,
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a',
|
||||
'b'],
|
||||
'operator': 'and',
|
||||
}
|
||||
}
|
||||
an_other_admin_auth = {'X-User-Id': uuidutils.generate_uuid(),
|
||||
'X-Project-Id': uuidutils.generate_uuid(),
|
||||
'X-Roles': 'admin'}
|
||||
if owner_is_set:
|
||||
json['project_id'] = an_other_admin_auth['X-Project-Id']
|
||||
json['user_id'] = an_other_admin_auth['X-User-Id']
|
||||
|
||||
self.post_json('/alarms', params=json, status=201,
|
||||
headers=an_other_admin_auth)
|
||||
alarms = list(self.alarm_conn.get_alarms(enabled=False))
|
||||
if alarms[0].name == 'added_alarm':
|
||||
for key in json:
|
||||
if key.endswith('_rule'):
|
||||
storage_key = 'rule'
|
||||
else:
|
||||
storage_key = key
|
||||
self.assertEqual(json[key], getattr(alarms[0], storage_key))
|
||||
else:
|
||||
self.fail("Alarm not found")
|
||||
|
||||
def test_post_combination_alarm_as_admin_success_owner_unset(self):
|
||||
self._do_post_combination_alarm_as_admin_success(False)
|
||||
|
||||
def test_post_combination_alarm_as_admin_success_owner_set(self):
|
||||
self._do_post_combination_alarm_as_admin_success(True)
|
||||
|
||||
def test_post_alarm_combination_duplicate_alarm_ids(self):
|
||||
"""Test combination alarm doesn't allow duplicate alarm ids."""
|
||||
json_body = {
|
||||
'name': 'dup_alarm_id',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'a', 'd', 'a', 'c', 'c', 'b'],
|
||||
}
|
||||
}
|
||||
self.post_json('/alarms', params=json_body, status=201,
|
||||
headers=self.auth_headers)
|
||||
alarms = list(self.alarm_conn.get_alarms(name='dup_alarm_id'))
|
||||
self.assertEqual(1, len(alarms))
|
||||
self.assertEqual(['a', 'd', 'c', 'b'],
|
||||
alarms[0].rule.get('alarm_ids'))
|
||||
|
||||
def _test_post_alarm_combination_rule_less_than_two_alarms(self,
|
||||
alarm_ids=None):
|
||||
json_body = {
|
||||
'name': 'one_alarm_in_combination_rule',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': alarm_ids or []
|
||||
}
|
||||
}
|
||||
|
||||
resp = self.post_json('/alarms', params=json_body,
|
||||
expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(
|
||||
'Alarm combination rule should contain at'
|
||||
' least two different alarm ids.',
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
def test_post_alarm_combination_rule_with_no_alarm(self):
|
||||
self._test_post_alarm_combination_rule_less_than_two_alarms()
|
||||
|
||||
def test_post_alarm_combination_rule_with_one_alarm(self):
|
||||
self._test_post_alarm_combination_rule_less_than_two_alarms(['a'])
|
||||
|
||||
def test_post_alarm_combination_rule_with_two_same_alarms(self):
|
||||
self._test_post_alarm_combination_rule_less_than_two_alarms(['a',
|
||||
'a'])
|
||||
|
||||
def test_put_alarm_combination_cannot_specify_itself(self):
|
||||
json = {
|
||||
'name': 'name4',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['d', 'a'],
|
||||
}
|
||||
}
|
||||
|
||||
data = self.get_json('/alarms',
|
||||
headers=self.auth_headers,
|
||||
q=[{'field': 'name',
|
||||
'value': 'name4',
|
||||
}])
|
||||
self.assertEqual(1, len(data))
|
||||
alarm_id = data[0]['alarm_id']
|
||||
|
||||
resp = self.put_json('/alarms/%s' % alarm_id,
|
||||
expect_errors=True, status=400,
|
||||
params=json,
|
||||
headers=self.auth_headers)
|
||||
|
||||
msg = 'Cannot specify alarm %s itself in combination rule' % alarm_id
|
||||
self.assertEqual(msg, resp.json['error_message']['faultstring'])
|
||||
|
||||
def _test_put_alarm_combination_rule_less_than_two_alarms(self,
|
||||
alarm_ids=None):
|
||||
json_body = {
|
||||
'name': 'name4',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': alarm_ids or []
|
||||
}
|
||||
}
|
||||
|
||||
data = self.get_json('/alarms',
|
||||
headers=self.auth_headers,
|
||||
q=[{'field': 'name',
|
||||
'value': 'name4',
|
||||
}])
|
||||
self.assertEqual(1, len(data))
|
||||
alarm_id = data[0]['alarm_id']
|
||||
|
||||
resp = self.put_json('/alarms/%s' % alarm_id, params=json_body,
|
||||
expect_errors=True, status=400,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(
|
||||
'Alarm combination rule should contain at'
|
||||
' least two different alarm ids.',
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
def test_put_alarm_combination_rule_with_no_alarm(self):
|
||||
self._test_put_alarm_combination_rule_less_than_two_alarms()
|
||||
|
||||
def test_put_alarm_combination_rule_with_one_alarm(self):
|
||||
self._test_put_alarm_combination_rule_less_than_two_alarms(['a'])
|
||||
|
||||
def test_put_alarm_combination_rule_with_two_same_alarm_itself(self):
|
||||
self._test_put_alarm_combination_rule_less_than_two_alarms(['d',
|
||||
'd'])
|
||||
|
||||
def test_put_combination_alarm_with_duplicate_ids(self):
|
||||
"""Test combination alarm doesn't allow duplicate alarm ids."""
|
||||
alarms = self.get_json('/alarms',
|
||||
headers=self.auth_headers,
|
||||
q=[{'field': 'name',
|
||||
'value': 'name4',
|
||||
}])
|
||||
self.assertEqual(1, len(alarms))
|
||||
alarm_id = alarms[0]['alarm_id']
|
||||
|
||||
json_body = {
|
||||
'name': 'name4',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['c', 'a', 'b', 'a', 'c', 'b'],
|
||||
}
|
||||
}
|
||||
self.put_json('/alarms/%s' % alarm_id,
|
||||
params=json_body, status=200,
|
||||
headers=self.auth_headers)
|
||||
|
||||
alarms = list(self.alarm_conn.get_alarms(alarm_id=alarm_id))
|
||||
self.assertEqual(1, len(alarms))
|
||||
self.assertEqual(['c', 'a', 'b'], alarms[0].rule.get('alarm_ids'))
|
||||
|
||||
|
||||
class TestAlarmsRuleGnocchi(TestAlarmsBase):
|
||||
|
||||
def setUp(self):
|
||||
@ -3313,11 +2897,11 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
data = self.get_json('/alarms?sort=name:desc',
|
||||
headers=self.auth_headers)
|
||||
names = [a['name'] for a in data]
|
||||
self.assertEqual(['name4', 'name3', 'name2', 'name1'], names)
|
||||
self.assertEqual(['name3', 'name2', 'name1'], names)
|
||||
data = self.get_json('/alarms?sort=name:asc',
|
||||
headers=self.auth_headers)
|
||||
names = [a['name'] for a in data]
|
||||
self.assertEqual(['name1', 'name2', 'name3', 'name4'], names)
|
||||
self.assertEqual(['name1', 'name2', 'name3'], names)
|
||||
|
||||
def test_sort_by_severity_with_its_value(self):
|
||||
if self.engine != "mysql":
|
||||
@ -3325,12 +2909,12 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
data = self.get_json('/alarms?sort=severity:asc',
|
||||
headers=self.auth_headers)
|
||||
severities = [a['severity'] for a in data]
|
||||
self.assertEqual(['low', 'moderate', 'critical', 'critical'],
|
||||
self.assertEqual(['moderate', 'critical', 'critical'],
|
||||
severities)
|
||||
data = self.get_json('/alarms?sort=severity:desc',
|
||||
headers=self.auth_headers)
|
||||
severities = [a['severity'] for a in data]
|
||||
self.assertEqual(['critical', 'critical', 'moderate', 'low'],
|
||||
self.assertEqual(['critical', 'critical', 'moderate'],
|
||||
severities)
|
||||
|
||||
def test_pagination_query_limit(self):
|
||||
@ -3345,17 +2929,17 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
def test_pagination_query_marker(self):
|
||||
data = self.get_json('/alarms?sort=name:desc',
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(4, len(data))
|
||||
self.assertEqual(3, len(data))
|
||||
alarm_ids = [a['alarm_id'] for a in data]
|
||||
names = [a['name'] for a in data]
|
||||
self.assertEqual(['name4', 'name3', 'name2', 'name1'], names)
|
||||
self.assertEqual(['name3', 'name2', 'name1'], names)
|
||||
marker_url = ('/alarms?sort=name:desc&marker=%s' % alarm_ids[1])
|
||||
data = self.get_json(marker_url, headers=self.auth_headers)
|
||||
self.assertEqual(2, len(data))
|
||||
self.assertEqual(1, len(data))
|
||||
new_alarm_ids = [a['alarm_id'] for a in data]
|
||||
self.assertEqual(alarm_ids[2:], new_alarm_ids)
|
||||
new_names = [a['name'] for a in data]
|
||||
self.assertEqual(['name2', 'name1'], new_names)
|
||||
self.assertEqual(['name1'], new_names)
|
||||
|
||||
def test_pagination_query_multiple_sorts(self):
|
||||
new_alarms = default_alarms(self.auth_headers)
|
||||
@ -3363,11 +2947,11 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
a_id[0].alarm_id = a_id[1]
|
||||
self.alarm_conn.create_alarm(a_id[0])
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(8, len(data))
|
||||
self.assertEqual(6, len(data))
|
||||
sort_url = '/alarms?sort=name:desc&sort=alarm_id:asc'
|
||||
data = self.get_json(sort_url, headers=self.auth_headers)
|
||||
name_ids = [(a['name'], a['alarm_id']) for a in data]
|
||||
expected = [('name4', 'd'), ('name4', 'h'), ('name3', 'c'),
|
||||
expected = [('name3', 'c'),
|
||||
('name3', 'g'), ('name2', 'b'), ('name2', 'f'),
|
||||
('name1', 'a'), ('name1', 'e')]
|
||||
self.assertEqual(expected, name_ids)
|
||||
@ -3392,7 +2976,7 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
data = self.get_json('/alarms?sort=name',
|
||||
headers=self.auth_headers)
|
||||
names = [a['name'] for a in data]
|
||||
self.assertEqual(['name1', 'name2', 'name3', 'name4'], names)
|
||||
self.assertEqual(['name1', 'name2', 'name3'], names)
|
||||
|
||||
def test_pagination_query_history_data(self):
|
||||
for i in moves.xrange(10):
|
||||
@ -3403,66 +2987,3 @@ class TestPaginationQuery(TestAlarmsBase):
|
||||
key=lambda d: (d['event_id'], d['timestamp']),
|
||||
reverse=True)
|
||||
self.assertEqual(sorted_data, data)
|
||||
|
||||
|
||||
class TestCombinationCompositeConversion(TestAlarmsBase):
|
||||
def setUp(self):
|
||||
super(TestCombinationCompositeConversion, self).setUp()
|
||||
alarms = default_alarms(self.auth_headers)
|
||||
for alarm in alarms:
|
||||
self.alarm_conn.create_alarm(alarm)
|
||||
com_parameters = alarms[3].as_dict()
|
||||
com_parameters.update(dict(name='name5', alarm_id='e', description='e',
|
||||
rule=dict(alarm_ids=['b', 'c'],
|
||||
operator='and')))
|
||||
combin1 = models.Alarm(**com_parameters)
|
||||
self.alarm_conn.create_alarm(combin1)
|
||||
com_parameters.update(dict(name='name6', alarm_id='f', description='f',
|
||||
rule=dict(alarm_ids=['d', 'e'],
|
||||
operator='and')))
|
||||
combin2 = models.Alarm(**com_parameters)
|
||||
self.alarm_conn.create_alarm(combin2)
|
||||
|
||||
def test_conversion_without_combination_deletion(self):
|
||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||
self.assertEqual(6, len(data))
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(3, len(combination_alarms))
|
||||
test_args = alarm_conversion.get_parser().parse_args([])
|
||||
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||
return_value=test_args):
|
||||
alarm_conversion.conversion()
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(3, len(composite_alarms))
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(3, len(combination_alarms))
|
||||
|
||||
def test_conversion_with_combination_deletion(self):
|
||||
test_args = alarm_conversion.get_parser().parse_args(
|
||||
['--delete-combination-alarm', 'True'])
|
||||
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||
return_value=test_args):
|
||||
alarm_conversion.conversion()
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(3, len(composite_alarms))
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(0, len(combination_alarms))
|
||||
|
||||
def test_conversion_with_alarm_specified(self):
|
||||
test_args = alarm_conversion.get_parser().parse_args(
|
||||
['--alarm-id', 'e'])
|
||||
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||
return_value=test_args):
|
||||
alarm_conversion.conversion()
|
||||
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||
self.assertEqual(1, len(composite_alarms))
|
||||
self.assertEqual('From-combination: e', composite_alarms[0]['name'])
|
||||
|
@ -174,8 +174,6 @@ class AlarmTest(AlarmTestBase):
|
||||
self.add_some_alarms()
|
||||
alarms = list(self.alarm_conn.get_alarms(alarm_type='threshold'))
|
||||
self.assertEqual(3, len(alarms))
|
||||
alarms = list(self.alarm_conn.get_alarms(alarm_type='combination'))
|
||||
self.assertEqual(0, len(alarms))
|
||||
|
||||
def test_list_excluded_by_name(self):
|
||||
self.add_some_alarms()
|
||||
|
@ -92,21 +92,3 @@ class TelemetryAlarmingAPITest(base.BaseAlarmingTest):
|
||||
# Get alarm state and verify
|
||||
state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
|
||||
self.assertEqual(new_state, state.data)
|
||||
|
||||
@decorators.idempotent_id('08d7e45a-1344-4e5c-ba6f-f6cbb77f55ba')
|
||||
@decorators.skip_because(bug='1585267')
|
||||
def test_create_delete_alarm_with_combination_rule(self):
|
||||
rule = {"alarm_ids": self.alarm_ids,
|
||||
"operator": "or"}
|
||||
# Verifies alarm create
|
||||
alarm_name = data_utils.rand_name('combination_alarm')
|
||||
body = self.alarming_client.create_alarm(name=alarm_name,
|
||||
combination_rule=rule,
|
||||
type='combination')
|
||||
self.assertEqual(alarm_name, body['name'])
|
||||
alarm_id = body['alarm_id']
|
||||
self.assertDictContainsSubset(rule, body['combination_rule'])
|
||||
# Verify alarm delete
|
||||
self.alarming_client.delete_alarm(alarm_id)
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.alarming_client.show_alarm, alarm_id)
|
||||
|
@ -1,376 +0,0 @@
|
||||
#
|
||||
# Copyright 2013 eNovance <licensing@enovance.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Tests for aodh/evaluator/combination.py
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from ceilometerclient import exc
|
||||
from ceilometerclient.v2 import alarms
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import pytz
|
||||
|
||||
from aodh.evaluator import combination
|
||||
from aodh.storage import models
|
||||
from aodh.tests import constants
|
||||
from aodh.tests.unit.evaluator import base
|
||||
|
||||
|
||||
class TestEvaluate(base.TestEvaluatorBase):
|
||||
EVALUATOR = combination.CombinationEvaluator
|
||||
|
||||
def prepare_alarms(self):
|
||||
self.alarms = [
|
||||
models.Alarm(name='or-alarm',
|
||||
description='the or alarm',
|
||||
type='combination',
|
||||
enabled=True,
|
||||
user_id='foobar',
|
||||
project_id='snafu',
|
||||
alarm_id=uuidutils.generate_uuid(),
|
||||
state='insufficient data',
|
||||
state_timestamp=constants.MIN_DATETIME,
|
||||
timestamp=constants.MIN_DATETIME,
|
||||
insufficient_data_actions=[],
|
||||
ok_actions=[],
|
||||
alarm_actions=[],
|
||||
repeat_actions=False,
|
||||
time_constraints=[],
|
||||
rule=dict(
|
||||
alarm_ids=[
|
||||
'9cfc3e51-2ff1-4b1d-ac01-c1bd4c6d0d1e',
|
||||
'1d441595-d069-4e05-95ab-8693ba6a8302'],
|
||||
operator='or',
|
||||
),
|
||||
severity='critical'),
|
||||
models.Alarm(name='and-alarm',
|
||||
description='the and alarm',
|
||||
type='combination',
|
||||
enabled=True,
|
||||
user_id='foobar',
|
||||
project_id='snafu',
|
||||
alarm_id=uuidutils.generate_uuid(),
|
||||
state='insufficient data',
|
||||
state_timestamp=constants.MIN_DATETIME,
|
||||
timestamp=constants.MIN_DATETIME,
|
||||
insufficient_data_actions=[],
|
||||
ok_actions=[],
|
||||
alarm_actions=[],
|
||||
repeat_actions=False,
|
||||
time_constraints=[],
|
||||
rule=dict(
|
||||
alarm_ids=[
|
||||
'b82734f4-9d06-48f3-8a86-fa59a0c99dc8',
|
||||
'15a700e5-2fe8-4b3d-8c55-9e92831f6a2b'],
|
||||
operator='and',
|
||||
),
|
||||
severity='critical')
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_alarms(state):
|
||||
return [alarms.Alarm(None, {'state': state})]
|
||||
|
||||
@staticmethod
|
||||
def _reason_data(alarm_ids):
|
||||
return {'type': 'combination', 'alarm_ids': alarm_ids}
|
||||
|
||||
def _combination_transition_reason(self, state, alarm_ids1, alarm_ids2):
|
||||
return ([('Transition to %(state)s due to alarms %(alarm_ids)s'
|
||||
' in state %(state)s')
|
||||
% {'state': state, 'alarm_ids': ",".join(alarm_ids1)},
|
||||
('Transition to %(state)s due to alarms %(alarm_ids)s'
|
||||
' in state %(state)s')
|
||||
% {'state': state, 'alarm_ids': ",".join(alarm_ids2)}],
|
||||
[self._reason_data(alarm_ids1), self._reason_data(alarm_ids2)])
|
||||
|
||||
def _combination_remaining_reason(self, state, alarm_ids1, alarm_ids2):
|
||||
return ([('Remaining as %(state)s due to alarms %(alarm_ids)s'
|
||||
' in state %(state)s')
|
||||
% {'state': state, 'alarm_ids': ",".join(alarm_ids1)},
|
||||
('Remaining as %(state)s due to alarms %(alarm_ids)s'
|
||||
' in state %(state)s')
|
||||
% {'state': state, 'alarm_ids': ",".join(alarm_ids2)}],
|
||||
[self._reason_data(alarm_ids1), self._reason_data(alarm_ids2)])
|
||||
|
||||
def test_retry_transient_api_failure(self):
|
||||
broken = exc.CommunicationError(message='broken')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
broken,
|
||||
broken,
|
||||
broken,
|
||||
broken,
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('insufficient data')
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
|
||||
def test_simple_insufficient(self):
|
||||
self._set_all_alarms('ok')
|
||||
broken = exc.CommunicationError(message='broken')
|
||||
self.storage_conn.get_alarms.side_effect = broken
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('insufficient data')
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
expected = [mock.call(
|
||||
alarm,
|
||||
'ok',
|
||||
('Alarms %s are in unknown state' %
|
||||
(",".join(alarm.rule['alarm_ids']))),
|
||||
self._reason_data(alarm.rule['alarm_ids']))
|
||||
for alarm in self.alarms]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_ok_with_all_ok(self):
|
||||
self._set_all_alarms('insufficient data')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'ok',
|
||||
self.alarms[0].rule['alarm_ids'],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'insufficient data',
|
||||
reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_ok_with_one_alarm(self):
|
||||
self._set_all_alarms('alarm')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'ok',
|
||||
self.alarms[0].rule['alarm_ids'],
|
||||
[self.alarms[1].rule['alarm_ids'][1]])
|
||||
expected = [mock.call(alarm, 'alarm', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_alarm_with_all_alarm(self):
|
||||
self._set_all_alarms('ok')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'alarm',
|
||||
self.alarms[0].rule['alarm_ids'],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'ok', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_alarm_with_one_insufficient_data(self):
|
||||
self._set_all_alarms('ok')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('insufficient data'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'alarm',
|
||||
[self.alarms[0].rule['alarm_ids'][1]],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'ok', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_alarm_with_one_ok(self):
|
||||
self._set_all_alarms('ok')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
self._get_alarms('alarm'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'alarm',
|
||||
[self.alarms[0].rule['alarm_ids'][1]],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'ok', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_to_unknown(self):
|
||||
self._set_all_alarms('ok')
|
||||
broken = exc.CommunicationError(message='broken')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
broken,
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('insufficient data'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
reasons = ['Alarms %s are in unknown state'
|
||||
% self.alarms[0].rule['alarm_ids'][0],
|
||||
'Alarms %s are in unknown state'
|
||||
% self.alarms[1].rule['alarm_ids'][0]]
|
||||
reason_datas = [
|
||||
self._reason_data([self.alarms[0].rule['alarm_ids'][0]]),
|
||||
self._reason_data([self.alarms[1].rule['alarm_ids'][0]])]
|
||||
expected = [mock.call(alarm, 'ok', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
def test_no_state_change(self):
|
||||
self._set_all_alarms('ok')
|
||||
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual([], update_calls)
|
||||
self.assertEqual([], self.notifier.notify.call_args_list)
|
||||
|
||||
def test_no_state_change_and_repeat_actions(self):
|
||||
self.alarms[0].repeat_actions = True
|
||||
self.alarms[1].repeat_actions = True
|
||||
self._set_all_alarms('ok')
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual([], update_calls)
|
||||
reasons, reason_datas = self._combination_remaining_reason(
|
||||
'ok',
|
||||
self.alarms[0].rule['alarm_ids'],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'ok', reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
@mock.patch.object(timeutils, 'utcnow')
|
||||
def test_state_change_inside_time_constraint(self, mock_utcnow):
|
||||
self._set_all_alarms('insufficient data')
|
||||
self.alarms[0].time_constraints = [
|
||||
{'name': 'test',
|
||||
'description': 'test',
|
||||
'start': '0 11 * * *', # daily at 11:00
|
||||
'duration': 10800, # 3 hours
|
||||
'timezone': 'Europe/Ljubljana'}
|
||||
]
|
||||
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||
dt = datetime.datetime(2014, 1, 1, 12, 0, 0,
|
||||
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||
mock_utcnow.return_value = dt.astimezone(pytz.UTC)
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls,
|
||||
"Alarm should change state if the current "
|
||||
"time is inside its time constraint.")
|
||||
reasons, reason_datas = self._combination_transition_reason(
|
||||
'ok',
|
||||
self.alarms[0].rule['alarm_ids'],
|
||||
self.alarms[1].rule['alarm_ids'])
|
||||
expected = [mock.call(alarm, 'insufficient data',
|
||||
reason, reason_data)
|
||||
for alarm, reason, reason_data
|
||||
in zip(self.alarms, reasons, reason_datas)]
|
||||
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||
|
||||
@mock.patch.object(timeutils, 'utcnow')
|
||||
def test_no_state_change_outside_time_constraint(self, mock_utcnow):
|
||||
self._set_all_alarms('insufficient data')
|
||||
self.alarms[0].time_constraints = [
|
||||
{'name': 'test',
|
||||
'description': 'test',
|
||||
'start': '0 11 * * *', # daily at 11:00
|
||||
'duration': 10800, # 3 hours
|
||||
'timezone': 'Europe/Ljubljana'}
|
||||
]
|
||||
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||
dt = datetime.datetime(2014, 1, 1, 15, 0, 0,
|
||||
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||
mock_utcnow.return_value = dt.astimezone(pytz.UTC)
|
||||
|
||||
self.storage_conn.get_alarms.side_effect = [
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
self._get_alarms('ok'),
|
||||
]
|
||||
self._evaluate_all_alarms()
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual([], update_calls,
|
||||
"Alarm should not change state if the current "
|
||||
" time is outside its time constraint.")
|
||||
self.assertEqual([], self.notifier.notify.call_args_list)
|
@ -35,9 +35,6 @@ Alarms
|
||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule
|
||||
:members:
|
||||
|
||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.combination.AlarmCombinationRule
|
||||
:members:
|
||||
|
||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule
|
||||
:members:
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
deprecations:
|
||||
- The deprecated combination alarms support have been removed.
|
@ -80,7 +80,6 @@ aodh.storage =
|
||||
|
||||
aodh.alarm.rule =
|
||||
threshold = aodh.api.controllers.v2.alarm_rules.threshold:AlarmThresholdRule
|
||||
combination = aodh.api.controllers.v2.alarm_rules.combination:AlarmCombinationRule
|
||||
gnocchi_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:MetricOfResourceRule
|
||||
gnocchi_aggregation_by_metrics_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricsByIdLookupRule
|
||||
gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule
|
||||
@ -89,7 +88,6 @@ aodh.alarm.rule =
|
||||
|
||||
aodh.evaluator =
|
||||
threshold = aodh.evaluator.threshold:ThresholdEvaluator
|
||||
combination = aodh.evaluator.combination:CombinationEvaluator
|
||||
gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator
|
||||
gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator
|
||||
gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator
|
||||
@ -114,7 +112,6 @@ console_scripts =
|
||||
aodh-evaluator = aodh.cmd.alarm:evaluator
|
||||
aodh-notifier = aodh.cmd.alarm:notifier
|
||||
aodh-listener = aodh.cmd.alarm:listener
|
||||
aodh-combination-alarm-conversion = aodh.cmd.alarm_conversion:conversion
|
||||
|
||||
oslo.config.opts =
|
||||
aodh = aodh.opts:list_opts
|
||||
|
Loading…
Reference in New Issue
Block a user