Remove deprecated combination alarms
Change-Id: I8977c69d0c73261782ddfcb295b7cfe1d76f9de3
This commit is contained in:
parent
021a3e1613
commit
1e63626e9c
@ -27,11 +27,4 @@ OPTS = [
|
|||||||
'auth_mode',
|
'auth_mode',
|
||||||
default="keystone",
|
default="keystone",
|
||||||
help="Authentication mode to use. Unset to disable authentication"),
|
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 wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
import aodh
|
import aodh
|
||||||
from aodh.api.controllers.v2.alarm_rules import combination
|
|
||||||
from aodh.api.controllers.v2 import base
|
from aodh.api.controllers.v2 import base
|
||||||
from aodh.api.controllers.v2 import utils as v2_utils
|
from aodh.api.controllers.v2 import utils as v2_utils
|
||||||
from aodh.api import rbac
|
from aodh.api import rbac
|
||||||
@ -184,13 +183,7 @@ ACTIONS_SCHEMA = extension.ExtensionManager(
|
|||||||
|
|
||||||
|
|
||||||
class Alarm(base.Base):
|
class Alarm(base.Base):
|
||||||
"""Representation of an alarm.
|
"""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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alarm_id = wtypes.text
|
alarm_id = wtypes.text
|
||||||
"The UUID of the alarm"
|
"The UUID of the alarm"
|
||||||
@ -289,10 +282,6 @@ class Alarm(base.Base):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_rule(alarm):
|
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
|
rule = '%s_rule' % alarm.type
|
||||||
if getattr(alarm, rule) in (wtypes.Unset, None):
|
if getattr(alarm, rule) in (wtypes.Unset, None):
|
||||||
error = _("%(rule)s must be set for %(type)s"
|
error = _("%(rule)s must be set for %(type)s"
|
||||||
@ -354,7 +343,7 @@ class Alarm(base.Base):
|
|||||||
return cls(alarm_id=None,
|
return cls(alarm_id=None,
|
||||||
name="SwiftObjectAlarm",
|
name="SwiftObjectAlarm",
|
||||||
description="An alarm",
|
description="An alarm",
|
||||||
type='combination',
|
type='threshold',
|
||||||
time_constraints=[AlarmTimeConstraint.sample().as_dict()],
|
time_constraints=[AlarmTimeConstraint.sample().as_dict()],
|
||||||
user_id="c96c887c216949acbdfbd8b494863567",
|
user_id="c96c887c216949acbdfbd8b494863567",
|
||||||
project_id="c96c887c216949acbdfbd8b494863567",
|
project_id="c96c887c216949acbdfbd8b494863567",
|
||||||
@ -367,7 +356,6 @@ class Alarm(base.Base):
|
|||||||
alarm_actions=["http://site:8000/alarm"],
|
alarm_actions=["http://site:8000/alarm"],
|
||||||
insufficient_data_actions=["http://site:8000/nodata"],
|
insufficient_data_actions=["http://site:8000/nodata"],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
combination_rule=combination.AlarmCombinationRule.sample(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def as_dict(self, db_model):
|
def as_dict(self, db_model):
|
||||||
|
@ -37,8 +37,6 @@ def get_auth_project(on_behalf_of=None):
|
|||||||
# we must ensure for:
|
# we must ensure for:
|
||||||
# - threshold alarm, that an implicit query constraint on project_id is
|
# - threshold alarm, that an implicit query constraint on project_id is
|
||||||
# added so that admin-level visibility on statistics is not leaked
|
# 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
|
# Hence, for null auth_project (indicating admin-ness) we check if
|
||||||
# the creating tenant differs from the tenant on whose behalf the
|
# the creating tenant differs from the tenant on whose behalf the
|
||||||
# alarm is being created
|
# 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
|
import webtest
|
||||||
|
|
||||||
from aodh.api import app
|
from aodh.api import app
|
||||||
from aodh.cmd import alarm_conversion
|
|
||||||
from aodh import messaging
|
from aodh import messaging
|
||||||
from aodh.storage import models
|
from aodh.storage import models
|
||||||
from aodh.tests import constants
|
from aodh.tests import constants
|
||||||
@ -116,27 +115,7 @@ def default_alarms(auth_headers):
|
|||||||
'op': 'eq', 'value':
|
'op': 'eq', 'value':
|
||||||
auth_headers['X-Project-Id']}
|
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):
|
class TestAlarmsBase(v2.FunctionalTest):
|
||||||
@ -199,15 +178,12 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_list_alarms(self):
|
def test_list_alarms(self):
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||||
self.assertEqual(4, len(data))
|
self.assertEqual(3, len(data))
|
||||||
self.assertEqual(set(['name1', 'name2', 'name3', 'name4']),
|
self.assertEqual(set(['name1', 'name2', 'name3']),
|
||||||
set(r['name'] for r in data))
|
set(r['name'] for r in data))
|
||||||
self.assertEqual(set(['meter.test', 'meter.mine']),
|
self.assertEqual(set(['meter.test', 'meter.mine']),
|
||||||
set(r['threshold_rule']['meter_name']
|
set(r['threshold_rule']['meter_name']
|
||||||
for r in data if 'threshold_rule' in r))
|
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):
|
def test_alarms_query_with_timestamp(self):
|
||||||
date_time = datetime.datetime(2012, 7, 2, 10, 41)
|
date_time = datetime.datetime(2012, 7, 2, 10, 41)
|
||||||
@ -241,10 +217,10 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_alarms_query_with_state(self):
|
def test_alarms_query_with_state(self):
|
||||||
alarm = models.Alarm(name='disabled',
|
alarm = models.Alarm(name='disabled',
|
||||||
type='combination',
|
type='threshold',
|
||||||
enabled=False,
|
enabled=False,
|
||||||
alarm_id='d',
|
alarm_id='c',
|
||||||
description='d',
|
description='c',
|
||||||
state='ok',
|
state='ok',
|
||||||
state_timestamp=constants.MIN_DATETIME,
|
state_timestamp=constants.MIN_DATETIME,
|
||||||
timestamp=constants.MIN_DATETIME,
|
timestamp=constants.MIN_DATETIME,
|
||||||
@ -255,7 +231,17 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
user_id=self.auth_headers['X-User-Id'],
|
user_id=self.auth_headers['X-User-Id'],
|
||||||
project_id=self.auth_headers['X-Project-Id'],
|
project_id=self.auth_headers['X-Project-Id'],
|
||||||
time_constraints=[],
|
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')
|
severity='critical')
|
||||||
self.alarm_conn.update_alarm(alarm)
|
self.alarm_conn.update_alarm(alarm)
|
||||||
resp = self.get_json('/alarms',
|
resp = self.get_json('/alarms',
|
||||||
@ -308,10 +294,10 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_get_alarm_disabled(self):
|
def test_get_alarm_disabled(self):
|
||||||
alarm = models.Alarm(name='disabled',
|
alarm = models.Alarm(name='disabled',
|
||||||
type='combination',
|
type='threshold',
|
||||||
enabled=False,
|
enabled=False,
|
||||||
alarm_id='d',
|
alarm_id='c',
|
||||||
description='d',
|
description='c',
|
||||||
state='insufficient data',
|
state='insufficient data',
|
||||||
state_timestamp=constants.MIN_DATETIME,
|
state_timestamp=constants.MIN_DATETIME,
|
||||||
timestamp=constants.MIN_DATETIME,
|
timestamp=constants.MIN_DATETIME,
|
||||||
@ -322,7 +308,17 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
user_id=self.auth_headers['X-User-Id'],
|
user_id=self.auth_headers['X-User-Id'],
|
||||||
project_id=self.auth_headers['X-Project-Id'],
|
project_id=self.auth_headers['X-Project-Id'],
|
||||||
time_constraints=[],
|
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')
|
severity='critical')
|
||||||
self.alarm_conn.update_alarm(alarm)
|
self.alarm_conn.update_alarm(alarm)
|
||||||
|
|
||||||
@ -366,7 +362,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
q=[{'field': field,
|
q=[{'field': field,
|
||||||
'op': 'eq',
|
'op': 'eq',
|
||||||
'value': project}])
|
'value': project}])
|
||||||
self.assertEqual(4, len(alarms))
|
self.assertEqual(3, len(alarms))
|
||||||
|
|
||||||
_test('project')
|
_test('project')
|
||||||
_test('project_id')
|
_test('project_id')
|
||||||
@ -433,11 +429,6 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
'meter_name': 'ameter',
|
'meter_name': 'ameter',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'combination_rule/alarm_ids': {
|
|
||||||
'name': 'missing alarm_ids',
|
|
||||||
'type': 'combination',
|
|
||||||
'combination_rule': {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for field, json in six.iteritems(jsons):
|
for field, json in six.iteritems(jsons):
|
||||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||||
@ -447,7 +438,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
% field.split('/', 1)[-1],
|
% field.split('/', 1)[-1],
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_time_constraint_start(self):
|
||||||
json = {
|
json = {
|
||||||
@ -468,7 +459,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_duplicate_time_constraint_name(self):
|
||||||
json = {
|
json = {
|
||||||
@ -497,7 +488,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
"Time constraint names must be unique for a given alarm.",
|
"Time constraint names must be unique for a given alarm.",
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_alarm_null_time_constraint(self):
|
||||||
json = {
|
json = {
|
||||||
@ -531,7 +522,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_time_constraint_timezone(self):
|
||||||
json = {
|
json = {
|
||||||
@ -553,7 +544,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
alarms = list(self.alarm_conn.get_alarms())
|
||||||
self.assertEqual(4, len(alarms))
|
self.assertEqual(3, len(alarms))
|
||||||
|
|
||||||
def test_post_invalid_alarm_period(self):
|
def test_post_invalid_alarm_period(self):
|
||||||
json = {
|
json = {
|
||||||
@ -571,14 +562,13 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
alarms = list(self.alarm_conn.get_alarms())
|
||||||
self.assertEqual(4, len(alarms))
|
self.assertEqual(3, len(alarms))
|
||||||
|
|
||||||
def test_post_null_rule(self):
|
def test_post_null_rule(self):
|
||||||
json = {
|
json = {
|
||||||
'name': 'added_alarm_invalid_threshold_rule',
|
'name': 'added_alarm_invalid_threshold_rule',
|
||||||
'type': 'threshold',
|
'type': 'threshold',
|
||||||
'threshold_rule': None,
|
'threshold_rule': None,
|
||||||
'combination_rule': None,
|
|
||||||
}
|
}
|
||||||
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
resp = self.post_json('/alarms', params=json, expect_errors=True,
|
||||||
status=400, headers=self.auth_headers)
|
status=400, headers=self.auth_headers)
|
||||||
@ -604,7 +594,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.assertIn(expected_err_msg,
|
self.assertIn(expected_err_msg,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_input_severity(self):
|
||||||
json = {
|
json = {
|
||||||
@ -625,7 +615,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.assertIn(expected_err_msg,
|
self.assertIn(expected_err_msg,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_input_type(self):
|
||||||
json = {
|
json = {
|
||||||
@ -646,7 +636,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.assertIn(expected_err_msg,
|
self.assertIn(expected_err_msg,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_input_enabled_str(self):
|
||||||
json = {
|
json = {
|
||||||
@ -666,7 +656,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.assertIn(expected_err_msg,
|
self.assertIn(expected_err_msg,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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):
|
def test_post_invalid_alarm_input_enabled_int(self):
|
||||||
json = {
|
json = {
|
||||||
@ -684,37 +674,8 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
self.assertFalse(resp.json['enabled'])
|
self.assertFalse(resp.json['enabled'])
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
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))
|
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,
|
def _do_post_alarm_invalid_action(self, ok_actions=None,
|
||||||
alarm_actions=None,
|
alarm_actions=None,
|
||||||
insufficient_data_actions=None,
|
insufficient_data_actions=None,
|
||||||
@ -748,7 +709,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
resp = self.post_json('/alarms', params=json, status=400,
|
resp = self.post_json('/alarms', params=json, status=400,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
alarms = list(self.alarm_conn.get_alarms())
|
||||||
self.assertEqual(4, len(alarms))
|
self.assertEqual(3, len(alarms))
|
||||||
self.assertEqual(error_message,
|
self.assertEqual(error_message,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
|
|
||||||
@ -799,7 +760,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
self.post_json('/alarms', params=json, status=201,
|
self.post_json('/alarms', params=json, status=201,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
alarms = list(self.alarm_conn.get_alarms())
|
||||||
self.assertEqual(5, len(alarms))
|
self.assertEqual(4, len(alarms))
|
||||||
for alarm in alarms:
|
for alarm in alarms:
|
||||||
if alarm.name == 'added_alarm_defaults':
|
if alarm.name == 'added_alarm_defaults':
|
||||||
for key in to_check:
|
for key in to_check:
|
||||||
@ -1655,18 +1616,18 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_delete_alarm(self):
|
def test_delete_alarm(self):
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
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'],
|
resp = self.delete('/alarms/%s' % data[0]['alarm_id'],
|
||||||
headers=self.auth_headers,
|
headers=self.auth_headers,
|
||||||
status=204)
|
status=204)
|
||||||
self.assertEqual(b'', resp.body)
|
self.assertEqual(b'', resp.body)
|
||||||
alarms = list(self.alarm_conn.get_alarms())
|
alarms = list(self.alarm_conn.get_alarms())
|
||||||
self.assertEqual(3, len(alarms))
|
self.assertEqual(2, len(alarms))
|
||||||
|
|
||||||
def test_get_state_alarm(self):
|
def test_get_state_alarm(self):
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
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'],
|
resp = self.get_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
@ -1674,7 +1635,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_set_state_alarm(self):
|
def test_set_state_alarm(self):
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
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'],
|
resp = self.put_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||||
headers=self.auth_headers,
|
headers=self.auth_headers,
|
||||||
@ -1686,7 +1647,7 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
|
|
||||||
def test_set_invalid_state_alarm(self):
|
def test_set_invalid_state_alarm(self):
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
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'],
|
self.put_json('/alarms/%s/state' % data[0]['alarm_id'],
|
||||||
headers=self.auth_headers,
|
headers=self.auth_headers,
|
||||||
@ -2443,383 +2404,6 @@ class TestAlarmsRuleThreshold(TestAlarmsBase):
|
|||||||
self.fail("Alarm not found")
|
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):
|
class TestAlarmsRuleGnocchi(TestAlarmsBase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -3313,11 +2897,11 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
data = self.get_json('/alarms?sort=name:desc',
|
data = self.get_json('/alarms?sort=name:desc',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
names = [a['name'] for a in data]
|
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',
|
data = self.get_json('/alarms?sort=name:asc',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
names = [a['name'] for a in data]
|
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):
|
def test_sort_by_severity_with_its_value(self):
|
||||||
if self.engine != "mysql":
|
if self.engine != "mysql":
|
||||||
@ -3325,12 +2909,12 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
data = self.get_json('/alarms?sort=severity:asc',
|
data = self.get_json('/alarms?sort=severity:asc',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
severities = [a['severity'] for a in data]
|
severities = [a['severity'] for a in data]
|
||||||
self.assertEqual(['low', 'moderate', 'critical', 'critical'],
|
self.assertEqual(['moderate', 'critical', 'critical'],
|
||||||
severities)
|
severities)
|
||||||
data = self.get_json('/alarms?sort=severity:desc',
|
data = self.get_json('/alarms?sort=severity:desc',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
severities = [a['severity'] for a in data]
|
severities = [a['severity'] for a in data]
|
||||||
self.assertEqual(['critical', 'critical', 'moderate', 'low'],
|
self.assertEqual(['critical', 'critical', 'moderate'],
|
||||||
severities)
|
severities)
|
||||||
|
|
||||||
def test_pagination_query_limit(self):
|
def test_pagination_query_limit(self):
|
||||||
@ -3345,17 +2929,17 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
def test_pagination_query_marker(self):
|
def test_pagination_query_marker(self):
|
||||||
data = self.get_json('/alarms?sort=name:desc',
|
data = self.get_json('/alarms?sort=name:desc',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
self.assertEqual(4, len(data))
|
self.assertEqual(3, len(data))
|
||||||
alarm_ids = [a['alarm_id'] for a in data]
|
alarm_ids = [a['alarm_id'] for a in data]
|
||||||
names = [a['name'] 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])
|
marker_url = ('/alarms?sort=name:desc&marker=%s' % alarm_ids[1])
|
||||||
data = self.get_json(marker_url, headers=self.auth_headers)
|
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]
|
new_alarm_ids = [a['alarm_id'] for a in data]
|
||||||
self.assertEqual(alarm_ids[2:], new_alarm_ids)
|
self.assertEqual(alarm_ids[2:], new_alarm_ids)
|
||||||
new_names = [a['name'] for a in data]
|
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):
|
def test_pagination_query_multiple_sorts(self):
|
||||||
new_alarms = default_alarms(self.auth_headers)
|
new_alarms = default_alarms(self.auth_headers)
|
||||||
@ -3363,11 +2947,11 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
a_id[0].alarm_id = a_id[1]
|
a_id[0].alarm_id = a_id[1]
|
||||||
self.alarm_conn.create_alarm(a_id[0])
|
self.alarm_conn.create_alarm(a_id[0])
|
||||||
data = self.get_json('/alarms', headers=self.auth_headers)
|
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'
|
sort_url = '/alarms?sort=name:desc&sort=alarm_id:asc'
|
||||||
data = self.get_json(sort_url, headers=self.auth_headers)
|
data = self.get_json(sort_url, headers=self.auth_headers)
|
||||||
name_ids = [(a['name'], a['alarm_id']) for a in data]
|
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'),
|
('name3', 'g'), ('name2', 'b'), ('name2', 'f'),
|
||||||
('name1', 'a'), ('name1', 'e')]
|
('name1', 'a'), ('name1', 'e')]
|
||||||
self.assertEqual(expected, name_ids)
|
self.assertEqual(expected, name_ids)
|
||||||
@ -3392,7 +2976,7 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
data = self.get_json('/alarms?sort=name',
|
data = self.get_json('/alarms?sort=name',
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers)
|
||||||
names = [a['name'] for a in data]
|
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):
|
def test_pagination_query_history_data(self):
|
||||||
for i in moves.xrange(10):
|
for i in moves.xrange(10):
|
||||||
@ -3403,66 +2987,3 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
key=lambda d: (d['event_id'], d['timestamp']),
|
key=lambda d: (d['event_id'], d['timestamp']),
|
||||||
reverse=True)
|
reverse=True)
|
||||||
self.assertEqual(sorted_data, data)
|
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()
|
self.add_some_alarms()
|
||||||
alarms = list(self.alarm_conn.get_alarms(alarm_type='threshold'))
|
alarms = list(self.alarm_conn.get_alarms(alarm_type='threshold'))
|
||||||
self.assertEqual(3, len(alarms))
|
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):
|
def test_list_excluded_by_name(self):
|
||||||
self.add_some_alarms()
|
self.add_some_alarms()
|
||||||
|
@ -92,21 +92,3 @@ class TelemetryAlarmingAPITest(base.BaseAlarmingTest):
|
|||||||
# Get alarm state and verify
|
# Get alarm state and verify
|
||||||
state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
|
state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
|
||||||
self.assertEqual(new_state, state.data)
|
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
|
.. autotype:: aodh.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.combination.AlarmCombinationRule
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule
|
.. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
deprecations:
|
||||||
|
- The deprecated combination alarms support have been removed.
|
@ -80,7 +80,6 @@ aodh.storage =
|
|||||||
|
|
||||||
aodh.alarm.rule =
|
aodh.alarm.rule =
|
||||||
threshold = aodh.api.controllers.v2.alarm_rules.threshold:AlarmThresholdRule
|
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_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_metrics_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricsByIdLookupRule
|
||||||
gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule
|
gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule
|
||||||
@ -89,7 +88,6 @@ aodh.alarm.rule =
|
|||||||
|
|
||||||
aodh.evaluator =
|
aodh.evaluator =
|
||||||
threshold = aodh.evaluator.threshold:ThresholdEvaluator
|
threshold = aodh.evaluator.threshold:ThresholdEvaluator
|
||||||
combination = aodh.evaluator.combination:CombinationEvaluator
|
|
||||||
gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator
|
gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator
|
||||||
gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator
|
gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator
|
||||||
gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator
|
gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator
|
||||||
@ -114,7 +112,6 @@ console_scripts =
|
|||||||
aodh-evaluator = aodh.cmd.alarm:evaluator
|
aodh-evaluator = aodh.cmd.alarm:evaluator
|
||||||
aodh-notifier = aodh.cmd.alarm:notifier
|
aodh-notifier = aodh.cmd.alarm:notifier
|
||||||
aodh-listener = aodh.cmd.alarm:listener
|
aodh-listener = aodh.cmd.alarm:listener
|
||||||
aodh-combination-alarm-conversion = aodh.cmd.alarm_conversion:conversion
|
|
||||||
|
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
aodh = aodh.opts:list_opts
|
aodh = aodh.opts:list_opts
|
||||||
|
Loading…
Reference in New Issue
Block a user