diff --git a/aodh/api/__init__.py b/aodh/api/__init__.py index c6c41abf9..f2a76d59c 100644 --- a/aodh/api/__init__.py +++ b/aodh/api/__init__.py @@ -27,11 +27,4 @@ OPTS = [ 'auth_mode', default="keystone", help="Authentication mode to use. Unset to disable authentication"), - cfg.BoolOpt('enable_combination_alarms', - default=False, - help="Enable deprecated combination alarms.", - deprecated_for_removal=True, - deprecated_reason="Combination alarms are deprecated. " - "This option and combination alarms will be " - "removed in Aodh 5.0."), ] diff --git a/aodh/api/controllers/v2/alarm_rules/combination.py b/aodh/api/controllers/v2/alarm_rules/combination.py deleted file mode 100644 index a128e5070..000000000 --- a/aodh/api/controllers/v2/alarm_rules/combination.py +++ /dev/null @@ -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']) diff --git a/aodh/api/controllers/v2/alarms.py b/aodh/api/controllers/v2/alarms.py index 8810b1fff..e4360738e 100644 --- a/aodh/api/controllers/v2/alarms.py +++ b/aodh/api/controllers/v2/alarms.py @@ -39,7 +39,6 @@ from wsme import types as wtypes import wsmeext.pecan as wsme_pecan import aodh -from aodh.api.controllers.v2.alarm_rules import combination from aodh.api.controllers.v2 import base from aodh.api.controllers.v2 import utils as v2_utils from aodh.api import rbac @@ -184,13 +183,7 @@ ACTIONS_SCHEMA = extension.ExtensionManager( class Alarm(base.Base): - """Representation of an alarm. - - .. note:: - combination_rule and threshold_rule are mutually exclusive. The *type* - of the alarm should be set to *threshold* or *combination* and the - appropriate rule should be filled. - """ + """Representation of an alarm.""" alarm_id = wtypes.text "The UUID of the alarm" @@ -289,10 +282,6 @@ class Alarm(base.Base): @staticmethod def check_rule(alarm): - if (not pecan.request.cfg.api.enable_combination_alarms - and alarm.type == 'combination'): - raise base.ClientSideError("Unavailable alarm type") - rule = '%s_rule' % alarm.type if getattr(alarm, rule) in (wtypes.Unset, None): error = _("%(rule)s must be set for %(type)s" @@ -354,7 +343,7 @@ class Alarm(base.Base): return cls(alarm_id=None, name="SwiftObjectAlarm", description="An alarm", - type='combination', + type='threshold', time_constraints=[AlarmTimeConstraint.sample().as_dict()], user_id="c96c887c216949acbdfbd8b494863567", project_id="c96c887c216949acbdfbd8b494863567", @@ -367,7 +356,6 @@ class Alarm(base.Base): alarm_actions=["http://site:8000/alarm"], insufficient_data_actions=["http://site:8000/nodata"], repeat_actions=False, - combination_rule=combination.AlarmCombinationRule.sample(), ) def as_dict(self, db_model): diff --git a/aodh/api/controllers/v2/utils.py b/aodh/api/controllers/v2/utils.py index dcdd99e9e..70475c85e 100644 --- a/aodh/api/controllers/v2/utils.py +++ b/aodh/api/controllers/v2/utils.py @@ -37,8 +37,6 @@ def get_auth_project(on_behalf_of=None): # we must ensure for: # - threshold alarm, that an implicit query constraint on project_id is # added so that admin-level visibility on statistics is not leaked - # - combination alarm, that alarm ids verification is scoped to - # alarms owned by the alarm project. # Hence, for null auth_project (indicating admin-ness) we check if # the creating tenant differs from the tenant on whose behalf the # alarm is being created diff --git a/aodh/cmd/alarm_conversion.py b/aodh/cmd/alarm_conversion.py deleted file mode 100644 index 971725bc3..000000000 --- a/aodh/cmd/alarm_conversion.py +++ /dev/null @@ -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) diff --git a/aodh/evaluator/combination.py b/aodh/evaluator/combination.py deleted file mode 100644 index 87b5cc76d..000000000 --- a/aodh/evaluator/combination.py +++ /dev/null @@ -1,116 +0,0 @@ -# -# Copyright 2013 eNovance -# -# 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) diff --git a/aodh/tests/functional/api/v2/test_alarm_scenarios.py b/aodh/tests/functional/api/v2/test_alarm_scenarios.py index 328124aa3..c7b1cbff9 100644 --- a/aodh/tests/functional/api/v2/test_alarm_scenarios.py +++ b/aodh/tests/functional/api/v2/test_alarm_scenarios.py @@ -26,7 +26,6 @@ from six import moves import webtest from aodh.api import app -from aodh.cmd import alarm_conversion from aodh import messaging from aodh.storage import models from aodh.tests import constants @@ -116,27 +115,7 @@ def default_alarms(auth_headers): 'op': 'eq', 'value': auth_headers['X-Project-Id']} ]), - ), - models.Alarm(name='name4', - type='combination', - enabled=True, - alarm_id='d', - description='d', - state='insufficient data', - severity='low', - state_timestamp=constants.MIN_DATETIME, - timestamp=constants.MIN_DATETIME, - ok_actions=[], - insufficient_data_actions=[], - alarm_actions=[], - repeat_actions=False, - user_id=auth_headers['X-User-Id'], - project_id=auth_headers['X-Project-Id'], - time_constraints=[], - rule=dict(alarm_ids=['a', 'b'], - operator='or'), - ), - ] + )] class TestAlarmsBase(v2.FunctionalTest): @@ -199,15 +178,12 @@ class TestAlarms(TestAlarmsBase): def test_list_alarms(self): data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(4, len(data)) - self.assertEqual(set(['name1', 'name2', 'name3', 'name4']), + self.assertEqual(3, len(data)) + self.assertEqual(set(['name1', 'name2', 'name3']), set(r['name'] for r in data)) self.assertEqual(set(['meter.test', 'meter.mine']), set(r['threshold_rule']['meter_name'] for r in data if 'threshold_rule' in r)) - self.assertEqual(set(['or']), - set(r['combination_rule']['operator'] - for r in data if 'combination_rule' in r)) def test_alarms_query_with_timestamp(self): date_time = datetime.datetime(2012, 7, 2, 10, 41) @@ -241,10 +217,10 @@ class TestAlarms(TestAlarmsBase): def test_alarms_query_with_state(self): alarm = models.Alarm(name='disabled', - type='combination', + type='threshold', enabled=False, - alarm_id='d', - description='d', + alarm_id='c', + description='c', state='ok', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, @@ -255,7 +231,17 @@ class TestAlarms(TestAlarmsBase): user_id=self.auth_headers['X-User-Id'], project_id=self.auth_headers['X-Project-Id'], time_constraints=[], - rule=dict(alarm_ids=['a', 'b'], operator='or'), + rule=dict(comparison_operator='gt', + threshold=3.0, + statistic='avg', + evaluation_periods=60, + period=1, + meter_name='meter.mine', + query=[ + {'field': 'project_id', + 'op': 'eq', 'value': + self.auth_headers['X-Project-Id']} + ]), severity='critical') self.alarm_conn.update_alarm(alarm) resp = self.get_json('/alarms', @@ -308,10 +294,10 @@ class TestAlarms(TestAlarmsBase): def test_get_alarm_disabled(self): alarm = models.Alarm(name='disabled', - type='combination', + type='threshold', enabled=False, - alarm_id='d', - description='d', + alarm_id='c', + description='c', state='insufficient data', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, @@ -322,7 +308,17 @@ class TestAlarms(TestAlarmsBase): user_id=self.auth_headers['X-User-Id'], project_id=self.auth_headers['X-Project-Id'], time_constraints=[], - rule=dict(alarm_ids=['a', 'b'], operator='or'), + rule=dict(comparison_operator='gt', + threshold=3.0, + statistic='avg', + evaluation_periods=60, + period=1, + meter_name='meter.mine', + query=[ + {'field': 'project_id', + 'op': 'eq', 'value': + self.auth_headers['X-Project-Id']} + ]), severity='critical') self.alarm_conn.update_alarm(alarm) @@ -366,7 +362,7 @@ class TestAlarms(TestAlarmsBase): q=[{'field': field, 'op': 'eq', 'value': project}]) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) _test('project') _test('project_id') @@ -433,11 +429,6 @@ class TestAlarms(TestAlarmsBase): 'meter_name': 'ameter', } }, - 'combination_rule/alarm_ids': { - 'name': 'missing alarm_ids', - 'type': 'combination', - 'combination_rule': {} - } } for field, json in six.iteritems(jsons): resp = self.post_json('/alarms', params=json, expect_errors=True, @@ -447,7 +438,7 @@ class TestAlarms(TestAlarmsBase): % field.split('/', 1)[-1], resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_time_constraint_start(self): json = { @@ -468,7 +459,7 @@ class TestAlarms(TestAlarmsBase): self.post_json('/alarms', params=json, expect_errors=True, status=400, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_duplicate_time_constraint_name(self): json = { @@ -497,7 +488,7 @@ class TestAlarms(TestAlarmsBase): "Time constraint names must be unique for a given alarm.", resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_alarm_null_time_constraint(self): json = { @@ -531,7 +522,7 @@ class TestAlarms(TestAlarmsBase): self.post_json('/alarms', params=json, expect_errors=True, status=400, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_time_constraint_timezone(self): json = { @@ -553,7 +544,7 @@ class TestAlarms(TestAlarmsBase): self.post_json('/alarms', params=json, expect_errors=True, status=400, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_period(self): json = { @@ -571,14 +562,13 @@ class TestAlarms(TestAlarmsBase): self.post_json('/alarms', params=json, expect_errors=True, status=400, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_null_rule(self): json = { 'name': 'added_alarm_invalid_threshold_rule', 'type': 'threshold', 'threshold_rule': None, - 'combination_rule': None, } resp = self.post_json('/alarms', params=json, expect_errors=True, status=400, headers=self.auth_headers) @@ -604,7 +594,7 @@ class TestAlarms(TestAlarmsBase): self.assertIn(expected_err_msg, resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_input_severity(self): json = { @@ -625,7 +615,7 @@ class TestAlarms(TestAlarmsBase): self.assertIn(expected_err_msg, resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_input_type(self): json = { @@ -646,7 +636,7 @@ class TestAlarms(TestAlarmsBase): self.assertIn(expected_err_msg, resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_input_enabled_str(self): json = { @@ -666,7 +656,7 @@ class TestAlarms(TestAlarmsBase): self.assertIn(expected_err_msg, resp.json['error_message']['faultstring']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) def test_post_invalid_alarm_input_enabled_int(self): json = { @@ -684,37 +674,8 @@ class TestAlarms(TestAlarmsBase): headers=self.auth_headers) self.assertFalse(resp.json['enabled']) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(5, len(alarms)) - - def test_post_invalid_alarm_have_multiple_rules(self): - json = { - 'name': 'added_alarm', - 'type': 'threshold', - 'threshold_rule': { - 'meter_name': 'ameter', - 'query': [{'field': 'meter', - 'value': 'ameter'}], - 'comparison_operator': 'gt', - 'threshold': 2.0, - }, - 'combination_rule': { - 'alarm_ids': ['a', 'b'], - - } - } - resp = self.post_json('/alarms', params=json, expect_errors=True, - status=400, headers=self.auth_headers) - alarms = list(self.alarm_conn.get_alarms()) self.assertEqual(4, len(alarms)) - # threshold_rule and combination_rule order is not - # predictable so it is not possible to do an exact match - # here - error_faultstring = resp.json['error_message']['faultstring'] - for expected_string in ['threshold_rule', 'combination_rule', - 'cannot be set at the same time']: - self.assertIn(expected_string, error_faultstring) - def _do_post_alarm_invalid_action(self, ok_actions=None, alarm_actions=None, insufficient_data_actions=None, @@ -748,7 +709,7 @@ class TestAlarms(TestAlarmsBase): resp = self.post_json('/alarms', params=json, status=400, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) + self.assertEqual(3, len(alarms)) self.assertEqual(error_message, resp.json['error_message']['faultstring']) @@ -799,7 +760,7 @@ class TestAlarms(TestAlarmsBase): self.post_json('/alarms', params=json, status=201, headers=self.auth_headers) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(5, len(alarms)) + self.assertEqual(4, len(alarms)) for alarm in alarms: if alarm.name == 'added_alarm_defaults': for key in to_check: @@ -1655,18 +1616,18 @@ class TestAlarms(TestAlarmsBase): def test_delete_alarm(self): data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(4, len(data)) + self.assertEqual(3, len(data)) resp = self.delete('/alarms/%s' % data[0]['alarm_id'], headers=self.auth_headers, status=204) self.assertEqual(b'', resp.body) alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(3, len(alarms)) + self.assertEqual(2, len(alarms)) def test_get_state_alarm(self): data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(4, len(data)) + self.assertEqual(3, len(data)) resp = self.get_json('/alarms/%s/state' % data[0]['alarm_id'], headers=self.auth_headers) @@ -1674,7 +1635,7 @@ class TestAlarms(TestAlarmsBase): def test_set_state_alarm(self): data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(4, len(data)) + self.assertEqual(3, len(data)) resp = self.put_json('/alarms/%s/state' % data[0]['alarm_id'], headers=self.auth_headers, @@ -1686,7 +1647,7 @@ class TestAlarms(TestAlarmsBase): def test_set_invalid_state_alarm(self): data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(4, len(data)) + self.assertEqual(3, len(data)) self.put_json('/alarms/%s/state' % data[0]['alarm_id'], headers=self.auth_headers, @@ -2443,383 +2404,6 @@ class TestAlarmsRuleThreshold(TestAlarmsBase): self.fail("Alarm not found") -class TestAlarmsRuleCombination(TestAlarmsBase): - - def setUp(self): - super(TestAlarmsRuleCombination, self).setUp() - self.CONF.set_override("enable_combination_alarms", True, "api") - for alarm in default_alarms(self.auth_headers): - self.alarm_conn.create_alarm(alarm) - - def test_get_alarm_combination(self): - alarms = self.get_json('/alarms', - headers=self.auth_headers, - q=[{'field': 'name', - 'value': 'name4', - }]) - self.assertEqual('name4', alarms[0]['name']) - self.assertEqual(['a', 'b'], - alarms[0]['combination_rule']['alarm_ids']) - self.assertEqual('or', alarms[0]['combination_rule']['operator']) - - one = self.get_json('/alarms/%s' % alarms[0]['alarm_id'], - headers=self.auth_headers) - self.assertEqual('name4', one['name']) - self.assertEqual(['a', 'b'], - alarms[0]['combination_rule']['alarm_ids']) - self.assertEqual('or', alarms[0]['combination_rule']['operator']) - self.assertEqual(alarms[0]['alarm_id'], one['alarm_id']) - self.assertEqual(alarms[0]['repeat_actions'], one['repeat_actions']) - - def test_post_alarm_combination(self): - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'and', - } - } - self.post_json('/alarms', params=json, status=201, - headers=self.auth_headers) - alarms = list(self.alarm_conn.get_alarms(enabled=False)) - self.assertEqual(1, len(alarms)) - if alarms[0].name == 'added_alarm': - for key in json: - if key.endswith('_rule'): - storage_key = 'rule' - else: - storage_key = key - self.assertEqual(json[key], getattr(alarms[0], storage_key)) - else: - self.fail("Alarm not found") - - def test_post_invalid_alarm_combination(self): - """Test that post a combination alarm with a not existing alarm id.""" - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['not_exists', - 'b'], - 'operator': 'and', - } - } - self.post_json('/alarms', params=json, status=404, - headers=self.auth_headers) - alarms = list(self.alarm_conn.get_alarms(enabled=False)) - self.assertEqual(0, len(alarms)) - - def test_post_invalid_combination_alarm_input_operator(self): - json = { - 'enabled': False, - 'name': 'alarm6', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'bad_operator', - } - } - resp = self.post_json('/alarms', params=json, expect_errors=True, - status=400, headers=self.auth_headers) - expected_err_msg = ("Invalid input for field/attribute" - " operator." - " Value: 'bad_operator'.") - self.assertIn(expected_err_msg, - resp.json['error_message']['faultstring']) - alarms = list(self.alarm_conn.get_alarms()) - self.assertEqual(4, len(alarms)) - - def test_post_combination_alarm_as_user_with_unauthorized_alarm(self): - """Test posting a combination alarm. - - Test that post a combination alarm as normal user/project - with an alarm_id unauthorized for this project/user - """ - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'and', - } - } - an_other_user_auth = {'X-User-Id': uuidutils.generate_uuid(), - 'X-Project-Id': uuidutils.generate_uuid()} - resp = self.post_json('/alarms', params=json, status=404, - headers=an_other_user_auth) - self.assertEqual("Alarm a not found in project " - "%s" % - an_other_user_auth['X-Project-Id'], - jsonutils.loads(resp.body)['error_message'] - ['faultstring']) - - def test_post_combination_alarm_as_admin_on_behalf_of_an_other_user(self): - """Test posting a combination alarm. - - Test that post a combination alarm as admin on behalf of an other - user/project with an alarm_id unauthorized for this project/user - """ - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'user_id': 'auseridthatisnotmine', - 'project_id': 'aprojectidthatisnotmine', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'and', - } - } - - headers = {} - headers.update(self.auth_headers) - headers['X-Roles'] = 'admin' - resp = self.post_json('/alarms', params=json, status=404, - headers=headers) - self.assertEqual("Alarm a not found in project " - "aprojectidthatisnotmine", - jsonutils.loads(resp.body)['error_message'] - ['faultstring']) - - def test_post_combination_alarm_with_reasonable_description(self): - """Test posting a combination alarm. - - Test that post a combination alarm with two blanks around the - operator in alarm description. - """ - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'and', - } - } - self.post_json('/alarms', params=json, status=201, - headers=self.auth_headers) - alarms = list(self.alarm_conn.get_alarms(enabled=False)) - self.assertEqual(1, len(alarms)) - self.assertEqual(u'Combined state of alarms a and b', - alarms[0].description) - - def _do_post_combination_alarm_as_admin_success(self, owner_is_set): - """Test posting a combination alarm. - - Test that post a combination alarm as admin on behalf of nobody - with an alarm_id of someone else, with owner set or not - """ - json = { - 'enabled': False, - 'name': 'added_alarm', - 'state': 'ok', - 'type': 'combination', - 'ok_actions': ['http://something/ok'], - 'alarm_actions': ['http://something/alarm'], - 'insufficient_data_actions': ['http://something/no'], - 'repeat_actions': True, - 'combination_rule': { - 'alarm_ids': ['a', - 'b'], - 'operator': 'and', - } - } - an_other_admin_auth = {'X-User-Id': uuidutils.generate_uuid(), - 'X-Project-Id': uuidutils.generate_uuid(), - 'X-Roles': 'admin'} - if owner_is_set: - json['project_id'] = an_other_admin_auth['X-Project-Id'] - json['user_id'] = an_other_admin_auth['X-User-Id'] - - self.post_json('/alarms', params=json, status=201, - headers=an_other_admin_auth) - alarms = list(self.alarm_conn.get_alarms(enabled=False)) - if alarms[0].name == 'added_alarm': - for key in json: - if key.endswith('_rule'): - storage_key = 'rule' - else: - storage_key = key - self.assertEqual(json[key], getattr(alarms[0], storage_key)) - else: - self.fail("Alarm not found") - - def test_post_combination_alarm_as_admin_success_owner_unset(self): - self._do_post_combination_alarm_as_admin_success(False) - - def test_post_combination_alarm_as_admin_success_owner_set(self): - self._do_post_combination_alarm_as_admin_success(True) - - def test_post_alarm_combination_duplicate_alarm_ids(self): - """Test combination alarm doesn't allow duplicate alarm ids.""" - json_body = { - 'name': 'dup_alarm_id', - 'type': 'combination', - 'combination_rule': { - 'alarm_ids': ['a', 'a', 'd', 'a', 'c', 'c', 'b'], - } - } - self.post_json('/alarms', params=json_body, status=201, - headers=self.auth_headers) - alarms = list(self.alarm_conn.get_alarms(name='dup_alarm_id')) - self.assertEqual(1, len(alarms)) - self.assertEqual(['a', 'd', 'c', 'b'], - alarms[0].rule.get('alarm_ids')) - - def _test_post_alarm_combination_rule_less_than_two_alarms(self, - alarm_ids=None): - json_body = { - 'name': 'one_alarm_in_combination_rule', - 'type': 'combination', - 'combination_rule': { - 'alarm_ids': alarm_ids or [] - } - } - - resp = self.post_json('/alarms', params=json_body, - expect_errors=True, status=400, - headers=self.auth_headers) - self.assertEqual( - 'Alarm combination rule should contain at' - ' least two different alarm ids.', - resp.json['error_message']['faultstring']) - - def test_post_alarm_combination_rule_with_no_alarm(self): - self._test_post_alarm_combination_rule_less_than_two_alarms() - - def test_post_alarm_combination_rule_with_one_alarm(self): - self._test_post_alarm_combination_rule_less_than_two_alarms(['a']) - - def test_post_alarm_combination_rule_with_two_same_alarms(self): - self._test_post_alarm_combination_rule_less_than_two_alarms(['a', - 'a']) - - def test_put_alarm_combination_cannot_specify_itself(self): - json = { - 'name': 'name4', - 'type': 'combination', - 'combination_rule': { - 'alarm_ids': ['d', 'a'], - } - } - - data = self.get_json('/alarms', - headers=self.auth_headers, - q=[{'field': 'name', - 'value': 'name4', - }]) - self.assertEqual(1, len(data)) - alarm_id = data[0]['alarm_id'] - - resp = self.put_json('/alarms/%s' % alarm_id, - expect_errors=True, status=400, - params=json, - headers=self.auth_headers) - - msg = 'Cannot specify alarm %s itself in combination rule' % alarm_id - self.assertEqual(msg, resp.json['error_message']['faultstring']) - - def _test_put_alarm_combination_rule_less_than_two_alarms(self, - alarm_ids=None): - json_body = { - 'name': 'name4', - 'type': 'combination', - 'combination_rule': { - 'alarm_ids': alarm_ids or [] - } - } - - data = self.get_json('/alarms', - headers=self.auth_headers, - q=[{'field': 'name', - 'value': 'name4', - }]) - self.assertEqual(1, len(data)) - alarm_id = data[0]['alarm_id'] - - resp = self.put_json('/alarms/%s' % alarm_id, params=json_body, - expect_errors=True, status=400, - headers=self.auth_headers) - self.assertEqual( - 'Alarm combination rule should contain at' - ' least two different alarm ids.', - resp.json['error_message']['faultstring']) - - def test_put_alarm_combination_rule_with_no_alarm(self): - self._test_put_alarm_combination_rule_less_than_two_alarms() - - def test_put_alarm_combination_rule_with_one_alarm(self): - self._test_put_alarm_combination_rule_less_than_two_alarms(['a']) - - def test_put_alarm_combination_rule_with_two_same_alarm_itself(self): - self._test_put_alarm_combination_rule_less_than_two_alarms(['d', - 'd']) - - def test_put_combination_alarm_with_duplicate_ids(self): - """Test combination alarm doesn't allow duplicate alarm ids.""" - alarms = self.get_json('/alarms', - headers=self.auth_headers, - q=[{'field': 'name', - 'value': 'name4', - }]) - self.assertEqual(1, len(alarms)) - alarm_id = alarms[0]['alarm_id'] - - json_body = { - 'name': 'name4', - 'type': 'combination', - 'combination_rule': { - 'alarm_ids': ['c', 'a', 'b', 'a', 'c', 'b'], - } - } - self.put_json('/alarms/%s' % alarm_id, - params=json_body, status=200, - headers=self.auth_headers) - - alarms = list(self.alarm_conn.get_alarms(alarm_id=alarm_id)) - self.assertEqual(1, len(alarms)) - self.assertEqual(['c', 'a', 'b'], alarms[0].rule.get('alarm_ids')) - - class TestAlarmsRuleGnocchi(TestAlarmsBase): def setUp(self): @@ -3313,11 +2897,11 @@ class TestPaginationQuery(TestAlarmsBase): data = self.get_json('/alarms?sort=name:desc', headers=self.auth_headers) names = [a['name'] for a in data] - self.assertEqual(['name4', 'name3', 'name2', 'name1'], names) + self.assertEqual(['name3', 'name2', 'name1'], names) data = self.get_json('/alarms?sort=name:asc', headers=self.auth_headers) names = [a['name'] for a in data] - self.assertEqual(['name1', 'name2', 'name3', 'name4'], names) + self.assertEqual(['name1', 'name2', 'name3'], names) def test_sort_by_severity_with_its_value(self): if self.engine != "mysql": @@ -3325,12 +2909,12 @@ class TestPaginationQuery(TestAlarmsBase): data = self.get_json('/alarms?sort=severity:asc', headers=self.auth_headers) severities = [a['severity'] for a in data] - self.assertEqual(['low', 'moderate', 'critical', 'critical'], + self.assertEqual(['moderate', 'critical', 'critical'], severities) data = self.get_json('/alarms?sort=severity:desc', headers=self.auth_headers) severities = [a['severity'] for a in data] - self.assertEqual(['critical', 'critical', 'moderate', 'low'], + self.assertEqual(['critical', 'critical', 'moderate'], severities) def test_pagination_query_limit(self): @@ -3345,17 +2929,17 @@ class TestPaginationQuery(TestAlarmsBase): def test_pagination_query_marker(self): data = self.get_json('/alarms?sort=name:desc', headers=self.auth_headers) - self.assertEqual(4, len(data)) + self.assertEqual(3, len(data)) alarm_ids = [a['alarm_id'] for a in data] names = [a['name'] for a in data] - self.assertEqual(['name4', 'name3', 'name2', 'name1'], names) + self.assertEqual(['name3', 'name2', 'name1'], names) marker_url = ('/alarms?sort=name:desc&marker=%s' % alarm_ids[1]) data = self.get_json(marker_url, headers=self.auth_headers) - self.assertEqual(2, len(data)) + self.assertEqual(1, len(data)) new_alarm_ids = [a['alarm_id'] for a in data] self.assertEqual(alarm_ids[2:], new_alarm_ids) new_names = [a['name'] for a in data] - self.assertEqual(['name2', 'name1'], new_names) + self.assertEqual(['name1'], new_names) def test_pagination_query_multiple_sorts(self): new_alarms = default_alarms(self.auth_headers) @@ -3363,11 +2947,11 @@ class TestPaginationQuery(TestAlarmsBase): a_id[0].alarm_id = a_id[1] self.alarm_conn.create_alarm(a_id[0]) data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(8, len(data)) + self.assertEqual(6, len(data)) sort_url = '/alarms?sort=name:desc&sort=alarm_id:asc' data = self.get_json(sort_url, headers=self.auth_headers) name_ids = [(a['name'], a['alarm_id']) for a in data] - expected = [('name4', 'd'), ('name4', 'h'), ('name3', 'c'), + expected = [('name3', 'c'), ('name3', 'g'), ('name2', 'b'), ('name2', 'f'), ('name1', 'a'), ('name1', 'e')] self.assertEqual(expected, name_ids) @@ -3392,7 +2976,7 @@ class TestPaginationQuery(TestAlarmsBase): data = self.get_json('/alarms?sort=name', headers=self.auth_headers) names = [a['name'] for a in data] - self.assertEqual(['name1', 'name2', 'name3', 'name4'], names) + self.assertEqual(['name1', 'name2', 'name3'], names) def test_pagination_query_history_data(self): for i in moves.xrange(10): @@ -3403,66 +2987,3 @@ class TestPaginationQuery(TestAlarmsBase): key=lambda d: (d['event_id'], d['timestamp']), reverse=True) self.assertEqual(sorted_data, data) - - -class TestCombinationCompositeConversion(TestAlarmsBase): - def setUp(self): - super(TestCombinationCompositeConversion, self).setUp() - alarms = default_alarms(self.auth_headers) - for alarm in alarms: - self.alarm_conn.create_alarm(alarm) - com_parameters = alarms[3].as_dict() - com_parameters.update(dict(name='name5', alarm_id='e', description='e', - rule=dict(alarm_ids=['b', 'c'], - operator='and'))) - combin1 = models.Alarm(**com_parameters) - self.alarm_conn.create_alarm(combin1) - com_parameters.update(dict(name='name6', alarm_id='f', description='f', - rule=dict(alarm_ids=['d', 'e'], - operator='and'))) - combin2 = models.Alarm(**com_parameters) - self.alarm_conn.create_alarm(combin2) - - def test_conversion_without_combination_deletion(self): - data = self.get_json('/alarms', headers=self.auth_headers) - self.assertEqual(6, len(data)) - url = '/alarms?q.field=type&q.op=eq&q.value=combination' - combination_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(3, len(combination_alarms)) - test_args = alarm_conversion.get_parser().parse_args([]) - with mock.patch('__builtin__.raw_input', return_value='yes'): - with mock.patch('argparse.ArgumentParser.parse_args', - return_value=test_args): - alarm_conversion.conversion() - url = '/alarms?q.field=type&q.op=eq&q.value=composite' - composite_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(3, len(composite_alarms)) - url = '/alarms?q.field=type&q.op=eq&q.value=combination' - combination_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(3, len(combination_alarms)) - - def test_conversion_with_combination_deletion(self): - test_args = alarm_conversion.get_parser().parse_args( - ['--delete-combination-alarm', 'True']) - with mock.patch('__builtin__.raw_input', return_value='yes'): - with mock.patch('argparse.ArgumentParser.parse_args', - return_value=test_args): - alarm_conversion.conversion() - url = '/alarms?q.field=type&q.op=eq&q.value=composite' - composite_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(3, len(composite_alarms)) - url = '/alarms?q.field=type&q.op=eq&q.value=combination' - combination_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(0, len(combination_alarms)) - - def test_conversion_with_alarm_specified(self): - test_args = alarm_conversion.get_parser().parse_args( - ['--alarm-id', 'e']) - with mock.patch('__builtin__.raw_input', return_value='yes'): - with mock.patch('argparse.ArgumentParser.parse_args', - return_value=test_args): - alarm_conversion.conversion() - url = '/alarms?q.field=type&q.op=eq&q.value=composite' - composite_alarms = self.get_json(url, headers=self.auth_headers) - self.assertEqual(1, len(composite_alarms)) - self.assertEqual('From-combination: e', composite_alarms[0]['name']) diff --git a/aodh/tests/functional/storage/test_storage_scenarios.py b/aodh/tests/functional/storage/test_storage_scenarios.py index 7cf07116d..6d3776ca5 100644 --- a/aodh/tests/functional/storage/test_storage_scenarios.py +++ b/aodh/tests/functional/storage/test_storage_scenarios.py @@ -174,8 +174,6 @@ class AlarmTest(AlarmTestBase): self.add_some_alarms() alarms = list(self.alarm_conn.get_alarms(alarm_type='threshold')) self.assertEqual(3, len(alarms)) - alarms = list(self.alarm_conn.get_alarms(alarm_type='combination')) - self.assertEqual(0, len(alarms)) def test_list_excluded_by_name(self): self.add_some_alarms() diff --git a/aodh/tests/tempest/api/test_alarming_api.py b/aodh/tests/tempest/api/test_alarming_api.py index 05f463eae..3aa4203ff 100644 --- a/aodh/tests/tempest/api/test_alarming_api.py +++ b/aodh/tests/tempest/api/test_alarming_api.py @@ -92,21 +92,3 @@ class TelemetryAlarmingAPITest(base.BaseAlarmingTest): # Get alarm state and verify state = self.alarming_client.show_alarm_state(alarm['alarm_id']) self.assertEqual(new_state, state.data) - - @decorators.idempotent_id('08d7e45a-1344-4e5c-ba6f-f6cbb77f55ba') - @decorators.skip_because(bug='1585267') - def test_create_delete_alarm_with_combination_rule(self): - rule = {"alarm_ids": self.alarm_ids, - "operator": "or"} - # Verifies alarm create - alarm_name = data_utils.rand_name('combination_alarm') - body = self.alarming_client.create_alarm(name=alarm_name, - combination_rule=rule, - type='combination') - self.assertEqual(alarm_name, body['name']) - alarm_id = body['alarm_id'] - self.assertDictContainsSubset(rule, body['combination_rule']) - # Verify alarm delete - self.alarming_client.delete_alarm(alarm_id) - self.assertRaises(lib_exc.NotFound, - self.alarming_client.show_alarm, alarm_id) diff --git a/aodh/tests/unit/evaluator/test_combination.py b/aodh/tests/unit/evaluator/test_combination.py deleted file mode 100644 index 354ae66de..000000000 --- a/aodh/tests/unit/evaluator/test_combination.py +++ /dev/null @@ -1,376 +0,0 @@ -# -# Copyright 2013 eNovance -# -# 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) diff --git a/doc/source/webapi/v2.rst b/doc/source/webapi/v2.rst index bd748a1b3..6fffc6ee3 100644 --- a/doc/source/webapi/v2.rst +++ b/doc/source/webapi/v2.rst @@ -35,9 +35,6 @@ Alarms .. autotype:: aodh.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule :members: -.. autotype:: aodh.api.controllers.v2.alarm_rules.combination.AlarmCombinationRule - :members: - .. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule :members: diff --git a/releasenotes/notes/remove-combination-alarms-a1a53655f3f7d1d1.yaml b/releasenotes/notes/remove-combination-alarms-a1a53655f3f7d1d1.yaml new file mode 100644 index 000000000..7101a6b30 --- /dev/null +++ b/releasenotes/notes/remove-combination-alarms-a1a53655f3f7d1d1.yaml @@ -0,0 +1,3 @@ +--- +deprecations: + - The deprecated combination alarms support have been removed. diff --git a/setup.cfg b/setup.cfg index d67b561b1..39dd5ada8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,7 +79,6 @@ aodh.storage = aodh.alarm.rule = threshold = aodh.api.controllers.v2.alarm_rules.threshold:AlarmThresholdRule - combination = aodh.api.controllers.v2.alarm_rules.combination:AlarmCombinationRule gnocchi_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:MetricOfResourceRule gnocchi_aggregation_by_metrics_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricsByIdLookupRule gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule @@ -88,7 +87,6 @@ aodh.alarm.rule = aodh.evaluator = threshold = aodh.evaluator.threshold:ThresholdEvaluator - combination = aodh.evaluator.combination:CombinationEvaluator gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator @@ -113,7 +111,6 @@ console_scripts = aodh-evaluator = aodh.cmd.alarm:evaluator aodh-notifier = aodh.cmd.alarm:notifier aodh-listener = aodh.cmd.alarm:listener - aodh-combination-alarm-conversion = aodh.cmd.alarm_conversion:conversion oslo.config.opts = aodh = aodh.opts:list_opts