Remove deprecated combination alarms

Change-Id: I8977c69d0c73261782ddfcb295b7cfe1d76f9de3
This commit is contained in:
Julien Danjou 2017-02-05 18:50:08 +01:00
parent 021a3e1613
commit 1e63626e9c
13 changed files with 65 additions and 1295 deletions

View File

@ -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."),
] ]

View File

@ -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'])

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'])

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -0,0 +1,3 @@
---
deprecations:
- The deprecated combination alarms support have been removed.

View File

@ -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